Skip to content

Commit 989bbe0

Browse files
committed
Merge #461: Introduce standard fee selection to Send
16187ac qml: Cleanup QML in FeeSelection (johnny9) 854e265 qml: Remove unnecessary Layout property in Send (johnny9) cb1f3d9 qml: Cleanup QML properties in FeeSelection (johnny9) f4f9b79 qml: Introduce FeeSelection to Send (johnny9) 0ff0688 qml: Add targetBlocks property to WalletQmlModel (johnny9) b4ade11 qml: Add caret down icon (johnny9) Pull request description: This replaces the placeholder items in the Send form with a dropdown menu. The menu currently has 3 standard options (High, Default, and Low). These choices map to specific block targets for fee calculation. ![Screenshot from 2025-05-31 15-15-53](https://github.com/user-attachments/assets/9540ea46-272f-4450-84e7-a5b4cd13d325) ![Screenshot from 2025-05-31 14-52-16](https://github.com/user-attachments/assets/c182bbfe-703d-4964-98c0-34e10c5e6ee2) ACKs for top commit: MarnixCroes: tACK 16187ac Tree-SHA512: a5b9f99091e8bb74c347e049a97923a306907e92afdaa09332512584685fdbebb26095574c686f69a750010fb983f0f34b1ff02235777f56e55fbe85736f009f
2 parents 25a18f0 + 16187ac commit 989bbe0

File tree

8 files changed

+205
-15
lines changed

8 files changed

+205
-15
lines changed

src/Makefile.qt.include

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ QML_RES_ICONS = \
360360
qml/res/icons/blockclock-size-showcase.png \
361361
qml/res/icons/blocktime-dark.png \
362362
qml/res/icons/blocktime-light.png \
363+
qml/res/icons/caret-down-medium-filled.png \
363364
qml/res/icons/caret-left.png \
364365
qml/res/icons/caret-right.png \
365366
qml/res/icons/check.png \
@@ -402,6 +403,7 @@ QML_RES_QML = \
402403
qml/components/ConnectionSettings.qml \
403404
qml/components/DeveloperOptions.qml \
404405
qml/components/ExternalPopup.qml \
406+
qml/components/FeeSelection.qml \
405407
qml/components/NetworkTrafficGraph.qml \
406408
qml/components/NetworkIndicator.qml \
407409
qml/components/OptionPopup.qml \

src/qml/bitcoin_qml.qrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<file>components/ConnectionSettings.qml</file>
99
<file>components/DeveloperOptions.qml</file>
1010
<file>components/ExternalPopup.qml</file>
11+
<file>components/FeeSelection.qml</file>
1112
<file>components/NetworkTrafficGraph.qml</file>
1213
<file>components/NetworkIndicator.qml</file>
1314
<file>components/OptionPopup.qml</file>
@@ -107,6 +108,7 @@
107108
<file alias="blocktime-dark">res/icons/blocktime-dark.png</file>
108109
<file alias="blocktime-light">res/icons/blocktime-light.png</file>
109110
<file alias="bitcoin">../qt/res/icons/bitcoin.png</file>
111+
<file alias="caret-down-medium-filled">res/icons/caret-down-medium-filled.png</file>
110112
<file alias="caret-left">res/icons/caret-left.png</file>
111113
<file alias="caret-right">res/icons/caret-right.png</file>
112114
<file alias="check">res/icons/check.png</file>

src/qml/components/FeeSelection.qml

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
// Copyright (c) 2025 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
import QtQuick 2.15
6+
import QtQuick.Controls 2.15
7+
import QtQuick.Layouts 1.15
8+
9+
import "../controls"
10+
import "../components"
11+
12+
RowLayout {
13+
id: root
14+
15+
property int selectedIndex: 1
16+
property string selectedLabel: feeModel.get(root.selectedIndex).feeLabel
17+
18+
signal feeChanged(int target)
19+
20+
height: 40
21+
22+
CoreText {
23+
Layout.fillWidth: true
24+
horizontalAlignment: Text.AlignLeft
25+
font.pixelSize: 15
26+
text: qsTr("Fee")
27+
}
28+
29+
Button {
30+
id: dropDownButton
31+
text: root.selectedLabel
32+
font.pixelSize: 15
33+
34+
hoverEnabled: true
35+
36+
HoverHandler {
37+
cursorShape: Qt.PointingHandCursor
38+
}
39+
40+
onPressed: feePopup.open()
41+
42+
contentItem: RowLayout {
43+
spacing: 5
44+
45+
CoreText {
46+
id: value
47+
text: root.selectedLabel
48+
font.pixelSize: 15
49+
50+
Behavior on color {
51+
ColorAnimation { duration: 150 }
52+
}
53+
}
54+
55+
Icon {
56+
id: caret
57+
source: "image://images/caret-down-medium-filled"
58+
Layout.preferredWidth: 30
59+
size: 30
60+
color: dropDownButton.enabled ? Theme.color.orange : Theme.color.neutral4
61+
62+
Behavior on color {
63+
ColorAnimation { duration: 150 }
64+
}
65+
}
66+
}
67+
68+
background: Rectangle {
69+
id: dropDownButtonBg
70+
color: Theme.color.background
71+
radius: 6
72+
Behavior on color {
73+
ColorAnimation { duration: 150 }
74+
}
75+
}
76+
77+
states: [
78+
State {
79+
name: "CHECKED"; when: dropDownButton.checked
80+
PropertyChanges { target: icon; color: activeColor }
81+
},
82+
State {
83+
name: "HOVER"; when: dropDownButton.hovered
84+
PropertyChanges { target: dropDownButtonBg; color: Theme.color.neutral2 }
85+
},
86+
State {
87+
name: "DISABLED"; when: !dropDownButton.enabled
88+
PropertyChanges { target: dropDownButtonBg; color: Theme.color.background }
89+
}
90+
]
91+
}
92+
93+
Popup {
94+
id: feePopup
95+
modal: true
96+
dim: false
97+
98+
background: Rectangle {
99+
color: Theme.color.background
100+
radius: 6
101+
border.color: Theme.color.neutral3
102+
}
103+
104+
width: 260
105+
height: Math.min(feeModel.count * 40 + 20, 300)
106+
x: feePopup.parent.width - feePopup.width
107+
y: feePopup.parent.height
108+
109+
contentItem: ListView {
110+
id: feeList
111+
model: feeModel
112+
interactive: false
113+
width: 260
114+
height: contentHeight
115+
delegate: ItemDelegate {
116+
id: delegate
117+
required property string feeLabel
118+
required property int index
119+
required property int target
120+
121+
width: ListView.view.width
122+
height: 40
123+
124+
background: Item {
125+
Rectangle {
126+
anchors.fill: parent
127+
radius: 6
128+
color: Theme.color.neutral3
129+
visible: delegate.hovered
130+
}
131+
Separator {
132+
width: parent.width
133+
anchors.top: parent.top
134+
color: Theme.color.neutral2
135+
visible: delegate.index > 0
136+
}
137+
}
138+
139+
contentItem: RowLayout {
140+
spacing: 10
141+
142+
CoreText {
143+
text: feeLabel
144+
horizontalAlignment: Text.AlignLeft
145+
Layout.fillWidth: true
146+
font.pixelSize: 15
147+
}
148+
149+
Icon {
150+
visible: delegate.index === root.selectedIndex
151+
source: "image://images/check"
152+
color: Theme.color.orange
153+
size: 20
154+
}
155+
}
156+
157+
HoverHandler {
158+
cursorShape: Qt.PointingHandCursor
159+
}
160+
161+
onClicked: {
162+
root.selectedIndex = delegate.index
163+
root.selectedLabel = feeLabel
164+
root.feeChanged(target)
165+
feePopup.close()
166+
}
167+
}
168+
}
169+
}
170+
171+
ListModel {
172+
id: feeModel
173+
ListElement { feeLabel: qsTr("High (~10 mins)"); target: 1 }
174+
ListElement { feeLabel: qsTr("Default (~60 mins)"); target: 6 }
175+
ListElement { feeLabel: qsTr("Low (~24 hrs)"); target: 144 }
176+
}
177+
}

src/qml/models/walletqmlmodel.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,3 +205,16 @@ std::vector<COutPoint> WalletQmlModel::listSelectedCoins() const
205205
{
206206
return m_coin_control.ListSelected();
207207
}
208+
209+
unsigned int WalletQmlModel::feeTargetBlocks() const
210+
{
211+
return m_coin_control.m_confirm_target.value_or(wallet::DEFAULT_TX_CONFIRM_TARGET);
212+
}
213+
214+
void WalletQmlModel::setFeeTargetBlocks(unsigned int target_blocks)
215+
{
216+
if (m_coin_control.m_confirm_target != target_blocks) {
217+
m_coin_control.m_confirm_target = target_blocks;
218+
Q_EMIT feeTargetBlocksChanged();
219+
}
220+
}

src/qml/models/walletqmlmodel.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class WalletQmlModel : public QObject
2828
Q_PROPERTY(CoinsListModel* coinsListModel READ coinsListModel CONSTANT)
2929
Q_PROPERTY(SendRecipient* sendRecipient READ sendRecipient CONSTANT)
3030
Q_PROPERTY(WalletQmlModelTransaction* currentTransaction READ currentTransaction NOTIFY currentTransactionChanged)
31+
Q_PROPERTY(unsigned int targetBlocks READ feeTargetBlocks WRITE setFeeTargetBlocks NOTIFY feeTargetBlocksChanged)
3132

3233
public:
3334
WalletQmlModel(std::unique_ptr<interfaces::Wallet> wallet, QObject* parent = nullptr);
@@ -63,11 +64,14 @@ class WalletQmlModel : public QObject
6364
void unselectCoin(const COutPoint& output);
6465
bool isSelectedCoin(const COutPoint& output);
6566
std::vector<COutPoint> listSelectedCoins() const;
67+
unsigned int feeTargetBlocks() const;
68+
void setFeeTargetBlocks(unsigned int target_blocks);
6669

6770
Q_SIGNALS:
6871
void nameChanged();
6972
void balanceChanged();
7073
void currentTransactionChanged();
74+
void feeTargetBlocksChanged();
7175

7276
private:
7377
std::unique_ptr<interfaces::Wallet> m_wallet;

src/qml/pages/wallet/Send.qml

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -199,23 +199,12 @@ PageStack {
199199
Layout.fillWidth: true
200200
}
201201

202-
Item {
203-
height: feeLabel.height + feeValue.height
202+
FeeSelection {
203+
id: feeSelection
204204
Layout.fillWidth: true
205-
CoreText {
206-
id: feeLabel
207-
anchors.left: parent.left
208-
anchors.top: parent.top
209-
text: "Fee"
210-
font.pixelSize: 15
211-
}
212205

213-
CoreText {
214-
id: feeValue
215-
anchors.right: parent.right
216-
anchors.top: parent.top
217-
text: qsTr("Default (~2,000 sats)")
218-
font.pixelSize: 15
206+
onFeeChanged: {
207+
root.wallet.targetBlocks = target
219208
}
220209
}
221210

283 Bytes
Loading
Lines changed: 3 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)