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:
parent
44b1d55ebb
commit
5ac4162f82
|
@ -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
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue