Files
ObsidianDragon/src/util/text_format.cpp
DanS de11850c74 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>
2026-07-02 17:29:12 -05:00

81 lines
2.9 KiB
C++

// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "text_format.h"
#include "i18n.h"
#include <cctype>
#include <cstdio>
#include <ctime>
namespace dragonx {
namespace util {
namespace {
std::int64_t secondsAgo(std::int64_t timestamp)
{
std::int64_t now = static_cast<std::int64_t>(std::time(nullptr));
std::int64_t diff = now - timestamp;
return diff < 0 ? 0 : diff;
}
} // namespace
std::string formatTimeAgo(std::int64_t timestamp)
{
if (timestamp <= 0) return {};
const std::int64_t diff = secondsAgo(timestamp);
char buf[48];
if (diff < 60) std::snprintf(buf, sizeof(buf), tr("time_seconds_ago"), static_cast<int>(diff));
else if (diff < 3600) std::snprintf(buf, sizeof(buf), tr("time_minutes_ago"), static_cast<int>(diff / 60));
else if (diff < 86400) std::snprintf(buf, sizeof(buf), tr("time_hours_ago"), static_cast<int>(diff / 3600));
else std::snprintf(buf, sizeof(buf), tr("time_days_ago"), static_cast<int>(diff / 86400));
return buf;
}
std::string formatTimeAgoShort(std::int64_t timestamp)
{
if (timestamp <= 0) return {};
const std::int64_t diff = secondsAgo(timestamp);
char buf[32];
if (diff < 60) std::snprintf(buf, sizeof(buf), "%llds ago", static_cast<long long>(diff));
else if (diff < 3600) std::snprintf(buf, sizeof(buf), "%lldm ago", static_cast<long long>(diff / 60));
else if (diff < 86400) std::snprintf(buf, sizeof(buf), "%lldh ago", static_cast<long long>(diff / 3600));
else std::snprintf(buf, sizeof(buf), "%lldd ago", static_cast<long long>(diff / 86400));
return buf;
}
std::string truncateMiddle(const std::string& s, int maxLen)
{
// Guard maxLen <= 3 before the unsigned (maxLen - 3): otherwise it wraps and the
// trailing substr(length - half) throws std::out_of_range on short inputs.
if (maxLen <= 3 || s.length() <= static_cast<std::size_t>(maxLen)) return s;
int half = (maxLen - 3) / 2;
return s.substr(0, half) + "..." + s.substr(s.length() - half);
}
std::string truncateMiddle(const std::string& s, int front, int back)
{
if (s.length() <= static_cast<std::size_t>(front + back + 3)) return s;
return s.substr(0, front) + "..." + s.substr(s.length() - back);
}
bool containsIgnoreCase(std::string_view haystack, std::string_view needle)
{
if (needle.empty()) return true;
if (needle.size() > haystack.size()) return false;
const auto low = [](char c) { return static_cast<char>(std::tolower(static_cast<unsigned char>(c))); };
const std::size_t last = haystack.size() - needle.size();
for (std::size_t i = 0; i <= last; ++i) {
std::size_t j = 0;
for (; j < needle.size(); ++j) {
if (low(haystack[i + j]) != low(needle[j])) break;
}
if (j == needle.size()) return true;
}
return false;
}
} // namespace util
} // namespace dragonx