Skip to content

Commit 2fc8e69

Browse files
committed
qml: Introduce the Desktop Wallet Activity Page
1 parent a8d15db commit 2fc8e69

21 files changed

+1099
-5
lines changed

src/Makefile.qt.include

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,18 @@ QT_FORMS_UI = \
3737
QT_MOC_CPP = \
3838
qml/components/moc_blockclockdial.cpp \
3939
qml/controls/moc_linegraph.cpp \
40+
qml/models/moc_activitylistmodel.cpp \
4041
qml/models/moc_chainmodel.cpp \
4142
qml/models/moc_networktraffictower.cpp \
4243
qml/models/moc_nodemodel.cpp \
4344
qml/models/moc_options_model.cpp \
4445
qml/models/moc_peerdetailsmodel.cpp \
4546
qml/models/moc_peerlistsortproxy.cpp \
47+
qml/models/moc_transaction.cpp \
4648
qml/models/moc_walletlistmodel.cpp \
4749
qml/models/moc_walletqmlmodel.cpp \
4850
qml/moc_appmode.cpp \
51+
qml/moc_clipboard.cpp \
4952
qml/moc_walletqmlcontroller.cpp \
5053
qt/moc_addressbookpage.cpp \
5154
qt/moc_addresstablemodel.cpp \
@@ -120,15 +123,18 @@ QT_QRC_LOCALE = qt/bitcoin_locale.qrc
120123
BITCOIN_QT_H = \
121124
qml/components/blockclockdial.h \
122125
qml/controls/linegraph.h \
126+
qml/models/activitylistmodel.h \
123127
qml/models/chainmodel.h \
124128
qml/models/networktraffictower.h \
125129
qml/models/nodemodel.h \
126130
qml/models/options_model.h \
127131
qml/models/peerdetailsmodel.h \
128132
qml/models/peerlistsortproxy.h \
133+
qml/models/transaction.h \
129134
qml/models/walletlistmodel.h \
130135
qml/models/walletqmlmodel.h \
131136
qml/appmode.h \
137+
qml/clipboard.h \
132138
qml/bitcoin.h \
133139
qml/guiconstants.h \
134140
qml/imageprovider.h \
@@ -312,12 +318,14 @@ BITCOIN_QML_BASE_CPP = \
312318
qml/bitcoin.cpp \
313319
qml/components/blockclockdial.cpp \
314320
qml/controls/linegraph.cpp \
321+
qml/models/activitylistmodel.cpp \
315322
qml/models/chainmodel.cpp \
316323
qml/models/networktraffictower.cpp \
317324
qml/models/nodemodel.cpp \
318325
qml/models/options_model.cpp \
319326
qml/models/peerdetailsmodel.cpp \
320327
qml/models/peerlistsortproxy.cpp \
328+
qml/models/transaction.cpp \
321329
qml/models/walletlistmodel.cpp \
322330
qml/models/walletqmlmodel.cpp \
323331
qml/imageprovider.cpp \
@@ -393,14 +401,15 @@ QML_RES_QML = \
393401
qml/controls/InformationPage.qml \
394402
qml/controls/IPAddressValueInput.qml \
395403
qml/controls/KeyValueRow.qml \
404+
qml/controls/LabeledTextInput.qml \
396405
qml/controls/NavButton.qml \
397-
qml/controls/PageIndicator.qml \
398406
qml/controls/NavigationBar.qml \
399407
qml/controls/NavigationBar2.qml \
400408
qml/controls/NavigationTab.qml \
401409
qml/controls/OptionButton.qml \
402410
qml/controls/OptionSwitch.qml \
403411
qml/controls/OutlineButton.qml \
412+
qml/controls/PageIndicator.qml \
404413
qml/controls/PageStack.qml \
405414
qml/controls/ProgressIndicator.qml \
406415
qml/controls/qmldir \
@@ -433,6 +442,8 @@ QML_RES_QML = \
433442
qml/pages/settings/SettingsProxy.qml \
434443
qml/pages/settings/SettingsStorage.qml \
435444
qml/pages/settings/SettingsTheme.qml \
445+
qml/pages/wallet/Activity.qml \
446+
qml/pages/wallet/ActivityDetails.qml \
436447
qml/pages/wallet/CreateBackup.qml \
437448
qml/pages/wallet/CreateConfirm.qml \
438449
qml/pages/wallet/CreateIntro.qml \

src/qml/bitcoin.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515
#include <node/interface_ui.h>
1616
#include <noui.h>
1717
#include <qml/appmode.h>
18+
#include <qml/clipboard.h>
1819
#ifdef __ANDROID__
1920
#include <qml/androidnotifier.h>
2021
#endif
2122
#include <qml/components/blockclockdial.h>
2223
#include <qml/controls/linegraph.h>
2324
#include <qml/guiconstants.h>
25+
#include <qml/models/activitylistmodel.h>
2426
#include <qml/models/chainmodel.h>
2527
#include <qml/models/networktraffictower.h>
2628
#include <qml/models/nodemodel.h>
@@ -325,11 +327,14 @@ int QmlGuiMain(int argc, char* argv[])
325327
engine.rootContext()->setContextProperty("needOnboarding", need_onboarding);
326328

327329
AppMode app_mode = SetupAppMode();
330+
Clipboard clipboard;
328331

329332
qmlRegisterSingletonInstance<AppMode>("org.bitcoincore.qt", 1, 0, "AppMode", &app_mode);
333+
qmlRegisterSingletonInstance<Clipboard>("org.bitcoincore.qt", 1, 0, "Clipboard", &clipboard);
330334
qmlRegisterType<BlockClockDial>("org.bitcoincore.qt", 1, 0, "BlockClockDial");
331335
qmlRegisterType<LineGraph>("org.bitcoincore.qt", 1, 0, "LineGraph");
332336
qmlRegisterUncreatableType<PeerDetailsModel>("org.bitcoincore.qt", 1, 0, "PeerDetailsModel", "");
337+
qmlRegisterUncreatableType<Transaction>("org.bitcoincore.qt", 1, 0, "Transaction", "");
333338

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

src/qml/bitcoin_qml.qrc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,15 @@
3030
<file>controls/InformationPage.qml</file>
3131
<file>controls/IPAddressValueInput.qml</file>
3232
<file>controls/KeyValueRow.qml</file>
33+
<file>controls/LabeledTextInput.qml</file>
3334
<file>controls/NavButton.qml</file>
34-
<file>controls/PageIndicator.qml</file>
3535
<file>controls/NavigationBar.qml</file>
3636
<file>controls/NavigationBar2.qml</file>
3737
<file>controls/NavigationTab.qml</file>
3838
<file>controls/OptionButton.qml</file>
3939
<file>controls/OptionSwitch.qml</file>
4040
<file>controls/OutlineButton.qml</file>
41+
<file>controls/PageIndicator.qml</file>
4142
<file>controls/PageStack.qml</file>
4243
<file>controls/ProgressIndicator.qml</file>
4344
<file>controls/qmldir</file>
@@ -70,6 +71,8 @@
7071
<file>pages/settings/SettingsProxy.qml</file>
7172
<file>pages/settings/SettingsStorage.qml</file>
7273
<file>pages/settings/SettingsTheme.qml</file>
74+
<file>pages/wallet/Activity.qml</file>
75+
<file>pages/wallet/ActivityDetails.qml</file>
7376
<file>pages/wallet/CreateBackup.qml</file>
7477
<file>pages/wallet/CreateConfirm.qml</file>
7578
<file>pages/wallet/CreateIntro.qml</file>

src/qml/clipboard.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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_CLIPBOARD_H
6+
#define BITCOIN_QML_CLIPBOARD_H
7+
8+
#include <QObject>
9+
#include <QClipboard>
10+
#include <QGuiApplication>
11+
12+
class Clipboard : public QObject
13+
{
14+
Q_OBJECT
15+
public:
16+
explicit Clipboard(QObject *parent = nullptr) : QObject(parent) {}
17+
18+
Q_INVOKABLE void setText(const QString &text) {
19+
QGuiApplication::clipboard()->setText(text);
20+
}
21+
22+
Q_INVOKABLE QString text() const {
23+
return QGuiApplication::clipboard()->text();
24+
}
25+
};
26+
27+
#endif // BITCOIN_QML_CLIPBOARD_H

src/qml/controls/LabeledTextInput.qml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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: input.height
22+
23+
CoreText {
24+
id: label
25+
anchors.left: parent.left
26+
anchors.verticalCenter: parent.verticalCenter
27+
horizontalAlignment: Text.AlignLeft
28+
width: 110
29+
color: Theme.color.neutral9
30+
font.pixelSize: 18
31+
}
32+
33+
TextField {
34+
id: input
35+
anchors.left: label.right
36+
anchors.right: iconContainer.left
37+
anchors.verticalCenter: parent.verticalCenter
38+
leftPadding: 0
39+
font.family: "Inter"
40+
font.styleName: "Regular"
41+
font.pixelSize: 18
42+
color: Theme.color.neutral9
43+
placeholderTextColor: Theme.color.neutral7
44+
background: Item {}
45+
selectByMouse: true
46+
onTextEdited: root.textEdited()
47+
}
48+
49+
Item {
50+
id: iconContainer
51+
anchors.right: parent.right
52+
anchors.verticalCenter: input.verticalCenter
53+
54+
Icon {
55+
id: icon
56+
source: ""
57+
color: Theme.color.neutral8
58+
size: 30
59+
enabled: source != ""
60+
onClicked: root.iconClicked()
61+
}
62+
}
63+
}

src/qml/models/activitylistmodel.cpp

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
// Copyright (c) 2024-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+
#include <qml/models/activitylistmodel.h>
6+
7+
#include <qml/models/walletqmlmodel.h>
8+
9+
ActivityListModel::ActivityListModel(WalletQmlModel *parent)
10+
: QAbstractListModel(parent)
11+
, m_wallet_model(parent)
12+
{
13+
if (m_wallet_model != nullptr) {
14+
refreshWallet();
15+
subsctribeToCoreSignals();
16+
}
17+
}
18+
19+
ActivityListModel::~ActivityListModel()
20+
{
21+
unsubscribeFromCoreSignals();
22+
}
23+
24+
int ActivityListModel::rowCount(const QModelIndex &parent) const
25+
{
26+
Q_UNUSED(parent)
27+
return m_transactions.size();
28+
}
29+
30+
void ActivityListModel::updateTransactionStatus(QSharedPointer<Transaction> tx) const
31+
{
32+
if (m_wallet_model == nullptr) {
33+
return;
34+
}
35+
interfaces::WalletTxStatus wtx;
36+
int num_blocks;
37+
int64_t block_time;
38+
if (m_wallet_model->tryGetTxStatus(tx->hash, wtx, num_blocks, block_time)) {
39+
tx->updateStatus(wtx, num_blocks, block_time);
40+
} else {
41+
tx->status = Transaction::Status::NotAccepted;
42+
}
43+
}
44+
45+
QVariant ActivityListModel::data(const QModelIndex &index, int role) const
46+
{
47+
if (!index.isValid() || index.row() < 0 || index.row() >= m_transactions.size())
48+
return QVariant();
49+
50+
QSharedPointer<Transaction> tx = m_transactions.at(index.row());
51+
updateTransactionStatus(tx);
52+
53+
switch (role) {
54+
case AddressRole:
55+
return tx->address;
56+
case AmountRole:
57+
return tx->prettyAmount();
58+
case DateTimeRole:
59+
return tx->dateTimeString();
60+
case DepthRole:
61+
return tx->depth;
62+
case LabelRole:
63+
return tx->label;
64+
case StatusRole:
65+
return tx->status;
66+
case TypeRole:
67+
return tx->type;
68+
default:
69+
return QVariant();
70+
}
71+
}
72+
73+
QHash<int, QByteArray> ActivityListModel::roleNames() const
74+
{
75+
QHash<int, QByteArray> roles;
76+
roles[AddressRole] = "address";
77+
roles[AmountRole] = "amount";
78+
roles[DateTimeRole] = "date";
79+
roles[DepthRole] = "depth";
80+
roles[LabelRole] = "label";
81+
roles[StatusRole] = "status";
82+
roles[TypeRole] = "type";
83+
return roles;
84+
}
85+
86+
void ActivityListModel::refreshWallet()
87+
{
88+
if (m_wallet_model == nullptr) {
89+
return;
90+
}
91+
for (const auto &tx : m_wallet_model->getWalletTxs()) {
92+
auto transactions = Transaction::fromWalletTx(tx);
93+
m_transactions.append(transactions);
94+
for (const auto &transaction : transactions) {
95+
updateTransactionStatus(transaction);
96+
}
97+
}
98+
std::sort(m_transactions.begin(), m_transactions.end(),
99+
[](const QSharedPointer<Transaction> &a, const QSharedPointer<Transaction> &b) {
100+
return a->depth < b->depth;
101+
});
102+
}
103+
104+
void ActivityListModel::updateTransaction(const uint256& hash, const interfaces::WalletTxStatus& tx_status, int num_blocks, int64_t block_time)
105+
{
106+
int index = findTransactionIndex(hash);
107+
108+
if (index != -1) {
109+
QSharedPointer<Transaction> tx = m_transactions.at(index);
110+
tx->updateStatus(tx_status, num_blocks, block_time);
111+
Q_EMIT dataChanged(this->index(index), this->index(index));
112+
} else {
113+
// new transaction
114+
interfaces::WalletTx wtx = m_wallet_model->getWalletTx(hash);
115+
auto transactions = Transaction::fromWalletTx(wtx);
116+
for (const auto& tx : transactions) {
117+
tx->updateStatus(tx_status, num_blocks, block_time);
118+
m_transactions.push_front(tx);
119+
}
120+
Q_EMIT dataChanged(this->index(0), this->index(m_transactions.size() - 1));
121+
}
122+
}
123+
124+
125+
int ActivityListModel::findTransactionIndex(const uint256& hash) const
126+
{
127+
auto it = std::find_if(m_transactions.begin(), m_transactions.end(),
128+
[&hash](const QSharedPointer<Transaction>& tx) {
129+
return tx->hash == hash;
130+
});
131+
if (it != m_transactions.end()) {
132+
return std::distance(m_transactions.begin(), it);
133+
}
134+
return -1;
135+
}
136+
137+
void ActivityListModel::subsctribeToCoreSignals()
138+
{
139+
// Connect signals to wallet
140+
m_handler_transaction_changed = m_wallet_model->handleTransactionChanged([this](const uint256& hash, ChangeType status) {
141+
interfaces::WalletTxStatus wtx;
142+
int num_blocks;
143+
int64_t block_time;
144+
if (m_wallet_model->tryGetTxStatus(hash, wtx, num_blocks, block_time)) {
145+
updateTransaction(hash, wtx, num_blocks, block_time);
146+
}
147+
});
148+
}
149+
150+
void ActivityListModel::unsubscribeFromCoreSignals()
151+
{
152+
// Disconnect signals from wallet
153+
if (m_handler_transaction_changed) {
154+
m_handler_transaction_changed->disconnect();
155+
}
156+
}

0 commit comments

Comments
 (0)