From ddd53dc006b6c3c3fc9d7a5f5d442c52fe83bf6b Mon Sep 17 00:00:00 2001 From: DanS Date: Sun, 5 Jul 2026 17:04:58 -0500 Subject: [PATCH] feat(send): add a shared contact picker to the recipient field MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 0c. The Send recipient field only offered Paste and a tx-history suggestion list — no way to pick a saved contact. Add a compact contacts icon button next to Paste that opens a picker popup over the App-owned address book; selecting a contact fills the recipient (re-validated next frame). - New header-only src/ui/windows/contact_picker.h: ContactPickerPopup(id, book, outBuf, outSz) — a reusable popup listing contacts ([Z]/[T] tag + label + short address) that copies the chosen address into the caller's buffer. Kept out of the material layer so the design-system headers stay free of data/ deps; reserved for Chat "new conversation" later. - send_tab: shrink the recipient input to make room for the icon button (ICON_MD_CONTACTS), wire the popup to app->addressBook(). Coexists with the existing Paste + suggestion affordances. - i18n: send_contacts_button tooltip. Co-Authored-By: Claude Opus 4.8 (1M context) --- CMakeLists.txt | 1 + src/ui/windows/contact_picker.h | 65 +++++++++++++++++++++++++++++++++ src/ui/windows/send_tab.cpp | 13 ++++++- src/util/i18n.cpp | 1 + 4 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 src/ui/windows/contact_picker.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d1485c2..dcf4c87 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -595,6 +595,7 @@ set(APP_HEADERS src/ui/windows/qr_popup_dialog.h src/ui/windows/validate_address_dialog.h src/ui/windows/contacts_tab.h + src/ui/windows/contact_picker.h src/ui/windows/shield_dialog.h src/ui/windows/request_payment_dialog.h src/ui/windows/block_info_dialog.h diff --git a/src/ui/windows/contact_picker.h b/src/ui/windows/contact_picker.h new file mode 100644 index 0000000..70cabbb --- /dev/null +++ b/src/ui/windows/contact_picker.h @@ -0,0 +1,65 @@ +// DragonX Wallet - ImGui Edition +// Copyright 2024-2026 The Hush Developers +// Released under the GPLv3 + +#pragma once + +#include "imgui.h" +#include "../../data/address_book.h" +#include "../../util/i18n.h" +#include "../../util/text_format.h" +#include +#include + +namespace dragonx { +namespace ui { + +/** + * @brief Shared address-book contact picker popup. + * + * Header-only so Send (and later Chat "new conversation") can reuse it without + * pulling the address book into the material layer. Trigger with + * ImGui::OpenPopup(id) from a button, then call this every frame. On selection + * it copies the chosen contact's address into outBuf (NUL-terminated) and + * returns true. + * + * @param id Popup id (must match the ImGui::OpenPopup(id) call). + * @param book The App-owned address book (app->addressBook()). + * @param outBuf Destination buffer for the chosen address. + * @param outSz Size of outBuf. + * @return true on the frame a contact is picked. + */ +inline bool ContactPickerPopup(const char* id, const data::AddressBook& book, + char* outBuf, size_t outSz) +{ + bool picked = false; + if (ImGui::BeginPopup(id)) { + if (book.empty()) { + ImGui::TextDisabled("%s", TR("address_book_empty")); + } else { + const auto& entries = book.entries(); + for (size_t i = 0; i < entries.size(); ++i) { + const auto& e = entries[i]; + ImGui::PushID(static_cast(i)); + // [Z]/[T] type marker (derived from the address) + label + short address. + const char* tag = (!e.address.empty() && e.address[0] == 'z') ? "[Z] " : "[T] "; + std::string shortAddr = util::truncateMiddle(e.address, 24); + std::string row = std::string(tag) + e.label + " " + shortAddr; + if (ImGui::Selectable(row.c_str())) { + if (outSz > 0) { + std::strncpy(outBuf, e.address.c_str(), outSz - 1); + outBuf[outSz - 1] = '\0'; + } + picked = true; + ImGui::CloseCurrentPopup(); + } + ImGui::PopID(); + } + } + ImGui::EndPopup(); + } + return picked; +} + +} // namespace ui +} // namespace dragonx diff --git a/src/ui/windows/send_tab.cpp b/src/ui/windows/send_tab.cpp index 4f7ad95..89f673a 100644 --- a/src/ui/windows/send_tab.cpp +++ b/src/ui/windows/send_tab.cpp @@ -16,6 +16,7 @@ #include "../../util/text_format.h" #include "../notifications.h" #include "../layout.h" +#include "contact_picker.h" #include "../schema/ui_schema.h" #include "../material/type.h" #include "../material/draw_helpers.h" @@ -1272,7 +1273,8 @@ void RenderSendTab(App* app) ImGui::Dummy(ImVec2(0, Layout::spacingSm())); float pasteW = std::max(schema::UI().drawElement("tabs.send", "paste-btn-min-width").size, colW * schema::UI().drawElement("tabs.send", "paste-btn-width-ratio").size); - ImGui::PushItemWidth(colW - pasteW - Layout::spacingSm()); + float contactsW = ImGui::GetFrameHeight(); // compact square icon button for the contact picker + ImGui::PushItemWidth(colW - pasteW - contactsW - Layout::spacingSm() * 2.0f); // Show clipboard preview as transparent overlay when paste button is hovered bool paste_hovered = false; @@ -1331,6 +1333,15 @@ void RenderSendTab(App* app) } } + // Contact picker — pick a saved contact's address as the recipient. + ImGui::SameLine(); + if (material::TactileButton(ICON_MD_CONTACTS "##pickContact", ImVec2(contactsW, 0), + material::Type().iconMed())) + ImGui::OpenPopup("##ContactPickerPopup"); + if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("send_contacts_button")); + ContactPickerPopup("##ContactPickerPopup", app->addressBook(), + s_to_address, sizeof(s_to_address)); + // Recently sent-to suggestions RenderAddressSuggestions(state, colW, "##AddrSugForm"); } diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index bd8063d..de75f8d 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -751,6 +751,7 @@ void I18n::loadBuiltinEnglish() // Send Tab strings_["pay_from"] = "Pay From"; strings_["send_to"] = "Send To"; + strings_["send_contacts_button"] = "Pick from contacts"; strings_["amount"] = "Amount"; strings_["memo"] = "Memo (optional, encrypted)"; strings_["miner_fee"] = "Miner Fee";