ObsidianDragon - DragonX ImGui Wallet
Full-node GUI wallet for DragonX cryptocurrency. Built with Dear ImGui, SDL3, and OpenGL3/DX11. Features: - Send/receive shielded and transparent transactions - Autoshield with merged transaction display - Built-in CPU mining (xmrig) - Peer management and network monitoring - Wallet encryption with PIN lock - QR code generation for receive addresses - Transaction history with pagination - Console for direct RPC commands - Cross-platform (Linux, Windows)
This commit is contained in:
298
src/ui/windows/request_payment_dialog.cpp
Normal file
298
src/ui/windows/request_payment_dialog.cpp
Normal file
@@ -0,0 +1,298 @@
|
||||
// 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 "../theme.h"
|
||||
#include "../effects/imgui_acrylic.h"
|
||||
#include "imgui.h"
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
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");
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(win.width, win.height), ImGuiCond_FirstUseEver);
|
||||
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
ImGui::SetNextWindowFocus();
|
||||
|
||||
const auto& acrylicTheme = GetCurrentAcrylicTheme();
|
||||
ImGui::OpenPopup("Request Payment");
|
||||
if (effects::ImGuiAcrylic::BeginAcrylicPopupModal("Request Payment", &s_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar, acrylicTheme.popup)) {
|
||||
const auto& state = app->getWalletState();
|
||||
|
||||
ImGui::TextWrapped(
|
||||
"Generate a payment request that others can scan or copy. "
|
||||
"The QR code contains your address and optional amount/memo."
|
||||
);
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Address selection
|
||||
ImGui::Text("Receive Address:");
|
||||
|
||||
std::string addr_display = s_address[0] ? s_address : "Select address...";
|
||||
if (addr_display.length() > static_cast<size_t>(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("-- Shielded Addresses --");
|
||||
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<size_t>(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("-- Transparent Addresses --");
|
||||
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<size_t>(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("Amount (optional):");
|
||||
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("Label (optional):");
|
||||
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("Memo (optional):");
|
||||
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("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("Copy URI", ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
|
||||
ImGui::SetClipboardText(s_payment_uri.c_str());
|
||||
Notifications::instance().success("Payment URI copied to clipboard");
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (material::StyledButton("Copy Address", ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
|
||||
ImGui::SetClipboardText(s_address);
|
||||
Notifications::instance().success("Address copied to clipboard");
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
// Close button
|
||||
if (material::StyledButton("Close", ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
|
||||
s_open = false;
|
||||
}
|
||||
}
|
||||
effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
|
||||
// Cleanup on close
|
||||
if (!s_open && s_qr_texture != 0) {
|
||||
FreeQRTexture(s_qr_texture);
|
||||
s_qr_texture = 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace dragonx
|
||||
Reference in New Issue
Block a user