refactor(audit): batch 4 — UI design-system helpers + i18n

Mechanical, behavior-preserving consolidations from the audit. Rendering
extractions were kept byte-equivalent; sites that couldn't be made identical
were left as-is (noted below).

Shared helpers:
- util::truncateMiddle(s,maxLen) / (s,front,back) in text_format.h — replaces
  6 file-local middle-ellipsis truncators + several inline substr sites
  (send/receive/transactions/balance_recent_tx/explorer + app.cpp + 3 dialogs).
  Carries the maxLen<=3 guard, fixing the latent unsigned-underflow copies.
- material::LoadingDots() in draw_helpers.h — one animated-ellipsis source for
  8 copy-pasted spinner sites (identical GetTime()*3 phase preserved).
- Reuse the existing FormatHashrate() in explorer_tab + peers_tab (dropped two
  inline hashrate ladders).
- material::DrawButtonGlassOverlay() — the glass-fill/rim/tactile overlay block
  shared by TactileButton / TactileSmallButton / schema TactileButton.
- material::CollapsibleHeader() — the invisible-button + label + chevron idiom
  (3 of 5 settings_page sites; RPC/Debug headers skipped — non-identical).
- material::GlassCardScope (RAII) — the ChannelsSplit/Indent/DrawGlassPanel/
  ChannelsMerge card scaffold (5 settings_page cards; About/send/receive skipped
  — different padding / logo interleaving).
- material::DialogWarningHeader()/DialogConfirmFooter() — warning header +
  50/50 Cancel/danger footer across the confirm dialogs; button height moved
  from a hardcoded 40px into ui.toml (components.overlay-dialog.confirm-btn-height).
- File-local enterLowSpec()/exitLowSpec() collapse the 3 low-spec snapshot copies.

i18n: wrapped hardcoded English in the shared balance render paths and the
whole Lite lifecycle/security section in TR(), with English defaults added to
loadBuiltinEnglish() (res/lang/*.json left for the translation tooling — TR
falls back to English, so output is unchanged).

Full-node + Lite build clean; ctest 1/1; hygiene clean. These are rendering
changes — the Settings page (cards/headers/confirm dialogs), all tactile
buttons, and the Lite settings section warrant a screenshot check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-02 17:29:12 -05:00
parent 280a71e973
commit de11850c74
21 changed files with 452 additions and 500 deletions

View File

@@ -6,6 +6,7 @@
#include "../../app.h"
#include "../../data/address_book.h"
#include "../../util/i18n.h"
#include "../../util/text_format.h"
#include "../notifications.h"
#include "../schema/ui_schema.h"
#include "../material/draw_helpers.h"
@@ -253,8 +254,7 @@ void AddressBookDialog::render(App* app)
std::string addr_display = entry.address;
int addrTruncLen = (addrTable.columns.count("address") && addrTable.columns.at("address").truncate > 0) ? addrTable.columns.at("address").truncate : 40;
if (addr_display.length() > static_cast<size_t>(addrTruncLen)) {
addr_display = addr_display.substr(0, addrFrontLbl.truncate) + "..." +
addr_display.substr(addr_display.length() - addrBackLbl.truncate);
addr_display = util::truncateMiddle(addr_display, addrFrontLbl.truncate, addrBackLbl.truncate);
}
ImGui::TextDisabled("%s", addr_display.c_str());
if (ImGui::IsItemHovered()) {

View File

@@ -11,6 +11,7 @@
#include <cstddef>
#include "../../app.h"
#include "../../util/i18n.h"
#include "../../util/text_format.h"
#include "../material/draw_helpers.h"
#include "../material/project_icons.h"
#include "../theme.h"
@@ -69,7 +70,7 @@ public:
ImGui::Spacing();
{
std::string display = s_address;
if (display.size() > 42) display = display.substr(0, 20) + "..." + display.substr(display.size() - 16);
if (display.size() > 42) display = util::truncateMiddle(display, 20, 16);
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), display.c_str());
}
ImGui::Spacing();

View File

@@ -200,10 +200,8 @@ void RenderSharedAddressList(App* app, float listH, float availW,
bool sharedAddrSyncing = state.sync.syncing && !state.sync.isSynced();
ImGui::BeginDisabled(sharedAddrSyncing);
if (s_generating_z_address) {
int dots = ((int)(ImGui::GetTime() * 3.0f)) % 4;
const char* dotStr[] = {"", ".", "..", "..."};
char genLabel[64];
snprintf(genLabel, sizeof(genLabel), "%s%s##shared_z", TR("generating"), dotStr[dots]);
snprintf(genLabel, sizeof(genLabel), "%s%s##shared_z", TR("generating"), material::LoadingDots());
TactileButton(genLabel, ImVec2(buttonWidth, 0),
S.resolveFont(addrBtn.font.empty() ? "button" : addrBtn.font));
} else if (TactileButton(TR("new_z_address"), ImVec2(buttonWidth, 0),
@@ -774,9 +772,9 @@ void RenderSharedRecentTx(App* app, float recentH, float availW, float hs, float
const auto& state = app->state();
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "RECENT TRANSACTIONS");
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("recent_transactions"));
ImGui::SameLine();
if (TactileSmallButton("View All", S.resolveFont(actionBtn.font.empty() ? "button" : actionBtn.font))) {
if (TactileSmallButton(TR("view_all"), S.resolveFont(actionBtn.font.empty() ? "button" : actionBtn.font))) {
app->setCurrentPage(NavPage::History);
}
ImGui::Spacing();
@@ -791,7 +789,7 @@ void RenderSharedRecentTx(App* app, float recentH, float availW, float hs, float
int count = (int)txs.size();
if (count == 0) {
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), "No transactions yet");
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("no_transactions_yet"));
} else {
ImDrawList* dl = ImGui::GetWindowDrawList();
ImFont* capFont = Type().caption();

View File

@@ -10,13 +10,6 @@ namespace dragonx {
namespace ui {
namespace {
std::string truncateRecentAddress(const std::string& address, int maxLen)
{
if (maxLen <= 3 || address.length() <= static_cast<size_t>(maxLen)) return address;
int half = (maxLen - 3) / 2;
return address.substr(0, half) + "..." + address.substr(address.length() - half);
}
std::string formatRecentAmount(const std::string& type, double amount)
{
char buffer[32];
@@ -36,7 +29,7 @@ RecentTxDisplay buildRecentTxDisplay(const TransactionInfo& tx, int addressMaxLe
{
return {
tx.getTypeDisplay(),
truncateRecentAddress(tx.address, addressMaxLen),
util::truncateMiddle(tx.address, addressMaxLen),
formatRecentAmount(tx.type, tx.amount),
recentTimeAgo(tx.timestamp)
};

View File

@@ -733,10 +733,8 @@ static void RenderBalanceClassic(App* app)
bool addrSyncing = state.sync.syncing && !state.sync.isSynced();
ImGui::BeginDisabled(addrSyncing);
if (s_generating_z_address) {
int dots = ((int)(ImGui::GetTime() * 3.0f)) % 4;
const char* dotStr[] = {"", ".", "..", "..."};
char genLabel[64];
snprintf(genLabel, sizeof(genLabel), "%s%s##bal_z", TR("generating"), dotStr[dots]);
snprintf(genLabel, sizeof(genLabel), "%s%s##bal_z", TR("generating"), material::LoadingDots());
TactileButton(genLabel, ImVec2(buttonWidth, 0), S.resolveFont(addrBtn.font.empty() ? "button" : addrBtn.font));
} else if (TactileButton(TR("new_z_address"), ImVec2(buttonWidth, 0), S.resolveFont(addrBtn.font.empty() ? "button" : addrBtn.font))) {
s_generating_z_address = true;
@@ -2189,9 +2187,9 @@ static void RenderBalanceShield(App* app) {
// Label below gauge
ImFont* capFont = Type().caption();
const char* statusMsg;
if (privPct >= 80.0f) statusMsg = "Great privacy!";
else if (privPct >= 50.0f) statusMsg = "Consider shielding more";
else statusMsg = "Low privacy — shield funds";
if (privPct >= 80.0f) statusMsg = TR("privacy_great");
else if (privPct >= 50.0f) statusMsg = TR("privacy_medium");
else statusMsg = TR("privacy_low");
ImVec2 msgSz = capFont->CalcTextSizeA(capFont->LegacySize, 1000, 0, statusMsg);
dl->AddText(capFont, capFont->LegacySize,
ImVec2(gaugeCx - msgSz.x * 0.5f, gaugeCy + 4 * dp),
@@ -2307,7 +2305,7 @@ static void RenderBalanceTimeline(App* app) {
DrawGlassPanel(dl, chartMin, chartMax, spec);
ImFont* capFont = Type().caption();
const char* msg = "Balance historycollecting data...";
const char* msg = TR("balance_history_collecting");
ImVec2 msgSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, msg);
dl->AddText(capFont, capFont->LegacySize,
ImVec2(chartMin.x + (availW - msgSz.x) * 0.5f,
@@ -2629,10 +2627,10 @@ static void RenderBalanceMinimal(App* app) {
ImGui::Dummy(heroSz);
// Line 2: Shielded + Transparent
snprintf(buf, sizeof(buf), "Shielded: %.8f", s_dispShielded);
snprintf(buf, sizeof(buf), TR("balance_shielded_fmt"), s_dispShielded);
Type().textColored(TypeStyle::Caption, Success(), buf);
ImGui::SameLine(0, Layout::spacingLg());
snprintf(buf, sizeof(buf), "Transparent: %.8f", s_dispTransparent);
snprintf(buf, sizeof(buf), TR("balance_transparent_fmt"), s_dispTransparent);
Type().textColored(TypeStyle::Caption, Warning(), buf);
// USD value

View File

@@ -36,9 +36,7 @@ std::string timeAgo(int64_t timestamp)
std::string truncateAddress(const std::string& address, int maxLen)
{
if (maxLen <= 3 || address.length() <= static_cast<size_t>(maxLen)) return address;
int half = (maxLen - 3) / 2;
return address.substr(0, half) + "..." + address.substr(address.length() - half);
return util::truncateMiddle(address, maxLen);
}
ImU32 recentTxIconColor(const std::string& type)

View File

@@ -7,6 +7,7 @@
#include "../../rpc/rpc_client.h"
#include "../../rpc/rpc_worker.h"
#include "../../util/i18n.h"
#include "../../util/text_format.h"
#include "../notifications.h"
#include "../schema/ui_schema.h"
#include "../material/draw_helpers.h"
@@ -251,7 +252,7 @@ void BlockInfoDialog::render(App* app)
// Truncate for display
std::string prev_short = s_prev_hash;
if (prev_short.length() > static_cast<size_t>(hashLbl.truncate)) {
prev_short = prev_short.substr(0, hashFrontLbl.truncate) + "..." + prev_short.substr(prev_short.length() - hashBackLbl.truncate);
prev_short = util::truncateMiddle(prev_short, hashFrontLbl.truncate, hashBackLbl.truncate);
}
ImGui::Text("%s", prev_short.c_str());
ImGui::PopStyleColor();
@@ -273,7 +274,7 @@ void BlockInfoDialog::render(App* app)
// Truncate for display
std::string next_short = s_next_hash;
if (next_short.length() > static_cast<size_t>(hashLbl.truncate)) {
next_short = next_short.substr(0, hashFrontLbl.truncate) + "..." + next_short.substr(next_short.length() - hashBackLbl.truncate);
next_short = util::truncateMiddle(next_short, hashFrontLbl.truncate, hashBackLbl.truncate);
}
ImGui::Text("%s", next_short.c_str());
ImGui::PopStyleColor();

View File

@@ -8,6 +8,8 @@
#include "../../rpc/rpc_worker.h"
#include "../../data/wallet_state.h"
#include "../../util/i18n.h"
#include "../../util/text_format.h"
#include "mining_tab_helpers.h"
#include "../explorer/explorer_block_cache.h"
#include "../schema/ui_schema.h"
#include "../material/type.h"
@@ -117,9 +119,7 @@ static const char* relativeTime(int64_t timestamp) {
}
static std::string truncateHash(const std::string& hash, int front, int back) {
if (hash.length() <= static_cast<size_t>(front + back + 3))
return hash;
return hash.substr(0, front) + "..." + hash.substr(hash.length() - back);
return util::truncateMiddle(hash, front, back);
}
// Adaptive hash truncation based on available pixel width
@@ -176,21 +176,6 @@ static std::string formatSize(int bytes) {
return b;
}
static std::string formatHashrate(double hashrate) {
char b[32];
if (hashrate >= 1e12)
snprintf(b, sizeof(b), "%.2f TH/s", hashrate / 1e12);
else if (hashrate >= 1e9)
snprintf(b, sizeof(b), "%.2f GH/s", hashrate / 1e9);
else if (hashrate >= 1e6)
snprintf(b, sizeof(b), "%.2f MH/s", hashrate / 1e6);
else if (hashrate >= 1e3)
snprintf(b, sizeof(b), "%.2f KH/s", hashrate / 1e3);
else
snprintf(b, sizeof(b), "%.0f H/s", hashrate);
return b;
}
// Copy icon button — draws a small icon that copies text to clipboard on click
static void copyButton(const char* id, const std::string& text, float x, float y) {
ImFont* iconFont = Type().iconSmall();
@@ -698,7 +683,7 @@ static void renderChainStats(App* app, float availWidth) {
MetricSpec metrics[4] = {
{ ICON_MD_SPEED, TR("difficulty"), difficulty, "", Warning() },
{ ICON_MD_SHOW_CHART, TR("peers_hashrate"), formatHashrate(state.mining.networkHashrate), "", Success() },
{ ICON_MD_SHOW_CHART, TR("peers_hashrate"), FormatHashrate(state.mining.networkHashrate), "", Success() },
{ ICON_MD_CONFIRMATION_NUMBER, TR("peers_notarized"), notarized, "", Primary() },
{ ICON_MD_STORAGE, TR("explorer_mempool"), mempoolTxs, s_mempool_loading ? std::string("") : formatSize(static_cast<int>(s_mempool_size)), Secondary() },
};
@@ -1030,10 +1015,8 @@ static void renderBlockDetailModal(App* app) {
// Show a loading modal while fetching
if (BeginOverlayDialog(TR("loading"), &s_detail_loading, 300.0f, 0.85f)) {
ImGui::Spacing();
int dots = ((int)(ImGui::GetTime() * 3.0f)) % 4;
const char* dotStr[] = {"", ".", "..", "..."};
char loadBuf[64];
snprintf(loadBuf, sizeof(loadBuf), "%s%s", TR("loading"), dotStr[dots]);
snprintf(loadBuf, sizeof(loadBuf), "%s%s", TR("loading"), material::LoadingDots());
ImGui::TextDisabled("%s", loadBuf);
ImGui::Spacing();
EndOverlayDialog();
@@ -1091,9 +1074,7 @@ static void renderBlockDetailModal(App* app) {
// Show loading state inside the modal when navigating between blocks
if (s_detail_loading) {
int dots = ((int)(ImGui::GetTime() * 3.0f)) % 4;
const char* dotStr[] = {"", ".", "..", "..."};
snprintf(buf, sizeof(buf), "%s%s", TR("loading"), dotStr[dots]);
snprintf(buf, sizeof(buf), "%s%s", TR("loading"), material::LoadingDots());
ImGui::TextDisabled("%s", buf);
EndOverlayDialog();
return;

View File

@@ -6,6 +6,7 @@
#include "../../app.h"
#include "../../data/wallet_state.h"
#include "../../util/i18n.h"
#include "mining_tab_helpers.h"
#include "../theme.h"
#include "../effects/imgui_acrylic.h"
#include "../effects/low_spec.h"
@@ -218,17 +219,8 @@ void RenderPeersTab(App* app)
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), TR("peers_hashrate"));
float valY = ry + capFont->LegacySize + Layout::spacingXs();
if (mining.networkHashrate > 0) {
if (mining.networkHashrate >= 1e12)
snprintf(buf, sizeof(buf), "%.2f TH/s", mining.networkHashrate / 1e12);
else if (mining.networkHashrate >= 1e9)
snprintf(buf, sizeof(buf), "%.2f GH/s", mining.networkHashrate / 1e9);
else if (mining.networkHashrate >= 1e6)
snprintf(buf, sizeof(buf), "%.2f MH/s", mining.networkHashrate / 1e6);
else if (mining.networkHashrate >= 1e3)
snprintf(buf, sizeof(buf), "%.2f KH/s", mining.networkHashrate / 1e3);
else
snprintf(buf, sizeof(buf), "%.2f H/s", mining.networkHashrate);
dl->AddText(sub1, sub1->LegacySize, ImVec2(cx, valY), Success(), buf);
std::string hr = FormatHashrate(mining.networkHashrate);
dl->AddText(sub1, sub1->LegacySize, ImVec2(cx, valY), Success(), hr.c_str());
} else {
dl->AddText(sub1, sub1->LegacySize, ImVec2(cx, valY), OnSurfaceDisabled(), "\xE2\x80\x94");
}

View File

@@ -13,6 +13,7 @@
#include "../../config/settings.h"
#include "../../util/i18n.h"
#include "../../util/platform.h"
#include "../../util/text_format.h"
#include "../../config/version.h"
#include "../../data/wallet_state.h"
#include "../../ui/widgets/qr_code.h"
@@ -71,14 +72,6 @@ static bool s_generating_address = false;
// ============================================================================
// Helpers
// ============================================================================
static std::string TruncateAddress(const std::string& addr, size_t maxLen = 40) {
// Guard maxLen <= 3 before the unsigned (maxLen - 3): otherwise it wraps and the
// trailing substr(length - halfLen) throws std::out_of_range on short inputs.
if (maxLen <= 3 || addr.length() <= maxLen) return addr;
size_t halfLen = (maxLen - 3) / 2;
return addr.substr(0, halfLen) + "..." + addr.substr(addr.length() - halfLen);
}
static void OpenExplorerURL(App* app, const std::string& address) {
std::string url = app->settings()->getAddressExplorerUrl() + address;
dragonx::util::Platform::openUrl(url);
@@ -201,8 +194,8 @@ static void RenderAddressDropdown(App* app, float width) {
const auto& addr = state.addresses[s_selected_address_idx];
bool isZ = addr.type == "shielded";
const char* tag = isZ ? "[Z]" : "[T]";
std::string trunc = TruncateAddress(addr.address,
static_cast<size_t>(std::max(schema::UI().drawElement("tabs.receive", "addr-preview-trunc-min").size, width / schema::UI().drawElement("tabs.receive", "addr-preview-trunc-divisor").size)));
std::string trunc = util::truncateMiddle(addr.address,
static_cast<int>(std::max(schema::UI().drawElement("tabs.receive", "addr-preview-trunc-min").size, width / schema::UI().drawElement("tabs.receive", "addr-preview-trunc-divisor").size)));
snprintf(buf, sizeof(buf), "%s %s \xe2\x80\x94 %.8f %s",
tag, trunc.c_str(), addr.balance, DRAGONX_TICKER);
s_source_preview = buf;
@@ -258,7 +251,7 @@ static void RenderAddressDropdown(App* app, float width) {
auto lblIt = s_address_labels.find(addr.address);
bool hasLabel = (lblIt != s_address_labels.end() && !lblIt->second.empty());
std::string trunc = TruncateAddress(addr.address, addrTruncLen);
std::string trunc = util::truncateMiddle(addr.address, (int)addrTruncLen);
if (hasLabel) {
snprintf(buf, sizeof(buf), "%s %s (%s) \xe2\x80\x94 %.8f %s%s",
tag, lblIt->second.c_str(), trunc.c_str(),
@@ -304,10 +297,8 @@ static void RenderAddressDropdown(App* app, float width) {
ImGui::SameLine(0, Layout::spacingSm());
ImGui::BeginDisabled(!app->isConnected() || s_generating_address);
if (s_generating_address) {
int dots = ((int)(ImGui::GetTime() * 3.0f)) % 4;
const char* dotStr[] = {"", ".", "..", "..."};
char genLabel[64];
snprintf(genLabel, sizeof(genLabel), "%s%s##recv", TR("generating"), dotStr[dots]);
snprintf(genLabel, sizeof(genLabel), "%s%s##recv", TR("generating"), material::LoadingDots());
TactileButton(genLabel, ImVec2(newBtnW, 0), schema::UI().resolveFont("button"));
} else if (TactileButton(TrId("new", "recv").c_str(), ImVec2(newBtnW, 0), schema::UI().resolveFont("button"))) {
s_generating_address = true;
@@ -418,8 +409,8 @@ static void RenderRecentReceived(const AddressInfo& /* addr */,
// Address (second line)
float addrX = txX + S.drawElement("tabs.balance", "recent-tx-addr-offset").sizeOr(65.0f);
std::string addrDisplay = TruncateAddress(tx.address,
(size_t)S.drawElement("tabs.balance", "recent-tx-addr-trunc").sizeOr(20.0f));
std::string addrDisplay = util::truncateMiddle(tx.address,
(int)S.drawElement("tabs.balance", "recent-tx-addr-trunc").sizeOr(20.0f));
rowDL->AddText(capFont, capFont->LegacySize,
ImVec2(addrX, rowPos.y + 2.0f * dp), OnSurfaceDisabled(), addrDisplay.c_str());

View File

@@ -147,14 +147,6 @@ static double GetAvailableBalance(App* app) {
return 0.0;
}
static std::string TruncateAddress(const std::string& addr, size_t maxLen = 40) {
// Guard maxLen <= 3 before the unsigned (maxLen - 3): otherwise it wraps and the
// trailing substr(length - halfLen) throws std::out_of_range on short inputs.
if (maxLen <= 3 || addr.length() <= maxLen) return addr;
size_t halfLen = (maxLen - 3) / 2;
return addr.substr(0, halfLen) + "..." + addr.substr(addr.length() - halfLen);
}
// Recipient validity = prefix/length pre-filter AND a real encoding-checksum check, so a
// transcription error that still matches the prefix/length is no longer labelled "Valid".
// The checksum verifiers are version-agnostic, so they never reject a genuine address.
@@ -256,8 +248,8 @@ static void RenderSourceDropdown(App* app, float width) {
const auto& addr = state.addresses[s_selected_from_idx];
bool isZ = addr.type == "shielded";
const char* tag = isZ ? "[Z]" : "[T]";
std::string trunc = TruncateAddress(addr.address,
static_cast<size_t>(std::max(S.drawElement("tabs.send", "addr-preview-trunc-min").size, width / S.drawElement("tabs.send", "addr-preview-trunc-divisor").size)));
std::string trunc = util::truncateMiddle(addr.address,
static_cast<int>(std::max(S.drawElement("tabs.send", "addr-preview-trunc-min").size, width / S.drawElement("tabs.send", "addr-preview-trunc-divisor").size)));
snprintf(buf, sizeof(buf), "%s %s — %.8f %s",
tag, trunc.c_str(), addr.balance, DRAGONX_TICKER);
s_source_preview = buf;
@@ -287,7 +279,7 @@ static void RenderSourceDropdown(App* app, float width) {
bool isZ = addr.type == "shielded";
const char* tag = isZ ? "[Z]" : "[T]";
std::string trunc = TruncateAddress(addr.address, addrTruncLen);
std::string trunc = util::truncateMiddle(addr.address, (int)addrTruncLen);
snprintf(buf, sizeof(buf), "%s %s — %.8f %s",
tag, trunc.c_str(), addr.balance, DRAGONX_TICKER);
@@ -347,7 +339,7 @@ static void RenderAddressSuggestions(const WalletState& state, float width, cons
ImGui::BeginChild(childId, ImVec2(width, sugH), true);
for (size_t si = 0; si < suggestions.size(); si++) {
int sugTrunc = (int)schema::UI().drawElement("tabs.send", "suggestion-trunc-len").size;
std::string dispSug = TruncateAddress(suggestions[si], sugTrunc > 0 ? sugTrunc : 50);
std::string dispSug = util::truncateMiddle(suggestions[si], sugTrunc > 0 ? sugTrunc : 50);
ImGui::PushID((int)si);
if (ImGui::Selectable(dispSug.c_str())) {
snprintf(s_to_address, sizeof(s_to_address), "%s", suggestions[si].c_str());
@@ -710,7 +702,7 @@ void RenderSendConfirmPopup(App* app) {
DrawGlassPanel(popDl, cMin, cMax, gs);
popDl->AddText(body2, body2->LegacySize,
ImVec2(cMin.x + Layout::spacingMd(), cMin.y + Layout::spacingSm()),
Success(), TruncateAddress(s_from_address, (size_t)S.drawElement("tabs.send", "confirm-addr-trunc-len").size).c_str());
Success(), util::truncateMiddle(s_from_address, (int)S.drawElement("tabs.send", "confirm-addr-trunc-len").size).c_str());
ImGui::Dummy(ImVec2(popW, addrCardH));
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
}
@@ -724,7 +716,7 @@ void RenderSendConfirmPopup(App* app) {
DrawGlassPanel(popDl, cMin, cMax, gs);
popDl->AddText(body2, body2->LegacySize,
ImVec2(cMin.x + Layout::spacingMd(), cMin.y + Layout::spacingSm()),
Success(), TruncateAddress(s_to_address, (size_t)S.drawElement("tabs.send", "confirm-addr-trunc-len").size).c_str());
Success(), util::truncateMiddle(s_to_address, (int)S.drawElement("tabs.send", "confirm-addr-trunc-len").size).c_str());
ImGui::Dummy(ImVec2(popW, addrCardH));
ImGui::Dummy(ImVec2(0, Layout::spacingMd()));
}
@@ -1060,8 +1052,8 @@ static void RenderRecentSends(const WalletState& state, float width, ImFont* cap
// Address (second line)
float addrX = txX + S.drawElement("tabs.balance", "recent-tx-addr-offset").sizeOr(65.0f);
std::string addrDisplay = TruncateAddress(tx.address,
(size_t)S.drawElement("tabs.balance", "recent-tx-addr-trunc").sizeOr(20.0f));
std::string addrDisplay = util::truncateMiddle(tx.address,
(int)S.drawElement("tabs.balance", "recent-tx-addr-trunc").sizeOr(20.0f));
rowDL->AddText(capFont, capFont->LegacySize,
ImVec2(addrX, rowPos.y + 2.0f * dp), OnSurfaceDisabled(), addrDisplay.c_str());

View File

@@ -487,12 +487,8 @@ void RenderSettingsWindow(App* app, bool* p_open)
// Confirmation dialog
if (s_confirm_clear_ztx) {
if (material::BeginOverlayDialog(TR("confirm_clear_ztx_title"), &s_confirm_clear_ztx, 480.0f, 0.94f)) {
ImGui::PushFont(material::Type().iconLarge());
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), ICON_MD_WARNING);
ImGui::PopFont();
ImGui::SameLine();
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "%s", TR("warning"));
material::DialogWarningHeader(TR("warning"), ImVec4(1.0f, 0.6f, 0.0f, 1.0f));
ImGui::Spacing();
ImGui::TextWrapped("%s", TR("confirm_clear_ztx_warning1"));
ImGui::Spacing();
@@ -500,15 +496,14 @@ void RenderSettingsWindow(App* app, bool* p_open)
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
float btnW = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
if (ImGui::Button(TR("cancel"), ImVec2(btnW, 40))) {
bool doCancel = false, doConfirm = false;
material::DialogConfirmFooter(TR("cancel"), TrId("clear_anyway", "clear_ztx_w").c_str(),
true, doCancel, doConfirm);
if (doCancel) {
s_confirm_clear_ztx = false;
}
ImGui::SameLine();
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.8f, 0.2f, 0.2f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.9f, 0.3f, 0.3f, 1.0f));
if (ImGui::Button(TrId("clear_anyway", "clear_ztx_w").c_str(), ImVec2(btnW, 40))) {
if (doConfirm) {
std::string ztx_file = util::Platform::getDragonXDataDir() + "ztx_history.json";
if (util::Platform::deleteFile(ztx_file)) {
Notifications::instance().success("Z-transaction history cleared");
@@ -517,7 +512,6 @@ void RenderSettingsWindow(App* app, bool* p_open)
}
s_confirm_clear_ztx = false;
}
ImGui::PopStyleColor(2);
material::EndOverlayDialog();
}
}

View File

@@ -37,13 +37,6 @@ static std::string TrId(const char* key, const char* id) {
return std::string(TR(key)) + "##" + id;
}
// Helper to truncate strings
static std::string truncateString(const std::string& str, int maxLen = 16) {
if (str.length() <= static_cast<size_t>(maxLen)) return str;
int half = (maxLen - 3) / 2;
return str.substr(0, half) + "..." + str.substr(str.length() - half);
}
// Case-insensitive string search
static bool containsIgnoreCase(const std::string& str, const std::string& search) {
return dragonx::util::containsIgnoreCase(str, search);
@@ -358,9 +351,7 @@ void RenderTransactionsTab(App* app)
ImGui::TextColored(ImVec4(0.6f, 0.8f, 1.0f, pulse), ICON_MD_HOURGLASS_EMPTY);
ImGui::PopFont();
ImGui::SameLine(0, Layout::spacingXs());
int dots = ((int)(ImGui::GetTime() * 3.0f)) % 4;
const char* dotStr[] = {"", ".", "..", "..."};
ImGui::TextColored(ImVec4(0.6f, 0.8f, 1.0f, 1.0f), "%s%s", txLoadingText.c_str(), dotStr[dots]);
ImGui::TextColored(ImVec4(0.6f, 0.8f, 1.0f, 1.0f), "%s%s", txLoadingText.c_str(), material::LoadingDots());
}
ImGui::Dummy(ImVec2(0, Layout::spacingSm() + Layout::spacingXs()));
@@ -679,9 +670,7 @@ void RenderTransactionsTab(App* app)
} else if (state.transactions.empty()) {
ImGui::Dummy(ImVec2(0, 20));
if (txLoading) {
int dots = ((int)(ImGui::GetTime() * 3.0f)) % 4;
const char* dotStr[] = {"", ".", "..", "..."};
snprintf(buf, sizeof(buf), "%s%s", txLoadingText.c_str(), dotStr[dots]);
snprintf(buf, sizeof(buf), "%s%s", txLoadingText.c_str(), material::LoadingDots());
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), buf);
} else {
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("no_transactions"));
@@ -756,7 +745,7 @@ void RenderTransactionsTab(App* app)
OnSurfaceDisabled(), ago.c_str());
// Address (second line, left side)
std::string addr_display = truncateString(tx.address, (addrLabel.truncate > 0) ? addrLabel.truncate : 20);
std::string addr_display = util::truncateMiddle(tx.address, (addrLabel.truncate > 0) ? addrLabel.truncate : 20);
dl->AddText(capFont, capFont->LegacySize, ImVec2(labelX, cy + body2->LegacySize + Layout::spacingXs()),
OnSurfaceMedium(), addr_display.c_str());