feat: Re-enable contact selection in the wallet send dialog

feat: Re-enable contact selection in the wallet send dialog

Selecting a ENS-verified contact will resolve it's ENS address so transactions can be sent directly to contacts from the wallet (if and only if they have ENS enabled for their account).

feat: add EnsResolver component that shows a loader and allows automated ENS resolution from within QML
This commit is contained in:
emizzle 2020-10-29 14:07:34 +11:00 committed by Iuri Matias
parent 44b1d55ebb
commit 5ac4162f82
4 changed files with 136 additions and 49 deletions

View File

@ -14,15 +14,14 @@ Item {
property alias text: inpAddress.text property alias text: inpAddress.text
property string selectedAddress property string selectedAddress
property var isValid: false property var isValid: false
property bool isPending: false property alias isPending: ensResolver.isPending
readonly property string uuid: Utils.uuid()
property alias readOnly: inpAddress.readOnly property alias readOnly: inpAddress.readOnly
property bool isResolvedAddress: false property bool isResolvedAddress: false
height: inpAddress.height height: inpAddress.height
onSelectedAddressChanged: validate() onSelectedAddressChanged: validate()
onTextChanged: resolveEns() onTextChanged: ensResolver.resolveEns(text)
function resetInternal() { function resetInternal() {
selectedAddress = "" selectedAddress = ""
@ -33,12 +32,6 @@ Item {
isResolvedAddress = false isResolvedAddress = false
} }
function resolveEns() {
if (Utils.isValidEns(text)) {
root.validateAsync(text)
}
}
function validate() { function validate() {
let isValidEns = Utils.isValidEns(text) let isValidEns = Utils.isValidEns(text)
let isValidAddress = Utils.isValidAddress(selectedAddress) let isValidAddress = Utils.isValidAddress(selectedAddress)
@ -51,26 +44,6 @@ Item {
return isValid return isValid
} }
property var validateAsync: Backpressure.debounce(inpAddress, 600, function (inputValue) {
root.isPending = true
root.selectedAddress = ""
var name = inputValue.startsWith("@") ? inputValue.substring(1) : inputValue
walletModel.resolveENS(name, uuid)
});
Connections {
target: walletModel
onEnsWasResolved: {
if (uuid !== root.uuid) {
return
}
root.isPending = false
root.isResolvedAddress = true
root.selectedAddress = resolvedAddress
}
}
Input { Input {
id: inpAddress id: inpAddress
//% "eg. 0x1234 or ENS" //% "eg. 0x1234 or ENS"
@ -92,7 +65,7 @@ Item {
onTextEdited: { onTextEdited: {
metrics.text = text metrics.text = text
resolveEns() ensResolver.resolveEns(text)
root.isResolvedAddress = false root.isResolvedAddress = false
root.selectedAddress = text root.selectedAddress = text
} }
@ -117,19 +90,19 @@ Item {
} }
} }
Loader { EnsResolver {
sourceComponent: loadingIndicator id: ensResolver
anchors.top: inpAddress.bottom anchors.top: inpAddress.bottom
anchors.right: inpAddress.right anchors.right: inpAddress.right
anchors.topMargin: Style.current.halfPadding anchors.topMargin: Style.current.halfPadding
active: root.isPending onResolved: {
} root.isResolvedAddress = true
root.selectedAddress = resolvedAddress
Component { }
id: loadingIndicator onIsPendingChanged: {
LoadingImage { if (isPending) {
width: 12 root.selectedAddress = ""
height: 12 }
} }
} }
} }

View File

@ -15,8 +15,13 @@ Item {
property string validationError: qsTrId("please-select-a-contact") property string validationError: qsTrId("please-select-a-contact")
property alias validationErrorAlignment: select.validationErrorAlignment property alias validationErrorAlignment: select.validationErrorAlignment
property bool isValid: false property bool isValid: false
property alias isPending: ensResolver.isPending
property var reset: function() {} property var reset: function() {}
property bool readOnly: false property bool readOnly: false
property bool isResolvedAddress: false
//% "Select a contact"
property string selectAContact: qsTrId("select-a-contact")
property string noEnsAddressMessage: qsTr("Contact does not have an ENS address. Please send a transaction in chat.")
function resetInternal() { function resetInternal() {
contacts = undefined contacts = undefined
@ -24,21 +29,40 @@ Item {
select.validationError = "" select.validationError = ""
isValid = false isValid = false
readOnly = false readOnly = false
isResolvedAddress = false
} }
onContactsChanged: { onContactsChanged: {
if (root.readOnly) { if (root.readOnly) {
return return
} }
//% "Select a contact" root.selectedContact = { name: selectAContact }
root.selectedContact = { name: qsTrId("select-a-contact") }
} }
onSelectedContactChanged: validate() onSelectedContactChanged: {
if (selectedContact && selectedContact.ensVerified) {
root.isResolvedAddress = false
ensResolver.resolveEns(selectedContact.name)
}
validate()
}
function validate() { function validate() {
const isValid = !!selectedContact && Utils.isValidAddress(selectedContact.address) if (!selectedContact) {
select.validationError = !isValid ? validationError : "" return root.isValid
}
let isValidAddress = Utils.isValidAddress(selectedContact.address)
let isDefaultValue = selectedContact.name === selectAContact
let isValid = (selectedContact.ensVerified && isValidAddress) || isPending || isValidAddress
select.validationError = ""
if (!isValid && !isDefaultValue &&
(
!selectedContact.ensVerified ||
(selectedContact.ensVerified && isResolvedAddress)
)
) {
select.validationError = !selectedContact.ensVerified ? noEnsAddressMessage : validationError
}
root.isValid = isValid root.isValid = isValid
return isValid return isValid
} }
@ -109,6 +133,23 @@ Item {
menu.width: dropdownWidth menu.width: dropdownWidth
} }
EnsResolver {
id: ensResolver
anchors.top: select.bottom
anchors.right: select.right
anchors.topMargin: Style.current.halfPadding
onResolved: {
root.isResolvedAddress = true
root.selectedContact.address = resolvedAddress
validate()
}
onIsPendingChanged: {
if (isPending) {
root.selectedContact.address = ""
}
}
}
Component { Component {
id: menuItem id: menuItem
MenuItem { MenuItem {
@ -187,7 +228,6 @@ Item {
onClicked: { onClicked: {
root.selectedContact = { address, name, alias, isContact, identicon, ensVerified } root.selectedContact = { address, name, alias, isContact, identicon, ensVerified }
select.menu.close() select.menu.close()
validate()
} }
} }
} }

50
ui/shared/EnsResolver.qml Normal file
View File

@ -0,0 +1,50 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import QtGraphicalEffects 1.13
import "../imports"
Item {
id: root
property bool isPending: false
readonly property string uuid: Utils.uuid()
readonly property var validateAsync: Backpressure.debounce(inpAddress, 600, function (inputValue) {
root.isPending = true
var name = inputValue.startsWith("@") ? inputValue.substring(1) : inputValue
walletModel.resolveENS(name, uuid)
});
signal resolved(string resolvedAddress)
function resolveEns(name) {
if (Utils.isValidEns(name)) {
root.validateAsync(name)
}
}
width: 12
height: 12
Loader {
anchors.fill: parent
sourceComponent: loadingIndicator
active: root.isPending
}
Component {
id: loadingIndicator
LoadingImage {
width: root.width
height: root.height
}
}
Connections {
target: walletModel
onEnsWasResolved: {
if (uuid !== root.uuid) {
return
}
root.resolved(resolvedAddress)
root.isPending = false
}
}
}

View File

@ -19,14 +19,26 @@ Item {
//% "Invalid ethereum address" //% "Invalid ethereum address"
readonly property string addressValidationError: qsTrId("invalid-ethereum-address") readonly property string addressValidationError: qsTrId("invalid-ethereum-address")
property bool isValid: false property bool isValid: false
property bool isPending: false property bool isPending: {
if (!selAddressSource.selectedSource) {
return false
}
switch (selAddressSource.selectedSource.value) {
case RecipientSelector.Type.Address:
return inpAddress.isPending
case RecipientSelector.Type.Contact:
return selContact.isPending
case RecipientSelector.Type.Account:
return selAccount.isPending
}
}
property var reset: function() {} property var reset: function() {}
readonly property var sources: [ readonly property var sources: [
//% "Address" //% "Address"
{ text: qsTrId("address"), value: RecipientSelector.Type.Address, visible: true }, { text: qsTrId("address"), value: RecipientSelector.Type.Address, visible: true },
//% "My account" //% "My account"
{ text: qsTrId("my-account"), value: RecipientSelector.Type.Account, visible: true }, { text: qsTrId("my-account"), value: RecipientSelector.Type.Account, visible: true },
{ text: qsTr("Contact"), value: RecipientSelector.Type.Contact, visible: false } { text: qsTr("Contact"), value: RecipientSelector.Type.Contact, visible: true }
] ]
property var selectedType: RecipientSelector.Type.Address property var selectedType: RecipientSelector.Type.Address
@ -36,7 +48,19 @@ Item {
selAccount.resetInternal() selAccount.resetInternal()
selAddressSource.resetInternal() selAddressSource.resetInternal()
isValid = false isValid = false
isPending = false isPending = Qt.binding(function() {
if (!selAddressSource.selectedSource) {
return false
}
switch (selAddressSource.selectedSource.value) {
case RecipientSelector.Type.Address:
return inpAddress.isPending
case RecipientSelector.Type.Contact:
return selContact.isPending
case RecipientSelector.Type.Account:
return selAccount.isPending
}
})
selectedType = RecipientSelector.Type.Address selectedType = RecipientSelector.Type.Address
selectedRecipient = undefined selectedRecipient = undefined
accounts = undefined accounts = undefined