diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include
index f2f6e31131..c52728fce2 100644
--- a/src/Makefile.qt.include
+++ b/src/Makefile.qt.include
@@ -39,6 +39,7 @@ QT_MOC_CPP = \
qml/controls/moc_linegraph.cpp \
qml/models/moc_activitylistmodel.cpp \
qml/models/moc_chainmodel.cpp \
+ qml/models/moc_coinslistmodel.cpp \
qml/models/moc_networktraffictower.cpp \
qml/models/moc_nodemodel.cpp \
qml/models/moc_options_model.cpp \
@@ -129,6 +130,7 @@ BITCOIN_QT_H = \
qml/controls/linegraph.h \
qml/models/activitylistmodel.h \
qml/models/chainmodel.h \
+ qml/models/coinslistmodel.h \
qml/models/networktraffictower.h \
qml/models/nodemodel.h \
qml/models/options_model.h \
@@ -328,6 +330,7 @@ BITCOIN_QML_BASE_CPP = \
qml/controls/linegraph.cpp \
qml/models/activitylistmodel.cpp \
qml/models/chainmodel.cpp \
+ qml/models/coinslistmodel.cpp \
qml/models/networktraffictower.cpp \
qml/models/nodemodel.cpp \
qml/models/options_model.cpp \
@@ -361,6 +364,7 @@ QML_RES_ICONS = \
qml/res/icons/copy.png \
qml/res/icons/coinbase.png \
qml/res/icons/cross.png \
+ qml/res/icons/ellipsis.png \
qml/res/icons/error.png \
qml/res/icons/export.png \
qml/res/icons/flip-vertical.png \
@@ -368,6 +372,7 @@ QML_RES_ICONS = \
qml/res/icons/gear-outline.png \
qml/res/icons/hidden.png \
qml/res/icons/info.png \
+ qml/res/icons/lock.png \
qml/res/icons/minus.png \
qml/res/icons/network-dark.png \
qml/res/icons/network-light.png \
@@ -395,9 +400,10 @@ QML_RES_QML = \
qml/components/ConnectionSettings.qml \
qml/components/DeveloperOptions.qml \
qml/components/ExternalPopup.qml \
- qml/components/PeersIndicator.qml \
qml/components/NetworkTrafficGraph.qml \
qml/components/NetworkIndicator.qml \
+ qml/components/OptionPopup.qml \
+ qml/components/PeersIndicator.qml \
qml/components/ProxySettings.qml \
qml/components/Separator.qml \
qml/components/StorageLocations.qml \
@@ -409,8 +415,11 @@ QML_RES_QML = \
qml/controls/AddWalletButton.qml \
qml/controls/CaretRightIcon.qml \
qml/controls/ContinueButton.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 \
@@ -419,6 +428,7 @@ QML_RES_QML = \
qml/controls/IPAddressValueInput.qml \
qml/controls/KeyValueRow.qml \
qml/controls/LabeledTextInput.qml \
+ qml/controls/LabeledCoinControlButton.qml \
qml/controls/NavButton.qml \
qml/controls/NavigationBar.qml \
qml/controls/NavigationBar2.qml \
@@ -431,6 +441,7 @@ QML_RES_QML = \
qml/controls/ProgressIndicator.qml \
qml/controls/qmldir \
qml/controls/Setting.qml \
+ qml/controls/SendOptionsPopup.qml \
qml/controls/TextButton.qml \
qml/controls/Theme.qml \
qml/controls/ToggleButton.qml \
@@ -461,6 +472,7 @@ QML_RES_QML = \
qml/pages/settings/SettingsTheme.qml \
qml/pages/wallet/Activity.qml \
qml/pages/wallet/ActivityDetails.qml \
+ qml/pages/wallet/CoinSelection.qml \
qml/pages/wallet/CreateBackup.qml \
qml/pages/wallet/CreateConfirm.qml \
qml/pages/wallet/CreateIntro.qml \
diff --git a/src/qml/bitcoin_qml.qrc b/src/qml/bitcoin_qml.qrc
index 69a07de568..8c4b231ef9 100644
--- a/src/qml/bitcoin_qml.qrc
+++ b/src/qml/bitcoin_qml.qrc
@@ -4,14 +4,14 @@
components/BlockClock.qml
components/BlockClockDisplayMode.qml
components/BlockCounter.qml
- controls/CaretRightIcon.qml
components/ConnectionOptions.qml
components/ConnectionSettings.qml
- components/PeersIndicator.qml
components/DeveloperOptions.qml
components/ExternalPopup.qml
components/NetworkTrafficGraph.qml
components/NetworkIndicator.qml
+ components/OptionPopup.qml
+ components/PeersIndicator.qml
components/ProxySettings.qml
components/StorageLocations.qml
components/Separator.qml
@@ -21,7 +21,9 @@
components/TotalBytesIndicator.qml
components/Tooltip.qml
controls/AddWalletButton.qml
+ controls/CaretRightIcon.qml
controls/ContinueButton.qml
+ controls/CoreCheckBox.qml
controls/CoreText.qml
controls/CoreTextField.qml
controls/ExternalLink.qml
@@ -32,6 +34,9 @@
controls/IPAddressValueInput.qml
controls/KeyValueRow.qml
controls/LabeledTextInput.qml
+ controls/LabeledCoinControlButton.qml
+ controls/EllipsisMenuButton.qml
+ controls/EllipsisMenuToggleItem.qml
controls/NavButton.qml
controls/NavigationBar.qml
controls/NavigationBar2.qml
@@ -43,6 +48,7 @@
controls/PageStack.qml
controls/ProgressIndicator.qml
controls/qmldir
+ controls/SendOptionsPopup.qml
controls/Setting.qml
controls/TextButton.qml
controls/Theme.qml
@@ -74,6 +80,7 @@
pages/settings/SettingsTheme.qml
pages/wallet/Activity.qml
pages/wallet/ActivityDetails.qml
+ pages/wallet/CoinSelection.qml
pages/wallet/CreateBackup.qml
pages/wallet/CreateConfirm.qml
pages/wallet/CreateIntro.qml
@@ -104,6 +111,7 @@
res/icons/copy.png
res/icons/coinbase.png
res/icons/cross.png
+ res/icons/ellipsis.png
res/icons/error.png
res/icons/export.png
res/icons/flip-vertical.png
@@ -111,6 +119,7 @@
res/icons/gear-outline.png
res/icons/hidden.png
res/icons/info.png
+ res/icons/lock.png
res/icons/minus.png
res/icons/network-dark.png
res/icons/network-light.png
diff --git a/src/qml/components/OptionPopup.qml b/src/qml/components/OptionPopup.qml
new file mode 100644
index 0000000000..3742cf2c54
--- /dev/null
+++ b/src/qml/components/OptionPopup.qml
@@ -0,0 +1,24 @@
+// 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.
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.15
+
+import "../controls"
+
+Popup {
+ id: root
+
+ background: Item {
+ anchors.fill: parent
+ Rectangle {
+ color: Theme.color.neutral0
+ border.color: Theme.color.neutral4
+ radius: 5
+ border.width: 1
+ anchors.fill: parent
+ }
+ }
+}
diff --git a/src/qml/controls/CoreCheckBox.qml b/src/qml/controls/CoreCheckBox.qml
new file mode 100644
index 0000000000..c7fe6af925
--- /dev/null
+++ b/src/qml/controls/CoreCheckBox.qml
@@ -0,0 +1,27 @@
+// 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.
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+
+AbstractButton {
+ id: root
+ implicitWidth: 20
+ implicitHeight: 20
+
+ property color borderColor: Theme.color.neutral9
+ property color fillColor: Theme.color.neutral9
+
+ background: null
+
+ checkable: true
+ hoverEnabled: AppMode.isDesktop
+
+ contentItem: Rectangle {
+ radius: 3
+ border.color: root.checked ? root.fillColor : root.borderColor
+ border.width: 1
+ color: root.checked ? root.fillColor : "transparent"
+ }
+}
diff --git a/src/qml/controls/EllipsisMenuButton.qml b/src/qml/controls/EllipsisMenuButton.qml
new file mode 100644
index 0000000000..884f990dbb
--- /dev/null
+++ b/src/qml/controls/EllipsisMenuButton.qml
@@ -0,0 +1,48 @@
+// 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.
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.15
+
+import org.bitcoincore.qt 1.0
+
+Button {
+ id: root
+
+ property color hoverColor: Theme.color.orange
+ property color activeColor: Theme.color.orange
+
+ hoverEnabled: AppMode.isDesktop
+ implicitHeight: 35
+ implicitWidth: 35
+
+ MouseArea {
+ anchors.fill: parent
+ enabled: false
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ }
+
+ background: null
+
+ contentItem: Icon {
+ id: ellipsisIcon
+ anchors.fill: parent
+ source: "image://images/ellipsis"
+ color: Theme.color.neutral9
+ size: 35
+ }
+
+ states: [
+ State {
+ name: "CHECKED"; when: root.checked
+ PropertyChanges { target: ellipsisIcon; color: activeColor }
+ },
+ State {
+ name: "HOVER"; when: root.hovered
+ PropertyChanges { target: ellipsisIcon; color: hoverColor }
+ }
+ ]
+}
diff --git a/src/qml/controls/EllipsisMenuToggleItem.qml b/src/qml/controls/EllipsisMenuToggleItem.qml
new file mode 100644
index 0000000000..71e7ec38d6
--- /dev/null
+++ b/src/qml/controls/EllipsisMenuToggleItem.qml
@@ -0,0 +1,74 @@
+// 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.
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.15
+import org.bitcoincore.qt 1.0
+
+Button {
+ property int bgRadius: 5
+ property color bgDefaultColor: "transparent"
+ property color bgHoverColor: Theme.color.neutral2
+ property color textColor: Theme.color.neutral7
+ property color textHoverColor: Theme.color.neutral9
+ property color textActiveColor: Theme.color.neutral7
+
+ id: root
+ checkable: true
+ checked: optionSwitch.checked
+ hoverEnabled: AppMode.isDesktop
+
+ implicitWidth: 280
+
+ MouseArea {
+ anchors.fill: parent
+ enabled: false
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ }
+
+ onClicked: {
+ optionSwitch.checked = !optionSwitch.checked
+ }
+
+ contentItem: RowLayout {
+ spacing: 7
+ anchors.fill: parent
+ anchors.centerIn: parent
+ anchors.margins: 10
+ CoreText {
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignVCenter
+ horizontalAlignment: Text.AlignLeft
+ font.pixelSize: 15
+ text: root.text
+ }
+ OptionSwitch {
+ id: optionSwitch
+ Layout.alignment: Qt.AlignVCenter
+ Layout.preferredWidth: 40
+ Layout.preferredHeight: 24
+ checked: root.checked
+ }
+ }
+
+ background: Rectangle {
+ id: bg
+ color: root.bgDefaultColor
+ radius: root.bgRadius
+
+ Behavior on color {
+ ColorAnimation { duration: 150 }
+ }
+ }
+
+ states: [
+ State {
+ name: "HOVER"; when: root.hovered
+ PropertyChanges { target: bg; color: root.bgHoverColor }
+ PropertyChanges { target: buttonText; color: root.textHoverColor }
+ }
+ ]
+}
diff --git a/src/qml/controls/LabeledCoinControlButton.qml b/src/qml/controls/LabeledCoinControlButton.qml
new file mode 100644
index 0000000000..7d82e89e8e
--- /dev/null
+++ b/src/qml/controls/LabeledCoinControlButton.qml
@@ -0,0 +1,57 @@
+// 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.
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.15
+
+Item {
+ property int coinsSelected: 0
+ property int coinCount: 0
+
+ signal openCoinControl
+
+ id: root
+ implicitHeight: label.height
+
+ CoreText {
+ id: label
+ anchors.left: parent.left
+ anchors.verticalCenter: parent.verticalCenter
+ horizontalAlignment: Text.AlignLeft
+ width: 110
+ color: Theme.color.neutral9
+ font.pixelSize: 18
+ text: qsTr("Inputs")
+ }
+
+ CoreText {
+ anchors.left: label.right
+ anchors.verticalCenter: parent.verticalCenter
+ horizontalAlignment: Text.AlignLeft
+ color: Theme.color.orangeLight1
+ font.pixelSize: 18
+ text: {
+ if (coinCount === 0) {
+ qsTr("No coins available")
+ } else if (coinsSelected === 0) {
+ qsTr("Select")
+ } else {
+ qsTr("%1 input%2 selected")
+ .arg(coinsSelected)
+ .arg(coinsSelected === 1 ? "" : "s")
+ }
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ if (coinCount > 0) {
+ root.openCoinControl()
+ }
+ }
+ cursorShape: Qt.PointingHandCursor
+ }
+ }
+}
diff --git a/src/qml/controls/SendOptionsPopup.qml b/src/qml/controls/SendOptionsPopup.qml
new file mode 100644
index 0000000000..f67ff139ec
--- /dev/null
+++ b/src/qml/controls/SendOptionsPopup.qml
@@ -0,0 +1,26 @@
+// 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.
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.15
+
+import "../components"
+import "../controls"
+
+OptionPopup {
+ id: root
+
+ property alias coinControlEnabled: coinControlToggle.checked
+
+ clip: true
+ modal: true
+ dim: false
+
+ EllipsisMenuToggleItem {
+ id: coinControlToggle
+ anchors.centerIn: parent
+ text: qsTr("Enable Coin control")
+ }
+}
\ No newline at end of file
diff --git a/src/qml/imageprovider.cpp b/src/qml/imageprovider.cpp
index aeed8a5b97..19b0b62f04 100644
--- a/src/qml/imageprovider.cpp
+++ b/src/qml/imageprovider.cpp
@@ -6,6 +6,7 @@
#include
+#include
#include
#include
#include
@@ -22,194 +23,15 @@ QPixmap ImageProvider::requestPixmap(const QString& id, QSize* size, const QSize
return {};
}
- if (id == "arrow-down") {
- *size = requested_size;
- return QIcon(":/icons/arrow-down").pixmap(requested_size);
- }
-
- if (id == "arrow-up") {
- *size = requested_size;
- return QIcon(":/icons/arrow-up").pixmap(requested_size);
- }
-
- if (id == "bitcoin-circle") {
- *size = requested_size;
- return QIcon(":/icons/bitcoin-circle").pixmap(requested_size);
- }
-
- if (id == "blockclock-size-compact") {
- *size = requested_size;
- return QIcon(":/icons/blockclock-size-compact").pixmap(requested_size);
- }
-
- if (id == "blockclock-size-showcase") {
- *size = requested_size;
- return QIcon(":/icons/blockclock-size-showcase").pixmap(requested_size);
- }
-
- if (id == "blocktime-dark") {
- *size = requested_size;
- return QIcon(":/icons/blocktime-dark").pixmap(requested_size);
- }
-
- if (id == "blocktime-light") {
- *size = requested_size;
- return QIcon(":/icons/blocktime-light").pixmap(requested_size);
- }
-
if (id == "app") {
*size = requested_size;
return m_network_style->getAppIcon().pixmap(requested_size);
}
- if (id == "caret-left") {
- *size = requested_size;
- return QIcon(":/icons/caret-left").pixmap(requested_size);
- }
-
- if (id == "caret-right") {
- *size = requested_size;
- return QIcon(":/icons/caret-right").pixmap(requested_size);
- }
-
- if (id == "coinbase") {
- *size = requested_size;
- return QIcon(":/icons/coinbase").pixmap(requested_size);
- }
-
- if (id == "check") {
- *size = requested_size;
- return QIcon(":/icons/check").pixmap(requested_size);
- }
-
- if (id == "copy") {
- *size = requested_size;
- return QIcon(":/icons/copy").pixmap(requested_size);
- }
-
- if (id == "cross") {
- *size = requested_size;
- return QIcon(":/icons/cross").pixmap(requested_size);
- }
-
- if (id == "error") {
- *size = requested_size;
- return QIcon(":/icons/error").pixmap(requested_size);
- }
-
- if (id == "export") {
- *size = requested_size;
- return QIcon(":/icons/export").pixmap(requested_size);
- }
-
- if (id == "flip-vertical") {
- *size = requested_size;
- return QIcon(":/icons/flip-vertical").pixmap(requested_size);
- }
-
- if (id == "gear") {
- *size = requested_size;
- return QIcon(":/icons/gear").pixmap(requested_size);
- }
-
- if (id == "gear-outline") {
- *size = requested_size;
- return QIcon(":/icons/gear-outline").pixmap(requested_size);
- }
-
- if (id == "info") {
+ if (QFile::exists(":/icons/" + id)) {
*size = requested_size;
- return QIcon(":/icons/info").pixmap(requested_size);
+ return QIcon(":/icons/" + id).pixmap(requested_size);
}
- if (id == "minus") {
- *size = requested_size;
- return QIcon(":/icons/minus").pixmap(requested_size);
- }
-
- if (id == "network-dark") {
- *size = requested_size;
- return QIcon(":/icons/network-dark").pixmap(requested_size);
- }
-
- if (id == "network-light") {
- *size = requested_size;
- return QIcon(":/icons/network-light").pixmap(requested_size);
- }
-
- if (id == "pending") {
- *size = requested_size;
- return QIcon(":/icons/pending").pixmap(requested_size);
- }
-
- if (id == "shutdown") {
- *size = requested_size;
- return QIcon(":/icons/shutdown").pixmap(requested_size);
- }
-
- if (id == "singlesig-wallet") {
- *size = requested_size;
- return QIcon(":/icons/singlesig-wallet").pixmap(requested_size);
- }
-
- if (id == "storage-dark") {
- *size = requested_size;
- return QIcon(":/icons/storage-dark").pixmap(requested_size);
- }
-
- if (id == "storage-light") {
- *size = requested_size;
- return QIcon(":/icons/storage-light").pixmap(requested_size);
- }
-
- if (id == "tooltip-arrow-dark") {
- *size = requested_size;
- return QIcon(":/icons/tooltip-arrow-dark").pixmap(requested_size);
- }
-
- if (id == "tooltip-arrow-light") {
- *size = requested_size;
- return QIcon(":/icons/tooltip-arrow-light").pixmap(requested_size);
- }
-
- if (id == "triangle-up") {
- *size = requested_size;
- return QIcon(":/icons/triangle-up").pixmap(requested_size);
- }
-
- if (id == "triangle-down") {
- *size = requested_size;
- return QIcon(":/icons/triangle-down").pixmap(requested_size);
- }
-
- if (id == "add-wallet-dark") {
- *size = requested_size;
- return QIcon(":/icons/add-wallet-dark").pixmap(requested_size);
- }
-
- if (id == "wallet") {
- *size = requested_size;
- return QIcon(":/icons/wallet").pixmap(requested_size);
- }
-
- if (id == "visible") {
- *size = requested_size;
- return QIcon(":/icons/visible").pixmap(requested_size);
- }
-
- if (id == "hidden") {
- *size = requested_size;
- return QIcon(":/icons/hidden").pixmap(requested_size);
- }
-
- if (id == "plus") {
- *size = requested_size;
- return QIcon(":/icons/plus").pixmap(requested_size);
- }
-
- if (id == "flip-vertical") {
- *size = requested_size;
- return QIcon(":/icons/flip-vertical").pixmap(requested_size);
- }
return {};
}
diff --git a/src/qml/models/coinslistmodel.cpp b/src/qml/models/coinslistmodel.cpp
new file mode 100644
index 0000000000..76142e74f3
--- /dev/null
+++ b/src/qml/models/coinslistmodel.cpp
@@ -0,0 +1,138 @@
+// 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
+#include
+#include
+#include
+#include
+#include
+#include
+
+CoinsListModel::CoinsListModel(WalletQmlModel* parent)
+ : QAbstractListModel(parent), m_wallet_model(parent), m_sort_by("amount"), m_total_amount(0)
+{
+ update();
+}
+
+CoinsListModel::~CoinsListModel() = default;
+
+int CoinsListModel::rowCount(const QModelIndex& parent) const
+{
+ return m_coins.size();
+}
+
+QVariant CoinsListModel::data(const QModelIndex& index, int role) const
+{
+ if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(m_coins.size()))
+ return QVariant();
+
+ const auto& [destination, outpoint, coin] = m_coins.at(index.row());
+ switch (role) {
+ case AddressRole:
+ return QString::fromStdString(EncodeDestination(destination));
+ case AmountRole:
+ return BitcoinUnits::format(BitcoinUnits::Unit::BTC, coin.txout.nValue);
+ case LabelRole:
+ return QString::fromStdString("");
+ case LockedRole:
+ return m_wallet_model->isLockedCoin(outpoint);
+ case SelectedRole:
+ return m_wallet_model->isSelectedCoin(outpoint);
+ default:
+ return QVariant();
+ }
+}
+
+QHash CoinsListModel::roleNames() const
+{
+ QHash roles;
+ roles[AmountRole] = "amount";
+ roles[AddressRole] = "address";
+ roles[DateTimeRole] = "date";
+ roles[LabelRole] = "label";
+ roles[LockedRole] = "locked";
+ roles[SelectedRole] = "selected";
+ return roles;
+}
+
+void CoinsListModel::update()
+{
+ if (m_wallet_model == nullptr) {
+ return;
+ }
+ auto coins_map = m_wallet_model->listCoins();
+ beginResetModel();
+ m_coins.clear();
+ for (const auto& [destination, vec] : coins_map) {
+ for (const auto& [outpoint, tx_out] : vec) {
+ auto tuple = std::make_tuple(destination, outpoint, tx_out);
+ m_coins.push_back(tuple);
+ }
+ }
+ endResetModel();
+ Q_EMIT coinCountChanged();
+}
+
+void CoinsListModel::setSortBy(const QString& roleName)
+{
+ if (m_sort_by != roleName) {
+ m_sort_by = roleName;
+ // sort(RoleNameToIndex(roleName));
+ Q_EMIT sortByChanged(roleName);
+ }
+}
+
+void CoinsListModel::toggleCoinSelection(const int index)
+{
+ if (index < 0 || index >= static_cast(m_coins.size())) {
+ return;
+ }
+ const auto& [destination, outpoint, coin] = m_coins.at(index);
+
+ if (m_wallet_model->isSelectedCoin(outpoint)) {
+ m_wallet_model->unselectCoin(outpoint);
+ m_total_amount -= coin.txout.nValue;
+ } else {
+ m_wallet_model->selectCoin(outpoint);
+ m_total_amount += coin.txout.nValue;
+ }
+ Q_EMIT selectedCoinsCountChanged();
+}
+
+unsigned int CoinsListModel::lockedCoinsCount() const
+{
+ std::vector lockedCoins;
+ m_wallet_model->listLockedCoins(lockedCoins);
+ return lockedCoins.size();
+}
+
+unsigned int CoinsListModel::selectedCoinsCount() const
+{
+ return m_wallet_model->listSelectedCoins().size();
+}
+
+QString CoinsListModel::totalSelected() const
+{
+ return BitcoinUnits::format(BitcoinUnits::Unit::BTC, m_total_amount);
+}
+
+QString CoinsListModel::changeAmount() const
+{
+ CAmount change = m_total_amount - m_wallet_model->sendRecipient()->cAmount();
+ change = std::abs(change);
+ return BitcoinUnits::format(BitcoinUnits::Unit::BTC, change);
+}
+
+bool CoinsListModel::overRequiredAmount() const
+{
+ return m_total_amount > m_wallet_model->sendRecipient()->cAmount();
+}
+
+int CoinsListModel::coinCount() const
+{
+ return m_coins.size();
+}
diff --git a/src/qml/models/coinslistmodel.h b/src/qml/models/coinslistmodel.h
new file mode 100644
index 0000000000..ecdc35f3a1
--- /dev/null
+++ b/src/qml/models/coinslistmodel.h
@@ -0,0 +1,70 @@
+// 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_COINSLISTMODEL_H
+#define BITCOIN_QML_MODELS_COINSLISTMODEL_H
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+class WalletQmlModel;
+
+class CoinsListModel : public QAbstractListModel
+{
+ Q_OBJECT
+ Q_PROPERTY(int lockedCoinsCount READ lockedCoinsCount NOTIFY lockedCoinsCountChanged)
+ Q_PROPERTY(int selectedCoinsCount READ selectedCoinsCount NOTIFY selectedCoinsCountChanged)
+ Q_PROPERTY(int coinCount READ coinCount NOTIFY coinCountChanged)
+ Q_PROPERTY(QString totalSelected READ totalSelected NOTIFY selectedCoinsCountChanged)
+ Q_PROPERTY(QString changeAmount READ changeAmount NOTIFY selectedCoinsCountChanged)
+ Q_PROPERTY(bool overRequiredAmount READ overRequiredAmount NOTIFY selectedCoinsCountChanged)
+
+public:
+ explicit CoinsListModel(WalletQmlModel* parent = nullptr);
+ ~CoinsListModel();
+
+ enum CoinsRoles {
+ AddressRole = Qt::UserRole + 1,
+ AmountRole,
+ DateTimeRole,
+ LabelRole,
+ LockedRole,
+ SelectedRole
+ };
+
+ int rowCount(const QModelIndex& parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
+ QHash roleNames() const override;
+
+public Q_SLOTS:
+ void update();
+ void setSortBy(const QString& roleName);
+ void toggleCoinSelection(const int index);
+ unsigned int lockedCoinsCount() const;
+ unsigned int selectedCoinsCount() const;
+ QString totalSelected() const;
+ QString changeAmount() const;
+ bool overRequiredAmount() const;
+ int coinCount() const;
+
+Q_SIGNALS:
+ void sortByChanged(const QString& roleName);
+ void lockedCoinsCountChanged();
+ void selectedCoinsCountChanged();
+ void coinCountChanged();
+
+private:
+ WalletQmlModel* m_wallet_model;
+ std::unique_ptr m_handler_transaction_changed;
+ std::vector> m_coins;
+ QString m_sort_by;
+ CAmount m_total_amount;
+};
+
+#endif // BITCOIN_QML_MODELS_COINSLISTMODEL_H
\ No newline at end of file
diff --git a/src/qml/models/sendrecipient.cpp b/src/qml/models/sendrecipient.cpp
index f40ec75456..138bea6559 100644
--- a/src/qml/models/sendrecipient.cpp
+++ b/src/qml/models/sendrecipient.cpp
@@ -70,6 +70,9 @@ 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();
}
diff --git a/src/qml/models/walletqmlmodel.cpp b/src/qml/models/walletqmlmodel.cpp
index 626f029bb7..55df50c8da 100644
--- a/src/qml/models/walletqmlmodel.cpp
+++ b/src/qml/models/walletqmlmodel.cpp
@@ -4,14 +4,13 @@
#include
-#include
-
-#include
-#include
-
#include
+#include
#include
#include
+#include
+#include
+#include
#include
#include
#include
@@ -23,6 +22,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);
}
@@ -30,9 +30,20 @@ WalletQmlModel::WalletQmlModel(QObject* parent)
: QObject(parent)
{
m_activity_list_model = new ActivityListModel(this);
+ m_coins_list_model = new CoinsListModel(this);
m_current_recipient = new SendRecipient(this);
}
+WalletQmlModel::~WalletQmlModel()
+{
+ delete m_activity_list_model;
+ delete m_coins_list_model;
+ delete m_current_recipient;
+ if (m_current_transaction) {
+ delete m_current_transaction;
+ }
+}
+
QString WalletQmlModel::balance() const
{
if (!m_wallet) {
@@ -76,11 +87,6 @@ bool WalletQmlModel::tryGetTxStatus(const uint256& txid,
return m_wallet->tryGetTxStatus(txid, tx_status, num_blocks, block_time);
}
-WalletQmlModel::~WalletQmlModel()
-{
- delete m_activity_list_model;
-}
-
std::unique_ptr WalletQmlModel::handleTransactionChanged(TransactionChangedFn fn)
{
if (!m_wallet) {
@@ -97,8 +103,7 @@ bool WalletQmlModel::prepareTransaction()
CScript scriptPubKey = GetScriptForDestination(DecodeDestination(m_current_recipient->address().toStdString()));
wallet::CRecipient recipient = {scriptPubKey, m_current_recipient->cAmount(), m_current_recipient->subtractFeeFromAmount()};
- wallet::CCoinControl coinControl;
- coinControl.m_feerate = CFeeRate(1000);
+ m_coin_control.m_feerate = CFeeRate(1000);
CAmount balance = m_wallet->getBalance();
if (balance < recipient.nAmount) {
@@ -108,7 +113,7 @@ bool WalletQmlModel::prepareTransaction()
std::vector vecSend{recipient};
int nChangePosRet = -1;
CAmount nFeeRequired = 0;
- const auto& res = m_wallet->createTransaction(vecSend, coinControl, true, nChangePosRet, nFeeRequired);
+ const auto& res = m_wallet->createTransaction(vecSend, m_coin_control, true, nChangePosRet, nFeeRequired);
if (res) {
if (m_current_transaction) {
delete m_current_transaction;
@@ -139,3 +144,63 @@ void WalletQmlModel::sendTransaction()
interfaces::WalletOrderForm order_form;
m_wallet->commitTransaction(newTx, value_map, order_form);
}
+
+interfaces::Wallet::CoinsList WalletQmlModel::listCoins() const
+{
+ if (!m_wallet) {
+ return {};
+ }
+ return m_wallet->listCoins();
+}
+
+bool WalletQmlModel::lockCoin(const COutPoint& output)
+{
+ if (!m_wallet) {
+ return false;
+ }
+ return m_wallet->lockCoin(output, true);
+}
+
+bool WalletQmlModel::unlockCoin(const COutPoint& output)
+{
+ if (!m_wallet) {
+ return false;
+ }
+ return m_wallet->unlockCoin(output);
+}
+
+bool WalletQmlModel::isLockedCoin(const COutPoint& output)
+{
+ if (!m_wallet) {
+ return false;
+ }
+ return m_wallet->isLockedCoin(output);
+}
+
+void WalletQmlModel::listLockedCoins(std::vector& outputs)
+{
+ if (!m_wallet) {
+ return;
+ }
+ m_wallet->listLockedCoins(outputs);
+}
+
+void WalletQmlModel::selectCoin(const COutPoint& output)
+{
+ m_coin_control.Select(output);
+}
+
+void WalletQmlModel::unselectCoin(const COutPoint& output)
+{
+ m_coin_control.UnSelect(output);
+}
+
+bool WalletQmlModel::isSelectedCoin(const COutPoint& output)
+{
+ return m_coin_control.IsSelected(output);
+}
+
+std::vector WalletQmlModel::listSelectedCoins() const
+{
+ return m_coin_control.ListSelected();
+}
diff --git a/src/qml/models/walletqmlmodel.h b/src/qml/models/walletqmlmodel.h
index 252a233482..d97cd0851f 100644
--- a/src/qml/models/walletqmlmodel.h
+++ b/src/qml/models/walletqmlmodel.h
@@ -5,15 +5,16 @@
#ifndef BITCOIN_QML_MODELS_WALLETQMLMODEL_H
#define BITCOIN_QML_MODELS_WALLETQMLMODEL_H
-#include
#include
-
+#include
#include
-
+#include
#include
#include
+#include
#include
+#include
#include
class ActivityListModel;
@@ -24,6 +25,7 @@ class WalletQmlModel : public QObject
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
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(WalletQmlModelTransaction* currentTransaction READ currentTransaction NOTIFY currentTransactionChanged)
@@ -35,6 +37,7 @@ class WalletQmlModel : public QObject
QString name() const;
QString balance() const;
ActivityListModel* activityListModel() const { return m_activity_list_model; }
+ CoinsListModel* coinsListModel() const { return m_coins_list_model; }
std::set getWalletTxs() const;
interfaces::WalletTx getWalletTx(const uint256& hash) const;
@@ -51,6 +54,16 @@ class WalletQmlModel : public QObject
using TransactionChangedFn = std::function;
virtual std::unique_ptr handleTransactionChanged(TransactionChangedFn fn);
+ interfaces::Wallet::CoinsList listCoins() const;
+ bool lockCoin(const COutPoint& output);
+ bool unlockCoin(const COutPoint& output);
+ bool isLockedCoin(const COutPoint& output);
+ void listLockedCoins(std::vector& outputs);
+ void selectCoin(const COutPoint& output);
+ void unselectCoin(const COutPoint& output);
+ bool isSelectedCoin(const COutPoint& output);
+ std::vector listSelectedCoins() const;
+
Q_SIGNALS:
void nameChanged();
void balanceChanged();
@@ -59,8 +72,10 @@ class WalletQmlModel : public QObject
private:
std::unique_ptr m_wallet;
ActivityListModel* m_activity_list_model{nullptr};
+ CoinsListModel* m_coins_list_model{nullptr};
SendRecipient* m_current_recipient{nullptr};
WalletQmlModelTransaction* m_current_transaction{nullptr};
+ wallet::CCoinControl m_coin_control;
};
#endif // BITCOIN_QML_MODELS_WALLETQMLMODEL_H
diff --git a/src/qml/pages/wallet/CoinSelection.qml b/src/qml/pages/wallet/CoinSelection.qml
new file mode 100644
index 0000000000..263a30781a
--- /dev/null
+++ b/src/qml/pages/wallet/CoinSelection.qml
@@ -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.
+
+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
+
+ property WalletQmlModel wallet: walletController.selectedWallet
+
+ signal done()
+
+ header: NavigationBar2 {
+ centerItem: Header {
+ headerBold: true
+ headerSize: 18
+ header: qsTr("Coin Selection")
+ }
+ rightItem: NavButton {
+ text: qsTr("Done")
+ onClicked: root.done()
+ }
+ }
+
+ background: Rectangle {
+ color: Theme.color.neutral0
+ }
+
+ ColumnLayout {
+ id: header
+ anchors.top: parent.top
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: Math.min(parent.width - 40, 450)
+
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 15
+ CoreText {
+ Layout.alignment: Qt.AlignLeft
+ Layout.fillWidth: true
+ Layout.preferredWidth: 0
+ font.pixelSize: 18
+ color: Theme.color.neutral9
+ elide: Text.ElideMiddle
+ wrapMode: Text.NoWrap
+ horizontalAlignment: Text.AlignLeft
+ text: qsTr("Total selected")
+ }
+ CoreText {
+ Layout.alignment: Qt.AlignRight
+ color: Theme.color.neutral9
+ font.pixelSize: 18
+ text: root.wallet.coinsListModel.totalSelected
+ }
+ }
+ RowLayout {
+ Layout.bottomMargin: 30
+ CoreText {
+ Layout.alignment: Qt.AlignLeft
+ Layout.fillWidth: true
+ Layout.preferredWidth: 0
+ font.pixelSize: 15
+ color: Theme.color.neutral7
+ elide: Text.ElideMiddle
+ wrapMode: Text.NoWrap
+ horizontalAlignment: Text.AlignLeft
+ text: if (root.wallet.coinsListModel.overRequiredAmount) {
+ qsTr("Over required amount")
+ } else {
+ qsTr("Remaining to select")
+ }
+ }
+ CoreText {
+ Layout.alignment: Qt.AlignRight
+ font.pixelSize: 15
+ color: Theme.color.neutral7
+ text: root.wallet.coinsListModel.changeAmount
+ }
+ }
+ }
+
+ ScrollView {
+ id: scrollView
+ width: Math.min(parent.width - 40, 460)
+ height: parent.height - header.height - 20
+ anchors.top: header.bottom
+ anchors.left: header.left
+ clip: true
+
+ ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
+
+ ListView {
+ id: listView
+ width: parent.width
+ model: root.wallet.coinsListModel
+ spacing: 15
+
+ delegate: ItemDelegate {
+ id: delegate
+ required property string address;
+ required property string amount;
+ required property string label;
+ required property bool locked;
+ required property bool selected;
+
+ required property int index;
+
+ readonly property color stateColor: {
+ if (delegate.down) {
+ return Theme.color.orange
+ } else if (delegate.hovered) {
+ return Theme.color.orangeLight1
+ }
+ return Theme.color.neutral9
+ }
+
+ leftPadding: 0
+ rightPadding: 10
+ topPadding: 0
+ bottomPadding: 14
+ width: listView.width
+
+ background: Item {
+ Separator {
+ anchors.bottom: parent.bottom
+ width: parent.width - 10
+ }
+ }
+
+ contentItem: RowLayout {
+ width: parent.width
+ CoreCheckBox {
+ id: checkBox
+ Layout.minimumWidth: 20
+ enabled: !locked
+ checked: selected
+ visible: !locked
+ MouseArea {
+ anchors.fill: parent
+ enabled: false
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ }
+
+ onToggled: listView.model.toggleCoinSelection(index)
+ }
+ Icon {
+ source: "qrc:/icons/lock"
+ color: Theme.color.neutral9
+ visible: locked
+ size: 20
+ }
+ CoreText {
+ text: amount
+ font.pixelSize: 18
+ }
+ CoreText {
+ Layout.fillWidth: true
+ text: label != "" ? label : address
+ font.pixelSize: 18
+ elide: Text.ElideMiddle
+ wrapMode: Text.NoWrap
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/qml/pages/wallet/DesktopWallets.qml b/src/qml/pages/wallet/DesktopWallets.qml
index d814c89fab..c63dd9f7bb 100644
--- a/src/qml/pages/wallet/DesktopWallets.qml
+++ b/src/qml/pages/wallet/DesktopWallets.qml
@@ -129,6 +129,7 @@ Page {
width: parent.width
height: parent.height
currentIndex: navigationTabs.checkedButton.index
+ clip: true
Activity {
id: activityTab
}
diff --git a/src/qml/pages/wallet/Send.qml b/src/qml/pages/wallet/Send.qml
index a8b234ece2..b646a60a54 100644
--- a/src/qml/pages/wallet/Send.qml
+++ b/src/qml/pages/wallet/Send.qml
@@ -5,176 +5,239 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
+import Qt.labs.settings 1.0
import org.bitcoincore.qt 1.0
import "../../controls"
import "../../components"
-Page {
+PageStack {
id: root
- background: null
+ vertical: true
property WalletQmlModel wallet: walletController.selectedWallet
property SendRecipient recipient: wallet.sendRecipient
signal transactionPrepared()
- ScrollView {
- clip: true
- width: parent.width
- height: parent.height
- contentWidth: width
-
- ColumnLayout {
- id: columnLayout
- width: 450
- anchors.horizontalCenter: parent.horizontalCenter
-
- spacing: 10
-
- CoreText {
- id: title
- Layout.topMargin: 30
- Layout.bottomMargin: 20
- text: qsTr("Send bitcoin")
- font.pixelSize: 21
- bold: true
- }
+ Connections {
+ target: walletController
+ function onSelectedWalletChanged() {
+ root.pop()
+ }
+ }
- LabeledTextInput {
- id: address
- Layout.fillWidth: true
- labelText: qsTr("Send to")
- placeholderText: qsTr("Enter address...")
- text: root.recipient.address
- onTextEdited: root.recipient.address = address.text
- }
+ initialItem: Page {
+ background: null
- Separator {
- Layout.fillWidth: true
- }
+ Settings {
+ id: settings
+ property alias coinControlEnabled: sendOptionsPopup.coinControlEnabled
+ }
+
+ ScrollView {
+ clip: true
+ width: parent.width
+ height: parent.height
+ contentWidth: width
+
+ ColumnLayout {
+ id: columnLayout
+ width: 450
+ anchors.horizontalCenter: parent.horizontalCenter
- Item {
- BitcoinAmount {
- id: bitcoinAmount
+ spacing: 10
+
+ Item {
+ id: titleRow
+ Layout.fillWidth: true
+ Layout.topMargin: 30
+ Layout.bottomMargin: 20
+ CoreText {
+ id: title
+ anchors.left: parent.left
+ anchors.verticalCenter: parent.verticalCenter
+ text: qsTr("Send bitcoin")
+ font.pixelSize: 21
+ bold: true
+ }
+ EllipsisMenuButton {
+ id: menuButton
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ checked: sendOptionsPopup.opened
+ onClicked: {
+ sendOptionsPopup.open()
+ }
+ }
+
+ SendOptionsPopup {
+ id: sendOptionsPopup
+ x: menuButton.x - width + menuButton.width
+ y: menuButton.y + menuButton.height
+ width: 300
+ height: 50
+ }
}
- height: amountInput.height
- Layout.fillWidth: true
- CoreText {
- id: amountLabel
- width: 110
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
- horizontalAlignment: Text.AlignLeft
- color: Theme.color.neutral9
- text: "Amount"
- font.pixelSize: 18
+ LabeledTextInput {
+ id: address
+ Layout.fillWidth: true
+ labelText: qsTr("Send to")
+ placeholderText: qsTr("Enter address...")
+ text: root.recipient.address
+ onTextEdited: root.recipient.address = address.text
}
- TextField {
- id: amountInput
- anchors.left: amountLabel.right
- anchors.verticalCenter: parent.verticalCenter
- leftPadding: 0
- font.family: "Inter"
- font.styleName: "Regular"
- font.pixelSize: 18
- color: Theme.color.neutral9
- placeholderTextColor: Theme.color.neutral7
- background: Item {}
- placeholderText: "0.00000000"
- selectByMouse: true
- onTextEdited: {
- amountInput.text = bitcoinAmount.amount = bitcoinAmount.sanitize(amountInput.text)
- root.recipient.amount = bitcoinAmount.satoshiAmount
- }
+ Separator {
+ Layout.fillWidth: true
}
+
Item {
- width: unitLabel.width + flipIcon.width
- height: Math.max(unitLabel.height, flipIcon.height)
- anchors.right: parent.right
- 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
- }
- }
+ BitcoinAmount {
+ id: bitcoinAmount
}
+
+ height: amountInput.height
+ Layout.fillWidth: true
CoreText {
- id: unitLabel
- anchors.right: flipIcon.left
+ id: amountLabel
+ width: 110
+ anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
- text: bitcoinAmount.unitLabel
+ horizontalAlignment: Text.AlignLeft
+ color: Theme.color.neutral9
+ text: qsTr("Amount")
font.pixelSize: 18
- color: Theme.color.neutral7
}
- Icon {
- id: flipIcon
+
+ TextField {
+ id: amountInput
+ anchors.left: amountLabel.right
+ anchors.verticalCenter: parent.verticalCenter
+ leftPadding: 0
+ font.family: "Inter"
+ font.styleName: "Regular"
+ font.pixelSize: 18
+ color: Theme.color.neutral9
+ placeholderTextColor: Theme.color.neutral7
+ background: Item {}
+ placeholderText: "0.00000000"
+ selectByMouse: true
+ onTextEdited: {
+ amountInput.text = bitcoinAmount.amount = bitcoinAmount.sanitize(amountInput.text)
+ root.recipient.amount = bitcoinAmount.satoshiAmount
+ }
+ }
+ Item {
+ width: unitLabel.width + flipIcon.width
+ height: Math.max(unitLabel.height, flipIcon.height)
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
- source: "image://images/flip-vertical"
- color: Theme.color.neutral8
- size: 30
+ 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
+ }
+ }
+ }
+ CoreText {
+ id: unitLabel
+ anchors.right: flipIcon.left
+ anchors.verticalCenter: parent.verticalCenter
+ text: bitcoinAmount.unitLabel
+ font.pixelSize: 18
+ color: Theme.color.neutral7
+ }
+ Icon {
+ id: flipIcon
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ source: "image://images/flip-vertical"
+ color: Theme.color.neutral8
+ size: 30
+ }
}
}
- }
- Separator {
- Layout.fillWidth: true
- }
+ Separator {
+ Layout.fillWidth: true
+ }
- LabeledTextInput {
- id: label
- Layout.fillWidth: true
- labelText: qsTr("Note to self")
- placeholderText: qsTr("Enter ...")
- onTextEdited: root.recipient.label = label.text
- }
+ LabeledTextInput {
+ id: label
+ Layout.fillWidth: true
+ labelText: qsTr("Note to self")
+ placeholderText: qsTr("Enter ...")
+ onTextEdited: root.recipient.label = label.text
+ }
- Separator {
- Layout.fillWidth: true
- }
+ Separator {
+ Layout.fillWidth: true
+ }
+
+ LabeledCoinControlButton {
+ visible: settings.coinControlEnabled
+ Layout.fillWidth: true
+ coinsSelected: wallet.coinsListModel.selectedCoinsCount
+ coinCount: wallet.coinsListModel.coinCount
+ onOpenCoinControl: {
+ root.wallet.coinsListModel.update()
+ root.push(coinSelectionPage)
+ }
+ }
- Item {
- height: feeLabel.height + feeValue.height
- Layout.fillWidth: true
- CoreText {
- id: feeLabel
- anchors.left: parent.left
- anchors.top: parent.top
- color: Theme.color.neutral9
- text: "Fee"
- font.pixelSize: 15
+ Separator {
+ visible: settings.coinControlEnabled
+ Layout.fillWidth: true
}
- CoreText {
- id: feeValue
- anchors.right: parent.right
- anchors.top: parent.top
- color: Theme.color.neutral9
- text: qsTr("Default (~2,000 sats)")
- font.pixelSize: 15
+ Item {
+ height: feeLabel.height + feeValue.height
+ Layout.fillWidth: true
+ CoreText {
+ id: feeLabel
+ anchors.left: parent.left
+ anchors.top: parent.top
+ color: Theme.color.neutral9
+ text: "Fee"
+ font.pixelSize: 15
+ }
+
+ CoreText {
+ id: feeValue
+ anchors.right: parent.right
+ anchors.top: parent.top
+ color: Theme.color.neutral9
+ text: qsTr("Default (~2,000 sats)")
+ font.pixelSize: 15
+ }
}
- }
- ContinueButton {
- id: continueButton
- Layout.fillWidth: true
- Layout.topMargin: 30
- text: qsTr("Review")
- onClicked: {
- if (root.wallet.prepareTransaction()) {
- root.transactionPrepared()
+ ContinueButton {
+ id: continueButton
+ Layout.fillWidth: true
+ Layout.topMargin: 30
+ text: qsTr("Review")
+ onClicked: {
+ if (root.wallet.prepareTransaction()) {
+ root.transactionPrepared()
+ }
}
}
}
}
}
+
+ Component {
+ id: coinSelectionPage
+ CoinSelection {
+ onDone: root.pop()
+ }
+ }
}
diff --git a/src/qml/res/icons/ellipsis.png b/src/qml/res/icons/ellipsis.png
new file mode 100644
index 0000000000..36762f8ae6
Binary files /dev/null and b/src/qml/res/icons/ellipsis.png differ
diff --git a/src/qml/res/icons/lock.png b/src/qml/res/icons/lock.png
new file mode 100644
index 0000000000..ebe454a943
Binary files /dev/null and b/src/qml/res/icons/lock.png differ
diff --git a/src/qml/res/icons/triangle-down.png b/src/qml/res/icons/triangle-down.png
index 60a708fb25..69f708082d 100644
Binary files a/src/qml/res/icons/triangle-down.png and b/src/qml/res/icons/triangle-down.png differ
diff --git a/src/qml/res/icons/triangle-up.png b/src/qml/res/icons/triangle-up.png
index 9b3edc0255..8e8e5dca7b 100644
Binary files a/src/qml/res/icons/triangle-up.png and b/src/qml/res/icons/triangle-up.png differ
diff --git a/src/qml/res/src/ellipsis.svg b/src/qml/res/src/ellipsis.svg
new file mode 100644
index 0000000000..0f9f2607a9
--- /dev/null
+++ b/src/qml/res/src/ellipsis.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/qml/res/src/lock.svg b/src/qml/res/src/lock.svg
new file mode 100644
index 0000000000..ba0908b898
--- /dev/null
+++ b/src/qml/res/src/lock.svg
@@ -0,0 +1,4 @@
+
diff --git a/test/lint/lint-circular-dependencies.py b/test/lint/lint-circular-dependencies.py
index 6dd55a8ee5..fa98b6fd69 100755
--- a/test/lint/lint-circular-dependencies.py
+++ b/test/lint/lint-circular-dependencies.py
@@ -16,6 +16,7 @@
"node/blockstorage -> validation -> node/blockstorage",
"node/utxo_snapshot -> validation -> node/utxo_snapshot",
"qml/models/activitylistmodel -> qml/models/walletqmlmodel -> qml/models/activitylistmodel",
+ "qml/models/coinslistmodel -> qml/models/walletqmlmodel -> qml/models/coinslistmodel",
"qt/addresstablemodel -> qt/walletmodel -> qt/addresstablemodel",
"qt/recentrequeststablemodel -> qt/walletmodel -> qt/recentrequeststablemodel",
"qt/sendcoinsdialog -> qt/walletmodel -> qt/sendcoinsdialog",