fix(ui): show real data and consistent values across tabs
- Market chart now plots the real accumulated price_history instead of a rand()-generated curve; the hover tooltip no longer claims a specific "Xh ago" price and the x-axis only labels the truthful "Now" point. Falls back to the existing empty state until there are >=2 real samples. - Transactions summary cards exclude autoshield legs (same txid send + receive-to-z) so a shield isn't double-counted into both Sent and Received, matching the list. - Send/Receive sync banners use verification_progress like every other surface, instead of the blocks/headers ratio that over-reports during early sync. - Fix printf format/type mismatches: %.0f<-int (market % shielded), %d<-size_t (peer counts), %ld<-int64_t (peer byte counters, wrong on Windows). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -29,8 +29,7 @@ namespace ui {
|
||||
using namespace material;
|
||||
|
||||
// ---- Market tab persistent state ----
|
||||
static std::vector<double> s_price_history;
|
||||
static std::vector<double> s_time_history;
|
||||
static std::vector<double> s_price_history; // mirror of WalletState market.price_history
|
||||
static bool s_history_initialized = false;
|
||||
static double s_last_refresh_time = 0.0;
|
||||
|
||||
@@ -322,18 +321,11 @@ void RenderMarketTab(App* app)
|
||||
// PRICE CHART — Custom drawn inside glass panel (matches app design)
|
||||
// ================================================================
|
||||
{
|
||||
// Initialize history with simulated data if not set
|
||||
if (!s_history_initialized && market.price_usd > 0) {
|
||||
s_price_history.clear();
|
||||
s_time_history.clear();
|
||||
double base = market.price_usd;
|
||||
for (int i = 0; i < 24; i++) {
|
||||
double variance = ((rand() % 1000) - 500) / 10000.0 * base;
|
||||
s_price_history.push_back(base + variance);
|
||||
s_time_history.push_back(static_cast<double>(i));
|
||||
}
|
||||
s_history_initialized = true;
|
||||
}
|
||||
// Plot the REAL accumulated price history (one point per refresh, oldest→newest).
|
||||
// No synthetic data: until at least two real samples exist, the empty-state below
|
||||
// ("not enough history yet") is shown rather than inventing a price curve.
|
||||
s_price_history = market.price_history;
|
||||
s_history_initialized = true;
|
||||
|
||||
// Chart height from schema
|
||||
float chartH = std::max(60.0f, chartElem.height * vs);
|
||||
@@ -411,19 +403,14 @@ void RenderMarketTab(App* app)
|
||||
dl->AddCircleFilled(points[i], dotR, dotCol);
|
||||
}
|
||||
|
||||
// X-axis labels
|
||||
const int xlabels[] = {0, 6, 12, 18, 23};
|
||||
const char* xlabelText[] = {TR("market_24h"), TR("market_18h"), TR("market_12h"), TR("market_6h"), TR("market_now")};
|
||||
for (int xi = 0; xi < 5; xi++) {
|
||||
int idx = xlabels[xi];
|
||||
float t = (float)idx / (float)(n - 1);
|
||||
float x = plotLeft + t * plotW;
|
||||
ImVec2 lblSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, xlabelText[xi]);
|
||||
float lx = x - lblSz.x * 0.5f;
|
||||
if (lx < plotLeft) lx = plotLeft;
|
||||
if (lx + lblSz.x > plotRight) lx = plotRight - lblSz.x;
|
||||
// X-axis: samples are per-refresh, not hourly-timestamped, so only the most
|
||||
// recent point can be labelled truthfully ("Now"). The series simply runs
|
||||
// older→newer, left→right. (Avoids the old fake "24h…6h" hourly gradations.)
|
||||
{
|
||||
const char* nowText = TR("market_now");
|
||||
ImVec2 lblSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, nowText);
|
||||
dl->AddText(capFont, capFont->LegacySize,
|
||||
ImVec2(lx, plotBottom + 2), OnSurfaceDisabled(), xlabelText[xi]);
|
||||
ImVec2(plotRight - lblSz.x, plotBottom + 2), OnSurfaceDisabled(), nowText);
|
||||
}
|
||||
|
||||
// Hover crosshair + tooltip
|
||||
@@ -450,8 +437,9 @@ void RenderMarketTab(App* app)
|
||||
dl->AddCircleFilled(ImVec2(px, py), hoverDotR, dotCol);
|
||||
dl->AddCircle(ImVec2(px, py), hoverRingR, IM_COL32(255, 255, 255, 80), 0, 1.5f);
|
||||
|
||||
snprintf(buf, sizeof(buf), "%dh ago: %s",
|
||||
24 - idx, FormatPrice(s_price_history[idx]).c_str());
|
||||
// Samples are per-refresh, not timestamped, so we don't claim a specific
|
||||
// "Xh ago" — just show the price at the hovered point.
|
||||
snprintf(buf, sizeof(buf), "%s", FormatPrice(s_price_history[idx]).c_str());
|
||||
ImVec2 tipSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, buf);
|
||||
float tipPad = Layout::spacingSm() + Layout::spacingXs();
|
||||
float tipX = px + 10;
|
||||
@@ -843,8 +831,10 @@ void RenderMarketTab(App* app)
|
||||
WithAlpha(Warning(), 200),
|
||||
shieldedW > 0.5f ? ImDrawFlags_RoundCornersRight : ImDrawFlags_RoundCornersAll, 3.0f);
|
||||
|
||||
int pct = (int)(shieldedRatio * 100.0f + 0.5f);
|
||||
snprintf(buf, sizeof(buf), TR("market_pct_shielded"), pct);
|
||||
// market_pct_shielded is "%.0f%% Shielded" — pass a double, not an int (passing
|
||||
// an int to a %f conversion is UB and renders garbage on x86-64).
|
||||
snprintf(buf, sizeof(buf), TR("market_pct_shielded"),
|
||||
static_cast<double>(shieldedRatio) * 100.0);
|
||||
dl->AddText(capFont, capFont->LegacySize,
|
||||
ImVec2(cx, cy + barH + 2), OnSurfaceDisabled(), buf);
|
||||
}
|
||||
|
||||
@@ -507,8 +507,10 @@ void RenderPeersTab(App* app)
|
||||
float toggleY = ImGui::GetCursorScreenPos().y;
|
||||
{
|
||||
char connLabel[64], banLabel[64];
|
||||
snprintf(connLabel, sizeof(connLabel), TR("peers_connected_count"), state.peers.size());
|
||||
snprintf(banLabel, sizeof(banLabel), TR("peers_banned_count"), state.bannedPeers.size());
|
||||
// Format strings use %d — cast size_t down to int (a 64-bit size_t passed to %d
|
||||
// is a format/type mismatch / UB; peer counts comfortably fit in int).
|
||||
snprintf(connLabel, sizeof(connLabel), TR("peers_connected_count"), (int)state.peers.size());
|
||||
snprintf(banLabel, sizeof(banLabel), TR("peers_banned_count"), (int)state.bannedPeers.size());
|
||||
|
||||
ImVec2 connSz = body2->CalcTextSizeA(body2->LegacySize, FLT_MAX, 0, connLabel);
|
||||
ImVec2 banSz = body2->CalcTextSizeA(body2->LegacySize, FLT_MAX, 0, banLabel);
|
||||
@@ -822,9 +824,10 @@ void RenderPeersTab(App* app)
|
||||
TTRow(TR("peers_tt_services"), peer.services.c_str());
|
||||
snprintf(ttBuf, sizeof(ttBuf), "%d", peer.startingheight);
|
||||
TTRow(TR("peers_tt_start_height"), ttBuf);
|
||||
snprintf(ttBuf, sizeof(ttBuf), "%ld bytes", peer.bytessent);
|
||||
// %lld + (long long) — %ld truncates int64_t on Windows (LLP64).
|
||||
snprintf(ttBuf, sizeof(ttBuf), "%lld bytes", (long long)peer.bytessent);
|
||||
TTRow(TR("peers_tt_sent"), ttBuf);
|
||||
snprintf(ttBuf, sizeof(ttBuf), "%ld bytes", peer.bytesrecv);
|
||||
snprintf(ttBuf, sizeof(ttBuf), "%lld bytes", (long long)peer.bytesrecv);
|
||||
TTRow(TR("peers_tt_received"), ttBuf);
|
||||
snprintf(ttBuf, sizeof(ttBuf), "%d / %d", peer.synced_headers, peer.synced_blocks);
|
||||
TTRow(TR("peers_tt_synced"), ttBuf);
|
||||
|
||||
@@ -106,8 +106,11 @@ static void TrackNewAddresses(const WalletState& state) {
|
||||
static void RenderSyncBanner(const WalletState& state) {
|
||||
if (!state.sync.syncing || state.sync.isSynced()) return;
|
||||
|
||||
float syncPct = (state.sync.headers > 0)
|
||||
? (float)state.sync.blocks / state.sync.headers * 100.0f : 0.0f;
|
||||
// Use the work-weighted verification_progress like every other surface (not the naive
|
||||
// blocks/headers ratio, which over-reports during early sync).
|
||||
double vp = state.sync.verification_progress;
|
||||
if (vp < 0.0) vp = 0.0; else if (vp > 1.0) vp = 1.0;
|
||||
float syncPct = (float)(vp * 100.0);
|
||||
char syncBuf[128];
|
||||
snprintf(syncBuf, sizeof(syncBuf),
|
||||
TR("blockchain_syncing"), syncPct);
|
||||
|
||||
@@ -191,8 +191,12 @@ static void PasteClipboardToAddress() {
|
||||
static void RenderSyncBanner(const WalletState& state) {
|
||||
if (!state.sync.syncing || state.sync.isSynced()) return;
|
||||
|
||||
float syncPct = (state.sync.headers > 0)
|
||||
? (float)state.sync.blocks / state.sync.headers * 100.0f : 0.0f;
|
||||
// Use the work-weighted verification_progress like every other surface (status bar,
|
||||
// balance, explorer, network, mining). The naive blocks/headers ratio over-reports
|
||||
// during early sync — misleading on the very screen where the user is about to spend.
|
||||
double vp = state.sync.verification_progress;
|
||||
if (vp < 0.0) vp = 0.0; else if (vp > 1.0) vp = 1.0;
|
||||
float syncPct = (float)(vp * 100.0);
|
||||
char syncBuf[128];
|
||||
snprintf(syncBuf, sizeof(syncBuf),
|
||||
TR("blockchain_syncing"), syncPct);
|
||||
|
||||
@@ -164,7 +164,30 @@ void RenderTransactionsTab(App* app)
|
||||
int recvCount = 0, sendCount = 0, minedCount = 0;
|
||||
double recvTotal = 0.0, sendTotal = 0.0, minedTotal = 0.0;
|
||||
|
||||
for (const auto& tx : state.transactions) {
|
||||
// Identify autoshield legs (same txid with a "send" leg and a "receive"-to-z leg):
|
||||
// that pair is a single internal shielding move — shown as one "Shield" row in the
|
||||
// list below — not income or spending. Counting both legs double-counts the amount
|
||||
// into BOTH the Sent and Received totals, so the cards disagree with the list.
|
||||
// Mirror the list's pairing logic and exclude both legs from the totals.
|
||||
std::unordered_map<std::string, std::vector<size_t>> summaryTxidMap;
|
||||
for (size_t i = 0; i < state.transactions.size(); i++)
|
||||
summaryTxidMap[state.transactions[i].txid].push_back(i);
|
||||
std::vector<bool> isShieldLeg(state.transactions.size(), false);
|
||||
for (const auto& kv : summaryTxidMap) {
|
||||
if (kv.second.size() < 2) continue;
|
||||
int send_i = -1, recv_i = -1;
|
||||
for (size_t si : kv.second) {
|
||||
const auto& stx = state.transactions[si];
|
||||
if (stx.type == "send" && send_i < 0) send_i = (int)si;
|
||||
else if (stx.type == "receive" && recv_i < 0 &&
|
||||
!stx.address.empty() && stx.address[0] == 'z') recv_i = (int)si;
|
||||
}
|
||||
if (send_i >= 0 && recv_i >= 0) { isShieldLeg[send_i] = true; isShieldLeg[recv_i] = true; }
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < state.transactions.size(); i++) {
|
||||
if (isShieldLeg[i]) continue; // internal shielding move — not received or sent
|
||||
const auto& tx = state.transactions[i];
|
||||
if (tx.type == "receive") {
|
||||
recvCount++;
|
||||
recvTotal += std::abs(tx.amount);
|
||||
|
||||
Reference in New Issue
Block a user