// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 #include "block_info_dialog.h" #include "../../app.h" #include "../../rpc/rpc_client.h" #include "../../util/i18n.h" #include "../notifications.h" #include "../schema/ui_schema.h" #include "../material/draw_helpers.h" #include "imgui.h" #include #include #include namespace dragonx { namespace ui { using json = nlohmann::json; // Static state static bool s_open = false; static int s_height = 0; static bool s_loading = false; static bool s_has_data = false; static std::string s_error; // Block data static std::string s_block_hash; static int64_t s_block_time = 0; static int s_tx_count = 0; static int s_block_size = 0; static std::string s_bits; static double s_difficulty = 0.0; static std::string s_prev_hash; static std::string s_next_hash; static std::string s_merkle_root; static int s_confirmations = 0; // Pending RPC app pointer (for async callback) static App* s_pending_app = nullptr; void BlockInfoDialog::show(int initialHeight) { s_open = true; s_height = initialHeight > 0 ? initialHeight : 1; s_loading = false; s_has_data = false; s_error.clear(); } // Callback to handle getblock response static void handleBlockResponseUnified(const json& result, const std::string& error) { s_loading = false; if (!error.empty()) { s_error = "Error: " + error; return; } if (!result.is_null()) { auto block = result; s_block_hash = block.value("hash", ""); s_block_time = block.value("time", (int64_t)0); s_confirmations = block.value("confirmations", 0); s_block_size = block.value("size", 0); s_bits = block.value("bits", ""); s_difficulty = block.value("difficulty", 0.0); s_prev_hash = block.value("previousblockhash", ""); s_next_hash = block.value("nextblockhash", ""); s_merkle_root = block.value("merkleroot", ""); if (block.contains("tx") && block["tx"].is_array()) { s_tx_count = static_cast(block["tx"].size()); } else { s_tx_count = 0; } s_has_data = true; } else { s_error = "Invalid response from daemon"; } } void BlockInfoDialog::render(App* app) { if (!s_open) return; auto& S = schema::UI(); auto win = S.window("dialogs.block-info"); auto heightInput = S.input("dialogs.block-info", "height-input"); auto lbl = S.label("dialogs.block-info", "label"); auto hashLbl = S.label("dialogs.block-info", "hash-label"); auto hashFrontLbl = S.label("dialogs.block-info", "hash-front-label"); auto hashBackLbl = S.label("dialogs.block-info", "hash-back-label"); auto closeBtn = S.button("dialogs.block-info", "close-button"); if (material::BeginOverlayDialog("Block Information", &s_open, win.width, 0.94f)) { auto* rpc = app->rpc(); const auto& state = app->getWalletState(); // Height input ImGui::Text("Block Height:"); ImGui::SetNextItemWidth(heightInput.width); ImGui::InputInt("##Height", &s_height); if (s_height < 1) s_height = 1; ImGui::SameLine(); // Current block info if (state.sync.blocks > 0) { ImGui::TextDisabled("(Current: %d)", state.sync.blocks); } ImGui::SameLine(); // Fetch button if (s_loading) { ImGui::BeginDisabled(); } if (material::StyledButton("Get Block Info", ImVec2(0,0), S.resolveFont(closeBtn.font))) { if (rpc && rpc->isConnected()) { s_loading = true; s_error.clear(); s_has_data = false; s_pending_app = app; // Use getBlock(height) which uses UnifiedCallback rpc->getBlock(s_height, handleBlockResponseUnified); } } if (s_loading) { ImGui::EndDisabled(); ImGui::SameLine(); ImGui::TextDisabled("Loading..."); } ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); // Error display if (!s_error.empty()) { ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.4f, 0.4f, 1.0f)); ImGui::TextWrapped("%s", s_error.c_str()); ImGui::PopStyleColor(); } // Block info display if (s_has_data) { // Block hash ImGui::Text("Block Hash:"); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.8f, 1.0f, 1.0f)); ImGui::TextWrapped("%s", s_block_hash.c_str()); ImGui::PopStyleColor(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Click to copy"); } if (ImGui::IsItemClicked()) { ImGui::SetClipboardText(s_block_hash.c_str()); Notifications::instance().success("Block hash copied"); } ImGui::Spacing(); // Timestamp ImGui::Text("Timestamp:"); ImGui::SameLine(lbl.position); if (s_block_time > 0) { std::time_t t = static_cast(s_block_time); char time_buf[64]; std::strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", std::localtime(&t)); ImGui::Text("%s", time_buf); } else { ImGui::TextDisabled("Unknown"); } // Confirmations ImGui::Text("Confirmations:"); ImGui::SameLine(lbl.position); ImGui::Text("%d", s_confirmations); // Transaction count ImGui::Text("Transactions:"); ImGui::SameLine(lbl.position); ImGui::Text("%d", s_tx_count); // Size ImGui::Text("Size:"); ImGui::SameLine(lbl.position); if (s_block_size > 1024 * 1024) { ImGui::Text("%.2f MB", s_block_size / (1024.0 * 1024.0)); } else if (s_block_size > 1024) { ImGui::Text("%.2f KB", s_block_size / 1024.0); } else { ImGui::Text("%d bytes", s_block_size); } // Difficulty ImGui::Text("Difficulty:"); ImGui::SameLine(lbl.position); ImGui::Text("%.4f", s_difficulty); // Bits ImGui::Text("Bits:"); ImGui::SameLine(lbl.position); ImGui::Text("%s", s_bits.c_str()); ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); // Merkle root ImGui::Text("Merkle Root:"); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 1.0f)); ImGui::TextWrapped("%s", s_merkle_root.c_str()); ImGui::PopStyleColor(); ImGui::Spacing(); // Previous block if (!s_prev_hash.empty()) { ImGui::Text("Previous Block:"); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.8f, 1.0f, 1.0f)); // Truncate for display std::string prev_short = s_prev_hash; if (prev_short.length() > static_cast(hashLbl.truncate)) { prev_short = prev_short.substr(0, hashFrontLbl.truncate) + "..." + prev_short.substr(prev_short.length() - hashBackLbl.truncate); } ImGui::Text("%s", prev_short.c_str()); ImGui::PopStyleColor(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Click to view previous block"); } if (ImGui::IsItemClicked() && s_height > 1) { s_height--; s_has_data = false; } } // Next block if (!s_next_hash.empty()) { ImGui::Text("Next Block:"); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.8f, 1.0f, 1.0f)); // Truncate for display std::string next_short = s_next_hash; if (next_short.length() > static_cast(hashLbl.truncate)) { next_short = next_short.substr(0, hashFrontLbl.truncate) + "..." + next_short.substr(next_short.length() - hashBackLbl.truncate); } ImGui::Text("%s", next_short.c_str()); ImGui::PopStyleColor(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Click to view next block"); } if (ImGui::IsItemClicked()) { s_height++; s_has_data = false; } } } ImGui::Spacing(); ImGui::Spacing(); // Navigation buttons if (s_has_data) { if (s_height > 1) { if (material::StyledButton("<< Previous", ImVec2(0,0), S.resolveFont(closeBtn.font))) { s_height--; s_has_data = false; s_error.clear(); } ImGui::SameLine(); } if (!s_next_hash.empty()) { if (material::StyledButton("Next >>", ImVec2(0,0), S.resolveFont(closeBtn.font))) { s_height++; s_has_data = false; s_error.clear(); } } } // Close button at bottom ImGui::SetCursorPosY(ImGui::GetWindowHeight() - 40); if (material::StyledButton("Close", ImVec2(closeBtn.width, 0), S.resolveFont(closeBtn.font))) { s_open = false; } material::EndOverlayDialog(); } } } // namespace ui } // namespace dragonx