feat(market): portfolio row-style redesign + per-wallet scope

Replace the drag/resize dashboard grid on the Market tab's portfolio with
three full-width row styles — compact (single-line), detailed (two-line),
and value-hero — cycled with Left/Right arrows like the Overview layouts.
Rows live in an internal scroll region bounded to the portfolio's height
budget, so the section stays put as groups are added.

- pfDrawRow renders each group in the selected style, reusing the icon /
  value / DRGX / 24h / sparkline pieces; per-entry field toggles honored.
- portfolio_style setting (0/1/2) persisted; arrow-key switcher + toast.
- Portfolio groups gain a per-wallet scope: new groups default to the
  active wallet's identity hash; legacy groups (empty scope) stay visible
  in every wallet. Scope filter drives both the height budget and the rows.
- Remove the now-dead grid machinery (mktDrawCard, pfComputeGridLayout,
  PfCell, PfGridDrag, MktCardCtx + MktCtx grid fields) — net code drop.
- Full UI sweep: seed demo portfolio groups + wavy price history and add
  market-rows-{compact,detailed,featured} surfaces so the styles are
  captured per skin (snapshot/restore the real portfolio + market data).
- i18n: portfolio_style_{label,compact,detailed,featured} + 8 langs; CJK
  subset rebuilt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-09 19:03:07 -05:00
parent a09adb14fb
commit 5db7941fbc
16 changed files with 294 additions and 331 deletions

View File

@@ -172,6 +172,8 @@ bool Settings::load(const std::string& path)
if (idx >= 0 && idx < 9) balance_layout_ = legacyIds[idx];
}
}
loadScalar(j, "portfolio_style", portfolio_style_);
if (portfolio_style_ < 0 || portfolio_style_ > 2) portfolio_style_ = 0;
loadScalar(j, "scanline_enabled", scanline_enabled_);
if (j.contains("hidden_addresses") && j["hidden_addresses"].is_array()) {
hidden_addresses_.clear();
@@ -344,6 +346,7 @@ bool Settings::load(const std::string& path)
entry.showSparkline = e["show_sparkline"].get<bool>();
if (e.contains("sparkline_interval") && e["sparkline_interval"].is_number_integer())
entry.sparklineInterval = e["sparkline_interval"].get<int>();
entry.scope = e.value("scope", "");
if (e.contains("grid_col") && e["grid_col"].is_number_integer())
entry.gridCol = e["grid_col"].get<int>();
if (e.contains("grid_row") && e["grid_row"].is_number_integer())
@@ -418,6 +421,7 @@ bool Settings::save(const std::string& path)
j["ui_opacity"] = ui_opacity_;
j["window_opacity"] = window_opacity_;
j["balance_layout"] = balance_layout_; // saved as string ID
j["portfolio_style"] = portfolio_style_;
j["scanline_enabled"] = scanline_enabled_;
j["hidden_addresses"] = json::array();
for (const auto& addr : hidden_addresses_)
@@ -520,6 +524,7 @@ bool Settings::save(const std::string& path)
entry["show_24h"] = e.show24h;
entry["show_sparkline"] = e.showSparkline;
entry["sparkline_interval"] = e.sparklineInterval;
entry["scope"] = e.scope;
entry["grid_col"] = e.gridCol;
entry["grid_row"] = e.gridRow;
entry["grid_w"] = e.gridW;