mirror of
https://github.com/status-im/status-desktop.git
synced 2025-02-17 00:56:39 +00:00
feat(TransactionModal): introduce async validation for ENS names
This commit extends the AddressInput to perform ENS lookups when valid ENS values are entered. The lookup happens asynchronously, so we show a loading indicator as the request is happening. Closes #790
This commit is contained in:
parent
d64446f868
commit
729a2781f0
@ -5,6 +5,7 @@ import ../../status/libstatus/wallet as status_wallet
|
|||||||
import ../../status/libstatus/tokens
|
import ../../status/libstatus/tokens
|
||||||
import ../../status/libstatus/types
|
import ../../status/libstatus/types
|
||||||
import ../../status/libstatus/utils as status_utils
|
import ../../status/libstatus/utils as status_utils
|
||||||
|
import ../../status/ens as status_ens
|
||||||
import views/[asset_list, account_list, account_item, token_list, transaction_list, collectibles_list]
|
import views/[asset_list, account_list, account_item, token_list, transaction_list, collectibles_list]
|
||||||
|
|
||||||
QtObject:
|
QtObject:
|
||||||
@ -527,3 +528,11 @@ QtObject:
|
|||||||
proc wei2Token*(self: WalletView, wei: string, decimals: int): string {.slot.} =
|
proc wei2Token*(self: WalletView, wei: string, decimals: int): string {.slot.} =
|
||||||
return status_utils.wei2Token(wei, decimals)
|
return status_utils.wei2Token(wei, decimals)
|
||||||
|
|
||||||
|
proc resolveENS*(self: WalletView, ens: string) {.slot.} =
|
||||||
|
spawnAndSend(self, "ensResolved") do:
|
||||||
|
status_ens.owner(ens)
|
||||||
|
|
||||||
|
proc ensWasResolved*(self: WalletView, resolvedPubKey: string) {.signal.}
|
||||||
|
|
||||||
|
proc ensResolved(self: WalletView, pubKey: string) {.slot.} =
|
||||||
|
self.ensWasResolved(pubKey)
|
||||||
|
@ -236,11 +236,10 @@ ModalPopup {
|
|||||||
id: btnNext
|
id: btnNext
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
label: qsTr("Next")
|
label: qsTr("Next")
|
||||||
disabled: !stack.currentGroup.isValid
|
disabled: !stack.currentGroup.isValid || stack.currentGroup.isPending
|
||||||
onClicked: {
|
onClicked: {
|
||||||
const isValid = stack.currentGroup.validate()
|
const validity = stack.currentGroup.validate()
|
||||||
|
if (validity.isValid && !validity.isPending) {
|
||||||
if (stack.currentGroup.validate()) {
|
|
||||||
if (stack.isLastGroup) {
|
if (stack.isLastGroup) {
|
||||||
return root.sendTransaction()
|
return root.sendTransaction()
|
||||||
}
|
}
|
||||||
|
@ -70,6 +70,13 @@ QtObject {
|
|||||||
return /^0x[a-fA-F0-9]{40}$/.test(inputValue)
|
return /^0x[a-fA-F0-9]{40}$/.test(inputValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isValidEns(inputValue) {
|
||||||
|
const isEmail = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/.test(inputValue)
|
||||||
|
const isDomain = /(?:(?:(?<thld>[\w\-]*)(?:\.))?(?<sld>[\w\-]*))\.(?<tld>[\w\-]*)/.test(inputValue)
|
||||||
|
return isEmail || isDomain || inputValue.startsWith("@")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes trailing zeros from a string-representation of a number. Throws
|
* Removes trailing zeros from a string-representation of a number. Throws
|
||||||
* if parameter is not a string
|
* if parameter is not a string
|
||||||
|
@ -3,13 +3,16 @@ import QtQuick.Controls 2.13
|
|||||||
import QtQuick.Layouts 1.13
|
import QtQuick.Layouts 1.13
|
||||||
import QtGraphicalEffects 1.13
|
import QtGraphicalEffects 1.13
|
||||||
import "../imports"
|
import "../imports"
|
||||||
|
import "../shared"
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
property string validationError: "Error"
|
property string validationError: "Error"
|
||||||
|
property string ensAsyncValidationError: qsTr("ENS Username not found")
|
||||||
property alias label: inpAddress.label
|
property alias label: inpAddress.label
|
||||||
property string selectedAddress
|
property string selectedAddress
|
||||||
property var isValid: false
|
property var isValid: false
|
||||||
|
property bool isPending: false
|
||||||
|
|
||||||
height: inpAddress.height
|
height: inpAddress.height
|
||||||
|
|
||||||
@ -20,24 +23,37 @@ Item {
|
|||||||
isValid = false
|
isValid = false
|
||||||
}
|
}
|
||||||
|
|
||||||
function isValidEns(inputValue) {
|
|
||||||
// TODO: Check if the entered value resolves to an address. Long operation.
|
|
||||||
// Issue tracked: https://github.com/status-im/nim-status-client/issues/718
|
|
||||||
const isEmail = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/.test(inputValue)
|
|
||||||
const isDomain = /(?:(?:(?<thld>[\w\-]*)(?:\.))?(?<sld>[\w\-]*))\.(?<tld>[\w\-]*)/.test(inputValue)
|
|
||||||
return isEmail || isDomain
|
|
||||||
}
|
|
||||||
|
|
||||||
function validate(inputValue) {
|
function validate(inputValue) {
|
||||||
if (!inputValue) inputValue = selectedAddress
|
if (!inputValue) inputValue = selectedAddress
|
||||||
let isValid =
|
let isValid =
|
||||||
(inputValue && inputValue.startsWith("0x") && Utils.isValidAddress(inputValue)) ||
|
(inputValue && inputValue.startsWith("0x") && Utils.isValidAddress(inputValue) || Utils.isValidEns(inputValue))
|
||||||
isValidEns(inputValue)
|
|
||||||
inpAddress.validationError = isValid ? "" : validationError
|
inpAddress.validationError = isValid ? "" : validationError
|
||||||
root.isValid = isValid
|
root.isValid = isValid
|
||||||
return isValid
|
return isValid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property var validateAsync: Backpressure.debounce(inpAddress, 300, function (inputValue) {
|
||||||
|
root.isPending = true
|
||||||
|
var name = inputValue.startsWith("@") ? inputValue.substring(1) : inputValue
|
||||||
|
walletModel.resolveENS(name)
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: walletModel
|
||||||
|
onEnsWasResolved: {
|
||||||
|
root.isPending = false
|
||||||
|
if (resolvedPubKey === ""){
|
||||||
|
inpAddress.validationError = root.ensAsyncValidationError
|
||||||
|
root.isValid = false
|
||||||
|
} else {
|
||||||
|
root.isValid = true
|
||||||
|
root.selectedAddress = resolvedPubKey
|
||||||
|
inpAddress.validationError = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Input {
|
Input {
|
||||||
id: inpAddress
|
id: inpAddress
|
||||||
//% "eg. 0x1234 or ENS"
|
//% "eg. 0x1234 or ENS"
|
||||||
@ -64,7 +80,11 @@ Item {
|
|||||||
metrics.text = text
|
metrics.text = text
|
||||||
const isValid = root.validate(inputValue)
|
const isValid = root.validate(inputValue)
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
|
if (Utils.isValidAddress(inputValue)) {
|
||||||
root.selectedAddress = inputValue
|
root.selectedAddress = inputValue
|
||||||
|
} else {
|
||||||
|
Qt.callLater(root.validateAsync, inputValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TextMetrics {
|
TextMetrics {
|
||||||
@ -86,6 +106,22 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
sourceComponent: loadingIndicator
|
||||||
|
anchors.top: inpAddress.bottom
|
||||||
|
anchors.right: inpAddress.right
|
||||||
|
anchors.topMargin: Style.current.halfPadding
|
||||||
|
active: root.isPending
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: loadingIndicator
|
||||||
|
LoadingImage {
|
||||||
|
width: 12
|
||||||
|
height: 12
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import "../imports"
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
property var isValid: true
|
property var isValid: true
|
||||||
|
property var isPending: false
|
||||||
property var validate: function() {
|
property var validate: function() {
|
||||||
let isValid = true
|
let isValid = true
|
||||||
for (let i=0; i<children.length; i++) {
|
for (let i=0; i<children.length; i++) {
|
||||||
@ -12,9 +13,13 @@ Rectangle {
|
|||||||
if (component.hasOwnProperty("validate") && typeof component.validate === "function") {
|
if (component.hasOwnProperty("validate") && typeof component.validate === "function") {
|
||||||
isValid = component.validate()
|
isValid = component.validate()
|
||||||
}
|
}
|
||||||
|
if (component.hasOwnProperty("isPending")) {
|
||||||
|
isPending = component.isPending
|
||||||
|
}
|
||||||
}
|
}
|
||||||
root.isValid = isValid
|
root.isValid = isValid
|
||||||
return isValid
|
root.isPending = isPending
|
||||||
|
return { isValid, isPending }
|
||||||
}
|
}
|
||||||
color: Style.current.background
|
color: Style.current.background
|
||||||
function reset() {
|
function reset() {
|
||||||
@ -41,19 +46,28 @@ Rectangle {
|
|||||||
for (let i=0; i<children.length; i++) {
|
for (let i=0; i<children.length; i++) {
|
||||||
const component = children[i]
|
const component = children[i]
|
||||||
if (component.hasOwnProperty("isValid")) {
|
if (component.hasOwnProperty("isValid")) {
|
||||||
component.isValidChanged.connect(updateGroupValidity)
|
component.isValidChanged.connect(updateGroupValidityAndPendingStatus)
|
||||||
root.isValid = root.isValid && component.isValid // set the initial state
|
root.isValid = root.isValid && component.isValid // set the initial state
|
||||||
}
|
}
|
||||||
|
if (component.hasOwnProperty("isPending")) {
|
||||||
|
component.isPendingChanged.connect(updateGroupValidityAndPendingStatus)
|
||||||
|
root.isPending = component.isPending
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function updateGroupValidity() {
|
function updateGroupValidityAndPendingStatus() {
|
||||||
let isValid = true
|
let isValid = true
|
||||||
|
let isPending = false
|
||||||
for (let i=0; i<children.length; i++) {
|
for (let i=0; i<children.length; i++) {
|
||||||
const component = children[i]
|
const component = children[i]
|
||||||
if (component.hasOwnProperty("isValid")) {
|
if (component.hasOwnProperty("isValid")) {
|
||||||
isValid = isValid && component.isValid
|
isValid = isValid && component.isValid
|
||||||
}
|
}
|
||||||
|
if (component.hasOwnProperty("isPending")) {
|
||||||
|
isPending = component.isPending
|
||||||
|
}
|
||||||
}
|
}
|
||||||
root.isValid = isValid
|
root.isValid = isValid
|
||||||
|
root.isPending = isPending
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user