Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce Send pages for singlesig, single input/output send #445

Merged
merged 1 commit into from
Mar 25, 2025
Merged
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
qml: Introduce Send pages for singlesig, sigle input/output send
johnny9 committed Mar 19, 2025
commit 727aa52e0576810947e34e2e0dda6ab72c3e4570
10 changes: 10 additions & 0 deletions src/Makefile.qt.include
Original file line number Diff line number Diff line change
@@ -45,8 +45,11 @@ QT_MOC_CPP = \
qml/models/moc_peerdetailsmodel.cpp \
qml/models/moc_peerlistsortproxy.cpp \
qml/models/moc_transaction.cpp \
qml/models/moc_sendrecipient.cpp \
qml/models/moc_walletlistmodel.cpp \
qml/models/moc_walletqmlmodel.cpp \
qml/models/moc_walletqmlmodel.cpp \
qml/models/moc_walletqmlmodeltransaction.cpp \
qml/moc_appmode.cpp \
qml/moc_bitcoinamount.cpp \
qml/moc_clipboard.cpp \
@@ -132,8 +135,10 @@ BITCOIN_QT_H = \
qml/models/peerdetailsmodel.h \
qml/models/peerlistsortproxy.h \
qml/models/transaction.h \
qml/models/sendrecipient.h \
qml/models/walletlistmodel.h \
qml/models/walletqmlmodel.h \
qml/models/walletqmlmodeltransaction.h \
qml/appmode.h \
qml/clipboard.h \
qml/bitcoin.h \
@@ -329,8 +334,10 @@ BITCOIN_QML_BASE_CPP = \
qml/models/peerdetailsmodel.cpp \
qml/models/peerlistsortproxy.cpp \
qml/models/transaction.cpp \
qml/models/sendrecipient.cpp \
qml/models/walletlistmodel.cpp \
qml/models/walletqmlmodel.cpp \
qml/models/walletqmlmodeltransaction.cpp \
qml/imageprovider.cpp \
qml/util.cpp \
qml/walletqmlcontroller.cpp
@@ -462,6 +469,9 @@ QML_RES_QML = \
qml/pages/wallet/CreateWalletWizard.qml \
qml/pages/wallet/DesktopWallets.qml \
qml/pages/wallet/RequestPayment.qml \
qml/pages/wallet/Send.qml \
qml/pages/wallet/SendResult.qml \
qml/pages/wallet/SendReview.qml \
qml/pages/wallet/WalletBadge.qml \
qml/pages/wallet/WalletSelect.qml

7 changes: 6 additions & 1 deletion src/qml/bitcoin.cpp
Original file line number Diff line number Diff line change
@@ -23,16 +23,18 @@
#include <qml/components/blockclockdial.h>
#include <qml/controls/linegraph.h>
#include <qml/guiconstants.h>
#include <qml/imageprovider.h>
#include <qml/models/activitylistmodel.h>
#include <qml/models/chainmodel.h>
#include <qml/models/networktraffictower.h>
#include <qml/models/nodemodel.h>
#include <qml/models/options_model.h>
#include <qml/models/peerdetailsmodel.h>
#include <qml/models/peerlistsortproxy.h>
#include <qml/models/sendrecipient.h>
#include <qml/models/walletlistmodel.h>
#include <qml/models/walletqmlmodel.h>
#include <qml/imageprovider.h>
#include <qml/models/walletqmlmodeltransaction.h>
#include <qml/util.h>
#include <qml/walletqmlcontroller.h>
#include <qt/guiutil.h>
@@ -337,10 +339,13 @@ int QmlGuiMain(int argc, char* argv[])
qmlRegisterUncreatableType<PeerDetailsModel>("org.bitcoincore.qt", 1, 0, "PeerDetailsModel", "");
qmlRegisterType<BitcoinAmount>("org.bitcoincore.qt", 1, 0, "BitcoinAmount");
qmlRegisterUncreatableType<Transaction>("org.bitcoincore.qt", 1, 0, "Transaction", "");
qmlRegisterUncreatableType<SendRecipient>("org.bitcoincore.qt", 1, 0, "SendRecipient", "");

#ifdef ENABLE_WALLET
qmlRegisterUncreatableType<WalletQmlModel>("org.bitcoincore.qt", 1, 0, "WalletQmlModel",
"WalletQmlModel cannot be instantiated from QML");
qmlRegisterUncreatableType<WalletQmlModelTransaction>("org.bitcoincore.qt", 1, 0, "WalletQmlModelTransaction",
"WalletQmlModelTransaction cannot be instantiated from QML");
#endif

engine.load(QUrl(QStringLiteral("qrc:///qml/pages/main.qml")));
4 changes: 3 additions & 1 deletion src/qml/bitcoin_qml.qrc
Original file line number Diff line number Diff line change
@@ -24,7 +24,6 @@
<file>controls/ContinueButton.qml</file>
<file>controls/CoreText.qml</file>
<file>controls/CoreTextField.qml</file>
<file>controls/LabeledTextInput.qml</file>
<file>controls/ExternalLink.qml</file>
<file>controls/FocusBorder.qml</file>
<file>controls/Header.qml</file>
@@ -83,6 +82,9 @@
<file>pages/wallet/CreateWalletWizard.qml</file>
<file>pages/wallet/DesktopWallets.qml</file>
<file>pages/wallet/RequestPayment.qml</file>
<file>pages/wallet/Send.qml</file>
<file>pages/wallet/SendResult.qml</file>
<file>pages/wallet/SendReview.qml</file>
<file>pages/wallet/WalletBadge.qml</file>
<file>pages/wallet/WalletSelect.qml</file>
</qresource>
31 changes: 29 additions & 2 deletions src/qml/bitcoinamount.cpp
Original file line number Diff line number Diff line change
@@ -68,15 +68,28 @@ QString BitcoinAmount::amount() const
return m_amount;
}

QString BitcoinAmount::satoshiAmount() const
{
return toSatoshis(m_amount);
}

void BitcoinAmount::setAmount(const QString& new_amount)
{
m_amount = sanitize(new_amount);
Q_EMIT amountChanged();
}

long long BitcoinAmount::toSatoshis(QString& amount, const Unit unit)
QString BitcoinAmount::toSatoshis(const QString& text) const
{
if (m_unit == Unit::SAT) {
return text;
} else {
return convert(text, m_unit);
}
}

long long BitcoinAmount::toSatoshis(QString& amount, const Unit unit)
{
int num_decimals = decimals(unit);

QStringList parts = amount.remove(' ').split(".");
@@ -93,7 +106,7 @@ long long BitcoinAmount::toSatoshis(QString& amount, const Unit unit)
return str.toLongLong();
}

QString BitcoinAmount::convert(const QString &amount, Unit unit)
QString BitcoinAmount::convert(const QString& amount, Unit unit) const
{
if (amount == "") {
return amount;
@@ -113,6 +126,10 @@ QString BitcoinAmount::convert(const QString &amount, Unit unit)
result.append(QString(8 - numDigitsAfterDecimal, '0'));
}
result.remove(decimalPosition, 1);

while (result.startsWith('0') && result.length() > 1) {
result.remove(0, 1);
}
} else if (unit == Unit::SAT) {
result.remove(decimalPosition, 1);
int newDecimalPosition = decimalPosition - 8;
@@ -121,6 +138,16 @@ QString BitcoinAmount::convert(const QString &amount, Unit unit)
newDecimalPosition = 0;
}
result.insert(newDecimalPosition, ".");

while (result.endsWith('0') && result.contains('.')) {
result.chop(1);
}
if (result.endsWith('.')) {
result.chop(1);
}
if (result.startsWith('.')) {
result.insert(0, "0");
}
}

return result;
10 changes: 7 additions & 3 deletions src/qml/bitcoinamount.h
Original file line number Diff line number Diff line change
@@ -5,16 +5,18 @@
#ifndef BITCOIN_QML_BITCOINAMOUNT_H
#define BITCOIN_QML_BITCOINAMOUNT_H

#include <consensus/amount.h>

#include <QObject>
#include <QString>
#include <qobjectdefs.h>

class BitcoinAmount : public QObject
{
Q_OBJECT
Q_PROPERTY(Unit unit READ unit WRITE setUnit NOTIFY unitChanged)
Q_PROPERTY(QString unitLabel READ unitLabel NOTIFY unitChanged)
Q_PROPERTY(QString amount READ amount WRITE setAmount NOTIFY amountChanged)
Q_PROPERTY(QString satoshiAmount READ satoshiAmount NOTIFY amountChanged)

public:
enum class Unit {
@@ -30,10 +32,12 @@ class BitcoinAmount : public QObject
QString unitLabel() const;
QString amount() const;
void setAmount(const QString& new_amount);
QString satoshiAmount() const;

public Q_SLOTS:
QString sanitize(const QString &text);
QString convert(const QString &text, Unit unit);
QString sanitize(const QString& text);
QString convert(const QString& text, Unit unit) const;
QString toSatoshis(const QString& text) const;

Q_SIGNALS:
void unitChanged();
5 changes: 5 additions & 0 deletions src/qml/imageprovider.cpp
Original file line number Diff line number Diff line change
@@ -206,5 +206,10 @@ QPixmap ImageProvider::requestPixmap(const QString& id, QSize* size, const QSize
*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 {};
}
87 changes: 87 additions & 0 deletions src/qml/models/sendrecipient.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// 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 <qml/models/sendrecipient.h>
#include <qobjectdefs.h>

SendRecipient::SendRecipient(QObject* parent)
: QObject(parent), m_address(""), m_label(""), m_amount(""), m_message("")
{
}

QString SendRecipient::address() const
{
return m_address;
}

void SendRecipient::setAddress(const QString& address)
{
if (m_address != address) {
m_address = address;
Q_EMIT addressChanged();
}
}

QString SendRecipient::label() const
{
return m_label;
}

void SendRecipient::setLabel(const QString& label)
{
if (m_label != label) {
m_label = label;
Q_EMIT labelChanged();
}
}

QString SendRecipient::amount() const
{
return m_amount;
}

void SendRecipient::setAmount(const QString& amount)
{
if (m_amount != amount) {
m_amount = amount;
Q_EMIT amountChanged();
}
}

QString SendRecipient::message() const
{
return m_message;
}

void SendRecipient::setMessage(const QString& message)
{
if (m_message != message) {
m_message = message;
Q_EMIT messageChanged();
}
}

bool SendRecipient::subtractFeeFromAmount() const
{
return m_subtractFeeFromAmount;
}

CAmount SendRecipient::cAmount() const
{
// TODO: Figure out who owns the parsing of SendRecipient::amount to CAmount
return m_amount.toLongLong();
}

void SendRecipient::clear()
{
m_address = "";
m_label = "";
m_amount = "";
m_message = "";
m_subtractFeeFromAmount = false;
Q_EMIT addressChanged();
Q_EMIT labelChanged();
Q_EMIT amountChanged();
Q_EMIT messageChanged();
}
55 changes: 55 additions & 0 deletions src/qml/models/sendrecipient.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// 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_SENDRECIPIENT_H
#define BITCOIN_QML_MODELS_SENDRECIPIENT_H

#include <QObject>
#include <QString>
#include <qml/bitcoinamount.h>

class SendRecipient : public QObject
{
Q_OBJECT
Q_PROPERTY(QString address READ address WRITE setAddress NOTIFY addressChanged)
Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged)
Q_PROPERTY(QString amount READ amount WRITE setAmount NOTIFY amountChanged)
Q_PROPERTY(QString message READ message WRITE setMessage NOTIFY messageChanged)

public:
explicit SendRecipient(QObject* parent = nullptr);

QString address() const;
void setAddress(const QString& address);

QString label() const;
void setLabel(const QString& label);

QString amount() const;
void setAmount(const QString& amount);

QString message() const;
void setMessage(const QString& message);

CAmount cAmount() const;

bool subtractFeeFromAmount() const;

Q_INVOKABLE void clear();

Q_SIGNALS:
void addressChanged();
void labelChanged();
void amountChanged();
void messageChanged();

private:
QString m_address;
QString m_label;
QString m_amount;
QString m_message;
bool m_subtractFeeFromAmount{false};
};

#endif // BITCOIN_QML_MODELS_SENDRECIPIENT_H
65 changes: 61 additions & 4 deletions src/qml/models/walletqmlmodel.cpp
Original file line number Diff line number Diff line change
@@ -6,10 +6,15 @@

#include <qml/models/activitylistmodel.h>

#include <outputtype.h>
#include <qt/bitcoinunits.h>
#include <qml/models/sendrecipient.h>
#include <qml/models/walletqmlmodeltransaction.h>

#include <consensus/amount.h>
#include <key_io.h>
#include <outputtype.h>
#include <qt/bitcoinunits.h>
#include <wallet/coincontrol.h>
#include <wallet/wallet.h>

#include <QTimer>

@@ -18,12 +23,14 @@ WalletQmlModel::WalletQmlModel(std::unique_ptr<interfaces::Wallet> wallet, QObje
{
m_wallet = std::move(wallet);
m_activity_list_model = new ActivityListModel(this);
m_current_recipient = new SendRecipient(this);
}

WalletQmlModel::WalletQmlModel(QObject *parent)
WalletQmlModel::WalletQmlModel(QObject* parent)
: QObject(parent)
{
m_activity_list_model = new ActivityListModel(this);
m_current_recipient = new SendRecipient(this);
}

QString WalletQmlModel::balance() const
@@ -42,7 +49,6 @@ QString WalletQmlModel::name() const
return QString::fromStdString(m_wallet->getWalletName());
}


std::set<interfaces::WalletTx> WalletQmlModel::getWalletTxs() const
{
if (!m_wallet) {
@@ -82,3 +88,54 @@ std::unique_ptr<interfaces::Handler> WalletQmlModel::handleTransactionChanged(Tr
}
return m_wallet->handleTransactionChanged(fn);
}

bool WalletQmlModel::prepareTransaction()
{
if (!m_wallet || !m_current_recipient) {
return false;
}

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);

CAmount balance = m_wallet->getBalance();
if (balance < recipient.nAmount) {
return false;
}

std::vector<wallet::CRecipient> vecSend{recipient};
int nChangePosRet = -1;
CAmount nFeeRequired = 0;
const auto& res = m_wallet->createTransaction(vecSend, coinControl, true, nChangePosRet, nFeeRequired);
if (res) {
if (m_current_transaction) {
delete m_current_transaction;
}
CTransactionRef newTx = *res;
m_current_transaction = new WalletQmlModelTransaction(m_current_recipient, this);
m_current_transaction->setWtx(newTx);
m_current_transaction->setTransactionFee(nFeeRequired);
Q_EMIT currentTransactionChanged();
return true;
} else {
return false;
}
}

void WalletQmlModel::sendTransaction()
{
if (!m_wallet || !m_current_transaction) {
return;
}

CTransactionRef newTx = m_current_transaction->getWtx();
if (!newTx) {
return;
}

interfaces::WalletValueMap value_map;
interfaces::WalletOrderForm order_form;
m_wallet->commitTransaction(newTx, value_map, order_form);
}
15 changes: 14 additions & 1 deletion src/qml/models/walletqmlmodel.h
Original file line number Diff line number Diff line change
@@ -10,6 +10,9 @@

#include <qml/models/activitylistmodel.h>

#include <qml/models/sendrecipient.h>
#include <qml/models/walletqmlmodeltransaction.h>

#include <QObject>
#include <vector>

@@ -21,9 +24,11 @@ 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(SendRecipient* sendRecipient READ sendRecipient CONSTANT)
Q_PROPERTY(WalletQmlModelTransaction* currentTransaction READ currentTransaction NOTIFY currentTransactionChanged)

public:
WalletQmlModel(std::unique_ptr<interfaces::Wallet> wallet, QObject *parent = nullptr);
WalletQmlModel(std::unique_ptr<interfaces::Wallet> wallet, QObject* parent = nullptr);
WalletQmlModel(QObject *parent = nullptr);
~WalletQmlModel();

@@ -38,16 +43,24 @@ class WalletQmlModel : public QObject
int& num_blocks,
int64_t& block_time) const;

SendRecipient* sendRecipient() const { return m_current_recipient; }
WalletQmlModelTransaction* currentTransaction() const { return m_current_transaction; }
Q_INVOKABLE bool prepareTransaction();
Q_INVOKABLE void sendTransaction();

using TransactionChangedFn = std::function<void(const uint256& txid, ChangeType status)>;
virtual std::unique_ptr<interfaces::Handler> handleTransactionChanged(TransactionChangedFn fn);

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

private:
std::unique_ptr<interfaces::Wallet> m_wallet;
ActivityListModel* m_activity_list_model{nullptr};
SendRecipient* m_current_recipient{nullptr};
WalletQmlModelTransaction* m_current_transaction{nullptr};
};

#endif // BITCOIN_QML_MODELS_WALLETQMLMODEL_H
66 changes: 66 additions & 0 deletions src/qml/models/walletqmlmodeltransaction.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) 2011-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 <qml/models/walletqmlmodeltransaction.h>

#include <policy/policy.h>
#include <qobject.h>

WalletQmlModelTransaction::WalletQmlModelTransaction(const SendRecipient* recipient, QObject* parent)
: QObject(parent), m_address(recipient->address()), m_amount(recipient->cAmount()), m_fee(0), m_label(recipient->label()), m_wtx(nullptr)
{
}

QString WalletQmlModelTransaction::amount() const
{
return QString::number(m_amount);
}

QString WalletQmlModelTransaction::address() const
{
return m_address;
}

QString WalletQmlModelTransaction::fee() const
{
return QString::number(m_fee);
}

QString WalletQmlModelTransaction::total() const
{
return QString::number(m_amount + m_fee);
}

QString WalletQmlModelTransaction::label() const
{
return m_label;
}

CTransactionRef& WalletQmlModelTransaction::getWtx()
{
return m_wtx;
}

void WalletQmlModelTransaction::setWtx(const CTransactionRef& newTx)
{
m_wtx = newTx;
}

CAmount WalletQmlModelTransaction::getTransactionFee() const
{
return m_fee;
}

void WalletQmlModelTransaction::setTransactionFee(const CAmount& newFee)
{
if (m_fee != newFee) {
m_fee = newFee;
Q_EMIT feeChanged();
}
}

CAmount WalletQmlModelTransaction::getTotalTransactionAmount() const
{
return m_amount + m_fee;
}
62 changes: 62 additions & 0 deletions src/qml/models/walletqmlmodeltransaction.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) 2011-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_WALLETQMLMODELTRANSACTION_H
#define BITCOIN_QML_MODELS_WALLETQMLMODELTRANSACTION_H

#include <primitives/transaction.h>
#include <qml/models/sendrecipient.h>

#include <consensus/amount.h>

#include <QObject>


class WalletQmlModelTransaction : public QObject
{
Q_OBJECT
Q_PROPERTY(QString address READ address CONSTANT)
Q_PROPERTY(QString amount READ amount NOTIFY amountChanged)
Q_PROPERTY(QString label READ label CONSTANT)
Q_PROPERTY(QString fee READ fee NOTIFY feeChanged)
Q_PROPERTY(QString total READ total NOTIFY totalChanged)
public:
explicit WalletQmlModelTransaction(const SendRecipient* recipient, QObject* parent = nullptr);

QString address() const;
QString amount() const;
QString fee() const;
QString label() const;
QString total() const;

QList<SendRecipient> getRecipients() const;

CTransactionRef& getWtx();
void setWtx(const CTransactionRef&);

unsigned int getTransactionSize();

void setTransactionFee(const CAmount& newFee);
CAmount getTransactionFee() const;

CAmount getTotalTransactionAmount() const;

void reassignAmounts(int nChangePosRet); // needed for the subtract-fee-from-amount feature

Q_SIGNALS:
void addressChanged();
void labelChanged();
void amountChanged();
void feeChanged();
void totalChanged();

private:
QString m_address;
CAmount m_amount;
CAmount m_fee;
QString m_label;
CTransactionRef m_wtx;
};

#endif // BITCOIN_QML_MODELS_WALLETQMLMODELTRANSACTION_H
22 changes: 22 additions & 0 deletions src/qml/pages/main.qml
Original file line number Diff line number Diff line change
@@ -84,6 +84,9 @@ ApplicationWindow {
onAddWallet: {
main.push(createWalletWizard)
}
onSendTransaction: {
main.push(sendReviewPage)
}
}
}

@@ -96,6 +99,25 @@ ApplicationWindow {
}
}

Component {
id: sendReviewPage
SendReview {
onBack: {
main.pop()
}
onTransactionSent: {
walletController.selectedWallet.sendRecipient.clear()
main.pop()
sendResult.open()
}
}
}

SendResult {
id: sendResult
closePolicy: Popup.CloseOnPressOutside
}

Component {
id: shutdown
Shutdown {}
5 changes: 3 additions & 2 deletions src/qml/pages/wallet/DesktopWallets.qml
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@ Page {
ButtonGroup { id: navigationTabs }

signal addWallet()
signal sendTransaction()

header: NavigationBar2 {
id: navBar
@@ -131,9 +132,9 @@ Page {
Activity {
id: activityTab
}
Item {
Send {
id: sendTab
CoreText { text: "Send" }
onTransactionPrepared: root.sendTransaction()
}
RequestPayment {
id: receiveTab
180 changes: 180 additions & 0 deletions src/qml/pages/wallet/Send.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
// Copyright (c) 2024 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
background: null

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
}

LabeledTextInput {
id: address
Layout.fillWidth: true
labelText: qsTr("Send to")
placeholderText: qsTr("Enter address...")
text: root.recipient.address
onTextEdited: root.recipient.address = address.text
}

Separator {
Layout.fillWidth: true
}

Item {
BitcoinAmount {
id: bitcoinAmount
}

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
}

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
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
}

LabeledTextInput {
id: label
Layout.fillWidth: true
labelText: qsTr("Note to self")
placeholderText: qsTr("Enter ...")
onTextEdited: root.recipient.label = label.text
}

Separator {
Layout.fillWidth: true
}

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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe
make the button only click-able / highlighted on hover when there is a valid input?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do. I will be coming back to this as a Validation PR.

id: continueButton
Layout.fillWidth: true
Layout.topMargin: 30
text: qsTr("Review")
onClicked: {
if (root.wallet.prepareTransaction()) {
root.transactionPrepared()
}
}
}
}
}
}
87 changes: 87 additions & 0 deletions src/qml/pages/wallet/SendResult.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright (c) 2024 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 QtQuick.Dialogs 1.2
import org.bitcoincore.qt 1.0

import "../../controls"
import "../../components"

Popup {
id: root
modal: true
anchors.centerIn: parent

background: Rectangle {
anchors.centerIn: parent
width: columnLayout.width + 40
height: columnLayout.height + 40
color: Theme.color.neutral0
border.color: Theme.color.neutral4
border.width: 1
radius: 5
}

ColumnLayout {
id: columnLayout
anchors.centerIn: parent
spacing: 20

Item {
width: 60
height: 60
Layout.alignment: Qt.AlignHCenter
Rectangle {
anchors.fill: parent
Layout.alignment: Qt.AlignHCenter
radius: 30
color: Theme.color.green
opacity: 0.2
}
Icon {
anchors.centerIn: parent
source: "qrc:/icons/check"
color: Theme.color.green
size: 30
opacity: 1.0
}
}

CoreText {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Transaction sent")
font.pixelSize: 28
bold: true
}

CoreText {
Layout.alignment: Qt.AlignHCenter
Layout.maximumWidth: 350
color: Theme.color.neutral7
text: qsTr("Based on your selected fee, it should be confirmed within the next 10 minutes.")
font.pixelSize: 18
}

ContinueButton {
Layout.preferredWidth: Math.min(200, parent.width - 2 * Layout.leftMargin)
Layout.leftMargin: 20
Layout.rightMargin: Layout.leftMargin
Layout.alignment: Qt.AlignCenter
text: qsTr("Close window")
borderColor: Theme.color.neutral6
borderHoverColor: Theme.color.neutral9
borderPressedColor: Theme.color.neutral9
textColor: Theme.color.neutral9
backgroundColor: "transparent"
backgroundHoverColor: "transparent"
backgroundPressedColor: "transparent"
onClicked: {
root.close()
}
}
}
}
139 changes: 139 additions & 0 deletions src/qml/pages/wallet/SendReview.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Copyright (c) 2024 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
background: null

property WalletQmlModel wallet: walletController.selectedWallet
property WalletQmlModelTransaction transaction: walletController.selectedWallet.currentTransaction

signal finished()
signal back()
signal transactionSent()

header: NavigationBar2 {
id: navbar
leftItem: NavButton {
iconSource: "image://images/caret-left"
text: qsTr("Back")
onClicked: {
root.back()
}
}
}

ScrollView {
clip: true
width: parent.width
height: parent.height
contentWidth: width

ColumnLayout {
id: columnLayout
width: 450
anchors.horizontalCenter: parent.horizontalCenter

spacing: 20

CoreText {
id: title
Layout.topMargin: 30
Layout.bottomMargin: 20
text: qsTr("Transaction details")
font.pixelSize: 21
bold: true
}

RowLayout {
CoreText {
text: qsTr("Send to")
font.pixelSize: 15
Layout.preferredWidth: 110
color: Theme.color.neutral7
}
CoreText {
text: root.transaction.address
font.pixelSize: 15
color: Theme.color.neutral9
}
}

RowLayout {
CoreText {
text: qsTr("Note")
font.pixelSize: 15
Layout.preferredWidth: 110
color: Theme.color.neutral7
}
CoreText {
text: root.transaction.label
font.pixelSize: 15
color: Theme.color.neutral9
}
}

RowLayout {
CoreText {
text: qsTr("Amount")
font.pixelSize: 15
Layout.preferredWidth: 110
color: Theme.color.neutral7
}
CoreText {
text: root.transaction.amount
font.pixelSize: 15
color: Theme.color.neutral9
}
}

RowLayout {
CoreText {
text: qsTr("Fee")
font.pixelSize: 15
Layout.preferredWidth: 110
color: Theme.color.neutral7
}
CoreText {
text: root.transaction.fee
font.pixelSize: 15
color: Theme.color.neutral9
}
}

RowLayout {
CoreText {
text: qsTr("Total")
font.pixelSize: 15
Layout.preferredWidth: 110
color: Theme.color.neutral7
}
CoreText {
text: root.transaction.total
font.pixelSize: 15
color: Theme.color.neutral9
}
}

ContinueButton {
id: confimationButton
Layout.fillWidth: true
Layout.topMargin: 30
text: qsTr("Send")
onClicked: {
root.wallet.sendTransaction()
root.transactionSent()
}
}
}
}
}