feat(send): add a shared contact picker to the recipient field
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) <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
65
src/ui/windows/contact_picker.h
Normal file
65
src/ui/windows/contact_picker.h
Normal file
@@ -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 <cstring>
|
||||
#include <string>
|
||||
|
||||
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<int>(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
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user