feat(@desktop/wallet): Account View Setting Actions

fixes #11689
This commit is contained in:
Khushboo Mehta 2023-07-28 09:57:58 +02:00 committed by Khushboo-dev-cpp
parent 5af56be60f
commit ea91cd605f
11 changed files with 351 additions and 117 deletions

View File

@ -3,12 +3,17 @@ import QtQuick 2.13
import StatusQ.Components 0.1 import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1 import StatusQ.Core.Utils 0.1
import StatusQ.Controls 0.1
import utils 1.0 import utils 1.0
StatusListItem { StatusListItem {
id: root
property var keyPair property var keyPair
signal buttonClicked()
title: keyPair ? keyPair.pairType === Constants.keycard.keyPairType.watchOnly ? qsTr("Watch only") : keyPair.name: "" title: keyPair ? keyPair.pairType === Constants.keycard.keyPairType.watchOnly ? qsTr("Watch only") : keyPair.name: ""
titleAsideText: keyPair && keyPair.pairType === Constants.keycard.keyPairType.profile ? Utils.getElidedCompressedPk(keyPair.pubKey): "" titleAsideText: keyPair && keyPair.pairType === Constants.keycard.keyPairType.profile ? Utils.getElidedCompressedPk(keyPair.pubKey): ""
asset { asset {
@ -21,7 +26,12 @@ StatusListItem {
charactersLen: 2 charactersLen: 2
isLetterIdenticon: !!keyPair && !keyPair.icon && !asset.name.toString() isLetterIdenticon: !!keyPair && !keyPair.icon && !asset.name.toString()
} }
color: Theme.palette.transparent color: {
if (sensor.containsMouse || root.highlighted) {
return Theme.palette.baseColor2
}
return Theme.palette.transparent
}
ringSettings { ringSettings {
ringSpecModel: keyPair && keyPair.pairType === Constants.keycard.keyPairType.profile ? Utils.getColorHashAsJson(root.userProfilePublicKey) : [] ringSpecModel: keyPair && keyPair.pairType === Constants.keycard.keyPairType.profile ? Utils.getColorHashAsJson(root.userProfilePublicKey) : []
ringPxSize: Math.max(asset.width / 24.0) ringPxSize: Math.max(asset.width / 24.0)
@ -42,4 +52,17 @@ StatusListItem {
titleText.font.pixelSize: 12 titleText.font.pixelSize: 12
titleText.color: Theme.palette.indirectColor1 titleText.color: Theme.palette.indirectColor1
} }
components: [
StatusRoundButton {
width: 32
height: 32
radius: 8
visible: root.sensor.containsMouse
type: StatusRoundButton.Type.Quinary
icon.name: "more"
icon.color: hovered ? Theme.palette.directColor1 : Theme.palette.baseColor1
icon.hoverColor: Theme.palette.primaryColor3
onClicked: root.buttonClicked()
}
]
} }

View File

@ -2,16 +2,71 @@ import QtQuick 2.13
import StatusQ.Components 0.1 import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import shared.controls 1.0
StatusListItem { StatusListItem {
id: root
property bool isInteractive: false
property bool copyButtonEnabled: false
property bool moreButtonEnabled: false
signal buttonClicked()
signal copyClicked()
QtObject {
id: d
readonly property var timer: Timer {}
}
statusListItemTitle.customColor: Theme.palette.baseColor1 statusListItemTitle.customColor: Theme.palette.baseColor1
statusListItemTitle.font.pixelSize: 13 statusListItemTitle.font.pixelSize: 13
statusListItemTitle.lineHeightMode: Text.FixedHeight statusListItemTitle.lineHeightMode: Text.FixedHeight
statusListItemTitle.lineHeight: 18 statusListItemTitle.lineHeight: 18
statusListItemSubTitle.customColor: Theme.palette.directColor1 statusListItemSubTitle.customColor: Theme.palette.directColor1
statusListItemSubTitle.textFormat: Qt.RichText statusListItemSubTitle.textFormat: Qt.RichText
statusListItemSubTitle.wrapMode: Qt.TextWrapAnywhere
statusListItemSubTitle.lineHeightMode: Text.FixedHeight statusListItemSubTitle.lineHeightMode: Text.FixedHeight
statusListItemSubTitle.lineHeight: 22 statusListItemSubTitle.lineHeight: 22
color: Theme.palette.transparent statusListItemSubTitle.elide: Text.ElideNone
statusListItemSubTitle.wrapMode: Text.WrapAnywhere
color: {
if (isInteractive && (sensor.containsMouse || root.highlighted)) {
return Theme.palette.baseColor2
}
return Theme.palette.transparent
}
components: [
StatusRoundButton {
width: 32
height: 32
radius: 8
visible: moreButtonEnabled && isInteractive && root.sensor.containsMouse
type: StatusRoundButton.Type.Quinary
icon.name: "more"
icon.color: hovered ? Theme.palette.directColor1 : Theme.palette.baseColor1
icon.hoverColor: Theme.palette.primaryColor3
onClicked: root.buttonClicked()
},
StatusRoundButton {
id: copyButton
property bool checked: false
width: 32
height: 32
radius: 8
visible: copyButtonEnabled && isInteractive && root.sensor.containsMouse
type: StatusRoundButton.Type.Quinary
icon.name: copyButton.checked ? "tiny/checkmark": "copy"
icon.color: copyButton.checked ? Theme.palette.successColor1 : hovered ? Theme.palette.directColor1 : Theme.palette.baseColor1
icon.hoverColor: copyButton.checked ? Theme.palette.successColor1 : Theme.palette.primaryColor3
onClicked: {
copyButton.checked = true
root.copyClicked()
d.timer.setTimeout(function() {
copyButton.checked = false
}, 1500)
}
}
]
} }

View File

@ -9,6 +9,8 @@ import StatusQ.Popups 0.1
import utils 1.0 import utils 1.0
import AppLayouts.Profile.popups 1.0
Rectangle { Rectangle {
id: root id: root
@ -75,60 +77,13 @@ Rectangle {
Loader { Loader {
id: menuLoader id: menuLoader
active: false active: false
sourceComponent: StatusMenu { sourceComponent: WalletAccountKeycardMenu {
onClosed: { onClosed: {
menuLoader.active = false menuLoader.active = false
} }
keyPair: root.keyPair
StatusAction { onRunRenameKeypairFlow: root.runRenameKeypairFlow()
text: enabled? qsTr("Show encrypted QR of keypairs on device") : "" onRunRemoveKeypairFlow: root.runRemoveKeypairFlow()
enabled: !d.isProfileKeypair &&
!model.keyPair.migratedToKeycard &&
!model.keyPair.operability === Constants.keypair.operability.nonOperable
icon.name: "qr"
icon.color: Theme.palette.primaryColor1
onTriggered: {
console.warn("TODO: show encrypted QR")
}
}
StatusAction {
text: model.keyPair.migratedToKeycard? qsTr("Stop using Keycard") : qsTr("Move keys to a Keycard")
icon.name: model.keyPair.migratedToKeycard? "keycard-crossed" : "keycard"
icon.color: Theme.palette.primaryColor1
onTriggered: {
if (model.keyPair.migratedToKeycard)
console.warn("TODO: stop using Keycard")
else
console.warn("TODO: move keys to a Keycard")
}
}
StatusAction {
text: enabled? qsTr("Rename keypair") : ""
enabled: !d.isProfileKeypair
icon.name: "edit"
icon.color: Theme.palette.primaryColor1
onTriggered: {
root.runRenameKeypairFlow()
}
}
StatusMenuSeparator {
visible: !d.isProfileKeypair
}
StatusAction {
text: enabled? qsTr("Remove keypair and associated accounts") : ""
enabled: !d.isProfileKeypair
type: StatusAction.Type.Danger
icon.name: "delete"
icon.color: Theme.palette.dangerColor1
onTriggered: {
root.runRemoveKeypairFlow()
}
}
} }
} }
}, },

View File

@ -0,0 +1,69 @@
import QtQuick 2.15
import StatusQ.Core.Theme 0.1
import StatusQ.Popups 0.1
import utils 1.0
StatusMenu {
id: root
property var keyPair
signal runRenameKeypairFlow()
signal runRemoveKeypairFlow()
QtObject {
id: d
readonly property bool isProfileKeypair: keyPair.pairType === Constants.keycard.keyPairType.profile
}
StatusAction {
text: enabled? qsTr("Show encrypted QR of keypairs on device") : ""
enabled: !d.isProfileKeypair &&
!keyPair.migratedToKeycard &&
!keyPair.operability === Constants.keypair.operability.nonOperable
icon.name: "qr"
icon.color: Theme.palette.primaryColor1
onTriggered: {
console.warn("TODO: show encrypted QR")
}
}
StatusAction {
text: keyPair.migratedToKeycard? qsTr("Stop using Keycard") : qsTr("Move keys to a Keycard")
icon.name: keyPair.migratedToKeycard? "keycard-crossed" : "keycard"
icon.color: Theme.palette.primaryColor1
onTriggered: {
if (keyPair.migratedToKeycard)
console.warn("TODO: stop using Keycard")
else
console.warn("TODO: move keys to a Keycard")
}
}
StatusAction {
text: enabled? qsTr("Rename keypair") : ""
enabled: !d.isProfileKeypair
icon.name: "edit"
icon.color: Theme.palette.primaryColor1
onTriggered: {
root.runRenameKeypairFlow()
}
}
StatusMenuSeparator {
visible: !d.isProfileKeypair
}
StatusAction {
text: enabled? qsTr("Remove keypair and associated accounts") : ""
enabled: !d.isProfileKeypair
type: StatusAction.Type.Danger
icon.name: "delete"
icon.color: Theme.palette.dangerColor1
onTriggered: {
root.runRemoveKeypairFlow()
}
}
}

View File

@ -0,0 +1,80 @@
import QtQuick 2.15
import StatusQ.Popups 0.1
import utils 1.0
import AppLayouts.Wallet.popups 1.0
StatusMenu {
id: root
property string selectedAddress
property bool areTestNetworksEnabled: false
property string preferredSharingNetworks
property var preferredSharingNetworksArray
signal copyToClipboard(string address)
function openMenu(delegate) {
const x = delegate.width - 40
const y = delegate.height / 2 + 20
root.popup(delegate, x, y)
}
StatusAction {
id: showOnEtherscanAction
text: qsTr("View address on Etherscan")
icon.name: "link"
onTriggered: {
const link = areTestNetworksEnabled ? Constants.networkExplorerLinks.goerliEtherscan : Constants.networkExplorerLinks.etherscan
Global.openLink("%1/%2/%3".arg(link).arg(Constants.networkExplorerLinks.addressPath).arg(root.selectedAddress))
}
}
StatusAction {
id: showOnArbiscanAction
text: qsTr("View address on Arbiscan")
icon.name: "link"
onTriggered: {
const link = areTestNetworksEnabled ? Constants.networkExplorerLinks.goerliArbiscan : Constants.networkExplorerLinks.arbiscan
Global.openLink("%1/%2/%3".arg(link).arg(Constants.networkExplorerLinks.addressPath).arg(root.selectedAddress))
}
}
StatusAction {
id: showOnOptimismAction
text: qsTr("View address on Optimism Explorer")
icon.name: "link"
onTriggered: {
const link = areTestNetworksEnabled ? Constants.networkExplorerLinks.goerliOptimistic : Constants.networkExplorerLinks.optimistic
Global.openLink("%1/%2/%3".arg(link).arg(Constants.networkExplorerLinks.addressPath).arg(root.selectedAddress))
}
}
StatusSuccessAction {
id: copyAddressAction
successText: qsTr("Address copied")
text: qsTr("Copy address")
icon.name: "copy"
onTriggered: root.copyToClipboard(root.selectedAddress)
}
StatusAction {
id: showQrAction
text: qsTr("Show address QR")
icon.name: "qr"
onTriggered: Global.openPopup(addressQr)
}
Component {
id: addressQr
ReceiveModal {
anchors.centerIn: parent
address: root.selectedAddress
chainShortNames: root.preferredSharingNetworks
preferredSharingNetworksArray: root.preferredSharingNetworksArray
readOnly: true
hasFloatingButtons: false
advancedHeaderComponent: null
description: qsTr("Address")
onClosed: destroy()
}
}
}

View File

@ -3,3 +3,6 @@ SetupSyncingPopup 1.0 SetupSyncingPopup.qml
AddSocialLinkModal 1.0 AddSocialLinkModal.qml AddSocialLinkModal 1.0 AddSocialLinkModal.qml
ModifySocialLinkModal 1.0 ModifySocialLinkModal.qml ModifySocialLinkModal 1.0 ModifySocialLinkModal.qml
RenameKeypairPopup 1.0 RenameKeypairPopup.qml RenameKeypairPopup 1.0 RenameKeypairPopup.qml
WalletAccountKeycardMenu 1.0 WalletAccountKeycardMenu.qml
WalletAddressMenu 1.0 WalletAddressMenu.qml
RenameAccontModal 1.0 RenameAccontModal.qml

View File

@ -104,4 +104,8 @@ QtObject {
} }
return prefChains return prefChains
} }
function copyToClipboard(textToCopy) {
globalUtils.copyToClipboard(textToCopy)
}
} }

View File

@ -109,6 +109,17 @@ SettingsContentBase {
onGoToAccountOrderView: { onGoToAccountOrderView: {
stackContainer.currentIndex = accountOrderViewIndex stackContainer.currentIndex = accountOrderViewIndex
} }
onRunRenameKeypairFlow: {
renameKeypairPopup.keyUid = model.keyPair.keyUid
renameKeypairPopup.name = model.keyPair.name
renameKeypairPopup.accounts = model.keyPair.accounts
renameKeypairPopup.active = true
}
onRunRemoveKeypairFlow: {
removeKeypairPopup.keyUid = model.keyPair.keyUid
removeKeypairPopup.name = model.keyPair.name
removeKeypairPopup.active = true
}
} }
NetworksView { NetworksView {
@ -156,6 +167,17 @@ SettingsContentBase {
userProfilePublicKey: walletStore.userProfilePublicKey userProfilePublicKey: walletStore.userProfilePublicKey
onGoBack: stackContainer.currentIndex = mainViewIndex onGoBack: stackContainer.currentIndex = mainViewIndex
onVisibleChanged: if(!visible) root.walletStore.selectedAccount = null onVisibleChanged: if(!visible) root.walletStore.selectedAccount = null
onRunRenameKeypairFlow: {
renameKeypairPopup.keyUid = keyPair.keyUid
renameKeypairPopup.name = keyPair.name
renameKeypairPopup.accounts = keyPair.accounts
renameKeypairPopup.active = true
}
onRunRemoveKeypairFlow: {
removeKeypairPopup.keyUid = keyPair.keyUid
removeKeypairPopup.name = keyPair.name
removeKeypairPopup.active = true
}
} }
DappPermissionsView { DappPermissionsView {
@ -180,5 +202,52 @@ SettingsContentBase {
image.fillMode: Image.PreserveAspectCrop image.fillMode: Image.PreserveAspectCrop
} }
} }
Loader {
id: renameKeypairPopup
active: false
property string keyUid
property string name
property var accounts
sourceComponent: RenameKeypairPopup {
accountsModule: root.walletStore.accountsModule
keyUid: renameKeypairPopup.keyUid
name: renameKeypairPopup.name
accounts: renameKeypairPopup.accounts
onClosed: {
renameKeypairPopup.active = false
}
}
onLoaded: {
renameKeypairPopup.item.open()
}
}
Loader {
id: removeKeypairPopup
active: false
property string keyUid
property string name
sourceComponent: ConfirmationDialog {
headerSettings.title: qsTr("Confirm %1 Removal").arg(removeKeypairPopup.name)
confirmationText: qsTr("You will not be able to restore viewing access to any account of this keypair in the future unless you import this keypair again.")
confirmButtonLabel: qsTr("Remove keypair")
onConfirmButtonClicked: {
root.walletStore.deleteKeypair(removeKeypairPopup.keyUid)
removeKeypairPopup.active = false
}
}
onLoaded: {
removeKeypairPopup.item.open()
}
}
} }
} }

View File

@ -9,6 +9,7 @@ import StatusQ.Core.Utils 0.1 as StatusQUtils
import AppLayouts.Wallet 1.0 import AppLayouts.Wallet 1.0
import AppLayouts.Wallet.controls 1.0 import AppLayouts.Wallet.controls 1.0
import AppLayouts.Profile.popups 1.0
import shared.popups 1.0 import shared.popups 1.0
import shared.panels 1.0 import shared.panels 1.0
@ -16,13 +17,14 @@ import utils 1.0
import SortFilterProxyModel 0.2 import SortFilterProxyModel 0.2
import "../../popups"
import "../../controls" import "../../controls"
ColumnLayout { ColumnLayout {
id: root id: root
signal goBack signal goBack
signal runRenameKeypairFlow()
signal runRemoveKeypairFlow()
property var account property var account
property var keyPair property var keyPair
@ -36,7 +38,11 @@ ColumnLayout {
readonly property bool privateKeyAccount: keyPair && keyPair.pairType ? keyPair.pairType === Constants.keycard.keyPairType.privateKeyImport: false readonly property bool privateKeyAccount: keyPair && keyPair.pairType ? keyPair.pairType === Constants.keycard.keyPairType.privateKeyImport: false
readonly property string preferredSharingNetworks: !!account ? account.preferredSharingChainIds: "" readonly property string preferredSharingNetworks: !!account ? account.preferredSharingChainIds: ""
property var preferredSharingNetworksArray: preferredSharingNetworks.split(":").filter(Boolean) property var preferredSharingNetworksArray: preferredSharingNetworks.split(":").filter(Boolean)
onPreferredSharingNetworksChanged: preferredSharingNetworksArray = preferredSharingNetworks.split(":").filter(Boolean) property string preferredSharingNetworkShortNames: walletStore.getNetworkShortNames(preferredSharingNetworks)
onPreferredSharingNetworksChanged: {
preferredSharingNetworksArray = preferredSharingNetworks.split(":")
preferredSharingNetworkShortNames = walletStore.getNetworkShortNames(preferredSharingNetworks)
}
} }
spacing: 0 spacing: 0
@ -103,11 +109,14 @@ ColumnLayout {
} }
WalletAccountDetailsListItem { WalletAccountDetailsListItem {
Layout.fillWidth: true Layout.fillWidth: true
isInteractive: true
moreButtonEnabled: true
title: qsTr("Address") title: qsTr("Address")
subTitle: { subTitle: {
let address = root.account && root.account.address ? root.account.address: "" let address = root.account && root.account.address ? root.account.address: ""
return WalletUtils.colorizedChainPrefix(walletStore.getNetworkShortNames(d.preferredSharingNetworks)) + address return WalletUtils.colorizedChainPrefix(d.preferredSharingNetworkShortNames) + address
} }
onButtonClicked: addressMenu.openMenu(this)
} }
Separator { Separator {
Layout.fillWidth: true Layout.fillWidth: true
@ -126,6 +135,7 @@ ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
keyPair: root.keyPair keyPair: root.keyPair
visible: !d.watchOnlyAccount visible: !d.watchOnlyAccount
onButtonClicked: keycardMenu.popup(this, this.width - 40, this.height / 2 + 20)
} }
Separator { Separator {
Layout.fillWidth: true Layout.fillWidth: true
@ -162,8 +172,11 @@ ColumnLayout {
WalletAccountDetailsListItem { WalletAccountDetailsListItem {
id: derivationPath id: derivationPath
Layout.fillWidth: true Layout.fillWidth: true
isInteractive: true
copyButtonEnabled: true
title: qsTr("Derivation Path") title: qsTr("Derivation Path")
subTitle: root.account ? Utils.getPathForDisplay(root.account.path) : "" subTitle: root.account ? Utils.getPathForDisplay(root.account.path) : ""
onCopyClicked: root.walletStore.copyToClipboard(root.account ? root.account.path : "")
visible: !!subTitle && !d.privateKeyAccount && !d.watchOnlyAccount visible: !!subTitle && !d.privateKeyAccount && !d.watchOnlyAccount
} }
Separator { Separator {
@ -258,4 +271,20 @@ ColumnLayout {
emojiPopup: root.emojiPopup emojiPopup: root.emojiPopup
} }
} }
WalletAddressMenu {
id: addressMenu
selectedAddress: !!root.account ? root.account.address: ""
areTestNetworksEnabled: root.walletStore.areTestNetworksEnabled
preferredSharingNetworks: d.preferredSharingNetworkShortNames
preferredSharingNetworksArray: d.preferredSharingNetworksArray
onCopyToClipboard: root.walletStore.copyToClipboard(address)
}
WalletAccountKeycardMenu {
id: keycardMenu
keyPair: root.keyPair
onRunRenameKeypairFlow: root.runRenameKeypairFlow()
onRunRemoveKeypairFlow: root.runRemoveKeypairFlow()
}
} }

View File

@ -25,6 +25,8 @@ Column {
signal goToAccountOrderView() signal goToAccountOrderView()
signal goToAccountView(var account, var keypair) signal goToAccountView(var account, var keypair)
signal goToDappPermissionsView() signal goToDappPermissionsView()
signal runRenameKeypairFlow(var model)
signal runRemoveKeypairFlow(var model)
spacing: 8 spacing: 8
@ -108,65 +110,9 @@ Column {
includeWatchOnlyAccount: walletStore.includeWatchOnlyAccount includeWatchOnlyAccount: walletStore.includeWatchOnlyAccount
onGoToAccountView: root.goToAccountView(account, keyPair) onGoToAccountView: root.goToAccountView(account, keyPair)
onToggleIncludeWatchOnlyAccount: walletStore.toggleIncludeWatchOnlyAccount() onToggleIncludeWatchOnlyAccount: walletStore.toggleIncludeWatchOnlyAccount()
onRunRenameKeypairFlow: { onRunRenameKeypairFlow: root.runRenameKeypairFlow(model)
renameKeypairPopup.keyUid = model.keyPair.keyUid onRunRemoveKeypairFlow: root.runRemoveKeypairFlow(model)
renameKeypairPopup.name = model.keyPair.name
renameKeypairPopup.accounts = model.keyPair.accounts
renameKeypairPopup.active = true
}
onRunRemoveKeypairFlow: {
removeKeypairPopup.keyUid = model.keyPair.keyUid
removeKeypairPopup.name = model.keyPair.name
removeKeypairPopup.active = true
}
} }
} }
} }
Loader {
id: renameKeypairPopup
active: false
property string keyUid
property string name
property var accounts
sourceComponent: RenameKeypairPopup {
accountsModule: root.walletStore.accountsModule
keyUid: renameKeypairPopup.keyUid
name: renameKeypairPopup.name
accounts: renameKeypairPopup.accounts
onClosed: {
renameKeypairPopup.active = false
}
}
onLoaded: {
renameKeypairPopup.item.open()
}
}
Loader {
id: removeKeypairPopup
active: false
property string keyUid
property string name
sourceComponent: ConfirmationDialog {
headerSettings.title: qsTr("Confirm %1 Removal").arg(removeKeypairPopup.name)
confirmationText: qsTr("You will not be able to restore viewing access to any account of this keypair in the future unless you import this keypair again.")
confirmButtonLabel: qsTr("Remove keypair")
onConfirmButtonClicked: {
root.walletStore.deleteKeypair(removeKeypairPopup.keyUid)
removeKeypairPopup.active = false
}
}
onLoaded: {
removeKeypairPopup.item.open()
}
}
} }

View File

@ -2,3 +2,4 @@ NetworkSelectPopup 1.0 NetworkSelectPopup.qml
ActivityFilterMenu 1.0 ActivityFilterMenu.qml ActivityFilterMenu 1.0 ActivityFilterMenu.qml
ActivityPeriodFilterSubMenu 1.0 filterSubMenus/ActivityPeriodFilterSubMenu.qml ActivityPeriodFilterSubMenu 1.0 filterSubMenus/ActivityPeriodFilterSubMenu.qml
ActivityTypeFilterSubMenu 1.0 filterSubMenus/ActivityTypeFilterSubMenu.qml ActivityTypeFilterSubMenu 1.0 filterSubMenus/ActivityTypeFilterSubMenu.qml
ReceiveModal 1.0 ReceiveModal.qml