diff --git a/MiddlePanel.qml b/MiddlePanel.qml
index 0bcca0be..2d850eac 100644
--- a/MiddlePanel.qml
+++ b/MiddlePanel.qml
@@ -51,7 +51,7 @@ Rectangle {
property alias flickable: mainFlickable
property Transfer transferView: Transfer {
- onPaymentClicked: root.paymentClicked(address, paymentId, amount, mixinCount, priority, description)
+ onPaymentClicked: root.paymentClicked(recipients, paymentId, mixinCount, priority, description)
onSweepUnmixableClicked: root.sweepUnmixableClicked()
}
property Receive receiveView: Receive { }
@@ -66,7 +66,7 @@ Rectangle {
property Keys keysView: Keys { }
property Account accountView: Account { }
- signal paymentClicked(string address, string paymentId, string amount, int mixinCount, int priority, string description)
+ signal paymentClicked(var recipients, string paymentId, int mixinCount, int priority, string description)
signal sweepUnmixableClicked()
signal generatePaymentIdInvoked()
signal getProofClicked(string txid, string address, string message);
diff --git a/components/TxConfirmationDialog.qml b/components/TxConfirmationDialog.qml
index 67a865eb..4904b4e6 100644
--- a/components/TxConfirmationDialog.qml
+++ b/components/TxConfirmationDialog.qml
@@ -27,7 +27,8 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.9
-import QtQuick.Controls 1.4
+import QtQuick.Controls 1.4 as QtQuickControls1
+import QtQuick.Controls 2.2
import QtQuick.Layouts 1.1
import "../components" as MoneroComponents
@@ -35,11 +36,14 @@ import FontAwesome 1.0
Rectangle {
id: root
+
+ property int margins: 25
+
x: parent.width/2 - root.width/2
y: parent.height/2 - root.height/2
// TODO: implement without hardcoding sizes
- width: 580
- height: 400
+ width: 590
+ height: layout.height + layout.anchors.margins * 2
color: MoneroComponents.Style.blackTheme ? "black" : "white"
visible: false
radius: 10
@@ -53,8 +57,8 @@ Rectangle {
}
KeyNavigation.tab: confirmButton
+ property var recipients: []
property var transactionAmount: ""
- property var transactionAddress: ""
property var transactionDescription: ""
property var transactionFee: ""
property var transactionPriority: ""
@@ -127,8 +131,8 @@ Rectangle {
}
function clearFields() {
+ root.recipients = [];
root.transactionAmount = "";
- root.transactionAddress = "";
root.transactionDescription = "";
root.transactionFee = "";
root.transactionPriority = "";
@@ -141,9 +145,12 @@ Rectangle {
}
ColumnLayout {
+ id: layout
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.margins: parent.margins
spacing: 10
- anchors.fill: parent
- anchors.margins: 25
RowLayout {
Layout.topMargin: 10
@@ -181,11 +188,11 @@ Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 71
- BusyIndicator {
- id: txAmountBusyIndicator
- Layout.fillWidth: true
- Layout.alignment : Qt.AlignTop | Qt.AlignLeft
- running: root.transactionAmount == "(all)"
+ QtQuickControls1.BusyIndicator {
+ id: txAmountBusyIndicator
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ running: root.transactionAmount == "(all)"
}
Text {
@@ -220,16 +227,10 @@ Rectangle {
columnSpacing: 15
rowSpacing: 16
- ColumnLayout {
- Layout.fillWidth: true
- Layout.alignment : Qt.AlignTop | Qt.AlignLeft
-
- Text {
- Layout.fillWidth: true
- color: MoneroComponents.Style.dimmedFontColor
- text: qsTr("From") + ":" + translationManager.emptyString
- font.pixelSize: 15
- }
+ Text {
+ color: MoneroComponents.Style.dimmedFontColor
+ text: qsTr("From") + ":" + translationManager.emptyString
+ font.pixelSize: 15
}
ColumnLayout {
@@ -266,57 +267,70 @@ Rectangle {
}
}
- ColumnLayout {
- Layout.fillWidth: true
- Layout.alignment : Qt.AlignTop | Qt.AlignLeft
-
- Text {
- Layout.fillWidth: true
- font.pixelSize: 15
- color: MoneroComponents.Style.dimmedFontColor
- text: qsTr("To") + ":" + translationManager.emptyString
- }
+ Text {
+ font.pixelSize: 15
+ color: MoneroComponents.Style.dimmedFontColor
+ text: qsTr("To") + ":" + translationManager.emptyString
}
- ColumnLayout {
+ Flickable {
+ id: flickable
+ property int linesInMultipleRecipientsMode: 7
Layout.fillWidth: true
- spacing: 16
+ Layout.preferredHeight: recipients.length > 1
+ ? linesInMultipleRecipientsMode * (recipientsArea.contentHeight / recipientsArea.lineCount)
+ : recipientsArea.contentHeight
+ boundsBehavior: isMac ? Flickable.DragAndOvershootBounds : Flickable.StopAtBounds
+ clip: true
- Text {
- Layout.fillWidth: true
- font.pixelSize: 15
- font.family: MoneroComponents.Style.fontRegular.name
- textFormat: Text.RichText
- wrapMode: Text.Wrap
+ TextArea.flickable: TextArea {
+ id : recipientsArea
color: MoneroComponents.Style.defaultFontColor
+ font.family: MoneroComponents.Style.fontMonoRegular.name
+ font.pixelSize: 14
+ topPadding: 0
+ bottomPadding: 0
+ leftPadding: 0
+ textMargin: 0
+ readOnly: true
+ selectByKeyboard: true
+ selectByMouse: true
+ selectionColor: MoneroComponents.Style.textSelectionColor
+ textFormat: TextEdit.RichText
+ wrapMode: TextEdit.Wrap
text: {
- if (root.transactionAddress) {
- const addressBookName = currentWallet ? currentWallet.addressBook.getDescription(root.transactionAddress) : null;
- var fulladdress = root.transactionAddress;
- var spacedaddress = fulladdress.match(/.{1,4}/g);
- var spacedaddress = spacedaddress.join(' ');
- if (!addressBookName) {
- return qsTr("Monero address") + "
" + spacedaddress + translationManager.emptyString;
- } else {
- return FontAwesome.addressBook + " " + addressBookName + "
" + spacedaddress;
+ return recipients.map(function (recipient, index) {
+ var addressBookName = null;
+ if (currentWallet) {
+ addressBookName = currentWallet.addressBook.getDescription(recipient.address);
}
- } else {
- return "";
- }
+ var title;
+ if (addressBookName) {
+ title = FontAwesome.addressBook + " " + addressBookName;
+ } else {
+ title = qsTr("Monero address") + translationManager.emptyString;
+ }
+ if (recipients.length > 1) {
+ title = "%1. %2 - %3 XMR".arg(index + 1).arg(title).arg(recipient.amount);
+ if (persistentSettings.fiatPriceEnabled) {
+ title += " (%1)".arg(showFiatConversion(recipient.amount));
+ }
+ }
+ const spacedaddress = recipient.address.match(/.{1,4}/g).join(' ');
+ return title + "
" + spacedaddress;
+ }).join("
");
}
}
+
+ ScrollBar.vertical: ScrollBar {
+ policy: recipientsArea.contentHeight > flickable.height ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff
+ }
}
- ColumnLayout {
- Layout.fillWidth: true
- Layout.alignment : Qt.AlignTop | Qt.AlignLeft
-
- Text {
- Layout.fillWidth: true
- color: MoneroComponents.Style.dimmedFontColor
- text: qsTr("Fee") + ":" + translationManager.emptyString
- font.pixelSize: 15
- }
+ Text {
+ color: MoneroComponents.Style.dimmedFontColor
+ text: qsTr("Fee") + ":" + translationManager.emptyString
+ font.pixelSize: 15
}
RowLayout {
@@ -364,7 +378,7 @@ Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 50
- BusyIndicator {
+ QtQuickControls1.BusyIndicator {
visible: !bottomTextAnimation.running
running: !bottomTextAnimation.running
scale: .5
diff --git a/main.qml b/main.qml
index 42ca1305..54cdbdab 100644
--- a/main.qml
+++ b/main.qml
@@ -852,48 +852,49 @@ ApplicationWindow {
}
}
+ function getDisplayAmountTotal(recipients) {
+ const amounts = recipients.map(function (recipient) {
+ return recipient.amount;
+ });
+ const total = walletManager.amountsSumFromStrings(amounts);
+ return Utils.removeTrailingZeros(walletManager.displayAmount(total));
+ }
// called on "transfer"
- function handlePayment(address, paymentId, amount, mixinCount, priority, description, createFile) {
+ function handlePayment(recipients, paymentId, mixinCount, priority, description, createFile) {
console.log("Creating transaction: ")
- console.log("\taddress: ", address,
+ console.log("\trecipients: ", recipients,
", payment_id: ", paymentId,
- ", amount: ", amount,
", mixins: ", mixinCount,
", priority: ", priority,
", description: ", description);
- txConfirmationPopup.bottomTextAnimation.running = false
- txConfirmationPopup.bottomText.text = qsTr("Creating transaction...") + translationManager.emptyString;
- txConfirmationPopup.transactionAddress = address;
- txConfirmationPopup.transactionAmount = Utils.removeTrailingZeros(amount);
- txConfirmationPopup.transactionPriority = priority;
- txConfirmationPopup.transactionDescription = description;
- // validate amount;
- if (amount !== "(all)") {
- var amountxmr = walletManager.amountFromString(amount);
- console.log("integer amount: ", amountxmr);
- console.log("integer unlocked", currentWallet.unlockedBalance())
- if (amountxmr <= 0) {
- txConfirmationPopup.errorText.text = qsTr("Amount is wrong: expected number from %1 to %2")
- .arg(walletManager.displayAmount(0))
- .arg(walletManager.displayAmount(currentWallet.unlockedBalance()))
- + translationManager.emptyString;
- return;
- } else if (amountxmr > currentWallet.unlockedBalance()) {
- txConfirmationPopup.errorText.text = qsTr("Insufficient funds. Unlocked balance: %1")
- .arg(walletManager.displayAmount(currentWallet.unlockedBalance()))
- + translationManager.emptyString;
- return;
- }
+ const recipientAll = recipients.find(function (recipient) {
+ return recipient.amount == "(all)";
+ });
+ if (recipientAll && recipients.length > 1) {
+ throw "Sending all requires one destination address";
}
+ txConfirmationPopup.bottomTextAnimation.running = false;
+ txConfirmationPopup.bottomText.text = qsTr("Creating transaction...") + translationManager.emptyString;
+ txConfirmationPopup.recipients = recipients;
+ txConfirmationPopup.transactionAmount = recipientAll ? "(all)" : getDisplayAmountTotal(recipients);
+ txConfirmationPopup.transactionPriority = priority;
+ txConfirmationPopup.transactionDescription = description;
txConfirmationPopup.open();
- if (amount === "(all)")
- currentWallet.createTransactionAllAsync(address, paymentId, mixinCount, priority);
- else
- currentWallet.createTransactionAsync([address], paymentId, [amountxmr], mixinCount, priority);
+ if (recipientAll) {
+ currentWallet.createTransactionAllAsync(recipientAll.address, paymentId, mixinCount, priority);
+ } else {
+ const addresses = recipients.map(function (recipient) {
+ return recipient.address;
+ });
+ const amountsxmr = recipients.map(function (recipient) {
+ return walletManager.amountFromString(recipient.amount);
+ });
+ currentWallet.createTransactionAsync(addresses, paymentId, amountsxmr, mixinCount, priority);
+ }
}
//Choose where to save transaction
diff --git a/pages/Transfer.qml b/pages/Transfer.qml
index 9a4d8b18..d4dd1d6e 100644
--- a/pages/Transfer.qml
+++ b/pages/Transfer.qml
@@ -44,8 +44,7 @@ import "../js/Utils.js" as Utils
Rectangle {
id: root
- signal paymentClicked(string address, string paymentId, string amount, int mixinCount,
- int priority, string description)
+ signal paymentClicked(var recipients, string paymentId, int mixinCount, int priority, string description)
signal sweepUnmixableClicked()
color: "transparent"
@@ -124,6 +123,10 @@ Rectangle {
priorityDropdown.currentIndex = 0
}
+ function getRecipients() {
+ return [{address: addressLine.text, amount: amountLine.text}];
+ }
+
// Information dialog
StandardDialog {
// dynamically change onclose handler
@@ -523,7 +526,7 @@ Rectangle {
console.log("amount: " + amountLine.text)
addressLine.text = addressLine.text.trim()
setPaymentId(paymentIdLine.text.trim());
- root.paymentClicked(addressLine.text, paymentIdLine.text, amountLine.text, root.mixin, priority, descriptionLine.text)
+ root.paymentClicked(getRecipients(), paymentIdLine.text, root.mixin, priority, descriptionLine.text)
}
}
}
@@ -597,7 +600,7 @@ Rectangle {
console.log("amount: " + amountLine.text)
addressLine.text = addressLine.text.trim()
setPaymentId(paymentIdLine.text.trim());
- root.paymentClicked(addressLine.text, paymentIdLine.text, amountLine.text, root.mixin, priority, descriptionLine.text)
+ root.paymentClicked(getRecipients(), paymentIdLine.text, root.mixin, priority, descriptionLine.text)
}
button2.text: qsTr("Sign (offline)") + translationManager.emptyString
button2.enabled: !appWindow.viewOnly
diff --git a/src/libwalletqt/WalletManager.cpp b/src/libwalletqt/WalletManager.cpp
index ecccef66..9876e1f3 100644
--- a/src/libwalletqt/WalletManager.cpp
+++ b/src/libwalletqt/WalletManager.cpp
@@ -251,7 +251,7 @@ QString WalletManager::errorString() const
return tr("Unknown error");
}
-quint64 WalletManager::maximumAllowedAmount() const
+quint64 WalletManager::maximumAllowedAmount()
{
return Monero::Wallet::maximumAllowedAmount();
}
@@ -266,7 +266,7 @@ QString WalletManager::displayAmount(quint64 amount)
return QString::fromStdString(Monero::Wallet::displayAmount(amount));
}
-quint64 WalletManager::amountFromString(const QString &amount) const
+quint64 WalletManager::amountFromString(const QString &amount)
{
return Monero::Wallet::amountFromString(amount.toStdString());
}
@@ -276,6 +276,17 @@ quint64 WalletManager::amountFromDouble(double amount) const
return Monero::Wallet::amountFromDouble(amount);
}
+QString WalletManager::amountsSumFromStrings(const QVector &amounts)
+{
+ quint64 sum = 0;
+ for (const auto &amountString : amounts)
+ {
+ const quint64 amount = amountFromString(amountString);
+ sum = sum + std::min(maximumAllowedAmount() - sum, amount);
+ }
+ return QString::number(sum);
+}
+
bool WalletManager::paymentIdValid(const QString &payment_id) const
{
return Monero::Wallet::paymentIdValid(payment_id.toStdString());
diff --git a/src/libwalletqt/WalletManager.h b/src/libwalletqt/WalletManager.h
index a45995af..25c62faf 100644
--- a/src/libwalletqt/WalletManager.h
+++ b/src/libwalletqt/WalletManager.h
@@ -133,9 +133,10 @@ public:
//! since we can't call static method from QML, move it to this class
Q_INVOKABLE static QString displayAmount(quint64 amount);
- Q_INVOKABLE quint64 amountFromString(const QString &amount) const;
+ Q_INVOKABLE static quint64 amountFromString(const QString &amount);
Q_INVOKABLE quint64 amountFromDouble(double amount) const;
- Q_INVOKABLE quint64 maximumAllowedAmount() const;
+ Q_INVOKABLE static QString amountsSumFromStrings(const QVector &amounts);
+ Q_INVOKABLE static quint64 maximumAllowedAmount();
// QML JS engine doesn't support unsigned integers
Q_INVOKABLE QString maximumAllowedAmountAsString() const;