feat(@desktop/wallet): Add copy details and repeat transaction buttons (#10904)

closes #10776
This commit is contained in:
Cuteivist 2023-06-13 10:18:53 +02:00 committed by GitHub
parent 43c7258328
commit a4731517d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 482 additions and 54 deletions

View File

@ -47,6 +47,11 @@ GridLayout {
}
}
StatusButton {
text: "Status with success actions"
onClicked: successActionsMenu.popup()
}
StatusMenu {
id: simpleMenu
@ -199,4 +204,19 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
fontSettings.pixelSize: 16
}
}
StatusMenu {
id: successActionsMenu
StatusSuccessAction {
text: "Action"
successText: "Success!"
icon.name: "copy"
}
StatusSuccessAction {
text: "Dismiss Action"
successText: "Dismiss success!"
icon.name: "destroy"
autoDismissMenu: true
}
}
}

View File

@ -104,15 +104,15 @@ QtObject {
locale = locale || Qt.locale()
if (!currencyAmount) {
return "N/A"
return qsTr("N/A")
}
if (typeof(currencyAmount) !== "object") {
console.warn("Wrong type for currencyAmount: " + JSON.stringify(currencyAmount))
console.trace()
return "N/A"
return qsTr("N/A")
}
if (typeof currencyAmount.amount === "undefined")
return "N/A"
return qsTr("N/A")
// Parse options
var optNoSymbol = false

View File

@ -0,0 +1,95 @@
import QtQuick 2.14
import StatusQ.Popups 0.1
/*!
\qmltype StatusSuccessAction
\inherits StatusMenuItem
\inqmlmodule StatusQ.Popups
\since StatusQ.Popups 0.1
\brief Menu action displaying success state.
The \c StatusSuccessAction visually indicate a success state after being triggered.
Success state is showed by changing action type to \c{Success}.
\qml
StatusSuccessAction.qml {
text: qsTr("Copy details")
successText: qsTr("Details copied")
}
\endqml
By default this action doesn't close the menu. The \l{autoDismissMenu} can be enabled
to enable this behavior.
*/
StatusMenuItem {
id: root
/*!
\qmlproperty bool StatusSuccessAction.qml::success
This property holds state of the action.
*/
property bool success: false
/*!
\qmlproperty string StatusSuccessAction.qml::successText
This property holds success text displayed on success state.
Default value is binded to \c{text}.
*/
property string successText: text
/*!
\qmlproperty string StatusSuccessAction.qml::successIconName
This property holds icon name displayed on success state.
Default value is \c{tiny/checkmark}.
*/
property string successIconName: "tiny/checkmark"
/*!
\qmlproperty bool StatusSuccessAction.qml::autoDismissMenu
This property enable menu closing on click.
*/
property bool autoDismissMenu: false
/*!
\qmlproperty int StatusSuccessAction.qml::timeout
This property controls how long success state is showed.
*/
property int timeout: 2000
/*! \internal Overriden signal to not close menu on click */
signal triggered()
onVisibleChanged: {
if (!visible)
success = false
}
Binding on text {
when: root.success
value: root.successText
}
action: StatusAction {
type: root.success ? StatusAction.Type.Success : StatusAction.Type.Normal
icon.name: root.success ? root.successIconName : root.icon.name
}
MouseArea {
// NOTE Using mouse area to block menu auto closing
anchors.fill: parent
hoverEnabled: true
cursorShape: containsMouse ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: {
root.triggered()
root.success = true
}
}
Timer {
id: debounceTimer
interval: root.timeout
running: root.success
onTriggered: {
root.success = false
if (root.autoDismissMenu && root.menu) {
root.menu.dismiss()
}
}
}
}

View File

@ -2,6 +2,7 @@ module StatusQ.Popups
StatusMenuSeparator 0.1 StatusMenuSeparator.qml
StatusAction 0.1 StatusAction.qml
StatusSuccessAction 0.1 StatusSuccessAction.qml
StatusMenu 0.1 StatusMenu.qml
StatusMenuItem 0.1 StatusMenuItem.qml
StatusMenuInstantiator 0.1 StatusMenuInstantiator.qml

View File

@ -166,6 +166,7 @@
<file>StatusQ/Popups/StatusColorDialog.qml</file>
<file>StatusQ/Popups/StatusMenuHeadline.qml</file>
<file>StatusQ/Popups/StatusAction.qml</file>
<file>StatusQ/Popups/StatusSuccessAction.qml</file>
<file>StatusQ/Popups/StatusMenuItem.qml</file>
<file>StatusQ/Popups/StatusMenuSeparator.qml</file>
<file>StatusQ/Popups/StatusModal.qml</file>

View File

@ -23,7 +23,7 @@ ColumnLayout {
QtObject {
id: d
readonly property bool finalized: (isLayer1 ? confirmations >= progressBar.steps : progress === duration) && !error
readonly property bool finalized: (isLayer1 ? confirmations >= progressBar.steps : progress >= duration) && !error
readonly property bool confirmed: confirmations >= progressBar.confirmationBlocks && !error
readonly property double confirmationTimeStamp: {
if (root.isLayer1) {

View File

@ -154,7 +154,6 @@ StatusMenu {
showOnOptimismAction.enabled = address.includes(Constants.networkShortChainNames.optimism + ":")
saveAddressAction.enabled = d.addressName.length === 0
editAddressAction.enabled = !isWalletAccount && !isContact && d.addressName.length > 0
copyAddressAction.isSuccessState = false
sendToAddressAction.enabled = true
showQrAction.enabled = true
@ -184,30 +183,6 @@ StatusMenu {
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 = ""
@ -230,7 +205,7 @@ StatusMenu {
id: showOnEtherscanAction
enabled: false
text: d.getViewText(qsTr("Etherscan"))
assetSettings.name: "link"
icon.name: "link"
onTriggered: {
const type = d.addressType === TransactionAddressMenu.Tx ? "tx" : "address"
const link = d.isGoerliTestnet ? Constants.networkExplorerLinks.goerliEtherscan : Constants.networkExplorerLinks.etherscan
@ -241,7 +216,7 @@ StatusMenu {
id: showOnArbiscanAction
enabled: false
text: d.getViewText(qsTr("Arbiscan"))
assetSettings.name: "link"
icon.name: "link"
onTriggered: {
const type = d.addressType === TransactionAddressMenu.Tx ? "tx" : "address"
const link = d.isGoerliTestnet ? Constants.networkExplorerLinks.goerliArbiscan : Constants.networkExplorerLinks.arbiscan
@ -252,14 +227,14 @@ StatusMenu {
id: showOnOptimismAction
enabled: false
text: d.getViewText(qsTr("Optimism Explorer"))
assetSettings.name: "link"
icon.name: "link"
onTriggered: {
const type = d.addressType === TransactionAddressMenu.Tx ? "tx" : "address"
const link = d.isGoerliTestnet ? Constants.networkExplorerLinks.goerliOptimistic : Constants.networkExplorerLinks.optimistic
Global.openLink("%1/%2/%3".arg(link).arg(type).arg(d.selectedAddress))
}
}
StatusCopyAction {
StatusSuccessAction {
id: copyAddressAction
successText: {
switch(d.addressType) {
@ -277,7 +252,7 @@ StatusMenu {
return qsTr("Address copied")
}
}
defaultText: {
text: {
switch(d.addressType) {
case TransactionAddressMenu.AddressType.Contract:
return qsTr("Copy contract address")
@ -293,6 +268,8 @@ StatusMenu {
return qsTr("Copy address")
}
}
icon.name: "copy"
onTriggered: RootStore.copyToClipboard(d.selectedAddress)
}
StatusAction {
id: showQrAction
@ -307,7 +284,7 @@ StatusMenu {
return qsTr("Show address QR")
}
}
assetSettings.name: "qr"
icon.name: "qr"
onTriggered: {
Global.openPopup(addressQr,
{
@ -329,7 +306,7 @@ StatusMenu {
return qsTr("Save address")
}
}
assetSettings.name: "star-icon-outline"
icon.name: "star-icon-outline"
onTriggered: {
Global.openPopup(addEditSavedAddress,
{
@ -344,7 +321,7 @@ StatusMenu {
id: editAddressAction
enabled: false
text: qsTr("Edit saved address")
assetSettings.name: "pencil-outline"
icon.name: "pencil-outline"
onTriggered: Global.openPopup(addEditSavedAddress,
{
edit: true,
@ -367,7 +344,7 @@ StatusMenu {
return qsTr("Send to address")
}
}
assetSettings.name: "send"
icon.name: "send"
onTriggered: root.openSendModal(d.selectedAddress)
}

View File

@ -104,7 +104,8 @@ Item {
addressNameTo: root.isTransactionValid ? WalletStores.RootStore.getNameForAddress(transaction.to): ""
addressNameFrom: root.isTransactionValid ? WalletStores.RootStore.getNameForAddress(transaction.from): ""
sensor.enabled: false
formatCurrencyAmount: RootStore.formatCurrencyAmount
rootStore: RootStore
walletRootStore: WalletStores.RootStore
color: Theme.palette.transparent
state: "header"
onRetryClicked: d.retryTransaction()
@ -117,7 +118,7 @@ Item {
id: progressBlock
width: Math.min(513, root.width)
error: transactionHeader.transactionStatus === Constants.TransactionStatus.Failed
isLayer1: RootStore.getNetworkLayer(root.transaction.chainId) == 1
isLayer1: root.isTransactionValid && RootStore.getNetworkLayer(root.transaction.chainId) == 1
confirmations: root.isTransactionValid ? Math.abs(WalletStores.RootStore.getLatestBlockNumber(root.transaction.chainId) - d.blockNumber): 0
chainName: d.networkFullName
timeStamp: root.isTransactionValid ? transaction.timestamp: ""
@ -296,7 +297,7 @@ Item {
return ""
switch(transactionHeader.transactionType) {
case Constants.TransactionType.Swap:
return transaction.contract
return "" // TODO fill swap contract address for Swap
case Constants.TransactionType.Bridge:
return "" // TODO fill swap token's contract address for 'to' network for Bridge
default:
@ -317,6 +318,7 @@ Item {
}
networkName: d.bridgeNetworkFullname
shortNetworkName: d.bridgeNetworkShortName
visible: root.isTransactionValid && !!subTitle
}
}
@ -528,6 +530,37 @@ Item {
}
}
}
Separator {
width: progressBlock.width
}
RowLayout {
width: progressBlock.width
visible: root.isTransactionValid
spacing: 8
StatusButton {
Layout.fillWidth: true
Layout.preferredHeight: copyDetailsButton.height
text: qsTr("Repeat transaction")
size: StatusButton.Small
visible: root.isTransactionValid && !root.overview.isWatchOnlyAccount && transactionHeader.transactionType === TransactionDelegate.Send
onClicked: {
root.sendModal.open(root.transaction.to)
// TODO handle other types
}
}
StatusButton {
id: copyDetailsButton
Layout.fillWidth: true
text: qsTr("Copy details")
icon.name: "copy"
icon.width: 20
icon.height: 20
size: StatusButton.Small
onClicked: RootStore.copyToClipboard(transactionHeader.getDetailsString())
}
}
}
}

View File

@ -38,7 +38,7 @@ import shared 1.0
feeCryptoValue: 0.013
transactionStatus: TransactionDelegate.Pending
transactionType: TransactionDelegate.Send
formatCurrencyAmount: RootStore.formatCurrencyAmount
rootStore: RootStore
loading: isModelDataValid && modelData.loadingTransaction
}
\endqml
@ -71,7 +71,8 @@ StatusListItem {
property string timeStampText
property string addressNameTo
property string addressNameFrom
property var formatCurrencyAmount: function() {}
property var rootStore
property var walletRootStore
readonly property bool isModelDataValid: modelData !== undefined && !!modelData
readonly property bool isNFT: isModelDataValid && modelData.isNFT
@ -81,14 +82,14 @@ StatusListItem {
if (root.isNFT) {
return modelData.nftName ? modelData.nftName : "#" + modelData.tokenID
} else {
return root.formatCurrencyAmount(cryptoValue, symbol)
return root.rootStore.formatCurrencyAmount(cryptoValue, symbol)
}
}
readonly property string swapTransactionValue: {
if (!isModelDataValid) {
return qsTr("N/A")
}
return root.formatCurrencyAmount(swapCryptoValue, swapSymbol)
return root.rootStore.formatCurrencyAmount(swapCryptoValue, swapSymbol)
}
readonly property string tokenImage: {
@ -164,6 +165,240 @@ StatusListItem {
property int subtitlePixelSize: 13
}
function getDetailsString() {
let details = ""
const endl = "\n"
const endl2 = endl + endl
const type = root.transactionType
const feeEthValue = rootStore.getGasEthValue(modelData.totalFees.amount, 1)
// TITLE
switch (type) {
case TransactionDelegate.TransactionType.Send:
details += qsTr("Send transaction details" + endl2)
break
case TransactionDelegate.TransactionType.Receive:
details += qsTr("Receive transaction details") + endl2
break
case TransactionDelegate.TransactionType.Buy:
details += qsTr("Buy transaction details") + endl2
break
case TransactionDelegate.TransactionType.Sell:
details += qsTr("Sell transaction details") + endl2
break
case TransactionDelegate.TransactionType.Destroy:
details += qsTr("Destroy transaction details") + endl2
break
case TransactionDelegate.TransactionType.Swap:
details += qsTr("Swap transaction details") + endl2
break
case TransactionDelegate.TransactionType.Bridge:
details += qsTr("Bridge transaction details") + endl2
break
default:
break
}
details += qsTr("Summary") + endl
switch(root.transactionType) {
case TransactionDelegate.TransactionType.Buy:
case TransactionDelegate.TransactionType.Sell:
case TransactionDelegate.TransactionType.Destroy:
case TransactionDelegate.TransactionType.Swap:
case TransactionDelegate.TransactionType.Bridge:
details += subTitle + endl2
break
default:
details += qsTr("%1 from %2 to %3 via %4").arg(transactionValue).arg(fromAddress).arg(toAddress).arg(networkName) + endl2
break
}
if (root.isNFT) {
details += qsTr("Token ID") + endl + modelData.tokenID + endl2
details += qsTr("Token name") + endl + modelData.nftName + endl2
}
// PROGRESS
const isLayer1 = rootStore.getNetworkLayer(modelData.chainId) === 1
// A block on layer1 is every 12s
const confirmationTimeStamp = isLayer1 ? modelData.timestamp + 12 * 4 : modelData.timestamp
// A block on layer1 is every 12s
const finalisationTimeStamp = isLayer1 ? modelData.timestamp + 12 * 64 : modelData.timestamp + 604800 // 7 days in seconds
switch(transactionStatus) {
case TransactionDelegate.TransactionStatus.Pending:
details += qsTr("Status") + endl
details += qsTr("Pending on %1").arg(root.networkName) + endl2
break
case TransactionDelegate.TransactionStatus.Failed:
details += qsTr("Status") + endl
details += qsTr("Failed on %1").arg(root.networkName) + endl2
break
case TransactionDelegate.TransactionStatus.Verified: {
const timestampString = LocaleUtils.formatDateTime(modelData.timestamp * 1000, Locale.LongFormat)
details += qsTr("Status") + endl
const epoch = parseFloat(Math.abs(walletRootStore.getLatestBlockNumber(modelData.chainId) - rootStore.hex2Dec(modelData.blockNumber)).toFixed(0)).toLocaleString()
details += qsTr("Finalised in epoch %1").arg(epoch.toFixed(0)) + endl2
details += qsTr("Signed") + endl + root.timestampString + endl2
details += qsTr("Confirmed") + endl
details += LocaleUtils.formatDateTime(confirmationTimeStamp * 1000, Locale.LongFormat) + endl2
break
}
case TransactionDelegate.TransactionStatus.Finished: {
const timestampString = LocaleUtils.formatDateTime(modelData.timestamp * 1000, Locale.LongFormat)
details += qsTr("Status") + endl
const epoch = Math.abs(walletRootStore.getLatestBlockNumber(modelData.chainId) - rootStore.hex2Dec(modelData.blockNumber))
details += qsTr("Finalised in epoch %1").arg(epoch.toFixed(0)) + endl2
details += qsTr("Signed") + endl + timestampString + endl2
details += qsTr("Confirmed") + endl
details += LocaleUtils.formatDateTime(confirmationTimeStamp * 1000, Locale.LongFormat) + endl2
details += qsTr("Finalised") + endl
details += LocaleUtils.formatDateTime(finalisationTimeStamp * 1000, Locale.LongFormat) + endl2
break
}
default:
break
}
// SUMMARY ADRESSES
switch (type) {
case TransactionDelegate.Swap:
details += qsTr("From") + endl + root.symbol + endl2
details += qsTr("To") + endl + root.swapSymbol + endl2
details += qsTr("In") + endl + root.fromAddress + endl2
break
case TransactionDelegate.Bridge:
details += qsTr("From") + endl + root.networkName + endl2
details += qsTr("To") + endl + root.bridgeNetworkName + endl2
details += qsTr("In") + endl + modelData.from + endl2
break
default:
details += qsTr("From") + endl + modelData.from + endl2
details += qsTr("To") + endl + modelData.to + endl2
break
}
const protocolName = "" // TODO fill protocol name for Bridge and Swap
if (!!protocolName) {
details += qsTr("Using") + endl + protocolName + endl2
}
if (!!modelData.txHash) {
details += qsTr("%1 Tx hash").arg(root.networkName) + endl + modelData.txHash + endl2
}
const bridgeTxHash = "" // TODO fill tx hash for Bridge
if (!!bridgeTxHash) {
details += qsTr("%1 Tx hash").arg(root.bridgeNetworkName) + endl + bridgeTxHash + endl2
}
const protocolFromContractAddress = "" // TODO fill protocol contract address for 'from' network for Bridge and Swap
if (!!protocolName && !!protocolFromContractAddress) {
details += qsTr("%1 %2 contract address").arg(root.networkName).arg(protocolName) + endl
details += protocolFromContractAddress + endl2
}
if (!!modelData.contract) {
details += qsTr("%1 %2 contract address").arg(root.networkName).arg(root.symbol) + endl
details += modelData.contract + endl2
}
const protocolToContractAddress = "" // TODO fill protocol contract address for 'to' network for Bridge
if (!!protocolToContractAddress && !!protocolName) {
details += qsTr("%1 %2 contract address").arg(root.bridgeNetworkName).arg(protocolName) + endl
details += protocolToContractAddress + endl2
}
const swapContractAddress = "" // TODO fill swap contract address for Swap
const bridgeContractAddress = "" // TODO fill token's contract address for 'to' network for Bridge
switch (type) {
case TransactionDelegate.Swap:
if (!!swapContractAddress) {
details += qsTr("%1 %2 contract address").arg(root.networkName).arg(root.swapSymbol) + endl
details += swapContractAddress + endl2
}
break
case TransactionDelegate.Bridge:
if (!!bridgeContractAddress) {
details += qsTr("%1 %2 contract address").arg(root.bridgeNetworkName).arg(root.symbol) + endl
details += bridgeContractAddress + endl2
}
break
default:
break
}
// SUMMARY DATA
if (type !== TransactionDelegate.Bridge) {
details += qsTr("Network") + endl + networkName + endl2
}
details += qsTr("Token format") + endl + modelData.type.toUpperCase() + endl2
details += qsTr("Nonce") + endl + rootStore.hex2Dec(modelData.nonce) + endl2
if (type === TransactionDelegate.Bridge) {
details += qsTr("Included in Block on %1").arg(networkName) + endl
details += rootStore.hex2Dec(modelData.blockNumber) + endl2
details += qsTr("Included in Block on %1").arg(bridgeNetworkName) + endl
const bridgeBlockNumber = 0 // TODO fill when bridge data is implemented
details += rootStore.hex2Dec(bridgeBlockNumber) + endl2
} else {
details += qsTr("Included in Block") + endl + rootStore.hex2Dec(modelData.blockNumber) + endl2
}
// VALUES
const fiatTransactionValue = rootStore.formatCurrencyAmount(root.fiatValue, root.currentCurrency)
const feeFiatValue = rootStore.getFiatValue(feeEthValue, "ETH", root.currentCurrency)
let valuesString = ""
if (!root.isNFT) {
switch(type) {
case TransactionDelegate.Send:
case TransactionDelegate.Swap:
case TransactionDelegate.Bridge:
valuesString += qsTr("Amount sent %1 (%2)").arg(root.transactionValue).arg(fiatTransactionValue) + endl2
break
default:
break
}
if (type === TransactionDelegate.Swap) {
const crypto = rootStore.formatCurrencyAmount(d.swapCryptoValue, d.swapSymbol)
const fiat = rootStore.formatCurrencyAmount(d.swapCryptoValue, d.swapSymbol)
valuesString += qsTr("Amount received %1 (%2)").arg(crypto).arg(fiat) + endl2
} else if (type === TransactionDelegate.Bridge) {
// Reduce crypto value by fee value
const valueInCrypto = rootStore.getCryptoValue(root.fiatValue - feeFiatValue, root.symbol, root.currentCurrency)
const crypto = rootStore.formatCurrencyAmount(valueInCrypto, d.symbol)
const fiat = rootStore.formatCurrencyAmount(root.fiatValue - feeFiatValue, root.currentCurrency)
valuesString += qsTr("Amount received %1 (%2)").arg(crypto).arg(fiat) + endl2
}
switch(type) {
case TransactionDelegate.Send:
case TransactionDelegate.Swap:
case TransactionDelegate.Bridge:
const feeValue = LocaleUtils.currencyAmountToLocaleString(modelData.totalFees)
const feeFiat = rootStore.formatCurrencyAmount(feeFiatValue, root.currentCurrency)
valuesString += qsTr("Fees %1 (%2)").arg(feeValue).arg(feeFiat) + endl2
break
default:
break
}
}
if (!root.isNFT || type !== TransactionDelegate.Receive) {
if (type === TransactionDelegate.Destroy || root.isNFT) {
const feeCrypto = rootStore.formatCurrencyAmount(feeEthValue, "ETH")
const feeFiat = rootStore.formatCurrencyAmount(feeFiatValue, root.currentCurrency)
valuesString += qsTr("Fees %1 (%2)").arg(feeCrypto).arg(feeFiat) + endl2
} else if (type === TransactionDelegate.Receive || (type === TransactionDelegate.Buy && isLayer1)) {
valuesString += qsTr("Total %1 (%2)").arg(root.transactionValue).arg(fiatTransactionValue) + endl2
} else {
const feeEth = rootStore.formatCurrencyAmount(feeEthValue, "ETH")
valuesString += qsTr("Total %1 + %2 (%3)").arg(root.transactionValue).arg(feeEth).arg(fiatTransactionValue) + endl2
}
}
if (valuesString !== "") {
const timestampString = LocaleUtils.formatDateTime(modelData.timestamp * 1000, Locale.LongFormat)
details += qsTr("Values at %1").arg(timestampString) + endl2
details += valuesString + endl2
}
// Remove locale specific number separator
details = details.replace(/\ /, ' ')
// Remove empty new lines at the end
return details.replace(/[\r\n\s]*$/, '')
}
rightPadding: 16
enabled: !loading
color: sensor.containsMouse ? Theme.palette.baseColor5 : Theme.palette.statusListItem.backgroundColor
@ -329,7 +564,7 @@ StatusListItem {
switch(root.transactionType) {
case Constants.TransactionType.Send:
case Constants.TransactionType.Sell:
return "-" + root.transactionValue
return "" + root.transactionValue
case Constants.TransactionType.Buy:
case Constants.TransactionType.Receive:
return "+" + root.transactionValue
@ -341,7 +576,7 @@ StatusListItem {
.arg(Theme.palette.successColor1)
.arg(root.swapTransactionValue)
case Constants.TransactionType.Bridge:
return "-" + root.formatCurrencyAmount(feeCryptoValue, root.symbol)
return "" + root.rootStore.formatCurrencyAmount(feeCryptoValue, root.symbol)
default:
return ""
}
@ -376,14 +611,14 @@ StatusListItem {
case Constants.TransactionType.Send:
case Constants.TransactionType.Sell:
case Constants.TransactionType.Buy:
return "-" + root.formatCurrencyAmount(root.fiatValue, root.currentCurrency)
return "" + root.rootStore.formatCurrencyAmount(root.fiatValue, root.currentCurrency)
case Constants.TransactionType.Receive:
return "+" + root.formatCurrencyAmount(root.fiatValue, root.currentCurrency)
return "+" + root.rootStore.formatCurrencyAmount(root.fiatValue, root.currentCurrency)
case Constants.TransactionType.Swap:
return "-%1 / +%2".arg(root.formatCurrencyAmount(root.fiatValue, root.currentCurrency))
.arg(root.formatCurrencyAmount(root.swapFiatValue, root.currentCurrency))
return "-%1 / +%2".arg(root.rootStore.formatCurrencyAmount(root.fiatValue, root.currentCurrency))
.arg(root.rootStore.formatCurrencyAmount(root.swapFiatValue, root.currentCurrency))
case Constants.TransactionType.Bridge:
return "-" + root.formatCurrencyAmount(root.feeFiatValue, root.currentCurrency)
return "" + root.rootStore.formatCurrencyAmount(root.feeFiatValue, root.currentCurrency)
default:
return ""
}

View File

@ -7,6 +7,7 @@ import StatusQ.Core 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Popups 0.1
import SortFilterProxyModel 0.2
@ -190,6 +191,64 @@ ColumnLayout {
onAtYEndChanged: if(atYEnd && RootStore.historyTransactions.count > 0 && RootStore.historyTransactions.hasMore) fetchHistory()
}
StatusMenu {
id: delegateMenu
hideDisabledItems: true
property var transaction
property var transactionDelegate
function openMenu(delegate, mouse) {
if (!delegate || !delegate.modelData)
return
delegateMenu.transactionDelegate = delegate
delegateMenu.transaction = delegate.modelData
repeatTransactionAction.enabled = !overview.isWatchOnlyAccount && delegate.transactionType === TransactionDelegate.Send
popup(delegate, mouse.x, mouse.y)
}
onClosed: {
delegateMenu.transaction = null
delegateMenu.transactionDelegate = null
}
StatusAction {
id: repeatTransactionAction
text: qsTr("Repeat transaction")
enabled: false
icon.name: "rotate"
onTriggered: {
if (!delegateMenu.transaction)
return
root.sendModal.open(delegateMenu.transaction.to)
}
}
StatusSuccessAction {
text: qsTr("Copy details")
successText: qsTr("Details copied")
icon.name: "copy"
onTriggered: {
if (!delegateMenu.transactionDelegate)
return
RootStore.copyToClipboard(delegateMenu.transactionDelegate.getDetailsString())
}
}
StatusMenuSeparator {
visible: filterAction.enabled
}
StatusAction {
id: filterAction
enabled: false
text: qsTr("Filter by similar")
icon.name: "filter"
onTriggered: {
// TODO apply filter
}
}
}
Component {
id: transactionDelegate
TransactionDelegate {
@ -207,8 +266,15 @@ ColumnLayout {
timeStampText: isModelDataValid ? LocaleUtils.formatRelativeTimestamp(modelData.timestamp * 1000) : ""
addressNameTo: isModelDataValid ? WalletStores.RootStore.getNameForAddress(modelData.to) : ""
addressNameFrom: isModelDataValid ? WalletStores.RootStore.getNameForAddress(modelData.from) : ""
formatCurrencyAmount: RootStore.formatCurrencyAmount
onClicked: launchTransactionDetail(modelData)
rootStore: RootStore
walletRootStore: WalletStores.RootStore
onClicked: {
if (mouse.button === Qt.RightButton) {
delegateMenu.openMenu(this, mouse, modelData)
} else {
launchTransactionDetail(modelData)
}
}
loading: isModelDataValid ? modelData.loadingTransaction : false
Component.onCompleted: {