From 7a7394628e1bcb99acc51eaf1d61e817a3eae105 Mon Sep 17 00:00:00 2001 From: Cuteivist Date: Fri, 30 Jun 2023 17:07:53 +0200 Subject: [PATCH] feat(@desktop/wallet): Handle multitransactions (#11124) closes #11071 --- .../wallet_section/activity/controller.nim | 1 - .../main/wallet_section/activity/entry.nim | 84 ++++----- .../Wallet/views/TransactionDetailView.qml | 173 +++++++++--------- .../shared/controls/TransactionAddress.qml | 2 +- .../shared/controls/TransactionDataTile.qml | 2 +- .../shared/controls/TransactionDelegate.qml | 155 ++++++++-------- ui/imports/shared/views/HistoryView.qml | 14 +- 7 files changed, 198 insertions(+), 233 deletions(-) diff --git a/src/app/modules/main/wallet_section/activity/controller.nim b/src/app/modules/main/wallet_section/activity/controller.nim index f7f8dc080f..9480355de8 100644 --- a/src/app/modules/main/wallet_section/activity/controller.nim +++ b/src/app/modules/main/wallet_section/activity/controller.nim @@ -21,7 +21,6 @@ import app_service/service/currency/service as currency_service import app_service/service/transaction/service as transaction_service import app_service/service/token/service as token_service - proc toRef*[T](obj: T): ref T = new(result) result[] = obj diff --git a/src/app/modules/main/wallet_section/activity/entry.nim b/src/app/modules/main/wallet_section/activity/entry.nim index a6f96a1d07..e6247dda50 100644 --- a/src/app/modules/main/wallet_section/activity/entry.nim +++ b/src/app/modules/main/wallet_section/activity/entry.nim @@ -93,38 +93,27 @@ QtObject: return self.transaction proc getSender*(self: ActivityEntry): string {.slot.} = - # TODO: lookup sender's name if self.isMultiTransaction(): return self.multi_transaction.fromAddress - + if self.transaction == nil: + error "getSender: ActivityEntry is not an transaction.Item" + return "" return self.transaction[].getfrom() QtProperty[string] sender: read = getSender proc getRecipient*(self: ActivityEntry): string {.slot.} = - # TODO: lookup recipient name if self.isMultiTransaction(): return self.multi_transaction.toAddress - + if self.transaction == nil: + error "getRecipient: ActivityEntry is not an transaction.Item" + return "" return self.transaction[].getTo() QtProperty[string] recipient: read = getRecipient - proc getInAmount*(self: ActivityEntry): float {.slot.} = - return float(self.extradata.inAmount) - - QtProperty[float] inAmount: - read = getInAmount - - proc getOutAmount*(self: ActivityEntry): float {.slot.} = - return float(self.extradata.outAmount) - - QtProperty[float] outAmount: - read = getOutAmount - - proc getInSymbol*(self: ActivityEntry): string {.slot.} = return self.extradata.inSymbol @@ -137,9 +126,20 @@ QtObject: QtProperty[string] outSymbol: read = getOutSymbol + proc getSymbol*(self: ActivityEntry): string {.slot.} = + if self.metadata.activityType == backend.ActivityType.Receive: + return self.getInSymbol() + return self.getOutSymbol() + + QtProperty[string] symbol: + read = getSymbol + proc getTimestamp*(self: ActivityEntry): int {.slot.} = if self.isMultiTransaction(): return self.multi_transaction.timestamp + if self.transaction == nil: + error "getTimestamp: ActivityEntry is not an transaction.Item" + return 0 # TODO: should we account for self.transaction[].isTimeStamp? return self.transaction[].getTimestamp() @@ -191,7 +191,7 @@ QtObject: proc getTotalFees*(self: ActivityEntry): QVariant {.slot.} = if self.transaction == nil: error "getTotalFees: ActivityEntry is not an transaction.Item" - return newQVariant(0) + return newQVariant(newCurrencyAmount()) return newQVariant(self.transaction[].getTotalFees()) QtProperty[QVariant] totalFees: @@ -257,47 +257,31 @@ QtObject: QtProperty[string] nonce: read = getNonce -# TODO: Replaced usage of these for in/out versions in the QML modules - proc getSymbol*(self: ActivityEntry): string {.slot.} = + proc getBlockNumber*(self: ActivityEntry): string {.slot.} = if self.transaction == nil: - error "getSymbol: ActivityEntry is not an transaction.Item" + error "getBlockNumber: ActivityEntry is not an transaction.Item" return "" + return $self.transaction[].getBlockNumber() - if self.metadata.activityType == backend.ActivityType.Receive: - return self.getInSymbol() + QtProperty[string] blockNumber: + read = getBlockNumber - return self.getOutSymbol() + proc getOutAmount*(self: ActivityEntry): float {.slot.} = + return float(self.extradata.outAmount) - QtProperty[string] symbol: - read = getSymbol + QtProperty[float] outAmount: + read = getOutAmount - proc getFromAmount*(self: ActivityEntry): float {.slot.} = - if self.isMultiTransaction(): - return self.getOutAmount() - error "getFromAmount: ActivityEntry is not a MultiTransaction" - return 0.0 + proc getInAmount*(self: ActivityEntry): float {.slot.} = + return float(self.extradata.inAmount) - QtProperty[float] fromAmount: - read = getFromAmount - - proc getToAmount*(self: ActivityEntry): float {.slot.} = - if self.isMultiTransaction(): - return self.getInAmount() - error "getToAmount: ActivityEntry is not a MultiTransaction" - return 0.0 - - QtProperty[float] toAmount: - read = getToAmount - - proc getValue*(self: ActivityEntry): float {.slot.} = - if self.isMultiTransaction(): - error "getToAmount: ActivityEntry is a MultiTransaction" - return 0.0 + QtProperty[float] inAmount: + read = getInAmount + proc getAmount*(self: ActivityEntry): float {.slot.} = if self.metadata.activityType == backend.ActivityType.Receive: return self.getInAmount() - return self.getOutAmount() - QtProperty[float] value: - read = getValue \ No newline at end of file + QtProperty[float] amount: + read = getAmount diff --git a/ui/app/AppLayouts/Wallet/views/TransactionDetailView.qml b/ui/app/AppLayouts/Wallet/views/TransactionDetailView.qml index 9fc9fb5315..f7987b8af7 100644 --- a/ui/app/AppLayouts/Wallet/views/TransactionDetailView.qml +++ b/ui/app/AppLayouts/Wallet/views/TransactionDetailView.qml @@ -39,33 +39,25 @@ Item { QtObject { id: d - readonly property bool isIncoming: root.isTransactionValid ? root.transaction.recipient.toLowerCase() === root.overview.mixedcaseAddress.toLowerCase() : false - readonly property bool isNFT: root.isTransactionValid ? root.transaction.isNFT : false - readonly property string savedAddressNameTo: root.isTransactionValid ? d.getNameForSavedWalletAddress(transaction.recipient) : "" - readonly property string savedAddressNameFrom: root.isTransactionValid ? d.getNameForSavedWalletAddress(transaction.sender): "" - readonly property string from: root.isTransactionValid ? !!savedAddressNameFrom ? savedAddressNameFrom : Utils.compactAddress(transaction.sender, 4): "" - readonly property string to: root.isTransactionValid ? !!savedAddressNameTo ? savedAddressNameTo : Utils.compactAddress(transaction.recipient, 4): "" - readonly property string savedAddressEns: root.isTransactionValid ? RootStore.getEnsForSavedWalletAddress(isIncoming ? transaction.sender : transaction.recipient) : "" - readonly property string savedAddressChains: root.isTransactionValid ? RootStore.getChainShortNamesForSavedWalletAddress(isIncoming ? transaction.sender : transaction.recipient) : "" + readonly property bool isIncoming: transactionType === Constants.TransactionType.Received readonly property string networkShortName: root.isTransactionValid ? RootStore.getNetworkShortName(transaction.chainId) : "" - readonly property string networkFullName: root.isTransactionValid ? RootStore.getNetworkFullName(transaction.chainId): "" - readonly property string networkIcon: root.isTransactionValid ? RootStore.getNetworkIcon(transaction.chainId): "" + readonly property string networkIcon: isTransactionValid ? RootStore.getNetworkIcon(transaction.chainId) : "" readonly property int blockNumber: root.isTransactionValid ? RootStore.hex2Dec(root.transaction.blockNumber) : 0 - readonly property string bridgeNetworkIcon: "" // TODO fill when bridge data is implemented - readonly property string bridgeNetworkFullname: "" // TODO fill when bridge data is implemented - readonly property string bridgeNetworkShortName: "" // TODO fill when bridge data is implemented - readonly property int bridgeBlockNumber: 0 // TODO fill when bridge data is implemented - readonly property double swapCryptoValue: 0 // TODO fill when swap data is implemented - readonly property string swapSymbol: "" // TODO fill when swap data is implemented - readonly property string symbol: root.isTransactionValid ? transaction.symbol : "" + readonly property int toBlockNumber: 0 // TODO fill when bridge data is implemented + readonly property string toNetworkIcon: "" // TODO fill when bridge data is implemented + readonly property string toNetworkShortName: "" // TODO fill when bridge data is implemented + readonly property string symbol: isTransactionValid ? transaction.symbol : "" + readonly property string inSymbol: isTransactionValid ? transaction.inSymbol : "" + readonly property string outSymbol: isTransactionValid ? transaction.outSymbol : "" readonly property var multichainNetworks: [] // TODO fill icon for networks for multichain - readonly property double cryptoValue: root.isTransactionValid ? transaction.value : 0.0 - readonly property double fiatValue: root.isTransactionValid ? RootStore.getFiatValue(cryptoValue, symbol, RootStore.currentCurrency): 0.0 - readonly property string fiatValueFormatted: root.isTransactionValid ? RootStore.formatCurrencyAmount(d.fiatValue, RootStore.currentCurrency) : "" - readonly property string cryptoValueFormatted: root.isTransactionValid ? RootStore.formatCurrencyAmount(d.cryptoValue, symbol) : "" - readonly property real feeEthValue: root.isTransactionValid && transaction.totalFees ? RootStore.getGasEthValue(transaction.totalFees.amount, 1) : 0 - readonly property real feeFiatValue: root.isTransactionValid ? RootStore.getFiatValue(d.feeEthValue, "ETH", RootStore.currentCurrency) : 0 + readonly property string fiatValueFormatted: root.isTransactionValid && !transactionHeader.isMultiTransaction ? RootStore.formatCurrencyAmount(transactionHeader.fiatValue, RootStore.currentCurrency) : "" + readonly property string cryptoValueFormatted: root.isTransactionValid && !transactionHeader.isMultiTransaction ? RootStore.formatCurrencyAmount(transaction.amount, transaction.symbol) : "" + readonly property string outFiatValueFormatted: root.isTransactionValid && transactionHeader.isMultiTransaction ? RootStore.formatCurrencyAmount(transactionHeader.outFiatValue, RootStore.currentCurrency) : "" + readonly property string outCryptoValueFormatted: root.isTransactionValid && transactionHeader.isMultiTransaction ? RootStore.formatCurrencyAmount(transaction.outAmount, transaction.outSymbol) : "" + readonly property real feeEthValue: root.isTransactionValid ? RootStore.getGasEthValue(transaction.totalFees.amount, 1) : 0 // TODO use directly? + readonly property real feeFiatValue: root.isTransactionValid ? RootStore.getFiatValue(d.feeEthValue, "ETH", RootStore.currentCurrency) : 0 // TODO use directly? readonly property int transactionType: root.isTransactionValid ? transaction.txType : Constants.TransactionType.Send + readonly property string toNetworkName: "" // TODO fill network name for bridge property string decodedInputData: "" @@ -116,26 +108,15 @@ Item { objectName: "transactionDetailHeader" width: parent.width leftPadding: 0 - - modelData: transaction - currentCurrency: RootStore.currentCurrency - cryptoValue: d.cryptoValue - fiatValue: d.fiatValue - networkIcon: d.networkIcon - networkColor: root.isTransactionValid ? RootStore.getNetworkColor(transaction.chainId): "" - networkName: d.networkFullName - swapSymbol: d.swapSymbol - bridgeNetworkName: d.bridgeNetworkFullname - symbol: d.symbol - transactionStatus: root.isTransactionValid ? transaction.status : Constants.TransactionStatus.Pending - timeStampText: root.isTransactionValid ? qsTr("Signed at %1").arg(LocaleUtils.formatDateTime(transaction.timestamp * 1000, Locale.LongFormat)): "" - addressNameTo: root.isTransactionValid ? WalletStores.RootStore.getNameForAddress(transaction.recipient): "" - addressNameFrom: root.isTransactionValid ? WalletStores.RootStore.getNameForAddress(transaction.sender): "" sensor.enabled: false - rootStore: RootStore - walletRootStore: WalletStores.RootStore color: Theme.palette.transparent state: "header" + + modelData: transaction + timeStampText: root.isTransactionValid ? qsTr("Signed at %1").arg(LocaleUtils.formatDateTime(transaction.timestamp * 1000, Locale.LongFormat)): "" + rootStore: RootStore + walletRootStore: WalletStores.RootStore + onRetryClicked: d.retryTransaction() } @@ -148,7 +129,7 @@ Item { error: transactionHeader.transactionStatus === Constants.TransactionStatus.Failed 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 + chainName: transactionHeader.networkName timeStamp: root.isTransactionValid ? transaction.timestamp: "" } @@ -157,7 +138,7 @@ Item { } WalletNftPreview { - visible: root.isTransactionValid && d.isNFT && !!transaction.nftImageUrl + visible: root.isTransactionValid && transactionHeader.isNFT && !!transaction.nftImageUrl width: Math.min(304, progressBlock.width) nftName: root.isTransactionValid ? transaction.nftName : "" nftUrl: root.isTransactionValid && !!transaction.nftImageUrl ? transaction.nftImageUrl : "" @@ -187,8 +168,7 @@ Item { RowLayout { spacing: 0 width: parent.width - height: opacity > 0 ? Math.max(implicitHeight, 85) : 0 - opacity: fromNetworkTile.visible || toNetworkTile.visible ? 1 : 0 + height: fromNetworkTile.visible || toNetworkTile.visible ? 85 : 0 TransactionDataTile { id: fromNetworkTile Layout.fillWidth: true @@ -197,9 +177,9 @@ Item { subTitle: { switch(d.transactionType) { case Constants.TransactionType.Swap: - return d.symbol + return d.outSymbol case Constants.TransactionType.Bridge: - return d.networkFullName + return transactionHeader.networkName default: return "" } @@ -207,7 +187,7 @@ Item { asset.name: { switch(d.transactionType) { case Constants.TransactionType.Swap: - return !!d.symbol ? Constants.tokenIcon(d.symbol) : "" + return !!d.outSymbol ? Constants.tokenIcon(d.outSymbol) : "" case Constants.TransactionType.Bridge: return !!d.networkIcon ? Style.svg(d.networkIcon) : "" default: @@ -224,9 +204,9 @@ Item { subTitle: { switch(d.transactionType) { case Constants.TransactionType.Swap: - return d.swapSymbol + return d.inSymbol case Constants.TransactionType.Bridge: - return d.bridgeNetworkFullname + return d.toNetworkName default: return "" } @@ -234,9 +214,9 @@ Item { asset.name: { switch(d.transactionType) { case Constants.TransactionType.Swap: - return !!d.swapSymbol ? Constants.tokenIcon(d.swapSymbol) : "" + return !!d.inSymbol ? Constants.tokenIcon(d.inSymbol) : "" case Constants.TransactionType.Bridge: - return !!d.bridgeNetworkIcon ? Style.svg(d.bridgeNetworkIcon) : "" + return !!d.toNetworkIcon ? Style.svg(d.toNetworkIcon) : "" default: return "" } @@ -262,7 +242,7 @@ Item { TransactionAddressTile { width: parent.width title: qsTr("To") - addresses: root.isTransactionValid ? [root.transaction.recipient] : [] + addresses: root.isTransactionValid && visible ? [root.transaction.recipient] : [] contactsStore: root.contactsStore rootStore: WalletStores.RootStore onButtonClicked: addressMenu.openReceiverMenu(this, addresses[0], d.networkShortName) @@ -281,7 +261,7 @@ Item { } TransactionDataTile { width: parent.width - title: qsTr("%1 Tx hash").arg(d.networkFullName) + title: qsTr("%1 Tx hash").arg(transactionHeader.networkName) subTitle: root.isTransactionValid ? root.transaction.txHash : "" visible: !!subTitle buttonIconName: "more" @@ -289,17 +269,17 @@ Item { } TransactionDataTile { width: parent.width - title: qsTr("%1 Tx hash").arg(d.bridgeNetworkFullname) + title: qsTr("%1 Tx hash").arg(d.toNetworkName) subTitle: "" // TODO fill tx hash for Bridge visible: !!subTitle buttonIconName: "more" - onButtonClicked: addressMenu.openTxMenu(this, subTitle, d.bridgeNetworkShortName) + onButtonClicked: addressMenu.openTxMenu(this, subTitle, d.toNetworkShortName) } TransactionContractTile { // Used for Bridge and Swap to display 'From' network Protocol contract address address: "" // TODO fill protocol contract address for 'from' network for Bridge and Swap symbol: "" // TODO fill protocol name for Bridge and Swap - networkName: d.networkFullName + networkName: transactionHeader.networkName shortNetworkName: d.networkShortName visible: !!subTitle && (d.transactionType === Constants.TransactionType.Bridge || d.transactionType === Constants.TransactionType.Swap) } @@ -307,15 +287,15 @@ Item { // Used to display contract address for any network address: root.isTransactionValid ? transaction.contract : "" symbol: root.isTransactionValid ? d.symbol : "" - networkName: d.networkFullName + networkName: transactionHeader.networkName shortNetworkName: d.networkShortName } TransactionContractTile { // Used for Bridge to display 'To' network Protocol contract address address: "" // TODO fill protocol contract address for 'to' network for Bridge symbol: "" // TODO fill protocol name for Bridge - networkName: d.bridgeNetworkFullname - shortNetworkName: d.bridgeNetworkShortName + networkName: d.toNetworkName + shortNetworkName: d.toNetworkShortName visible: !!subTitle && d.transactionType === Constants.TransactionType.Bridge } TransactionContractTile { @@ -337,15 +317,15 @@ Item { return "" switch(d.transactionType) { case Constants.TransactionType.Swap: - return d.swapSymbol + return d.inSymbol case Constants.TransactionType.Bridge: - return d.symbol + return d.outSymbol default: return "" } } - networkName: d.bridgeNetworkFullname - shortNetworkName: d.bridgeNetworkShortName + networkName: d.toNetworkName + shortNetworkName: d.toNetworkShortName visible: root.isTransactionValid && !!subTitle } } @@ -393,7 +373,7 @@ Item { Layout.fillHeight: true Layout.fillWidth: true title: qsTr("Network") - subTitle: d.networkFullName + subTitle: transactionHeader.networkName asset.name: !!d.networkIcon ? Style.svg("%1".arg(d.networkIcon)) : "" smallIcon: true visible: d.transactionType !== Constants.TransactionType.Bridge @@ -446,17 +426,17 @@ Item { } TransactionDataTile { width: parent.width - title: !!d.networkFullName ? qsTr("Included in Block on %1").arg(d.networkFullName) : qsTr("Included on Block") + title: !!transactionHeader.networkName ? qsTr("Included in Block on %1").arg(transactionHeader.networkName) : qsTr("Included on Block") subTitle: d.blockNumber tertiaryTitle: root.isTransactionValid ? LocaleUtils.formatDateTime(transaction.timestamp * 1000, Locale.LongFormat) : "" visible: d.blockNumber > 0 } TransactionDataTile { width: parent.width - title: !!d.bridgeNetworkFullname ? qsTr("Included in Block on %1").arg(d.bridgeNetworkFullname) : qsTr("Included on Block") - subTitle: d.bridgeBlockNumber + title: !!d.toNetworkName ? qsTr("Included in Block on %1").arg(d.toNetworkName) : qsTr("Included on Block") + subTitle: d.toBlockNumber tertiaryTitle: root.isTransactionValid ? LocaleUtils.formatDateTime(transaction.timestamp * 1000, Locale.LongFormat) : "" - visible: d.bridgeBlockNumber > 0 + visible: d.toBlockNumber > 0 } } } @@ -464,7 +444,7 @@ Item { Column { width: progressBlock.width spacing: Style.current.smallPadding - visible: !(d.isNFT && d.isIncoming) + visible: !(transactionHeader.isNFT && d.isIncoming) RowLayout { width: parent.width @@ -489,10 +469,10 @@ Item { TransactionDataTile { width: parent.width title: qsTr("Amount sent") - subTitle: d.cryptoValueFormatted - tertiaryTitle: d.fiatValueFormatted + subTitle: transactionHeader.isMultiTransaction ? d.outCryptoValueFormatted : d.cryptoValueFormatted + tertiaryTitle: transactionHeader.isMultiTransaction ? d.outFiatValueFormatted : d.fiatValueFormatted visible: { - if (d.isNFT) + if (transactionHeader.isNFT) return false switch(d.transactionType) { case Constants.TransactionType.Send: @@ -508,24 +488,24 @@ Item { width: parent.width title: transactionHeader.transactionStatus === Constants.TransactionType.Pending ? qsTr("Amount to receive") : qsTr("Amount received") subTitle: { - if (d.isNFT) + if (!root.isTransactionValid || transactionHeader.isNFT) return "" const type = d.transactionType if (type === Constants.TransactionType.Swap) { - return RootStore.formatCurrencyAmount(d.swapCryptoValue, d.swapSymbol) + return RootStore.formatCurrencyAmount(transactionHeader.inCryptoValue, d.inSymbol) } else if (type === Constants.TransactionType.Bridge) { // Reduce crypto value by fee value - const valueInCrypto = RootStore.getCryptoValue(d.fiatValue - d.feeFiatValue, d.symbol, RootStore.currentCurrency) - return RootStore.formatCurrencyAmount(valueInCrypto, d.symbol) + const valueInCrypto = RootStore.getCryptoValue(transactionHeader.fiatValue - d.feeFiatValue, d.inSymbol, RootStore.currentCurrency) + return RootStore.formatCurrencyAmount(valueInCrypto, d.inSymbol) } return "" } tertiaryTitle: { const type = d.transactionType if (type === Constants.TransactionType.Swap) { - return RootStore.formatCurrencyAmount(d.swapCryptoValue, d.swapSymbol) + return RootStore.formatCurrencyAmount(transactionHeader.inFiatValue, RootStore.currentCurrency) } else if (type === Constants.TransactionType.Bridge) { - return RootStore.formatCurrencyAmount(d.fiatValue - d.feeFiatValue, RootStore.currentCurrency) + return RootStore.formatCurrencyAmount(transactionHeader.fiatValue - d.feeFiatValue, RootStore.currentCurrency) } return "" } @@ -535,7 +515,7 @@ Item { width: parent.width title: qsTr("Fees") subTitle: { - if (!root.isTransactionValid || d.isNFT) + if (!root.isTransactionValid || transactionHeader.isNFT) return "" switch(d.transactionType) { case Constants.TransactionType.Send: @@ -552,28 +532,30 @@ Item { TransactionDataTile { width: parent.width // Using fees in this tile because of same higlight and color settings as Total - title: d.transactionType === Constants.TransactionType.Destroy || d.isNFT ? qsTr("Fees") : qsTr("Total") + title: d.transactionType === Constants.TransactionType.Destroy || transactionHeader.isNFT ? qsTr("Fees") : qsTr("Total") subTitle: { - if (d.isNFT && d.isIncoming) + if (transactionHeader.isNFT && d.isIncoming) return "" const type = d.transactionType - if (type === Constants.TransactionType.Destroy || d.isNFT) { + if (type === Constants.TransactionType.Destroy || transactionHeader.isNFT) { return RootStore.formatCurrencyAmount(d.feeEthValue, "ETH") } else if (type === Constants.TransactionType.Receive || (type === Constants.TransactionType.Buy && progressBlock.isLayer1)) { return d.cryptoValueFormatted } - return "%1 + %2".arg(d.cryptoValueFormatted).arg(RootStore.formatCurrencyAmount(d.feeEthValue, "ETH")) + const cryptoValue = transactionHeader.isMultiTransaction ? d.outCryptoValueFormatted : d.cryptoValueFormatted + return "%1 + %2".arg(cryptoValue).arg(RootStore.formatCurrencyAmount(d.feeEthValue, "ETH")) } tertiaryTitle: { - if (d.isNFT && d.isIncoming) + if (transactionHeader.isNFT && d.isIncoming) return "" const type = d.transactionType - if (type === Constants.TransactionType.Destroy || d.isNFT) { + if (type === Constants.TransactionType.Destroy || transactionHeader.isNFT) { return RootStore.formatCurrencyAmount(d.feeFiatValue, RootStore.currentCurrency) } else if (type === Constants.TransactionType.Receive || (type === Constants.TransactionType.Buy && progressBlock.isLayer1)) { return d.fiatValueFormatted } - return RootStore.formatCurrencyAmount(d.fiatValue + d.feeFiatValue, RootStore.currentCurrency) + const fiatValue = transactionHeader.isMultiTransaction ? transactionHeader.outFiatValue : transactionHeader.fiatValue + return RootStore.formatCurrencyAmount(fiatValue + d.feeFiatValue, RootStore.currentCurrency) } visible: !!subTitle highlighted: true @@ -624,7 +606,22 @@ Item { component DetailsPanel: Item { width: parent.width - height: detailsColumn.childrenRect.height + height: { + // Using childrenRect and transactionvalid properties to refresh this binding + if (!isTransactionValid || detailsColumn.childrenRect.height === 0) + return 0 + + // Height is calculated from visible children because Column doesn't handle + // visibility change properly and childrenRect.height gives different values + // comparing to manual check + var visibleHeight = 0 + for (var i = 0 ; i < detailsColumn.children.length ; i++) { + if (detailsColumn.children[i].visible) + visibleHeight += detailsColumn.children[i].height + } + return visibleHeight + } + default property alias content: detailsColumn.children Rectangle { @@ -659,7 +656,7 @@ Item { property string address: "" property string shortNetworkName: "" width: parent.width - title: qsTr("%1 %2 contract address").arg(networkName).arg(symbol) + title: visible ? qsTr("%1 %2 contract address").arg(networkName).arg(symbol) : "" subTitle: !!address && !/0x0+$/.test(address) ? address : "" buttonIconName: "more" visible: !!subTitle diff --git a/ui/imports/shared/controls/TransactionAddress.qml b/ui/imports/shared/controls/TransactionAddress.qml index 8772bb9ef0..1dfc299be8 100644 --- a/ui/imports/shared/controls/TransactionAddress.qml +++ b/ui/imports/shared/controls/TransactionAddress.qml @@ -64,7 +64,7 @@ Item { charactersLen: 2 } - implicitHeight: Math.max(identicon.height, contentColumn.height) + 12 + implicitHeight: Math.max(44, contentColumn.height) + 12 QtObject { id: d diff --git a/ui/imports/shared/controls/TransactionDataTile.qml b/ui/imports/shared/controls/TransactionDataTile.qml index 85e441da13..89fcfc42a1 100644 --- a/ui/imports/shared/controls/TransactionDataTile.qml +++ b/ui/imports/shared/controls/TransactionDataTile.qml @@ -62,7 +62,7 @@ StatusListItem { height: visible ? implicitHeight + bottomPadding : 0 radius: 0 sensor.cursorShape: Qt.ArrowCursor - color: sensor.containsMouse ? Theme.palette.baseColor5 : Style.current.transparent + color: sensor.containsMouse || highlighted ? Theme.palette.baseColor5 : Style.current.transparent // Title statusListItemTitle.customColor: Theme.palette.directColor5 diff --git a/ui/imports/shared/controls/TransactionDelegate.qml b/ui/imports/shared/controls/TransactionDelegate.qml index 960d9499c3..b5bbeb77aa 100644 --- a/ui/imports/shared/controls/TransactionDelegate.qml +++ b/ui/imports/shared/controls/TransactionDelegate.qml @@ -23,23 +23,10 @@ import shared 1.0 TransactionDelegate { id: delegate width: ListView.view.width - modelData: model - swapCryptoValue: 0.18 - swapFiatValue: 340 - swapSymbol: "SNT" - timeStampText: LocaleUtils.formatRelativeTimestamp(modelData.timestamp * 1000) - cryptoValue: 0.1234 - fiatValue: 123123 - currentCurrency: "USD" - networkName: "Optimism" - symbol: "ETH" - bridgeNetworkName: "Mainnet" - feeFiatValue: 10.34 - feeCryptoValue: 0.013 - transactionStatus: TransactionDelegate.Pending - transactionType: TransactionDelegate.Send + modelData: model.activityEntry rootStore: RootStore - loading: isModelDataValid && modelData.loadingTransaction + walletRootStore: WalletStore.RootStore + loading: isModelDataValid } \endqml @@ -52,59 +39,53 @@ StatusListItem { signal retryClicked() property var modelData - property string symbol - property string swapSymbol // TODO fill when swap data is implemented - property int transactionStatus - property string currentCurrency - property double cryptoValue - property double swapCryptoValue // TODO fill when swap data is implemented - property double fiatValue - property double swapFiatValue // TODO fill when swap data is implemented - property double feeCryptoValue // TODO fill when bridge data is implemented - property double feeFiatValue // TODO fill when bridge data is implemented - property string networkIcon - property string networkColor - property string networkName - property string bridgeNetworkName // TODO fill when bridge data is implemented - property string timeStampText - property string addressNameTo - property string addressNameFrom - property var rootStore - property var walletRootStore + property string timeStampText: isModelDataValid ? LocaleUtils.formatRelativeTimestamp(modelData.timestamp * 1000) : "" + + required property var rootStore + required property var walletRootStore readonly property bool isModelDataValid: modelData !== undefined && !!modelData + + readonly property int transactionStatus: isModelDataValid ? modelData.status : Constants.TransactionStatus.Pending + readonly property bool isMultiTransaction: isModelDataValid && modelData.isMultiTransaction + readonly property string currentCurrency: rootStore.currentCurrency + readonly property double cryptoValue: isModelDataValid && !isMultiTransaction ? modelData.amount : 0.0 + readonly property double fiatValue: isModelDataValid && !isMultiTransaction ? rootStore.getFiatValue(cryptoValue, modelData.symbol, currentCurrency) : 0.0 + readonly property double inCryptoValue: isModelDataValid ? modelData.inAmount : 0.0 + readonly property double inFiatValue: isModelDataValid && isMultiTransaction ? rootStore.getFiatValue(inCryptoValue, modelData.inSymbol, currentCurrency): 0.0 + readonly property double outCryptoValue: isModelDataValid ? modelData.outAmount : 0.0 + readonly property double outFiatValue: isModelDataValid && isMultiTransaction ? rootStore.getFiatValue(outCryptoValue, modelData.outSymbol, currentCurrency): 0.0 + readonly property double feeCryptoValue: 0.0 // TODO fill when bridge data is implemented + readonly property double feeFiatValue: 0.0 // TODO fill when bridge data is implemented + readonly property string networkColor: isModelDataValid ? rootStore.getNetworkColor(modelData.chainId) : "" + readonly property string networkName: isModelDataValid ? rootStore.getNetworkFullName(modelData.chainId) : "" + readonly property string addressNameTo: isModelDataValid ? walletRootStore.getNameForAddress(modelData.recipient) : "" + readonly property string addressNameFrom: isModelDataValid ? walletRootStore.getNameForAddress(modelData.sender) : "" readonly property bool isNFT: isModelDataValid && modelData.isNFT + readonly property string transactionValue: { - if (!isModelDataValid) - return qsTr("N/A") - if (root.isNFT) { - return modelData.nftName ? modelData.nftName : "#" + modelData.tokenID - } else { - return root.rootStore.formatCurrencyAmount(cryptoValue, symbol) - } - } - readonly property string swapTransactionValue: { if (!isModelDataValid) { return qsTr("N/A") + } else if (root.isNFT) { + return modelData.nftName ? modelData.nftName : "#" + modelData.tokenID } - return root.rootStore.formatCurrencyAmount(swapCryptoValue, swapSymbol) + return root.rootStore.formatCurrencyAmount(cryptoValue, modelData.symbol) } + readonly property string inTransactionValue: isModelDataValid && isMultiTransaction ? rootStore.formatCurrencyAmount(inCryptoValue, modelData.inSymbol) : qsTr("N/A") + readonly property string outTransactionValue: isModelDataValid && isMultiTransaction ? rootStore.formatCurrencyAmount(outCryptoValue, modelData.outSymbol) : qsTr("N/A") + readonly property string tokenImage: { if (!isModelDataValid) return "" if (root.isNFT) { return modelData.nftImageUrl ? modelData.nftImageUrl : "" } else { - return Constants.tokenIcon(root.symbol) + return Constants.tokenIcon(isMultiTransaction ? modelData.outSymbol : modelData.symbol) } } - readonly property string swapTokenImage: { - if (!isModelDataValid) - return "" - return Constants.tokenIcon(root.swapSymbol) - } + readonly property string inTokenImage: isModelDataValid ? Constants.tokenIcon(modelData.inSymbol) : "" readonly property string toAddress: !!addressNameTo ? addressNameTo : @@ -168,7 +149,7 @@ StatusListItem { const endl = "\n" const endl2 = endl + endl const type = modelData.txType - const feeEthValue = modelData.totalFees ? rootStore.getGasEthValue(modelData.totalFees.amount, 1) : 0 + const feeEthValue = rootStore.getGasEthValue(modelData.totalFees.amount, 1) // TITLE switch (type) { @@ -258,20 +239,21 @@ StatusListItem { } // SUMMARY ADRESSES + const toNetworkName = "" // TODO fill when bridge data is implemented switch (type) { case Constants.TransactionType.Swap: - details += qsTr("From") + endl + root.symbol + endl2 - details += qsTr("To") + endl + root.swapSymbol + endl2 + details += qsTr("From") + endl + modelData.outSymbol + endl2 + details += qsTr("To") + endl + modelData.inSymbol + endl2 details += qsTr("In") + endl + root.fromAddress + endl2 break case Constants.TransactionType.Bridge: details += qsTr("From") + endl + root.networkName + endl2 - details += qsTr("To") + endl + root.bridgeNetworkName + endl2 + details += qsTr("To") + endl + toNetworkName + endl2 details += qsTr("In") + endl + modelData.from + endl2 break default: - details += qsTr("From") + endl + modelData.from + endl2 - details += qsTr("To") + endl + modelData.to + endl2 + details += qsTr("From") + endl + modelData.sender + endl2 + details += qsTr("To") + endl + modelData.recipient + endl2 break } const protocolName = "" // TODO fill protocol name for Bridge and Swap @@ -283,7 +265,7 @@ StatusListItem { } const bridgeTxHash = "" // TODO fill tx hash for Bridge if (!!bridgeTxHash) { - details += qsTr("%1 Tx hash").arg(root.bridgeNetworkName) + endl + bridgeTxHash + endl2 + details += qsTr("%1 Tx hash").arg(toNetworkName) + endl + bridgeTxHash + endl2 } const protocolFromContractAddress = "" // TODO fill protocol contract address for 'from' network for Bridge and Swap if (!!protocolName && !!protocolFromContractAddress) { @@ -291,12 +273,12 @@ StatusListItem { details += protocolFromContractAddress + endl2 } if (!!modelData.contract) { - details += qsTr("%1 %2 contract address").arg(root.networkName).arg(root.symbol) + endl + details += qsTr("%1 %2 contract address").arg(root.networkName).arg(modelData.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 += qsTr("%1 %2 contract address").arg(toNetworkName).arg(protocolName) + endl details += protocolToContractAddress + endl2 } const swapContractAddress = "" // TODO fill swap contract address for Swap @@ -304,13 +286,13 @@ StatusListItem { switch (type) { case Constants.TransactionType.Swap: if (!!swapContractAddress) { - details += qsTr("%1 %2 contract address").arg(root.networkName).arg(root.swapSymbol) + endl + details += qsTr("%1 %2 contract address").arg(root.networkName).arg(modelData.toSymbol) + endl details += swapContractAddress + endl2 } break case Constants.TransactionType.Bridge: if (!!bridgeContractAddress) { - details += qsTr("%1 %2 contract address").arg(root.bridgeNetworkName).arg(root.symbol) + endl + details += qsTr("%1 %2 contract address").arg(toNetworkName).arg(modelData.symbol) + endl details += bridgeContractAddress + endl2 } break @@ -327,7 +309,7 @@ StatusListItem { if (type === Constants.TransactionType.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 + details += qsTr("Included in Block on %1").arg(toNetworkName) + endl const bridgeBlockNumber = 0 // TODO fill when bridge data is implemented details += rootStore.hex2Dec(bridgeBlockNumber) + endl2 } else { @@ -335,27 +317,29 @@ StatusListItem { } // VALUES - const fiatTransactionValue = rootStore.formatCurrencyAmount(root.fiatValue, root.currentCurrency) + const fiatTransactionValue = rootStore.formatCurrencyAmount(isMultiTransaction ? root.outFiatValue : root.fiatValue, root.currentCurrency) const feeFiatValue = rootStore.getFiatValue(feeEthValue, "ETH", root.currentCurrency) let valuesString = "" if (!root.isNFT) { switch(type) { case Constants.TransactionType.Send: + valuesString += qsTr("Amount sent %1 (%2)").arg(root.transactionValue).arg(fiatTransactionValue) + endl2 + break case Constants.TransactionType.Swap: case Constants.TransactionType.Bridge: - valuesString += qsTr("Amount sent %1 (%2)").arg(root.transactionValue).arg(fiatTransactionValue) + endl2 + valuesString += qsTr("Amount sent %1 (%2)").arg(root.outTransactionValue).arg(fiatTransactionValue) + endl2 break default: break } if (type === Constants.TransactionType.Swap) { - const crypto = rootStore.formatCurrencyAmount(root.swapCryptoValue, root.swapSymbol) - const fiat = rootStore.formatCurrencyAmount(root.swapCryptoValue, root.swapSymbol) + const crypto = rootStore.formatCurrencyAmount(root.inCryptoValue, modelData.inSymbol) + const fiat = rootStore.formatCurrencyAmount(root.inCryptoValue, modelData.inSymbol) valuesString += qsTr("Amount received %1 (%2)").arg(crypto).arg(fiat) + endl2 } else if (type === Constants.TransactionType.Bridge) { // Reduce crypto value by fee value - const valueInCrypto = rootStore.getCryptoValue(root.fiatValue - feeFiatValue, root.symbol, root.currentCurrency) - const crypto = rootStore.formatCurrencyAmount(valueInCrypto, root.symbol) + const valueInCrypto = rootStore.getCryptoValue(root.fiatValue - feeFiatValue, modelData.inSymbol, root.currentCurrency) + const crypto = rootStore.formatCurrencyAmount(valueInCrypto, modelData.inSymbol) const fiat = rootStore.formatCurrencyAmount(root.fiatValue - feeFiatValue, root.currentCurrency) valuesString += qsTr("Amount received %1 (%2)").arg(crypto).arg(fiat) + endl2 } @@ -381,7 +365,8 @@ StatusListItem { 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 + const txValue = isMultiTransaction ? root.inTransactionValue : root.transactionValue + valuesString += qsTr("Total %1 + %2 (%3)").arg(txValue).arg(feeEth).arg(fiatTransactionValue) + endl2 } } @@ -399,6 +384,7 @@ StatusListItem { rightPadding: 16 enabled: !loading + loading: !isModelDataValid color: sensor.containsMouse ? Theme.palette.baseColor5 : Style.current.transparent statusListItemIcon.active: (loading || root.asset.name) @@ -486,15 +472,15 @@ StatusListItem { spacing: 8 Row { visible: !root.loading - spacing: swapTokenImage.visible ? -tokenImage.width * 0.2 : 0 + spacing: secondTokenImage.visible ? -tokenImage.width * 0.2 : 0 StatusRoundIcon { id: tokenImage anchors.verticalCenter: parent.verticalCenter asset: root.tokenIconAsset } StatusRoundIcon { - id: swapTokenImage - visible: root.isModelDataValid && !root.isNFT && !!root.swapTokenImage &&modelData.txType === Constants.TransactionType.Swap + id: secondTokenImage + visible: root.isModelDataValid && !root.isNFT && !!root.inTokenImage &&modelData.txType === Constants.TransactionType.Swap anchors.verticalCenter: parent.verticalCenter asset: StatusAssetSettings { width: root.tokenIconAsset.width @@ -505,7 +491,7 @@ StatusListItem { bgColor: root.color isImage:root.tokenIconAsset.isImage color: root.tokenIconAsset.color - name: root.swapTokenImage + name: root.inTokenImage isLetterIdenticon: root.tokenIconAsset.isLetterIdenticon } } @@ -537,11 +523,12 @@ StatusListItem { case Constants.TransactionType.Sell: return qsTr("%1 on %2 via %3").arg(transactionValue).arg(toAddress).arg(networkName) case Constants.TransactionType.Destroy: - return qsTr("%1 at %2 via %3").arg(transactionValue).arg(toAddress).arg(networkName) + return qsTr("%1 at %2 via %3").arg(inTransactionValue).arg(toAddress).arg(networkName) case Constants.TransactionType.Swap: - return qsTr("%1 to %2 via %3").arg(transactionValue).arg(swapTransactionValue).arg(networkName) + return qsTr("%1 to %2 via %3").arg(outTransactionValue).arg(inTransactionValue).arg(networkName) case Constants.TransactionType.Bridge: - return qsTr("%1 from %2 to %3").arg(transactionValue).arg(bridgeNetworkName).arg(networkName) + let toNetworkName = "" // TODO fill when Bridge data is implemented + return qsTr("%1 from %2 to %3").arg(inTransactionValue).arg(networkName).arg(toNetworkName) default: return qsTr("%1 to %2 via %3").arg(transactionValue).arg(toAddress).arg(networkName) } @@ -574,14 +561,18 @@ StatusListItem { case Constants.TransactionType.Receive: return "+" + root.transactionValue case Constants.TransactionType.Swap: + let outValue = root.outTransactionValue + outValue = outValue.replace('<', '<') + let inValue = root.inTransactionValue + inValue = inValue.replace('<', '<') return "-%2 / +%5" .arg(Theme.palette.directColor1) - .arg(root.transactionValue) + .arg(outValue) .arg(Theme.palette.baseColor1) .arg(Theme.palette.successColor1) - .arg(root.swapTransactionValue) + .arg(inValue) case Constants.TransactionType.Bridge: - return "−" + root.rootStore.formatCurrencyAmount(feeCryptoValue, root.symbol) + return "−" + root.rootStore.formatCurrencyAmount(feeCryptoValue, modelData.symbol) default: return "" } @@ -623,8 +614,8 @@ StatusListItem { case Constants.TransactionType.Receive: return "+" + root.rootStore.formatCurrencyAmount(root.fiatValue, root.currentCurrency) case Constants.TransactionType.Swap: - return "-%1 / +%2".arg(root.rootStore.formatCurrencyAmount(root.fiatValue, root.currentCurrency)) - .arg(root.rootStore.formatCurrencyAmount(root.swapFiatValue, root.currentCurrency)) + return "-%1 / +%2".arg(root.rootStore.formatCurrencyAmount(root.outFiatValue, root.currentCurrency)) + .arg(root.rootStore.formatCurrencyAmount(root.inFiatValue, root.currentCurrency)) case Constants.TransactionType.Bridge: return "−" + root.rootStore.formatCurrencyAmount(root.feeFiatValue, root.currentCurrency) default: diff --git a/ui/imports/shared/views/HistoryView.qml b/ui/imports/shared/views/HistoryView.qml index 238dd8b4a3..3c072cccee 100644 --- a/ui/imports/shared/views/HistoryView.qml +++ b/ui/imports/shared/views/HistoryView.qml @@ -256,19 +256,11 @@ ColumnLayout { Component { id: transactionDelegate TransactionDelegate { + required property var model + required property int index width: ListView.view.width modelData: model.activityEntry - currentCurrency: RootStore.currentCurrency - cryptoValue: isModelDataValid ? modelData.value : 0.0 - fiatValue: isModelDataValid ? RootStore.getFiatValue(cryptoValue, symbol, currentCurrency): 0.0 - networkIcon: isModelDataValid ? RootStore.getNetworkIcon(modelData.chainId) : "" - networkColor: isModelDataValid ? RootStore.getNetworkColor(modelData.chainId) : "" - networkName: isModelDataValid ? RootStore.getNetworkFullName(modelData.chainId) : "" - symbol: isModelDataValid && !!modelData.symbol ? modelData.symbol : "" - transactionStatus: isModelDataValid ? modelData.status : 0 timeStampText: isModelDataValid ? LocaleUtils.formatRelativeTimestamp(modelData.timestamp * 1000, true) : "" - addressNameTo: isModelDataValid ? WalletStores.RootStore.getNameForAddress(modelData.recipient) : "" - addressNameFrom: isModelDataValid ? WalletStores.RootStore.getNameForAddress(modelData.sender) : "" rootStore: RootStore walletRootStore: WalletStores.RootStore onClicked: { @@ -309,6 +301,8 @@ ColumnLayout { model: RootStore.historyTransactions.hasMore || d.isInitialLoading ? 10 : 0 TransactionDelegate { Layout.fillWidth: true + rootStore: RootStore + walletRootStore: WalletStores.RootStore loading: true } }