diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include
index 306dbe2aa6..019b2bd15c 100644
--- a/src/Makefile.qt.include
+++ b/src/Makefile.qt.include
@@ -44,7 +44,9 @@ QT_MOC_CPP = \
qml/models/moc_nodemodel.cpp \
qml/models/moc_options_model.cpp \
qml/models/moc_peerdetailsmodel.cpp \
- qml/models/moc_peerlistsortproxy.cpp \
+ qml/models/moc_peerlistsortproxy.cpp \\
+ qml/models/moc_sendrecipient.cpp \
+ qml/models/moc_sendrecipientslistmodel.cpp \
qml/models/moc_transaction.cpp \
qml/models/moc_sendrecipient.cpp \
qml/models/moc_walletlistmodel.cpp \
@@ -138,6 +140,7 @@ BITCOIN_QT_H = \
qml/models/peerlistsortproxy.h \
qml/models/transaction.h \
qml/models/sendrecipient.h \
+ qml/models/sendrecipientslistmodel.h \
qml/models/walletlistmodel.h \
qml/models/walletqmlmodel.h \
qml/models/walletqmlmodeltransaction.h \
@@ -339,6 +342,7 @@ BITCOIN_QML_BASE_CPP = \
qml/models/peerlistsortproxy.cpp \
qml/models/transaction.cpp \
qml/models/sendrecipient.cpp \
+ qml/models/sendrecipientslistmodel.cpp \
qml/models/walletlistmodel.cpp \
qml/models/walletqmlmodel.cpp \
qml/models/walletqmlmodeltransaction.cpp \
@@ -379,6 +383,7 @@ QML_RES_ICONS = \
qml/res/icons/network-dark.png \
qml/res/icons/network-light.png \
qml/res/icons/plus.png \
+ qml/res/icons/plus-big-filled.png \
qml/res/icons/pending.png \
qml/res/icons/shutdown.png \
qml/res/icons/singlesig-wallet.png \
@@ -420,12 +425,12 @@ QML_RES_QML = \
qml/controls/CoreCheckBox.qml \
qml/controls/CoreText.qml \
qml/controls/CoreTextField.qml \
- qml/controls/EllipsisMenuButton.qml \
qml/controls/EllipsisMenuToggleItem.qml \
qml/controls/ExternalLink.qml \
qml/controls/FocusBorder.qml \
qml/controls/Header.qml \
qml/controls/Icon.qml \
+ qml/controls/IconButton.qml \
qml/controls/InformationPage.qml \
qml/controls/IPAddressValueInput.qml \
qml/controls/KeyValueRow.qml \
@@ -484,6 +489,7 @@ QML_RES_QML = \
qml/pages/wallet/CreatePassword.qml \
qml/pages/wallet/CreateWalletWizard.qml \
qml/pages/wallet/DesktopWallets.qml \
+ qml/pages/wallet/MultipleSendReview.qml \
qml/pages/wallet/RequestPayment.qml \
qml/pages/wallet/Send.qml \
qml/pages/wallet/SendResult.qml \
diff --git a/src/qml/bitcoin_qml.qrc b/src/qml/bitcoin_qml.qrc
index 8557ebe0e6..6bcd5f0705 100644
--- a/src/qml/bitcoin_qml.qrc
+++ b/src/qml/bitcoin_qml.qrc
@@ -30,12 +30,12 @@
controls/FocusBorder.qml
controls/Header.qml
controls/Icon.qml
+ controls/IconButton.qml
controls/InformationPage.qml
controls/IPAddressValueInput.qml
controls/KeyValueRow.qml
controls/LabeledTextInput.qml
controls/LabeledCoinControlButton.qml
- controls/EllipsisMenuButton.qml
controls/EllipsisMenuToggleItem.qml
controls/NavButton.qml
controls/NavigationBar.qml
@@ -90,6 +90,7 @@
pages/wallet/CreatePassword.qml
pages/wallet/CreateWalletWizard.qml
pages/wallet/DesktopWallets.qml
+ pages/wallet/MultipleSendReview.qml
pages/wallet/RequestPayment.qml
pages/wallet/Send.qml
pages/wallet/SendResult.qml
@@ -126,6 +127,7 @@
res/icons/network-dark.png
res/icons/network-light.png
res/icons/plus.png
+ res/icons/plus-big-filled.png
res/icons/pending.png
res/icons/shutdown.png
res/icons/singlesig-wallet.png
diff --git a/src/qml/bitcoinamount.cpp b/src/qml/bitcoinamount.cpp
index 153d8fabae..3cc31605d8 100644
--- a/src/qml/bitcoinamount.cpp
+++ b/src/qml/bitcoinamount.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 The Bitcoin Core developers
+// Copyright (c) 2024-2025 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -7,19 +7,9 @@
#include
#include
-
-BitcoinAmount::BitcoinAmount(QObject *parent) : QObject(parent)
+BitcoinAmount::BitcoinAmount(QObject* parent)
+ : QObject(parent)
{
- m_unit = Unit::BTC;
-}
-
-int BitcoinAmount::decimals(Unit unit)
-{
- switch (unit) {
- case Unit::BTC: return 8;
- case Unit::SAT: return 0;
- } // no default case, so the compiler can warn about missing cases
- assert(false);
}
QString BitcoinAmount::sanitize(const QString &text)
@@ -43,6 +33,30 @@ QString BitcoinAmount::sanitize(const QString &text)
return result;
}
+qint64 BitcoinAmount::satoshi() const
+{
+ return m_satoshi;
+}
+
+void BitcoinAmount::setSatoshi(qint64 new_amount)
+{
+ m_isSet = true;
+ if (m_satoshi != new_amount) {
+ m_satoshi = new_amount;
+ Q_EMIT amountChanged();
+ }
+}
+
+void BitcoinAmount::clear()
+{
+ if (!m_isSet && m_satoshi == 0) {
+ return;
+ }
+ m_satoshi = 0;
+ m_isSet = false;
+ Q_EMIT amountChanged();
+}
+
BitcoinAmount::Unit BitcoinAmount::unit() const
{
return m_unit;
@@ -52,103 +66,94 @@ void BitcoinAmount::setUnit(const Unit unit)
{
m_unit = unit;
Q_EMIT unitChanged();
+ Q_EMIT displayChanged();
}
QString BitcoinAmount::unitLabel() const
{
switch (m_unit) {
case Unit::BTC: return "₿";
- case Unit::SAT: return "Sat";
+ case Unit::SAT: return "sat";
}
assert(false);
}
-QString BitcoinAmount::amount() const
+void BitcoinAmount::flipUnit()
{
- return m_amount;
+ if (m_unit == Unit::BTC) {
+ m_unit = Unit::SAT;
+ } else {
+ m_unit = Unit::BTC;
+ }
+ Q_EMIT unitChanged();
+ Q_EMIT displayChanged();
}
-QString BitcoinAmount::satoshiAmount() const
+QString BitcoinAmount::satsToBtcString(qint64 sat)
{
- return toSatoshis(m_amount);
-}
+ const bool negative = sat < 0;
+ qint64 absSat = negative ? -sat : sat;
-void BitcoinAmount::setAmount(const QString& new_amount)
-{
- m_amount = sanitize(new_amount);
- Q_EMIT amountChanged();
+ const qint64 wholePart = absSat / COIN;
+ const qint64 fracInt = absSat % COIN;
+ QString fracPart = QString("%1").arg(fracInt, 8, 10, QLatin1Char('0'));
+
+ QString result = QString::number(wholePart) + '.' + fracPart;
+ if (negative) {
+ result.prepend('-');
+ }
+ return result;
}
-QString BitcoinAmount::toSatoshis(const QString& text) const
+QString BitcoinAmount::toDisplay() const
{
+ if (!m_isSet) {
+ return "";
+ }
if (m_unit == Unit::SAT) {
- return text;
+ return QString::number(m_satoshi);
} else {
- return convert(text, m_unit);
+ return satsToBtcString(m_satoshi);
}
}
-long long BitcoinAmount::toSatoshis(QString& amount, const Unit unit)
+qint64 BitcoinAmount::btcToSats(const QString& btcSanitized)
{
- int num_decimals = decimals(unit);
+ if (btcSanitized.isEmpty() || btcSanitized == ".") return 0;
- QStringList parts = amount.remove(' ').split(".");
+ QString cleaned = btcSanitized;
+ if (cleaned.startsWith('.')) cleaned.prepend('0');
- QString whole = parts[0];
- QString decimals;
-
- if(parts.size() > 1)
- {
- decimals = parts[1];
+ QStringList parts = cleaned.split('.');
+ const qint64 whole = parts[0].isEmpty() ? 0 : parts[0].toLongLong();
+ qint64 frac = 0;
+ if (parts.size() == 2) {
+ frac = parts[1].leftJustified(8, '0').toLongLong();
}
- QString str = whole + decimals.leftJustified(num_decimals, '0', true);
- return str.toLongLong();
+ return whole * COIN + frac;
}
-QString BitcoinAmount::convert(const QString& amount, Unit unit) const
+void BitcoinAmount::fromDisplay(const QString& text)
{
- if (amount == "") {
- return amount;
- }
-
- QString result = amount;
- int decimalPosition = result.indexOf(".");
-
- if (decimalPosition == -1) {
- decimalPosition = result.length();
- result.append(".");
+ if (text.trimmed().isEmpty()) {
+ clear();
+ return;
}
- if (unit == Unit::BTC) {
- int numDigitsAfterDecimal = result.length() - decimalPosition - 1;
- if (numDigitsAfterDecimal < 8) {
- result.append(QString(8 - numDigitsAfterDecimal, '0'));
- }
- result.remove(decimalPosition, 1);
-
- while (result.startsWith('0') && result.length() > 1) {
- result.remove(0, 1);
- }
- } else if (unit == Unit::SAT) {
- result.remove(decimalPosition, 1);
- int newDecimalPosition = decimalPosition - 8;
- if (newDecimalPosition < 1) {
- result = QString("0").repeated(-newDecimalPosition) + result;
- newDecimalPosition = 0;
- }
- result.insert(newDecimalPosition, ".");
-
- while (result.endsWith('0') && result.contains('.')) {
- result.chop(1);
- }
- if (result.endsWith('.')) {
- result.chop(1);
- }
- if (result.startsWith('.')) {
- result.insert(0, "0");
- }
+ qint64 newSat = 0;
+ if (m_unit == Unit::BTC) {
+ QString sanitized = sanitize(text);
+ newSat = btcToSats(sanitized);
+ } else {
+ QString digitsOnly = text;
+ digitsOnly.remove(QRegExp("[^0-9]"));
+ newSat = digitsOnly.trimmed().isEmpty() ? 0 : digitsOnly.toLongLong();
}
+ setSatoshi(newSat);
+}
- return result;
+void BitcoinAmount::format()
+{
+ Q_EMIT displayChanged();
}
diff --git a/src/qml/bitcoinamount.h b/src/qml/bitcoinamount.h
index 0631a05b87..c451b34fd3 100644
--- a/src/qml/bitcoinamount.h
+++ b/src/qml/bitcoinamount.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 The Bitcoin Core developers
+// Copyright (c) 2024-2025 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -15,8 +15,8 @@ class BitcoinAmount : public QObject
Q_OBJECT
Q_PROPERTY(Unit unit READ unit WRITE setUnit NOTIFY unitChanged)
Q_PROPERTY(QString unitLabel READ unitLabel NOTIFY unitChanged)
- Q_PROPERTY(QString amount READ amount WRITE setAmount NOTIFY amountChanged)
- Q_PROPERTY(QString satoshiAmount READ satoshiAmount NOTIFY amountChanged)
+ Q_PROPERTY(QString display READ toDisplay WRITE fromDisplay NOTIFY displayChanged)
+ Q_PROPERTY(qint64 satoshi READ satoshi WRITE setSatoshi NOTIFY amountChanged)
public:
enum class Unit {
@@ -30,27 +30,34 @@ class BitcoinAmount : public QObject
Unit unit() const;
void setUnit(Unit unit);
QString unitLabel() const;
- QString amount() const;
- void setAmount(const QString& new_amount);
- QString satoshiAmount() const;
+
+ QString toDisplay() const;
+ void fromDisplay(const QString& new_amount);
+ qint64 satoshi() const;
+ void setSatoshi(qint64 new_amount);
+
+ bool isSet() const { return m_isSet; }
+
+ Q_INVOKABLE void format();
+
+ static QString satsToBtcString(qint64 sat);
public Q_SLOTS:
- QString sanitize(const QString& text);
- QString convert(const QString& text, Unit unit) const;
- QString toSatoshis(const QString& text) const;
+ void flipUnit();
+ void clear();
Q_SIGNALS:
void unitChanged();
- void unitLabelChanged();
void amountChanged();
+ void displayChanged();
private:
- long long toSatoshis(QString &amount, const Unit unit);
- int decimals(Unit unit);
+ QString sanitize(const QString& text);
+ static qint64 btcToSats(const QString& btc);
- Unit m_unit;
- QString m_unitLabel;
- QString m_amount;
+ qint64 m_satoshi{0};
+ bool m_isSet{false};
+ Unit m_unit{Unit::BTC};
};
#endif // BITCOIN_QML_BITCOINAMOUNT_H
diff --git a/src/qml/controls/EllipsisMenuButton.qml b/src/qml/controls/IconButton.qml
similarity index 51%
rename from src/qml/controls/EllipsisMenuButton.qml
rename to src/qml/controls/IconButton.qml
index 593ede0902..10df825620 100644
--- a/src/qml/controls/EllipsisMenuButton.qml
+++ b/src/qml/controls/IconButton.qml
@@ -11,12 +11,16 @@ import org.bitcoincore.qt 1.0
Button {
id: root
+ property color iconColor: Theme.color.orange
property color hoverColor: Theme.color.orange
property color activeColor: Theme.color.orange
+ property int size: 35
+ property alias iconSource: icon.source
hoverEnabled: AppMode.isDesktop
- implicitHeight: 35
- implicitWidth: 35
+ height: root.size
+ width: root.size
+ padding: 0
MouseArea {
anchors.fill: parent
@@ -25,28 +29,44 @@ Button {
cursorShape: Qt.PointingHandCursor
}
- background: null
+ background: Rectangle {
+ id: bg
+ anchors.fill: parent
+ radius: 5
+ color: Theme.color.background
+
+
+ Behavior on color {
+ ColorAnimation { duration: 150 }
+ }
+ }
contentItem: Icon {
- id: ellipsisIcon
+ id: icon
anchors.fill: parent
source: "image://images/ellipsis"
- color: Theme.color.neutral9
- size: 35
+ size: root.size
+ color: iconColor
+
+ Behavior on color {
+ ColorAnimation { duration: 150 }
+ }
}
states: [
State {
name: "CHECKED"; when: root.checked
- PropertyChanges { target: ellipsisIcon; color: activeColor }
+ PropertyChanges { target: icon; color: activeColor }
},
State {
name: "HOVER"; when: root.hovered
- PropertyChanges { target: ellipsisIcon; color: hoverColor }
+ PropertyChanges { target: icon; color: hoverColor }
+ PropertyChanges { target: bg; color: Theme.color.neutral2 }
},
State {
name: "DISABLED"; when: !root.enabled
- PropertyChanges { target: ellipsisIcon; color: Theme.color.neutral4 }
+ PropertyChanges { target: icon; color: Theme.color.neutral4 }
+ PropertyChanges { target: bg; color: Theme.color.background }
}
]
}
diff --git a/src/qml/controls/NavButton.qml b/src/qml/controls/NavButton.qml
index 965161b983..37e4114a03 100644
--- a/src/qml/controls/NavButton.qml
+++ b/src/qml/controls/NavButton.qml
@@ -53,6 +53,7 @@ AbstractButton {
}
contentItem: RowLayout {
spacing: 0
+ anchors.fill: parent
Loader {
id: button_background
active: root.iconSource.toString().length > 0
diff --git a/src/qml/controls/SendOptionsPopup.qml b/src/qml/controls/SendOptionsPopup.qml
index f67ff139ec..b96ffc5ec8 100644
--- a/src/qml/controls/SendOptionsPopup.qml
+++ b/src/qml/controls/SendOptionsPopup.qml
@@ -13,14 +13,36 @@ OptionPopup {
id: root
property alias coinControlEnabled: coinControlToggle.checked
+ property alias multipleRecipientsEnabled: multipleRecipientsToggle.checked
+
+ implicitWidth: 300
+ implicitHeight: 100
clip: true
modal: true
dim: false
- EllipsisMenuToggleItem {
- id: coinControlToggle
+ ColumnLayout {
+ id: columnLayout
anchors.centerIn: parent
- text: qsTr("Enable Coin control")
+ anchors.margins: 10
+ spacing: 0
+
+ EllipsisMenuToggleItem {
+ id: coinControlToggle
+ Layout.fillWidth: true
+ text: qsTr("Enable Coin control")
+ }
+
+ Separator {
+ id: separator
+ Layout.fillWidth: true
+ }
+
+ EllipsisMenuToggleItem {
+ id: multipleRecipientsToggle
+ Layout.fillWidth: true
+ text: qsTr("Multiple Recipients")
+ }
}
-}
\ No newline at end of file
+}
diff --git a/src/qml/models/coinslistmodel.cpp b/src/qml/models/coinslistmodel.cpp
index 76142e74f3..a34449111f 100644
--- a/src/qml/models/coinslistmodel.cpp
+++ b/src/qml/models/coinslistmodel.cpp
@@ -122,14 +122,14 @@ QString CoinsListModel::totalSelected() const
QString CoinsListModel::changeAmount() const
{
- CAmount change = m_total_amount - m_wallet_model->sendRecipient()->cAmount();
+ CAmount change = m_total_amount - m_wallet_model->sendRecipientList()->totalAmountSatoshi();
change = std::abs(change);
return BitcoinUnits::format(BitcoinUnits::Unit::BTC, change);
}
bool CoinsListModel::overRequiredAmount() const
{
- return m_total_amount > m_wallet_model->sendRecipient()->cAmount();
+ return m_total_amount > m_wallet_model->sendRecipientList()->totalAmountSatoshi();
}
int CoinsListModel::coinCount() const
diff --git a/src/qml/models/sendrecipient.cpp b/src/qml/models/sendrecipient.cpp
index 138bea6559..ce4943d099 100644
--- a/src/qml/models/sendrecipient.cpp
+++ b/src/qml/models/sendrecipient.cpp
@@ -3,10 +3,11 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include
-#include
+
+#include
SendRecipient::SendRecipient(QObject* parent)
- : QObject(parent), m_address(""), m_label(""), m_amount(""), m_message("")
+ : QObject(parent), m_amount(new BitcoinAmount(this))
{
}
@@ -36,19 +37,11 @@ void SendRecipient::setLabel(const QString& label)
}
}
-QString SendRecipient::amount() const
+BitcoinAmount* SendRecipient::amount() const
{
return m_amount;
}
-void SendRecipient::setAmount(const QString& amount)
-{
- if (m_amount != amount) {
- m_amount = amount;
- Q_EMIT amountChanged();
- }
-}
-
QString SendRecipient::message() const
{
return m_message;
@@ -69,22 +62,18 @@ bool SendRecipient::subtractFeeFromAmount() const
CAmount SendRecipient::cAmount() const
{
- // TODO: Figure out who owns the parsing of SendRecipient::amount to CAmount
- if (m_amount == "") {
- return 0;
- }
- return m_amount.toLongLong();
+ return m_amount->satoshi();
}
void SendRecipient::clear()
{
m_address = "";
m_label = "";
- m_amount = "";
+ m_amount->setSatoshi(0);
m_message = "";
m_subtractFeeFromAmount = false;
Q_EMIT addressChanged();
Q_EMIT labelChanged();
- Q_EMIT amountChanged();
Q_EMIT messageChanged();
+ Q_EMIT amount()->amountChanged();
}
diff --git a/src/qml/models/sendrecipient.h b/src/qml/models/sendrecipient.h
index 042e97c9de..80af868a1c 100644
--- a/src/qml/models/sendrecipient.h
+++ b/src/qml/models/sendrecipient.h
@@ -5,17 +5,18 @@
#ifndef BITCOIN_QML_MODELS_SENDRECIPIENT_H
#define BITCOIN_QML_MODELS_SENDRECIPIENT_H
+#include
+
#include
#include
-#include
class SendRecipient : public QObject
{
Q_OBJECT
Q_PROPERTY(QString address READ address WRITE setAddress NOTIFY addressChanged)
Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged)
- Q_PROPERTY(QString amount READ amount WRITE setAmount NOTIFY amountChanged)
Q_PROPERTY(QString message READ message WRITE setMessage NOTIFY messageChanged)
+ Q_PROPERTY(BitcoinAmount* amount READ amount CONSTANT)
public:
explicit SendRecipient(QObject* parent = nullptr);
@@ -26,7 +27,7 @@ class SendRecipient : public QObject
QString label() const;
void setLabel(const QString& label);
- QString amount() const;
+ BitcoinAmount* amount() const;
void setAmount(const QString& amount);
QString message() const;
@@ -41,14 +42,13 @@ class SendRecipient : public QObject
Q_SIGNALS:
void addressChanged();
void labelChanged();
- void amountChanged();
void messageChanged();
private:
- QString m_address;
- QString m_label;
- QString m_amount;
- QString m_message;
+ QString m_address{""};
+ QString m_label{""};
+ QString m_message{""};
+ BitcoinAmount* m_amount;
bool m_subtractFeeFromAmount{false};
};
diff --git a/src/qml/models/sendrecipientslistmodel.cpp b/src/qml/models/sendrecipientslistmodel.cpp
new file mode 100644
index 0000000000..c4c4d0a683
--- /dev/null
+++ b/src/qml/models/sendrecipientslistmodel.cpp
@@ -0,0 +1,176 @@
+// Copyright (c) 2025 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include
+
+#include
+
+SendRecipientsListModel::SendRecipientsListModel(QObject* parent)
+ : QAbstractListModel(parent)
+{
+ auto* recipient = new SendRecipient(this);
+ connect(recipient->amount(), &BitcoinAmount::amountChanged,
+ this, &SendRecipientsListModel::updateTotalAmount);
+ m_recipients.append(recipient);
+}
+
+int SendRecipientsListModel::rowCount(const QModelIndex&) const
+{
+ return m_recipients.size();
+}
+
+QVariant SendRecipientsListModel::data(const QModelIndex& index, int role) const
+{
+ if (!index.isValid() || index.row() >= m_recipients.size())
+ return {};
+
+ const auto& r = m_recipients[index.row()];
+ switch (role) {
+ case AddressRole: return r->address();
+ case LabelRole: return r->label();
+ case AmountRole: return r->amount()->toDisplay();
+ case MessageRole: return r->message();
+ default: return {};
+ }
+ return {};
+}
+
+QHash SendRecipientsListModel::roleNames() const
+{
+ return {
+ {AddressRole, "address"},
+ {LabelRole, "label"},
+ {AmountRole, "amount"},
+ {MessageRole, "message"},
+ };
+}
+
+void SendRecipientsListModel::add()
+{
+ const int row = m_recipients.size();
+ beginInsertRows(QModelIndex(), row, row);
+ auto* recipient = new SendRecipient(this);
+ connect(recipient->amount(), &BitcoinAmount::amountChanged,
+ this, &SendRecipientsListModel::updateTotalAmount);
+ if (m_recipients.size() > 0) {
+ recipient->amount()->setUnit(m_recipients[m_current]->amount()->unit());
+ }
+ m_recipients.append(recipient);
+
+ endInsertRows();
+ Q_EMIT countChanged();
+ setCurrentIndex(row);
+}
+
+void SendRecipientsListModel::setCurrentIndex(int row)
+{
+ if (row < 0 || row >= m_recipients.size())
+ return;
+
+ if (row == m_current)
+ return;
+
+ m_current = row;
+
+ Q_EMIT currentIndexChanged();
+ Q_EMIT currentRecipientChanged();
+}
+
+void SendRecipientsListModel::next()
+{
+ setCurrentIndex(m_current + 1);
+}
+
+void SendRecipientsListModel::prev()
+{
+ setCurrentIndex(m_current - 1);
+}
+
+void SendRecipientsListModel::remove()
+{
+ if (m_recipients.size() == 1) {
+ return;
+ }
+ beginRemoveRows(QModelIndex(), m_current, m_current);
+ delete m_recipients.takeAt(m_current);
+ endRemoveRows();
+ Q_EMIT countChanged();
+
+ if (m_current > 0) {
+ setCurrentIndex(m_current - 1);
+ } else {
+ Q_EMIT currentRecipientChanged();
+ }
+}
+
+SendRecipient* SendRecipientsListModel::currentRecipient() const
+{
+ if (m_current < 0 || m_current >= m_recipients.size())
+ return nullptr;
+
+ return m_recipients[m_current];
+}
+
+void SendRecipientsListModel::updateTotalAmount()
+{
+ qint64 total = 0;
+ for (const auto& recipient : m_recipients) {
+ total += recipient->amount()->satoshi();
+ }
+ m_totalAmount = total;
+ Q_EMIT totalAmountChanged();
+}
+
+QString SendRecipientsListModel::totalAmount() const
+{
+ return BitcoinAmount::satsToBtcString(m_totalAmount);
+}
+
+void SendRecipientsListModel::clear()
+{
+ beginResetModel();
+ for (auto* recipient : m_recipients) {
+ delete recipient;
+ }
+ m_recipients.clear();
+ m_current = 0;
+ m_totalAmount = 0;
+
+ auto* recipient = new SendRecipient(this);
+ connect(recipient->amount(), &BitcoinAmount::amountChanged,
+ this, &SendRecipientsListModel::updateTotalAmount);
+ m_recipients.append(recipient);
+ endResetModel();
+
+ Q_EMIT countChanged();
+ Q_EMIT totalAmountChanged();
+ Q_EMIT currentRecipientChanged();
+ Q_EMIT currentIndexChanged();
+ Q_EMIT listCleared();
+}
+
+void SendRecipientsListModel::clearToFront()
+{
+ bool count_changed = false;
+ while (m_recipients.size() > 1) {
+ delete m_recipients.at(1);
+ m_recipients.removeAt(1);
+ count_changed = true;
+ }
+
+ if (count_changed) {
+ Q_EMIT countChanged();
+ }
+
+ if (m_totalAmount != m_recipients[0]->amount()->satoshi()) {
+ m_totalAmount = m_recipients[0]->amount()->satoshi();
+ Q_EMIT totalAmountChanged();
+ }
+
+ if (m_current != 0) {
+ m_current = 0;
+ Q_EMIT currentRecipientChanged();
+ Q_EMIT currentIndexChanged();
+ }
+}
diff --git a/src/qml/models/sendrecipientslistmodel.h b/src/qml/models/sendrecipientslistmodel.h
new file mode 100644
index 0000000000..0e9b77d4b7
--- /dev/null
+++ b/src/qml/models/sendrecipientslistmodel.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2025 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_QML_MODELS_SENDRECIPIENTSLISTMODEL_H
+#define BITCOIN_QML_MODELS_SENDRECIPIENTSLISTMODEL_H
+
+#include
+
+#include
+#include
+#include
+
+class SendRecipientsListModel : public QAbstractListModel
+{
+ Q_OBJECT
+ Q_PROPERTY(int currentIndex READ currentIndex NOTIFY currentIndexChanged)
+ Q_PROPERTY(int count READ count NOTIFY countChanged)
+ Q_PROPERTY(SendRecipient* current READ currentRecipient NOTIFY currentRecipientChanged)
+ Q_PROPERTY(QString totalAmount READ totalAmount NOTIFY totalAmountChanged)
+
+public:
+ enum Roles {
+ AddressRole = Qt::UserRole + 1,
+ LabelRole,
+ AmountRole,
+ MessageRole
+ };
+
+ explicit SendRecipientsListModel(QObject* parent = nullptr);
+
+ int rowCount(const QModelIndex& parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
+ QHash roleNames() const override;
+
+ Q_INVOKABLE void add();
+ Q_INVOKABLE void next();
+ Q_INVOKABLE void prev();
+ Q_INVOKABLE void remove();
+ Q_INVOKABLE void clear();
+ Q_INVOKABLE void clearToFront();
+
+ int currentIndex() const { return m_current + 1; }
+ void setCurrentIndex(int row);
+ SendRecipient* currentRecipient() const;
+ int count() const { return m_recipients.size(); }
+ QList recipients() const { return m_recipients; }
+ QString totalAmount() const;
+ qint64 totalAmountSatoshi() const { return m_totalAmount; }
+
+Q_SIGNALS:
+ void currentIndexChanged();
+ void currentRecipientChanged();
+ void countChanged();
+ void totalAmountChanged();
+ void listCleared();
+
+private:
+ void updateTotalAmount();
+
+ QList m_recipients;
+ int m_current{0};
+ qint64 m_totalAmount{0};
+};
+
+#endif // BITCOIN_QML_MODELS_SENDRECIPIENTSLISTMODEL_H
diff --git a/src/qml/models/walletqmlmodel.cpp b/src/qml/models/walletqmlmodel.cpp
index cdce215608..22162cd62d 100644
--- a/src/qml/models/walletqmlmodel.cpp
+++ b/src/qml/models/walletqmlmodel.cpp
@@ -1,3 +1,4 @@
+
// Copyright (c) 2024 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -6,6 +7,7 @@
#include
#include
+#include
#include
#include
@@ -24,7 +26,7 @@ WalletQmlModel::WalletQmlModel(std::unique_ptr wallet, QObje
m_wallet = std::move(wallet);
m_activity_list_model = new ActivityListModel(this);
m_coins_list_model = new CoinsListModel(this);
- m_current_recipient = new SendRecipient(this);
+ m_send_recipients = new SendRecipientsListModel(this);
}
WalletQmlModel::WalletQmlModel(QObject* parent)
@@ -32,14 +34,14 @@ WalletQmlModel::WalletQmlModel(QObject* parent)
{
m_activity_list_model = new ActivityListModel(this);
m_coins_list_model = new CoinsListModel(this);
- m_current_recipient = new SendRecipient(this);
+ m_send_recipients = new SendRecipientsListModel(this);
}
WalletQmlModel::~WalletQmlModel()
{
delete m_activity_list_model;
delete m_coins_list_model;
- delete m_current_recipient;
+ delete m_send_recipients;
if (m_current_transaction) {
delete m_current_transaction;
}
@@ -98,20 +100,25 @@ std::unique_ptr WalletQmlModel::handleTransactionChanged(Tr
bool WalletQmlModel::prepareTransaction()
{
- if (!m_wallet || !m_current_recipient) {
+ if (!m_wallet || !m_send_recipients || m_send_recipients->recipients().empty()) {
return false;
}
- CScript scriptPubKey = GetScriptForDestination(DecodeDestination(m_current_recipient->address().toStdString()));
- wallet::CRecipient recipient = {scriptPubKey, m_current_recipient->cAmount(), m_current_recipient->subtractFeeFromAmount()};
- m_coin_control.m_feerate = CFeeRate(1000);
+ std::vector vecSend;
+ CAmount total = 0;
+ for (auto* recipient : m_send_recipients->recipients()) {
+ CScript scriptPubKey = GetScriptForDestination(DecodeDestination(recipient->address().toStdString()));
+ wallet::CRecipient c_recipient = {scriptPubKey, recipient->cAmount(), recipient->subtractFeeFromAmount()};
+ m_coin_control.m_feerate = CFeeRate(1000);
+ vecSend.push_back(c_recipient);
+ total += recipient->cAmount();
+ }
CAmount balance = m_wallet->getBalance();
- if (balance < recipient.nAmount) {
+ if (balance < total) {
return false;
}
- std::vector vecSend{recipient};
int nChangePosRet = -1;
CAmount nFeeRequired = 0;
const auto& res = m_wallet->createTransaction(vecSend, m_coin_control, true, nChangePosRet, nFeeRequired);
@@ -120,7 +127,7 @@ bool WalletQmlModel::prepareTransaction()
delete m_current_transaction;
}
CTransactionRef newTx = *res;
- m_current_transaction = new WalletQmlModelTransaction(m_current_recipient, this);
+ m_current_transaction = new WalletQmlModelTransaction(m_send_recipients, this);
m_current_transaction->setWtx(newTx);
m_current_transaction->setTransactionFee(nFeeRequired);
Q_EMIT currentTransactionChanged();
diff --git a/src/qml/models/walletqmlmodel.h b/src/qml/models/walletqmlmodel.h
index d97cd0851f..de8c5540ac 100644
--- a/src/qml/models/walletqmlmodel.h
+++ b/src/qml/models/walletqmlmodel.h
@@ -10,6 +10,7 @@
#include
#include
#include
+#include
#include
#include
@@ -26,7 +27,7 @@ class WalletQmlModel : public QObject
Q_PROPERTY(QString balance READ balance NOTIFY balanceChanged)
Q_PROPERTY(ActivityListModel* activityListModel READ activityListModel CONSTANT)
Q_PROPERTY(CoinsListModel* coinsListModel READ coinsListModel CONSTANT)
- Q_PROPERTY(SendRecipient* sendRecipient READ sendRecipient CONSTANT)
+ Q_PROPERTY(SendRecipientsListModel* recipients READ sendRecipientList CONSTANT)
Q_PROPERTY(WalletQmlModelTransaction* currentTransaction READ currentTransaction NOTIFY currentTransactionChanged)
public:
@@ -38,6 +39,10 @@ class WalletQmlModel : public QObject
QString balance() const;
ActivityListModel* activityListModel() const { return m_activity_list_model; }
CoinsListModel* coinsListModel() const { return m_coins_list_model; }
+ SendRecipientsListModel* sendRecipientList() const { return m_send_recipients; }
+ WalletQmlModelTransaction* currentTransaction() const { return m_current_transaction; }
+ Q_INVOKABLE bool prepareTransaction();
+ Q_INVOKABLE void sendTransaction();
std::set getWalletTxs() const;
interfaces::WalletTx getWalletTx(const uint256& hash) const;
@@ -46,11 +51,6 @@ class WalletQmlModel : public QObject
int& num_blocks,
int64_t& block_time) const;
- SendRecipient* sendRecipient() const { return m_current_recipient; }
- WalletQmlModelTransaction* currentTransaction() const { return m_current_transaction; }
- Q_INVOKABLE bool prepareTransaction();
- Q_INVOKABLE void sendTransaction();
-
using TransactionChangedFn = std::function;
virtual std::unique_ptr handleTransactionChanged(TransactionChangedFn fn);
@@ -73,7 +73,7 @@ class WalletQmlModel : public QObject
std::unique_ptr m_wallet;
ActivityListModel* m_activity_list_model{nullptr};
CoinsListModel* m_coins_list_model{nullptr};
- SendRecipient* m_current_recipient{nullptr};
+ SendRecipientsListModel* m_send_recipients{nullptr};
WalletQmlModelTransaction* m_current_transaction{nullptr};
wallet::CCoinControl m_coin_control;
};
diff --git a/src/qml/models/walletqmlmodeltransaction.cpp b/src/qml/models/walletqmlmodeltransaction.cpp
index 199103377a..2303606a0e 100644
--- a/src/qml/models/walletqmlmodeltransaction.cpp
+++ b/src/qml/models/walletqmlmodeltransaction.cpp
@@ -5,10 +5,9 @@
#include
#include
-#include
-WalletQmlModelTransaction::WalletQmlModelTransaction(const SendRecipient* recipient, QObject* parent)
- : QObject(parent), m_address(recipient->address()), m_amount(recipient->cAmount()), m_fee(0), m_label(recipient->label()), m_wtx(nullptr)
+WalletQmlModelTransaction::WalletQmlModelTransaction(const SendRecipientsListModel* recipient, QObject* parent)
+ : QObject(parent), m_address(recipient->recipients().at(0)->address()), m_amount(recipient->totalAmountSatoshi()), m_fee(0), m_label(recipient->recipients().at(0)->label()), m_wtx(nullptr)
{
}
diff --git a/src/qml/models/walletqmlmodeltransaction.h b/src/qml/models/walletqmlmodeltransaction.h
index 7bf914e06a..35112249de 100644
--- a/src/qml/models/walletqmlmodeltransaction.h
+++ b/src/qml/models/walletqmlmodeltransaction.h
@@ -5,12 +5,10 @@
#ifndef BITCOIN_QML_MODELS_WALLETQMLMODELTRANSACTION_H
#define BITCOIN_QML_MODELS_WALLETQMLMODELTRANSACTION_H
-#include
-#include
+#include
#include
-
-#include
+#include
class WalletQmlModelTransaction : public QObject
@@ -22,7 +20,7 @@ class WalletQmlModelTransaction : public QObject
Q_PROPERTY(QString fee READ fee NOTIFY feeChanged)
Q_PROPERTY(QString total READ total NOTIFY totalChanged)
public:
- explicit WalletQmlModelTransaction(const SendRecipient* recipient, QObject* parent = nullptr);
+ explicit WalletQmlModelTransaction(const SendRecipientsListModel* recipient, QObject* parent = nullptr);
QString address() const;
QString amount() const;
@@ -30,8 +28,6 @@ class WalletQmlModelTransaction : public QObject
QString label() const;
QString total() const;
- QList getRecipients() const;
-
CTransactionRef& getWtx();
void setWtx(const CTransactionRef&);
diff --git a/src/qml/pages/main.qml b/src/qml/pages/main.qml
index 60aa6c2705..710e2c3bae 100644
--- a/src/qml/pages/main.qml
+++ b/src/qml/pages/main.qml
@@ -85,7 +85,11 @@ ApplicationWindow {
main.push(createWalletWizard)
}
onSendTransaction: {
- main.push(sendReviewPage)
+ if (multipleRecipientsEnabled) {
+ main.push(multipleSendReviewPage)
+ } else {
+ main.push(sendReviewPage)
+ }
}
}
}
@@ -106,7 +110,21 @@ ApplicationWindow {
main.pop()
}
onTransactionSent: {
- walletController.selectedWallet.sendRecipient.clear()
+ walletController.selectedWallet.recipients.clear()
+ main.pop()
+ sendResult.open()
+ }
+ }
+ }
+
+ Component {
+ id: multipleSendReviewPage
+ MultipleSendReview {
+ onBack: {
+ main.pop()
+ }
+ onTransactionSent: {
+ walletController.selectedWallet.recipients.clear()
main.pop()
sendResult.open()
}
diff --git a/src/qml/pages/wallet/DesktopWallets.qml b/src/qml/pages/wallet/DesktopWallets.qml
index d2bf0469b3..bdb6bb4ba6 100644
--- a/src/qml/pages/wallet/DesktopWallets.qml
+++ b/src/qml/pages/wallet/DesktopWallets.qml
@@ -20,7 +20,7 @@ Page {
ButtonGroup { id: navigationTabs }
signal addWallet()
- signal sendTransaction()
+ signal sendTransaction(bool multipleRecipientsEnabled)
header: NavigationBar2 {
id: navBar
@@ -132,7 +132,7 @@ Page {
Activity {
}
Send {
- onTransactionPrepared: root.sendTransaction()
+ onTransactionPrepared: root.sendTransaction(multipleRecipientsEnabled)
}
RequestPayment {
}
diff --git a/src/qml/pages/wallet/MultipleSendReview.qml b/src/qml/pages/wallet/MultipleSendReview.qml
new file mode 100644
index 0000000000..91f6ee2d96
--- /dev/null
+++ b/src/qml/pages/wallet/MultipleSendReview.qml
@@ -0,0 +1,155 @@
+// Copyright (c) 2024 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.15
+import org.bitcoincore.qt 1.0
+
+import "../../controls"
+import "../../components"
+
+Page {
+ id: root
+ background: null
+
+ property WalletQmlModel wallet: walletController.selectedWallet
+ property WalletQmlModelTransaction transaction: walletController.selectedWallet.currentTransaction
+
+ signal finished()
+ signal back()
+ signal transactionSent()
+
+ header: NavigationBar2 {
+ id: navbar
+ leftItem: NavButton {
+ iconSource: "image://images/caret-left"
+ text: qsTr("Back")
+ onClicked: {
+ root.back()
+ }
+ }
+ }
+
+ ScrollView {
+ clip: true
+ width: parent.width
+ height: parent.height
+ contentWidth: width
+
+ ColumnLayout {
+ id: columnLayout
+ width: 450
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ spacing: 15
+
+ CoreText {
+ id: title
+ Layout.topMargin: 30
+ Layout.bottomMargin: 15
+ text: qsTr("Transaction details")
+ font.pixelSize: 21
+ bold: true
+ }
+
+ ListView {
+ id: inputsList
+ Layout.fillWidth: true
+ Layout.preferredHeight: contentHeight
+ model: root.wallet.recipients
+ delegate: Item {
+ id: delegate
+ height: 55
+ width: ListView.view.width
+
+ required property string address;
+ required property string label;
+ required property string amount;
+
+ RowLayout {
+ spacing: 10
+ anchors.fill: parent
+ CoreText {
+ Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
+ Layout.fillWidth: true
+ horizontalAlignment: Text.AlignLeft
+ text: label == "" ? address : label
+ font.pixelSize: 18
+ elide: Text.ElideMiddle
+ }
+
+ CoreText {
+ Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
+ text: amount
+ font.pixelSize: 18
+ }
+ }
+
+ Separator {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ color: Theme.color.neutral3
+ }
+ }
+ }
+
+ RowLayout {
+ Layout.topMargin: 20
+ CoreText {
+ text: qsTr("Total amount")
+ font.pixelSize: 20
+ color: Theme.color.neutral9
+ horizontalAlignment: Text.AlignLeft
+ }
+ Item {
+ Layout.fillWidth: true
+ }
+ CoreText {
+ text: root.transaction.total
+ font.pixelSize: 20
+ color: Theme.color.neutral9
+ }
+ }
+
+ Separator {
+ Layout.fillWidth: true
+ color: Theme.color.neutral3
+ }
+
+ RowLayout {
+ CoreText {
+ text: qsTr("Fee")
+ font.pixelSize: 18
+ Layout.preferredWidth: 110
+ horizontalAlignment: Text.AlignLeft
+ }
+ Item {
+ Layout.fillWidth: true
+ }
+ CoreText {
+ text: root.transaction.fee
+ font.pixelSize: 15
+ }
+ }
+
+ Separator {
+ Layout.fillWidth: true
+ color: Theme.color.neutral3
+ }
+
+ ContinueButton {
+ id: confimationButton
+ Layout.fillWidth: true
+ Layout.topMargin: 30
+ text: qsTr("Send")
+ onClicked: {
+ root.wallet.sendTransaction()
+ root.transactionSent()
+ }
+ }
+ }
+ }
+}
diff --git a/src/qml/pages/wallet/Send.qml b/src/qml/pages/wallet/Send.qml
index 9c1905db99..2e4841b0a0 100644
--- a/src/qml/pages/wallet/Send.qml
+++ b/src/qml/pages/wallet/Send.qml
@@ -16,9 +16,9 @@ PageStack {
vertical: true
property WalletQmlModel wallet: walletController.selectedWallet
- property SendRecipient recipient: wallet.sendRecipient
+ property SendRecipient recipient: wallet.recipients.current
- signal transactionPrepared()
+ signal transactionPrepared(bool multipleRecipientsEnabled)
Connections {
target: walletController
@@ -27,18 +27,36 @@ PageStack {
}
}
+ Connections {
+ target: root.wallet.recipients
+ function onListCleared() {
+ settings.multipleRecipientsEnabled = false
+ }
+ }
+
+
initialItem: Page {
background: null
Settings {
id: settings
property alias coinControlEnabled: sendOptionsPopup.coinControlEnabled
+ property alias multipleRecipientsEnabled: sendOptionsPopup.multipleRecipientsEnabled
+
+ onMultipleRecipientsEnabledChanged: {
+ if (!multipleRecipientsEnabled) {
+ root.wallet.recipients.clearToFront()
+ } else {
+ root.wallet.recipients.add()
+ }
+ }
}
ScrollView {
clip: true
width: parent.width
height: parent.height
+
contentWidth: width
ColumnLayout {
@@ -55,6 +73,7 @@ PageStack {
Layout.fillWidth: true
Layout.topMargin: 30
Layout.bottomMargin: 20
+
CoreText {
id: title
anchors.left: parent.left
@@ -64,11 +83,13 @@ PageStack {
color: Theme.color.neutral9
bold: true
}
- EllipsisMenuButton {
+
+ IconButton {
id: menuButton
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
checked: sendOptionsPopup.opened
+ iconSource: "image://images/ellipsis"
onClicked: {
sendOptionsPopup.open()
}
@@ -78,11 +99,77 @@ PageStack {
id: sendOptionsPopup
x: menuButton.x - width + menuButton.width
y: menuButton.y + menuButton.height
- width: 300
- height: 50
}
}
+ RowLayout {
+ id: selectAndAddRecipients
+ Layout.fillWidth: true
+ Layout.topMargin: 10
+ Layout.bottomMargin: 10
+ visible: settings.multipleRecipientsEnabled
+
+ CoreText {
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignLeft
+ id: selectAndAddRecipientsLabel
+ text: qsTr("Recipient %1 of %2").arg(wallet.recipients.currentIndex).arg(wallet.recipients.count)
+ horizontalAlignment: Text.AlignLeft
+ font.pixelSize: 18
+ color: Theme.color.neutral9
+ }
+
+ IconButton {
+ Layout.preferredWidth: 30
+ Layout.preferredHeight: 30
+ size: 30
+ iconSource: "image://images/caret-left"
+ enabled: wallet.recipients.currentIndex - 1 > 0
+ onClicked: {
+ wallet.recipients.prev()
+
+ }
+ }
+
+ IconButton {
+ Layout.preferredWidth: 30
+ Layout.preferredHeight: 30
+ size: 30
+ iconSource: "image://images/caret-right"
+ enabled: wallet.recipients.currentIndex < wallet.recipients.count
+ onClicked: {
+ wallet.recipients.next()
+ }
+ }
+
+ IconButton {
+ Layout.preferredWidth: 30
+ Layout.preferredHeight: 30
+ size: 30
+ iconSource: "image://images/plus-big-filled"
+ enabled: wallet.recipients.count < 25
+ onClicked: {
+ wallet.recipients.add()
+ }
+ }
+
+ IconButton {
+ Layout.preferredWidth: 30
+ Layout.preferredHeight: 30
+ size: 30
+ iconSource: "image://images/minus"
+ enabled: wallet.recipients.count > 1
+ onClicked: {
+ wallet.recipients.remove()
+ }
+ }
+ }
+
+ Separator {
+ visible: settings.multipleRecipientsEnabled
+ Layout.fillWidth: true
+ }
+
LabeledTextInput {
id: address
Layout.fillWidth: true
@@ -97,10 +184,6 @@ PageStack {
}
Item {
- BitcoinAmount {
- id: bitcoinAmount
- }
-
height: amountInput.height
Layout.fillWidth: true
CoreText {
@@ -126,9 +209,13 @@ PageStack {
background: Item {}
placeholderText: "0.00000000"
selectByMouse: true
- onTextEdited: {
- amountInput.text = bitcoinAmount.amount = bitcoinAmount.sanitize(amountInput.text)
- root.recipient.amount = bitcoinAmount.satoshiAmount
+ text: root.recipient.amount.display
+ onTextEdited: root.recipient.amount.display = text
+ onEditingFinished: root.recipient.amount.format()
+ onActiveFocusChanged: {
+ if (!activeFocus) {
+ root.recipient.amount.display = text
+ }
}
}
Item {
@@ -138,21 +225,13 @@ PageStack {
anchors.verticalCenter: parent.verticalCenter
MouseArea {
anchors.fill: parent
- onClicked: {
- if (bitcoinAmount.unit == BitcoinAmount.BTC) {
- amountInput.text = bitcoinAmount.convert(amountInput.text, BitcoinAmount.BTC)
- bitcoinAmount.unit = BitcoinAmount.SAT
- } else {
- amountInput.text = bitcoinAmount.convert(amountInput.text, BitcoinAmount.SAT)
- bitcoinAmount.unit = BitcoinAmount.BTC
- }
- }
+ onClicked: root.recipient.amount.flipUnit()
}
CoreText {
id: unitLabel
anchors.right: flipIcon.left
anchors.verticalCenter: parent.verticalCenter
- text: bitcoinAmount.unitLabel
+ text: root.recipient.amount.unitLabel
font.pixelSize: 18
color: enabled ? Theme.color.neutral7 : Theme.color.neutral4
}
@@ -176,6 +255,7 @@ PageStack {
Layout.fillWidth: true
labelText: qsTr("Note to self")
placeholderText: qsTr("Enter ...")
+ text: root.recipient.label
onTextEdited: root.recipient.label = label.text
}
@@ -226,7 +306,7 @@ PageStack {
text: qsTr("Review")
onClicked: {
if (root.wallet.prepareTransaction()) {
- root.transactionPrepared()
+ root.transactionPrepared(settings.multipleRecipientsEnabled);
}
}
}
diff --git a/src/qml/res/icons/plus-big-filled.png b/src/qml/res/icons/plus-big-filled.png
new file mode 100644
index 0000000000..365ed049e5
Binary files /dev/null and b/src/qml/res/icons/plus-big-filled.png differ
diff --git a/src/qml/res/src/plus-big-filled.svg b/src/qml/res/src/plus-big-filled.svg
new file mode 100644
index 0000000000..2efe7ba2c5
--- /dev/null
+++ b/src/qml/res/src/plus-big-filled.svg
@@ -0,0 +1,3 @@
+