// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 #include "transaction_details_dialog.h" #include "../../app.h" #include "../../config/settings.h" #include "../../util/i18n.h" #include "../theme.h" #include "../schema/ui_schema.h" #include "../material/draw_helpers.h" #include "imgui.h" #include namespace dragonx { namespace ui { // Static member initialization bool TransactionDetailsDialog::s_open = false; TransactionInfo TransactionDetailsDialog::s_transaction; void TransactionDetailsDialog::show(const TransactionInfo& tx) { s_open = true; s_transaction = tx; } bool TransactionDetailsDialog::isOpen() { return s_open; } void TransactionDetailsDialog::render(App* app) { if (!s_open) return; auto& S = schema::UI(); auto win = S.window("dialogs.transaction-details"); auto lbl = S.label("dialogs.transaction-details", "label"); auto confLbl = S.label("dialogs.transaction-details", "confirmations-label"); auto txidInput = S.input("dialogs.transaction-details", "txid-input"); auto copyBtn = S.button("dialogs.transaction-details", "copy-button"); auto addrInput = S.input("dialogs.transaction-details", "address-input"); auto memoInput = S.input("dialogs.transaction-details", "memo-input"); auto bottomBtn = S.button("dialogs.transaction-details", "bottom-button"); if (material::BeginOverlayDialog(TR("tx_details_title"), &s_open, win.width, 0.94f)) { const auto& tx = s_transaction; // Type indicator with color ImVec4 type_color; std::string type_display; if (tx.type == "receive") { type_color = ImVec4(0.3f, 0.8f, 0.3f, 1.0f); type_display = TR("tx_received"); } else if (tx.type == "send") { type_color = ImVec4(0.8f, 0.3f, 0.3f, 1.0f); type_display = TR("tx_sent"); } else if (tx.type == "generate" || tx.type == "mined") { type_color = ImVec4(0.3f, 0.6f, 0.9f, 1.0f); type_display = TR("tx_mined"); } else if (tx.type == "immature") { type_color = ImVec4(0.8f, 0.8f, 0.3f, 1.0f); type_display = TR("tx_immature"); } else { type_color = ImVec4(0.7f, 0.7f, 0.7f, 1.0f); type_display = tx.type; } ImGui::TextColored(type_color, "%s", type_display.c_str()); ImGui::SameLine(ImGui::GetWindowWidth() - confLbl.position); // Confirmations badge if (tx.confirmations == 0) { ImGui::TextColored(ImVec4(0.8f, 0.6f, 0.0f, 1.0f), "%s", TR("pending")); } else if (tx.confirmations < 10) { ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.3f, 1.0f), TR("tx_confirmations"), tx.confirmations); } else { ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), TR("tx_confirmations"), tx.confirmations); } ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); // Amount (prominent display) ImGui::Text("%s", TR("amount_label")); ImGui::SameLine(lbl.position); if (tx.amount >= 0) { ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "+%.8f DRGX", tx.amount); } else { ImGui::TextColored(ImVec4(0.8f, 0.3f, 0.3f, 1.0f), "%.8f DRGX", tx.amount); } // USD equivalent if price available double price_usd = app->state().market.price_usd; if (price_usd > 0) { ImGui::SameLine(); ImGui::TextDisabled("(~$%.2f USD)", std::abs(tx.amount) * price_usd); } ImGui::Spacing(); // Date/Time ImGui::Text("%s", TR("date_label")); ImGui::SameLine(lbl.position); ImGui::Text("%s", tx.getTimeString().c_str()); ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); // Transaction ID ImGui::Text("%s", TR("tx_id_label")); char txid_buf[128]; strncpy(txid_buf, tx.txid.c_str(), sizeof(txid_buf) - 1); txid_buf[sizeof(txid_buf) - 1] = '\0'; ImGui::SetNextItemWidth(txidInput.width); ImGui::InputText("##TxID", txid_buf, sizeof(txid_buf), ImGuiInputTextFlags_ReadOnly); ImGui::SameLine(); if (material::StyledButton("Copy##TxID", ImVec2(copyBtn.width, 0), S.resolveFont(copyBtn.font))) { ImGui::SetClipboardText(tx.txid.c_str()); } ImGui::Spacing(); // Address if (!tx.address.empty()) { ImGui::Text("%s", tx.type == "send" ? TR("tx_to_address") : TR("tx_from_address")); // Use multiline for z-addresses if (tx.address.length() > 50) { char addr_buf[512]; strncpy(addr_buf, tx.address.c_str(), sizeof(addr_buf) - 1); addr_buf[sizeof(addr_buf) - 1] = '\0'; ImGui::InputTextMultiline("##Address", addr_buf, sizeof(addr_buf), ImVec2(addrInput.width, addrInput.height > 0 ? addrInput.height : 50), ImGuiInputTextFlags_ReadOnly); } else { char addr_buf[128]; strncpy(addr_buf, tx.address.c_str(), sizeof(addr_buf) - 1); addr_buf[sizeof(addr_buf) - 1] = '\0'; ImGui::SetNextItemWidth(addrInput.width); ImGui::InputText("##Address", addr_buf, sizeof(addr_buf), ImGuiInputTextFlags_ReadOnly); } ImGui::SameLine(); if (material::StyledButton("Copy##Addr", ImVec2(copyBtn.width, 0), S.resolveFont(copyBtn.font))) { ImGui::SetClipboardText(tx.address.c_str()); } } ImGui::Spacing(); // Memo (if present) if (!tx.memo.empty()) { ImGui::Separator(); ImGui::Spacing(); ImGui::Text("%s", TR("memo_label")); char memo_buf[512]; strncpy(memo_buf, tx.memo.c_str(), sizeof(memo_buf) - 1); memo_buf[sizeof(memo_buf) - 1] = '\0'; ImGui::InputTextMultiline("##Memo", memo_buf, sizeof(memo_buf), ImVec2(-1, memoInput.height > 0 ? memoInput.height : 60), ImGuiInputTextFlags_ReadOnly); } ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); // Buttons float button_width = bottomBtn.width; float total_width = button_width * 2 + ImGui::GetStyle().ItemSpacing.x; float start_x = (ImGui::GetWindowWidth() - total_width) / 2.0f; ImGui::SetCursorPosX(start_x); if (material::StyledButton(TR("tx_view_explorer"), ImVec2(button_width, 0), S.resolveFont(bottomBtn.font))) { std::string url = app->settings()->getTxExplorerUrl() + tx.txid; // Platform-specific URL opening #ifdef _WIN32 ShellExecuteA(nullptr, "open", url.c_str(), nullptr, nullptr, SW_SHOWNORMAL); #elif __APPLE__ std::string cmd = "open \"" + url + "\""; system(cmd.c_str()); #else std::string cmd = "xdg-open \"" + url + "\" &"; system(cmd.c_str()); #endif } ImGui::SameLine(); if (material::StyledButton(TR("close"), ImVec2(button_width, 0), S.resolveFont(bottomBtn.font))) { s_open = false; } material::EndOverlayDialog(); } } } // namespace ui } // namespace dragonx