Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Makefile.qt.include
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ QML_RES_ICONS = \
qml/res/icons/blockclock-size-showcase.png \
qml/res/icons/blocktime-dark.png \
qml/res/icons/blocktime-light.png \
qml/res/icons/caret-down-medium-filled.png \
qml/res/icons/caret-left.png \
qml/res/icons/caret-right.png \
qml/res/icons/check.png \
Expand Down Expand Up @@ -402,6 +403,7 @@ QML_RES_QML = \
qml/components/ConnectionSettings.qml \
qml/components/DeveloperOptions.qml \
qml/components/ExternalPopup.qml \
qml/components/FeeSelection.qml \
qml/components/NetworkTrafficGraph.qml \
qml/components/NetworkIndicator.qml \
qml/components/OptionPopup.qml \
Expand Down
2 changes: 2 additions & 0 deletions src/qml/bitcoin_qml.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<file>components/ConnectionSettings.qml</file>
<file>components/DeveloperOptions.qml</file>
<file>components/ExternalPopup.qml</file>
<file>components/FeeSelection.qml</file>
<file>components/NetworkTrafficGraph.qml</file>
<file>components/NetworkIndicator.qml</file>
<file>components/OptionPopup.qml</file>
Expand Down Expand Up @@ -107,6 +108,7 @@
<file alias="blocktime-dark">res/icons/blocktime-dark.png</file>
<file alias="blocktime-light">res/icons/blocktime-light.png</file>
<file alias="bitcoin">../qt/res/icons/bitcoin.png</file>
<file alias="caret-down-medium-filled">res/icons/caret-down-medium-filled.png</file>
<file alias="caret-left">res/icons/caret-left.png</file>
<file alias="caret-right">res/icons/caret-right.png</file>
<file alias="check">res/icons/check.png</file>
Expand Down
177 changes: 177 additions & 0 deletions src/qml/components/FeeSelection.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// 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"
import "../components"

RowLayout {
id: root

property int selectedIndex: 1
property string selectedLabel: feeModel.get(root.selectedIndex).feeLabel

signal feeChanged(int target)

height: 40

CoreText {
Layout.fillWidth: true
horizontalAlignment: Text.AlignLeft
font.pixelSize: 15
text: qsTr("Fee")
}

Button {
id: dropDownButton
text: root.selectedLabel
font.pixelSize: 15

hoverEnabled: true

HoverHandler {
cursorShape: Qt.PointingHandCursor
}

onPressed: feePopup.open()

contentItem: RowLayout {
spacing: 5

CoreText {
id: value
text: root.selectedLabel
font.pixelSize: 15

Behavior on color {
ColorAnimation { duration: 150 }
}
}

Icon {
id: caret
source: "image://images/caret-down-medium-filled"
Layout.preferredWidth: 30
size: 30
color: dropDownButton.enabled ? Theme.color.orange : Theme.color.neutral4

Behavior on color {
ColorAnimation { duration: 150 }
}
}
}

background: Rectangle {
id: dropDownButtonBg
color: Theme.color.background
radius: 6
Behavior on color {
ColorAnimation { duration: 150 }
}
}

states: [
State {
name: "CHECKED"; when: dropDownButton.checked
PropertyChanges { target: icon; color: activeColor }
},
State {
name: "HOVER"; when: dropDownButton.hovered
PropertyChanges { target: dropDownButtonBg; color: Theme.color.neutral2 }
},
State {
name: "DISABLED"; when: !dropDownButton.enabled
PropertyChanges { target: dropDownButtonBg; color: Theme.color.background }
}
]
}

Popup {
id: feePopup
modal: true
dim: false

background: Rectangle {
color: Theme.color.background
radius: 6
border.color: Theme.color.neutral3
}

width: 260
height: Math.min(feeModel.count * 40 + 20, 300)
x: feePopup.parent.width - feePopup.width
y: feePopup.parent.height

contentItem: ListView {
id: feeList
model: feeModel
interactive: false
width: 260
height: contentHeight
delegate: ItemDelegate {
id: delegate
required property string feeLabel
required property int index
required property int target

width: ListView.view.width
height: 40

background: Item {
Rectangle {
anchors.fill: parent
radius: 6
color: Theme.color.neutral3
visible: delegate.hovered
}
Separator {
width: parent.width
anchors.top: parent.top
color: Theme.color.neutral2
visible: delegate.index > 0
}
}

contentItem: RowLayout {
spacing: 10

CoreText {
text: feeLabel
horizontalAlignment: Text.AlignLeft
Layout.fillWidth: true
font.pixelSize: 15
}

Icon {
visible: delegate.index === root.selectedIndex
source: "image://images/check"
color: Theme.color.orange
size: 20
}
}

HoverHandler {
cursorShape: Qt.PointingHandCursor
}

onClicked: {
root.selectedIndex = delegate.index
root.selectedLabel = feeLabel
root.feeChanged(target)
feePopup.close()
}
}
}
}

ListModel {
id: feeModel
ListElement { feeLabel: qsTr("High (~10 mins)"); target: 1 }
ListElement { feeLabel: qsTr("Default (~60 mins)"); target: 6 }
ListElement { feeLabel: qsTr("Low (~24 hrs)"); target: 144 }
}
}
13 changes: 13 additions & 0 deletions src/qml/models/walletqmlmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,16 @@ std::vector<COutPoint> WalletQmlModel::listSelectedCoins() const
{
return m_coin_control.ListSelected();
}

unsigned int WalletQmlModel::feeTargetBlocks() const
{
return m_coin_control.m_confirm_target.value_or(wallet::DEFAULT_TX_CONFIRM_TARGET);
}

void WalletQmlModel::setFeeTargetBlocks(unsigned int target_blocks)
{
if (m_coin_control.m_confirm_target != target_blocks) {
m_coin_control.m_confirm_target = target_blocks;
Q_EMIT feeTargetBlocksChanged();
}
}
4 changes: 4 additions & 0 deletions src/qml/models/walletqmlmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class WalletQmlModel : public QObject
Q_PROPERTY(CoinsListModel* coinsListModel READ coinsListModel CONSTANT)
Q_PROPERTY(SendRecipient* sendRecipient READ sendRecipient CONSTANT)
Q_PROPERTY(WalletQmlModelTransaction* currentTransaction READ currentTransaction NOTIFY currentTransactionChanged)
Q_PROPERTY(unsigned int targetBlocks READ feeTargetBlocks WRITE setFeeTargetBlocks NOTIFY feeTargetBlocksChanged)

public:
WalletQmlModel(std::unique_ptr<interfaces::Wallet> wallet, QObject* parent = nullptr);
Expand Down Expand Up @@ -63,11 +64,14 @@ class WalletQmlModel : public QObject
void unselectCoin(const COutPoint& output);
bool isSelectedCoin(const COutPoint& output);
std::vector<COutPoint> listSelectedCoins() const;
unsigned int feeTargetBlocks() const;
void setFeeTargetBlocks(unsigned int target_blocks);

Q_SIGNALS:
void nameChanged();
void balanceChanged();
void currentTransactionChanged();
void feeTargetBlocksChanged();

private:
std::unique_ptr<interfaces::Wallet> m_wallet;
Expand Down
19 changes: 4 additions & 15 deletions src/qml/pages/wallet/Send.qml
Original file line number Diff line number Diff line change
Expand Up @@ -199,23 +199,12 @@ PageStack {
Layout.fillWidth: true
}

Item {
height: feeLabel.height + feeValue.height
FeeSelection {
id: feeSelection
Layout.fillWidth: true
CoreText {
id: feeLabel
anchors.left: parent.left
anchors.top: parent.top
text: "Fee"
font.pixelSize: 15
}

CoreText {
id: feeValue
anchors.right: parent.right
anchors.top: parent.top
text: qsTr("Default (~2,000 sats)")
font.pixelSize: 15
onFeeChanged: {
root.wallet.targetBlocks = target
}
}

Expand Down
Binary file added src/qml/res/icons/caret-down-medium-filled.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/qml/res/src/caret-down-medium-filled.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading