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:
@@ -1219,6 +1219,9 @@ notification-progress = { color = "var(--primary)", height = 4, position = 18 }
|
||||
fill-alpha = { size = 12.0 }
|
||||
noise-alpha = { size = 14.0 }
|
||||
|
||||
[components.overlay-dialog]
|
||||
confirm-btn-height = { size = 40.0 }
|
||||
|
||||
[components.qr-code]
|
||||
module-scale = { size = 4 }
|
||||
border-modules = { size = 2 }
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
#include "ui/notifications.h"
|
||||
#include "util/i18n.h"
|
||||
#include "util/platform.h"
|
||||
#include "util/text_format.h"
|
||||
#include "util/payment_uri.h"
|
||||
#include "util/texture_loader.h"
|
||||
#include "util/bootstrap.h"
|
||||
@@ -2722,7 +2723,7 @@ void App::renderExportKeyDialog()
|
||||
if (ImGui::BeginCombo("##exportaddr", export_address_.empty() ? "Select address..." : export_address_.c_str())) {
|
||||
for (const auto& addr : all_addrs) {
|
||||
bool selected = (export_address_ == addr);
|
||||
std::string display = addr.substr(0, addrFrontLen) + "..." + addr.substr(addr.length() - addrBackLen);
|
||||
std::string display = util::truncateMiddle(addr, addrFrontLen, addrBackLen);
|
||||
if (ImGui::Selectable(display.c_str(), selected)) {
|
||||
export_address_ = addr;
|
||||
export_result_.clear();
|
||||
|
||||
@@ -935,10 +935,8 @@ void App::renderLockScreen() {
|
||||
float rowH = captionFont->LegacySize + 8.0f;
|
||||
if (lock_unlock_in_progress_) {
|
||||
// Animated spinner dots
|
||||
int dots = ((int)(ImGui::GetTime() * 3.0f)) % 4;
|
||||
const char* dotStr[] = {"", ".", "..", "..."};
|
||||
char msg[64];
|
||||
snprintf(msg, sizeof(msg), "Unlocking%s", dotStr[dots]);
|
||||
snprintf(msg, sizeof(msg), "Unlocking%s", ui::material::LoadingDots());
|
||||
ImVec2 ms = captionFont->CalcTextSizeA(captionFont->LegacySize, FLT_MAX, 0, msg);
|
||||
dl->AddText(captionFont, captionFont->LegacySize,
|
||||
ImVec2(cardX + (cardW - ms.x) * 0.5f, cy),
|
||||
|
||||
@@ -35,6 +35,13 @@ inline ImU32 ScaleAlpha(ImU32 col, float scale) {
|
||||
return (col & ~IM_COL32_A_MASK) | (static_cast<ImU32>(a) << IM_COL32_A_SHIFT);
|
||||
}
|
||||
|
||||
// Animated "loading" ellipsis: "", ".", "..", "..." cycling on a ~3Hz phase.
|
||||
inline const char* LoadingDots() {
|
||||
int n = ((int)(ImGui::GetTime() * 3.0f)) % 4;
|
||||
static const char* kDots[4] = { "", ".", "..", "..." };
|
||||
return kDots[n];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Text Drop Shadow
|
||||
// ============================================================================
|
||||
@@ -210,6 +217,30 @@ inline void DrawTactileOverlay(ImDrawList* dl, const ImVec2& bMin,
|
||||
dl->PopClipRect();
|
||||
}
|
||||
|
||||
// Glass-card button surface: frosted-glass hover/idle fill (skipped while
|
||||
// pressed) + rim light + tactile depth overlay. Shared by the tactile
|
||||
// button wrappers. Call after computing the button rect / rounding /
|
||||
// active / hovered state.
|
||||
inline void DrawButtonGlassOverlay(ImDrawList* dl, const ImVec2& bMin,
|
||||
const ImVec2& bMax, float rounding,
|
||||
bool active, bool hovered)
|
||||
{
|
||||
// Frosted glass highlight — subtle fill on top
|
||||
if (!active) {
|
||||
ImU32 col = hovered
|
||||
? schema::UI().resolveColor("var(--hover-overlay)", IM_COL32(255, 255, 255, 12))
|
||||
: schema::UI().resolveColor("var(--glass-fill)", IM_COL32(255, 255, 255, 6));
|
||||
dl->AddRectFilled(bMin, bMax, col, rounding);
|
||||
}
|
||||
// Rim light
|
||||
ImU32 rim = schema::UI().resolveColor("var(--rim-light)", IM_COL32(255, 255, 255, 25));
|
||||
dl->AddRect(bMin, bMax,
|
||||
active ? ScaleAlpha(rim, 0.6f) : rim,
|
||||
rounding, 0, 1.0f);
|
||||
// Tactile depth
|
||||
DrawTactileOverlay(dl, bMin, bMax, rounding, active);
|
||||
}
|
||||
|
||||
// Convenience: call right after any ImGui::Button() / SmallButton() call
|
||||
// to add tactile depth. Uses the last item rect automatically.
|
||||
inline void ApplyTactile(ImDrawList* dl = nullptr)
|
||||
@@ -278,20 +309,7 @@ inline bool TactileButton(const char* label, const ImVec2& size = ImVec2(0, 0),
|
||||
float rounding = ImGui::GetStyle().FrameRounding;
|
||||
bool active = ImGui::IsItemActive();
|
||||
bool hovered = ImGui::IsItemHovered();
|
||||
// Frosted glass highlight — subtle fill on top
|
||||
if (!active) {
|
||||
ImU32 col = hovered
|
||||
? schema::UI().resolveColor("var(--hover-overlay)", IM_COL32(255, 255, 255, 12))
|
||||
: schema::UI().resolveColor("var(--glass-fill)", IM_COL32(255, 255, 255, 6));
|
||||
dl->AddRectFilled(bMin, bMax, col, rounding);
|
||||
}
|
||||
// Rim light
|
||||
ImU32 rim = schema::UI().resolveColor("var(--rim-light)", IM_COL32(255, 255, 255, 25));
|
||||
dl->AddRect(bMin, bMax,
|
||||
active ? ScaleAlpha(rim, 0.6f) : rim,
|
||||
rounding, 0, 1.0f);
|
||||
// Tactile depth
|
||||
DrawTactileOverlay(dl, bMin, bMax, rounding, active);
|
||||
DrawButtonGlassOverlay(dl, bMin, bMax, rounding, active, hovered);
|
||||
return pressed;
|
||||
}
|
||||
|
||||
@@ -306,20 +324,44 @@ inline bool TactileSmallButton(const char* label, ImFont* font = nullptr)
|
||||
float rounding = ImGui::GetStyle().FrameRounding;
|
||||
bool active = ImGui::IsItemActive();
|
||||
bool hovered = ImGui::IsItemHovered();
|
||||
if (!active) {
|
||||
ImU32 col = hovered
|
||||
? schema::UI().resolveColor("var(--hover-overlay)", IM_COL32(255, 255, 255, 12))
|
||||
: schema::UI().resolveColor("var(--glass-fill)", IM_COL32(255, 255, 255, 6));
|
||||
dl->AddRectFilled(bMin, bMax, col, rounding);
|
||||
}
|
||||
ImU32 rim = schema::UI().resolveColor("var(--rim-light)", IM_COL32(255, 255, 255, 25));
|
||||
dl->AddRect(bMin, bMax,
|
||||
active ? ScaleAlpha(rim, 0.6f) : rim,
|
||||
rounding, 0, 1.0f);
|
||||
DrawTactileOverlay(dl, bMin, bMax, rounding, active);
|
||||
DrawButtonGlassOverlay(dl, bMin, bMax, rounding, active, hovered);
|
||||
return pressed;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Collapsible section header
|
||||
// ============================================================================
|
||||
// Renders a full-width transparent clickable header row: `label` drawn on the
|
||||
// left and an expand/collapse chevron on the right. Toggles `expanded` when
|
||||
// clicked and returns the (possibly toggled) state. The label font governs
|
||||
// the row height (frame height) and text metrics; the chevron uses the small
|
||||
// icon font. `arrowInset` shifts the chevron left of the right edge.
|
||||
inline bool CollapsibleHeader(ImDrawList* dl, const char* id, const char* label,
|
||||
bool& expanded, float width, ImFont* labelFont,
|
||||
ImU32 labelCol, float arrowInset = 0.0f)
|
||||
{
|
||||
const char* arrow = expanded ? ICON_MD_EXPAND_LESS : ICON_MD_EXPAND_MORE;
|
||||
ImGui::PushFont(labelFont);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1,1,1,0.05f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1,1,1,0.08f));
|
||||
{
|
||||
ImVec2 hdrPos = ImGui::GetCursorScreenPos();
|
||||
if (ImGui::Button(id, ImVec2(width, ImGui::GetFrameHeight()))) {
|
||||
expanded = !expanded;
|
||||
}
|
||||
float textY = hdrPos.y + (ImGui::GetFrameHeight() - labelFont->LegacySize) * 0.5f;
|
||||
dl->AddText(labelFont, labelFont->LegacySize, ImVec2(hdrPos.x, textY), labelCol, label);
|
||||
ImFont* iconFont = Type().iconSmall();
|
||||
if (!iconFont) iconFont = labelFont;
|
||||
float arrowW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, arrow).x;
|
||||
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(hdrPos.x + width - arrowW - arrowInset, textY), labelCol, arrow);
|
||||
}
|
||||
ImGui::PopStyleColor(3);
|
||||
ImGui::PopFont();
|
||||
return expanded;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Glass Panel (glassmorphism card)
|
||||
// ============================================================================
|
||||
@@ -434,6 +476,57 @@ inline void DrawGlassPanel(ImDrawList* dl, const ImVec2& pMin,
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Glass Card Scope (RAII) — channel-split glass-card scaffold
|
||||
// ============================================================================
|
||||
// Captures the current cursor as the card's top-left, splits the draw list
|
||||
// into two channels (content on 1, glass on 0), insets the content by `pad`,
|
||||
// and — on scope exit — appends the bottom pad, sizes the card to its content,
|
||||
// draws the glass panel behind it on channel 0, merges, and advances the
|
||||
// cursor past the card. `width` is the full card width, `bottomPad` the
|
||||
// trailing pad. Reproduces the hand-written prologue/epilogue exactly.
|
||||
//
|
||||
// {
|
||||
// material::GlassCardScope card(dl, availWidth, pad, bottomPad, glassSpec);
|
||||
// ... card content ...
|
||||
// }
|
||||
struct GlassCardScope {
|
||||
ImDrawList* dl;
|
||||
ImVec2 cardMin;
|
||||
float width;
|
||||
float pad;
|
||||
float bottomPad;
|
||||
GlassPanelSpec spec;
|
||||
|
||||
GlassCardScope(ImDrawList* dl_, float width_, float pad_, float bottomPad_,
|
||||
const GlassPanelSpec& spec_)
|
||||
: dl(dl_), width(width_), pad(pad_), bottomPad(bottomPad_), spec(spec_)
|
||||
{
|
||||
cardMin = ImGui::GetCursorScreenPos();
|
||||
dl->ChannelsSplit(2);
|
||||
dl->ChannelsSetCurrent(1);
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMin.y + pad));
|
||||
ImGui::Indent(pad);
|
||||
}
|
||||
|
||||
~GlassCardScope()
|
||||
{
|
||||
ImGui::Dummy(ImVec2(0, bottomPad));
|
||||
ImGui::Unindent(pad);
|
||||
|
||||
ImVec2 cardMax(cardMin.x + width, ImGui::GetCursorScreenPos().y);
|
||||
dl->ChannelsSetCurrent(0);
|
||||
DrawGlassPanel(dl, cardMin, cardMax, spec);
|
||||
dl->ChannelsMerge();
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
|
||||
ImGui::Dummy(ImVec2(width, 0));
|
||||
}
|
||||
|
||||
GlassCardScope(const GlassCardScope&) = delete;
|
||||
GlassCardScope& operator=(const GlassCardScope&) = delete;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Stat Card — reusable card with overline / value / subtitle + accent stripe
|
||||
// ============================================================================
|
||||
@@ -656,17 +749,7 @@ inline bool TactileButton(const char* label, const schema::ButtonStyle& style,
|
||||
float rounding = ImGui::GetStyle().FrameRounding;
|
||||
bool active = ImGui::IsItemActive();
|
||||
bool hovered = ImGui::IsItemHovered();
|
||||
if (!active) {
|
||||
ImU32 col = hovered
|
||||
? schema::UI().resolveColor("var(--hover-overlay)", IM_COL32(255, 255, 255, 12))
|
||||
: schema::UI().resolveColor("var(--glass-fill)", IM_COL32(255, 255, 255, 6));
|
||||
dl->AddRectFilled(bMin, bMax, col, rounding);
|
||||
}
|
||||
ImU32 rim = schema::UI().resolveColor("var(--rim-light)", IM_COL32(255, 255, 255, 25));
|
||||
dl->AddRect(bMin, bMax,
|
||||
active ? ScaleAlpha(rim, 0.6f) : rim,
|
||||
rounding, 0, 1.0f);
|
||||
DrawTactileOverlay(dl, bMin, bMax, rounding, active);
|
||||
DrawButtonGlassOverlay(dl, bMin, bMax, rounding, active, hovered);
|
||||
|
||||
ImGui::PopStyleVar(styleCount);
|
||||
ImGui::PopStyleColor(colorCount);
|
||||
@@ -1069,6 +1152,49 @@ inline void BeginOverlayDialogFooter(float totalActionWidth, bool drawSeparator
|
||||
PlaceOverlayDialogActions(totalActionWidth);
|
||||
}
|
||||
|
||||
// ── Confirmation dialog header / footer ─────────────────────────────────
|
||||
// Shared scaffolding for the "warning + Cancel/confirm" overlay dialogs.
|
||||
|
||||
// Warning icon + "Warning" label row, drawn in `col` (the caller owns the
|
||||
// exact accent colour; new callers can omit it to use the theme warning).
|
||||
// Reproduces: PushFont(iconLarge) + ICON_MD_WARNING, PopFont, SameLine,
|
||||
// TextColored(label). The label text is passed in (already translated).
|
||||
inline void DialogWarningHeader(const char* warningLabel, const ImVec4& col = WarningVec4())
|
||||
{
|
||||
ImGui::PushFont(Type().iconLarge());
|
||||
ImGui::TextColored(col, ICON_MD_WARNING);
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(col, "%s", warningLabel);
|
||||
}
|
||||
|
||||
// 50/50 Cancel / confirm footer for overlay dialogs. Sets `outCancel` /
|
||||
// `outConfirm` when the respective button is pressed (the caller runs the
|
||||
// action bodies). When `danger`, the confirm button gets the red danger
|
||||
// style. Button height comes from ui.toml (falls back to 40). Reproduces
|
||||
// the hand-written footer exactly (btnW split + SameLine + optional danger
|
||||
// PushStyleColor/PopStyleColor(2)).
|
||||
inline void DialogConfirmFooter(const char* cancelId, const char* confirmLabel,
|
||||
bool danger, bool& outCancel, bool& outConfirm)
|
||||
{
|
||||
float btnH = schema::UI().drawElement("components.overlay-dialog", "confirm-btn-height").sizeOr(40.0f);
|
||||
float btnW = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
|
||||
if (ImGui::Button(cancelId, ImVec2(btnW, btnH))) {
|
||||
outCancel = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (danger) {
|
||||
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(confirmLabel, ImVec2(btnW, btnH))) {
|
||||
outConfirm = true;
|
||||
}
|
||||
if (danger) {
|
||||
ImGui::PopStyleColor(2);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace material
|
||||
} // namespace ui
|
||||
} // namespace dragonx
|
||||
|
||||
@@ -170,6 +170,51 @@ struct SettingsPageState {
|
||||
|
||||
static SettingsPageState s_settingsState;
|
||||
|
||||
// Low-spec mode enter: snapshot the current effects prefs, then force the
|
||||
// low-cost overrides. When applyEffects is true, also push the overrides to
|
||||
// the live effects subsystems (the runtime-sync path passes false since it
|
||||
// only mirrors an already-applied hotkey toggle into the checkbox state).
|
||||
static void enterLowSpec(bool applyEffects) {
|
||||
s_settingsState.low_spec_snapshot.valid = true;
|
||||
s_settingsState.low_spec_snapshot.acrylic_enabled = s_settingsState.acrylic_enabled;
|
||||
s_settingsState.low_spec_snapshot.blur_amount = s_settingsState.blur_amount;
|
||||
s_settingsState.low_spec_snapshot.ui_opacity = s_settingsState.ui_opacity;
|
||||
s_settingsState.low_spec_snapshot.window_opacity = s_settingsState.window_opacity;
|
||||
s_settingsState.low_spec_snapshot.theme_effects_enabled = s_settingsState.theme_effects_enabled;
|
||||
s_settingsState.low_spec_snapshot.scanline_enabled = s_settingsState.scanline_enabled;
|
||||
s_settingsState.acrylic_enabled = false;
|
||||
s_settingsState.blur_amount = 0.0f;
|
||||
s_settingsState.ui_opacity = 1.0f;
|
||||
s_settingsState.window_opacity = 1.0f;
|
||||
s_settingsState.theme_effects_enabled = false;
|
||||
s_settingsState.scanline_enabled = false;
|
||||
if (applyEffects) {
|
||||
effects::ImGuiAcrylic::ApplyBlurAmount(0.0f);
|
||||
effects::ImGuiAcrylic::SetUIOpacity(1.0f);
|
||||
effects::ThemeEffects::instance().setEnabled(false);
|
||||
ConsoleTab::s_scanline_enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Low-spec mode exit: restore the previously snapshotted effects prefs and
|
||||
// clear the snapshot. When applyEffects is true, also push them to the live
|
||||
// effects subsystems. Callers guard this on low_spec_snapshot.valid.
|
||||
static void exitLowSpec(bool applyEffects) {
|
||||
s_settingsState.blur_amount = s_settingsState.low_spec_snapshot.blur_amount;
|
||||
s_settingsState.acrylic_enabled = (s_settingsState.blur_amount > 0.001f);
|
||||
s_settingsState.ui_opacity = s_settingsState.low_spec_snapshot.ui_opacity;
|
||||
s_settingsState.window_opacity = s_settingsState.low_spec_snapshot.window_opacity;
|
||||
s_settingsState.theme_effects_enabled = s_settingsState.low_spec_snapshot.theme_effects_enabled;
|
||||
s_settingsState.scanline_enabled = s_settingsState.low_spec_snapshot.scanline_enabled;
|
||||
if (applyEffects) {
|
||||
effects::ImGuiAcrylic::ApplyBlurAmount(s_settingsState.blur_amount);
|
||||
effects::ImGuiAcrylic::SetUIOpacity(s_settingsState.ui_opacity);
|
||||
effects::ThemeEffects::instance().setEnabled(s_settingsState.theme_effects_enabled);
|
||||
ConsoleTab::s_scanline_enabled = s_settingsState.scanline_enabled;
|
||||
}
|
||||
s_settingsState.low_spec_snapshot.valid = false;
|
||||
}
|
||||
|
||||
// Count whitespace-separated words in a (seed) buffer — used to validate/guide restore input.
|
||||
static int liteSeedWordCount(const char* s) {
|
||||
int words = 0;
|
||||
@@ -274,7 +319,7 @@ static void evaluateLiteLifecycleRequestFromPageState(App* app) {
|
||||
}
|
||||
if (started) {
|
||||
s_settingsState.lite_lifecycle_pending = true;
|
||||
s_settingsState.lite_lifecycle_status = "Working…";
|
||||
s_settingsState.lite_lifecycle_status = TR("lite_working");
|
||||
s_settingsState.lite_lifecycle_summary.clear();
|
||||
} else {
|
||||
// Rejected before any thread launched (wallet already open, an attempt in flight, or no
|
||||
@@ -428,28 +473,10 @@ void RenderSettingsPage(App* app) {
|
||||
if (s_settingsState.low_spec_mode != runtimeLowSpec) {
|
||||
if (runtimeLowSpec) {
|
||||
// Hotkey turned low-spec ON — save snapshot, override statics
|
||||
s_settingsState.low_spec_snapshot.valid = true;
|
||||
s_settingsState.low_spec_snapshot.acrylic_enabled = s_settingsState.acrylic_enabled;
|
||||
s_settingsState.low_spec_snapshot.blur_amount = s_settingsState.blur_amount;
|
||||
s_settingsState.low_spec_snapshot.ui_opacity = s_settingsState.ui_opacity;
|
||||
s_settingsState.low_spec_snapshot.window_opacity = s_settingsState.window_opacity;
|
||||
s_settingsState.low_spec_snapshot.theme_effects_enabled = s_settingsState.theme_effects_enabled;
|
||||
s_settingsState.low_spec_snapshot.scanline_enabled = s_settingsState.scanline_enabled;
|
||||
s_settingsState.acrylic_enabled = false;
|
||||
s_settingsState.blur_amount = 0.0f;
|
||||
s_settingsState.ui_opacity = 1.0f;
|
||||
s_settingsState.window_opacity = 1.0f;
|
||||
s_settingsState.theme_effects_enabled = false;
|
||||
s_settingsState.scanline_enabled = false;
|
||||
enterLowSpec(false);
|
||||
} else if (s_settingsState.low_spec_snapshot.valid) {
|
||||
// Hotkey turned low-spec OFF — restore snapshot
|
||||
s_settingsState.blur_amount = s_settingsState.low_spec_snapshot.blur_amount;
|
||||
s_settingsState.acrylic_enabled = (s_settingsState.blur_amount > 0.001f);
|
||||
s_settingsState.ui_opacity = s_settingsState.low_spec_snapshot.ui_opacity;
|
||||
s_settingsState.window_opacity = s_settingsState.low_spec_snapshot.window_opacity;
|
||||
s_settingsState.theme_effects_enabled = s_settingsState.low_spec_snapshot.theme_effects_enabled;
|
||||
s_settingsState.scanline_enabled = s_settingsState.low_spec_snapshot.scanline_enabled;
|
||||
s_settingsState.low_spec_snapshot.valid = false;
|
||||
exitLowSpec(false);
|
||||
} else if (app->settings()) {
|
||||
// No snapshot — read prefs from settings file
|
||||
s_settingsState.blur_amount = app->settings()->getBlurMultiplier();
|
||||
@@ -561,12 +588,7 @@ void RenderSettingsPage(App* app) {
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("theme_language"));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
ImVec2 cardMin = ImGui::GetCursorScreenPos();
|
||||
dl->ChannelsSplit(2);
|
||||
dl->ChannelsSetCurrent(1);
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMin.y + pad));
|
||||
ImGui::Indent(pad);
|
||||
material::GlassCardScope card(dl, availWidth, pad, bottomPad, glassSpec);
|
||||
|
||||
float contentW = availWidth - pad * 2;
|
||||
float comboGap = S.drawElement("components.settings-page", "combo-row-gap").size;
|
||||
@@ -767,27 +789,9 @@ void RenderSettingsPage(App* app) {
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
// --- Collapsible: Advanced Effects... ---
|
||||
{
|
||||
const char* arrow = s_settingsState.effects_expanded ? ICON_MD_EXPAND_LESS : ICON_MD_EXPAND_MORE;
|
||||
ImGui::PushFont(body2);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1,1,1,0.05f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1,1,1,0.08f));
|
||||
{
|
||||
ImVec2 hdrPos = ImGui::GetCursorScreenPos();
|
||||
if (ImGui::Button("##EffectsToggle", ImVec2(contentW, ImGui::GetFrameHeight()))) {
|
||||
s_settingsState.effects_expanded = !s_settingsState.effects_expanded;
|
||||
}
|
||||
float textY = hdrPos.y + (ImGui::GetFrameHeight() - body2->LegacySize) * 0.5f;
|
||||
dl->AddText(body2, body2->LegacySize, ImVec2(hdrPos.x, textY), OnSurfaceMedium(), TR("advanced_effects"));
|
||||
ImFont* iconFont = Type().iconSmall();
|
||||
if (!iconFont) iconFont = body2;
|
||||
float arrowW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, arrow).x;
|
||||
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(hdrPos.x + contentW - arrowW, textY), OnSurfaceMedium(), arrow);
|
||||
}
|
||||
ImGui::PopStyleColor(3);
|
||||
ImGui::PopFont();
|
||||
}
|
||||
material::CollapsibleHeader(dl, "##EffectsToggle", TR("advanced_effects"),
|
||||
s_settingsState.effects_expanded, contentW,
|
||||
body2, OnSurfaceMedium());
|
||||
|
||||
if (s_settingsState.effects_expanded) {
|
||||
ImGui::PushFont(body2);
|
||||
@@ -796,35 +800,9 @@ void RenderSettingsPage(App* app) {
|
||||
if (ImGui::Checkbox(TrId("low_spec_mode", "low_spec").c_str(), &s_settingsState.low_spec_mode)) {
|
||||
effects::setLowSpecMode(s_settingsState.low_spec_mode);
|
||||
if (s_settingsState.low_spec_mode) {
|
||||
s_settingsState.low_spec_snapshot.valid = true;
|
||||
s_settingsState.low_spec_snapshot.acrylic_enabled = s_settingsState.acrylic_enabled;
|
||||
s_settingsState.low_spec_snapshot.blur_amount = s_settingsState.blur_amount;
|
||||
s_settingsState.low_spec_snapshot.ui_opacity = s_settingsState.ui_opacity;
|
||||
s_settingsState.low_spec_snapshot.window_opacity = s_settingsState.window_opacity;
|
||||
s_settingsState.low_spec_snapshot.theme_effects_enabled = s_settingsState.theme_effects_enabled;
|
||||
s_settingsState.low_spec_snapshot.scanline_enabled = s_settingsState.scanline_enabled;
|
||||
s_settingsState.acrylic_enabled = false;
|
||||
s_settingsState.blur_amount = 0.0f;
|
||||
s_settingsState.ui_opacity = 1.0f;
|
||||
s_settingsState.window_opacity = 1.0f;
|
||||
s_settingsState.theme_effects_enabled = false;
|
||||
s_settingsState.scanline_enabled = false;
|
||||
effects::ImGuiAcrylic::ApplyBlurAmount(0.0f);
|
||||
effects::ImGuiAcrylic::SetUIOpacity(1.0f);
|
||||
effects::ThemeEffects::instance().setEnabled(false);
|
||||
ConsoleTab::s_scanline_enabled = false;
|
||||
enterLowSpec(true);
|
||||
} else if (s_settingsState.low_spec_snapshot.valid) {
|
||||
s_settingsState.blur_amount = s_settingsState.low_spec_snapshot.blur_amount;
|
||||
s_settingsState.acrylic_enabled = (s_settingsState.blur_amount > 0.001f);
|
||||
s_settingsState.ui_opacity = s_settingsState.low_spec_snapshot.ui_opacity;
|
||||
s_settingsState.window_opacity = s_settingsState.low_spec_snapshot.window_opacity;
|
||||
s_settingsState.theme_effects_enabled = s_settingsState.low_spec_snapshot.theme_effects_enabled;
|
||||
s_settingsState.scanline_enabled = s_settingsState.low_spec_snapshot.scanline_enabled;
|
||||
effects::ImGuiAcrylic::ApplyBlurAmount(s_settingsState.blur_amount);
|
||||
effects::ImGuiAcrylic::SetUIOpacity(s_settingsState.ui_opacity);
|
||||
effects::ThemeEffects::instance().setEnabled(s_settingsState.theme_effects_enabled);
|
||||
ConsoleTab::s_scanline_enabled = s_settingsState.scanline_enabled;
|
||||
s_settingsState.low_spec_snapshot.valid = false;
|
||||
exitLowSpec(true);
|
||||
}
|
||||
saveSettingsPageState(app->settings());
|
||||
}
|
||||
@@ -1065,26 +1043,10 @@ void RenderSettingsPage(App* app) {
|
||||
|
||||
// --- Collapsible: Advanced Effects... ---
|
||||
{
|
||||
const char* arrow = s_settingsState.effects_expanded ? ICON_MD_EXPAND_LESS : ICON_MD_EXPAND_MORE;
|
||||
ImGui::PushFont(body2);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1,1,1,0.05f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1,1,1,0.08f));
|
||||
{
|
||||
float narrowContentW = availWidth - pad * 2;
|
||||
ImVec2 hdrPos = ImGui::GetCursorScreenPos();
|
||||
if (ImGui::Button("##EffectsToggleN", ImVec2(narrowContentW, ImGui::GetFrameHeight()))) {
|
||||
s_settingsState.effects_expanded = !s_settingsState.effects_expanded;
|
||||
}
|
||||
float textY = hdrPos.y + (ImGui::GetFrameHeight() - body2->LegacySize) * 0.5f;
|
||||
dl->AddText(body2, body2->LegacySize, ImVec2(hdrPos.x, textY), OnSurfaceMedium(), TR("advanced_effects"));
|
||||
ImFont* iconFont = Type().iconSmall();
|
||||
if (!iconFont) iconFont = body2;
|
||||
float arrowW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, arrow).x;
|
||||
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(hdrPos.x + narrowContentW - arrowW, textY), OnSurfaceMedium(), arrow);
|
||||
}
|
||||
ImGui::PopStyleColor(3);
|
||||
ImGui::PopFont();
|
||||
float narrowContentW = availWidth - pad * 2;
|
||||
material::CollapsibleHeader(dl, "##EffectsToggleN", TR("advanced_effects"),
|
||||
s_settingsState.effects_expanded, narrowContentW,
|
||||
body2, OnSurfaceMedium());
|
||||
}
|
||||
|
||||
if (s_settingsState.effects_expanded) {
|
||||
@@ -1093,35 +1055,9 @@ void RenderSettingsPage(App* app) {
|
||||
if (ImGui::Checkbox(TrId("low_spec_mode", "low_spec").c_str(), &s_settingsState.low_spec_mode)) {
|
||||
effects::setLowSpecMode(s_settingsState.low_spec_mode);
|
||||
if (s_settingsState.low_spec_mode) {
|
||||
s_settingsState.low_spec_snapshot.valid = true;
|
||||
s_settingsState.low_spec_snapshot.acrylic_enabled = s_settingsState.acrylic_enabled;
|
||||
s_settingsState.low_spec_snapshot.blur_amount = s_settingsState.blur_amount;
|
||||
s_settingsState.low_spec_snapshot.ui_opacity = s_settingsState.ui_opacity;
|
||||
s_settingsState.low_spec_snapshot.window_opacity = s_settingsState.window_opacity;
|
||||
s_settingsState.low_spec_snapshot.theme_effects_enabled = s_settingsState.theme_effects_enabled;
|
||||
s_settingsState.low_spec_snapshot.scanline_enabled = s_settingsState.scanline_enabled;
|
||||
s_settingsState.acrylic_enabled = false;
|
||||
s_settingsState.blur_amount = 0.0f;
|
||||
s_settingsState.ui_opacity = 1.0f;
|
||||
s_settingsState.window_opacity = 1.0f;
|
||||
s_settingsState.theme_effects_enabled = false;
|
||||
s_settingsState.scanline_enabled = false;
|
||||
effects::ImGuiAcrylic::ApplyBlurAmount(0.0f);
|
||||
effects::ImGuiAcrylic::SetUIOpacity(1.0f);
|
||||
effects::ThemeEffects::instance().setEnabled(false);
|
||||
ConsoleTab::s_scanline_enabled = false;
|
||||
enterLowSpec(true);
|
||||
} else if (s_settingsState.low_spec_snapshot.valid) {
|
||||
s_settingsState.blur_amount = s_settingsState.low_spec_snapshot.blur_amount;
|
||||
s_settingsState.acrylic_enabled = (s_settingsState.blur_amount > 0.001f);
|
||||
s_settingsState.ui_opacity = s_settingsState.low_spec_snapshot.ui_opacity;
|
||||
s_settingsState.window_opacity = s_settingsState.low_spec_snapshot.window_opacity;
|
||||
s_settingsState.theme_effects_enabled = s_settingsState.low_spec_snapshot.theme_effects_enabled;
|
||||
s_settingsState.scanline_enabled = s_settingsState.low_spec_snapshot.scanline_enabled;
|
||||
effects::ImGuiAcrylic::ApplyBlurAmount(s_settingsState.blur_amount);
|
||||
effects::ImGuiAcrylic::SetUIOpacity(s_settingsState.ui_opacity);
|
||||
effects::ThemeEffects::instance().setEnabled(s_settingsState.theme_effects_enabled);
|
||||
ConsoleTab::s_scanline_enabled = s_settingsState.scanline_enabled;
|
||||
s_settingsState.low_spec_snapshot.valid = false;
|
||||
exitLowSpec(true);
|
||||
}
|
||||
saveSettingsPageState(app->settings());
|
||||
}
|
||||
@@ -1219,19 +1155,6 @@ void RenderSettingsPage(App* app) {
|
||||
ImGui::PopFont();
|
||||
} // s_settingsState.effects_expanded
|
||||
}
|
||||
|
||||
// Bottom padding
|
||||
ImGui::Dummy(ImVec2(0, bottomPad));
|
||||
ImGui::Unindent(pad);
|
||||
|
||||
// Draw glass panel behind content (auto-sized)
|
||||
ImVec2 cardMax(cardMin.x + availWidth, ImGui::GetCursorScreenPos().y);
|
||||
dl->ChannelsSetCurrent(0);
|
||||
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
|
||||
dl->ChannelsMerge();
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
|
||||
ImGui::Dummy(ImVec2(availWidth, 0));
|
||||
}
|
||||
|
||||
ImGui::Dummy(ImVec2(0, gap));
|
||||
@@ -1243,11 +1166,8 @@ void RenderSettingsPage(App* app) {
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("wallet"));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
ImVec2 cardMin = ImGui::GetCursorScreenPos();
|
||||
dl->ChannelsSplit(2);
|
||||
dl->ChannelsSetCurrent(1);
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMin.y + pad));
|
||||
ImGui::Indent(pad);
|
||||
material::GlassCardScope card(dl, availWidth, pad, bottomPad, glassSpec);
|
||||
const ImVec2& cardMin = card.cardMin;
|
||||
|
||||
float contentW = availWidth - pad * 2;
|
||||
|
||||
@@ -1307,27 +1227,9 @@ void RenderSettingsPage(App* app) {
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
// --- Collapsible: Tools & Actions... ---
|
||||
{
|
||||
const char* arrow = s_settingsState.tools_expanded ? ICON_MD_EXPAND_LESS : ICON_MD_EXPAND_MORE;
|
||||
ImGui::PushFont(body2);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1,1,1,0.05f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1,1,1,0.08f));
|
||||
{
|
||||
ImVec2 hdrPos = ImGui::GetCursorScreenPos();
|
||||
if (ImGui::Button("##ToolsToggle", ImVec2(contentW, ImGui::GetFrameHeight()))) {
|
||||
s_settingsState.tools_expanded = !s_settingsState.tools_expanded;
|
||||
}
|
||||
float textY = hdrPos.y + (ImGui::GetFrameHeight() - body2->LegacySize) * 0.5f;
|
||||
dl->AddText(body2, body2->LegacySize, ImVec2(hdrPos.x, textY), OnSurfaceMedium(), TR("tools_actions"));
|
||||
ImFont* iconFont = Type().iconSmall();
|
||||
if (!iconFont) iconFont = body2;
|
||||
float arrowW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, arrow).x;
|
||||
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(hdrPos.x + contentW - arrowW, textY), OnSurfaceMedium(), arrow);
|
||||
}
|
||||
ImGui::PopStyleColor(3);
|
||||
ImGui::PopFont();
|
||||
}
|
||||
material::CollapsibleHeader(dl, "##ToolsToggle", TR("tools_actions"),
|
||||
s_settingsState.tools_expanded, contentW,
|
||||
body2, OnSurfaceMedium());
|
||||
|
||||
if (s_settingsState.tools_expanded) {
|
||||
float btnSpacing = Layout::spacingMd();
|
||||
@@ -1435,17 +1337,6 @@ void RenderSettingsPage(App* app) {
|
||||
|
||||
if (scale < 1.0f) ImGui::SetWindowFontScale(1.0f);
|
||||
}
|
||||
|
||||
ImGui::Dummy(ImVec2(0, bottomPad));
|
||||
ImGui::Unindent(pad);
|
||||
|
||||
ImVec2 cardMax(cardMin.x + availWidth, ImGui::GetCursorScreenPos().y);
|
||||
dl->ChannelsSetCurrent(0);
|
||||
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
|
||||
dl->ChannelsMerge();
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
|
||||
ImGui::Dummy(ImVec2(availWidth, 0));
|
||||
}
|
||||
|
||||
ImGui::Dummy(ImVec2(0, gap));
|
||||
@@ -1458,11 +1349,7 @@ void RenderSettingsPage(App* app) {
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("node_security"));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
ImVec2 cardMin = ImGui::GetCursorScreenPos();
|
||||
dl->ChannelsSplit(2);
|
||||
dl->ChannelsSetCurrent(1);
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMin.y + pad));
|
||||
ImGui::Indent(pad);
|
||||
material::GlassCardScope card(dl, availWidth, pad, bottomPad, glassSpec);
|
||||
|
||||
float contentW = availWidth - pad * 2;
|
||||
float minBtnW = S.drawElement("components.settings-page", "wallet-btn-min-width").sizeOr(130.0f);
|
||||
@@ -1618,18 +1505,18 @@ void RenderSettingsPage(App* app) {
|
||||
|
||||
// Lite-server selection lives in the dedicated Network tab now.
|
||||
Type().textColored(TypeStyle::Body2, OnSurfaceMedium(),
|
||||
"Lite servers are managed in the Network tab.");
|
||||
TR("lite_servers_network_tab"));
|
||||
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
if (ImGui::Button("Lite wallet request##LiteLifecycleToggle", ImVec2(liteInputW, 0))) {
|
||||
if (ImGui::Button(TrId("lite_wallet_request", "LiteLifecycleToggle").c_str(), ImVec2(liteInputW, 0))) {
|
||||
s_settingsState.lite_lifecycle_expanded = !s_settingsState.lite_lifecycle_expanded;
|
||||
}
|
||||
|
||||
if (s_settingsState.lite_lifecycle_expanded) {
|
||||
const char* lifecycleLabels[] = {"Create", "Open", "Restore"};
|
||||
const char* lifecycleLabels[] = {TR("lite_op_create"), TR("lite_op_open"), TR("lite_op_restore")};
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Action");
|
||||
ImGui::TextUnformatted(TR("lite_action"));
|
||||
ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
|
||||
ImGui::SetNextItemWidth(liteInputW);
|
||||
if (ImGui::BeginCombo("##LiteLifecycleOperation",
|
||||
@@ -1650,7 +1537,7 @@ void RenderSettingsPage(App* app) {
|
||||
s_settingsState.lite_lifecycle_operation == 2) {
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Wallet");
|
||||
ImGui::TextUnformatted(TR("lite_wallet_label"));
|
||||
ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
|
||||
ImGui::SetNextItemWidth(liteInputW);
|
||||
ImGui::InputText("##LiteWalletPath", s_settingsState.lite_wallet_path,
|
||||
@@ -1660,7 +1547,7 @@ void RenderSettingsPage(App* app) {
|
||||
if (s_settingsState.lite_lifecycle_operation == 2) {
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Seed");
|
||||
ImGui::TextUnformatted(TR("lite_seed_label"));
|
||||
ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
|
||||
ImGui::SetNextItemWidth(liteInputW);
|
||||
ImGui::InputText("##LiteRestoreSeed", s_settingsState.lite_restore_seed,
|
||||
@@ -1670,38 +1557,38 @@ void RenderSettingsPage(App* app) {
|
||||
{
|
||||
const int wc = liteSeedWordCount(s_settingsState.lite_restore_seed);
|
||||
char wbuf[32];
|
||||
snprintf(wbuf, sizeof(wbuf), "%d / 24 words", wc);
|
||||
snprintf(wbuf, sizeof(wbuf), TR("lite_word_count"), wc);
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
Type().textColored(TypeStyle::Caption, wc == 24 ? Success() : Warning(), wbuf);
|
||||
}
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Birthday");
|
||||
ImGui::TextUnformatted(TR("lite_birthday_label"));
|
||||
ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
|
||||
ImGui::SetNextItemWidth(std::min(160.0f, liteInputW));
|
||||
ImGui::InputInt("##LiteRestoreBirthday", &s_settingsState.lite_restore_birthday);
|
||||
if (s_settingsState.lite_restore_birthday < 0) s_settingsState.lite_restore_birthday = 0;
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
|
||||
"Block height to start scanning from. Leave 0 if unknown (slower full scan).");
|
||||
TR("lite_birthday_hint"));
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Account");
|
||||
ImGui::TextUnformatted(TR("lite_account_label"));
|
||||
ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
|
||||
ImGui::SetNextItemWidth(std::min(160.0f, liteInputW));
|
||||
ImGui::InputInt("##LiteRestoreAccount", &s_settingsState.lite_restore_account);
|
||||
if (s_settingsState.lite_restore_account < 0) s_settingsState.lite_restore_account = 0;
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::Checkbox("Overwrite##LiteRestoreOverwrite",
|
||||
ImGui::Checkbox(TrId("lite_overwrite", "LiteRestoreOverwrite").c_str(),
|
||||
&s_settingsState.lite_restore_overwrite);
|
||||
}
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Passphrase");
|
||||
ImGui::TextUnformatted(TR("lite_passphrase_label"));
|
||||
ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
|
||||
ImGui::SetNextItemWidth(liteInputW);
|
||||
ImGui::InputText("##LiteLifecyclePassphrase",
|
||||
@@ -1719,7 +1606,7 @@ void RenderSettingsPage(App* app) {
|
||||
const auto& result = lite->lastLifecycleResult();
|
||||
s_settingsState.lite_lifecycle_summary = result.bridgeResponseRedacted;
|
||||
if (result.walletReady) {
|
||||
s_settingsState.lite_lifecycle_status = "Wallet ready";
|
||||
s_settingsState.lite_lifecycle_status = TR("lite_wallet_ready");
|
||||
} else {
|
||||
s_settingsState.lite_lifecycle_status = result.error.empty()
|
||||
? result.status.message
|
||||
@@ -1735,7 +1622,7 @@ void RenderSettingsPage(App* app) {
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
const bool liteLifecycleBusy = s_settingsState.lite_lifecycle_pending;
|
||||
if (liteLifecycleBusy) ImGui::BeginDisabled();
|
||||
if (TactileButton("Validate##LiteLifecycleValidate", ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
if (TactileButton(TrId("lite_validate", "LiteLifecycleValidate").c_str(), ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
evaluateLiteLifecycleRequestFromPageState(app);
|
||||
}
|
||||
if (liteLifecycleBusy) ImGui::EndDisabled();
|
||||
@@ -1765,17 +1652,15 @@ void RenderSettingsPage(App* app) {
|
||||
if (app->liteWallet() && app->liteWallet()->walletOpen()) {
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
Type().text(TypeStyle::Body2, "Backup & keys");
|
||||
Type().text(TypeStyle::Body2, TR("lite_backup_keys"));
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
if (TactileButton("Show seed##LiteExportSeed", ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
if (TactileButton(TrId("lite_show_seed", "LiteExportSeed").c_str(), ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
auto r = app->liteWallet()->exportSeed();
|
||||
wallet::secureWipeLiteSecret(s_settingsState.lite_export_secret);
|
||||
if (r.ok) {
|
||||
s_settingsState.lite_export_secret = r.seedPhrase;
|
||||
s_settingsState.lite_export_label =
|
||||
"Seed phrase — the ONLY way to restore your wallet. "
|
||||
"Write it down, store it offline, never share it.";
|
||||
s_settingsState.lite_export_label = TR("lite_seed_warning");
|
||||
s_settingsState.lite_export_is_seed = true;
|
||||
s_settingsState.lite_export_birthday = r.birthday;
|
||||
s_settingsState.lite_backup_status.clear();
|
||||
@@ -1787,13 +1672,13 @@ void RenderSettingsPage(App* app) {
|
||||
wallet::secureWipeLiteSecret(r.seedPhrase); // wipe the result copy
|
||||
}
|
||||
ImGui::SameLine(0, Layout::spacingSm());
|
||||
if (TactileButton("Show private keys##LiteExportKeys", ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
if (TactileButton(TrId("lite_show_private_keys", "LiteExportKeys").c_str(), ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
auto r = app->liteWallet()->exportPrivateKeys();
|
||||
wallet::secureWipeLiteSecret(s_settingsState.lite_export_secret);
|
||||
s_settingsState.lite_export_is_seed = false; // keys, not a seed
|
||||
if (r.ok) {
|
||||
s_settingsState.lite_export_secret = r.privateKeysJson;
|
||||
s_settingsState.lite_export_label = "Private keys — anyone with these can spend your funds";
|
||||
s_settingsState.lite_export_label = TR("lite_private_keys_warning");
|
||||
s_settingsState.lite_backup_status.clear();
|
||||
} else {
|
||||
s_settingsState.lite_export_label.clear();
|
||||
@@ -1814,19 +1699,19 @@ void RenderSettingsPage(App* app) {
|
||||
// The seed's birthday is needed to restore quickly — show + back it up too.
|
||||
if (s_settingsState.lite_export_is_seed) {
|
||||
char bday[64];
|
||||
snprintf(bday, sizeof(bday), "Birthday: %llu (back this up too)",
|
||||
snprintf(bday, sizeof(bday), TR("lite_birthday_backup"),
|
||||
s_settingsState.lite_export_birthday);
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), bday);
|
||||
}
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
if (TactileButton("Copy##LiteExportCopy", ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
if (TactileButton(TrId("lite_copy", "LiteExportCopy").c_str(), ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
app->copySecretToClipboard(s_settingsState.lite_export_secret);
|
||||
}
|
||||
ImGui::SameLine(0, Layout::spacingSm());
|
||||
// Save the seed (+ birthday) to an owner-only (0600) file in the config dir.
|
||||
if (s_settingsState.lite_export_is_seed &&
|
||||
TactileButton("Save to file##LiteExportSave", ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
TactileButton(TrId("lite_save_to_file", "LiteExportSave").c_str(), ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
std::string path = util::Platform::getConfigDir() + "/lite-seed-backup.txt";
|
||||
std::string content = s_settingsState.lite_export_secret + "\nBirthday: " +
|
||||
std::to_string(s_settingsState.lite_export_birthday) + "\n";
|
||||
@@ -1834,11 +1719,11 @@ void RenderSettingsPage(App* app) {
|
||||
path, content, /*restrictPermissions=*/true);
|
||||
wallet::secureWipeLiteSecret(content);
|
||||
s_settingsState.lite_backup_status = ok
|
||||
? ("Saved (plaintext, owner-only) to " + path)
|
||||
: ("Could not write " + path);
|
||||
? (std::string(TR("lite_saved_to")) + path)
|
||||
: (std::string(TR("lite_could_not_write")) + path);
|
||||
}
|
||||
if (s_settingsState.lite_export_is_seed) ImGui::SameLine(0, Layout::spacingSm());
|
||||
if (TactileButton("Hide & wipe##LiteExportHide", ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
if (TactileButton(TrId("lite_hide_wipe", "LiteExportHide").c_str(), ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
wallet::secureWipeLiteSecret(s_settingsState.lite_export_secret);
|
||||
s_settingsState.lite_export_label.clear();
|
||||
s_settingsState.lite_export_is_seed = false;
|
||||
@@ -1848,18 +1733,18 @@ void RenderSettingsPage(App* app) {
|
||||
// Import a spending/viewing key (history appears after the next sync).
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Import key");
|
||||
ImGui::TextUnformatted(TR("lite_import_key_label"));
|
||||
ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
|
||||
ImGui::SetNextItemWidth(liteInputW);
|
||||
ImGui::InputText("##LiteImportKey", s_settingsState.lite_import_key,
|
||||
sizeof(s_settingsState.lite_import_key),
|
||||
ImGuiInputTextFlags_Password);
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
if (TactileButton("Import##LiteImportKeyBtn", ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
if (TactileButton(TrId("lite_import", "LiteImportKeyBtn").c_str(), ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
const auto r = app->liteWallet()->importKey(s_settingsState.lite_import_key);
|
||||
sodium_memzero(s_settingsState.lite_import_key, sizeof(s_settingsState.lite_import_key));
|
||||
s_settingsState.lite_backup_status =
|
||||
r.ok ? "Key imported — run a sync to scan its history" : r.error;
|
||||
r.ok ? TR("lite_key_imported") : r.error;
|
||||
}
|
||||
|
||||
if (!s_settingsState.lite_backup_status.empty()) {
|
||||
@@ -1871,57 +1756,57 @@ void RenderSettingsPage(App* app) {
|
||||
// ---- Security: passphrase encryption (encrypt / unlock / lock / decrypt) ----
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
Type().text(TypeStyle::Body2, "Security");
|
||||
Type().text(TypeStyle::Body2, TR("lite_security"));
|
||||
const auto& wstate = app->getWalletState();
|
||||
const float encLabelX = leftX - sectionOrigin.x + liteLabelW;
|
||||
if (!wstate.isEncrypted()) {
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Passphrase");
|
||||
ImGui::TextUnformatted(TR("lite_passphrase_label"));
|
||||
ImGui::SameLine(encLabelX);
|
||||
ImGui::SetNextItemWidth(liteInputW);
|
||||
ImGui::InputText("##LiteEncryptPass", s_settingsState.lite_enc_pass,
|
||||
sizeof(s_settingsState.lite_enc_pass), ImGuiInputTextFlags_Password);
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
if (TactileButton("Encrypt wallet##LiteEncrypt", ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
if (TactileButton(TrId("lite_encrypt_wallet", "LiteEncrypt").c_str(), ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
const auto r = app->liteWallet()->encryptWallet(s_settingsState.lite_enc_pass);
|
||||
sodium_memzero(s_settingsState.lite_enc_pass, sizeof(s_settingsState.lite_enc_pass));
|
||||
s_settingsState.lite_encryption_status = r.ok ? "Wallet encrypted" : r.error;
|
||||
s_settingsState.lite_encryption_status = r.ok ? TR("lite_wallet_encrypted") : r.error;
|
||||
}
|
||||
} else {
|
||||
if (wstate.isLocked()) {
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Unlock");
|
||||
ImGui::TextUnformatted(TR("lite_unlock"));
|
||||
ImGui::SameLine(encLabelX);
|
||||
ImGui::SetNextItemWidth(liteInputW);
|
||||
ImGui::InputText("##LiteUnlockPass", s_settingsState.lite_enc_pass,
|
||||
sizeof(s_settingsState.lite_enc_pass), ImGuiInputTextFlags_Password);
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
if (TactileButton("Unlock##LiteUnlock", ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
if (TactileButton(TrId("lite_unlock", "LiteUnlock").c_str(), ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
const bool ok = app->liteWallet()->unlockWallet(s_settingsState.lite_enc_pass);
|
||||
sodium_memzero(s_settingsState.lite_enc_pass, sizeof(s_settingsState.lite_enc_pass));
|
||||
s_settingsState.lite_encryption_status = ok ? "Wallet unlocked" : "Unlock failed";
|
||||
s_settingsState.lite_encryption_status = ok ? TR("lite_wallet_unlocked") : TR("lite_unlock_failed");
|
||||
}
|
||||
} else {
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
if (TactileButton("Lock now##LiteLock", ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
if (TactileButton(TrId("lite_lock_now", "LiteLock").c_str(), ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
s_settingsState.lite_encryption_status =
|
||||
app->liteWallet()->lockWallet() ? "Wallet locked" : "Lock failed";
|
||||
app->liteWallet()->lockWallet() ? TR("lite_wallet_locked") : TR("lite_lock_failed");
|
||||
}
|
||||
}
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Passphrase");
|
||||
ImGui::TextUnformatted(TR("lite_passphrase_label"));
|
||||
ImGui::SameLine(encLabelX);
|
||||
ImGui::SetNextItemWidth(liteInputW);
|
||||
ImGui::InputText("##LiteDecryptPass", s_settingsState.lite_dec_pass,
|
||||
sizeof(s_settingsState.lite_dec_pass), ImGuiInputTextFlags_Password);
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
if (TactileButton("Remove encryption##LiteDecrypt", ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
if (TactileButton(TrId("lite_remove_encryption", "LiteDecrypt").c_str(), ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
const auto r = app->liteWallet()->decryptWallet(s_settingsState.lite_dec_pass);
|
||||
sodium_memzero(s_settingsState.lite_dec_pass, sizeof(s_settingsState.lite_dec_pass));
|
||||
s_settingsState.lite_encryption_status = r.ok ? "Encryption removed" : r.error;
|
||||
s_settingsState.lite_encryption_status = r.ok ? TR("lite_encryption_removed") : r.error;
|
||||
}
|
||||
}
|
||||
if (!s_settingsState.lite_encryption_status.empty()) {
|
||||
@@ -2347,17 +2232,6 @@ void RenderSettingsPage(App* app) {
|
||||
ImGui::SetCursorScreenPos(ImVec2(sectionOrigin.x, ImGui::GetCursorScreenPos().y));
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Dummy(ImVec2(0, bottomPad));
|
||||
ImGui::Unindent(pad);
|
||||
|
||||
ImVec2 cardMax(cardMin.x + availWidth, ImGui::GetCursorScreenPos().y);
|
||||
dl->ChannelsSetCurrent(0);
|
||||
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
|
||||
dl->ChannelsMerge();
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
|
||||
ImGui::Dummy(ImVec2(availWidth, 0));
|
||||
}
|
||||
|
||||
ImGui::Dummy(ImVec2(0, gap));
|
||||
@@ -2369,11 +2243,7 @@ void RenderSettingsPage(App* app) {
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("explorer_section"));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
ImVec2 cardMin = ImGui::GetCursorScreenPos();
|
||||
dl->ChannelsSplit(2);
|
||||
dl->ChannelsSetCurrent(1);
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMin.y + pad));
|
||||
ImGui::Indent(pad);
|
||||
material::GlassCardScope card(dl, availWidth, pad, bottomPad, glassSpec);
|
||||
|
||||
float contentW = availWidth - pad * 2;
|
||||
ImGui::PushFont(body2);
|
||||
@@ -2413,17 +2283,6 @@ void RenderSettingsPage(App* app) {
|
||||
util::Platform::openUrl("https://explorer.dragonx.is");
|
||||
}
|
||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_block_explorer"));
|
||||
|
||||
ImGui::Dummy(ImVec2(0, bottomPad));
|
||||
ImGui::Unindent(pad);
|
||||
|
||||
ImVec2 cardMax(cardMin.x + availWidth, ImGui::GetCursorScreenPos().y);
|
||||
dl->ChannelsSetCurrent(0);
|
||||
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
|
||||
dl->ChannelsMerge();
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
|
||||
ImGui::Dummy(ImVec2(availWidth, 0));
|
||||
}
|
||||
|
||||
ImGui::Dummy(ImVec2(0, gap));
|
||||
@@ -2597,11 +2456,7 @@ void RenderSettingsPage(App* app) {
|
||||
if (s_settingsState.debug_expanded) {
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
ImVec2 cardMin = ImGui::GetCursorScreenPos();
|
||||
dl->ChannelsSplit(2);
|
||||
dl->ChannelsSetCurrent(1);
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMin.y + pad));
|
||||
ImGui::Indent(pad);
|
||||
material::GlassCardScope card(dl, availWidth, pad, bottomPad, glassSpec);
|
||||
|
||||
ImGui::TextColored(ImVec4(1,1,1,0.5f), "%s", TR("settings_debug_select"));
|
||||
ImGui::TextColored(ImVec4(1,1,1,0.35f), "%s", TR("settings_debug_restart_note"));
|
||||
@@ -2684,17 +2539,6 @@ void RenderSettingsPage(App* app) {
|
||||
}
|
||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_restart_daemon"));
|
||||
}
|
||||
|
||||
ImGui::Dummy(ImVec2(0, bottomPad));
|
||||
ImGui::Unindent(pad);
|
||||
|
||||
ImVec2 cardMax(cardMin.x + availWidth, ImGui::GetCursorScreenPos().y);
|
||||
dl->ChannelsSetCurrent(0);
|
||||
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
|
||||
dl->ChannelsMerge();
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
|
||||
ImGui::Dummy(ImVec2(availWidth, 0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2739,12 +2583,8 @@ void RenderSettingsPage(App* app) {
|
||||
// Confirmation dialog for clearing z-tx history
|
||||
if (s_settingsState.confirm_clear_ztx) {
|
||||
if (BeginOverlayDialog(TR("confirm_clear_ztx_title"), &s_settingsState.confirm_clear_ztx, 480.0f, 0.94f)) {
|
||||
ImGui::PushFont(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();
|
||||
@@ -2752,15 +2592,14 @@ void RenderSettingsPage(App* app) {
|
||||
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_btn").c_str(),
|
||||
true, doCancel, doConfirm);
|
||||
if (doCancel) {
|
||||
s_settingsState.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_btn").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");
|
||||
@@ -2769,7 +2608,6 @@ void RenderSettingsPage(App* app) {
|
||||
}
|
||||
s_settingsState.confirm_clear_ztx = false;
|
||||
}
|
||||
ImGui::PopStyleColor(2);
|
||||
EndOverlayDialog();
|
||||
}
|
||||
}
|
||||
@@ -2777,12 +2615,8 @@ void RenderSettingsPage(App* app) {
|
||||
// Confirmation dialog for deleting blockchain data
|
||||
if (s_settingsState.confirm_delete_blockchain) {
|
||||
if (BeginOverlayDialog(TR("confirm_delete_blockchain_title"), &s_settingsState.confirm_delete_blockchain, 500.0f, 0.94f)) {
|
||||
ImGui::PushFont(Type().iconLarge());
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), ICON_MD_WARNING);
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "%s", TR("warning"));
|
||||
|
||||
material::DialogWarningHeader(TR("warning"), ImVec4(1.0f, 0.3f, 0.3f, 1.0f));
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped("%s", TR("confirm_delete_blockchain_msg"));
|
||||
ImGui::Spacing();
|
||||
@@ -2790,20 +2624,18 @@ void RenderSettingsPage(App* app) {
|
||||
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("delete_blockchain_confirm", "del_bc_btn").c_str(),
|
||||
true, doCancel, doConfirm);
|
||||
if (doCancel) {
|
||||
s_settingsState.confirm_delete_blockchain = 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("delete_blockchain_confirm", "del_bc_btn").c_str(), ImVec2(btnW, 40))) {
|
||||
if (doConfirm) {
|
||||
if (app->supportsFullNodeLifecycleActions())
|
||||
app->deleteBlockchainData();
|
||||
s_settingsState.confirm_delete_blockchain = false;
|
||||
}
|
||||
ImGui::PopStyleColor(2);
|
||||
EndOverlayDialog();
|
||||
}
|
||||
}
|
||||
@@ -2825,11 +2657,7 @@ void RenderSettingsPage(App* app) {
|
||||
}
|
||||
|
||||
if (BeginOverlayDialog(TR("confirm_rescan_title"), &s_settingsState.confirm_rescan, 500.0f, 0.94f)) {
|
||||
ImGui::PushFont(Type().iconLarge());
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), ICON_MD_WARNING);
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "%s", TR("warning"));
|
||||
material::DialogWarningHeader(TR("warning"), ImVec4(1.0f, 0.8f, 0.2f, 1.0f));
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
@@ -2877,11 +2705,7 @@ void RenderSettingsPage(App* app) {
|
||||
// Confirm: repair wallet (-zapwallettxes=2 — wipe & rebuild wallet tx records, then rescan)
|
||||
if (s_settingsState.confirm_repair_wallet) {
|
||||
if (BeginOverlayDialog(TR("confirm_repair_wallet_title"), &s_settingsState.confirm_repair_wallet, 500.0f, 0.94f)) {
|
||||
ImGui::PushFont(Type().iconLarge());
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), ICON_MD_WARNING);
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "%s", TR("warning"));
|
||||
material::DialogWarningHeader(TR("warning"), ImVec4(1.0f, 0.8f, 0.2f, 1.0f));
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped("%s", TR("confirm_repair_wallet_msg"));
|
||||
@@ -2891,12 +2715,14 @@ void RenderSettingsPage(App* app) {
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
float btnW = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
|
||||
if (ImGui::Button(TrId("cancel", "repair_wallet_cancel").c_str(), ImVec2(btnW, 40))) {
|
||||
bool doCancel = false, doConfirm = false;
|
||||
material::DialogConfirmFooter(TrId("cancel", "repair_wallet_cancel").c_str(),
|
||||
TrId("repair_wallet", "repair_wallet_confirm").c_str(),
|
||||
false, doCancel, doConfirm);
|
||||
if (doCancel) {
|
||||
s_settingsState.confirm_repair_wallet = false;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(TrId("repair_wallet", "repair_wallet_confirm").c_str(), ImVec2(btnW, 40))) {
|
||||
if (doConfirm) {
|
||||
app->repairWallet();
|
||||
s_settingsState.confirm_repair_wallet = false;
|
||||
}
|
||||
@@ -2907,11 +2733,7 @@ void RenderSettingsPage(App* app) {
|
||||
// Confirm: reinstall the bundled daemon binary (stop → overwrite → restart)
|
||||
if (s_settingsState.confirm_reinstall_daemon) {
|
||||
if (BeginOverlayDialog(TR("confirm_reinstall_daemon_title"), &s_settingsState.confirm_reinstall_daemon, 500.0f, 0.94f)) {
|
||||
ImGui::PushFont(Type().iconLarge());
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), ICON_MD_WARNING);
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "%s", TR("warning"));
|
||||
material::DialogWarningHeader(TR("warning"), ImVec4(1.0f, 0.8f, 0.2f, 1.0f));
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped("%s", TR("confirm_reinstall_daemon_msg"));
|
||||
@@ -2921,12 +2743,14 @@ void RenderSettingsPage(App* app) {
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
float btnW = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
|
||||
if (ImGui::Button(TrId("cancel", "reinstall_daemon_cancel").c_str(), ImVec2(btnW, 40))) {
|
||||
bool doCancel = false, doConfirm = false;
|
||||
material::DialogConfirmFooter(TrId("cancel", "reinstall_daemon_cancel").c_str(),
|
||||
TrId("daemon_install_bundled", "reinstall_daemon_confirm").c_str(),
|
||||
false, doCancel, doConfirm);
|
||||
if (doCancel) {
|
||||
s_settingsState.confirm_reinstall_daemon = false;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(TrId("daemon_install_bundled", "reinstall_daemon_confirm").c_str(), ImVec2(btnW, 40))) {
|
||||
if (doConfirm) {
|
||||
app->reinstallBundledDaemon();
|
||||
s_settingsState.daemon_info_loaded = false; // refresh the panel after the swap
|
||||
s_settingsState.confirm_reinstall_daemon = false;
|
||||
@@ -2938,11 +2762,7 @@ void RenderSettingsPage(App* app) {
|
||||
// Confirm: restart daemon (briefly drops the connection to apply changed options)
|
||||
if (s_settingsState.confirm_restart_daemon) {
|
||||
if (BeginOverlayDialog(TR("confirm_restart_daemon_title"), &s_settingsState.confirm_restart_daemon, 500.0f, 0.94f)) {
|
||||
ImGui::PushFont(Type().iconLarge());
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), ICON_MD_WARNING);
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "%s", TR("warning"));
|
||||
material::DialogWarningHeader(TR("warning"), ImVec4(1.0f, 0.8f, 0.2f, 1.0f));
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped("%s", TR("confirm_restart_daemon_msg"));
|
||||
@@ -2950,12 +2770,14 @@ void RenderSettingsPage(App* app) {
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
float btnW = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
|
||||
if (ImGui::Button(TrId("cancel", "restartd_cancel").c_str(), ImVec2(btnW, 40))) {
|
||||
bool doCancel = false, doConfirm = false;
|
||||
material::DialogConfirmFooter(TrId("cancel", "restartd_cancel").c_str(),
|
||||
TrId("settings_restart_daemon", "restartd_confirm").c_str(),
|
||||
false, doCancel, doConfirm);
|
||||
if (doCancel) {
|
||||
s_settingsState.confirm_restart_daemon = false;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(TrId("settings_restart_daemon", "restartd_confirm").c_str(), ImVec2(btnW, 40))) {
|
||||
if (doConfirm) {
|
||||
s_settingsState.debug_cats_dirty = false;
|
||||
app->restartDaemon();
|
||||
s_settingsState.confirm_restart_daemon = false;
|
||||
@@ -2967,11 +2789,7 @@ void RenderSettingsPage(App* app) {
|
||||
// Confirm: lite wallet re-download blocks (rescan from the lite server — long but safe)
|
||||
if (s_settingsState.confirm_lite_redownload) {
|
||||
if (BeginOverlayDialog(TR("confirm_lite_redownload_title"), &s_settingsState.confirm_lite_redownload, 500.0f, 0.94f)) {
|
||||
ImGui::PushFont(Type().iconLarge());
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), ICON_MD_WARNING);
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "%s", TR("warning"));
|
||||
material::DialogWarningHeader(TR("warning"), ImVec4(1.0f, 0.8f, 0.2f, 1.0f));
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped("%s", TR("confirm_lite_redownload_msg"));
|
||||
@@ -2981,12 +2799,14 @@ void RenderSettingsPage(App* app) {
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
float btnW = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
|
||||
if (ImGui::Button(TrId("cancel", "lite_redl_cancel").c_str(), ImVec2(btnW, 40))) {
|
||||
bool doCancel = false, doConfirm = false;
|
||||
material::DialogConfirmFooter(TrId("cancel", "lite_redl_cancel").c_str(),
|
||||
TrId("lite_redownload_blocks", "lite_redl_confirm").c_str(),
|
||||
false, doCancel, doConfirm);
|
||||
if (doCancel) {
|
||||
s_settingsState.confirm_lite_redownload = false;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(TrId("lite_redownload_blocks", "lite_redl_confirm").c_str(), ImVec2(btnW, 40))) {
|
||||
if (doConfirm) {
|
||||
if (auto* lite = app->liteWallet()) lite->startRescan();
|
||||
s_settingsState.confirm_lite_redownload = false;
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
};
|
||||
|
||||
@@ -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 history — collecting 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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -452,6 +452,49 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["confirm_lite_redownload_title"] = "Redownload Blocks";
|
||||
strings_["confirm_lite_redownload_msg"] = "This clears the wallet's downloaded block data and re-fetches and re-scans every block from the lite server. It can take a while; the wallet shows sync progress until it finishes.";
|
||||
strings_["confirm_lite_redownload_safe"] = "Your wallet, keys, and seed are not affected — only the block data is re-downloaded.";
|
||||
// Lite wallet lifecycle / backup / security section
|
||||
strings_["lite_servers_network_tab"] = "Lite servers are managed in the Network tab.";
|
||||
strings_["lite_wallet_request"] = "Lite wallet request";
|
||||
strings_["lite_action"] = "Action";
|
||||
strings_["lite_op_create"] = "Create";
|
||||
strings_["lite_op_open"] = "Open";
|
||||
strings_["lite_op_restore"] = "Restore";
|
||||
strings_["lite_wallet_label"] = "Wallet";
|
||||
strings_["lite_seed_label"] = "Seed";
|
||||
strings_["lite_word_count"] = "%d / 24 words";
|
||||
strings_["lite_birthday_label"] = "Birthday";
|
||||
strings_["lite_birthday_hint"] = "Block height to start scanning from. Leave 0 if unknown (slower full scan).";
|
||||
strings_["lite_account_label"] = "Account";
|
||||
strings_["lite_overwrite"] = "Overwrite";
|
||||
strings_["lite_passphrase_label"] = "Passphrase";
|
||||
strings_["lite_validate"] = "Validate";
|
||||
strings_["lite_working"] = "Working…";
|
||||
strings_["lite_wallet_ready"] = "Wallet ready";
|
||||
strings_["lite_backup_keys"] = "Backup & keys";
|
||||
strings_["lite_show_seed"] = "Show seed";
|
||||
strings_["lite_seed_warning"] = "Seed phrase — the ONLY way to restore your wallet. Write it down, store it offline, never share it.";
|
||||
strings_["lite_show_private_keys"] = "Show private keys";
|
||||
strings_["lite_private_keys_warning"] = "Private keys — anyone with these can spend your funds";
|
||||
strings_["lite_birthday_backup"] = "Birthday: %llu (back this up too)";
|
||||
strings_["lite_copy"] = "Copy";
|
||||
strings_["lite_save_to_file"] = "Save to file";
|
||||
strings_["lite_saved_to"] = "Saved (plaintext, owner-only) to ";
|
||||
strings_["lite_could_not_write"] = "Could not write ";
|
||||
strings_["lite_hide_wipe"] = "Hide & wipe";
|
||||
strings_["lite_import_key_label"] = "Import key";
|
||||
strings_["lite_import"] = "Import";
|
||||
strings_["lite_key_imported"] = "Key imported — run a sync to scan its history";
|
||||
strings_["lite_security"] = "Security";
|
||||
strings_["lite_encrypt_wallet"] = "Encrypt wallet";
|
||||
strings_["lite_wallet_encrypted"] = "Wallet encrypted";
|
||||
strings_["lite_unlock"] = "Unlock";
|
||||
strings_["lite_wallet_unlocked"] = "Wallet unlocked";
|
||||
strings_["lite_unlock_failed"] = "Unlock failed";
|
||||
strings_["lite_lock_now"] = "Lock now";
|
||||
strings_["lite_wallet_locked"] = "Wallet locked";
|
||||
strings_["lite_lock_failed"] = "Lock failed";
|
||||
strings_["lite_remove_encryption"] = "Remove encryption";
|
||||
strings_["lite_encryption_removed"] = "Encryption removed";
|
||||
strings_["tt_encrypt"] = "Encrypt wallet.dat with a passphrase";
|
||||
strings_["tt_change_pass"] = "Change the wallet encryption passphrase";
|
||||
strings_["tt_lock"] = "Lock the wallet immediately";
|
||||
@@ -652,6 +695,15 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["transparent"] = "Transparent";
|
||||
strings_["total"] = "Total";
|
||||
strings_["unconfirmed"] = "Unconfirmed";
|
||||
strings_["recent_transactions"] = "RECENT TRANSACTIONS";
|
||||
strings_["view_all"] = "View All";
|
||||
strings_["no_transactions_yet"] = "No transactions yet";
|
||||
strings_["privacy_great"] = "Great privacy!";
|
||||
strings_["privacy_medium"] = "Consider shielding more";
|
||||
strings_["privacy_low"] = "Low privacy — shield funds";
|
||||
strings_["balance_history_collecting"] = "Balance history — collecting data...";
|
||||
strings_["balance_shielded_fmt"] = "Shielded: %.8f";
|
||||
strings_["balance_transparent_fmt"] = "Transparent: %.8f";
|
||||
strings_["your_addresses"] = "Your Addresses";
|
||||
strings_["z_addresses"] = "Z-Addresses";
|
||||
strings_["t_addresses"] = "T-Addresses";
|
||||
|
||||
@@ -45,6 +45,21 @@ std::string formatTimeAgoShort(std::int64_t timestamp)
|
||||
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;
|
||||
|
||||
@@ -24,5 +24,14 @@ std::string formatTimeAgoShort(std::int64_t timestamp);
|
||||
// An empty needle matches everything. Safe for per-frame filtering of large lists.
|
||||
bool containsIgnoreCase(std::string_view haystack, std::string_view needle);
|
||||
|
||||
// Middle-ellipsis truncation, half/half form: "prefix...suffix" so the total (including "...")
|
||||
// is at most maxLen. Guards maxLen <= 3 (returns s unchanged) to avoid unsigned underflow on the
|
||||
// (maxLen - 3) split. Strings already at/under maxLen are returned unchanged.
|
||||
std::string truncateMiddle(const std::string& s, int maxLen);
|
||||
|
||||
// Middle-ellipsis truncation with explicit prefix/suffix lengths: "<front chars>...<back chars>".
|
||||
// Returns s unchanged when it is no longer than front + back + 3 (the "..." would not save space).
|
||||
std::string truncateMiddle(const std::string& s, int front, int back);
|
||||
|
||||
} // namespace util
|
||||
} // namespace dragonx
|
||||
|
||||
Reference in New Issue
Block a user