feat: improve private account listing

This commit is contained in:
Sergio Chouhy 2026-07-24 20:51:16 -03:00
parent 275415f34c
commit 97bae8ad1f
3 changed files with 117 additions and 10 deletions

View File

@ -31,6 +31,7 @@ QVariant LEZWalletAccountModel::data(const QModelIndex& index, int role) const
case SectionKeyRole: return e.sectionKey;
case KeysJsonRole: return e.keysJson;
case IsFirstInGroupRole: return e.isFirstInGroup;
case IsFirstPrivateRole: return e.isFirstPrivate;
case IsInitializedRole: return e.isInitialized;
default: return QVariant();
}
@ -47,6 +48,7 @@ QHash<int, QByteArray> LEZWalletAccountModel::roleNames() const
{ SectionKeyRole, "sectionKey" },
{ KeysJsonRole, "keysJson" },
{ IsFirstInGroupRole, "isFirstInGroup" },
{ IsFirstPrivateRole, "isFirstPrivate" },
{ IsInitializedRole, "isInitialized" }
};
}
@ -100,8 +102,13 @@ void LEZWalletAccountModel::replaceFromVariantList(const QVariantList& list)
if (a.isPublic != b.isPublic) return a.isPublic;
return a.sectionKey < b.sectionKey;
});
for (int i = 0; i < m_entries.size(); ++i)
for (int i = 0; i < m_entries.size(); ++i) {
m_entries[i].isFirstInGroup = (i == 0) || (m_entries[i].sectionKey != m_entries[i - 1].sectionKey);
// All private key-groups sit under a single "Private" title (unlike the public
// section, they don't each get their own top-level header) — so only the very
// first private row across all groups needs to flag it.
m_entries[i].isFirstPrivate = !m_entries[i].isPublic && (i == 0 || m_entries[i - 1].isPublic);
}
endResetModel();
if (oldCount != m_entries.size())
emit countChanged();

View File

@ -18,7 +18,8 @@ struct LEZWalletAccountEntry {
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
bool isFirstInGroup = false; // QML renders the per-key-set header (copy button) above rows where this is true
bool isFirstPrivate = false; // QML renders the single "Private" section title above rows where this is true
// Whether some program (in practice, the authenticated-transfer program) has claimed
// this account yet. Defaults to false (shown as needing init) so an account whose
// state we failed to enrich isn't silently mistaken for a usable one.
@ -42,6 +43,7 @@ public:
SectionKeyRole,
KeysJsonRole,
IsFirstInGroupRole,
IsFirstPrivateRole,
IsInitializedRole
};
Q_ENUM(Role)

View File

@ -149,26 +149,124 @@ Rectangle {
// row is created.
property string keysJsonWarm: model.keysJson ?? ""
// "Public Accounts" title: the public section is a single group, so this
// is equivalent to showing it once above the first public row.
RowLayout {
Layout.fillWidth: true
visible: model.isFirstInGroup ?? false
visible: model.isPublic && (model.isFirstInGroup ?? false)
spacing: Theme.spacing.small
LogosText {
text: model.isPublic
? qsTr("Public Accounts")
: qsTr("Private")
font.pixelSize: Theme.typography.secondaryText
text: qsTr("Public Accounts")
font.pixelSize: Theme.typography.primaryText
font.bold: true
color: Theme.palette.textSecondary
color: Theme.palette.text
}
}
// "Private Accounts" title: shown once above the whole private section,
// unlike the per-key-set row below which repeats for every private key
// group. Wrapped the same way as the "Public Accounts" title above so
// both line up identically.
RowLayout {
Layout.fillWidth: true
visible: model.isFirstPrivate ?? false
spacing: Theme.spacing.small
LogosText {
text: qsTr("Private Accounts")
font.pixelSize: Theme.typography.primaryText
font.bold: true
color: Theme.palette.text
}
}
// Per-key-set row: separates each private key group within the Private
// section, naming the group by its Npk/Vpk pair, and holds the copy
// button for that pair.
RowLayout {
id: keyGroupHeader
Layout.fillWidth: true
visible: !model.isPublic && (model.isFirstInGroup ?? false)
spacing: Theme.spacing.small
property var groupKeys: {
try { return JSON.parse(model.keysJson ?? "{}") } catch (e) { return {} }
}
Item { Layout.fillWidth: true }
function shortKey(key) {
return key && key.length > 12 ? key.slice(0, 6) + "…" + key.slice(-4) : (key || "")
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
LogosText {
Layout.fillWidth: true
text: qsTr("Accounts under keys")
font.pixelSize: Theme.typography.secondaryText
color: Theme.palette.textSecondary
}
// Each of Npk/Vpk gets its own bullet, aligned with "Private
// Accounts"/"Accounts under keys" above. Labels share a fixed
// width (the wider of the two) so the value column still lines
// up between the two rows.
RowLayout {
Layout.fillWidth: true
spacing: Theme.spacing.small
LogosText {
text: "•"
font.pixelSize: Theme.typography.secondaryText
color: Theme.palette.textSecondary
}
LogosText {
id: npkLabel
Layout.preferredWidth: Math.max(npkLabel.implicitWidth, vpkLabel.implicitWidth)
text: qsTr("Npk:")
font.pixelSize: Theme.typography.secondaryText
color: Theme.palette.textSecondary
}
LogosText {
Layout.fillWidth: true
text: keyGroupHeader.shortKey(keyGroupHeader.groupKeys.nullifier_public_key)
font.pixelSize: Theme.typography.secondaryText
color: Theme.palette.textSecondary
elide: Text.ElideRight
}
}
RowLayout {
Layout.fillWidth: true
spacing: Theme.spacing.small
LogosText {
text: "•"
font.pixelSize: Theme.typography.secondaryText
color: Theme.palette.textSecondary
}
LogosText {
id: vpkLabel
Layout.preferredWidth: Math.max(npkLabel.implicitWidth, vpkLabel.implicitWidth)
text: qsTr("Vpk:")
font.pixelSize: Theme.typography.secondaryText
color: Theme.palette.textSecondary
}
LogosText {
Layout.fillWidth: true
text: keyGroupHeader.shortKey(keyGroupHeader.groupKeys.viewing_public_key)
font.pixelSize: Theme.typography.secondaryText
color: Theme.palette.textSecondary
elide: Text.ElideRight
}
}
}
LogosCopyButton {
Layout.preferredHeight: 32
Layout.preferredWidth: 32
visible: !model.isPublic
icon.color: Theme.palette.textMuted
onCopyText: root.copyRequested(model.keysJson ?? "")
}