Files
ObsidianDragon/src/ui/windows/request_payment_dialog.cpp
DanS 04a5078133 feat(receive): migrate QR + Request-payment modals to the reference design
Bring the two Receive-tab modals onto the settings-modal reference language
(presentation-only):
- struct-form BeginOverlayDialog(OverlayDialogSpec{BlurFloat, cardWidth, idSuffix})
  replacing the legacy positional overload (idSuffix qrpopup / requestpay)
- StyledButton -> TactileButton (2 + 3 sites)
- drop Request-payment's description divider

No logic touched: the QR texture lifecycle, payment-URI builder, address combo,
and clipboard/notification calls are unchanged. The "DRGX" ticker stays hardcoded
per the existing codebase convention (7 sites, no currency TR key).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 16:20:02 -05:00

232 lines
8.2 KiB
C++

// 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 "../../util/payment_uri.h"
#include "../notifications.h"
#include "../schema/ui_schema.h"
#include "../widgets/qr_code.h"
#include "../material/draw_helpers.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 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()
{
// Route through the shared builder so this dialog and the receive tab can't diverge on scheme/encoding.
return dragonx::util::buildPaymentUri(s_address, s_amount, s_label, s_memo);
}
void RequestPaymentDialog::show(const std::string& address)
{
s_open = true;
s_amount = 0.0;
s_memo[0] = '\0';
s_label[0] = '\0';
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");
material::OverlayDialogSpec ov;
ov.title = TR("request_title"); ov.p_open = &s_open;
ov.style = material::OverlayStyle::BlurFloat;
ov.cardWidth = win.width; ov.idSuffix = "requestpay";
if (material::BeginOverlayDialog(ov)) {
const auto& state = app->getWalletState();
ImGui::TextWrapped("%s", TR("request_description"));
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<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("%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<size_t>(zAddrLbl.truncate)) {
label = label.substr(0, zAddrFrontLbl.truncate) + "..." + label.substr(label.length() - zAddrBackLbl.truncate);
}
if (ImGui::Selectable(label.c_str(), addr.address == s_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<size_t>(tAddrLbl.truncate)) {
label = label.substr(0, tAddrFrontLbl.truncate) + "..." + label.substr(label.length() - tAddrBackLbl.truncate);
}
if (ImGui::Selectable(label.c_str(), addr.address == s_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)
if (material::LabeledInput(TR("request_label"), "##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) {
if (material::LabeledInputMultiline(TR("request_memo"), "##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()) {
// Use a selectable text area for the URI
char uri_buf[1024];
strncpy(uri_buf, s_payment_uri.c_str(), sizeof(uri_buf) - 1);
material::LabeledInput(TR("request_payment_uri"), "##URI", uri_buf, sizeof(uri_buf),
-1.0f, nullptr, ImGuiInputTextFlags_ReadOnly);
ImGui::Spacing();
// Copy button
if (material::TactileButton(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::TactileButton(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::TactileButton(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