diff --git a/src/services/network_refresh_service.cpp b/src/services/network_refresh_service.cpp index f8bdbd6..8e1c2da 100644 --- a/src/services/network_refresh_service.cpp +++ b/src/services/network_refresh_service.cpp @@ -2,6 +2,7 @@ #include "history_scan_policy.h" #include "rpc/rpc_busy_gate.h" #include "../util/logger.h" +#include "../util/i18n.h" // util::tr for the price-fetch-failed default #include #include @@ -1995,7 +1996,7 @@ void NetworkRefreshService::applyPriceRefreshFailure(WalletState& state, { state.market.price_loading = false; state.market.price_error = errorMessage.empty() - ? std::string("Price fetch failed") + ? std::string(util::tr("market_price_fetch_failed")) : errorMessage; } diff --git a/src/ui/windows/market_tab.cpp b/src/ui/windows/market_tab.cpp index 2197913..a62fc67 100644 --- a/src/ui/windows/market_tab.cpp +++ b/src/ui/windows/market_tab.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include namespace dragonx { @@ -1496,7 +1497,18 @@ static void mktDrawPriceHero(const MktCtx& cx) // ---- HERO PRICE (large, prominent) ---- ImFont* h3 = Type().h3(); std::string priceStr = FormatPrice(heroPrice); - ImU32 priceCol = Success(); + // Stale-price cue: the aggregate price is kept on a failed/old refresh (it's never cleared), so a + // green number can be hours out of date. Flag it amber when the last refresh failed or the + // aggregate is >=5 min old. Only when the hero IS the aggregate (an exchange pair has its own feed). + const bool haveFetch = market.last_fetch_time.time_since_epoch().count() != 0; + const long ageMin = haveFetch + ? (long)std::chrono::duration_cast( + std::chrono::steady_clock::now() - market.last_fetch_time).count() + : 0; + const bool usingAggregate = (heroPrice == market.price_usd); + const bool priceStale = usingAggregate && haveFetch && + (!market.price_error.empty() || ageMin >= 5); + ImU32 priceCol = priceStale ? Warning() : Success(); DrawTextShadow(dl, h3, h3->LegacySize, ImVec2(cx0, cy), priceCol, priceStr.c_str()); // Ticker label after price @@ -1519,6 +1531,29 @@ static void mktDrawPriceHero(const MktCtx& cx) dl->AddRectFilled(bMin, bMax, badgeBg, 4.0f * dp); dl->AddText(capFont, capFont->LegacySize, ImVec2(bMin.x + badgePadH, bMin.y + badgePadV), chgCol, buf); + // Stale "as of Nh ago" caption + hover tooltip, right of the change badge (cursor saved/restored + // so the custom draw-list layout below is undisturbed). + if (priceStale) { + char age[64]; + if (ageMin >= 60) snprintf(age, sizeof(age), TR("market_price_stale_hour"), (int)(ageMin / 60)); + else snprintf(age, sizeof(age), TR("market_price_stale_min"), (int)std::max(1L, ageMin)); + ImVec2 asz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, age); + float ax = bMax.x + Layout::spacingMd(); + float limit = cardMax.x - pad - 260.0f * dp; // reserve the trade pill + chart toggles + if (ax + asz.x < limit) { + dl->AddText(capFont, capFont->LegacySize, ImVec2(ax, bMin.y + badgePadV), Warning(), age); + ImVec2 sv = ImGui::GetCursorScreenPos(); + ImGui::SetCursorScreenPos(ImVec2(ax, bMin.y)); + ImGui::InvisibleButton("##PriceStale", ImVec2(asz.x, bMax.y - bMin.y)); + if (ImGui::IsItemHovered()) { + std::string tip = market.price_error.empty() + ? std::string(TR("market_price_fetch_failed")) : market.price_error; + material::Tooltip(TR("market_price_stale_tip"), tip.c_str()); + } + ImGui::SetCursorScreenPos(sv); + } + } + // (Divider is drawn in the chart block, below the interval buttons.) // ---- TRADE BUTTON (top-right of card) ---- diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index ba1061b..30fb009 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -2222,6 +2222,10 @@ void I18n::loadBuiltinEnglish() strings_["market_24h_change"] = "24h"; strings_["market_price_loading"] = "Loading price data..."; strings_["market_price_unavailable"] = "Price data unavailable"; + strings_["market_price_fetch_failed"] = "Price fetch failed"; + strings_["market_price_stale_min"] = "as of %d min ago"; + strings_["market_price_stale_hour"] = "as of %dh ago"; + strings_["market_price_stale_tip"] = "Showing the last known price. The latest refresh failed: %s"; strings_["market_refresh_price"] = "Refresh price data"; strings_["market_trade_on"] = "Trade on %s";