diff --git a/src/ui/windows/mining_earnings.cpp b/src/ui/windows/mining_earnings.cpp index d529b73..87f5048 100644 --- a/src/ui/windows/mining_earnings.cpp +++ b/src/ui/windows/mining_earnings.cpp @@ -191,18 +191,18 @@ void RenderMiningEarnings(App* app, const WalletState& state, const MiningInfo& }; snprintf(valBuf, sizeof(valBuf), "+%.4f", minedToday); - snprintf(subBuf2, sizeof(subBuf2), "(%d txn)", minedTodayCount); + snprintf(subBuf2, sizeof(subBuf2), TR("mining_txn_count"), minedTodayCount); char todayVal[64], todaySub[64]; strncpy(todayVal, valBuf, sizeof(todayVal)); strncpy(todaySub, subBuf2, sizeof(todaySub)); char yesterdayVal[64], yesterdaySub[64]; snprintf(yesterdayVal, sizeof(yesterdayVal), "+%.4f", minedYesterday); - snprintf(yesterdaySub, sizeof(yesterdaySub), "(%d txn)", minedYesterdayCount); + snprintf(yesterdaySub, sizeof(yesterdaySub), TR("mining_txn_count"), minedYesterdayCount); char allVal[64], allSub[64]; snprintf(allVal, sizeof(allVal), "+%.4f", minedAllTime); - snprintf(allSub, sizeof(allSub), "(%d txn)", minedAllTimeCount); + snprintf(allSub, sizeof(allSub), TR("mining_txn_count"), minedAllTimeCount); char estVal[64]; if (estActive) @@ -324,6 +324,11 @@ void RenderMiningEarnings(App* app, const WalletState& state, const MiningInfo& // -- Mining Address -- dl->AddText(capFont, capFont->LegacySize, ImVec2(col3X, cy), OnSurfaceMedium(), TR("mining_mining_addr")); + { + ImVec2 lblSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, TR("mining_mining_addr")); + if (material::IsRectHovered(ImVec2(col3X, cy), ImVec2(col3X + lblSz.x, cy + lblSz.y))) + material::Tooltip("%s", TR("mining_addr_explainer")); + } std::string mining_address = ""; for (const auto& addr : state.addresses) { if (addr.type == "transparent" && !addr.address.empty()) { @@ -351,6 +356,12 @@ void RenderMiningEarnings(App* app, const WalletState& state, const MiningInfo& ImVec2(Layout::spacingMd(), 4 * dp))) { Notifications::instance().info(TR("mining_address_copied")); } + } else { + // No mining (transparent) address yet — explain where rewards go instead of a blank. + dl->AddText(capFont, capFont->LegacySize, ImVec2(col3X + valOffX, cy), OnSurfaceMedium(), TR("mining_addr_none")); + ImVec2 emSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, TR("mining_addr_none")); + if (material::IsRectHovered(ImVec2(col3X + valOffX, cy), ImVec2(col3X + valOffX + emSz.x, cy + emSz.y))) + material::Tooltip("%s", TR("mining_addr_none_tip")); } } diff --git a/src/ui/windows/mining_mode_toggle.cpp b/src/ui/windows/mining_mode_toggle.cpp index a054bf9..5f1f892 100644 --- a/src/ui/windows/mining_mode_toggle.cpp +++ b/src/ui/windows/mining_mode_toggle.cpp @@ -146,6 +146,8 @@ void RenderMiningModeToggle(App* app, const WalletState& state, const MiningInfo ImGui::SetCursorScreenPos(ImVec2(tMin.x, tMax.y)); ImGui::Dummy(ImVec2(totalW, 0)); + bool payoutBadThisFrame = false; // set when the inline payout error is drawn; reserves a row below + // Pool URL + Worker inputs inline next to toggle (pool mode only) if (s_pool_mode && soloMiningActive) { // Solo mining is active — show disabled message instead of inputs @@ -427,6 +429,24 @@ void RenderMiningModeToggle(App* app, const WalletState& state, const MiningInfo } } + // Persistent inline payout error — a hover-only tooltip is missed by anyone who doesn't hover, + // so outline the field in red and draw a short reason below it (draw-list only; no layout calls + // here, the cursor is hand-positioned). The field height is reserved at the end of the function. + { + const ImVec2 wMin = ImGui::GetItemRectMin(); + const ImVec2 wMax = ImGui::GetItemRectMax(); + const std::string wk(s_pool_worker); + if (!wk.empty() && wk != "x" && !util::isValidRecipientAddress(wk)) { + payoutBadThisFrame = true; + dl->AddRect(wMin, wMax, Error(), 4.0f * dp, 0, 1.5f * dp); + const char* msg = util::isTransparentAddress(wk) + ? TR("mining_payout_needs_shielded_short") : TR("mining_payout_invalid_short"); + std::string shown = material::TruncateToWidth(msg, capFont, capFont->LegacySize, wrkGroupW); + dl->AddText(capFont, capFont->LegacySize, ImVec2(wMin.x, wMax.y + 1.0f * dp), + ReadableError(), shown.c_str()); + } + } + // --- Worker: Dropdown arrow button --- ImGui::SameLine(0, 0); { @@ -642,7 +662,9 @@ void RenderMiningModeToggle(App* app, const WalletState& state, const MiningInfo // Ensure cursor Y is at toggle bottom regardless of pool input widgets, // so the cards below stay at the same position in both solo and pool modes. ImGui::SetCursorScreenPos(ImVec2(tMin.x, tMax.y)); - ImGui::Dummy(ImVec2(0, gap * 0.5f)); + // Reserve a row for the inline payout error so it doesn't overlap the card below (cards shift down + // transiently while the error is shown). + ImGui::Dummy(ImVec2(0, gap * 0.5f + (payoutBadThisFrame ? capFont->LegacySize + 2.0f * dp : 0.0f))); } } // namespace ui diff --git a/src/ui/windows/mining_stats.cpp b/src/ui/windows/mining_stats.cpp index 704578b..98edbde 100644 --- a/src/ui/windows/mining_stats.cpp +++ b/src/ui/windows/mining_stats.cpp @@ -243,9 +243,9 @@ static void RenderLeftPoolCard(App* app, const WalletState& state, ImDrawList* d { char uptBuf[64]; int64_t up = pm.uptime_sec; - if (up <= 0) snprintf(uptBuf, sizeof(uptBuf), "N/A"); - else if (up < 3600) snprintf(uptBuf, sizeof(uptBuf), "%lldm %llds", (long long)(up / 60), (long long)(up % 60)); - else snprintf(uptBuf, sizeof(uptBuf), "%lldh %lldm", (long long)(up / 3600), (long long)((up % 3600) / 60)); + if (up <= 0) snprintf(uptBuf, sizeof(uptBuf), "%s", TR("grpc_na")); + else if (up < 3600) snprintf(uptBuf, sizeof(uptBuf), TR("mining_uptime_ms"), (long long)(up / 60), (long long)(up % 60)); + else snprintf(uptBuf, sizeof(uptBuf), TR("mining_uptime_hm"), (long long)(up / 3600), (long long)((up % 3600) / 60)); metricRow(TR("mining_uptime"), uptBuf, OnSurface()); } // Connection status — replaces the old transient "Connecting" banner, with a timeout so a bad URL / diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index 47f1a41..aae6678 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -278,6 +278,10 @@ void I18n::loadBuiltinEnglish() strings_["chat_export_warn"] = "Saves the decrypted messages as plain text. Store the file securely."; strings_["chat_export_done"] = "Conversation exported"; strings_["chat_export_failed"] = "Could not write the export file."; + strings_["chat_export_confirm_title"] = "Export chat as plain text?"; + strings_["chat_export_confirm_body"] = "This saves the decrypted messages as an UNENCRYPTED plain-text file. Anyone who can open the file can read the whole conversation. Store it securely and delete it when you no longer need it."; + strings_["chat_export_confirm_path"] = "Will be saved to:"; + strings_["chat_export_confirm_btn"] = "Export"; strings_["chat_len_over"] = "Message too long"; strings_["chat_mute"] = "Mute"; strings_["chat_unmute"] = "Unmute"; @@ -2319,6 +2323,8 @@ void I18n::loadBuiltinEnglish() strings_["send_mining_addr_short"] = "Mining address — can't be paid"; strings_["send_mining_addr_blocked"] = "This is a mining (transparent) address. On DragonX these can only receive mining rewards — the network rejects payments to them. Ask the recipient for a shielded address (it starts with zs)."; strings_["mining_payout_needs_shielded"] = "Pools pay out with ordinary transactions, which can't reach a mining (transparent, R…) address on DragonX. Use a shielded address (it starts with zs) as your payout address."; + strings_["mining_payout_invalid_short"] = "Not a valid DragonX address"; + strings_["mining_payout_needs_shielded_short"] = "Use a shielded (zs…) address, not a mining address"; strings_["new_mining_address"] = "New mining address"; strings_["tt_new_mining_address"] = "Creates a transparent (R…) address for solo-mining rewards. It can't receive payments — share a shielded address for those."; strings_["mining_address"] = "Mining address"; @@ -2335,6 +2341,8 @@ void I18n::loadBuiltinEnglish() strings_["failed_create_mining_address"] = "Failed to create mining address"; strings_["new_mining_address_created"] = "New mining address created"; strings_["mining_addr_explainer"] = "Receives solo-mining rewards only — the network rejects payments to it. Rewards are shielded automatically once they mature."; + strings_["mining_addr_none"] = "None yet"; + strings_["mining_addr_none_tip"] = "No mining address yet. Create one in the Receive tab (New mining address). Solo-mining rewards are paid to a transparent (R...) mining address, then shielded automatically once they mature."; strings_["receive_use_shielded"] = "Use a shielded address"; strings_["receive_not_for_payments"] = "Not for payments"; strings_["validate_mining_type"] = "Mining address (transparent)"; @@ -2392,6 +2400,9 @@ void I18n::loadBuiltinEnglish() strings_["mining_to_save"] = "to save"; strings_["mining_today"] = "Today"; strings_["mining_uptime"] = "Uptime"; + strings_["mining_txn_count"] = "(%d txn)"; + strings_["mining_uptime_ms"] = "%lldm %llds"; + strings_["mining_uptime_hm"] = "%lldh %lldm"; strings_["mining_yesterday"] = "Yesterday"; // --- Miner (xmrig) updater ---