From fc414eeed452b4e8c80c70a4379b8c94719acbc1 Mon Sep 17 00:00:00 2001 From: DanS Date: Thu, 16 Jul 2026 23:26:04 -0500 Subject: [PATCH] feat(chat): hide conversations + contact address picker for new conversation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hide conversations: - A "Hide" action in the thread header drops a conversation from the list. The messages stay in the seed-encrypted store (on-chain history can't be deleted); a new INCOMING message un-hides it (you can't un-receive), so nothing is lost. - Hidden cids persist in settings (mirrors the mute list) and are skipped by both the conversation list and the unread badge. New-conversation address picker: - A "Choose from contacts" dropdown lists the address book's shielded (z-address) contacts and fills the recipient field on selection; manual paste still works. 8-language strings + CJK subset (+1 glyph 届). Co-Authored-By: Claude Opus 4.8 (1M context) --- res/fonts/NotoSansCJK-Subset.ttf | Bin 638208 -> 638384 bytes res/lang/de.json | 4 ++++ res/lang/es.json | 4 ++++ res/lang/fr.json | 4 ++++ res/lang/ja.json | 4 ++++ res/lang/ko.json | 4 ++++ res/lang/pt.json | 4 ++++ res/lang/ru.json | 4 ++++ res/lang/zh.json | 4 ++++ src/app_network.cpp | 15 +++++++++++++++ src/config/settings.cpp | 8 ++++++++ src/config/settings.h | 14 ++++++++++++++ src/ui/windows/chat_tab.cpp | 32 +++++++++++++++++++++++++++++++ src/util/i18n.cpp | 4 ++++ 14 files changed, 105 insertions(+) diff --git a/res/fonts/NotoSansCJK-Subset.ttf b/res/fonts/NotoSansCJK-Subset.ttf index a6d186fe6a416210f1411c1a4951748ea3a3c2a2..043abbbf8bc07b9e1f648f8f61a7452be62001b8 100644 GIT binary patch delta 18812 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 RG|zOVas=!i5o<=|{U2s4t&ji! delta 18515 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 I`bJ9AO_cjW(y~11EF3wbz zv1_w9b?v&yWY@PFAhX@XZi1|Kh}{v{?Cy4V6tH{R!%@&4X^%%md#XJHwd`5;Y}B#m z+H+CQo^LNieS5LJ0*&lwI~pzAnw?YouxVJ-S>tu9tql1&*DTv-qQLj@J z{hX3cMf7(nJ2fzz_d)>}42$DC*Bdw;2lwK z%ys%Z1F^sv;tWL;?~cN-#2Mp+V}&!`8IM)YL}wymoGH!}tmgev1b>(q>CD0!XAVD8 zjCbZa-g#K(EOLBU?<{v#;ZtWdZ>P5Nc4|HLI2)bK*z0`eY{Nm`TJ6ALXP2`FN1T1m zJ{)%r@CNGyZ?I0`jC02M92cCk&RJaKUDich;w|D8Ty<_ZU*Vc_-}x3doM+B6e4Ti+ z`Th!z9_JnmU%fDUZ_h}3ksW2PbW%Ebox)CWr-W0JsVwZ4Ag zb|FukZ$Y?S#{m$o%w{vv=lT$woP{{o$ufcZ&iknWdj}r|8@9a`` zN%tn-Ny!`a>bzaA%G>ptykQUE&3k3u!dK+Yd z1cv4r#b+M7lkdqyJ4cZZ);Q#T^yGf*)cxPW%z|4o%dveWz4i~@`^E#~sqwS%#&|D0 zLJ3PaBDqK<(uxcslgKKvi+rMhC@hMKlA^SzCTfa6QCrj(O+|ANBHD=#qO<5OdWivI zkQge4ixFa!7$e4sNn(nK5HrPW5hYfLRbsu^C^n0&V!PNS_KQQ}m^dZQh%db2OL0|P z7hj2+;-a=B8*$XK~nu9KVOHn~&olZWIn z={+Z}$Q$yu{6;>IkK|MNgZxGQA^(!^lvJj2RC1M4rB>-x7L`rqRC!bZRY(<4rBnq~ zLj|b%s*!4{TB~-dgX*lhsUE7A>ZAIp0cx-sszTKW6{hB>`D&qBqL!%`wLxuF+te<# zSM67a)G>7?vpTD;svGKSbxYk*chv*+RJ~9y)ob6R*>*N>I-OBx)>(CSom=PA1$1Fu zUpLZCb%^ewd+1)euO6sJ>u^0$PuA1)481@v);_&LuhMIDyxyoc>#cgb-lO;HgZijG zq0i`Z`b&L9U)SI21pU4KN&jtnOlfMq2~KIIHq)7z%&cY(GnZM|EN+&}ZdNcGm`%(| zMsIcAbHu1%{3+)qb(OEr6#GYCwn=udC)77!l0DfsaE6^??;n%+_BMt4ldl8*AAOzD zU-+_5vCC(*bJ)4<{q`aIl>NDV~#(+p(S#p3gn!JQq9{Jy-1` zo|~TAp8NJu&lArJ`8s3>T!Mo$1b*sr~H-x~ZM;vHk7ub-4XdPqrV~kL@S+Q~R0yT(7dbzOa9=U)n#~+4T2^mRLz{rck?uz&yWHDKrG8YpTOxBp070_yyKHUEo9`^N&` z8VSZT{&@1O@s2NW#)qFzrVyz`I+0Oi7TH7&k=Iv!lAR-6YtdeG6kS9Q(OV1@gT*l6 zUo<&9n|u+I?AB&8+0y+jrLWrzJF_7pec?0gqQ1P7?Hr!8zM7NmEWY-W`7*LtEmh0a z3Ki{}Hrbw?g<_dlDPqJ1{yOzD{yue& zI3Ny-x+!YVS3-L<4lfwUN)r>MLf2mqj7MEpYIaxv0l?`Q6*_^pTs48lRdZM9dCOV03qGw_!un!{G2}=_@L2MD*#7?nS927^yNpV435--JX z;=N2M)5!EPyR0aKWeeF#_Ld{~3OrVhlXLib`-$8k_sFC2g1jbga{GBMfAI1Z_O0`r zz2msuqjIR+D!(eON~$udysD(Ct6HkIs;3&NCaRTctLCXFG-?v%WtBHNui|sku-=MSqY0ks{H0Cq; za)A6(zVUZwGJk*mU)||;=3KStKf3aN_T=~Khku$fsUn;69{C)3{eDBGKCZ=u!p%$)xZjfY*u diff --git a/res/lang/de.json b/res/lang/de.json index ac91ca4..9100aed 100644 --- a/res/lang/de.json +++ b/res/lang/de.json @@ -146,6 +146,8 @@ "chat_export_done": "Unterhaltung exportiert", "chat_export_failed": "Exportdatei konnte nicht geschrieben werden.", "chat_export_warn": "Speichert die entschlüsselten Nachrichten als Klartext. Bewahre die Datei sicher auf.", + "chat_hidden_toast": "Unterhaltung ausgeblendet – eine neue Nachricht holt sie zurück", + "chat_hide": "Ausblenden", "chat_jump_latest": "Neueste", "chat_len_over": "Nachricht zu lang", "chat_locked_hint": "Entsperre deine Wallet, um deine Chats zu laden.", @@ -157,6 +159,8 @@ "chat_new_title": "Neue Unterhaltung", "chat_new_zaddr": "z-Adresse des Empfängers", "chat_no_matches": "Keine Unterhaltungen entsprechen deiner Suche.", + "chat_no_z_contacts": "Noch keine Kontakte mit geschützter Adresse", + "chat_pick_contact": "Aus Kontakten wählen…", "chat_retry": "Wiederholen", "chat_search": "Unterhaltungen durchsuchen", "chat_select_hint": "Wähle eine Unterhaltung aus, um sie anzuzeigen.", diff --git a/res/lang/es.json b/res/lang/es.json index bd11176..8aeadbe 100644 --- a/res/lang/es.json +++ b/res/lang/es.json @@ -146,6 +146,8 @@ "chat_export_done": "Conversación exportada", "chat_export_failed": "No se pudo escribir el archivo de exportación.", "chat_export_warn": "Guarda los mensajes descifrados como texto sin cifrar. Guarda el archivo de forma segura.", + "chat_hidden_toast": "Conversación oculta: un mensaje nuevo la recupera", + "chat_hide": "Ocultar", "chat_jump_latest": "Recientes", "chat_len_over": "Mensaje demasiado largo", "chat_locked_hint": "Desbloquea tu monedero para cargar tus chats.", @@ -157,6 +159,8 @@ "chat_new_title": "Nueva conversación", "chat_new_zaddr": "Dirección z del destinatario", "chat_no_matches": "Ninguna conversación coincide con tu búsqueda.", + "chat_no_z_contacts": "Aún no hay contactos con dirección blindada", + "chat_pick_contact": "Elegir de contactos…", "chat_retry": "Reintentar", "chat_search": "Buscar conversaciones", "chat_select_hint": "Selecciona una conversación para verla.", diff --git a/res/lang/fr.json b/res/lang/fr.json index aa54a97..773188a 100644 --- a/res/lang/fr.json +++ b/res/lang/fr.json @@ -146,6 +146,8 @@ "chat_export_done": "Conversation exportée", "chat_export_failed": "Impossible d'écrire le fichier d'exportation.", "chat_export_warn": "Enregistre les messages déchiffrés en texte clair. Conservez le fichier en lieu sûr.", + "chat_hidden_toast": "Conversation masquée — un nouveau message la fait réapparaître", + "chat_hide": "Masquer", "chat_jump_latest": "Récents", "chat_len_over": "Message trop long", "chat_locked_hint": "Déverrouillez votre portefeuille pour charger vos discussions.", @@ -157,6 +159,8 @@ "chat_new_title": "Nouvelle conversation", "chat_new_zaddr": "Adresse Z du destinataire", "chat_no_matches": "Aucune conversation ne correspond à votre recherche.", + "chat_no_z_contacts": "Aucun contact avec adresse blindée pour l'instant", + "chat_pick_contact": "Choisir dans les contacts…", "chat_retry": "Réessayer", "chat_search": "Rechercher des conversations", "chat_select_hint": "Sélectionnez une conversation pour l'afficher.", diff --git a/res/lang/ja.json b/res/lang/ja.json index ba02496..5cf26d2 100644 --- a/res/lang/ja.json +++ b/res/lang/ja.json @@ -146,6 +146,8 @@ "chat_export_done": "会話をエクスポートしました", "chat_export_failed": "エクスポートファイルを書き込めませんでした。", "chat_export_warn": "復号したメッセージを平文で保存します。ファイルは安全に保管してください。", + "chat_hidden_toast": "会話を非表示にしました — 新しいメッセージが届くと再表示されます", + "chat_hide": "非表示", "chat_jump_latest": "最新", "chat_len_over": "メッセージが長すぎます", "chat_locked_hint": "チャットを読み込むにはウォレットのロックを解除してください。", @@ -157,6 +159,8 @@ "chat_new_title": "新しい会話", "chat_new_zaddr": "宛先Zアドレス", "chat_no_matches": "検索に一致する会話がありません。", + "chat_no_z_contacts": "シールドアドレスの連絡先はまだありません", + "chat_pick_contact": "連絡先から選択…", "chat_retry": "再送信", "chat_search": "会話を検索", "chat_select_hint": "表示する会話を選択してください。", diff --git a/res/lang/ko.json b/res/lang/ko.json index a62e0cc..ec3300a 100644 --- a/res/lang/ko.json +++ b/res/lang/ko.json @@ -146,6 +146,8 @@ "chat_export_done": "대화를 내보냈습니다", "chat_export_failed": "내보내기 파일을 쓸 수 없습니다.", "chat_export_warn": "복호화된 메시지를 일반 텍스트로 저장합니다. 파일을 안전하게 보관하세요.", + "chat_hidden_toast": "대화를 숨겼습니다 — 새 메시지가 오면 다시 표시됩니다", + "chat_hide": "숨기기", "chat_jump_latest": "최신", "chat_len_over": "메시지가 너무 깁니다", "chat_locked_hint": "채팅을 불러오려면 지갑 잠금을 해제하세요.", @@ -157,6 +159,8 @@ "chat_new_title": "새 대화", "chat_new_zaddr": "받는 사람 z-주소", "chat_no_matches": "검색과 일치하는 대화가 없습니다.", + "chat_no_z_contacts": "보호 주소 연락처가 아직 없습니다", + "chat_pick_contact": "연락처에서 선택…", "chat_retry": "다시 시도", "chat_search": "대화 검색", "chat_select_hint": "볼 대화를 선택하세요.", diff --git a/res/lang/pt.json b/res/lang/pt.json index 84a8bb3..20107c8 100644 --- a/res/lang/pt.json +++ b/res/lang/pt.json @@ -146,6 +146,8 @@ "chat_export_done": "Conversa exportada", "chat_export_failed": "Não foi possível gravar o arquivo de exportação.", "chat_export_warn": "Salva as mensagens descriptografadas como texto simples. Guarde o arquivo com segurança.", + "chat_hidden_toast": "Conversa ocultada — uma nova mensagem a traz de volta", + "chat_hide": "Ocultar", "chat_jump_latest": "Recentes", "chat_len_over": "Mensagem muito longa", "chat_locked_hint": "Desbloqueie sua carteira para carregar suas conversas.", @@ -157,6 +159,8 @@ "chat_new_title": "Nova conversa", "chat_new_zaddr": "Endereço-z do destinatário", "chat_no_matches": "Nenhuma conversa corresponde à sua pesquisa.", + "chat_no_z_contacts": "Ainda não há contatos com endereço blindado", + "chat_pick_contact": "Escolher dos contatos…", "chat_retry": "Tentar novamente", "chat_search": "Pesquisar conversas", "chat_select_hint": "Selecione uma conversa para visualizá-la.", diff --git a/res/lang/ru.json b/res/lang/ru.json index 5ef3838..a1cc8f8 100644 --- a/res/lang/ru.json +++ b/res/lang/ru.json @@ -146,6 +146,8 @@ "chat_export_done": "Разговор экспортирован", "chat_export_failed": "Не удалось записать файл экспорта.", "chat_export_warn": "Сохраняет расшифрованные сообщения в виде обычного текста. Храните файл в надёжном месте.", + "chat_hidden_toast": "Разговор скрыт — новое сообщение вернёт его", + "chat_hide": "Скрыть", "chat_jump_latest": "Новые", "chat_len_over": "Сообщение слишком длинное", "chat_locked_hint": "Разблокируйте кошелёк, чтобы загрузить переписку.", @@ -157,6 +159,8 @@ "chat_new_title": "Новая переписка", "chat_new_zaddr": "Z-адрес получателя", "chat_no_matches": "Нет разговоров, соответствующих запросу.", + "chat_no_z_contacts": "Пока нет контактов с защищённым адресом", + "chat_pick_contact": "Выбрать из контактов…", "chat_retry": "Повторить", "chat_search": "Поиск разговоров", "chat_select_hint": "Выберите переписку для просмотра.", diff --git a/res/lang/zh.json b/res/lang/zh.json index 71c2d4f..fff5e90 100644 --- a/res/lang/zh.json +++ b/res/lang/zh.json @@ -146,6 +146,8 @@ "chat_export_done": "会话已导出", "chat_export_failed": "无法写入导出文件。", "chat_export_warn": "将解密后的消息保存为纯文本。请妥善保管该文件。", + "chat_hidden_toast": "会话已隐藏——收到新消息后会重新显示", + "chat_hide": "隐藏", "chat_jump_latest": "最新", "chat_len_over": "消息过长", "chat_locked_hint": "解锁钱包以加载您的聊天记录。", @@ -157,6 +159,8 @@ "chat_new_title": "新建对话", "chat_new_zaddr": "收款方 z 地址", "chat_no_matches": "没有与搜索匹配的会话。", + "chat_no_z_contacts": "暂无使用隐私地址的联系人", + "chat_pick_contact": "从联系人中选择…", "chat_retry": "重试", "chat_search": "搜索会话", "chat_select_hint": "选择一个对话以查看。", diff --git a/src/app_network.cpp b/src/app_network.cpp index e74b8a4..181a89d 100644 --- a/src/app_network.cpp +++ b/src/app_network.cpp @@ -1262,6 +1262,7 @@ int App::chatUnreadCount() const const auto& store = chat_service_.store(); for (const auto& cid : store.conversationIds()) { if (settings_ && settings_->isChatMuted(cid)) continue; // muted conversations don't badge (Q10) + if (settings_ && settings_->isChatHidden(cid)) continue; // hidden conversations don't badge std::int64_t seen = 0; const auto it = chat_seen_watermark_.find(cid); if (it != chat_seen_watermark_.end()) seen = it->second; @@ -1719,6 +1720,13 @@ void App::refreshTransactionData() for (const auto& tx : result.transactions) chatTxTimes[tx.txid] = tx.timestamp; std::vector newChatCids; chat_service_.ingest(result.hushChatMetadata, chatTxTimes, std::time(nullptr), &newChatCids); + // A new message un-hides a hidden conversation (you can't un-receive), so nothing is lost. + if (settings_) { + bool unhid = false; + for (const auto& cid : newChatCids) + if (settings_->isChatHidden(cid)) { settings_->setChatHidden(cid, false); unhid = true; } + if (unhid) settings_->save(); + } // Toast when a NON-muted conversation received a new message and we're off the Chat tab. // Gate on the ingest-reported cids, not a seen-watermark delta — the latter can be swallowed // by block-time vs wall-clock (echo) skew (Q10 + Q4). @@ -1785,6 +1793,13 @@ void App::refreshRecentTransactionData() for (const auto& tx : result.transactions) chatTxTimes[tx.txid] = tx.timestamp; std::vector newChatCids; chat_service_.ingest(result.hushChatMetadata, chatTxTimes, std::time(nullptr), &newChatCids); + // A new message un-hides a hidden conversation (you can't un-receive), so nothing is lost. + if (settings_) { + bool unhid = false; + for (const auto& cid : newChatCids) + if (settings_->isChatHidden(cid)) { settings_->setChatHidden(cid, false); unhid = true; } + if (unhid) settings_->save(); + } // Toast when a NON-muted conversation received a new message and we're off the Chat tab. // Gate on the ingest-reported cids, not a seen-watermark delta — the latter can be swallowed // by block-time vs wall-clock (echo) skew (Q10 + Q4). diff --git a/src/config/settings.cpp b/src/config/settings.cpp index 6e82421..499a742 100644 --- a/src/config/settings.cpp +++ b/src/config/settings.cpp @@ -152,6 +152,11 @@ bool Settings::load(const std::string& path) for (const auto& c : j["muted_chat_cids"]) if (c.is_string()) muted_chat_cids_.push_back(c.get()); } + if (j.contains("hidden_chat_cids") && j["hidden_chat_cids"].is_array()) { + hidden_chat_cids_.clear(); + for (const auto& c : j["hidden_chat_cids"]) + if (c.is_string()) hidden_chat_cids_.push_back(c.get()); + } loadScalar(j, "acrylic_enabled", acrylic_enabled_); loadScalar(j, "acrylic_quality", acrylic_quality_); loadScalar(j, "blur_multiplier", blur_multiplier_); @@ -430,6 +435,9 @@ bool Settings::save(const std::string& path) j["muted_chat_cids"] = json::array(); for (const auto& c : muted_chat_cids_) j["muted_chat_cids"].push_back(c); + j["hidden_chat_cids"] = json::array(); + for (const auto& c : hidden_chat_cids_) + j["hidden_chat_cids"].push_back(c); j["acrylic_enabled"] = acrylic_enabled_; j["acrylic_quality"] = acrylic_quality_; j["blur_multiplier"] = blur_multiplier_; diff --git a/src/config/settings.h b/src/config/settings.h index d698df7..59f31d4 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -130,6 +130,19 @@ public: muted_chat_cids_.end()); } + // Hidden chat conversations (by cid) — hidden ones are filtered out of the list; a new incoming + // message un-hides them (you can't un-receive) so nothing is silently lost. + bool isChatHidden(const std::string& cid) const { + return std::find(hidden_chat_cids_.begin(), hidden_chat_cids_.end(), cid) != hidden_chat_cids_.end(); + } + void setChatHidden(const std::string& cid, bool hidden) { + const bool already = isChatHidden(cid); + if (hidden && !already) hidden_chat_cids_.push_back(cid); + else if (!hidden && already) + hidden_chat_cids_.erase(std::remove(hidden_chat_cids_.begin(), hidden_chat_cids_.end(), cid), + hidden_chat_cids_.end()); + } + // Privacy bool getSaveZtxs() const { return save_ztxs_; } void setSaveZtxs(bool save) { save_ztxs_ = save; } @@ -486,6 +499,7 @@ private: std::string skin_id_ = "dragonx"; std::string chat_reply_zaddr_; std::vector muted_chat_cids_; // muted chat conversations by cid (Q10) + std::vector hidden_chat_cids_; // hidden chat conversations by cid bool save_ztxs_ = true; bool auto_shield_ = true; bool use_tor_ = false; diff --git a/src/ui/windows/chat_tab.cpp b/src/ui/windows/chat_tab.cpp index aa0303b..32c5571 100644 --- a/src/ui/windows/chat_tab.cpp +++ b/src/ui/windows/chat_tab.cpp @@ -216,6 +216,7 @@ void RenderChatTab(App* app) // Build conversation summaries (single scan per conversation), sorted by most-recent activity. std::vector convs; for (const auto& cid : store.conversationIds()) { + if (app->settings() && app->settings()->isChatHidden(cid)) continue; // hidden — a new message un-hides it const auto messages = store.conversation(cid); if (messages.empty()) continue; ConvSummary c; @@ -439,6 +440,16 @@ void RenderChatTab(App* app) app->settings()->setChatMuted(sel->cid, !muted); app->settings()->save(); } + // Hide conversation — drops it from the list (messages stay in the encrypted store; a + // new incoming message brings it back). Deselect so the thread resets to the next one. + ImGui::SameLine(); + if (ImGui::SmallButton(TR("chat_hide")) && app->settings()) { + app->markChatConversationSeen(sel->cid, sel->lastTs); // don't leave a phantom unread + app->settings()->setChatHidden(sel->cid, true); + app->settings()->save(); + s_selected_cid.clear(); + Notifications::instance().info(TR("chat_hidden_toast")); + } } ImGui::Separator(); @@ -620,6 +631,27 @@ void RenderChatTab(App* app) if (material::BeginOverlayDialog(ov)) { const float fieldW = ImGui::GetContentRegionAvail().x; material::LabeledInput(TR("chat_new_zaddr"), "##newz", s_new_zaddr, sizeof(s_new_zaddr), fieldW); + // Or pick from contacts — chat needs a shielded z-address, so only z-addr contacts are listed. + // Selecting one fills the field above (manual paste still works). + ImGui::SetNextItemWidth(fieldW); + if (ImGui::BeginCombo("##newzpick", TR("chat_pick_contact"), ImGuiComboFlags_HeightLarge)) { + int shown = 0; + for (const auto& e : book.entries()) { + if (e.address.empty() || e.address[0] != 'z') continue; // chat requires a z-address + ++shown; + const std::string item = e.label + " " + shorten(e.address, 16, 8); + if (ImGui::Selectable(item.c_str())) { + std::strncpy(s_new_zaddr, e.address.c_str(), sizeof(s_new_zaddr) - 1); + s_new_zaddr[sizeof(s_new_zaddr) - 1] = '\0'; + } + } + if (shown == 0) { + ImGui::PushStyleColor(ImGuiCol_Text, material::OnSurfaceMedium()); + ImGui::TextUnformatted(TR("chat_no_z_contacts")); + ImGui::PopStyleColor(); + } + ImGui::EndCombo(); + } ImGui::Dummy(ImVec2(0, Layout::spacingSm())); material::LabeledInput(TR("chat_new_message"), "##newm", s_new_msg, sizeof(s_new_msg), fieldW); ImGui::Dummy(ImVec2(0, Layout::spacingMd())); diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index f951f58..e9f1a50 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -252,6 +252,10 @@ void I18n::loadBuiltinEnglish() strings_["chat_len_over"] = "Message too long"; strings_["chat_mute"] = "Mute"; strings_["chat_unmute"] = "Unmute"; + strings_["chat_hide"] = "Hide"; + strings_["chat_hidden_toast"] = "Conversation hidden — a new message brings it back"; + strings_["chat_pick_contact"] = "Choose from contacts\xE2\x80\xA6"; + strings_["chat_no_z_contacts"] = "No shielded-address contacts yet"; // Seed-phrase backup (full-node) strings_["seed_backup_button"] = "Seed phrase"; strings_["tt_seed_backup"] = "Show and back up your wallet's 24-word recovery seed phrase";