feat(@desktop/wallet): Activity address interactions

closes #10619
This commit is contained in:
Emil Sawicki 2023-05-23 10:44:35 +02:00 committed by Iuri Matias
parent bd4bb0a566
commit fc15f82e1b
4 changed files with 405 additions and 14 deletions

View File

@ -8,7 +8,6 @@ import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Popups 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Core.Utils 0.1
import utils 1.0
@ -22,10 +21,15 @@ import "../stores"
StatusModal {
id: root
property string address: RootStore.selectedReceiveAccount.address
property string chainShortNames: ""
property string description: qsTr("Your Address")
property bool readOnly: false
QtObject {
id: d
property string selectedAccountAddress: RootStore.selectedReceiveAccount.address
property string networkPrefix
property string completeAddressWithNetworkPrefix
}
@ -91,7 +95,8 @@ StatusModal {
visible: model.isEnabled
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
cursorShape: root.readOnly ? Qt.ArrowCursor : Qt.PointingHandCursor
enabled: !root.readOnly
onClicked: selectPopup.open()
}
}
@ -103,6 +108,7 @@ StatusModal {
height: 32
icon.name: "edit_pencil"
color: Theme.palette.primaryColor3
visible: !root.readOnly
onClicked: selectPopup.open()
}
}
@ -183,7 +189,7 @@ StatusModal {
id: contactsLabel
font.pixelSize: 15
color: Theme.palette.baseColor1
text: qsTr("Your Address")
text: root.description
}
RowLayout {
id: networksLabel
@ -198,10 +204,12 @@ StatusModal {
text: shortName + ":"
visible: model.isEnabled
onVisibleChanged: {
if (root.readOnly)
return
if (visible) {
d.networkPrefix += text
root.chainShortNames += text
} else {
d.networkPrefix = d.networkPrefix.replace(text, "")
root.chainShortNames = root.chainShortNames.replace(text, "")
}
}
}
@ -212,7 +220,7 @@ StatusModal {
id: txtWalletAddress
color: Theme.palette.directColor1
font.pixelSize: 15
text: d.selectedAccountAddress
text: root.address
}
}
Column {
@ -258,7 +266,7 @@ StatusModal {
// rowData used to clone returns string. Convert it to bool for bool arithmetics
rolesOverride: [{
role: "isEnabled",
transform: (modelData) => Boolean(modelData.isEnabled)
transform: (modelData) => root.readOnly ? root.chainShortNames.includes(modelData.shortName) : Boolean(modelData.isEnabled)
}]
}
@ -293,7 +301,7 @@ StatusModal {
}
PropertyChanges {
target: d
completeAddressWithNetworkPrefix: d.selectedAccountAddress
completeAddressWithNetworkPrefix: root.address
}
},
State {
@ -313,11 +321,11 @@ StatusModal {
}
PropertyChanges {
target: copyToClipBoard
textToCopy: d.networkPrefix + txtWalletAddress.text
textToCopy: root.chainShortNames + txtWalletAddress.text
}
PropertyChanges {
target: d
completeAddressWithNetworkPrefix: d.networkPrefix + d.selectedAccountAddress
completeAddressWithNetworkPrefix: root.chainShortNames + root.address
}
}
]

View File

@ -0,0 +1,371 @@
import QtQuick 2.15
import QtQuick.Layouts 1.13
import QtQuick.Controls 2.14
import QtQuick.Window 2.12
import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core 0.1
import StatusQ.Controls 0.1
import StatusQ.Popups 0.1
import shared.controls 1.0
import utils 1.0
import shared.stores 1.0
import "../stores" as WalletStores
StatusMenu {
id: root
property var contactsStore
// TODO get those names from model
readonly property string arbiscanShortChainName: "arb"
readonly property string optimismShortChainName: "opt"
signal openSendModal(address: string)
enum AddressType {
Address,
Sender,
Receiver,
Tx,
InputData,
Contract
}
QtObject {
id: d
property string selectedAddress: ""
property string addressName: ""
property string addressEns: ""
property string addressChains: ""
property string contractName: ""
property int addressType: TransactionAddressMenu.AddressType.Address
function getViewText(target) {
switch(d.addressType) {
case TransactionAddressMenu.AddressType.Contract:
if (d.contractName.length > 0)
return qsTr("View %1 contract address on %2").arg(d.contractName).arg(target)
return qsTr("View contract address on %1").arg(target)
case TransactionAddressMenu.AddressType.InputData:
return qsTr("View input data on %1").arg(target)
case TransactionAddressMenu.AddressType.Tx:
return qsTr("View transaction on %1").arg(target)
case TransactionAddressMenu.AddressType.Sender:
return qsTr("View sender address on %1").arg(target)
case TransactionAddressMenu.AddressType.Receiver:
return qsTr("View receiver address on %1").arg(target)
default:
return qsTr("View address on %1").arg(target)
}
}
function openMenu(delegate) {
const x = delegate.width - root.contentWidth / 2
const y = delegate.height / 2 + 20
root.popup(delegate, x, y)
}
readonly property TextMetrics contentMetrics: TextMetrics {
id: contentMetrics
font.pixelSize: root.fontSettings.pixelSize
font.family: Theme.palette.baseFont.name
text: {
// Getting longest possible text
if (showOnEtherscanAction.enabled) {
return showOnEtherscanAction.text
} else if (showOnArbiscanAction.enabled) {
return showOnArbiscanAction.text
}
return showOnOptimismAction.text
}
}
}
function openSenderMenu(delegate, address) {
d.addressType = TransactionAddressMenu.AddressType.Sender
openEthAddressMenu(delegate, address, true, false)
}
function openReceiverMenu(delegate, address) {
d.addressType = TransactionAddressMenu.AddressType.Receiver
openEthAddressMenu(delegate, address)
}
function openEthAddressMenu(delegate, address) {
d.selectedAddress = address
const contactPubKey = "" // TODO retrive contact public key or contact data directly from address
let contactData = Utils.getContactDetailsAsJson(contactPubKey)
let isWalletAccount = false
const isContact = contactData.isContact
if (isContact) {
d.addressName = contactData.name
} else {
d.addressName = WalletStores.RootStore.getNameForWalletAddress(address)
isWalletAccount = d.addressName.length > 0
if (!isWalletAccount) {
d.addressName = WalletStores.RootStore.getNameForSavedWalletAddress(address)
}
}
d.addressName = contactData.isContact ? contactData.name : WalletStores.RootStore.getNameForAddress(address)
d.addressEns = RootStore.getEnsForSavedWalletAddress(address)
d.addressChains = RootStore.getChainShortNamesForSavedWalletAddress(address)
showOnEtherscanAction.enabled = true
showOnArbiscanAction.enabled = address.includes(root.arbiscanShortChainName + ":")
showOnOptimismAction.enabled = address.includes(root.optimismShortChainName + ":")
saveAddressAction.enabled = d.addressName.length === 0
editAddressAction.enabled = !isWalletAccount && !isContact && d.addressName.length > 0
copyAddressAction.isSuccessState = false
sendToAddressAction.enabled = true
showQrAction.enabled = true
d.openMenu(delegate)
}
function openTxMenu(delegate, address, chainShortName="") {
d.addressType = TransactionAddressMenu.AddressType.Tx
d.selectedAddress = address
if (chainShortName === root.arbiscanShortChainName) {
showOnArbiscanAction.enabled = true
} else if (chainShortName === root.optimismShortChainName) {
showOnOptimismAction.enabled = true
} else {
showOnEtherscanAction.enabled = true
}
d.openMenu(delegate)
}
function openContractMenu(delegate, address, chainShortName="", name="") {
d.addressType = TransactionAddressMenu.AddressType.Contract
d.contractName = name
d.selectedAddress = address
if (chainShortName === root.arbiscanShortChainName) {
showOnArbiscanAction.enabled = true
} else if (chainShortName === root.optimismShortChainName) {
showOnOptimismAction.enabled = true
} else {
showOnEtherscanAction.enabled = true
}
d.openMenu(delegate)
}
function openInputDataMenu(delegate, address) {
d.addressType = TransactionAddressMenu.AddressType.InputData
d.selectedAddress = address
d.openMenu(delegate)
}
component StatusCopyAction: StatusMenuItem {
id: copyAction
property bool isSuccessState: false
property string successText: ""
property string defaultText: ""
text: isSuccessState ? successText : defaultText
action: StatusAction {
type: copyAddressAction.isSuccessState ? StatusAction.Type.Success : StatusAction.Type.Normal
icon.name: copyAddressAction.isSuccessState ? "tiny/checkmark" : "copy"
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: containsMouse ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: {
RootStore.copyToClipboard(d.selectedAddress)
copyAction.isSuccessState = true
Backpressure.debounce(addressMenu, 2000, () => { copyAction.isSuccessState = false })()
}
}
}
onClosed: {
d.addressType = TransactionAddressMenu.AddressType.Address
d.contractName = ""
showOnEtherscanAction.enabled = false
showOnArbiscanAction.enabled = false
showOnOptimismAction.enabled = false
showQrAction.enabled = false
saveAddressAction.enabled = false
editAddressAction.enabled = false
sendToAddressAction.enabled = false
}
// Additional offset for menu icon
contentWidth: contentMetrics.width + 50
hideDisabledItems: true
StatusAction {
id: showOnEtherscanAction
enabled: false
text: d.getViewText(qsTr("Etherscan"))
assetSettings.name: "link"
onTriggered: Global.openLink("https://etherscan.io/address/%1".arg(d.selectedAddress))
}
StatusAction {
id: showOnArbiscanAction
enabled: false
text: d.getViewText(qsTr("Arbiscan"))
assetSettings.name: "link"
onTriggered: Global.openLink("https://arbiscan.io/address/%1".arg(d.selectedAddress))
}
StatusAction {
id: showOnOptimismAction
enabled: false
text: d.getViewText(qsTr("Optimism Explorer"))
assetSettings.name: "link"
onTriggered: Global.openLink("https://optimistic.etherscan.io/address/%1".arg(d.selectedAddress))
}
StatusCopyAction {
id: copyAddressAction
successText: {
switch(d.addressType) {
case TransactionAddressMenu.AddressType.Contract:
if (d.contractName.length > 0)
return qsTr("%1 contract address copied").arg(d.contractName)
return qsTr("Contract address copied")
case TransactionAddressMenu.AddressType.InputData:
return qsTr("Input data copied")
case TransactionAddressMenu.AddressType.Tx:
return qsTr("Tx hash copied")
case TransactionAddressMenu.AddressType.Sender:
return qsTr("Sender address copied")
case TransactionAddressMenu.AddressType.Receiver:
return qsTr("Receiver address copied")
default:
return qsTr("Address copied")
}
}
defaultText: {
switch(d.addressType) {
case TransactionAddressMenu.AddressType.Contract:
if (d.contractName.length > 0)
return qsTr("Copy %1 contract address").arg(d.contractName)
return qsTr("Copy contract address")
case TransactionAddressMenu.AddressType.InputData:
return qsTr("Copy input data")
case TransactionAddressMenu.AddressType.Tx:
return qsTr("Copy Tx hash")
case TransactionAddressMenu.AddressType.Sender:
return qsTr("Copy sender address")
case TransactionAddressMenu.AddressType.Receiver:
return qsTr("Copy receiver address")
default:
return qsTr("Copy address")
}
}
}
StatusAction {
id: showQrAction
enabled: false
text: {
switch(d.addressType) {
case TransactionAddressMenu.AddressType.Sender:
return qsTr("Show sender address QR")
case TransactionAddressMenu.AddressType.Receiver:
return qsTr("Show receiver address QR")
default:
return qsTr("Show address QR")
}
}
assetSettings.name: "qr"
onTriggered: {
Global.openPopup(addressQr,
{
address: d.selectedAddress,
chainShortNames: d.addressChains
})
}
}
StatusAction {
id: saveAddressAction
enabled: false
text: {
switch(d.addressType) {
case TransactionAddressMenu.AddressType.Sender:
return qsTr("Save sender address")
case TransactionAddressMenu.AddressType.Receiver:
return qsTr("Save receiver address")
default:
return qsTr("Save address")
}
}
assetSettings.name: "star-icon-outline"
onTriggered: {
Global.openPopup(addEditSavedAddress,
{
addAddress: true,
address: d.selectedAddress,
ens: d.addressEns,
chainShortNames: d.addressChains
})
}
}
StatusAction {
id: editAddressAction
enabled: false
text: qsTr("Edit saved address")
assetSettings.name: "pencil-outline"
onTriggered: Global.openPopup(addEditSavedAddress,
{
edit: true,
name: d.addressName,
address: d.selectedAddress,
ens: d.addressEns,
chainShortNames: d.addressChains
})
}
StatusAction {
id: sendToAddressAction
enabled: false
text: {
switch(d.addressType) {
case TransactionAddressMenu.AddressType.Sender:
return qsTr("Send to sender address")
case TransactionAddressMenu.AddressType.Receiver:
return qsTr("Send to receiver address")
default:
return qsTr("Send to address")
}
}
assetSettings.name: "send"
onTriggered: root.openSendModal(d.selectedAddress)
}
Component {
id: addEditSavedAddress
AddEditSavedAddressPopup {
id: addEditModal
anchors.centerIn: parent
onClosed: destroy()
contactsStore: root.contactsStore
store: WalletStores.RootStore
onSave: {
RootStore.createOrUpdateSavedAddress(name, address, false, chainShortNames, ens)
close()
}
}
}
Component {
id: addressQr
ReceiveModal {
anchors.centerIn: parent
readOnly: true
hasFloatingButtons: false
advancedHeaderComponent: Item {}
description: qsTr("Address")
}
}
}

View File

@ -175,10 +175,14 @@ QtObject {
return walletSectionSavedAddresses.getNameByAddress(address)
}
function getNameForWalletAddress(address) {
return walletSectionAccounts.getNameByAddress(address)
}
function getNameForAddress(address) {
let name = getNameForSavedWalletAddress(address)
let name = getNameForWalletAddress(address)
if (name.length === 0) {
name = walletSectionAccounts.getNameByAddress(address)
name = getNameForSavedWalletAddress(address)
}
return name
}

View File

@ -14,6 +14,7 @@ import utils 1.0
import shared.stores 1.0
import "../controls"
import "../popups"
import "../stores" as WalletStores
import ".."
import "../panels"
@ -253,4 +254,11 @@ Item {
}
}
}
TransactionAddressMenu {
id: addressMenu
contactsStore: root.contactsStore
onOpenSendModal: (address) => root.sendModal.open(address)
}
}