Skip to content

Commit 100eedf

Browse files
committed
Merge #435: Initial Template for Request Payment page
13535ca qml: Introduce RequestPayment page (johnny9) 9f4411c qml: Introduce BitcoinAmount (johnny9) b2d8c7b qml: Add pending icon (johnny9) f7cc57d qml: Add flip-vertical icon (johnny9) e8dd09d qml: Add copy icon (johnny9) a395838 qml: Introduce LabeledTextInput control (johnny9) Pull request description: These commits introduce the Request Payment flow. This view is available under the "Receive" tab on the Desktop Wallets main navigation view. The Request Payment form itself currently only includes fields currently supported by the Qt wallet (label, message, and amount). Follow up PR will add support for local notes. The Amount field in the Request form takes only valid Satoshi/Bitcoin amounts and supports flipping the units with the associated button toggle. When the Continue button is pressed the Request Confirmation page gets pushed to the stack will eventually reflect the fields inputted in the previous form backed by the output of the WalletModel result. In its current implemention it just has statically defined properties to check the visual layout. The page also contains an address field with the copy icon that can be used to move the address into your clipboard. Ubuntu screenshots ![](https://github.com/user-attachments/assets/d3cca6cd-5c6f-4a2f-bf4f-e37960bb3746) ![](https://github.com/user-attachments/assets/61fc296d-dfc8-4a22-a56d-29603042f2c4) ACKs for top commit: hebasto: ACK 13535ca, I have reviewed the code and it looks OK. Tree-SHA512: bd0ad001c858d59a258c22fa0e963021ddd3bfeab93d1e47ce460b41ceb96cda834eb3c5ebbef99f3c583844c166462fcc3673bf96cb7a0464f47da55ad0f50d
2 parents a38cf5b + 13535ca commit 100eedf

15 files changed

+522
-2
lines changed

Diff for: src/Makefile.qt.include

+8
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ QT_MOC_CPP = \
4646
qml/models/moc_walletlistmodel.cpp \
4747
qml/models/moc_walletqmlmodel.cpp \
4848
qml/moc_appmode.cpp \
49+
qml/moc_bitcoinamount.cpp \
4950
qml/moc_walletqmlcontroller.cpp \
5051
qt/moc_addressbookpage.cpp \
5152
qt/moc_addresstablemodel.cpp \
@@ -130,6 +131,7 @@ BITCOIN_QT_H = \
130131
qml/models/walletqmlmodel.h \
131132
qml/appmode.h \
132133
qml/bitcoin.h \
134+
qml/bitcoinamount.h \
133135
qml/guiconstants.h \
134136
qml/imageprovider.h \
135137
qml/util.h \
@@ -310,6 +312,7 @@ BITCOIN_QT_WALLET_CPP = \
310312

311313
BITCOIN_QML_BASE_CPP = \
312314
qml/bitcoin.cpp \
315+
qml/bitcoinamount.cpp \
313316
qml/components/blockclockdial.cpp \
314317
qml/controls/linegraph.cpp \
315318
qml/models/chainmodel.cpp \
@@ -340,9 +343,11 @@ QML_RES_ICONS = \
340343
qml/res/icons/caret-left.png \
341344
qml/res/icons/caret-right.png \
342345
qml/res/icons/check.png \
346+
qml/res/icons/copy.png \
343347
qml/res/icons/cross.png \
344348
qml/res/icons/error.png \
345349
qml/res/icons/export.png \
350+
qml/res/icons/flip-vertical.png \
346351
qml/res/icons/gear.png \
347352
qml/res/icons/gear-outline.png \
348353
qml/res/icons/hidden.png \
@@ -351,6 +356,7 @@ QML_RES_ICONS = \
351356
qml/res/icons/network-dark.png \
352357
qml/res/icons/network-light.png \
353358
qml/res/icons/plus.png \
359+
qml/res/icons/pending.png \
354360
qml/res/icons/shutdown.png \
355361
qml/res/icons/singlesig-wallet.png \
356362
qml/res/icons/storage-dark.png \
@@ -393,6 +399,7 @@ QML_RES_QML = \
393399
qml/controls/InformationPage.qml \
394400
qml/controls/IPAddressValueInput.qml \
395401
qml/controls/KeyValueRow.qml \
402+
qml/controls/LabeledTextInput.qml \
396403
qml/controls/NavButton.qml \
397404
qml/controls/PageIndicator.qml \
398405
qml/controls/NavigationBar.qml \
@@ -440,6 +447,7 @@ QML_RES_QML = \
440447
qml/pages/wallet/CreatePassword.qml \
441448
qml/pages/wallet/CreateWalletWizard.qml \
442449
qml/pages/wallet/DesktopWallets.qml \
450+
qml/pages/wallet/RequestPayment.qml \
443451
qml/pages/wallet/WalletBadge.qml \
444452
qml/pages/wallet/WalletSelect.qml
445453

Diff for: src/qml/bitcoin.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <node/interface_ui.h>
1616
#include <noui.h>
1717
#include <qml/appmode.h>
18+
#include <qml/bitcoinamount.h>
1819
#ifdef __ANDROID__
1920
#include <qml/androidnotifier.h>
2021
#endif
@@ -330,6 +331,7 @@ int QmlGuiMain(int argc, char* argv[])
330331
qmlRegisterType<BlockClockDial>("org.bitcoincore.qt", 1, 0, "BlockClockDial");
331332
qmlRegisterType<LineGraph>("org.bitcoincore.qt", 1, 0, "LineGraph");
332333
qmlRegisterUncreatableType<PeerDetailsModel>("org.bitcoincore.qt", 1, 0, "PeerDetailsModel", "");
334+
qmlRegisterType<BitcoinAmount>("org.bitcoincore.qt", 1, 0, "BitcoinAmount");
333335

334336
#ifdef ENABLE_WALLET
335337
qmlRegisterUncreatableType<WalletQmlModel>("org.bitcoincore.qt", 1, 0, "WalletQmlModel",

Diff for: src/qml/bitcoin_qml.qrc

+5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<file>controls/ContinueButton.qml</file>
2424
<file>controls/CoreText.qml</file>
2525
<file>controls/CoreTextField.qml</file>
26+
<file>controls/LabeledTextInput.qml</file>
2627
<file>controls/ExternalLink.qml</file>
2728
<file>controls/FocusBorder.qml</file>
2829
<file>controls/Header.qml</file>
@@ -77,6 +78,7 @@
7778
<file>pages/wallet/CreatePassword.qml</file>
7879
<file>pages/wallet/CreateWalletWizard.qml</file>
7980
<file>pages/wallet/DesktopWallets.qml</file>
81+
<file>pages/wallet/RequestPayment.qml</file>
8082
<file>pages/wallet/WalletBadge.qml</file>
8183
<file>pages/wallet/WalletSelect.qml</file>
8284
</qresource>
@@ -93,9 +95,11 @@
9395
<file alias="caret-left">res/icons/caret-left.png</file>
9496
<file alias="caret-right">res/icons/caret-right.png</file>
9597
<file alias="check">res/icons/check.png</file>
98+
<file alias="copy">res/icons/copy.png</file>
9699
<file alias="cross">res/icons/cross.png</file>
97100
<file alias="error">res/icons/error.png</file>
98101
<file alias="export">res/icons/export.png</file>
102+
<file alias="flip-vertical">res/icons/flip-vertical.png</file>
99103
<file alias="gear">res/icons/gear.png</file>
100104
<file alias="gear-outline">res/icons/gear-outline.png</file>
101105
<file alias="hidden">res/icons/hidden.png</file>
@@ -104,6 +108,7 @@
104108
<file alias="network-dark">res/icons/network-dark.png</file>
105109
<file alias="network-light">res/icons/network-light.png</file>
106110
<file alias="plus">res/icons/plus.png</file>
111+
<file alias="pending">res/icons/pending.png</file>
107112
<file alias="shutdown">res/icons/shutdown.png</file>
108113
<file alias="singlesig-wallet">res/icons/singlesig-wallet.png</file>
109114
<file alias="storage-dark">res/icons/storage-dark.png</file>

Diff for: src/qml/bitcoinamount.cpp

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// Copyright (c) 2024 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+
#include <qml/bitcoinamount.h>
6+
7+
#include <QRegExp>
8+
#include <QStringList>
9+
10+
11+
BitcoinAmount::BitcoinAmount(QObject *parent) : QObject(parent)
12+
{
13+
m_unit = Unit::BTC;
14+
}
15+
16+
int BitcoinAmount::decimals(Unit unit)
17+
{
18+
switch (unit) {
19+
case Unit::BTC: return 8;
20+
case Unit::SAT: return 0;
21+
} // no default case, so the compiler can warn about missing cases
22+
assert(false);
23+
}
24+
25+
QString BitcoinAmount::sanitize(const QString &text)
26+
{
27+
QString result = text;
28+
29+
// Remove any invalid characters
30+
result.remove(QRegExp("[^0-9.]"));
31+
32+
// Ensure only one decimal point
33+
QStringList parts = result.split('.');
34+
if (parts.size() > 2) {
35+
result = parts[0] + "." + parts[1];
36+
}
37+
38+
// Limit decimal places to 8
39+
if (parts.size() == 2 && parts[1].length() > 8) {
40+
result = parts[0] + "." + parts[1].left(8);
41+
}
42+
43+
return result;
44+
}
45+
46+
BitcoinAmount::Unit BitcoinAmount::unit() const
47+
{
48+
return m_unit;
49+
}
50+
51+
void BitcoinAmount::setUnit(const Unit unit)
52+
{
53+
m_unit = unit;
54+
Q_EMIT unitChanged();
55+
}
56+
57+
QString BitcoinAmount::unitLabel() const
58+
{
59+
switch (m_unit) {
60+
case Unit::BTC: return "";
61+
case Unit::SAT: return "Sat";
62+
}
63+
assert(false);
64+
}
65+
66+
QString BitcoinAmount::amount() const
67+
{
68+
return m_amount;
69+
}
70+
71+
void BitcoinAmount::setAmount(const QString& new_amount)
72+
{
73+
m_amount = sanitize(new_amount);
74+
Q_EMIT amountChanged();
75+
}
76+
77+
long long BitcoinAmount::toSatoshis(QString& amount, const Unit unit)
78+
{
79+
80+
int num_decimals = decimals(unit);
81+
82+
QStringList parts = amount.remove(' ').split(".");
83+
84+
QString whole = parts[0];
85+
QString decimals;
86+
87+
if(parts.size() > 1)
88+
{
89+
decimals = parts[1];
90+
}
91+
QString str = whole + decimals.leftJustified(num_decimals, '0', true);
92+
93+
return str.toLongLong();
94+
}
95+
96+
QString BitcoinAmount::convert(const QString &amount, Unit unit)
97+
{
98+
if (amount == "") {
99+
return amount;
100+
}
101+
102+
QString result = amount;
103+
int decimalPosition = result.indexOf(".");
104+
105+
if (decimalPosition == -1) {
106+
decimalPosition = result.length();
107+
result.append(".");
108+
}
109+
110+
if (unit == Unit::BTC) {
111+
int numDigitsAfterDecimal = result.length() - decimalPosition - 1;
112+
if (numDigitsAfterDecimal < 8) {
113+
result.append(QString(8 - numDigitsAfterDecimal, '0'));
114+
}
115+
result.remove(decimalPosition, 1);
116+
} else if (unit == Unit::SAT) {
117+
result.remove(decimalPosition, 1);
118+
int newDecimalPosition = decimalPosition - 8;
119+
if (newDecimalPosition < 1) {
120+
result = QString("0").repeated(-newDecimalPosition) + result;
121+
newDecimalPosition = 0;
122+
}
123+
result.insert(newDecimalPosition, ".");
124+
}
125+
126+
return result;
127+
}

Diff for: src/qml/bitcoinamount.h

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright (c) 2024 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+
#ifndef BITCOIN_QML_BITCOINAMOUNT_H
6+
#define BITCOIN_QML_BITCOINAMOUNT_H
7+
8+
#include <QObject>
9+
#include <QString>
10+
#include <qobjectdefs.h>
11+
12+
class BitcoinAmount : public QObject
13+
{
14+
Q_OBJECT
15+
Q_PROPERTY(Unit unit READ unit WRITE setUnit NOTIFY unitChanged)
16+
Q_PROPERTY(QString unitLabel READ unitLabel NOTIFY unitChanged)
17+
Q_PROPERTY(QString amount READ amount WRITE setAmount NOTIFY amountChanged)
18+
19+
public:
20+
enum class Unit {
21+
BTC,
22+
SAT
23+
};
24+
Q_ENUM(Unit)
25+
26+
explicit BitcoinAmount(QObject *parent = nullptr);
27+
28+
Unit unit() const;
29+
void setUnit(Unit unit);
30+
QString unitLabel() const;
31+
QString amount() const;
32+
void setAmount(const QString& new_amount);
33+
34+
public Q_SLOTS:
35+
QString sanitize(const QString &text);
36+
QString convert(const QString &text, Unit unit);
37+
38+
Q_SIGNALS:
39+
void unitChanged();
40+
void unitLabelChanged();
41+
void amountChanged();
42+
43+
private:
44+
long long toSatoshis(QString &amount, const Unit unit);
45+
int decimals(Unit unit);
46+
47+
Unit m_unit;
48+
QString m_unitLabel;
49+
QString m_amount;
50+
};
51+
52+
#endif // BITCOIN_QML_BITCOINAMOUNT_H

Diff for: src/qml/controls/LabeledTextInput.qml

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright (c) 2024 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+
Item {
10+
property alias labelText: label.text
11+
property alias text: input.text
12+
property alias placeholderText: input.placeholderText
13+
property alias iconSource: icon.source
14+
property alias customIcon: iconContainer.data
15+
property alias enabled: input.enabled
16+
17+
signal iconClicked
18+
signal textEdited
19+
20+
id: root
21+
implicitHeight: label.height + input.height
22+
23+
CoreText {
24+
id: label
25+
anchors.left: parent.left
26+
anchors.top: parent.top
27+
color: Theme.color.neutral7
28+
font.pixelSize: 15
29+
}
30+
31+
TextField {
32+
id: input
33+
anchors.left: parent.left
34+
anchors.right: iconContainer.left
35+
anchors.bottom: parent.bottom
36+
leftPadding: 0
37+
font.family: "Inter"
38+
font.styleName: "Regular"
39+
font.pixelSize: 18
40+
color: Theme.color.neutral9
41+
placeholderTextColor: Theme.color.neutral7
42+
background: Item {}
43+
selectByMouse: true
44+
onTextEdited: root.textEdited()
45+
}
46+
47+
Item {
48+
id: iconContainer
49+
anchors.right: parent.right
50+
anchors.verticalCenter: input.verticalCenter
51+
52+
Icon {
53+
id: icon
54+
source: ""
55+
color: Theme.color.neutral8
56+
size: 30
57+
enabled: source != ""
58+
onClicked: root.iconClicked()
59+
}
60+
}
61+
}

Diff for: src/qml/imageprovider.cpp

+15
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ QPixmap ImageProvider::requestPixmap(const QString& id, QSize* size, const QSize
7777
return QIcon(":/icons/check").pixmap(requested_size);
7878
}
7979

80+
if (id == "copy") {
81+
*size = requested_size;
82+
return QIcon(":/icons/copy").pixmap(requested_size);
83+
}
84+
8085
if (id == "cross") {
8186
*size = requested_size;
8287
return QIcon(":/icons/cross").pixmap(requested_size);
@@ -92,6 +97,11 @@ QPixmap ImageProvider::requestPixmap(const QString& id, QSize* size, const QSize
9297
return QIcon(":/icons/export").pixmap(requested_size);
9398
}
9499

100+
if (id == "flip-vertical") {
101+
*size = requested_size;
102+
return QIcon(":/icons/flip-vertical").pixmap(requested_size);
103+
}
104+
95105
if (id == "gear") {
96106
*size = requested_size;
97107
return QIcon(":/icons/gear").pixmap(requested_size);
@@ -122,6 +132,11 @@ QPixmap ImageProvider::requestPixmap(const QString& id, QSize* size, const QSize
122132
return QIcon(":/icons/network-light").pixmap(requested_size);
123133
}
124134

135+
if (id == "pending") {
136+
*size = requested_size;
137+
return QIcon(":/icons/pending").pixmap(requested_size);
138+
}
139+
125140
if (id == "shutdown") {
126141
*size = requested_size;
127142
return QIcon(":/icons/shutdown").pixmap(requested_size);

0 commit comments

Comments
 (0)