feat(market): loading spinner while the price chart fetches (pair switches)
Switching exchange pairs (or first load) briefly leaves the chart with no series while the venue's candles fetch, which showed the bare "No price history available" empty state. When a chart fetch is in flight — App::isMarketChartLoading() (the CoinGecko aggregate OR the per-exchange fetch) — draw a spinner + animated "Loading price history…" in the plot area instead; the "no history" text only shows when genuinely empty and idle. New i18n key market_chart_loading across all 8 languages. Verified via a forced-state render (spinner + label centered). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -339,6 +339,9 @@ public:
|
||||
// Market chart shows the real per-exchange price. Re-fetches on pair change; falls back to the
|
||||
// CoinGecko aggregate for unmapped venues / failed fetches. Safe to call every frame.
|
||||
void refreshExchangeChart();
|
||||
// True while a market price-history fetch is in flight (CoinGecko aggregate OR per-exchange). The
|
||||
// Market chart shows a loading indicator instead of the empty state during pair switches.
|
||||
bool isMarketChartLoading() const { return chart_fetch_in_flight_ || exchange_chart_fetch_in_flight_; }
|
||||
|
||||
/// @brief Per-category refresh intervals, adjusted by active tab
|
||||
using RefreshIntervals = services::NetworkRefreshService::Intervals;
|
||||
|
||||
@@ -1605,11 +1605,28 @@ static void mktDrawPriceChart(const MktCtx& cx)
|
||||
}
|
||||
} else {
|
||||
// Empty state, centered in the plot area (the interval strip + refresh are already drawn).
|
||||
const char* msg = TR("market_no_history");
|
||||
ImVec2 ts = sub1->CalcTextSizeA(sub1->LegacySize, FLT_MAX, 0, msg);
|
||||
dl->AddText(sub1, sub1->LegacySize,
|
||||
ImVec2(chartMin.x + (availWidth - ts.x) * 0.5f, (plotTop + plotBottom) * 0.5f - ts.y * 0.5f),
|
||||
OnSurfaceDisabled(), msg);
|
||||
const float dp = Layout::dpiScale();
|
||||
const float ecx = chartMin.x + availWidth * 0.5f;
|
||||
const float ecy = (plotTop + plotBottom) * 0.5f;
|
||||
if (app->isMarketChartLoading()) {
|
||||
// A fetch is in flight — e.g. just switched exchange pairs. Show a spinner + animated label
|
||||
// instead of "no history", since the data is on its way.
|
||||
const float r = 10.0f * dp;
|
||||
const float t = (float)ImGui::GetTime() * 4.5f; // rotate ~0.7 rev/s
|
||||
dl->PathArcTo(ImVec2(ecx, ecy - 8.0f * dp), r, t, t + IM_PI * 1.5f, 24);
|
||||
dl->PathStroke(OnSurfaceMedium(), false, 2.5f * dp);
|
||||
char lb[80];
|
||||
snprintf(lb, sizeof(lb), "%s%s", TR("market_chart_loading"), LoadingDots());
|
||||
ImVec2 ts = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, lb);
|
||||
dl->AddText(capFont, capFont->LegacySize,
|
||||
ImVec2(ecx - ts.x * 0.5f, ecy + 10.0f * dp), OnSurfaceMedium(), lb);
|
||||
} else {
|
||||
const char* msg = TR("market_no_history");
|
||||
ImVec2 ts = sub1->CalcTextSizeA(sub1->LegacySize, FLT_MAX, 0, msg);
|
||||
dl->AddText(sub1, sub1->LegacySize,
|
||||
ImVec2(chartMin.x + (availWidth - ts.x) * 0.5f, ecy - ts.y * 0.5f),
|
||||
OnSurfaceDisabled(), msg);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(chartMin.x, chartMin.y + chartH));
|
||||
|
||||
@@ -1347,6 +1347,7 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["market_attribution"] = "Price data from CoinGecko";
|
||||
strings_["market_btc_price"] = "BTC PRICE";
|
||||
strings_["market_no_history"] = "No price history available";
|
||||
strings_["market_chart_loading"] = "Loading price history";
|
||||
strings_["market_no_price"] = "No price data";
|
||||
strings_["market_now"] = "Now";
|
||||
strings_["market_pct_shielded"] = "%.0f%% Shielded";
|
||||
|
||||
Reference in New Issue
Block a user