// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 #include "request_payment_dialog.h" #include "../../app.h" #include "../../util/i18n.h" #include "../notifications.h" #include "../schema/ui_schema.h" #include "../widgets/qr_code.h" #include "../material/draw_helpers.h" #include "imgui.h" #include #include #include namespace dragonx { namespace ui { // Static state static bool s_open = false; static char s_address[512] = ""; static double s_amount = 0.0; static char s_memo[512] = ""; static char s_label[128] = ""; static int s_selected_addr_idx = -1; static std::string s_payment_uri; static uintptr_t s_qr_texture = 0; static int s_qr_width = 0; static int s_qr_height = 0; static bool s_uri_dirty = true; // Helper to build payment URI static std::string buildPaymentUri() { if (s_address[0] == '\0') return ""; std::ostringstream uri; uri << "drgx:" << s_address; bool hasParams = false; auto addParam = [&](const char* key, const std::string& value) { if (value.empty()) return; uri << (hasParams ? "&" : "?") << key << "=" << value; hasParams = true; }; if (s_amount > 0) { std::ostringstream amt; amt << std::fixed << std::setprecision(8) << s_amount; addParam("amount", amt.str()); } if (s_label[0] != '\0') { // URL encode label std::string encoded; for (char c : std::string(s_label)) { if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') { encoded += c; } else if (c == ' ') { encoded += "%20"; } else { char hex[4]; snprintf(hex, sizeof(hex), "%%%02X", (unsigned char)c); encoded += hex; } } addParam("label", encoded); } if (s_memo[0] != '\0') { // URL encode memo std::string encoded; for (char c : std::string(s_memo)) { if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') { encoded += c; } else if (c == ' ') { encoded += "%20"; } else { char hex[4]; snprintf(hex, sizeof(hex), "%%%02X", (unsigned char)c); encoded += hex; } } addParam("memo", encoded); } return uri.str(); } void RequestPaymentDialog::show(const std::string& address) { s_open = true; s_amount = 0.0; s_memo[0] = '\0'; s_label[0] = '\0'; s_selected_addr_idx = -1; s_uri_dirty = true; if (!address.empty()) { strncpy(s_address, address.c_str(), sizeof(s_address) - 1); } else { s_address[0] = '\0'; } // Free old QR texture if (s_qr_texture != 0) { FreeQRTexture(s_qr_texture); s_qr_texture = 0; } } void RequestPaymentDialog::render(App* app) { if (!s_open) return; auto& S = schema::UI(); auto win = S.window("dialogs.request-payment"); auto zAddrLbl = S.label("dialogs.request-payment", "z-addr-label"); auto zAddrFrontLbl = S.label("dialogs.request-payment", "z-addr-front-label"); auto zAddrBackLbl = S.label("dialogs.request-payment", "z-addr-back-label"); auto tAddrLbl = S.label("dialogs.request-payment", "t-addr-label"); auto tAddrFrontLbl = S.label("dialogs.request-payment", "t-addr-front-label"); auto tAddrBackLbl = S.label("dialogs.request-payment", "t-addr-back-label"); auto amountInput = S.input("dialogs.request-payment", "amount-input"); auto memoInput = S.input("dialogs.request-payment", "memo-input"); auto qr = S.drawElement("dialogs.request-payment", "qr-code"); auto actionBtn = S.button("dialogs.request-payment", "action-button"); if (material::BeginOverlayDialog(TR("request_title"), &s_open, win.width, 0.94f)) { const auto& state = app->getWalletState(); ImGui::TextWrapped("%s", TR("request_description")); ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); // Address selection ImGui::Text("%s", TR("request_receive_address")); std::string addr_display = s_address[0] ? s_address : TR("request_select_address"); if (addr_display.length() > static_cast(zAddrLbl.truncate)) { addr_display = addr_display.substr(0, zAddrFrontLbl.truncate) + "..." + addr_display.substr(addr_display.length() - zAddrBackLbl.truncate); } ImGui::SetNextItemWidth(-1); if (ImGui::BeginCombo("##Address", addr_display.c_str())) { // Z-addresses if (!state.z_addresses.empty()) { ImGui::TextDisabled("%s", TR("request_shielded_addrs")); for (size_t i = 0; i < state.z_addresses.size(); i++) { const auto& addr = state.z_addresses[i]; std::string label = addr.address; if (label.length() > static_cast(zAddrLbl.truncate)) { label = label.substr(0, zAddrFrontLbl.truncate) + "..." + label.substr(label.length() - zAddrBackLbl.truncate); } if (ImGui::Selectable(label.c_str(), s_address == addr.address)) { strncpy(s_address, addr.address.c_str(), sizeof(s_address) - 1); s_uri_dirty = true; } } } // T-addresses if (!state.t_addresses.empty()) { ImGui::TextDisabled("%s", TR("request_transparent_addrs")); for (size_t i = 0; i < state.t_addresses.size(); i++) { const auto& addr = state.t_addresses[i]; std::string label = addr.address; if (label.length() > static_cast(tAddrLbl.truncate)) { label = label.substr(0, tAddrFrontLbl.truncate) + "..." + label.substr(label.length() - tAddrBackLbl.truncate); } if (ImGui::Selectable(label.c_str(), s_address == addr.address)) { strncpy(s_address, addr.address.c_str(), sizeof(s_address) - 1); s_uri_dirty = true; } } } ImGui::EndCombo(); } ImGui::Spacing(); // Amount (optional) ImGui::Text("%s", TR("request_amount")); ImGui::SetNextItemWidth(amountInput.width); if (ImGui::InputDouble("##Amount", &s_amount, 0.1, 1.0, "%.8f")) { s_uri_dirty = true; } ImGui::SameLine(); ImGui::TextDisabled("DRGX"); ImGui::Spacing(); // Label (optional) ImGui::Text("%s", TR("request_label")); ImGui::SetNextItemWidth(-1); if (ImGui::InputText("##Label", s_label, sizeof(s_label))) { s_uri_dirty = true; } ImGui::Spacing(); // Memo (optional, only for z-addr) bool is_zaddr = (s_address[0] == 'z'); if (is_zaddr) { ImGui::Text("%s", TR("request_memo")); ImGui::SetNextItemWidth(-1); if (ImGui::InputTextMultiline("##Memo", s_memo, sizeof(s_memo), ImVec2(-1, memoInput.height > 0 ? memoInput.height : 60))) { s_uri_dirty = true; } } else { s_memo[0] = '\0'; // Clear memo for t-addr } ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); // Build and display payment URI if (s_uri_dirty && s_address[0] != '\0') { s_payment_uri = buildPaymentUri(); // Generate new QR code if (s_qr_texture != 0) { FreeQRTexture(s_qr_texture); } s_qr_texture = GenerateQRTexture(s_payment_uri.c_str(), &s_qr_width, &s_qr_height); s_uri_dirty = false; } // QR Code display if (s_qr_texture != 0) { RenderQRCode(s_qr_texture, qr.size > 0 ? qr.size : 200); } ImGui::Spacing(); // Payment URI display if (!s_payment_uri.empty()) { ImGui::Text("%s", TR("request_payment_uri")); ImGui::SetNextItemWidth(-1); // Use a selectable text area for the URI char uri_buf[1024]; strncpy(uri_buf, s_payment_uri.c_str(), sizeof(uri_buf) - 1); ImGui::InputText("##URI", uri_buf, sizeof(uri_buf), ImGuiInputTextFlags_ReadOnly); ImGui::Spacing(); // Copy button if (material::StyledButton(TR("request_copy_uri"), ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) { ImGui::SetClipboardText(s_payment_uri.c_str()); Notifications::instance().success(TR("request_uri_copied")); } ImGui::SameLine(); if (material::StyledButton(TR("copy_address"), ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) { ImGui::SetClipboardText(s_address); Notifications::instance().success(TR("address_copied")); } } ImGui::Spacing(); // Close button if (material::StyledButton(TR("close"), ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) { s_open = false; } material::EndOverlayDialog(); } // Cleanup on close if (!s_open && s_qr_texture != 0) { FreeQRTexture(s_qr_texture); s_qr_texture = 0; } } } // namespace ui } // namespace dragonx