feat(@desktop/wallet): Add copy details and repeat transaction buttons (#10904)
closes #10776
This commit is contained in:
parent
43c7258328
commit
a4731517d6
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 ""
|
||||
}
|
||||
|
|
|
@ -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: {
|
||||
|
|
Loading…
Reference in New Issue