diff --git a/src/app_sweep.cpp b/src/app_sweep.cpp index 85df33f..840ec56 100644 --- a/src/app_sweep.cpp +++ b/src/app_sweep.cpp @@ -20,6 +20,7 @@ #include "ui/windows/send_tab.h" #include "ui/windows/wallets_dialog.h" #include "ui/windows/daemon_download_dialog.h" +#include "ui/windows/xmrig_download_dialog.h" #include "util/platform.h" #include "wallet/wallet_capabilities.h" @@ -476,6 +477,33 @@ void App::buildSweepCatalog() }, [](App&) { ui::DaemonUpdateDialog::sweepClose(); }); + // Miner (xmrig) updater — same two-pane version picker, seeded with fake releases for the sweep. + add("modal-xmrig-update", ui::NavPage::Mining, + [](App& a) { + auto mk = [](const char* tag, const char* name, const char* date, const char* body, bool pre) { + util::XmrigRelease r; r.ok = true; r.tag = tag; r.name = name; r.body = body; + r.prerelease = pre; r.publishedAt = std::string(date) + "T10:00:00Z"; + util::XmrigReleaseAsset as; + as.name = std::string("drg-xmrig-") + tag + "-linux-x64.zip"; + as.downloadUrl = "https://git.dragonx.is/" + as.name; as.size = 8000000; + r.assets.push_back(as); + return r; + }; + std::vector rels; + rels.push_back(mk("v6.25.3", "DRG-XMRig v6.25.3", "2026-06-20", + "## What's new\n- Rebased on upstream XMRig 6.25.3\n- **RandomX** JIT speedups on modern CPUs\n" + "- Fix `--cpu-priority` parsing on Windows\n\n## Checksums\n| File | SHA-256 |\n" + "|---|---|\n| drg-xmrig-v6.25.3-linux-x64.zip | `ab12cd34` |\n", false)); + rels.push_back(mk("v6.24.0", "DRG-XMRig v6.24.0", "2026-04-30", + "## Notes\n- Pool TLS fixes\n- Lower idle CPU\n", false)); + rels.push_back(mk("v6.23.0", "DRG-XMRig v6.23.0", "2026-03-11", + "- First DRG-XMRig build\n", false)); + rels.push_back(mk("v6.26.0-rc1", "DRG-XMRig v6.26.0-rc1", "2026-07-02", + "Release candidate. **Testing only.**\n", true)); + ui::XmrigDownloadDialog::sweepSeed(&a, rels, util::XmrigUpdater::State::ReleaseList, "v6.24.0"); + }, + [](App&) { ui::XmrigDownloadDialog::sweepClose(); }); + // In-app folder picker (Wallets → Scan another folder). Populate a small demo tree so the // list shows sub-folders + wallet files, then open the wallets dialog + the picker over it. add("modal-folder-picker", ui::NavPage::Settings, diff --git a/src/ui/windows/xmrig_download_dialog.h b/src/ui/windows/xmrig_download_dialog.h index 533073a..06a7668 100644 --- a/src/ui/windows/xmrig_download_dialog.h +++ b/src/ui/windows/xmrig_download_dialog.h @@ -3,13 +3,18 @@ // Released under the GPLv3 // // Modal dialog to download / update the DRG-XMRig miner from the project Gitea, driven by -// util::XmrigUpdater. States follow the updater: Checking -> UpToDate/UpdateAvailable -> -// Downloading/Verifying/Extracting -> Done / Failed. Header-only (static inline state), mirroring -// BootstrapDownloadDialog. The installed release tag is persisted to settings on success. +// util::XmrigUpdater. Two-pane layout (mirrors the node updater / Manage-Portfolio editor): the list +// of miner versions on the LEFT, the selected version's detail + install action on the RIGHT. +// States: Listing -> ReleaseList -> Downloading/Verifying/Extracting -> Done / Failed. Header-only. +// The installed release tag is persisted to settings on success. Installs are refused while the +// miner is running (you can't replace a running binary). #pragma once +#include +#include #include +#include #include #include @@ -18,9 +23,10 @@ #include "../../resources/embedded_resources.h" // resources::getDaemonDirectory() #include "../../util/i18n.h" #include "../../util/xmrig_updater.h" -#include "../material/draw_helpers.h" -#include "../material/type.h" #include "../material/colors.h" +#include "../material/draw_helpers.h" +#include "../material/markdown.h" +#include "../material/type.h" #include "../theme.h" #include "release_list_view.h" #include "imgui.h" @@ -30,151 +36,345 @@ namespace ui { class XmrigDownloadDialog { public: + using Progress = util::XmrigUpdater::Progress; + using St = util::XmrigUpdater::State; + static void show(App* app) { if (!app) return; s_app = app; s_open = true; s_persisted = false; + s_sel = 0; + s_sweep = false; s_installed_tag = app->settings() ? app->settings()->getXmrigVersion() : std::string(); s_rows.clear(); s_releases.clear(); s_updater = std::make_unique(); - s_updater->startCheck(s_installed_tag); + s_updater->startListReleases(); // fetch every version up front for the two-pane picker } static bool isOpen() { return s_open; } + // Sweep-only: seed fake releases + a forced state so the offline UI sweep can render the modal. + static void sweepSeed(App* app, std::vector releases, St state, + const std::string& installedTag) { + s_app = app; + s_open = true; + s_persisted = false; + s_installed_tag = installedTag; + s_sel = 0; + s_sweep = true; + s_releases = std::move(releases); + s_rows.clear(); + s_updater.reset(); + s_sweepProgress = Progress{}; + s_sweepProgress.state = state; + if (!s_releases.empty()) s_sweepProgress.latest_tag = s_releases.front().tag; + s_sweepProgress.installed_tag = installedTag; + s_sweepProgress.percent = 62.0f; + s_sweepProgress.status_text = "drg-xmrig-linux-x64.zip"; + } + static void sweepClose() { s_open = false; s_sweep = false; s_releases.clear(); s_rows.clear(); } + static void render() { - if (!s_open || !s_app || !s_updater) { - if (!s_open) s_updater.reset(); // closed: drop the updater (dtor joins the worker) + if (!s_open || !s_app) { + if (!s_open) s_updater.reset(); return; } using namespace material; const float dp = Layout::dpiScale(); - const auto p = s_updater->getProgress(); - // No window X during an active download/verify/extract — closing would orphan/block on the - // worker; the in-dialog Cancel is the intended way to abort. + const Progress p = s_sweep ? s_sweepProgress + : (s_updater ? s_updater->getProgress() : Progress{}); const bool active = (p.state == St::Downloading || p.state == St::Verifying || p.state == St::Extracting); OverlayDialogSpec ov; - ov.title = TR("xmrig_update_title"); ov.p_open = active ? nullptr : &s_open; - ov.style = OverlayStyle::BlurFloat; ov.cardWidth = 480.0f; ov.cardBottomViewportRatio = 0.94f; + ov.title = TR("xmrig_update_title"); + ov.p_open = active ? nullptr : &s_open; + ov.style = OverlayStyle::BlurFloat; + ov.cardWidth = 880.0f; + ov.cardHeight = std::min(760.0f, ImGui::GetMainViewport()->Size.y * 0.84f / dp); + ov.idSuffix = "xmrigupd"; if (BeginOverlayDialog(ov)) { switch (p.state) { - case St::Checking: renderChecking(dp, p); break; + case St::Idle: + case St::Checking: case St::UpToDate: - case St::UpdateAvailable: renderPrompt(dp, p); break; - case St::Unavailable: renderUnavailable(dp, p); break; - case St::Listing: renderListing(dp, p); break; - case St::ReleaseList: renderReleaseList(dp, p); break; - case St::Downloading: - case St::Verifying: - case St::Extracting: renderProgress(dp, p); break; - case St::Done: renderDone(dp, p); break; - case St::Failed: renderFailed(dp, p); break; - default: break; + case St::UpdateAvailable: + case St::Listing: renderCentered(TR("xmrig_loading_releases")); break; + case St::Unavailable: renderUnavailable(p); break; + case St::Failed: + if (s_releases.empty()) { renderListFailed(p); break; } + renderTwoPane(dp, p, active); break; + default: renderTwoPane(dp, p, active); break; } EndOverlayDialog(); } - if (!s_open) s_updater.reset(); + if (!s_open) { s_updater.reset(); s_sweep = false; } } private: - using Progress = util::XmrigUpdater::Progress; - using St = util::XmrigUpdater::State; - static float fullW() { return ImGui::GetContentRegionAvail().x; } - // Install button that refuses (with a note) while the miner is running, re-checked live so a - // dialog opened before mining started can't replace a now-running binary (TOCTOU guard). - static void installAction(const char* label) { + static void renderCentered(const char* msg) { using namespace material; - if (s_app->isPoolMinerRunning()) { - Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), - TR("xmrig_stop_mining_first")); - return; - } - if (TactileButton(label, ImVec2(fullW(), 0))) - s_updater->startInstall(resources::getDaemonDirectory()); + ImVec2 av = ImGui::GetContentRegionAvail(); + ImFont* f = Type().body2(); + ImVec2 ts = f->CalcTextSizeA(f->LegacySize, FLT_MAX, 0, msg); + ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + std::max(0.0f, (av.x - ts.x) * 0.5f), + ImGui::GetCursorPosY() + av.y * 0.45f)); + Type().text(TypeStyle::Body2, msg); } - static void renderChecking(float, const Progress&) { - using namespace material; - Type().text(TypeStyle::Body2, TR("xmrig_checking")); - } - - static void renderUnavailable(float, const Progress& p) { + static void renderUnavailable(const Progress& p) { using namespace material; Type().text(TypeStyle::Subtitle2, TR("xmrig_unavailable_title")); ImGui::Spacing(); - ImGui::TextWrapped("%s", p.status_text.empty() - ? TR("xmrig_unavailable_body") - : p.status_text.c_str()); + ImGui::TextWrapped("%s", p.status_text.empty() ? TR("xmrig_unavailable_body") + : p.status_text.c_str()); ImGui::Spacing(); - if (TactileButton(TR("close"), ImVec2(fullW(), 0))) s_open = false; + if (TactileButton(TR("close"), ImVec2(std::min(fullW(), 200.0f * Layout::dpiScale()), 0))) + s_open = false; } - static void renderPrompt(float, const Progress& p) { + static void renderListFailed(const Progress& p) { using namespace material; - const std::string installed = p.installed_tag.empty() ? "none" : p.installed_tag; - if (p.state == St::UpdateAvailable) { - Type().text(TypeStyle::Subtitle2, TR("xmrig_update_available")); - ImGui::Spacing(); - ImGui::Text("%s %s", TR("xmrig_latest"), p.latest_tag.c_str()); - ImGui::Text("%s %s", TR("xmrig_installed"), installed.c_str()); - ImGui::Spacing(); - Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), TR("xmrig_verify_note")); - ImGui::Spacing(); - installAction(TR("xmrig_download_install")); - } else { - Type().text(TypeStyle::Subtitle2, TR("xmrig_up_to_date")); - ImGui::Spacing(); - ImGui::Text("%s %s", TR("xmrig_installed"), p.latest_tag.c_str()); - ImGui::Spacing(); - installAction(TR("xmrig_reinstall")); - } + const float bw = std::min(fullW(), 220.0f * Layout::dpiScale()); + Type().textColored(TypeStyle::Subtitle2, Error(), TR("xmrig_update_failed")); ImGui::Spacing(); - // Browse all releases (pre-releases / older versions). - if (TactileButton(TR("xmrig_browse_releases"), ImVec2(fullW(), 0))) { + ImGui::TextWrapped("%s", p.error.empty() ? TR("xmrig_unknown_error") : p.error.c_str()); + ImGui::Spacing(); + if (TactileButton(TR("retry"), ImVec2(bw, 0)) && s_updater) { + s_releases.clear(); s_rows.clear(); s_updater->startListReleases(); } ImGui::Spacing(); - if (TactileButton(TR("close"), ImVec2(fullW(), 0))) s_open = false; + if (TactileButton(TR("close"), ImVec2(bw, 0))) s_open = false; } - static void renderListing(float, const Progress&) { - using namespace material; - Type().text(TypeStyle::Body2, TR("xmrig_loading_releases")); + static void buildRows() { + if (!s_rows.empty() || s_releases.empty()) return; + const std::string token = util::currentXmrigPlatformToken(); + s_rows.reserve(s_releases.size()); + for (const auto& r : s_releases) { + ReleaseRow row; + row.tag = r.tag; + row.title = r.name; + row.date = r.publishedAt.size() >= 10 ? r.publishedAt.substr(0, 10) : r.publishedAt; + row.prerelease = r.prerelease; + row.hasAsset = util::selectXmrigAsset(r, token) >= 0; + row.installed = !s_installed_tag.empty() && r.tag == s_installed_tag; + s_rows.push_back(std::move(row)); + } } - static void renderReleaseList(float dp, const Progress&) { + static std::string cleanNotes(const std::string& body) { + std::istringstream ss(body); + std::string line, out; + while (std::getline(ss, line)) { + size_t a = line.find_first_not_of(" \t"); + std::string lt = (a == std::string::npos) ? std::string() : line.substr(a); + std::string low = lt; + std::transform(low.begin(), low.end(), low.begin(), + [](unsigned char c) { return (char)std::tolower(c); }); + if (!lt.empty() && lt[0] == '|') break; + if (low.rfind("## checksum", 0) == 0 || low.rfind("#### checksum", 0) == 0 || + low.rfind("checksums", 0) == 0 || low.rfind("### sha", 0) == 0) break; + out += line; + out += '\n'; + } + while (!out.empty() && (out.back() == '\n' || out.back() == '\r' || + out.back() == ' ' || out.back() == '\t')) out.pop_back(); + return out; + } + + static void startInstallSelected() { + if (!s_updater || s_app->isPoolMinerRunning()) return; // can't replace a running miner + if (s_sel < 0 || s_sel >= (int)s_releases.size()) return; + const util::XmrigRelease rel = s_releases[s_sel]; + s_updater->startInstallRelease(resources::getDaemonDirectory(), rel); + } + + // ================= Two-pane: version list (left) + detail/progress (right) ================= + static void renderTwoPane(float dp, const Progress& p, bool active) { using namespace material; - // Snapshot + build rows once per listing (not every frame). Cleared on browse / show. - if (s_rows.empty()) { - s_releases = s_updater->getReleases(); - const std::string token = util::currentXmrigPlatformToken(); - s_rows.reserve(s_releases.size()); - for (const auto& r : s_releases) { - ReleaseRow row; - row.tag = r.tag; - row.title = r.name; - row.date = r.publishedAt.size() >= 10 ? r.publishedAt.substr(0, 10) : r.publishedAt; - row.prerelease = r.prerelease; - row.hasAsset = util::selectXmrigAsset(r, token) >= 0; - row.installed = !s_installed_tag.empty() && r.tag == s_installed_tag; - s_rows.push_back(std::move(row)); + if (!s_sweep && s_releases.empty() && s_updater) s_releases = s_updater->getReleases(); + buildRows(); + if (s_sel >= (int)s_rows.size()) s_sel = s_rows.empty() ? 0 : (int)s_rows.size() - 1; + + ImFont* bodyF = Type().body2(); + ImFont* capF = Type().caption(); + + const bool showFooter = (p.state == St::ReleaseList); + const float footerH = showFooter + ? (ImGui::GetFrameHeight() * 1.25f + ImGui::GetStyle().ItemSpacing.y * 2.0f + Layout::spacingSm()) + : 0.0f; + const float bodyH = std::max(120.0f * dp, ImGui::GetContentRegionAvail().y - footerH); + const float contentW = ImGui::GetContentRegionAvail().x; + const float gap = Layout::spacingLg(); + const float masterW = std::min(std::max(contentW * 0.34f, 240.0f * dp), 340.0f * dp); + const float detailW = contentW - masterW - gap; + + // ---- LEFT: borderless list of tactile version cards ---- + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + ImGui::BeginChild("##xmrigVersions", ImVec2(masterW, bodyH), false); + { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, Layout::spacingSm())); + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0, 0, 0, 0)); + ImDrawList* mdl = ImGui::GetWindowDrawList(); + ImFont* icoF = Type().iconSmall(); + const float rowH = Layout::spacingMd() * 2.0f + bodyF->LegacySize + Layout::spacingXs() + capF->LegacySize; + const float rowW = ImGui::GetContentRegionAvail().x; + const float round = 8.0f * dp; + for (int i = 0; i < (int)s_rows.size(); ++i) { + const ReleaseRow& r = s_rows[i]; + ImGui::PushID(i); + bool sel = (s_sel == i); + ImVec2 rmn = ImGui::GetCursorScreenPos(); + if (ImGui::Selectable("##ver", sel, ImGuiSelectableFlags_SpanAvailWidth, ImVec2(0, rowH))) { + if (!active) s_sel = i; + } + bool hov = ImGui::IsItemHovered(); + bool held = ImGui::IsItemActive(); + ImVec2 rmx(rmn.x + rowW, rmn.y + rowH); + ImU32 fill = sel ? WithAlpha(Primary(), held ? 55 : 42) + : held ? WithAlpha(OnSurface(), 34) + : hov ? WithAlpha(OnSurface(), 24) + : WithAlpha(OnSurface(), 12); + mdl->AddRectFilled(rmn, rmx, fill, round); + if (sel) mdl->AddRect(rmn, rmx, WithAlpha(Primary(), 150), round, 0, 1.6f * dp); + if (hov || held) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + const char* ico = nullptr; ImU32 statusC = 0; const char* tt = nullptr; const char* lbl = nullptr; + if (r.installed) { ico = ICON_MD_CHECK_CIRCLE; statusC = Success(); tt = TR("xmrig_active_tt"); lbl = TR("upd_installed_badge"); } + else if (r.prerelease) { ico = ICON_MD_SCIENCE; statusC = Warning(); tt = TR("xmrig_prerelease_tt"); lbl = TR("upd_prerelease"); } + else if (i == 0) { ico = ICON_MD_NEW_RELEASES; statusC = Primary(); tt = TR("xmrig_latest_tt"); lbl = TR("xmrig_latest_badge"); } + float rightLimit = rmx.x - Layout::spacingMd(); + if (ico) { + float cs = bodyF->LegacySize * 1.3f; + ImVec2 cp(rmx.x - Layout::spacingMd() - cs, (rmn.y + rmx.y) * 0.5f - cs * 0.5f); + mdl->AddText(icoF, cs, cp, statusC, ico); + if (tt && hov && ImGui::IsMouseHoveringRect(cp, ImVec2(cp.x + cs, cp.y + cs))) + Tooltip("%s", tt); + rightLimit = cp.x - Layout::spacingSm(); + } + float x = rmn.x + Layout::spacingMd(); + float tagY = rmn.y + Layout::spacingMd(); + mdl->AddText(bodyF, bodyF->LegacySize, ImVec2(x, tagY), + r.hasAsset ? OnSurface() : OnSurfaceDisabled(), r.tag.c_str()); + if (lbl) { + float bx = x + bodyF->CalcTextSizeA(bodyF->LegacySize, FLT_MAX, 0, r.tag.c_str()).x + Layout::spacingSm(); + float by = tagY + (bodyF->LegacySize - capF->LegacySize) * 0.5f; + if (bx + capF->CalcTextSizeA(capF->LegacySize, FLT_MAX, 0, lbl).x <= rightLimit) + mdl->AddText(capF, capF->LegacySize, ImVec2(bx, by), statusC, lbl); + } + if (!r.date.empty()) + mdl->AddText(capF, capF->LegacySize, + ImVec2(x, rmx.y - Layout::spacingMd() - capF->LegacySize), + OnSurfaceMedium(), r.date.c_str()); + ImGui::PopID(); } + ImGui::PopStyleColor(3); + ImGui::PopStyleVar(); } - // Can't replace a running miner binary — disable installs while mining (TOCTOU guard). - const char* disabledNote = s_app->isPoolMinerRunning() ? TR("xmrig_stop_mining_first") : nullptr; - bool back = false; - const int idx = RenderReleaseList(s_rows, dp, &back, disabledNote); - if (back) { s_rows.clear(); s_updater->startCheck(s_installed_tag); return; } - if (idx >= 0 && !s_app->isPoolMinerRunning() && idx < static_cast(s_releases.size())) { - const util::XmrigRelease rel = s_releases[idx]; - s_rows.clear(); - s_updater->startInstallRelease(resources::getDaemonDirectory(), rel); + ImGui::EndChild(); + ImGui::PopStyleVar(); + + ImGui::SameLine(0.0f, gap); + + // ---- RIGHT: detail / progress / done / failed for the selected version ---- + ImGui::BeginChild("##xmrigDetail", ImVec2(detailW, bodyH), false); + switch (p.state) { + case St::ReleaseList: renderDetail(dp, p); break; + case St::Downloading: + case St::Verifying: + case St::Extracting: renderProgress(dp, p); break; + case St::Done: renderDone(dp, p); break; + case St::Failed: renderFailed(dp, p); break; + default: break; } + ImGui::EndChild(); + + if (showFooter) { + ImGui::Spacing(); + const float bw = 170.0f * dp, bh = ImGui::GetFrameHeight() * 1.25f; + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + std::max(0.0f, ImGui::GetContentRegionAvail().x - bw)); + if (TactileButton(TR("close"), ImVec2(bw, bh))) s_open = false; + } + } + + static void renderDetail(float dp, const Progress&) { + using namespace material; + if (s_sel < 0 || s_sel >= (int)s_releases.size() || s_sel >= (int)s_rows.size()) return; + const util::XmrigRelease& rel = s_releases[s_sel]; + const ReleaseRow& row = s_rows[s_sel]; + const bool haveInstalled = !s_installed_tag.empty(); + const bool downgrade = !row.installed && s_sel != 0 && haveInstalled; + const bool mining = s_app->isPoolMinerRunning(); + + const char* noteStr = mining ? TR("xmrig_stop_mining_first") + : !row.hasAsset ? TR("upd_no_build_platform") + : downgrade ? TR("xmrig_downgrade_note") + : TR("xmrig_verify_note"); + const char* label = row.installed ? TR("xmrig_reinstall") + : (s_sel == 0) ? TR("xmrig_download_install") + : TR("xmrig_install_this"); + + const float btnH = ImGui::GetFrameHeight() * 1.15f; + const float noteH = ImGui::CalcTextSize(noteStr, nullptr, false, ImGui::GetContentRegionAvail().x).y; + const float reserve = noteH + btnH + Layout::spacingMd() * 3.0f; + const float infoH = std::max(120.0f * dp, ImGui::GetContentRegionAvail().y - reserve); + + // ---- Info card: ONE rounded Material surface (version header + release notes markdown) ---- + ImGui::PushStyleColor(ImGuiCol_ChildBg, WithAlpha(OnSurface(), 22)); + ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 10.0f * dp); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(Layout::spacingLg(), Layout::spacingLg())); + ImGui::BeginChild("##xmrigInfo", ImVec2(0, infoH), ImGuiChildFlags_AlwaysUseWindowPadding); + { + Type().text(TypeStyle::H6, rel.tag.c_str()); + if (row.installed) + Type().textColored(TypeStyle::Subtitle2, Success(), TR("xmrig_status_current")); + else if (s_sel == 0) + Type().textColored(TypeStyle::Subtitle2, Primary(), + haveInstalled ? TR("xmrig_update_available") : TR("xmrig_status_latest")); + else if (haveInstalled) + Type().textColored(TypeStyle::Subtitle2, Warning(), TR("xmrig_status_older")); + else + Type().text(TypeStyle::Subtitle2, rel.name.empty() ? rel.tag.c_str() : rel.name.c_str()); + + const std::string installed = s_installed_tag.empty() ? std::string(TR("xmrig_none")) + : s_installed_tag; + ImGui::Spacing(); + Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), + (std::string(TR("xmrig_installed")) + " " + installed).c_str()); + if (!row.date.empty()) + Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), + (std::string(TR("xmrig_released")) + " " + row.date).c_str()); + ImGui::Dummy(ImVec2(0, Layout::spacingSm())); + + const std::string notes = cleanNotes(rel.body); + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0, 0, 0, 0)); + ImGui::BeginChild("##xmrigNotes", ImVec2(0, std::max(0.0f, ImGui::GetContentRegionAvail().y)), false); + if (notes.empty()) + Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("xmrig_no_notes")); + else + RenderMarkdown(notes); + ImGui::EndChild(); + ImGui::PopStyleColor(); + } + ImGui::EndChild(); + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(); + + // ---- Below the info card: verify / stop-mining note + install button (centered, text-fit) ---- + ImGui::Dummy(ImVec2(0, Layout::spacingSm())); + Type().textColored(TypeStyle::Caption, (mining || downgrade) ? Warning() : OnSurfaceMedium(), noteStr); + ImGui::Spacing(); + const float bw = ImGui::CalcTextSize(label).x + ImGui::GetStyle().FramePadding.x * 2.0f + Layout::spacingLg(); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + std::max(0.0f, (ImGui::GetContentRegionAvail().x - bw) * 0.5f)); + ImGui::BeginDisabled(!row.hasAsset || mining); + if (TactileButton(label, ImVec2(bw, btnH))) startInstallSelected(); + ImGui::EndDisabled(); } static void renderProgress(float dp, const Progress& p) { @@ -186,11 +386,10 @@ private: ImGui::Spacing(); material::ProgressBar(fullW(), 8.0f * dp, p.percent / 100.0f); ImGui::Spacing(); - ImGui::Text("%s", p.status_text.c_str()); + ImGui::TextWrapped("%s", p.status_text.c_str()); ImGui::Spacing(); - // Cancel aborts the in-flight transfer promptly (the curl progress callback returns abort). if (TactileButton(TR("cancel"), ImVec2(fullW(), 0))) { - s_updater->cancel(); + if (s_updater) s_updater->cancel(); s_open = false; } } @@ -217,16 +416,21 @@ private: ImGui::Spacing(); ImGui::TextWrapped("%s", p.error.empty() ? TR("xmrig_unknown_error") : p.error.c_str()); ImGui::Spacing(); - installAction(TR("retry")); + ImGui::BeginDisabled(s_app->isPoolMinerRunning()); + if (TactileButton(TR("retry"), ImVec2(fullW(), 0))) startInstallSelected(); + ImGui::EndDisabled(); ImGui::Spacing(); if (TactileButton(TR("close"), ImVec2(fullW(), 0))) s_open = false; } static inline bool s_open = false; static inline bool s_persisted = false; + static inline bool s_sweep = false; + static inline int s_sel = 0; static inline std::string s_installed_tag; static inline std::vector s_rows; // built once per listing (UI rows) static inline std::vector s_releases; // matching releases (install by index) + static inline Progress s_sweepProgress; static inline App* s_app = nullptr; static inline std::unique_ptr s_updater; }; diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index eea43f2..3ab923f 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -1444,6 +1444,17 @@ void I18n::loadBuiltinEnglish() strings_["xmrig_unknown_error"] = "Unknown error."; strings_["xmrig_browse_releases"] = "Browse all releases…"; strings_["xmrig_loading_releases"] = "Loading releases…"; + strings_["xmrig_latest_badge"] = "latest"; + strings_["xmrig_status_current"] = "This version is installed"; + strings_["xmrig_status_latest"] = "Latest version"; + strings_["xmrig_status_older"] = "Older than your installed version"; + strings_["xmrig_released"] = "Released:"; + strings_["xmrig_no_notes"] = "No release notes for this version."; + strings_["xmrig_install_this"] = "Install this version"; + strings_["xmrig_downgrade_note"] = "Installing a different miner version replaces the current one; stop mining first, then it takes effect on the next start."; + strings_["xmrig_active_tt"] = "The currently installed (active) miner"; + strings_["xmrig_latest_tt"] = "Newest available version"; + strings_["xmrig_prerelease_tt"] = "Pre-release / testing build"; // --- Shared release picker ("Browse all releases") --- strings_["upd_select_version"] = "Select a version to install";