mirror of
https://github.com/status-im/status-desktop.git
synced 2025-01-23 12:59:44 +00:00
feat: 1-on-1 chat command ENS flow
1-on-1 chat command to send and request a transaction to/from a contact with ENS enabled.
This commit is contained in:
parent
326c6bb6c3
commit
bc1525f513
@ -401,6 +401,7 @@ QtObject:
|
||||
# Updating usernames for all the messages list
|
||||
for k in self.messageList.keys:
|
||||
self.messageList[k].updateUsernames(contacts)
|
||||
self.activeChannel.contactsUpdated()
|
||||
|
||||
proc updateChannelForContacts*(self: ChatsView, contacts: seq[Profile]) =
|
||||
for contact in contacts:
|
||||
|
@ -35,6 +35,8 @@ QtObject:
|
||||
QtProperty[string] id:
|
||||
read = id
|
||||
|
||||
proc contactsUpdated*(self: ChatItemView) {.signal}
|
||||
|
||||
proc userNameOrAlias(self: ChatItemView, pubKey: string): string {.slot.} =
|
||||
if self.status.chat.contacts.hasKey(pubKey):
|
||||
return ens.userNameOrAlias(self.status.chat.contacts[pubKey])
|
||||
@ -48,6 +50,18 @@ QtObject:
|
||||
|
||||
QtProperty[string] name:
|
||||
read = name
|
||||
notify = contactsUpdated
|
||||
|
||||
proc ensVerified*(self: ChatItemView): bool {.slot.} =
|
||||
if self.chatItem != nil and
|
||||
self.chatItem.chatType.isOneToOne and
|
||||
self.status.chat.contacts.hasKey(self.chatItem.id):
|
||||
return self.status.chat.contacts[self.chatItem.id].ensVerified
|
||||
result = false
|
||||
|
||||
QtProperty[bool] ensVerified:
|
||||
read = ensVerified
|
||||
notify = contactsUpdated
|
||||
|
||||
proc color*(self: ChatItemView): string {.slot.} = result = ?.self.chatItem.color
|
||||
|
||||
|
@ -9,6 +9,7 @@ QtObject:
|
||||
address*: string
|
||||
pubKey*: string
|
||||
appearance*: int
|
||||
ensVerified*: bool
|
||||
|
||||
proc setup(self: ProfileInfoView) =
|
||||
self.QObject.setup
|
||||
@ -23,6 +24,7 @@ QtObject:
|
||||
result.username = ""
|
||||
result.identicon = ""
|
||||
result.appearance = 0
|
||||
result.ensVerified = false
|
||||
result.setup
|
||||
|
||||
proc profileChanged*(self: ProfileInfoView) {.signal.}
|
||||
@ -33,6 +35,7 @@ QtObject:
|
||||
self.appearance = profile.appearance
|
||||
self.pubKey = profile.id
|
||||
self.address = profile.address
|
||||
self.ensVerified = profile.ensVerified
|
||||
self.profileChanged()
|
||||
|
||||
proc username*(self: ProfileInfoView): string {.slot.} = result = self.username
|
||||
@ -67,3 +70,9 @@ QtObject:
|
||||
QtProperty[string] address:
|
||||
read = address
|
||||
notify = profileChanged
|
||||
|
||||
proc ensVerified*(self: ProfileInfoView): bool {.slot.} = self.ensVerified
|
||||
|
||||
QtProperty[bool] ensVerified:
|
||||
read = ensVerified
|
||||
notify = profileChanged
|
||||
|
@ -562,13 +562,17 @@ QtObject:
|
||||
self.accounts.getAccount(index).transactions)
|
||||
self.loadingTrxHistoryChanged(false)
|
||||
|
||||
proc resolveENS*(self: WalletView, ens: string) {.slot.} =
|
||||
proc resolveENS*(self: WalletView, ens: string, uuid: string) {.slot.} =
|
||||
spawnAndSend(self, "ensResolved") do:
|
||||
status_ens.owner(ens)
|
||||
$ %* { "address": status_ens.address(ens), "uuid": uuid }
|
||||
|
||||
proc ensWasResolved*(self: WalletView, resolvedPubKey: string) {.signal.}
|
||||
proc ensWasResolved*(self: WalletView, resolvedAddress: string, uuid: string) {.signal.}
|
||||
|
||||
proc ensResolved(self: WalletView, pubKey: string) {.slot.} =
|
||||
self.ensWasResolved(pubKey)
|
||||
proc ensResolved(self: WalletView, addressUuidJson: string) {.slot.} =
|
||||
let
|
||||
parsed = addressUuidJson.parseJson
|
||||
address = parsed["address"].to(string)
|
||||
uuid = parsed["uuid"].to(string)
|
||||
self.ensWasResolved(address, uuid)
|
||||
|
||||
proc transactionCompleted*(self: WalletView, success: bool, txHash: string, revertReason: string = "") {.signal.}
|
||||
|
@ -8,6 +8,7 @@ import "./components"
|
||||
import "./ChatColumn"
|
||||
import "./ChatColumn/ChatComponents"
|
||||
import "./data"
|
||||
import "../Wallet"
|
||||
|
||||
StackLayout {
|
||||
id: chatColumnLayout
|
||||
@ -219,36 +220,18 @@ StackLayout {
|
||||
stickerPackList: chatsModel.stickerPacks
|
||||
chatType: chatsModel.activeChannel.chatType
|
||||
onSendTransactionCommandButtonClicked: {
|
||||
chatCommandModal.sendChatCommand = chatColumnLayout.requestAddressForTransaction
|
||||
chatCommandModal.isRequested = false
|
||||
//% "Send"
|
||||
chatCommandModal.commandTitle = qsTrId("command-button-send")
|
||||
chatCommandModal.title = chatCommandModal.commandTitle
|
||||
//% "Request Address"
|
||||
chatCommandModal.finalButtonLabel = qsTrId("request-address")
|
||||
chatCommandModal.selectedRecipient = {
|
||||
address: Constants.zeroAddress, // Setting as zero address since we don't have the address yet
|
||||
identicon: chatsModel.activeChannel.identicon,
|
||||
name: chatsModel.activeChannel.name,
|
||||
type: RecipientSelector.Type.Contact
|
||||
txModalLoader.sourceComponent = undefined
|
||||
if (chatsModel.activeChannel.ensVerified) {
|
||||
txModalLoader.sourceComponent = cmpSendTransactionWithEns
|
||||
} else {
|
||||
txModalLoader.sourceComponent = cmpSendTransactionNoEns
|
||||
}
|
||||
chatCommandModal.open()
|
||||
txModalLoader.item.open()
|
||||
}
|
||||
onReceiveTransactionCommandButtonClicked: {
|
||||
chatCommandModal.sendChatCommand = chatColumnLayout.requestTransaction
|
||||
chatCommandModal.isRequested = true
|
||||
//% "Request"
|
||||
chatCommandModal.commandTitle = qsTrId("wallet-request")
|
||||
chatCommandModal.title = chatCommandModal.commandTitle
|
||||
//% "Request"
|
||||
chatCommandModal.finalButtonLabel = qsTrId("wallet-request")
|
||||
chatCommandModal.selectedRecipient = {
|
||||
address: Constants.zeroAddress, // Setting as zero address since we don't have the address yet
|
||||
identicon: chatsModel.activeChannel.identicon,
|
||||
name: chatsModel.activeChannel.name,
|
||||
type: RecipientSelector.Type.Contact
|
||||
}
|
||||
chatCommandModal.open()
|
||||
txModalLoader.sourceComponent = undefined
|
||||
txModalLoader.sourceComponent = cmpReceiveTransaction
|
||||
txModalLoader.item.open()
|
||||
}
|
||||
onStickerSelected: {
|
||||
chatsModel.sendSticker(hashId, packId)
|
||||
@ -259,8 +242,111 @@ StackLayout {
|
||||
|
||||
EmptyChat {}
|
||||
|
||||
ChatCommandModal {
|
||||
id: chatCommandModal
|
||||
Loader {
|
||||
id: txModalLoader
|
||||
}
|
||||
Component {
|
||||
id: cmpSendTransactionNoEns
|
||||
ChatCommandModal {
|
||||
id: sendTransactionNoEns
|
||||
sendChatCommand: chatColumnLayout.requestAddressForTransaction
|
||||
isRequested: false
|
||||
//% "Send"
|
||||
commandTitle: qsTrId("command-button-send")
|
||||
title: commandTitle
|
||||
//% "Request Address"
|
||||
finalButtonLabel: qsTrId("request-address")
|
||||
selectRecipient.selectedRecipient: {
|
||||
return {
|
||||
address: Constants.zeroAddress, // Setting as zero address since we don't have the address yet
|
||||
identicon: chatsModel.activeChannel.identicon,
|
||||
name: chatsModel.activeChannel.name,
|
||||
type: RecipientSelector.Type.Contact
|
||||
}
|
||||
}
|
||||
selectRecipient.selectedType: RecipientSelector.Type.Contact
|
||||
selectRecipient.readOnly: true
|
||||
onReset: {
|
||||
selectRecipient.selectedRecipient = Qt.binding(function() {
|
||||
return {
|
||||
address: Constants.zeroAddress, // Setting as zero address since we don't have the address yet
|
||||
identicon: chatsModel.activeChannel.identicon,
|
||||
name: chatsModel.activeChannel.name,
|
||||
type: RecipientSelector.Type.Contact
|
||||
}
|
||||
})
|
||||
selectRecipient.selectedType = RecipientSelector.Type.Contact
|
||||
selectRecipient.readOnly = true
|
||||
}
|
||||
}
|
||||
}
|
||||
Component {
|
||||
id: cmpReceiveTransaction
|
||||
ChatCommandModal {
|
||||
id: receiveTransaction
|
||||
sendChatCommand: chatColumnLayout.requestTransaction
|
||||
isRequested: true
|
||||
//% "Request"
|
||||
commandTitle: qsTrId("wallet-request")
|
||||
title: commandTitle
|
||||
//% "Request"
|
||||
finalButtonLabel: qsTrId("wallet-request")
|
||||
selectRecipient.selectedRecipient: {
|
||||
return {
|
||||
address: Constants.zeroAddress, // Setting as zero address since we don't have the address yet
|
||||
identicon: chatsModel.activeChannel.identicon,
|
||||
name: chatsModel.activeChannel.name,
|
||||
type: RecipientSelector.Type.Contact
|
||||
}
|
||||
}
|
||||
selectRecipient.selectedType: RecipientSelector.Type.Contact
|
||||
selectRecipient.readOnly: true
|
||||
onReset: {
|
||||
selectRecipient.selectedRecipient = Qt.binding(function() {
|
||||
return {
|
||||
address: Constants.zeroAddress, // Setting as zero address since we don't have the address yet
|
||||
identicon: chatsModel.activeChannel.identicon,
|
||||
name: chatsModel.activeChannel.name,
|
||||
type: RecipientSelector.Type.Contact
|
||||
}
|
||||
})
|
||||
selectRecipient.selectedType = RecipientSelector.Type.Contact
|
||||
selectRecipient.readOnly = true
|
||||
}
|
||||
}
|
||||
}
|
||||
Component {
|
||||
id: cmpSendTransactionWithEns
|
||||
SendModal {
|
||||
id: sendTransactionWithEns
|
||||
onOpened: {
|
||||
walletModel.getGasPricePredictions()
|
||||
}
|
||||
selectRecipient.readOnly: true
|
||||
selectRecipient.selectedRecipient: {
|
||||
return {
|
||||
address: "",
|
||||
identicon: chatsModel.activeChannel.identicon,
|
||||
name: chatsModel.activeChannel.name,
|
||||
type: RecipientSelector.Type.Address,
|
||||
ensVerified: true
|
||||
}
|
||||
}
|
||||
selectRecipient.selectedType: RecipientSelector.Type.Address
|
||||
onReset: {
|
||||
selectRecipient.readOnly = true
|
||||
selectRecipient.selectedRecipient = Qt.binding(function() {
|
||||
return {
|
||||
address: "",
|
||||
identicon: chatsModel.activeChannel.identicon,
|
||||
name: chatsModel.activeChannel.name,
|
||||
type: RecipientSelector.Type.Address,
|
||||
ensVerified: true
|
||||
}
|
||||
})
|
||||
selectRecipient.selectedType = RecipientSelector.Type.Address
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,15 +11,17 @@ ModalPopup {
|
||||
property string finalButtonLabel: "Request address"
|
||||
property var sendChatCommand: function () {}
|
||||
property bool isRequested: false
|
||||
signal reset
|
||||
|
||||
id: root
|
||||
title: root.commandTitle
|
||||
height: 504
|
||||
|
||||
property alias selectedRecipient: selectRecipient.selectedRecipient
|
||||
property alias selectRecipient: selectRecipient
|
||||
|
||||
onClosed: {
|
||||
stack.reset()
|
||||
root.reset()
|
||||
}
|
||||
|
||||
TransactionStackView {
|
||||
@ -78,7 +80,6 @@ ModalPopup {
|
||||
label: root.isRequested ?
|
||||
qsTr("From") :
|
||||
qsTr("To")
|
||||
readOnly: true
|
||||
anchors.top: separator.bottom
|
||||
anchors.topMargin: 10
|
||||
width: stack.width
|
||||
@ -102,7 +103,7 @@ ModalPopup {
|
||||
defaultCurrency: walletModel.defaultCurrency
|
||||
getFiatValue: walletModel.getFiatValue
|
||||
getCryptoValue: walletModel.getCryptoValue
|
||||
isRequested: root.isRequested
|
||||
validateBalance: !root.isRequested
|
||||
width: stack.width
|
||||
reset: function() {
|
||||
selectedAccount = Qt.binding(function() { return selectFromAccount.selectedAccount })
|
||||
|
@ -1,110 +0,0 @@
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import QtGraphicalEffects 1.13
|
||||
import "../../../../../imports"
|
||||
import "../../../../../shared"
|
||||
|
||||
Popup {
|
||||
id: root
|
||||
width: buttonRow.width
|
||||
height: buttonRow.height
|
||||
padding: 0
|
||||
margins: 0
|
||||
|
||||
background: Rectangle {
|
||||
color: Style.current.background
|
||||
radius: Style.current.radius
|
||||
border.width: 0
|
||||
layer.enabled: true
|
||||
layer.effect: DropShadow {
|
||||
verticalOffset: 3
|
||||
radius: 8
|
||||
samples: 15
|
||||
fast: true
|
||||
cached: true
|
||||
color: "#22000000"
|
||||
}
|
||||
}
|
||||
|
||||
function requestAddressForTransaction(address, amount, tokenAddress, tokenDecimals = 18) {
|
||||
amount = utilsModel.eth2Wei(amount.toString(), tokenDecimals)
|
||||
chatsModel.requestAddressForTransaction(chatsModel.activeChannel.id,
|
||||
address,
|
||||
amount,
|
||||
tokenAddress)
|
||||
chatCommandModal.close()
|
||||
}
|
||||
function requestTransaction(address, amount, tokenAddress, tokenDecimals = 18) {
|
||||
amount = utilsModel.eth2Wei(amount.toString(), tokenDecimals)
|
||||
chatsModel.requestTransaction(chatsModel.activeChannel.id,
|
||||
address,
|
||||
amount,
|
||||
tokenAddress)
|
||||
chatCommandModal.close()
|
||||
}
|
||||
|
||||
Row {
|
||||
id: buttonRow
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 0
|
||||
padding: Style.current.halfPadding
|
||||
spacing: Style.current.halfPadding
|
||||
|
||||
|
||||
ChatCommandButton {
|
||||
iconColor: Style.current.purple
|
||||
iconSource: "../../../../img/send.svg"
|
||||
//% "Send transaction"
|
||||
text: qsTrId("send-transaction")
|
||||
onClicked: function () {
|
||||
chatCommandModal.sendChatCommand = root.requestAddressForTransaction
|
||||
chatCommandModal.isRequested = false
|
||||
//% "Send"
|
||||
chatCommandModal.commandTitle = qsTrId("command-button-send")
|
||||
chatCommandModal.title = chatCommandModal.commandTitle
|
||||
//% "Request Address"
|
||||
chatCommandModal.finalButtonLabel = qsTrId("request-address")
|
||||
chatCommandModal.selectedRecipient = {
|
||||
address: Constants.zeroAddress, // Setting as zero address since we don't have the address yet
|
||||
identicon: chatsModel.activeChannel.identicon,
|
||||
name: chatsModel.activeChannel.name,
|
||||
type: RecipientSelector.Type.Contact
|
||||
}
|
||||
chatCommandModal.open()
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ChatCommandButton {
|
||||
iconColor: Style.current.orange
|
||||
iconSource: "../../../../img/send.svg"
|
||||
rotatedImage: true
|
||||
//% "Request transaction"
|
||||
text: qsTrId("request-transaction")
|
||||
onClicked: function () {
|
||||
chatCommandModal.sendChatCommand = root.requestTransaction
|
||||
chatCommandModal.isRequested = true
|
||||
//% "Request"
|
||||
chatCommandModal.commandTitle = qsTrId("wallet-request")
|
||||
chatCommandModal.title = chatCommandModal.commandTitle
|
||||
//% "Request"
|
||||
chatCommandModal.finalButtonLabel = qsTrId("wallet-request")
|
||||
chatCommandModal.selectedRecipient = {
|
||||
address: Constants.zeroAddress, // Setting as zero address since we don't have the address yet
|
||||
identicon: chatsModel.activeChannel.identicon,
|
||||
name: chatsModel.activeChannel.name,
|
||||
type: RecipientSelector.Type.Contact
|
||||
}
|
||||
chatCommandModal.open()
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
|
||||
ChatCommandModal {
|
||||
id: chatCommandModal
|
||||
}
|
||||
}
|
||||
}
|
@ -45,7 +45,7 @@ Item {
|
||||
SignTransactionModal {
|
||||
id: signTransactionModal
|
||||
onOpened: {
|
||||
walletModel.getGasPricePredictions()
|
||||
walletModel.getGasPricePredictions()
|
||||
}
|
||||
selectedRecipient: {
|
||||
return {
|
||||
|
@ -8,8 +8,11 @@ import "../../../shared/status"
|
||||
import "./components"
|
||||
|
||||
ModalPopup {
|
||||
property alias selectFromAccount: selectFromAccount
|
||||
id: root
|
||||
property alias selectFromAccount: selectFromAccount
|
||||
property alias selectRecipient: selectRecipient
|
||||
property alias stack: stack
|
||||
signal reset
|
||||
|
||||
//% "Send"
|
||||
title: qsTrId("command-button-send")
|
||||
@ -25,6 +28,7 @@ ModalPopup {
|
||||
|
||||
onClosed: {
|
||||
stack.reset()
|
||||
root.reset()
|
||||
}
|
||||
|
||||
function sendTransaction() {
|
||||
@ -86,7 +90,7 @@ ModalPopup {
|
||||
reset: function() {
|
||||
accounts = Qt.binding(function() { return walletModel.accounts })
|
||||
contacts = Qt.binding(function() { return profileModel.addedContacts })
|
||||
selectedRecipient = {}
|
||||
selectedRecipient = undefined
|
||||
}
|
||||
onSelectedRecipientChanged: if (isValid) { gasSelector.estimateGas() }
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ Item {
|
||||
property int dropdownWidth: width
|
||||
property alias dropdownAlignment: select.menuAlignment
|
||||
property bool isValid: true
|
||||
property bool readOnly: false
|
||||
property var reset: function() {}
|
||||
|
||||
function resetInternal() {
|
||||
@ -33,6 +34,7 @@ Item {
|
||||
minRequiredAssetBalance = 0
|
||||
assetFound = undefined
|
||||
isValid = true
|
||||
readOnly = false
|
||||
}
|
||||
|
||||
function validate() {
|
||||
|
@ -11,47 +11,63 @@ Item {
|
||||
//% "ENS Username not found"
|
||||
property string ensAsyncValidationError: qsTrId("ens-username-not-found")
|
||||
property alias label: inpAddress.label
|
||||
property alias text: inpAddress.text
|
||||
property string selectedAddress
|
||||
property var isValid: false
|
||||
property bool isPending: false
|
||||
readonly property string uuid: Utils.uuid()
|
||||
property alias readOnly: inpAddress.readOnly
|
||||
property bool isResolvedAddress: false
|
||||
|
||||
height: inpAddress.height
|
||||
|
||||
onSelectedAddressChanged: validate()
|
||||
onTextChanged: resolveEns()
|
||||
|
||||
function resetInternal() {
|
||||
selectedAddress = ""
|
||||
inpAddress.resetInternal()
|
||||
metrics.text = ""
|
||||
isValid = false
|
||||
isPending = false
|
||||
isResolvedAddress = false
|
||||
}
|
||||
|
||||
function validate(inputValue) {
|
||||
if (!inputValue) inputValue = selectedAddress
|
||||
let isValid =
|
||||
(inputValue && inputValue.startsWith("0x") && Utils.isValidAddress(inputValue) || Utils.isValidEns(inputValue))
|
||||
inpAddress.validationError = isValid ? "" : validationError
|
||||
function resolveEns() {
|
||||
if (Utils.isValidEns(text)) {
|
||||
root.validateAsync(text)
|
||||
}
|
||||
}
|
||||
|
||||
function validate() {
|
||||
let isValidEns = Utils.isValidEns(text)
|
||||
let isValidAddress = Utils.isValidAddress(selectedAddress)
|
||||
let isValid = (isValidEns && !isResolvedAddress) || isPending || isValidAddress
|
||||
inpAddress.validationError = ""
|
||||
if (!isValid){
|
||||
inpAddress.validationError = isResolvedAddress ? ensAsyncValidationError : validationError
|
||||
}
|
||||
root.isValid = isValid
|
||||
return isValid
|
||||
}
|
||||
|
||||
property var validateAsync: Backpressure.debounce(inpAddress, 300, function (inputValue) {
|
||||
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)
|
||||
walletModel.resolveENS(name, uuid)
|
||||
});
|
||||
|
||||
|
||||
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 = ""
|
||||
if (uuid !== root.uuid) {
|
||||
return
|
||||
}
|
||||
root.isPending = false
|
||||
root.isResolvedAddress = true
|
||||
root.selectedAddress = resolvedAddress
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,29 +80,21 @@ Item {
|
||||
validationErrorTopMargin: 8
|
||||
textField.onFocusChanged: {
|
||||
let isValid = true
|
||||
if (text !== "") {
|
||||
isValid = root.validate(metrics.text)
|
||||
}
|
||||
if (!isValid) {
|
||||
return
|
||||
}
|
||||
if (textField.focus) {
|
||||
text = metrics.text
|
||||
} else if (Utils.isValidAddress(metrics.text)) {
|
||||
text = metrics.elidedText
|
||||
if (text !== "" && Utils.isValidAddress(metrics.text)) {
|
||||
if (textField.focus) {
|
||||
text = metrics.text
|
||||
} else {
|
||||
text = metrics.elidedText
|
||||
}
|
||||
}
|
||||
}
|
||||
textField.rightPadding: 73
|
||||
onTextEdited: {
|
||||
metrics.text = text
|
||||
const isValid = root.validate(inputValue)
|
||||
if (isValid) {
|
||||
if (Utils.isValidAddress(inputValue)) {
|
||||
root.selectedAddress = inputValue
|
||||
} else {
|
||||
Qt.callLater(root.validateAsync, inputValue)
|
||||
}
|
||||
}
|
||||
|
||||
resolveEns()
|
||||
root.isResolvedAddress = false
|
||||
root.selectedAddress = text
|
||||
}
|
||||
TextMetrics {
|
||||
id: metrics
|
||||
@ -98,6 +106,7 @@ Item {
|
||||
anchors.rightMargin: 8
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 14
|
||||
visible: !root.readOnly
|
||||
//% "Paste"
|
||||
label: qsTrId("paste")
|
||||
onClicked: {
|
||||
|
@ -7,14 +7,14 @@ import "../imports"
|
||||
Item {
|
||||
id: root
|
||||
property var sources: []
|
||||
property string selectedSource: sources[0] || "Address"
|
||||
property var selectedSource: sources.length ? sources[0] : null
|
||||
property int dropdownWidth: 220
|
||||
property var reset: function() {}
|
||||
height: select.height
|
||||
|
||||
function resetInternal() {
|
||||
sources = []
|
||||
selectedSource = sources[0] || "Address"
|
||||
selectedSource = sources.length ? sources[0] : null
|
||||
}
|
||||
|
||||
Select {
|
||||
@ -26,7 +26,7 @@ Item {
|
||||
anchors.fill: parent
|
||||
StyledText {
|
||||
id: selectedTextField
|
||||
text: root.selectedSource
|
||||
text: !!root.selectedSource ? root.selectedSource.text : qsTr("Invalid source")
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Style.current.padding
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@ -49,7 +49,7 @@ Item {
|
||||
|
||||
StyledText {
|
||||
id: itemText
|
||||
text: root.sources[index]
|
||||
text: root.sources[index].text
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Style.current.padding
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
@ -21,7 +21,7 @@ Item {
|
||||
property var getFiatValue: function () {}
|
||||
property var getCryptoValue: function () {}
|
||||
property bool isDirty: false
|
||||
property bool isRequested: false
|
||||
property bool validateBalance: true
|
||||
property bool isValid: false
|
||||
property var reset: function() {}
|
||||
|
||||
@ -57,7 +57,7 @@ Item {
|
||||
} else if (input === 0.00 && hasTyped) {
|
||||
error = greaterThan0ErrorMessage
|
||||
isValid = false
|
||||
} else if (!isRequested && input > balance && !noInput) {
|
||||
} else if (validateBalance && input > balance && !noInput) {
|
||||
error = balanceErrorMessage
|
||||
isValid = false
|
||||
}
|
||||
@ -86,7 +86,7 @@ Item {
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: !root.isRequested
|
||||
visible: root.validateBalance
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
|
@ -16,31 +16,56 @@ Item {
|
||||
property alias validationErrorAlignment: select.validationErrorAlignment
|
||||
property bool isValid: false
|
||||
property var reset: function() {}
|
||||
property bool readOnly: false
|
||||
|
||||
function resetInternal() {
|
||||
contacts = undefined
|
||||
selectedContact = undefined
|
||||
select.validationError = ""
|
||||
isValid = false
|
||||
readOnly = false
|
||||
}
|
||||
|
||||
onContactsChanged: {
|
||||
if (root.readOnly) {
|
||||
return
|
||||
}
|
||||
//% "Select a contact"
|
||||
root.selectedContact = { name: qsTrId("select-a-contact") }
|
||||
}
|
||||
|
||||
onSelectedContactChanged: validate()
|
||||
|
||||
function validate() {
|
||||
const isValid = !!(selectedContact && selectedContact.address)
|
||||
const isValid = !!selectedContact && Utils.isValidAddress(selectedContact.address)
|
||||
select.validationError = !isValid ? validationError : ""
|
||||
root.isValid = isValid
|
||||
return isValid
|
||||
}
|
||||
|
||||
Input {
|
||||
id: inpReadOnly
|
||||
visible: root.readOnly
|
||||
width: parent.width
|
||||
text: (root.selectedContact && root.selectedContact.name) ? root.selectedContact.name : qsTr("No contact selected")
|
||||
textField.leftPadding: 14
|
||||
textField.topPadding: 18
|
||||
textField.bottomPadding: 18
|
||||
textField.verticalAlignment: TextField.AlignVCenter
|
||||
textField.font.pixelSize: 15
|
||||
textField.color: Style.current.secondaryText
|
||||
readOnly: true
|
||||
validationErrorAlignment: TextEdit.AlignRight
|
||||
validationErrorTopMargin: 8
|
||||
customHeight: 56
|
||||
}
|
||||
|
||||
Select {
|
||||
id: select
|
||||
label: ""
|
||||
model: root.contacts
|
||||
width: parent.width
|
||||
visible: !root.readOnly
|
||||
menuAlignment: Select.MenuAlignment.Left
|
||||
selectedItemView: Item {
|
||||
anchors.fill: parent
|
||||
|
@ -15,27 +15,35 @@ Item {
|
||||
property alias additionalInfo: txtAddlInfo.text
|
||||
property var selectedRecipient
|
||||
property bool readOnly: false
|
||||
height: (readOnly ? inpReadOnly.height : inpAddress.height) + txtLabel.height
|
||||
height: inpAddress.height + txtLabel.height
|
||||
//% "Invalid ethereum address"
|
||||
readonly property string addressValidationError: qsTrId("invalid-ethereum-address")
|
||||
property bool isValid: false || readOnly
|
||||
property bool isValid: false
|
||||
property bool isPending: false
|
||||
property var reset: function() {}
|
||||
readonly property var sources: [
|
||||
//% "Address"
|
||||
qsTrId("address"),
|
||||
{ text: qsTrId("address"), value: RecipientSelector.Type.Address, visible: true },
|
||||
//% "My account"
|
||||
qsTrId("my-account")
|
||||
{ text: qsTrId("my-account"), value: RecipientSelector.Type.Account, visible: true },
|
||||
{ text: qsTr("Contact"), value: RecipientSelector.Type.Contact, visible: false }
|
||||
]
|
||||
property var selectedType: RecipientSelector.Type.Address
|
||||
|
||||
function resetInternal() {
|
||||
inpAddress.resetInternal()
|
||||
selContact.resetInternal()
|
||||
selAccount.resetInternal()
|
||||
selAddressSource.resetInternal()
|
||||
isValid = false
|
||||
isPending = false
|
||||
selectedType = RecipientSelector.Type.Address
|
||||
selectedRecipient = undefined
|
||||
accounts = undefined
|
||||
contacts = undefined
|
||||
selContact.reset()
|
||||
selAccount.reset()
|
||||
selAddressSource.reset()
|
||||
isValid = Qt.binding(function() { return false || readOnly })
|
||||
}
|
||||
|
||||
enum Type {
|
||||
@ -46,20 +54,52 @@ Item {
|
||||
|
||||
function validate() {
|
||||
let isValid = true
|
||||
if (readOnly) {
|
||||
isValid = Utils.isValidAddress(selectedRecipient.address)
|
||||
if (!isValid) {
|
||||
inpReadOnly.validationError = addressValidationError
|
||||
}
|
||||
} else if (selAddressSource.selectedSource === "Address") {
|
||||
isValid = inpAddress.validate()
|
||||
} else if (selAddressSource.selectedSource === "Contact") {
|
||||
isValid = selContact.validate()
|
||||
switch (root.selectedType) {
|
||||
case RecipientSelector.Type.Address:
|
||||
isValid = inpAddress.isValid
|
||||
break
|
||||
case RecipientSelector.Type.Contact:
|
||||
isValid = selContact.isValid
|
||||
break
|
||||
case RecipientSelector.Type.Account:
|
||||
isValid = selAccount.isValid
|
||||
break
|
||||
}
|
||||
root.isValid = isValid
|
||||
return isValid
|
||||
}
|
||||
|
||||
function getSourceByType(type) {
|
||||
return root.sources.find(source => source.value === type)
|
||||
}
|
||||
|
||||
onSelectedTypeChanged: {
|
||||
if (selectedType !== undefined) {
|
||||
selAddressSource.selectedSource = getSourceByType(selectedType)
|
||||
}
|
||||
if (!selectedRecipient) {
|
||||
return
|
||||
}
|
||||
switch (root.selectedType) {
|
||||
case RecipientSelector.Type.Address:
|
||||
inpAddress.text = selectedRecipient.name || ""
|
||||
inpAddress.selectedAddress = selectedRecipient.address
|
||||
inpAddress.visible = true
|
||||
selContact.visible = selAccount.visible = false
|
||||
break
|
||||
case RecipientSelector.Type.Contact:
|
||||
selContact.selectedContact = selectedRecipient
|
||||
selContact.visible = true
|
||||
inpAddress.visible = selAccount.visible = false
|
||||
break
|
||||
case RecipientSelector.Type.Account:
|
||||
selAccount.selectedAccount = selectedRecipient
|
||||
selAccount.visible = true
|
||||
inpAddress.visible = selContact.visible = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: txtLabel
|
||||
visible: label !== ""
|
||||
@ -91,48 +131,24 @@ Item {
|
||||
anchors.right: parent.right
|
||||
spacing: 8
|
||||
|
||||
Input {
|
||||
id: inpReadOnly
|
||||
visible: root.readOnly
|
||||
Layout.preferredWidth: selAddressSource.visible ? root.inputWidth : parent.width
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
anchors.left: undefined
|
||||
anchors.right: undefined
|
||||
//% "No recipient selected"
|
||||
text: (root.selectedRecipient && root.selectedRecipient.name) ? root.selectedRecipient.name : qsTrId("no-recipient-selected")
|
||||
textField.leftPadding: 14
|
||||
textField.topPadding: 18
|
||||
textField.bottomPadding: 18
|
||||
textField.verticalAlignment: TextField.AlignVCenter
|
||||
textField.font.pixelSize: 15
|
||||
textField.color: Style.current.secondaryText
|
||||
readOnly: true
|
||||
validationErrorAlignment: TextEdit.AlignRight
|
||||
validationErrorTopMargin: 8
|
||||
customHeight: 56
|
||||
}
|
||||
|
||||
AddressInput {
|
||||
id: inpAddress
|
||||
width: root.inputWidth
|
||||
label: ""
|
||||
visible: !root.readOnly
|
||||
readOnly: root.readOnly
|
||||
visible: true
|
||||
Layout.preferredWidth: selAddressSource.visible ? root.inputWidth : parent.width
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
validationError: root.addressValidationError
|
||||
onSelectedAddressChanged: {
|
||||
if (root.readOnly) {
|
||||
if (!selAddressSource.selectedSource || (selAddressSource.selectedSource && selAddressSource.selectedSource.value !== RecipientSelector.Type.Address)) {
|
||||
return
|
||||
}
|
||||
root.selectedRecipient = { address: selectedAddress, type: RecipientSelector.Type.Address }
|
||||
}
|
||||
onIsValidChanged: {
|
||||
if (selAddressSource.selectedSource === "Address") {
|
||||
root.isValid = isValid
|
||||
}
|
||||
root.selectedRecipient.address = selectedAddress
|
||||
root.selectedRecipient.type = RecipientSelector.Type.Address
|
||||
}
|
||||
onIsValidChanged: root.validate()
|
||||
}
|
||||
|
||||
ContactSelector {
|
||||
@ -141,26 +157,22 @@ Item {
|
||||
visible: false
|
||||
width: root.inputWidth
|
||||
dropdownWidth: parent.width
|
||||
readOnly: root.readOnly
|
||||
Layout.preferredWidth: selAddressSource.visible ? root.inputWidth : parent.width
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
reset: function() {
|
||||
contacts = root.contacts
|
||||
contacts = Qt.binding(function() { return root.contacts })
|
||||
readOnly = Qt.binding(function() { return root.readOnly })
|
||||
}
|
||||
onSelectedContactChanged: {
|
||||
if (root.readOnly) {
|
||||
if (!selectedContact || !selAddressSource.selectedSource || !selectedContact.address || (selAddressSource.selectedSource && selAddressSource.selectedSource.value !== RecipientSelector.Type.Contact)) {
|
||||
return
|
||||
}
|
||||
if(selectedContact && selectedContact.address) {
|
||||
const { address, name, alias, isContact, identicon, ensVerified } = selectedContact
|
||||
root.selectedRecipient = { address, name, alias, isContact, identicon, ensVerified, type: RecipientSelector.Type.Contact }
|
||||
}
|
||||
}
|
||||
onIsValidChanged: {
|
||||
if (selAddressSource.selectedSource === "Contact") {
|
||||
root.isValid = isValid
|
||||
}
|
||||
const { address, name, alias, isContact, identicon, ensVerified } = selectedContact
|
||||
root.selectedRecipient = { address, name, alias, isContact, identicon, ensVerified, type: RecipientSelector.Type.Contact }
|
||||
}
|
||||
onIsValidChanged: root.validate()
|
||||
}
|
||||
|
||||
AccountSelector {
|
||||
@ -174,40 +186,44 @@ Item {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
reset: function() {
|
||||
accounts = root.accounts
|
||||
accounts = Qt.binding(function() { return root.accounts })
|
||||
}
|
||||
onSelectedAccountChanged: {
|
||||
if (root.readOnly || !selectedAccount) {
|
||||
if (!selectedAccount || !selAddressSource.selectedSource || (selAddressSource.selectedSource && selAddressSource.selectedSource.value !== RecipientSelector.Type.Account)) {
|
||||
return
|
||||
}
|
||||
const { address, name, iconColor, assets, fiatBalance } = selectedAccount
|
||||
root.selectedRecipient = { address, name, iconColor, assets, fiatBalance, type: RecipientSelector.Type.Account }
|
||||
}
|
||||
onIsValidChanged: root.validate()
|
||||
}
|
||||
AddressSourceSelector {
|
||||
id: selAddressSource
|
||||
visible: !root.readOnly
|
||||
sources: root.sources
|
||||
sources: root.sources.filter(source => source.visible)
|
||||
width: sourceSelectWidth
|
||||
Layout.preferredWidth: root.sourceSelectWidth
|
||||
Layout.alignment: Qt.AlignTop
|
||||
reset: function() {
|
||||
sources = root.sources
|
||||
sources = Qt.binding(function() { return root.sources.filter(source => source.visible) })
|
||||
selectedSource = root.getSourceByType(root.selectedType)
|
||||
}
|
||||
|
||||
onSelectedSourceChanged: {
|
||||
if (root.readOnly) {
|
||||
if (root.readOnly || !selectedSource) {
|
||||
return
|
||||
}
|
||||
let address, name
|
||||
switch (selectedSource) {
|
||||
case "Address":
|
||||
switch (selectedSource.value) {
|
||||
case RecipientSelector.Type.Address:
|
||||
inpAddress.visible = true
|
||||
selContact.visible = selAccount.visible = false
|
||||
root.height = Qt.binding(function() { return inpAddress.height + txtLabel.height })
|
||||
root.selectedRecipient = { address: inpAddress.selectedAddress, type: RecipientSelector.Type.Address }
|
||||
if (root.selectedType !== RecipientSelector.Type.Address) root.selectedType = RecipientSelector.Type.Address
|
||||
root.isValid = inpAddress.isValid
|
||||
break;
|
||||
case "Contact":
|
||||
case RecipientSelector.Type.Contact:
|
||||
selContact.visible = true
|
||||
inpAddress.visible = selAccount.visible = false
|
||||
root.height = Qt.binding(function() { return selContact.height + txtLabel.height })
|
||||
@ -215,9 +231,10 @@ Item {
|
||||
address = selContact.selectedContact.address
|
||||
name = selContact.selectedContact.name
|
||||
root.selectedRecipient = { address, name, alias, isContact, identicon, ensVerified, type: RecipientSelector.Type.Contact }
|
||||
if (root.selectedType !== RecipientSelector.Type.Contact) root.selectedType = RecipientSelector.Type.Contact
|
||||
root.isValid = selContact.isValid
|
||||
break;
|
||||
case "My account":
|
||||
case RecipientSelector.Type.Account:
|
||||
selAccount.visible = true
|
||||
inpAddress.visible = selContact.visible = false
|
||||
root.height = Qt.binding(function() { return selAccount.height + txtLabel.height })
|
||||
@ -225,6 +242,7 @@ Item {
|
||||
address = selAccount.selectedAccount.address
|
||||
name = selAccount.selectedAccount.name
|
||||
root.selectedRecipient = { address, name, iconColor, assets, fiatBalance, type: RecipientSelector.Type.Account }
|
||||
if (root.selectedType !== RecipientSelector.Type.Account) root.selectedType = RecipientSelector.Type.Account
|
||||
root.isValid = selAccount.isValid
|
||||
break;
|
||||
}
|
||||
|
@ -4,8 +4,8 @@ import "../imports"
|
||||
|
||||
TextField {
|
||||
font.family: Style.current.fontRegular.name
|
||||
color: Style.current.textColor
|
||||
selectByMouse: true
|
||||
color: readOnly ? Style.current.secondaryText : Style.current.textColor
|
||||
selectByMouse: !readOnly
|
||||
selectedTextColor: Style.current.textColor
|
||||
selectionColor: Style.current.secondaryHover
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ Item {
|
||||
when: !!root.toAccount && root.toAccount.type === RecipientSelector.Type.Address
|
||||
PropertyChanges {
|
||||
target: txtToPrimary
|
||||
text: root.toAccount ? root.toAccount.address : ""
|
||||
text: !!root.toAccount ? root.toAccount.address : ""
|
||||
elide: Text.ElideMiddle
|
||||
anchors.leftMargin: 190
|
||||
anchors.right: parent.right
|
||||
|
Loading…
x
Reference in New Issue
Block a user