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 string selectedAddress
property var isValid: false
property bool isPending: false
readonly property string uuid: Utils.uuid()
property alias isPending: ensResolver.isPending
property alias readOnly: inpAddress.readOnly
property bool isResolvedAddress: false
height: inpAddress.height
onSelectedAddressChanged: validate()
onTextChanged: resolveEns()
onTextChanged: ensResolver.resolveEns(text)
function resetInternal() {
selectedAddress = ""
@ -33,12 +32,6 @@ Item {
isResolvedAddress = false
}
function resolveEns() {
if (Utils.isValidEns(text)) {
root.validateAsync(text)
}
}
function validate() {
let isValidEns = Utils.isValidEns(text)
let isValidAddress = Utils.isValidAddress(selectedAddress)
@ -51,26 +44,6 @@ Item {
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 {
id: inpAddress
//% "eg. 0x1234 or ENS"
@ -92,7 +65,7 @@ Item {
onTextEdited: {
metrics.text = text
resolveEns()
ensResolver.resolveEns(text)
root.isResolvedAddress = false
root.selectedAddress = text
}
@ -117,19 +90,19 @@ Item {
}
}
Loader {
sourceComponent: loadingIndicator
EnsResolver {
id: ensResolver
anchors.top: inpAddress.bottom
anchors.right: inpAddress.right
anchors.topMargin: Style.current.halfPadding
active: root.isPending
}
Component {
id: loadingIndicator
LoadingImage {
width: 12
height: 12
onResolved: {
root.isResolvedAddress = true
root.selectedAddress = resolvedAddress
}
onIsPendingChanged: {
if (isPending) {
root.selectedAddress = ""
}
}
}
}

View File

@ -15,8 +15,13 @@ Item {
property string validationError: qsTrId("please-select-a-contact")
property alias validationErrorAlignment: select.validationErrorAlignment
property bool isValid: false
property alias isPending: ensResolver.isPending
property var reset: function() {}
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() {
contacts = undefined
@ -24,21 +29,40 @@ Item {
select.validationError = ""
isValid = false
readOnly = false
isResolvedAddress = false
}
onContactsChanged: {
if (root.readOnly) {
return
}
//% "Select a contact"
root.selectedContact = { name: qsTrId("select-a-contact") }
root.selectedContact = { name: selectAContact }
}
onSelectedContactChanged: validate()
onSelectedContactChanged: {
if (selectedContact && selectedContact.ensVerified) {
root.isResolvedAddress = false
ensResolver.resolveEns(selectedContact.name)
}
validate()
}
function validate() {
const isValid = !!selectedContact && Utils.isValidAddress(selectedContact.address)
select.validationError = !isValid ? validationError : ""
if (!selectedContact) {
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
return isValid
}
@ -109,6 +133,23 @@ Item {
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 {
id: menuItem
MenuItem {
@ -187,7 +228,6 @@ Item {
onClicked: {
root.selectedContact = { address, name, alias, isContact, identicon, ensVerified }
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"
readonly property string addressValidationError: qsTrId("invalid-ethereum-address")
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() {}
readonly property var sources: [
//% "Address"
{ text: qsTrId("address"), value: RecipientSelector.Type.Address, visible: true },
//% "My account"
{ 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
@ -36,7 +48,19 @@ Item {
selAccount.resetInternal()
selAddressSource.resetInternal()
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
selectedRecipient = undefined
accounts = undefined