feat: modernize address list with drag-transfer, labels, and UX polish

- Rewrite RenderSharedAddressList with two-pass layout architecture
- Add drag-to-transfer: drag address onto another to open transfer dialog
- Add AddressLabelDialog with custom label text and 20-icon picker
- Add AddressTransferDialog with amount input, fee, and balance preview
- Add AddressMeta persistence (label, icon, sortOrder) in settings.json
- Gold favorite border inset 2dp from container edge
- Show hide button on all addresses, not just zero-balance
- Smaller star/hide buttons to clear favorite border
- Semi-transparent dragged row with context-aware tooltip
- Copy-to-clipboard deferred to mouse-up (no copy on drag)
- Themed colors via resolveColor() with CSS variable fallbacks
- Keyboard nav (Up/Down/J/K, Enter to copy, F2 to edit label)
- Add i18n keys for all new UI strings
This commit is contained in:
dan_s
2026-04-12 17:29:56 -05:00
parent 79d8f0d809
commit 9f23b2781c
10 changed files with 1026 additions and 174 deletions

View File

@@ -126,6 +126,19 @@ bool Settings::load(const std::string& path)
for (const auto& a : j["favorite_addresses"])
if (a.is_string()) favorite_addresses_.insert(a.get<std::string>());
}
if (j.contains("address_meta") && j["address_meta"].is_object()) {
address_meta_.clear();
for (auto& [addr, meta] : j["address_meta"].items()) {
AddressMeta m;
if (meta.contains("label") && meta["label"].is_string())
m.label = meta["label"].get<std::string>();
if (meta.contains("icon") && meta["icon"].is_string())
m.icon = meta["icon"].get<std::string>();
if (meta.contains("order") && meta["order"].is_number_integer())
m.sortOrder = meta["order"].get<int>();
address_meta_[addr] = m;
}
}
if (j.contains("wizard_completed")) wizard_completed_ = j["wizard_completed"].get<bool>();
if (j.contains("auto_lock_timeout")) auto_lock_timeout_ = j["auto_lock_timeout"].get<int>();
if (j.contains("unlock_duration")) unlock_duration_ = j["unlock_duration"].get<int>();
@@ -230,6 +243,18 @@ bool Settings::save(const std::string& path)
j["favorite_addresses"] = json::array();
for (const auto& addr : favorite_addresses_)
j["favorite_addresses"].push_back(addr);
{
json meta_obj = json::object();
for (const auto& [addr, m] : address_meta_) {
if (m.label.empty() && m.icon.empty() && m.sortOrder < 0) continue;
json entry = json::object();
if (!m.label.empty()) entry["label"] = m.label;
if (!m.icon.empty()) entry["icon"] = m.icon;
if (m.sortOrder >= 0) entry["order"] = m.sortOrder;
meta_obj[addr] = entry;
}
j["address_meta"] = meta_obj;
}
j["wizard_completed"] = wizard_completed_;
j["auto_lock_timeout"] = auto_lock_timeout_;
j["unlock_duration"] = unlock_duration_;