feat(chat): copy, add-to-contacts, and new-message toast (Q2/Q3/Q4)
- Q3: copy the peer z-address from the thread header (SmallButton), and right-click any message to copy its body. - Q2: an "Add contact" action in the header when the peer isn't already known — one click saves them to the address book (rename later in Contacts). - Q4: capture ChatService::ingest's new-message count (previously discarded) and fire an in-app toast when new encrypted chat arrives while the user isn't on the Chat tab (main-thread MainCb sites only). i18n (EN + 8 languages, additive; no new CJK glyphs). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -135,12 +135,15 @@
|
||||
"change_pass_title": "Passphrase ändern",
|
||||
"characters": "Zeichen",
|
||||
"chat": "Chat",
|
||||
"chat_add_contact": "Kontakt hinzufügen",
|
||||
"chat_cancel": "Abbrechen",
|
||||
"chat_contact_added": "Kontakt hinzugefügt – benenne ihn in Kontakte um",
|
||||
"chat_contact_request": "kontaktanfrage",
|
||||
"chat_empty_hint": "Noch keine Unterhaltungen. Nachrichten, die du erhältst, erscheinen hier.",
|
||||
"chat_locked_hint": "Entsperre deine Wallet, um deine Chats zu laden.",
|
||||
"chat_new_button": "Neue Unterhaltung",
|
||||
"chat_new_message": "Nachricht",
|
||||
"chat_new_message_toast": "Neue verschlüsselte Chat-Nachricht",
|
||||
"chat_new_send": "Anfrage senden",
|
||||
"chat_new_title": "Neue Unterhaltung",
|
||||
"chat_new_zaddr": "z-Adresse des Empfängers",
|
||||
|
||||
@@ -135,12 +135,15 @@
|
||||
"change_pass_title": "Cambiar frase de contraseña",
|
||||
"characters": "caracteres",
|
||||
"chat": "Chat",
|
||||
"chat_add_contact": "Añadir contacto",
|
||||
"chat_cancel": "Cancelar",
|
||||
"chat_contact_added": "Contacto añadido: renómbralo en Contactos",
|
||||
"chat_contact_request": "solicitud de contacto",
|
||||
"chat_empty_hint": "Aún no hay conversaciones. Los mensajes que recibas aparecerán aquí.",
|
||||
"chat_locked_hint": "Desbloquea tu monedero para cargar tus chats.",
|
||||
"chat_new_button": "Nueva conversación",
|
||||
"chat_new_message": "Mensaje",
|
||||
"chat_new_message_toast": "Nuevo mensaje de chat cifrado",
|
||||
"chat_new_send": "Enviar solicitud",
|
||||
"chat_new_title": "Nueva conversación",
|
||||
"chat_new_zaddr": "Dirección z del destinatario",
|
||||
|
||||
@@ -135,12 +135,15 @@
|
||||
"change_pass_title": "Changer la phrase secrète",
|
||||
"characters": "caractères",
|
||||
"chat": "Discussion",
|
||||
"chat_add_contact": "Ajouter un contact",
|
||||
"chat_cancel": "Annuler",
|
||||
"chat_contact_added": "Contact ajouté — renommez-le dans Contacts",
|
||||
"chat_contact_request": "demande de contact",
|
||||
"chat_empty_hint": "Aucune conversation pour l'instant. Les messages que vous recevez apparaîtront ici.",
|
||||
"chat_locked_hint": "Déverrouillez votre portefeuille pour charger vos discussions.",
|
||||
"chat_new_button": "Nouvelle conversation",
|
||||
"chat_new_message": "Message",
|
||||
"chat_new_message_toast": "Nouveau message chiffré",
|
||||
"chat_new_send": "Envoyer la demande",
|
||||
"chat_new_title": "Nouvelle conversation",
|
||||
"chat_new_zaddr": "Adresse Z du destinataire",
|
||||
|
||||
@@ -135,12 +135,15 @@
|
||||
"change_pass_title": "パスフレーズを変更",
|
||||
"characters": "文字",
|
||||
"chat": "チャット",
|
||||
"chat_add_contact": "連絡先に追加",
|
||||
"chat_cancel": "キャンセル",
|
||||
"chat_contact_added": "連絡先を追加しました — 連絡先で名前を変更できます",
|
||||
"chat_contact_request": "連絡リクエスト",
|
||||
"chat_empty_hint": "まだ会話はありません。受信したメッセージはここに表示されます。",
|
||||
"chat_locked_hint": "チャットを読み込むにはウォレットのロックを解除してください。",
|
||||
"chat_new_button": "新しい会話",
|
||||
"chat_new_message": "メッセージ",
|
||||
"chat_new_message_toast": "新しい暗号化チャットメッセージ",
|
||||
"chat_new_send": "リクエストを送信",
|
||||
"chat_new_title": "新しい会話",
|
||||
"chat_new_zaddr": "宛先Zアドレス",
|
||||
|
||||
@@ -135,12 +135,15 @@
|
||||
"change_pass_title": "암호 변경",
|
||||
"characters": "문자",
|
||||
"chat": "채팅",
|
||||
"chat_add_contact": "연락처 추가",
|
||||
"chat_cancel": "취소",
|
||||
"chat_contact_added": "연락처 추가됨 — 연락처에서 이름을 변경하세요",
|
||||
"chat_contact_request": "연락 요청",
|
||||
"chat_empty_hint": "아직 대화가 없습니다. 받은 메시지가 여기에 표시됩니다.",
|
||||
"chat_locked_hint": "채팅을 불러오려면 지갑 잠금을 해제하세요.",
|
||||
"chat_new_button": "새 대화",
|
||||
"chat_new_message": "메시지",
|
||||
"chat_new_message_toast": "새 암호화 채팅 메시지",
|
||||
"chat_new_send": "요청 보내기",
|
||||
"chat_new_title": "새 대화",
|
||||
"chat_new_zaddr": "받는 사람 z-주소",
|
||||
|
||||
@@ -135,12 +135,15 @@
|
||||
"change_pass_title": "Alterar senha",
|
||||
"characters": "caracteres",
|
||||
"chat": "Chat",
|
||||
"chat_add_contact": "Adicionar contato",
|
||||
"chat_cancel": "Cancelar",
|
||||
"chat_contact_added": "Contato adicionado — renomeie em Contatos",
|
||||
"chat_contact_request": "solicitação de contato",
|
||||
"chat_empty_hint": "Nenhuma conversa ainda. As mensagens que você receber aparecerão aqui.",
|
||||
"chat_locked_hint": "Desbloqueie sua carteira para carregar suas conversas.",
|
||||
"chat_new_button": "Nova conversa",
|
||||
"chat_new_message": "Mensagem",
|
||||
"chat_new_message_toast": "Nova mensagem de chat criptografada",
|
||||
"chat_new_send": "Enviar solicitação",
|
||||
"chat_new_title": "Nova conversa",
|
||||
"chat_new_zaddr": "Endereço-z do destinatário",
|
||||
|
||||
@@ -135,12 +135,15 @@
|
||||
"change_pass_title": "Сменить пароль",
|
||||
"characters": "символов",
|
||||
"chat": "Чат",
|
||||
"chat_add_contact": "Добавить контакт",
|
||||
"chat_cancel": "Отмена",
|
||||
"chat_contact_added": "Контакт добавлен — переименуйте его в Контактах",
|
||||
"chat_contact_request": "запрос контакта",
|
||||
"chat_empty_hint": "Пока нет переписок. Полученные сообщения появятся здесь.",
|
||||
"chat_locked_hint": "Разблокируйте кошелёк, чтобы загрузить переписку.",
|
||||
"chat_new_button": "Новая переписка",
|
||||
"chat_new_message": "Сообщение",
|
||||
"chat_new_message_toast": "Новое зашифрованное сообщение",
|
||||
"chat_new_send": "Отправить запрос",
|
||||
"chat_new_title": "Новая переписка",
|
||||
"chat_new_zaddr": "Z-адрес получателя",
|
||||
|
||||
@@ -135,12 +135,15 @@
|
||||
"change_pass_title": "更改密码短语",
|
||||
"characters": "字符",
|
||||
"chat": "聊天",
|
||||
"chat_add_contact": "添加联系人",
|
||||
"chat_cancel": "取消",
|
||||
"chat_contact_added": "已添加联系人——可在联系人中重命名",
|
||||
"chat_contact_request": "联系人请求",
|
||||
"chat_empty_hint": "暂无对话。您收到的消息将显示在此处。",
|
||||
"chat_locked_hint": "解锁钱包以加载您的聊天记录。",
|
||||
"chat_new_button": "新建对话",
|
||||
"chat_new_message": "消息",
|
||||
"chat_new_message_toast": "新的加密聊天消息",
|
||||
"chat_new_send": "发送请求",
|
||||
"chat_new_title": "新建对话",
|
||||
"chat_new_zaddr": "收款方 z 地址",
|
||||
|
||||
@@ -1668,7 +1668,9 @@ void App::refreshTransactionData()
|
||||
!result.hushChatMetadata.empty()) {
|
||||
std::unordered_map<std::string, std::int64_t> chatTxTimes;
|
||||
for (const auto& tx : result.transactions) chatTxTimes[tx.txid] = tx.timestamp;
|
||||
chat_service_.ingest(result.hushChatMetadata, chatTxTimes, std::time(nullptr));
|
||||
const int newMsgs = chat_service_.ingest(result.hushChatMetadata, chatTxTimes, std::time(nullptr));
|
||||
if (newMsgs > 0 && current_page_ != ui::NavPage::Chat)
|
||||
ui::Notifications::instance().info(TR("chat_new_message_toast")); // Q4: alert when not on the Chat tab
|
||||
}
|
||||
NetworkRefreshService::applyTransactionRefreshResult(
|
||||
state_, cacheUpdate, std::move(result), std::time(nullptr));
|
||||
@@ -1726,7 +1728,9 @@ void App::refreshRecentTransactionData()
|
||||
!result.hushChatMetadata.empty()) {
|
||||
std::unordered_map<std::string, std::int64_t> chatTxTimes;
|
||||
for (const auto& tx : result.transactions) chatTxTimes[tx.txid] = tx.timestamp;
|
||||
chat_service_.ingest(result.hushChatMetadata, chatTxTimes, std::time(nullptr));
|
||||
const int newMsgs = chat_service_.ingest(result.hushChatMetadata, chatTxTimes, std::time(nullptr));
|
||||
if (newMsgs > 0 && current_page_ != ui::NavPage::Chat)
|
||||
ui::Notifications::instance().info(TR("chat_new_message_toast")); // Q4: alert when not on the Chat tab
|
||||
}
|
||||
NetworkRefreshService::applyTransactionRefreshResult(
|
||||
state_, cacheUpdate, std::move(result), std::time(nullptr));
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "../material/draw_helpers.h" // TactileButton / LabeledInput / BeginOverlayDialog
|
||||
#include "../material/project_icons.h" // ICON_MD_*
|
||||
#include "../layout.h" // Layout::dpiScale()
|
||||
#include "../notifications.h" // Notifications — add-to-contacts confirmation
|
||||
#include "imgui.h"
|
||||
|
||||
#include <sodium.h> // sodium_memzero — wipe typed plaintext on a wallet switch
|
||||
@@ -284,6 +285,16 @@ void RenderChatTab(App* app)
|
||||
ImGui::TextUnformatted(shorten(sel->peerZaddr, 20, 12).c_str());
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopFont();
|
||||
// Header actions: copy the peer z-address (Q3), and add them to contacts when unknown (Q2).
|
||||
if (ImGui::SmallButton(TR("copy_address"))) ImGui::SetClipboardText(sel->peerZaddr.c_str());
|
||||
if (book.findByAddress(sel->peerZaddr) < 0) {
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton(TR("chat_add_contact"))) {
|
||||
data::AddressBookEntry e(sel->peerName, sel->peerZaddr); // label = shortened addr; rename in Contacts
|
||||
if (book.addEntry(e)) Notifications::instance().success(TR("chat_contact_added"));
|
||||
else Notifications::instance().error(TR("address_book_exists"));
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::Separator();
|
||||
|
||||
@@ -291,7 +302,9 @@ void RenderChatTab(App* app)
|
||||
ImGui::BeginChild("##ChatMessages", ImVec2(0, ImGui::GetContentRegionAvail().y - footerH), false);
|
||||
{
|
||||
const auto messages = store.conversation(s_selected_cid);
|
||||
for (const auto& m : messages) {
|
||||
for (std::size_t mi = 0; mi < messages.size(); ++mi) {
|
||||
const auto& m = messages[mi];
|
||||
ImGui::PushID(static_cast<int>(mi));
|
||||
const bool outgoing = (m.direction == chat::ChatDirection::Outgoing);
|
||||
const bool request = (m.kind == chat::ChatMessageKind::ContactRequest);
|
||||
const bool failed = outgoing && m.delivery == chat::ChatDelivery::Failed;
|
||||
@@ -307,13 +320,20 @@ void RenderChatTab(App* app)
|
||||
ImGui::TextUnformatted(who.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopFont();
|
||||
// Body (wrapped).
|
||||
// Body (wrapped). Right-click to copy (Q3).
|
||||
ImGui::PushFont(nameFont);
|
||||
ImGui::PushTextWrapPos(0.0f);
|
||||
ImGui::TextWrapped("%s", m.body.c_str());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::PopFont();
|
||||
if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right))
|
||||
ImGui::OpenPopup("##msgmenu");
|
||||
if (ImGui::BeginPopup("##msgmenu")) {
|
||||
if (ImGui::MenuItem(TR("copy"))) ImGui::SetClipboardText(m.body.c_str());
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
ImGui::Dummy(ImVec2(0.0f, 6.0f));
|
||||
ImGui::PopID();
|
||||
}
|
||||
if (s_scroll_to_cid == s_selected_cid) {
|
||||
ImGui::SetScrollHereY(1.0f);
|
||||
|
||||
@@ -226,6 +226,9 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["chat_new_message"] = "Message";
|
||||
strings_["chat_new_send"] = "Send request";
|
||||
strings_["chat_cancel"] = "Cancel";
|
||||
strings_["chat_add_contact"] = "Add contact";
|
||||
strings_["chat_contact_added"] = "Contact added — rename it in Contacts";
|
||||
strings_["chat_new_message_toast"] = "New encrypted chat message";
|
||||
strings_["chat_toast_not_connected"] = "Not connected — chat message not sent.";
|
||||
strings_["chat_toast_no_zaddr"] = "No z-address available to send chat from.";
|
||||
strings_["chat_toast_lite_busy"] = "A send is already in progress, or no wallet is open.";
|
||||
|
||||
Reference in New Issue
Block a user