From 7b2c886ddc0b82cc2a01ec1b7248f49479f32b86 Mon Sep 17 00:00:00 2001 From: rating89us <45968869+rating89us@users.noreply.github.com> Date: Thu, 3 Jun 2021 19:06:12 +0200 Subject: [PATCH] History: display details of transactions with multiple recipients --- components/TextPlain.qml | 1 + components/Tooltip.qml | 1 + js/TxUtils.js | 28 ++++- pages/History.qml | 246 +++++++++++++++++++++++++++++---------- 4 files changed, 209 insertions(+), 67 deletions(-) diff --git a/components/TextPlain.qml b/components/TextPlain.qml index 1dfe32fd..c05a782f 100644 --- a/components/TextPlain.qml +++ b/components/TextPlain.qml @@ -16,6 +16,7 @@ Text { property alias tooltipLeft: tooltip.tooltipLeft property alias tooltipIconVisible: tooltip.tooltipIconVisible property alias tooltipPopup: tooltip.tooltipPopup + property alias tooltipWrapMode: tooltip.tooltipWrapMode font.family: MoneroComponents.Style.fontMedium.name font.bold: false font.pixelSize: 14 diff --git a/components/Tooltip.qml b/components/Tooltip.qml index b33d90ef..bed32272 100644 --- a/components/Tooltip.qml +++ b/components/Tooltip.qml @@ -36,6 +36,7 @@ import "." as MoneroComponents Rectangle { property alias text: tooltip.text property alias tooltipPopup: popup + property alias tooltipWrapMode: tooltip.wrapMode property bool tooltipIconVisible: false property bool tooltipLeft: false property bool tooltipBottom: tooltipIconVisible ? false : true diff --git a/js/TxUtils.js b/js/TxUtils.js index 31f8553c..6aea325f 100644 --- a/js/TxUtils.js +++ b/js/TxUtils.js @@ -2,13 +2,33 @@ function destinationsToAmount(destinations){ // Gets amount from destinations line // input: "20.000000000000: 9tLGyK277MnYrDc7Vzi6TB1pJvstFoviziFwsqQNFbwA9rvg5RxYVYjEezFKDjvDHgAzTELJhJHVx6JAaWZKeVqSUZkXeKk" // returns: 20.000000000000 - return destinations.split(" ")[0].split(":")[0]; + var numberOfDestinations = (destinations.match(/:/g) || []).length; + var amountList = ""; + for (var i = 0; i < numberOfDestinations; i++) { + var destinationAndAmount = destinations.split("
")[i]; + var amount = destinationAndAmount.split(":")[0]; + if (i+1 != numberOfDestinations) { + amountList += amount + " "; + } else { + amountList += amount; + } + } + return amountList; } function destinationsToAddress(destinations){ - var address = destinations.split(" ")[1]; - if(address === undefined) return "" - return address; + var numberOfDestinations = (destinations.match(/:/g) || []).length; + var addressList = ""; + for (var i = 0; i < numberOfDestinations; i++) { + var destinationAndAmount = destinations.split("
")[i]; + var address = destinationAndAmount.split(": ")[1]; + if (i+1 != numberOfDestinations) { + addressList += address + " "; + } else { + addressList += address; + } + } + return addressList; } function addressTruncate(address, range){ diff --git a/pages/History.qml b/pages/History.qml index 03d30c5e..9dcd3741 100644 --- a/pages/History.qml +++ b/pages/History.qml @@ -573,7 +573,7 @@ Rectangle { anchors.right: parent ? parent.right : undefined height: { if(!collapsed) return 60; - return 320; + return 200 + middleColumn.height; } color: { if(!collapsed) return "transparent" @@ -628,9 +628,10 @@ Rectangle { Layout.preferredHeight: 60 ColumnLayout { + id: leftColumn spacing: 0 clip: true - Layout.preferredHeight: 120 + Layout.preferredHeight: 100 + amountRectangle.height Layout.minimumWidth: 180 Rectangle { @@ -656,23 +657,63 @@ Rectangle { } Rectangle { + id: amountRectangle color: "transparent" Layout.fillWidth: true - Layout.preferredHeight: 20 + Layout.preferredHeight: amountColumn.height + Component.onCompleted: { + var amountListArray = []; + amountListArray = amountList.split(" "); + for (var i = 0; i < amountListArray.length; i++) { + amountFieldModel.append({amountArray: amountListArray[i]}); + } + } - MoneroComponents.TextPlain { - font.family: MoneroComponents.Style.fontRegular.name - font.pixelSize: 15 - text: (amount == 0 ? qsTr("Unknown amount") : displayAmount) + translationManager.emptyString - color: MoneroComponents.Style.defaultFontColor - anchors.verticalCenter: parent.verticalCenter + ListModel { + id: amountFieldModel + } - MouseArea { - state: "copyable" - anchors.fill: parent - hoverEnabled: true - onEntered: parent.color = MoneroComponents.Style.orange - onExited: parent.color = MoneroComponents.Style.defaultFontColor + ColumnLayout { + id: amountColumn + + Repeater { + id: amountFieldRepeater + model: amountFieldModel + + MoneroComponents.TextPlain { + id: amountField + font.family: MoneroComponents.Style.fontRegular.name + font.pixelSize: 15 + textFormat: Text.RichText + color: MoneroComponents.Style.defaultFontColor + text: { + if (numberOfDestinations > 1) { + if(!collapsed) { + if (index == 0) { + return displayAmount; + } else { + return ""; + } + } else { + return amountFieldModel.get(index).amountArray + " XMR" + } + } else { + if (amount == 0) { + return qsTr("Unknown amount") + translationManager.emptyString + } else { + return displayAmount; + } + } + } + + MouseArea { + state: "copyable" + anchors.fill: parent + hoverEnabled: true + onEntered: parent.color = MoneroComponents.Style.orange + onExited: parent.color = MoneroComponents.Style.defaultFontColor + } + } } } } @@ -740,9 +781,10 @@ Rectangle { } ColumnLayout { + id: middleColumn spacing: 0 clip: true - Layout.preferredHeight: 120 + Layout.preferredHeight: 100 + addressRectangle.height Layout.minimumWidth: 230 Rectangle { @@ -768,51 +810,101 @@ Rectangle { } Rectangle { + id: addressRectangle color: "transparent" Layout.fillWidth: true - Layout.preferredHeight: 20 + Layout.preferredHeight: addressColumn.height + Component.onCompleted: { + var addressListArray = []; + addressListArray = addressList.split(" "); + for (var i = 0; i < addressListArray.length; i++) { + addressFieldModel.append({addressArray: addressListArray[i]}); + } + } - MoneroComponents.TextPlain { - id: addressField - font.family: MoneroComponents.Style.fontRegular.name - font.pixelSize: 15 - text: { - if (isout) { - if (address) { - return (addressBookName ? FontAwesome.addressBook + " " + addressBookName : TxUtils.addressTruncate(address, 8)); - } - if (amount != 0) { - return qsTr("Unknown recipient") + translationManager.emptyString; - } else { - return qsTr("My wallet") + translationManager.emptyString; - } - } else { - if (receivingAddress) { - if (subaddrIndex == 0) { - return qsTr("Address") + " #0" + " (" + qsTr("Primary address") + ")" + translationManager.emptyString; + ListModel { + id: addressFieldModel + } + + ColumnLayout { + id: addressColumn + + Repeater { + id: addressFieldRepeater + model: addressFieldModel + + MoneroComponents.TextPlain { + id: addressField + font.family: MoneroComponents.Style.fontRegular.name + font.pixelSize: 15 + textFormat: Text.RichText + color: MoneroComponents.Style.defaultFontColor + property var addressFromAddressListArray: "" + tooltip: numberOfDestinations > 1 ? (!collapsed ? "" : addressFromAddressListArray) + : address ? address + : receivingAddress ? receivingAddress + : "" + tooltipWrapMode: Text.Wrap + text: { + if (numberOfDestinations > 1) { + if(!collapsed) { + if (index == 0) { + return qsTr("Multiple recipients") + " (" + numberOfDestinations + ")" + translationManager.emptyString; + } else { + return ""; + } + } else { + addressFromAddressListArray = addressFieldModel.get(index).addressArray; + var addressOnAddressBook = currentWallet ? currentWallet.addressBook.getDescription(addressFromAddressListArray) : null; + if (addressOnAddressBook) { + return FontAwesome.addressBook + " " + addressOnAddressBook; + } else { + return TxUtils.addressTruncate(addressFieldModel.get(index).addressArray, 8); + } + } } else { - if (receivingAddressLabel) { - return qsTr("Address") + " #" + subaddrIndex + " (" + receivingAddressLabel + ")" + translationManager.emptyString; + if (isout) { + if (address) { + return (addressBookName ? FontAwesome.addressBook + " " + addressBookName : TxUtils.addressTruncate(address, 8)); + } + if (amount != 0) { + return qsTr("Unknown recipient") + translationManager.emptyString; + } else { + return qsTr("My wallet") + translationManager.emptyString; + } } else { - return qsTr("Address") + " #" + subaddrIndex + " (" + TxUtils.addressTruncate(receivingAddress, 4) + ")" + translationManager.emptyString; + if (receivingAddress) { + if (subaddrIndex == 0) { + return qsTr("Address") + " #0" + " (" + qsTr("Primary address") + ")" + translationManager.emptyString; + } else { + if (receivingAddressLabel) { + return qsTr("Address") + " #" + subaddrIndex + " (" + receivingAddressLabel + ")" + translationManager.emptyString; + } else { + return qsTr("Address") + " #" + subaddrIndex + " (" + TxUtils.addressTruncate(receivingAddress, 4) + ")" + translationManager.emptyString; + } + } + } else { + return qsTr("Unknown address") + translationManager.emptyString; + } } } - } else { - return qsTr("Unknown address") + translationManager.emptyString; + } + + MouseArea { + state: isout ? "copyable_address" : "copyable_receiving_address" + anchors.fill: parent + hoverEnabled: true + onEntered: { + parent.color = MoneroComponents.Style.orange + tooltip ? parent.tooltipPopup.open() : "" + } + onExited: { + parent.color = MoneroComponents.Style.defaultFontColor + tooltip ? parent.tooltipPopup.close() : "" + } } } } - - color: MoneroComponents.Style.defaultFontColor - anchors.verticalCenter: parent.verticalCenter - - MouseArea { - state: isout ? "copyable_address" : "copyable_receiving_address" - anchors.fill: parent - hoverEnabled: true - onEntered: parent.color = MoneroComponents.Style.orange - onExited: parent.color = MoneroComponents.Style.defaultFontColor - } } } @@ -875,10 +967,12 @@ Rectangle { } ColumnLayout { + id: rightColumn spacing: 0 clip: true Layout.preferredHeight: 120 Layout.minimumWidth: 130 + Layout.alignment: Qt.AlignTop Rectangle { color: "transparent" @@ -1245,7 +1339,9 @@ Rectangle { for(var i = 0; i < res.length; i+=1){ if(res[i].containsMouse === true){ if(res[i].state === 'copyable' && res[i].parent.hasOwnProperty('text')) toClipboard(res[i].parent.text); - if(res[i].state === 'copyable_address') (address ? root.toClipboard(address) : root.toClipboard(addressField.text)); + if(res[i].state === 'copyable_address') (address ? root.toClipboard(address) + : res[i].parent.addressFromAddressListArray ? root.toClipboard(res[i].parent.addressFromAddressListArray) + : root.toClipboard(res[i].parent.text)); if(res[i].state === 'copyable_receiving_address') root.toClipboard(currentWallet.address(subaddrAccount, subaddrIndex)); if(res[i].state === 'copyable_txkey') root.getTxKey(hash, res[i]); if(res[i].state === 'set_tx_note') root.editDescription(hash, tx_note); @@ -1434,7 +1530,11 @@ Rectangle { } if(root.sortSearchString.length >= 1){ - if(item.amount && item.amount.toString().startsWith(root.sortSearchString)){ + if(item.displayAmount && item.displayAmount.startsWith(root.sortSearchString)){ + txs.push(item); + } else if(item.amountList != "" && item.amountList.toLowerCase().includes(root.sortSearchString.toLowerCase())){ + txs.push(item); + } else if(item.addressList != "" && item.addressList.toLowerCase().includes(root.sortSearchString.toLowerCase())){ txs.push(item); } else if(item.address !== "" && item.address.toLowerCase().startsWith(root.sortSearchString.toLowerCase())){ txs.push(item); @@ -1538,26 +1638,43 @@ Rectangle { var subaddrIndex = model.data(idx, TransactionHistoryModel.TransactionSubaddrIndexRole); var timestamp = new Date(date + " " + time).getTime() / 1000; var dateHuman = Utils.ago(timestamp); + var numberOfDestinations = (destinations.match(/:/g) || []).length; + var amountList = TxUtils.destinationsToAmount(destinations); - if (amount === 0) { - // transactions to the same account have amount === 0, while the 'destinations string' - // has the correct amount, so we try to fetch it from that instead. - amount = Number(TxUtils.destinationsToAmount(destinations)); + if (numberOfDestinations >1) { + var totalAmount = 0; + for (var i = 0; i < numberOfDestinations; i++) { + var amountFromAmountList = amountList.split(" ")[i]; + var amountAsNumber = Number(amountFromAmountList); + totalAmount += amountAsNumber; + } + var displayAmount = Utils.removeTrailingZeros(totalAmount.toFixed(12)) + " XMR"; + } else { + if (amount === 0) { + // transactions to the same account have amount === 0, while the 'destinations string' + // has the correct amount, so we try to fetch it from that instead. + amount = Number(TxUtils.destinationsToAmount(destinations)); + } + var displayAmount = Utils.removeTrailingZeros(amount.toFixed(12)) + " XMR"; } - var displayAmount = Utils.removeTrailingZeros(amount.toFixed(12)) + " XMR"; var tx_note = currentWallet.getUserNote(hash); var address = ""; var addressBookName = ""; var receivingAddress = ""; var receivingAddressLabel = ""; + var addressList = ""; - if (isout) { - address = TxUtils.destinationsToAddress(destinations); - addressBookName = currentWallet ? currentWallet.addressBook.getDescription(address) : null; + if (numberOfDestinations >1) { + addressList = TxUtils.destinationsToAddress(destinations); } else { - receivingAddress = currentWallet ? currentWallet.address(subaddrAccount, subaddrIndex) : null; - receivingAddressLabel = currentWallet ? appWindow.currentWallet.getSubaddressLabel(subaddrAccount, subaddrIndex) : null; + if (isout) { + address = TxUtils.destinationsToAddress(destinations); + addressBookName = currentWallet ? currentWallet.addressBook.getDescription(address) : null; + } else { + receivingAddress = currentWallet ? currentWallet.address(subaddrAccount, subaddrIndex) : null; + receivingAddressLabel = currentWallet ? appWindow.currentWallet.getSubaddressLabel(subaddrAccount, subaddrIndex) : null; + } } if (isout) @@ -1572,11 +1689,14 @@ Rectangle { "isout": isout, "amount": amount, "displayAmount": displayAmount, + "amountList": amountList, "hash": hash, "paymentId": paymentId, "address": address, + "addressList": addressList, "addressBookName": addressBookName, "destinations": destinations, + "numberOfDestinations": numberOfDestinations, "tx_note": tx_note, "dateHuman": dateHuman, "dateTime": date + " " + time,