mirror of
https://github.com/logos-blockchain/logos-execution-zone-wallet-ui.git
synced 2026-07-29 22:23:30 +00:00
add account list sections separated by public or (npk,vpk) groups
This commit is contained in:
parent
fc28a83191
commit
2de3f043d9
@ -1,5 +1,6 @@
|
||||
#include "LEZWalletAccountModel.h"
|
||||
#include <QVariantMap>
|
||||
#include <algorithm>
|
||||
|
||||
LEZWalletAccountModel::LEZWalletAccountModel(QObject* parent)
|
||||
: QAbstractListModel(parent)
|
||||
@ -24,6 +25,9 @@ QVariant LEZWalletAccountModel::data(const QModelIndex& index, int role) const
|
||||
case AccountIdRole: return e.accountId;
|
||||
case BalanceRole: return e.balance;
|
||||
case IsPublicRole: return e.isPublic;
|
||||
case SectionKeyRole: return e.sectionKey;
|
||||
case KeysJsonRole: return e.keysJson;
|
||||
case IsFirstInGroupRole: return e.isFirstInGroup;
|
||||
default: return QVariant();
|
||||
}
|
||||
}
|
||||
@ -34,7 +38,10 @@ QHash<int, QByteArray> LEZWalletAccountModel::roleNames() const
|
||||
{ NameRole, "name" },
|
||||
{ AccountIdRole, "accountId" },
|
||||
{ BalanceRole, "balance" },
|
||||
{ IsPublicRole, "isPublic" }
|
||||
{ IsPublicRole, "isPublic" },
|
||||
{ SectionKeyRole, "sectionKey" },
|
||||
{ KeysJsonRole, "keysJson" },
|
||||
{ IsFirstInGroupRole, "isFirstInGroup" }
|
||||
};
|
||||
}
|
||||
|
||||
@ -51,12 +58,28 @@ void LEZWalletAccountModel::replaceFromVariantList(const QVariantList& list)
|
||||
const QVariantMap map = v.toMap();
|
||||
e.accountId = map.value(QStringLiteral("account_id")).toString();
|
||||
e.isPublic = map.value(QStringLiteral("is_public"), true).toBool();
|
||||
if (e.isPublic) {
|
||||
e.sectionKey = PublicSectionKey;
|
||||
} else {
|
||||
e.sectionKey = map.value(QStringLiteral("npk")).toString();
|
||||
e.keysJson = map.value(QStringLiteral("keys_json")).toString();
|
||||
}
|
||||
} else {
|
||||
e.accountId = v.toString();
|
||||
e.isPublic = true;
|
||||
e.sectionKey = PublicSectionKey;
|
||||
}
|
||||
m_entries.append(e);
|
||||
}
|
||||
// Keep entries grouped by section (public first) so consecutive rows of the same
|
||||
// group are contiguous, then mark each group's first row for the QML header.
|
||||
std::stable_sort(m_entries.begin(), m_entries.end(),
|
||||
[](const LEZWalletAccountEntry& a, const LEZWalletAccountEntry& b) {
|
||||
if (a.isPublic != b.isPublic) return a.isPublic;
|
||||
return a.sectionKey < b.sectionKey;
|
||||
});
|
||||
for (int i = 0; i < m_entries.size(); ++i)
|
||||
m_entries[i].isFirstInGroup = (i == 0) || (m_entries[i].sectionKey != m_entries[i - 1].sectionKey);
|
||||
endResetModel();
|
||||
if (oldCount != m_entries.size())
|
||||
emit countChanged();
|
||||
|
||||
@ -4,13 +4,25 @@
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
|
||||
// Public accounts have no key group, so they all share the PublicSectionKey section.
|
||||
// Private accounts section by NPK (the key group they belong to) — see
|
||||
// LEZWalletBackend::buildEnrichedAccountList, which attaches "npk"/"keys_json" per entry.
|
||||
inline const QString PublicSectionKey = QStringLiteral("public");
|
||||
|
||||
struct LEZWalletAccountEntry {
|
||||
QString name;
|
||||
QString accountId;
|
||||
QString balance;
|
||||
bool isPublic = true;
|
||||
QString sectionKey;
|
||||
QString keysJson; // {nullifier_public_key, viewing_public_key} shared by the whole section; private only
|
||||
bool isFirstInGroup = false; // QML renders the section header above rows where this is true
|
||||
};
|
||||
|
||||
// Note: this model is exposed to QML via Qt Remote Objects model replication (see
|
||||
// logos.model() in ExecutionZoneWalletView.qml), which only replicates roles — not
|
||||
// arbitrary Q_PROPERTYs. Anything QML needs must be a role on the row, not a property
|
||||
// on the model itself.
|
||||
class LEZWalletAccountModel : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int count READ count NOTIFY countChanged)
|
||||
@ -19,7 +31,10 @@ public:
|
||||
NameRole = Qt::UserRole + 1,
|
||||
AccountIdRole,
|
||||
BalanceRole,
|
||||
IsPublicRole
|
||||
IsPublicRole,
|
||||
SectionKeyRole,
|
||||
KeysJsonRole,
|
||||
IsFirstInGroupRole
|
||||
};
|
||||
Q_ENUM(Role)
|
||||
|
||||
|
||||
@ -133,11 +133,34 @@ void LEZWalletBackend::openIfPathsConfigured()
|
||||
}
|
||||
}
|
||||
|
||||
// Tags each private account with the NPK of the key group it belongs to (plus that
|
||||
// group's {nullifier_public_key, viewing_public_key} JSON), so the model can section
|
||||
// accounts by key group instead of listing them flat. Public accounts are untouched.
|
||||
QVariantList LEZWalletBackend::buildEnrichedAccountList()
|
||||
{
|
||||
QVariantList raw = m_logos->logos_execution_zone.list_accounts();
|
||||
QVariantList enriched;
|
||||
enriched.reserve(raw.size());
|
||||
for (const QVariant& v : raw) {
|
||||
QVariantMap map = v.toMap();
|
||||
if (!map.value(QStringLiteral("is_public"), true).toBool()) {
|
||||
const QString accountId = map.value(QStringLiteral("account_id")).toString();
|
||||
const QString keysJson = getPrivateAccountKeys(accountId);
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(keysJson.toUtf8());
|
||||
if (doc.isObject()) {
|
||||
map[QStringLiteral("npk")] = doc.object().value(QStringLiteral("nullifier_public_key")).toString();
|
||||
map[QStringLiteral("keys_json")] = keysJson;
|
||||
}
|
||||
}
|
||||
enriched.append(map);
|
||||
}
|
||||
return enriched;
|
||||
}
|
||||
|
||||
void LEZWalletBackend::finishOpeningWallet()
|
||||
{
|
||||
setIsWalletOpen(true);
|
||||
QVariantList arr = m_logos->logos_execution_zone.list_accounts();
|
||||
m_accountModel->replaceFromVariantList(arr);
|
||||
m_accountModel->replaceFromVariantList(buildEnrichedAccountList());
|
||||
fetchAndUpdateBlockHeights();
|
||||
startChunkedSync();
|
||||
refreshSequencerAddr();
|
||||
@ -145,8 +168,7 @@ void LEZWalletBackend::finishOpeningWallet()
|
||||
|
||||
void LEZWalletBackend::refreshAccounts()
|
||||
{
|
||||
QVariantList arr = m_logos->logos_execution_zone.list_accounts();
|
||||
m_accountModel->replaceFromVariantList(arr);
|
||||
m_accountModel->replaceFromVariantList(buildEnrichedAccountList());
|
||||
fetchAndUpdateBlockHeights();
|
||||
if (!m_syncing)
|
||||
startChunkedSync();
|
||||
@ -177,8 +199,7 @@ void LEZWalletBackend::syncNextChunk()
|
||||
m_syncing = false;
|
||||
// Sync may have discovered new private accounts (e.g. shielded transfers to a
|
||||
// foreign NPK/VPK); re-list so the model picks them up without a restart.
|
||||
QVariantList arr = m_logos->logos_execution_zone.list_accounts();
|
||||
m_accountModel->replaceFromVariantList(arr);
|
||||
m_accountModel->replaceFromVariantList(buildEnrichedAccountList());
|
||||
fetchAndUpdateBlockHeights();
|
||||
updateBalances();
|
||||
return;
|
||||
|
||||
@ -57,6 +57,7 @@ private:
|
||||
void applySequencerAddrToConfig(const QString& configPath, const QString& sequencerAddr);
|
||||
void fetchAndUpdateBlockHeights();
|
||||
void startChunkedSync();
|
||||
QVariantList buildEnrichedAccountList();
|
||||
|
||||
void updateBalances();
|
||||
void refreshSequencerAddr();
|
||||
|
||||
@ -237,16 +237,6 @@ Rectangle {
|
||||
clipHelper.selectAll()
|
||||
clipHelper.copy()
|
||||
}
|
||||
onCopyPublicKeysRequested: (accountIdHex) => {
|
||||
if (!backend) return
|
||||
logos.watch(backend.getPrivateAccountKeys(accountIdHex),
|
||||
function(keys) {
|
||||
clipHelper.text = keys
|
||||
clipHelper.selectAll()
|
||||
clipHelper.copy()
|
||||
},
|
||||
function(error) { console.warn("getPrivateAccountKeys failed:", error) })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +12,6 @@ ComboBox {
|
||||
// Forwarded from AccountDelegate's copy button — bubble up to the parent
|
||||
// view, which calls backend.copyToClipboard().
|
||||
signal copyRequested(string text)
|
||||
signal copyPublicKeysRequested(string accountIdHex)
|
||||
|
||||
leftPadding: 12
|
||||
rightPadding: 12
|
||||
@ -62,7 +61,6 @@ ComboBox {
|
||||
width: root.popup ? (root.popup.width - root.popup.leftPadding - root.popup.rightPadding) : 368
|
||||
highlighted: root.highlightedIndex === index
|
||||
onCopyRequested: (text) => root.copyRequested(text)
|
||||
onCopyPublicKeysRequested: (id) => root.copyPublicKeysRequested(id)
|
||||
}
|
||||
|
||||
popup: Popup {
|
||||
|
||||
@ -13,8 +13,10 @@ ItemDelegate {
|
||||
// to backend.copyToClipboard(...) — AccountDelegate doesn't reach into
|
||||
// the global QML scope for `backend` since it now lives behind the
|
||||
// logos.module() bridge in the parent view.
|
||||
// Public accounts only — private accounts copy their group's NPK/VPK via
|
||||
// the section header instead, since an individual account ID isn't an
|
||||
// address you'd hand out (only the (npk, vpk) group identity is).
|
||||
signal copyRequested(string text)
|
||||
signal copyPublicKeysRequested(string accountIdHex)
|
||||
|
||||
leftPadding: Theme.spacing.medium
|
||||
rightPadding: Theme.spacing.medium
|
||||
@ -78,10 +80,8 @@ ItemDelegate {
|
||||
LogosCopyButton {
|
||||
Layout.preferredHeight: 40
|
||||
Layout.preferredWidth: 40
|
||||
onCopyText: model.isPublic
|
||||
? root.copyRequested(Base58.encode(model.accountId ?? ""))
|
||||
: root.copyPublicKeysRequested(model.accountId ?? "")
|
||||
visible: addressLabel.text
|
||||
onCopyText: root.copyRequested(Base58.encode(model.accountId ?? ""))
|
||||
visible: addressLabel.text && model.isPublic
|
||||
icon.color: Theme.palette.textMuted
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import Logos.Controls
|
||||
// TODO: remove relative paths and use qmldir instead
|
||||
import "../controls"
|
||||
import "../popups"
|
||||
import "../Base58.js" as Base58
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
@ -21,7 +22,6 @@ Rectangle {
|
||||
signal createPrivateAccountRequested()
|
||||
signal fetchBalancesRequested()
|
||||
signal copyRequested(string text)
|
||||
signal copyPublicKeysRequested(string accountIdHex)
|
||||
|
||||
radius: Theme.spacing.radiusXlarge
|
||||
color: Theme.palette.backgroundSecondary
|
||||
@ -63,7 +63,7 @@ Rectangle {
|
||||
// Sync progress
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.spacing.xsmall
|
||||
spacing: Theme.spacing.small
|
||||
visible: root.currentBlockHeight > 0 && root.lastSyncedBlock < root.currentBlockHeight
|
||||
|
||||
RowLayout {
|
||||
@ -127,10 +127,44 @@ Rectangle {
|
||||
spacing: Theme.spacing.small
|
||||
model: root.accountModel
|
||||
|
||||
delegate: AccountDelegate {
|
||||
// Each private account's "keysJson"/"sectionKey"/"isFirstInGroup" are plain
|
||||
// model roles (replicated like any other row data), so the group header for
|
||||
// the section a row starts is rendered inline here rather than via
|
||||
// ListView.section — section.delegate only gets the section's string value,
|
||||
// with no way back to that row's data once the model is a remote replica.
|
||||
delegate: ColumnLayout {
|
||||
width: listView.width
|
||||
onCopyRequested: (text) => root.copyRequested(text)
|
||||
onCopyPublicKeysRequested: (id) => root.copyPublicKeysRequested(id)
|
||||
spacing: Theme.spacing.small
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
visible: model.isFirstInGroup ?? false
|
||||
spacing: Theme.spacing.small
|
||||
|
||||
LogosText {
|
||||
text: model.isPublic
|
||||
? qsTr("Public Accounts")
|
||||
: qsTr("Private — %1").arg(Base58.encode(model.sectionKey ?? "").slice(0, 8))
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
font.bold: true
|
||||
color: Theme.palette.textSecondary
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
LogosCopyButton {
|
||||
Layout.preferredHeight: 32
|
||||
Layout.preferredWidth: 32
|
||||
visible: !model.isPublic
|
||||
icon.color: Theme.palette.textMuted
|
||||
onCopyText: root.copyRequested(model.keysJson ?? "")
|
||||
}
|
||||
}
|
||||
|
||||
AccountDelegate {
|
||||
Layout.fillWidth: true
|
||||
onCopyRequested: (text) => root.copyRequested(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -30,7 +30,6 @@ Rectangle {
|
||||
signal transferShieldedOwnedRequested(string fromAccountId, string toAccountId, string amount)
|
||||
signal transferDeshieldedRequested(string fromAccountId, string toAccountId, string amount)
|
||||
signal copyRequested(string copyText)
|
||||
signal copyPublicKeysRequested(string accountIdHex)
|
||||
|
||||
color: Theme.palette.background
|
||||
|
||||
@ -52,7 +51,6 @@ Rectangle {
|
||||
onCreatePrivateAccountRequested: root.createPrivateAccountRequested()
|
||||
onFetchBalancesRequested: root.fetchBalancesRequested()
|
||||
onCopyRequested: (text) => root.copyRequested(text)
|
||||
onCopyPublicKeysRequested: (id) => root.copyPublicKeysRequested(id)
|
||||
}
|
||||
|
||||
TransferPanel {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user