Merge pull request #13 from logos-blockchain/feat/addCopyButton

feat: add copy button to addresses
This commit is contained in:
Khushboo-dev-cpp 2026-02-27 13:44:06 +05:30 committed by GitHub
commit 9f6c0a5e91
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 405 additions and 164 deletions

View File

@ -85,6 +85,8 @@ endif()
# Source files
set(SOURCES
src/AccountsModel.cpp
src/AccountsModel.h
src/BlockchainPlugin.cpp
src/BlockchainPlugin.h
src/BlockchainBackend.cpp

73
src/AccountsModel.cpp Normal file
View File

@ -0,0 +1,73 @@
#include "AccountsModel.h"
int AccountsModel::rowCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
return m_entries.size();
}
QVariant AccountsModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid() || index.row() < 0 || index.row() >= m_entries.size())
return QVariant();
const Entry& e = m_entries.at(index.row());
switch (role) {
case AddressRole:
return e.address;
case BalanceRole:
return e.balance;
case Qt::DisplayRole:
return e.address;
default:
return QVariant();
}
}
QHash<int, QByteArray> AccountsModel::roleNames() const
{
QHash<int, QByteArray> names;
names[AddressRole] = "address";
names[BalanceRole] = "balance";
return names;
}
void AccountsModel::setAddresses(const QStringList& addresses)
{
QHash<QString, QString> balanceCache;
for (const Entry& e : m_entries)
balanceCache.insert(e.address, e.balance);
QVector<Entry> newEntries;
newEntries.reserve(addresses.size());
for (const QString& addr : addresses) {
Entry e;
e.address = addr;
e.balance = balanceCache.value(addr, QStringLiteral("---"));
newEntries.append(e);
}
if (m_entries == newEntries)
return;
beginResetModel();
m_entries = std::move(newEntries);
endResetModel();
}
void AccountsModel::setBalanceForAddress(const QString& address, const QString& balance)
{
const QString valueToSet = balance.trimmed().startsWith(QStringLiteral("Error"))
? QStringLiteral("---")
: balance;
for (int i = 0; i < m_entries.size(); ++i) {
if (m_entries[i].address == address) {
if (m_entries[i].balance != valueToSet) {
m_entries[i].balance = valueToSet;
const QModelIndex idx = index(i, 0);
emit dataChanged(idx, idx, { BalanceRole });
}
return;
}
}
}

31
src/AccountsModel.h Normal file
View File

@ -0,0 +1,31 @@
#pragma once
#include <QAbstractListModel>
#include <QString>
#include <QStringList>
#include <QVector>
class AccountsModel : public QAbstractListModel {
Q_OBJECT
public:
enum Roles { AddressRole = Qt::UserRole + 1, BalanceRole };
explicit AccountsModel(QObject* parent = nullptr) : QAbstractListModel(parent) {}
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
void setAddresses(const QStringList& addresses);
Q_INVOKABLE void setBalanceForAddress(const QString& address, const QString& balance);
private:
struct Entry {
QString address;
QString balance;
bool operator==(const Entry& other) const {
return address == other.address && balance == other.balance;
}
};
QVector<Entry> m_entries;
};

View File

@ -7,6 +7,7 @@
#include <QGuiApplication>
#include <QJsonDocument>
#include <QJsonObject>
#include <QModelIndex>
#include <QSettings>
#include <QTimer>
#include <QUrl>
@ -26,6 +27,7 @@ BlockchainBackend::BlockchainBackend(LogosAPI* logosAPI, QObject* parent)
m_userConfig(""),
m_deploymentConfig(""),
m_logModel(new LogModel(this)),
m_accountsModel(new AccountsModel(this)),
m_logosAPI(nullptr),
m_blockchainClient(nullptr)
{
@ -122,11 +124,15 @@ void BlockchainBackend::copyToClipboard(const QString& text)
QString BlockchainBackend::getBalance(const QString& addressHex)
{
QString result;
if (!m_blockchainClient) {
return QStringLiteral("Error: Module not initialized.");
result = QStringLiteral("Error: Module not initialized.");
} else {
QVariant v = m_blockchainClient->invokeRemoteMethod(BLOCKCHAIN_MODULE_NAME, "wallet_get_balance", addressHex);
result = v.isValid() ? v.toString() : QStringLiteral("Error: Call failed.");
}
QVariant result = m_blockchainClient->invokeRemoteMethod(BLOCKCHAIN_MODULE_NAME, "wallet_get_balance", addressHex);
return result.isValid() ? result.toString() : QStringLiteral("Error: Call failed.");
m_accountsModel->setBalanceForAddress(addressHex, result);
return result;
}
QString BlockchainBackend::transferFunds(const QString& fromKeyHex, const QString& toKeyHex, const QString& amountStr)
@ -160,7 +166,7 @@ void BlockchainBackend::startBlockchain()
if (resultCode == 0 || resultCode == 1) {
setStatus(Running);
QTimer::singleShot(500, this, [this]() { refreshKnownAddresses(); });
QTimer::singleShot(500, this, [this]() { refreshAccounts(); });
} else if (resultCode == 2) {
setStatus(ErrorConfigMissing);
} else if (resultCode == 3) {
@ -170,15 +176,25 @@ void BlockchainBackend::startBlockchain()
}
}
void BlockchainBackend::refreshKnownAddresses()
void BlockchainBackend::refreshAccounts()
{
if (!m_blockchainClient) return;
QVariant result = m_blockchainClient->invokeRemoteMethod(BLOCKCHAIN_MODULE_NAME, "wallet_get_known_addresses");
QStringList list = result.isValid() && result.canConvert<QStringList>() ? result.toStringList() : QStringList();
qDebug() << "BlockchainBackend: received from blockchain lib: type=QStringList, count=" << list.size();
if (m_knownAddresses != list) {
m_knownAddresses = std::move(list);
emit knownAddressesChanged();
m_accountsModel->setAddresses(list);
QTimer::singleShot(0, this, [this, list]() { fetchBalancesForAccounts(list); });
}
void BlockchainBackend::fetchBalancesForAccounts(const QStringList& list)
{
if (!m_blockchainClient) return;
const int n = list.size();
for (int i = 0; i < n; ++i) {
const QString address = list[i];
if (address.isEmpty()) continue;
const QString balance = getBalance(address);
m_accountsModel->setBalanceForAddress(address, balance);
}
}

View File

@ -6,6 +6,7 @@
#include <QVariant>
#include "logos_api.h"
#include "logos_api_client.h"
#include "AccountsModel.h"
#include "LogModel.h"
class BlockchainBackend : public QObject {
@ -32,7 +33,7 @@ public:
Q_PROPERTY(QString deploymentConfig READ deploymentConfig WRITE setDeploymentConfig NOTIFY deploymentConfigChanged)
Q_PROPERTY(bool useGeneratedConfig READ useGeneratedConfig WRITE setUseGeneratedConfig NOTIFY useGeneratedConfigChanged)
Q_PROPERTY(LogModel* logModel READ logModel CONSTANT)
Q_PROPERTY(QStringList knownAddresses READ knownAddresses NOTIFY knownAddressesChanged)
Q_PROPERTY(AccountsModel* accountsModel READ accountsModel CONSTANT)
Q_PROPERTY(QString generatedUserConfigPath READ generatedUserConfigPath CONSTANT)
explicit BlockchainBackend(LogosAPI* logosAPI = nullptr, QObject* parent = nullptr);
@ -43,7 +44,7 @@ public:
QString deploymentConfig() const { return m_deploymentConfig; }
bool useGeneratedConfig() const { return m_useGeneratedConfig; }
LogModel* logModel() const { return m_logModel; }
QStringList knownAddresses() const { return m_knownAddresses; }
AccountsModel* accountsModel() const { return m_accountsModel; }
void setUserConfig(const QString& path);
void setDeploymentConfig(const QString& path);
@ -57,7 +58,7 @@ public:
const QString& amountStr);
Q_INVOKABLE void startBlockchain();
Q_INVOKABLE void stopBlockchain();
Q_INVOKABLE void refreshKnownAddresses();
Q_INVOKABLE void refreshAccounts();
Q_INVOKABLE int generateConfig(const QString& outputPath,
const QStringList& initialPeers,
int netPort,
@ -78,17 +79,17 @@ signals:
void userConfigChanged();
void deploymentConfigChanged();
void useGeneratedConfigChanged();
void knownAddressesChanged();
private:
void setStatus(BlockchainStatus newStatus);
void fetchBalancesForAccounts(const QStringList& list);
BlockchainStatus m_status;
QString m_userConfig;
QString m_deploymentConfig;
bool m_useGeneratedConfig = false;
LogModel* m_logModel;
QStringList m_knownAddresses;
AccountsModel* m_accountsModel;
LogosAPI* m_logosAPI;
LogosAPIClient* m_blockchainClient;

View File

@ -8,6 +8,11 @@
<file>qml/views/GenerateConfigView.qml</file>
<file>qml/views/ConfigChoiceView.qml</file>
<file>qml/views/SetConfigPathView.qml</file>
<file>qml/controls/AccountDelegate.qml</file>
<file>qml/controls/LogosCopyButton.qml</file>
<file>icons/blockchain.png</file>
<file>icons/copy.svg</file>
<file>icons/checkmark.svg</file>
<file>icons/refresh.svg</file>
</qresource>
</RCC>

1
src/icons/checkmark.svg Normal file
View File

@ -0,0 +1 @@
<svg fill="none" height="17" viewBox="0 0 16 17" width="16" xmlns="http://www.w3.org/2000/svg"><path clip-rule="evenodd" d="m6.76413 10.1901 5.56067-5.41122c.382-.37171 1-.37297 1.3863.00299.3837.37336.3857.97674.0031 1.34906l-6.25845 6.09017c-.18993.1849-.43822.2781-.68737.2789-.25655-.0019-.50554-.0937-.69254-.2756l-2.79161-2.7166c-.38013-.36991-.37994-.96985.00641-1.34581.38367-.37336 1.00799-.37115 1.38299-.00623z" fill="#000" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 462 B

1
src/icons/copy.svg Normal file
View File

@ -0,0 +1 @@
<svg fill="none" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#000"><path d="m4.16634 7c.27614 0 .5-.22386.5-.5s-.22386-.5-.5-.5h-.16667c-1.47275 0-2.66666 1.19391-2.66666 2.66667v3.33333c0 1.4728 1.19391 2.6667 2.66666 2.6667h3.33334c1.47276 0 2.66666-1.1939 2.66666-2.6667v-.1667c0-.2761-.22385-.5-.5-.5-.27614 0-.5.2239-.5.5v.1667c0 .9205-.74619 1.6667-1.66666 1.6667h-3.33334c-.92047 0-1.66666-.7462-1.66666-1.6667v-3.33333c0-.92048.74619-1.66667 1.66666-1.66667z"/><path clip-rule="evenodd" d="m5.99967 4c0-1.47276 1.19391-2.66666 2.66667-2.66666h3.33336c1.4727 0 2.6666 1.1939 2.6666 2.66666v3.33334c0 1.47276-1.1939 2.66666-2.6666 2.66666h-3.33336c-1.47276 0-2.66667-1.1939-2.66667-2.66666zm2.66667-1.66666h3.33336c.9204 0 1.6666.74619 1.6666 1.66666v3.33334c0 .92047-.7462 1.66666-1.6666 1.66666h-3.33336c-.92047 0-1.66667-.74619-1.66667-1.66666v-3.33334c0-.92047.7462-1.66666 1.66667-1.66666z" fill-rule="evenodd"/></g></svg>

After

Width:  |  Height:  |  Size: 976 B

1
src/icons/refresh.svg Normal file
View File

@ -0,0 +1 @@
<svg fill="none" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m12 6.25c4.2802 0 7.75 3.46979 7.75 7.75 0 4.2802-3.4698 7.75-7.75 7.75-4.28021 0-7.75-3.4698-7.75-7.75 0-.4142.33579-.75.75-.75s.75.3358.75.75c0 3.4518 2.79822 6.25 6.25 6.25 3.4518 0 6.25-2.7982 6.25-6.25s-2.7982-6.25-6.25-6.25h-.9822c-.4455 0-.6686.53857-.3536.85355l1.8661 1.86615c.2929.2929.2929.7677 0 1.0606s-.7677.2929-1.0606 0l-4.00003-3.99997c-.29289-.29289-.29289-.76777 0-1.06066l4.00003-4c.2929-.29289.7677-.29289 1.0606 0s.2929.76777 0 1.06066l-1.8661 1.86612c-.315.31498-.0919.85355.3536.85355z" fill="#000"/></svg>

After

Width:  |  Height:  |  Size: 634 B

View File

@ -90,12 +90,13 @@ Rectangle {
SplitView {
orientation: Qt.Vertical
RowLayout {
ColumnLayout {
SplitView.fillWidth: true
SplitView.minimumHeight: 200
spacing: Theme.spacing.large
StatusConfigView {
Layout.preferredWidth: parent.width / 2
Layout.fillWidth: true
statusText: _d.getStatusString(backend.status)
statusColor: _d.getStatusColor(backend.status)
userConfig: backend.userConfig
@ -113,16 +114,28 @@ Rectangle {
WalletView {
id: walletView
Layout.preferredWidth: parent.width / 2
knownAddresses: backend.knownAddresses
accountsModel: backend.accountsModel
onGetBalanceRequested: function(addressHex) {
walletView.setBalanceResult(backend.getBalance(addressHex))
var result = backend.getBalance(addressHex)
if ((result || "").indexOf("Error") === 0) {
lastBalanceErrorAddress = addressHex
lastBalanceError = result
}
else {
lastBalanceErrorAddress = ""
lastBalanceError = ""
}
}
onCopyToClipboard: (text) => backend.copyToClipboard(text)
onTransferRequested: function(fromKeyHex, toKeyHex, amount) {
walletView.setTransferResult(backend.transferFunds(fromKeyHex, toKeyHex, amount))
}
}
Item {
Layout.preferredHeight: Theme.spacing.small
}
}
LogsView {

View File

@ -0,0 +1,76 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Logos.Theme
import Logos.Controls
ItemDelegate {
id: root
property string balanceError: ""
signal getBalanceRequested(string addressHex)
signal copyRequested(string text)
width: ListView.view ? ListView.view.width : implicitWidth
background: Rectangle {
color: root.hovered ? Theme.palette.backgroundSecondary : "transparent"
}
contentItem: ColumnLayout {
spacing: Theme.spacing.small
RowLayout {
Layout.fillWidth: true
spacing: Theme.spacing.small
LogosText {
Layout.fillWidth: true
text: model.address || ""
elide: Text.ElideMiddle
font.pixelSize: Theme.typography.secondaryText
}
LogosText {
Layout.preferredWidth: contentWidth
Layout.alignment: Qt.AlignRight
visible: (model.balance || "").length > 0
text: model.balance || ""
font.pixelSize: Theme.typography.secondaryText
color: Theme.palette.textSecondary
elide: Text.ElideRight
}
Button {
Layout.alignment: Qt.AlignRight
Layout.leftMargin: parent.spacing
Layout.preferredHeight: 40
Layout.preferredWidth: 40
display: AbstractButton.IconOnly
flat: true
icon.source: "qrc:/icons/refresh.svg"
font.pixelSize: Theme.typography.secondaryText
padding: 4
onClicked: root.getBalanceRequested(model.address || "")
}
LogosCopyButton {
Layout.alignment: Qt.AlignRight
Layout.preferredHeight: 40
Layout.preferredWidth: 40
onCopyText: root.copyRequested(model.address || "")
}
}
LogosText {
Layout.fillWidth: true
visible: !!text
text: root.balanceError || ""
font.pixelSize: Theme.typography.secondaryText
color: Theme.palette.error
wrapMode: Text.WordWrap
}
}
}

View File

@ -0,0 +1,36 @@
import QtQuick
import QtQuick.Controls
Button {
id: root
signal copyText()
implicitWidth: 24
implicitHeight: 24
display: AbstractButton.IconOnly
flat: true
property string iconSource: "qrc:/icons/copy.svg"
icon.source: root.iconSource
icon.width: 24
icon.height: 24
function reset() {
iconSource = "qrc:/icons/copy.svg"
}
Timer {
id: resetTimer
interval: 1500
repeat: false
onTriggered: root.reset()
}
onClicked: {
root.copyText()
root.iconSource = "qrc:/icons/checkmark.svg"
resetTimer.restart()
}
}

View File

@ -94,6 +94,7 @@ ColumnLayout {
placeholderText: qsTr("Peer addresses, one per line")
placeholderTextColor: Theme.palette.textTertiary
font.pixelSize: Theme.typography.secondaryText
color: Theme.palette.text
}
}

View File

@ -5,7 +5,7 @@ import QtQuick.Layouts
import Logos.Theme
import Logos.Controls
ColumnLayout {
Rectangle {
id: root
// --- Public API ---
@ -21,124 +21,108 @@ ColumnLayout {
signal stopRequested()
signal changeConfigRequested()
spacing: Theme.spacing.large
implicitHeight: contentLayout.height + Theme.spacing.large
color: Theme.palette.backgroundTertiary
radius: Theme.spacing.radiusLarge
border.color: Theme.palette.border
border.width: 1
// Status Card
Rectangle {
Layout.alignment: Qt.AlignTop
Layout.preferredWidth: parent.width * 0.9
Layout.preferredHeight: implicitHeight
implicitHeight: statusContent.implicitHeight + 2 * Theme.spacing.large
color: Theme.palette.backgroundTertiary
radius: Theme.spacing.radiusLarge
border.color: Theme.palette.border
border.width: 1
RowLayout {
id: contentLayout
ColumnLayout {
id: statusContent
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Theme.spacing.large
spacing: Theme.spacing.large
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Theme.spacing.large
// Status Card
RowLayout {
Layout.alignment: Qt.AlignVCenter
spacing: Theme.spacing.medium
LogosText {
Layout.alignment: Qt.AlignLeft
font.bold: true
text: root.statusText
color: root.statusColor
ColumnLayout {
LogosText {
font.bold: true
text: root.statusText
color: root.statusColor
}
LogosText {
text: qsTr("Mainnet - chain ID 1")
font.pixelSize: Theme.typography.secondaryText
color: Theme.palette.textSecondary
}
}
LogosText {
Layout.alignment: Qt.AlignLeft
Layout.topMargin: -Theme.spacing.medium
text: qsTr("Mainnet - chain ID 1")
font.pixelSize: Theme.typography.secondaryText
color: Theme.palette.textSecondary
}
LogosButton {
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: parent.width
Layout.preferredHeight: 50
Layout.preferredHeight: 40
Layout.preferredWidth: 100
enabled: root.canStart
text: root.isRunning ? qsTr("Stop Node") : qsTr("Start Node")
onClicked: root.isRunning ? root.stopRequested() : root.startRequested()
}
}
}
// Config Card
Rectangle {
Layout.preferredWidth: parent.width * 0.9
Layout.preferredHeight: implicitHeight
implicitHeight: contentLayout.implicitHeight + 2 * Theme.spacing.large
Rectangle {
Layout.preferredWidth: 1
Layout.fillHeight: true
color: Theme.palette.borderSecondary
}
color: Theme.palette.backgroundTertiary
radius: Theme.spacing.radiusLarge
border.color: Theme.palette.border
border.width: 1
ColumnLayout {
id: contentLayout
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Theme.spacing.large
// Config Card
RowLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
spacing: Theme.spacing.medium
LogosText {
text: qsTr("Config")
font.bold: true
}
RowLayout {
ColumnLayout {
Layout.fillWidth: true
spacing: Theme.spacing.small
LogosText {
text: qsTr("User Config: ")
font.bold: true
}
LogosText {
Layout.fillWidth: true
text: (root.userConfig || qsTr("No file selected")) +
(root.useGeneratedConfig ? " " + qsTr("(Generated)") : "")
font.pixelSize: Theme.typography.secondaryText
color: Theme.palette.textSecondary
wrapMode: Text.WordWrap
}
}
RowLayout {
Layout.fillWidth: true
Layout.topMargin: -Theme.spacing.small
spacing: Theme.spacing.small
LogosText {
text: qsTr("Deployment Config: ")
font.bold: true
}
LogosText {
RowLayout {
Layout.fillWidth: true
text: (root.useGeneratedConfig && root.deploymentConfig ? root.deploymentConfig :
root.useGeneratedConfig ? qsTr("Devnet (default)") :
(root.deploymentConfig || qsTr("No file selected")))
font.pixelSize: Theme.typography.secondaryText
color: Theme.palette.textSecondary
wrapMode: Text.WordWrap
spacing: Theme.spacing.small
LogosText {
text: qsTr("User Config: ")
font.pixelSize: Theme.typography.secondaryText
}
LogosText {
Layout.fillWidth: true
text: (root.userConfig || qsTr("No file selected")) +
(root.useGeneratedConfig ? " " + qsTr("(Generated)") : "")
font.pixelSize: Theme.typography.secondaryText
color: Theme.palette.textSecondary
wrapMode: Text.WordWrap
}
}
RowLayout {
Layout.fillWidth: true
Layout.topMargin: -Theme.spacing.small
spacing: Theme.spacing.small
LogosText {
text: qsTr("Deployment Config: ")
font.pixelSize: Theme.typography.secondaryText
}
LogosText {
Layout.fillWidth: true
text: (root.useGeneratedConfig && root.deploymentConfig ?
root.deploymentConfig :
root.useGeneratedConfig ?
qsTr("Devnet (default)") :
(root.deploymentConfig || qsTr("No file selected")))
font.pixelSize: Theme.typography.secondaryText
color: Theme.palette.textSecondary
wrapMode: Text.WordWrap
}
}
}
LogosButton {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
Layout.preferredHeight: 50
Layout.preferredWidth: 100
Layout.preferredHeight: 40
text: qsTr("Change")
onClicked: root.changeConfigRequested()
}
}
}
Item { Layout.fillHeight: true }
}

View File

@ -5,20 +5,20 @@ import QtQuick.Layouts
import Logos.Theme
import Logos.Controls
ColumnLayout {
import "../controls"
RowLayout {
id: root
// list of known wallet addresses for Get balance dropdown
property var knownAddresses: []
required property var accountsModel
property string lastBalanceError: ""
property string lastBalanceErrorAddress: ""
// --- Public API ---
signal getBalanceRequested(string addressHex)
signal transferRequested(string fromKeyHex, string toKeyHex, string amount)
signal copyToClipboard(string text)
// Call these from the parent to display results
function setBalanceResult(text) {
balanceResultText.text = text
}
function setTransferResult(text) {
transferResultText.text = text
}
@ -28,8 +28,8 @@ ColumnLayout {
// Get balance card
Rectangle {
Layout.fillWidth: true
implicitHeight: balanceCol.implicitHeight + 2 * Theme.spacing.large
Layout.preferredHeight: implicitHeight
implicitHeight: transferRect.height
Layout.preferredHeight: Math.min(implicitHeight, 400)
color: Theme.palette.backgroundTertiary
radius: Theme.spacing.radiusLarge
border.color: Theme.palette.border
@ -44,40 +44,32 @@ ColumnLayout {
spacing: Theme.spacing.large
LogosText {
text: qsTr("Get balance")
text: qsTr("Accounts")
font.pixelSize: Theme.typography.secondaryText
font.bold: true
}
// Dropdown of known addresses, or type a custom address
StyledAddressComboBox {
id: balanceAddressCombo
model: knownAddresses
LogosText {
text: qsTr("Start node to see accounts here.")
font.pixelSize: Theme.typography.secondaryText
color: Theme.palette.textSecondary
wrapMode: Text.WordWrap
visible: balanceListView.count === 0
}
RowLayout {
ListView {
id: balanceListView
Layout.fillWidth: true
Layout.preferredHeight: balanceButton.implicitHeight
spacing: Theme.spacing.large
Layout.preferredHeight: Math.min(contentHeight, 320)
clip: true
model: root.accountsModel
spacing: Theme.spacing.small
LogosButton {
id: balanceButton
text: qsTr("Get balance")
onClicked: root.getBalanceRequested(balanceAddressCombo.currentText.trim())
}
LogosButton {
Layout.fillWidth: true
enabled: false
padding: Theme.spacing.medium
contentItem: Text {
id: balanceResultText
width: parent.width
color: Theme.palette.textSecondary
font.pixelSize: Theme.typography.secondaryText
font.weight: Theme.typography.weightMedium
wrapMode: Text.WordWrap
}
delegate: AccountDelegate {
balanceError: root.lastBalanceErrorAddress === model.address ?
root.lastBalanceError: ""
onGetBalanceRequested: (addr) => root.getBalanceRequested(addr)
onCopyRequested: (text) => root.copyToClipboard(text)
}
}
}
@ -85,6 +77,7 @@ ColumnLayout {
// Transfer funds card
Rectangle {
id: transferRect
Layout.fillWidth: true
Layout.preferredHeight: transferCol.height + 2 * Theme.spacing.large
color: Theme.palette.backgroundTertiary
@ -108,7 +101,8 @@ ColumnLayout {
StyledAddressComboBox {
id: transferFromCombo
model: knownAddresses
model: root.accountsModel
textRole: "address"
}
LogosTextField {
@ -128,37 +122,44 @@ ColumnLayout {
RowLayout {
Layout.fillWidth: true
Layout.preferredHeight: transferButton.implicitHeight
spacing: Theme.spacing.large
LogosButton {
id: transferButton
text: qsTr("Transfer")
Layout.preferredWidth: 60
Layout.alignment: Qt.AlignRight
text: qsTr("Send")
onClicked: root.transferRequested(transferFromCombo.currentText.trim(), transferToField.text.trim(), transferAmountField.text)
}
LogosButton {
Layout.fillWidth: true
enabled: false
padding: Theme.spacing.medium
contentItem: Text {
id: transferResultText
enabled: true
padding: Theme.spacing.small
contentItem: RowLayout {
width: parent.width
color: Theme.palette.textSecondary
font.pixelSize: Theme.typography.secondaryText
font.weight: Theme.typography.weightMedium
wrapMode: Text.WordWrap
anchors.centerIn: parent
LogosText {
id: transferResultText
Layout.fillWidth: true
color: Theme.palette.textSecondary
font.pixelSize: Theme.typography.secondaryText
font.weight: Theme.typography.weightMedium
wrapMode: Text.WordWrap
elide: Text.ElideRight
}
LogosCopyButton {
Layout.alignment: Qt.AlignRight
Layout.preferredHeight: 40
Layout.preferredWidth: 40
onCopyText: root.copyRequested(transferResultText.text)
visible: transferResultText.text
}
}
}
}
}
}
Item {
Layout.fillWidth: true
Layout.preferredHeight: Theme.spacing.small
}
component StyledAddressComboBox: ComboBox {
id: comboControl
@ -195,7 +196,6 @@ ColumnLayout {
bottomPadding: 0
verticalAlignment: Text.AlignVCenter
font.pixelSize: Theme.typography.secondaryText
font.bold: true
text: comboControl.editText
onTextChanged: if (text !== comboControl.editText) comboControl.editText = text
selectByMouse: true
@ -219,7 +219,7 @@ ColumnLayout {
height: contentHeight + Theme.spacing.large
font.pixelSize: Theme.typography.secondaryText
font.bold: true
text: modelData
text: (typeof model.address !== "undefined" ? model.address : modelData) || ""
elide: Text.ElideMiddle
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter