mirror of
https://github.com/monero-project/monero-gui.git
synced 2025-01-28 18:56:32 +02:00
TxConfirmationDialog: implement multiple recipients support, layout fixes
This commit is contained in:
parent
c073867657
commit
92a2ae1a11
@ -51,7 +51,7 @@ Rectangle {
|
|||||||
property alias flickable: mainFlickable
|
property alias flickable: mainFlickable
|
||||||
|
|
||||||
property Transfer transferView: Transfer {
|
property Transfer transferView: Transfer {
|
||||||
onPaymentClicked: root.paymentClicked(address, paymentId, amount, mixinCount, priority, description)
|
onPaymentClicked: root.paymentClicked(recipients, paymentId, mixinCount, priority, description)
|
||||||
onSweepUnmixableClicked: root.sweepUnmixableClicked()
|
onSweepUnmixableClicked: root.sweepUnmixableClicked()
|
||||||
}
|
}
|
||||||
property Receive receiveView: Receive { }
|
property Receive receiveView: Receive { }
|
||||||
@ -66,7 +66,7 @@ Rectangle {
|
|||||||
property Keys keysView: Keys { }
|
property Keys keysView: Keys { }
|
||||||
property Account accountView: Account { }
|
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 sweepUnmixableClicked()
|
||||||
signal generatePaymentIdInvoked()
|
signal generatePaymentIdInvoked()
|
||||||
signal getProofClicked(string txid, string address, string message);
|
signal getProofClicked(string txid, string address, string message);
|
||||||
|
@ -27,7 +27,8 @@
|
|||||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
import QtQuick 2.9
|
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 QtQuick.Layouts 1.1
|
||||||
|
|
||||||
import "../components" as MoneroComponents
|
import "../components" as MoneroComponents
|
||||||
@ -35,11 +36,14 @@ import FontAwesome 1.0
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
property int margins: 25
|
||||||
|
|
||||||
x: parent.width/2 - root.width/2
|
x: parent.width/2 - root.width/2
|
||||||
y: parent.height/2 - root.height/2
|
y: parent.height/2 - root.height/2
|
||||||
// TODO: implement without hardcoding sizes
|
// TODO: implement without hardcoding sizes
|
||||||
width: 580
|
width: 590
|
||||||
height: 400
|
height: layout.height + layout.anchors.margins * 2
|
||||||
color: MoneroComponents.Style.blackTheme ? "black" : "white"
|
color: MoneroComponents.Style.blackTheme ? "black" : "white"
|
||||||
visible: false
|
visible: false
|
||||||
radius: 10
|
radius: 10
|
||||||
@ -53,8 +57,8 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
KeyNavigation.tab: confirmButton
|
KeyNavigation.tab: confirmButton
|
||||||
|
|
||||||
|
property var recipients: []
|
||||||
property var transactionAmount: ""
|
property var transactionAmount: ""
|
||||||
property var transactionAddress: ""
|
|
||||||
property var transactionDescription: ""
|
property var transactionDescription: ""
|
||||||
property var transactionFee: ""
|
property var transactionFee: ""
|
||||||
property var transactionPriority: ""
|
property var transactionPriority: ""
|
||||||
@ -127,8 +131,8 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function clearFields() {
|
function clearFields() {
|
||||||
|
root.recipients = [];
|
||||||
root.transactionAmount = "";
|
root.transactionAmount = "";
|
||||||
root.transactionAddress = "";
|
|
||||||
root.transactionDescription = "";
|
root.transactionDescription = "";
|
||||||
root.transactionFee = "";
|
root.transactionFee = "";
|
||||||
root.transactionPriority = "";
|
root.transactionPriority = "";
|
||||||
@ -141,9 +145,12 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
|
id: layout
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.margins: parent.margins
|
||||||
spacing: 10
|
spacing: 10
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 25
|
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.topMargin: 10
|
Layout.topMargin: 10
|
||||||
@ -181,11 +188,11 @@ Rectangle {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: 71
|
Layout.preferredHeight: 71
|
||||||
|
|
||||||
BusyIndicator {
|
QtQuickControls1.BusyIndicator {
|
||||||
id: txAmountBusyIndicator
|
id: txAmountBusyIndicator
|
||||||
Layout.fillWidth: true
|
Layout.fillHeight: true
|
||||||
Layout.alignment : Qt.AlignTop | Qt.AlignLeft
|
Layout.fillWidth: true
|
||||||
running: root.transactionAmount == "(all)"
|
running: root.transactionAmount == "(all)"
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
@ -220,16 +227,10 @@ Rectangle {
|
|||||||
columnSpacing: 15
|
columnSpacing: 15
|
||||||
rowSpacing: 16
|
rowSpacing: 16
|
||||||
|
|
||||||
ColumnLayout {
|
Text {
|
||||||
Layout.fillWidth: true
|
color: MoneroComponents.Style.dimmedFontColor
|
||||||
Layout.alignment : Qt.AlignTop | Qt.AlignLeft
|
text: qsTr("From") + ":" + translationManager.emptyString
|
||||||
|
font.pixelSize: 15
|
||||||
Text {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
color: MoneroComponents.Style.dimmedFontColor
|
|
||||||
text: qsTr("From") + ":" + translationManager.emptyString
|
|
||||||
font.pixelSize: 15
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
@ -266,57 +267,70 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
Text {
|
||||||
Layout.fillWidth: true
|
font.pixelSize: 15
|
||||||
Layout.alignment : Qt.AlignTop | Qt.AlignLeft
|
color: MoneroComponents.Style.dimmedFontColor
|
||||||
|
text: qsTr("To") + ":" + translationManager.emptyString
|
||||||
Text {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
font.pixelSize: 15
|
|
||||||
color: MoneroComponents.Style.dimmedFontColor
|
|
||||||
text: qsTr("To") + ":" + translationManager.emptyString
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
Flickable {
|
||||||
|
id: flickable
|
||||||
|
property int linesInMultipleRecipientsMode: 7
|
||||||
Layout.fillWidth: true
|
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 {
|
TextArea.flickable: TextArea {
|
||||||
Layout.fillWidth: true
|
id : recipientsArea
|
||||||
font.pixelSize: 15
|
|
||||||
font.family: MoneroComponents.Style.fontRegular.name
|
|
||||||
textFormat: Text.RichText
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
color: MoneroComponents.Style.defaultFontColor
|
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: {
|
text: {
|
||||||
if (root.transactionAddress) {
|
return recipients.map(function (recipient, index) {
|
||||||
const addressBookName = currentWallet ? currentWallet.addressBook.getDescription(root.transactionAddress) : null;
|
var addressBookName = null;
|
||||||
var fulladdress = root.transactionAddress;
|
if (currentWallet) {
|
||||||
var spacedaddress = fulladdress.match(/.{1,4}/g);
|
addressBookName = currentWallet.addressBook.getDescription(recipient.address);
|
||||||
var spacedaddress = spacedaddress.join(' ');
|
|
||||||
if (!addressBookName) {
|
|
||||||
return qsTr("Monero address") + "<br>" + spacedaddress + translationManager.emptyString;
|
|
||||||
} else {
|
|
||||||
return FontAwesome.addressBook + " " + addressBookName + "<br>" + spacedaddress;
|
|
||||||
}
|
}
|
||||||
} else {
|
var title;
|
||||||
return "";
|
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 + "<br>" + spacedaddress;
|
||||||
|
}).join("<br><br>");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ScrollBar.vertical: ScrollBar {
|
||||||
|
policy: recipientsArea.contentHeight > flickable.height ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
Text {
|
||||||
Layout.fillWidth: true
|
color: MoneroComponents.Style.dimmedFontColor
|
||||||
Layout.alignment : Qt.AlignTop | Qt.AlignLeft
|
text: qsTr("Fee") + ":" + translationManager.emptyString
|
||||||
|
font.pixelSize: 15
|
||||||
Text {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
color: MoneroComponents.Style.dimmedFontColor
|
|
||||||
text: qsTr("Fee") + ":" + translationManager.emptyString
|
|
||||||
font.pixelSize: 15
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
@ -364,7 +378,7 @@ Rectangle {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: 50
|
Layout.preferredHeight: 50
|
||||||
|
|
||||||
BusyIndicator {
|
QtQuickControls1.BusyIndicator {
|
||||||
visible: !bottomTextAnimation.running
|
visible: !bottomTextAnimation.running
|
||||||
running: !bottomTextAnimation.running
|
running: !bottomTextAnimation.running
|
||||||
scale: .5
|
scale: .5
|
||||||
|
61
main.qml
61
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"
|
// 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("Creating transaction: ")
|
||||||
console.log("\taddress: ", address,
|
console.log("\trecipients: ", recipients,
|
||||||
", payment_id: ", paymentId,
|
", payment_id: ", paymentId,
|
||||||
", amount: ", amount,
|
|
||||||
", mixins: ", mixinCount,
|
", mixins: ", mixinCount,
|
||||||
", priority: ", priority,
|
", priority: ", priority,
|
||||||
", description: ", description);
|
", 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;
|
const recipientAll = recipients.find(function (recipient) {
|
||||||
if (amount !== "(all)") {
|
return recipient.amount == "(all)";
|
||||||
var amountxmr = walletManager.amountFromString(amount);
|
});
|
||||||
console.log("integer amount: ", amountxmr);
|
if (recipientAll && recipients.length > 1) {
|
||||||
console.log("integer unlocked", currentWallet.unlockedBalance())
|
throw "Sending all requires one destination address";
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
txConfirmationPopup.open();
|
||||||
|
|
||||||
if (amount === "(all)")
|
if (recipientAll) {
|
||||||
currentWallet.createTransactionAllAsync(address, paymentId, mixinCount, priority);
|
currentWallet.createTransactionAllAsync(recipientAll.address, paymentId, mixinCount, priority);
|
||||||
else
|
} else {
|
||||||
currentWallet.createTransactionAsync([address], paymentId, [amountxmr], mixinCount, priority);
|
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
|
//Choose where to save transaction
|
||||||
|
@ -44,8 +44,7 @@ import "../js/Utils.js" as Utils
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
signal paymentClicked(string address, string paymentId, string amount, int mixinCount,
|
signal paymentClicked(var recipients, string paymentId, int mixinCount, int priority, string description)
|
||||||
int priority, string description)
|
|
||||||
signal sweepUnmixableClicked()
|
signal sweepUnmixableClicked()
|
||||||
|
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
@ -124,6 +123,10 @@ Rectangle {
|
|||||||
priorityDropdown.currentIndex = 0
|
priorityDropdown.currentIndex = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getRecipients() {
|
||||||
|
return [{address: addressLine.text, amount: amountLine.text}];
|
||||||
|
}
|
||||||
|
|
||||||
// Information dialog
|
// Information dialog
|
||||||
StandardDialog {
|
StandardDialog {
|
||||||
// dynamically change onclose handler
|
// dynamically change onclose handler
|
||||||
@ -523,7 +526,7 @@ Rectangle {
|
|||||||
console.log("amount: " + amountLine.text)
|
console.log("amount: " + amountLine.text)
|
||||||
addressLine.text = addressLine.text.trim()
|
addressLine.text = addressLine.text.trim()
|
||||||
setPaymentId(paymentIdLine.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)
|
console.log("amount: " + amountLine.text)
|
||||||
addressLine.text = addressLine.text.trim()
|
addressLine.text = addressLine.text.trim()
|
||||||
setPaymentId(paymentIdLine.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.text: qsTr("Sign (offline)") + translationManager.emptyString
|
||||||
button2.enabled: !appWindow.viewOnly
|
button2.enabled: !appWindow.viewOnly
|
||||||
|
@ -251,7 +251,7 @@ QString WalletManager::errorString() const
|
|||||||
return tr("Unknown error");
|
return tr("Unknown error");
|
||||||
}
|
}
|
||||||
|
|
||||||
quint64 WalletManager::maximumAllowedAmount() const
|
quint64 WalletManager::maximumAllowedAmount()
|
||||||
{
|
{
|
||||||
return Monero::Wallet::maximumAllowedAmount();
|
return Monero::Wallet::maximumAllowedAmount();
|
||||||
}
|
}
|
||||||
@ -266,7 +266,7 @@ QString WalletManager::displayAmount(quint64 amount)
|
|||||||
return QString::fromStdString(Monero::Wallet::displayAmount(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());
|
return Monero::Wallet::amountFromString(amount.toStdString());
|
||||||
}
|
}
|
||||||
@ -276,6 +276,17 @@ quint64 WalletManager::amountFromDouble(double amount) const
|
|||||||
return Monero::Wallet::amountFromDouble(amount);
|
return Monero::Wallet::amountFromDouble(amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString WalletManager::amountsSumFromStrings(const QVector<QString> &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
|
bool WalletManager::paymentIdValid(const QString &payment_id) const
|
||||||
{
|
{
|
||||||
return Monero::Wallet::paymentIdValid(payment_id.toStdString());
|
return Monero::Wallet::paymentIdValid(payment_id.toStdString());
|
||||||
|
@ -133,9 +133,10 @@ public:
|
|||||||
|
|
||||||
//! since we can't call static method from QML, move it to this class
|
//! since we can't call static method from QML, move it to this class
|
||||||
Q_INVOKABLE static QString displayAmount(quint64 amount);
|
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 amountFromDouble(double amount) const;
|
||||||
Q_INVOKABLE quint64 maximumAllowedAmount() const;
|
Q_INVOKABLE static QString amountsSumFromStrings(const QVector<QString> &amounts);
|
||||||
|
Q_INVOKABLE static quint64 maximumAllowedAmount();
|
||||||
|
|
||||||
// QML JS engine doesn't support unsigned integers
|
// QML JS engine doesn't support unsigned integers
|
||||||
Q_INVOKABLE QString maximumAllowedAmountAsString() const;
|
Q_INVOKABLE QString maximumAllowedAmountAsString() const;
|
||||||
|
Loading…
Reference in New Issue
Block a user