3 Commits

Author SHA1 Message Date
64b27db2ff feat(chat): emoji picker as an in-pane overlay with keyword search
Reworked the emoji picker from a floating popup (which overlapped the message
area) into an overlay that takes over the conversation-list pane while open:
a Cancel button + a keyword search box at the top, then a responsive grid below
that wraps to the pane width.

Each emoji now carries search keywords (grin/heart/fire/…) so the search box
filters the ~150-emoji set. The 🙂 composer button toggles the overlay;
selecting an emoji appends it to the composer (byte-cap respected) and the
overlay stays open for multiple picks.

+1 CJK glyph (絵) for the ja "Search emoji" string.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 08:06:37 -05:00
03fe6e077c feat(chat): emoji picker + show-hidden toggle
- Emoji picker: a 🙂 button on the composer row opens a scrollable grid of ~150
  common single-codepoint emoji (all verified present in the bundled NotoEmoji
  subset); clicking appends to the composer, respecting the byte cap. ImGui does
  no shaping, so the set is single-codepoint only (ZWJ sequences / flags omitted).
- Show hidden: when there are hidden conversations, a "Show hidden (N)" toggle
  appears in the list; toggling it reveals them (dimmed) and the thread header's
  Hide button becomes Unhide. Off by default, and resets when nothing is hidden.

8-language strings; no new CJK glyphs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 01:08:35 -05:00
125ffa8863 feat(chat): dedicated ~2.5s poll for the 0-conf fast-scan
The fast-scan was hung on the page's Transactions timer, which is 10–15s on most
pages (15s on Chat), so incoming messages still took up to ~15s to appear. Give it
its own ~2.5s accumulator (delta-time based, independent of the page cadence) so
messages land in a few seconds — network propagation then dominates, which is as
fast as 0-conf gets. Still gated behind the warmup/rescan block and self-gated in
fastScanChatMemos; the in-flight guard prevents overlapping RPCs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 00:58:42 -05:00
13 changed files with 164 additions and 14 deletions

Binary file not shown.

View File

@@ -139,6 +139,7 @@
"chat_cancel": "Abbrechen", "chat_cancel": "Abbrechen",
"chat_contact_added": "Kontakt hinzugefügt benenne ihn in Kontakte um", "chat_contact_added": "Kontakt hinzugefügt benenne ihn in Kontakte um",
"chat_contact_request": "kontaktanfrage", "chat_contact_request": "kontaktanfrage",
"chat_emoji_search": "Emoji suchen",
"chat_empty_hint": "Noch keine Unterhaltungen. Nachrichten, die du erhältst, erscheinen hier.", "chat_empty_hint": "Noch keine Unterhaltungen. Nachrichten, die du erhältst, erscheinen hier.",
"chat_empty_start": "Starte eine mit \"Neue Unterhaltung\".", "chat_empty_start": "Starte eine mit \"Neue Unterhaltung\".",
"chat_empty_title": "Noch keine Unterhaltungen", "chat_empty_title": "Noch keine Unterhaltungen",
@@ -148,6 +149,7 @@
"chat_export_warn": "Speichert die entschlüsselten Nachrichten als Klartext. Bewahre die Datei sicher auf.", "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_hidden_toast": "Unterhaltung ausgeblendet eine neue Nachricht holt sie zurück",
"chat_hide": "Ausblenden", "chat_hide": "Ausblenden",
"chat_hide_hidden": "Ausgeblendete verbergen",
"chat_jump_latest": "Neueste", "chat_jump_latest": "Neueste",
"chat_len_over": "Nachricht zu lang", "chat_len_over": "Nachricht zu lang",
"chat_locked_hint": "Entsperre deine Wallet, um deine Chats zu laden.", "chat_locked_hint": "Entsperre deine Wallet, um deine Chats zu laden.",
@@ -167,6 +169,7 @@
"chat_send": "Senden", "chat_send": "Senden",
"chat_send_failed": "nicht gesendet", "chat_send_failed": "nicht gesendet",
"chat_sending": "senden…", "chat_sending": "senden…",
"chat_show_hidden": "Ausgeblendete anzeigen",
"chat_time_now": "jetzt", "chat_time_now": "jetzt",
"chat_toast_compose_failed": "Nachricht konnte nicht erstellt werden (zu lang?).", "chat_toast_compose_failed": "Nachricht konnte nicht erstellt werden (zu lang?).",
"chat_toast_lite_busy": "Es wird bereits gesendet, oder es ist keine Wallet geöffnet.", "chat_toast_lite_busy": "Es wird bereits gesendet, oder es ist keine Wallet geöffnet.",
@@ -176,6 +179,7 @@
"chat_toast_request_compose_failed": "Kontaktanfrage konnte nicht erstellt werden (ungültige Adresse / ungültiger Text?).", "chat_toast_request_compose_failed": "Kontaktanfrage konnte nicht erstellt werden (ungültige Adresse / ungültiger Text?).",
"chat_toast_request_queued": "Kontaktanfrage in Warteschlange.", "chat_toast_request_queued": "Kontaktanfrage in Warteschlange.",
"chat_toast_waiting_reply": "Warte auf die Antwort des Kontakts, bevor du ihm schreiben kannst.", "chat_toast_waiting_reply": "Warte auf die Antwort des Kontakts, bevor du ihm schreiben kannst.",
"chat_unhide": "Einblenden",
"chat_unmute": "Stummschaltung aufheben", "chat_unmute": "Stummschaltung aufheben",
"chat_waiting_reply": "Warte auf die Antwort dieses Kontakts sobald er antwortet, kannst du ihm schreiben.", "chat_waiting_reply": "Warte auf die Antwort dieses Kontakts sobald er antwortet, kannst du ihm schreiben.",
"chat_you": "Du", "chat_you": "Du",

View File

@@ -139,6 +139,7 @@
"chat_cancel": "Cancelar", "chat_cancel": "Cancelar",
"chat_contact_added": "Contacto añadido: renómbralo en Contactos", "chat_contact_added": "Contacto añadido: renómbralo en Contactos",
"chat_contact_request": "solicitud de contacto", "chat_contact_request": "solicitud de contacto",
"chat_emoji_search": "Buscar emoji",
"chat_empty_hint": "Aún no hay conversaciones. Los mensajes que recibas aparecerán aquí.", "chat_empty_hint": "Aún no hay conversaciones. Los mensajes que recibas aparecerán aquí.",
"chat_empty_start": "Inicia una con \"Nueva conversación\".", "chat_empty_start": "Inicia una con \"Nueva conversación\".",
"chat_empty_title": "Aún no hay conversaciones", "chat_empty_title": "Aún no hay conversaciones",
@@ -148,6 +149,7 @@
"chat_export_warn": "Guarda los mensajes descifrados como texto sin cifrar. Guarda el archivo de forma segura.", "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_hidden_toast": "Conversación oculta: un mensaje nuevo la recupera",
"chat_hide": "Ocultar", "chat_hide": "Ocultar",
"chat_hide_hidden": "Ocultar ocultos",
"chat_jump_latest": "Recientes", "chat_jump_latest": "Recientes",
"chat_len_over": "Mensaje demasiado largo", "chat_len_over": "Mensaje demasiado largo",
"chat_locked_hint": "Desbloquea tu monedero para cargar tus chats.", "chat_locked_hint": "Desbloquea tu monedero para cargar tus chats.",
@@ -167,6 +169,7 @@
"chat_send": "Enviar", "chat_send": "Enviar",
"chat_send_failed": "no enviado", "chat_send_failed": "no enviado",
"chat_sending": "enviando…", "chat_sending": "enviando…",
"chat_show_hidden": "Ver ocultos",
"chat_time_now": "ahora", "chat_time_now": "ahora",
"chat_toast_compose_failed": "No se pudo componer el mensaje (¿demasiado largo?).", "chat_toast_compose_failed": "No se pudo componer el mensaje (¿demasiado largo?).",
"chat_toast_lite_busy": "Ya hay un envío en curso, o no hay ningún monedero abierto.", "chat_toast_lite_busy": "Ya hay un envío en curso, o no hay ningún monedero abierto.",
@@ -176,6 +179,7 @@
"chat_toast_request_compose_failed": "No se pudo componer la solicitud de contacto (¿dirección o texto no válidos?).", "chat_toast_request_compose_failed": "No se pudo componer la solicitud de contacto (¿dirección o texto no válidos?).",
"chat_toast_request_queued": "Solicitud de contacto en cola.", "chat_toast_request_queued": "Solicitud de contacto en cola.",
"chat_toast_waiting_reply": "Espera a que este contacto responda antes de poder escribirle.", "chat_toast_waiting_reply": "Espera a que este contacto responda antes de poder escribirle.",
"chat_unhide": "Mostrar",
"chat_unmute": "Reactivar", "chat_unmute": "Reactivar",
"chat_waiting_reply": "Esperando a que este contacto responda: podrás escribirle una vez lo haga.", "chat_waiting_reply": "Esperando a que este contacto responda: podrás escribirle una vez lo haga.",
"chat_you": "Tú", "chat_you": "Tú",

View File

@@ -139,6 +139,7 @@
"chat_cancel": "Annuler", "chat_cancel": "Annuler",
"chat_contact_added": "Contact ajouté — renommez-le dans Contacts", "chat_contact_added": "Contact ajouté — renommez-le dans Contacts",
"chat_contact_request": "demande de contact", "chat_contact_request": "demande de contact",
"chat_emoji_search": "Rechercher un emoji",
"chat_empty_hint": "Aucune conversation pour l'instant. Les messages que vous recevez apparaîtront ici.", "chat_empty_hint": "Aucune conversation pour l'instant. Les messages que vous recevez apparaîtront ici.",
"chat_empty_start": "Commencez-en une avec « Nouvelle conversation ».", "chat_empty_start": "Commencez-en une avec « Nouvelle conversation ».",
"chat_empty_title": "Aucune conversation pour l'instant", "chat_empty_title": "Aucune conversation pour l'instant",
@@ -148,6 +149,7 @@
"chat_export_warn": "Enregistre les messages déchiffrés en texte clair. Conservez le fichier en lieu sûr.", "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_hidden_toast": "Conversation masquée — un nouveau message la fait réapparaître",
"chat_hide": "Masquer", "chat_hide": "Masquer",
"chat_hide_hidden": "Masquer masqués",
"chat_jump_latest": "Récents", "chat_jump_latest": "Récents",
"chat_len_over": "Message trop long", "chat_len_over": "Message trop long",
"chat_locked_hint": "Déverrouillez votre portefeuille pour charger vos discussions.", "chat_locked_hint": "Déverrouillez votre portefeuille pour charger vos discussions.",
@@ -167,6 +169,7 @@
"chat_send": "Envoyer", "chat_send": "Envoyer",
"chat_send_failed": "non envoyé", "chat_send_failed": "non envoyé",
"chat_sending": "envoi…", "chat_sending": "envoi…",
"chat_show_hidden": "Afficher masqués",
"chat_time_now": "à l'instant", "chat_time_now": "à l'instant",
"chat_toast_compose_failed": "Impossible de composer le message (trop long ?).", "chat_toast_compose_failed": "Impossible de composer le message (trop long ?).",
"chat_toast_lite_busy": "Un envoi est déjà en cours, ou aucun portefeuille n'est ouvert.", "chat_toast_lite_busy": "Un envoi est déjà en cours, ou aucun portefeuille n'est ouvert.",
@@ -176,6 +179,7 @@
"chat_toast_request_compose_failed": "Impossible de composer la demande de contact (adresse / texte invalide ?).", "chat_toast_request_compose_failed": "Impossible de composer la demande de contact (adresse / texte invalide ?).",
"chat_toast_request_queued": "Demande de contact mise en file d'attente.", "chat_toast_request_queued": "Demande de contact mise en file d'attente.",
"chat_toast_waiting_reply": "En attente de la réponse de ce contact avant de pouvoir lui écrire.", "chat_toast_waiting_reply": "En attente de la réponse de ce contact avant de pouvoir lui écrire.",
"chat_unhide": "Afficher",
"chat_unmute": "Réactiver", "chat_unmute": "Réactiver",
"chat_waiting_reply": "En attente de la réponse de ce contact — vous pourrez lui écrire dès qu'il aura répondu.", "chat_waiting_reply": "En attente de la réponse de ce contact — vous pourrez lui écrire dès qu'il aura répondu.",
"chat_you": "Vous", "chat_you": "Vous",

View File

@@ -139,6 +139,7 @@
"chat_cancel": "キャンセル", "chat_cancel": "キャンセル",
"chat_contact_added": "連絡先を追加しました — 連絡先で名前を変更できます", "chat_contact_added": "連絡先を追加しました — 連絡先で名前を変更できます",
"chat_contact_request": "連絡リクエスト", "chat_contact_request": "連絡リクエスト",
"chat_emoji_search": "絵文字を検索",
"chat_empty_hint": "まだ会話はありません。受信したメッセージはここに表示されます。", "chat_empty_hint": "まだ会話はありません。受信したメッセージはここに表示されます。",
"chat_empty_start": "「新しい会話」から始めましょう。", "chat_empty_start": "「新しい会話」から始めましょう。",
"chat_empty_title": "会話はまだありません", "chat_empty_title": "会話はまだありません",
@@ -148,6 +149,7 @@
"chat_export_warn": "復号したメッセージを平文で保存します。ファイルは安全に保管してください。", "chat_export_warn": "復号したメッセージを平文で保存します。ファイルは安全に保管してください。",
"chat_hidden_toast": "会話を非表示にしました — 新しいメッセージが届くと再表示されます", "chat_hidden_toast": "会話を非表示にしました — 新しいメッセージが届くと再表示されます",
"chat_hide": "非表示", "chat_hide": "非表示",
"chat_hide_hidden": "非表示を隠す",
"chat_jump_latest": "最新", "chat_jump_latest": "最新",
"chat_len_over": "メッセージが長すぎます", "chat_len_over": "メッセージが長すぎます",
"chat_locked_hint": "チャットを読み込むにはウォレットのロックを解除してください。", "chat_locked_hint": "チャットを読み込むにはウォレットのロックを解除してください。",
@@ -167,6 +169,7 @@
"chat_send": "送信", "chat_send": "送信",
"chat_send_failed": "未送信", "chat_send_failed": "未送信",
"chat_sending": "送信中…", "chat_sending": "送信中…",
"chat_show_hidden": "非表示を表示",
"chat_time_now": "たった今", "chat_time_now": "たった今",
"chat_toast_compose_failed": "メッセージを作成できませんでした(長すぎませんか?)。", "chat_toast_compose_failed": "メッセージを作成できませんでした(長すぎませんか?)。",
"chat_toast_lite_busy": "すでに送信処理が進行中か、ウォレットが開かれていません。", "chat_toast_lite_busy": "すでに送信処理が進行中か、ウォレットが開かれていません。",
@@ -176,6 +179,7 @@
"chat_toast_request_compose_failed": "連絡リクエストを作成できませんでした(アドレスまたはテキストが無効?)。", "chat_toast_request_compose_failed": "連絡リクエストを作成できませんでした(アドレスまたはテキストが無効?)。",
"chat_toast_request_queued": "連絡リクエストを送信待ちに追加しました。", "chat_toast_request_queued": "連絡リクエストを送信待ちに追加しました。",
"chat_toast_waiting_reply": "メッセージを送るには、この相手からの返信を待つ必要があります。", "chat_toast_waiting_reply": "メッセージを送るには、この相手からの返信を待つ必要があります。",
"chat_unhide": "再表示",
"chat_unmute": "ミュート解除", "chat_unmute": "ミュート解除",
"chat_waiting_reply": "この相手からの返信を待っています — 返信があればメッセージを送れます。", "chat_waiting_reply": "この相手からの返信を待っています — 返信があればメッセージを送れます。",
"chat_you": "自分", "chat_you": "自分",

View File

@@ -139,6 +139,7 @@
"chat_cancel": "취소", "chat_cancel": "취소",
"chat_contact_added": "연락처 추가됨 — 연락처에서 이름을 변경하세요", "chat_contact_added": "연락처 추가됨 — 연락처에서 이름을 변경하세요",
"chat_contact_request": "연락 요청", "chat_contact_request": "연락 요청",
"chat_emoji_search": "이모지 검색",
"chat_empty_hint": "아직 대화가 없습니다. 받은 메시지가 여기에 표시됩니다.", "chat_empty_hint": "아직 대화가 없습니다. 받은 메시지가 여기에 표시됩니다.",
"chat_empty_start": "\"새 대화\"로 시작하세요.", "chat_empty_start": "\"새 대화\"로 시작하세요.",
"chat_empty_title": "아직 대화가 없습니다", "chat_empty_title": "아직 대화가 없습니다",
@@ -148,6 +149,7 @@
"chat_export_warn": "복호화된 메시지를 일반 텍스트로 저장합니다. 파일을 안전하게 보관하세요.", "chat_export_warn": "복호화된 메시지를 일반 텍스트로 저장합니다. 파일을 안전하게 보관하세요.",
"chat_hidden_toast": "대화를 숨겼습니다 — 새 메시지가 오면 다시 표시됩니다", "chat_hidden_toast": "대화를 숨겼습니다 — 새 메시지가 오면 다시 표시됩니다",
"chat_hide": "숨기기", "chat_hide": "숨기기",
"chat_hide_hidden": "숨김 접기",
"chat_jump_latest": "최신", "chat_jump_latest": "최신",
"chat_len_over": "메시지가 너무 깁니다", "chat_len_over": "메시지가 너무 깁니다",
"chat_locked_hint": "채팅을 불러오려면 지갑 잠금을 해제하세요.", "chat_locked_hint": "채팅을 불러오려면 지갑 잠금을 해제하세요.",
@@ -167,6 +169,7 @@
"chat_send": "전송", "chat_send": "전송",
"chat_send_failed": "전송 안 됨", "chat_send_failed": "전송 안 됨",
"chat_sending": "전송 중…", "chat_sending": "전송 중…",
"chat_show_hidden": "숨김 보기",
"chat_time_now": "방금", "chat_time_now": "방금",
"chat_toast_compose_failed": "메시지를 작성할 수 없습니다 (너무 긴가요?).", "chat_toast_compose_failed": "메시지를 작성할 수 없습니다 (너무 긴가요?).",
"chat_toast_lite_busy": "이미 전송이 진행 중이거나 열린 지갑이 없습니다.", "chat_toast_lite_busy": "이미 전송이 진행 중이거나 열린 지갑이 없습니다.",
@@ -176,6 +179,7 @@
"chat_toast_request_compose_failed": "연락 요청을 작성할 수 없습니다 (잘못된 주소 / 텍스트?).", "chat_toast_request_compose_failed": "연락 요청을 작성할 수 없습니다 (잘못된 주소 / 텍스트?).",
"chat_toast_request_queued": "연락 요청이 대기열에 추가되었습니다.", "chat_toast_request_queued": "연락 요청이 대기열에 추가되었습니다.",
"chat_toast_waiting_reply": "상대방이 답장해야 메시지를 보낼 수 있습니다.", "chat_toast_waiting_reply": "상대방이 답장해야 메시지를 보낼 수 있습니다.",
"chat_unhide": "다시 표시",
"chat_unmute": "음소거 해제", "chat_unmute": "음소거 해제",
"chat_waiting_reply": "상대방의 답장을 기다리는 중입니다 — 답장하면 메시지를 보낼 수 있습니다.", "chat_waiting_reply": "상대방의 답장을 기다리는 중입니다 — 답장하면 메시지를 보낼 수 있습니다.",
"chat_you": "나", "chat_you": "나",

View File

@@ -139,6 +139,7 @@
"chat_cancel": "Cancelar", "chat_cancel": "Cancelar",
"chat_contact_added": "Contato adicionado — renomeie em Contatos", "chat_contact_added": "Contato adicionado — renomeie em Contatos",
"chat_contact_request": "solicitação de contato", "chat_contact_request": "solicitação de contato",
"chat_emoji_search": "Pesquisar emoji",
"chat_empty_hint": "Nenhuma conversa ainda. As mensagens que você receber aparecerão aqui.", "chat_empty_hint": "Nenhuma conversa ainda. As mensagens que você receber aparecerão aqui.",
"chat_empty_start": "Inicie uma com \"Nova conversa\".", "chat_empty_start": "Inicie uma com \"Nova conversa\".",
"chat_empty_title": "Ainda não há conversas", "chat_empty_title": "Ainda não há conversas",
@@ -148,6 +149,7 @@
"chat_export_warn": "Salva as mensagens descriptografadas como texto simples. Guarde o arquivo com segurança.", "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_hidden_toast": "Conversa ocultada — uma nova mensagem a traz de volta",
"chat_hide": "Ocultar", "chat_hide": "Ocultar",
"chat_hide_hidden": "Ocultar ocultas",
"chat_jump_latest": "Recentes", "chat_jump_latest": "Recentes",
"chat_len_over": "Mensagem muito longa", "chat_len_over": "Mensagem muito longa",
"chat_locked_hint": "Desbloqueie sua carteira para carregar suas conversas.", "chat_locked_hint": "Desbloqueie sua carteira para carregar suas conversas.",
@@ -167,6 +169,7 @@
"chat_send": "Enviar", "chat_send": "Enviar",
"chat_send_failed": "não enviada", "chat_send_failed": "não enviada",
"chat_sending": "enviando…", "chat_sending": "enviando…",
"chat_show_hidden": "Ver ocultas",
"chat_time_now": "agora", "chat_time_now": "agora",
"chat_toast_compose_failed": "Não foi possível compor a mensagem (muito longa?).", "chat_toast_compose_failed": "Não foi possível compor a mensagem (muito longa?).",
"chat_toast_lite_busy": "Já há um envio em andamento, ou nenhuma carteira está aberta.", "chat_toast_lite_busy": "Já há um envio em andamento, ou nenhuma carteira está aberta.",
@@ -176,6 +179,7 @@
"chat_toast_request_compose_failed": "Não foi possível compor a solicitação de contato (endereço / texto inválido?).", "chat_toast_request_compose_failed": "Não foi possível compor a solicitação de contato (endereço / texto inválido?).",
"chat_toast_request_queued": "Solicitação de contato na fila.", "chat_toast_request_queued": "Solicitação de contato na fila.",
"chat_toast_waiting_reply": "Aguardando a resposta do contato antes que você possa enviar mensagens a ele.", "chat_toast_waiting_reply": "Aguardando a resposta do contato antes que você possa enviar mensagens a ele.",
"chat_unhide": "Mostrar",
"chat_unmute": "Reativar som", "chat_unmute": "Reativar som",
"chat_waiting_reply": "Aguardando a resposta deste contato — você poderá enviar mensagens assim que ele responder.", "chat_waiting_reply": "Aguardando a resposta deste contato — você poderá enviar mensagens assim que ele responder.",
"chat_you": "Você", "chat_you": "Você",

View File

@@ -139,6 +139,7 @@
"chat_cancel": "Отмена", "chat_cancel": "Отмена",
"chat_contact_added": "Контакт добавлен — переименуйте его в Контактах", "chat_contact_added": "Контакт добавлен — переименуйте его в Контактах",
"chat_contact_request": "запрос контакта", "chat_contact_request": "запрос контакта",
"chat_emoji_search": "Поиск эмодзи",
"chat_empty_hint": "Пока нет переписок. Полученные сообщения появятся здесь.", "chat_empty_hint": "Пока нет переписок. Полученные сообщения появятся здесь.",
"chat_empty_start": "Начните новый с помощью «Новый разговор».", "chat_empty_start": "Начните новый с помощью «Новый разговор».",
"chat_empty_title": "Пока нет разговоров", "chat_empty_title": "Пока нет разговоров",
@@ -148,6 +149,7 @@
"chat_export_warn": "Сохраняет расшифрованные сообщения в виде обычного текста. Храните файл в надёжном месте.", "chat_export_warn": "Сохраняет расшифрованные сообщения в виде обычного текста. Храните файл в надёжном месте.",
"chat_hidden_toast": "Разговор скрыт — новое сообщение вернёт его", "chat_hidden_toast": "Разговор скрыт — новое сообщение вернёт его",
"chat_hide": "Скрыть", "chat_hide": "Скрыть",
"chat_hide_hidden": "Скрыть скрытые",
"chat_jump_latest": "Новые", "chat_jump_latest": "Новые",
"chat_len_over": "Сообщение слишком длинное", "chat_len_over": "Сообщение слишком длинное",
"chat_locked_hint": "Разблокируйте кошелёк, чтобы загрузить переписку.", "chat_locked_hint": "Разблокируйте кошелёк, чтобы загрузить переписку.",
@@ -167,6 +169,7 @@
"chat_send": "Отправить", "chat_send": "Отправить",
"chat_send_failed": "не отправлено", "chat_send_failed": "не отправлено",
"chat_sending": "отправка…", "chat_sending": "отправка…",
"chat_show_hidden": "Показать скрытые",
"chat_time_now": "сейчас", "chat_time_now": "сейчас",
"chat_toast_compose_failed": "Не удалось составить сообщение (слишком длинное?).", "chat_toast_compose_failed": "Не удалось составить сообщение (слишком длинное?).",
"chat_toast_lite_busy": "Отправка уже выполняется, или кошелёк не открыт.", "chat_toast_lite_busy": "Отправка уже выполняется, или кошелёк не открыт.",
@@ -176,6 +179,7 @@
"chat_toast_request_compose_failed": "Не удалось составить запрос контакта (неверный адрес / текст?).", "chat_toast_request_compose_failed": "Не удалось составить запрос контакта (неверный адрес / текст?).",
"chat_toast_request_queued": "Запрос контакта поставлен в очередь.", "chat_toast_request_queued": "Запрос контакта поставлен в очередь.",
"chat_toast_waiting_reply": "Ожидание ответа от контакта — вы сможете писать ему только после этого.", "chat_toast_waiting_reply": "Ожидание ответа от контакта — вы сможете писать ему только после этого.",
"chat_unhide": "Показать",
"chat_unmute": "Включить уведомления", "chat_unmute": "Включить уведомления",
"chat_waiting_reply": "Ожидание ответа от контакта — вы сможете писать ему, как только он ответит.", "chat_waiting_reply": "Ожидание ответа от контакта — вы сможете писать ему, как только он ответит.",
"chat_you": "Вы", "chat_you": "Вы",

View File

@@ -139,6 +139,7 @@
"chat_cancel": "取消", "chat_cancel": "取消",
"chat_contact_added": "已添加联系人——可在联系人中重命名", "chat_contact_added": "已添加联系人——可在联系人中重命名",
"chat_contact_request": "联系人请求", "chat_contact_request": "联系人请求",
"chat_emoji_search": "搜索表情",
"chat_empty_hint": "暂无对话。您收到的消息将显示在此处。", "chat_empty_hint": "暂无对话。您收到的消息将显示在此处。",
"chat_empty_start": "点击\"新建会话\"开始。", "chat_empty_start": "点击\"新建会话\"开始。",
"chat_empty_title": "还没有会话", "chat_empty_title": "还没有会话",
@@ -148,6 +149,7 @@
"chat_export_warn": "将解密后的消息保存为纯文本。请妥善保管该文件。", "chat_export_warn": "将解密后的消息保存为纯文本。请妥善保管该文件。",
"chat_hidden_toast": "会话已隐藏——收到新消息后会重新显示", "chat_hidden_toast": "会话已隐藏——收到新消息后会重新显示",
"chat_hide": "隐藏", "chat_hide": "隐藏",
"chat_hide_hidden": "收起已隐藏",
"chat_jump_latest": "最新", "chat_jump_latest": "最新",
"chat_len_over": "消息过长", "chat_len_over": "消息过长",
"chat_locked_hint": "解锁钱包以加载您的聊天记录。", "chat_locked_hint": "解锁钱包以加载您的聊天记录。",
@@ -167,6 +169,7 @@
"chat_send": "发送", "chat_send": "发送",
"chat_send_failed": "未发送", "chat_send_failed": "未发送",
"chat_sending": "发送中…", "chat_sending": "发送中…",
"chat_show_hidden": "显示已隐藏",
"chat_time_now": "刚刚", "chat_time_now": "刚刚",
"chat_toast_compose_failed": "无法编写该消息(内容过长?)。", "chat_toast_compose_failed": "无法编写该消息(内容过长?)。",
"chat_toast_lite_busy": "已有发送正在进行中,或未打开任何钱包。", "chat_toast_lite_busy": "已有发送正在进行中,或未打开任何钱包。",
@@ -176,6 +179,7 @@
"chat_toast_request_compose_failed": "无法编写联系人请求(地址或文本无效?)。", "chat_toast_request_compose_failed": "无法编写联系人请求(地址或文本无效?)。",
"chat_toast_request_queued": "联系人请求已排队。", "chat_toast_request_queued": "联系人请求已排队。",
"chat_toast_waiting_reply": "等待该联系人回复——对方回复后您即可向其发送消息。", "chat_toast_waiting_reply": "等待该联系人回复——对方回复后您即可向其发送消息。",
"chat_unhide": "取消隐藏",
"chat_unmute": "取消静音", "chat_unmute": "取消静音",
"chat_waiting_reply": "等待该联系人回复——对方回复后您即可向其发送消息。", "chat_waiting_reply": "等待该联系人回复——对方回复后您即可向其发送消息。",
"chat_you": "我", "chat_you": "我",

View File

@@ -1168,8 +1168,14 @@ void App::update()
} else if (walletDataPage && shouldRefreshRecentTransactions()) { } else if (walletDataPage && shouldRefreshRecentTransactions()) {
refreshRecentTransactionData(); refreshRecentTransactionData();
} }
// 0-conf chat: the normal harvest above only re-scans on a new block, so also do a light }
// mempool scan of the chat address every cycle to surface messages before confirmation. // 0-conf chat: a DEDICATED ~2.5s mempool scan of the chat address, independent of the page's
// (slower, 1015s) Transactions cadence — so incoming messages surface in a few seconds. The
// normal harvest only re-scans on a new block. Self-gated (no-op without a chat identity /
// connection / on lite); the in-flight guard keeps overlapping RPCs from stacking.
chat_fast_scan_accum_ += ImGui::GetIO().DeltaTime;
if (chat_fast_scan_accum_ >= 2.5f) {
chat_fast_scan_accum_ = 0.0f;
fastScanChatMemos(); fastScanChatMemos();
} }
if (network_refresh_.consumeDue(RefreshTimer::Addresses)) { if (network_refresh_.consumeDue(RefreshTimer::Addresses)) {

View File

@@ -735,6 +735,7 @@ private:
// (they still un-hide via the normal confirmed harvest). Self-gated; no-op without a chat identity. // (they still un-hide via the normal confirmed harvest). Self-gated; no-op without a chat identity.
void fastScanChatMemos(); void fastScanChatMemos();
bool chat_fast_scan_in_flight_ = false; // guard against overlapping fast-scan RPCs bool chat_fast_scan_in_flight_ = false; // guard against overlapping fast-scan RPCs
float chat_fast_scan_accum_ = 0.0f; // seconds since the last fast-scan (dedicated ~2.5s poll)
// Lite first-run welcome prompt: dismissed for the session once the user picks an action. // Lite first-run welcome prompt: dismissed for the session once the user picks an action.
bool lite_firstrun_dismissed_ = false; bool lite_firstrun_dismissed_ = false;
// Lite send-time unlock: set to show the unlock modal when a spend is attempted while locked. // Lite send-time unlock: set to show the unlock modal when a spend is attempted while locked.

View File

@@ -48,6 +48,9 @@ bool s_show_new_convo = false;
char s_new_zaddr[128] = ""; char s_new_zaddr[128] = "";
char s_new_msg[256] = ""; char s_new_msg[256] = "";
char s_search[80] = ""; // conversation-list filter (Q8) char s_search[80] = ""; // conversation-list filter (Q8)
bool s_show_hidden = false; // when on, the list also shows hidden conversations (with an Unhide action)
bool s_show_emoji_picker = false; // emoji picker overlay — fills the conversation-list pane while open
char s_emoji_search[48] = ""; // emoji picker keyword filter
// Effective draw-list font size for a material font (mirrors sidebar's ScaledFontSize). // Effective draw-list font size for a material font (mirrors sidebar's ScaledFontSize).
float scaledSize(ImFont* f) { return f->LegacySize * ImGui::GetStyle().FontScaleMain; } float scaledSize(ImFont* f) { return f->LegacySize * ImGui::GetStyle().FontScaleMain; }
@@ -136,6 +139,7 @@ struct ConvSummary {
std::string lastBody; std::string lastBody;
std::int64_t lastTs = 0; std::int64_t lastTs = 0;
int count = 0; int count = 0;
bool hidden = false; // shown only while "Show hidden" is on
}; };
// Centered, muted, wrapped hint for the empty states. // Centered, muted, wrapped hint for the empty states.
@@ -199,6 +203,77 @@ void centeredEmptyState(const char* icon, const char* title, const char* hint) {
} }
} }
// Common single-codepoint emoji (all verified present in the bundled NotoEmoji subset), each with
// keyword text for the picker's search box. Single-codepoint only — ImGui does no shaping, so ZWJ
// sequences / flags wouldn't compose.
struct EmojiEntry { const char* glyph; const char* keywords; };
static const EmojiEntry kEmoji[] = {
{u8"😀","grin happy smile"},{u8"😃","smile happy joy"},{u8"😄","laugh happy smile"},{u8"😁","grin beam happy"},
{u8"😆","laugh haha"},{u8"😅","sweat laugh nervous"},{u8"😂","joy laugh tears cry funny"},{u8"🤣","rofl laugh rolling funny"},
{u8"😊","blush smile happy"},{u8"😇","angel innocent halo"},{u8"🙂","slight smile"},{u8"🙃","upside down silly"},
{u8"😉","wink"},{u8"😌","relieved calm"},{u8"😍","heart eyes love"},{u8"🥰","love hearts adore"},{u8"😘","kiss blow"},
{u8"😗","kiss"},{u8"😙","kiss smile"},{u8"😚","kiss closed"},{u8"😋","yum tasty tongue"},{u8"😛","tongue playful"},
{u8"😝","tongue squint"},{u8"😜","wink tongue crazy"},{u8"🤪","zany crazy silly"},{u8"🤨","raised eyebrow suspicious"},
{u8"🧐","monocle thinking"},{u8"🤓","nerd geek glasses"},{u8"😎","cool sunglasses"},{u8"🥳","party celebrate hat"},
{u8"😏","smirk"},{u8"😒","unamused meh"},{u8"😞","disappointed sad"},{u8"😔","pensive sad"},{u8"😟","worried"},
{u8"😕","confused"},{u8"🙁","frown sad"},{u8"😣","persevere"},{u8"😖","confounded"},{u8"😫","tired"},{u8"😩","weary"},
{u8"🥺","pleading puppy beg"},{u8"😢","cry sad tear"},{u8"😭","sob cry bawl"},{u8"😤","huff triumph steam"},
{u8"😠","angry mad"},{u8"😡","rage mad angry"},{u8"🤬","curse swear angry"},{u8"🤯","mind blown shock"},
{u8"😳","flushed embarrassed"},{u8"🥵","hot heat sweat"},{u8"🥶","cold freezing"},{u8"😱","scream fear shock"},
{u8"😨","fearful scared"},{u8"😰","anxious sweat"},{u8"😥","sad sweat"},{u8"😓","sweat"},{u8"🤗","hug"},
{u8"🤔","think hmm"},{u8"🤭","giggle oops hand"},{u8"🤫","shush quiet"},{u8"🤥","lie"},{u8"😶","no mouth blank"},
{u8"😐","neutral meh"},{u8"😑","expressionless"},{u8"😬","grimace awkward"},{u8"🙄","eye roll"},{u8"😯","hushed surprise"},
{u8"😦","frown open"},{u8"😧","anguished"},{u8"😮","wow surprise oh"},{u8"😲","astonished shock"},{u8"🥱","yawn tired bored"},
{u8"😴","sleep zzz"},{u8"🤤","drool"},{u8"😪","sleepy"},{u8"🤐","zipper quiet secret"},{u8"🥴","woozy dizzy drunk"},
{u8"🤢","nausea sick"},{u8"🤮","vomit sick"},{u8"🤧","sneeze sick"},{u8"😷","mask sick"},{u8"🤒","sick thermometer"},
{u8"🤕","hurt bandage injured"},{u8"😈","devil imp evil"},{u8"👿","devil angry"},{u8"💀","skull dead"},{u8"👻","ghost boo"},
{u8"👽","alien"},{u8"🤖","robot bot"},{u8"😺","cat smile"},{u8"🙀","cat scream"},
{u8"👍","thumbs up like yes good"},{u8"👎","thumbs down dislike no bad"},{u8"👌","ok perfect"},{u8"","peace victory"},
{u8"🤞","fingers crossed luck"},{u8"🤟","love you"},{u8"🤘","rock horns"},{u8"👏","clap applause"},
{u8"🙌","raise hands celebrate"},{u8"👐","open hands"},{u8"🙏","pray thanks please"},{u8"💪","muscle strong flex"},
{u8"👋","wave hi hello bye"},{u8"🤙","call shaka"},{u8"👊","fist punch bump"},{u8"","fist raised"},{u8"🤛","fist left"},
{u8"🤜","fist right"},{u8"👆","up point"},{u8"👇","down point"},{u8"👈","left point"},{u8"👉","right point"},
{u8"","heart red love"},{u8"🧡","heart orange"},{u8"💛","heart yellow"},{u8"💚","heart green"},{u8"💙","heart blue"},
{u8"💜","heart purple"},{u8"🖤","heart black"},{u8"🤍","heart white"},{u8"💔","broken heart"},{u8"💕","hearts love"},
{u8"💞","revolving hearts"},{u8"💗","growing heart"},{u8"💖","sparkling heart"},{u8"💘","heart arrow cupid"},
{u8"💝","heart gift ribbon"},{u8"🔥","fire lit hot flame"},{u8"","star"},{u8"🎉","party tada celebrate confetti"},
{u8"🎊","confetti party"},{u8"","sparkles shiny"},{u8"💯","hundred perfect 100"},{u8"","check yes done tick"},
{u8"","cross no wrong x"},{u8"","question mark"},{u8"","exclamation mark"},{u8"👀","eyes look"},{u8"🎂","cake birthday"},
{u8"🚀","rocket launch"},{u8"💰","money bag cash"},{u8"🎁","gift present"},{u8"🍺","beer"},{u8"🍻","beers cheers"},
{u8"","coffee tea"},{u8"🍕","pizza"},{u8"👑","crown king queen"},{u8"💎","diamond gem"},{u8"🌟","glowing star"},
{u8"💥","boom explosion"},
};
// Emoji picker overlay: fills the conversation-list pane (cancel + keyword search at the top, then a
// grid). Clicking an emoji appends its UTF-8 bytes to `buf` (the composer), respecting the buffer.
void renderEmojiPickerOverlay(char* buf, std::size_t bufSize) {
if (ImGui::SmallButton(TR("chat_cancel"))) { s_show_emoji_picker = false; s_emoji_search[0] = '\0'; return; }
ImGui::SameLine();
ImGui::SetNextItemWidth(-FLT_MIN);
ImGui::InputTextWithHint("##emojisearch", TR("chat_emoji_search"), s_emoji_search, sizeof(s_emoji_search));
ImGui::Separator();
const std::string q = s_emoji_search;
ImGui::BeginChild("##emojigrid", ImVec2(0, 0), false);
ImGui::PushFont(material::Type().subtitle1()); // larger + emoji-capable
const float cell = 32.0f * Layout::dpiScale();
const float step = cell + ImGui::GetStyle().ItemSpacing.x;
const int perRow = std::max(1, static_cast<int>(ImGui::GetContentRegionAvail().x / step));
int shown = 0;
for (const auto& e : kEmoji) {
if (!q.empty() && !containsCI(e.keywords, q)) continue;
ImGui::PushID(shown);
if (ImGui::Button(e.glyph, ImVec2(cell, cell))) {
const std::size_t cur = std::strlen(buf), add = std::strlen(e.glyph);
if (cur + add < bufSize) { std::memcpy(buf + cur, e.glyph, add); buf[cur + add] = '\0'; }
}
ImGui::PopID();
if (++shown % perRow != 0) ImGui::SameLine();
}
ImGui::PopFont();
ImGui::EndChild();
}
} // namespace } // namespace
void RenderChatTab(App* app) void RenderChatTab(App* app)
@@ -215,12 +290,16 @@ void RenderChatTab(App* app)
// Build conversation summaries (single scan per conversation), sorted by most-recent activity. // Build conversation summaries (single scan per conversation), sorted by most-recent activity.
std::vector<ConvSummary> convs; std::vector<ConvSummary> convs;
int hiddenCount = 0;
for (const auto& cid : store.conversationIds()) { for (const auto& cid : store.conversationIds()) {
if (app->settings() && app->settings()->isChatHidden(cid)) continue; // hidden — a new message un-hides it const bool hidden = app->settings() && app->settings()->isChatHidden(cid);
if (hidden) ++hiddenCount;
if (hidden && !s_show_hidden) continue; // filtered out unless "Show hidden" is on
const auto messages = store.conversation(cid); const auto messages = store.conversation(cid);
if (messages.empty()) continue; if (messages.empty()) continue;
ConvSummary c; ConvSummary c;
c.cid = cid; c.cid = cid;
c.hidden = hidden;
c.count = static_cast<int>(messages.size()); c.count = static_cast<int>(messages.size());
for (const auto& m : messages) { // pin to the EARLIEST (establishing) peer z-addr / key (B2 — the for (const auto& m : messages) { // pin to the EARLIEST (establishing) peer z-addr / key (B2 — the
if (c.peerZaddr.empty() && !m.peer_zaddr.empty()) c.peerZaddr = m.peer_zaddr; // memo header rides if (c.peerZaddr.empty() && !m.peer_zaddr.empty()) c.peerZaddr = m.peer_zaddr; // memo header rides
@@ -268,7 +347,10 @@ void RenderChatTab(App* app)
// Faint sidebar tint so the list reads as distinct from the thread pane (V6). // Faint sidebar tint so the list reads as distinct from the thread pane (V6).
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::ColorConvertU32ToFloat4(material::WithAlpha(material::OnSurface(), 10))); ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::ColorConvertU32ToFloat4(material::WithAlpha(material::OnSurface(), 10)));
ImGui::BeginChild("##ChatList", ImVec2(listW, avail.y), true); ImGui::BeginChild("##ChatList", ImVec2(listW, avail.y), true);
{ if (s_show_emoji_picker) {
// Emoji picker overlay takes over the conversation-list pane while open (search + cancel + grid).
renderEmojiPickerOverlay(s_compose, sizeof(s_compose));
} else {
// Accented "New conversation" action (house tactile style). // Accented "New conversation" action (house tactile style).
ImGui::PushStyleColor(ImGuiCol_Button, ImGui::ColorConvertU32ToFloat4(material::WithAlpha(material::Primary(), 205))); ImGui::PushStyleColor(ImGuiCol_Button, ImGui::ColorConvertU32ToFloat4(material::WithAlpha(material::Primary(), 205)));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(material::Primary())); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(material::Primary()));
@@ -285,6 +367,18 @@ void RenderChatTab(App* app)
ImGui::SetNextItemWidth(-FLT_MIN); ImGui::SetNextItemWidth(-FLT_MIN);
ImGui::InputTextWithHint("##chatsearch", TR("chat_search"), s_search, sizeof(s_search)); ImGui::InputTextWithHint("##chatsearch", TR("chat_search"), s_search, sizeof(s_search));
} }
// Show-hidden toggle — only when there are hidden conversations. Reveals them in the list (with
// an Unhide action in the thread header).
if (hiddenCount > 0) {
ImGui::PushFont(metaFont);
const std::string lbl = s_show_hidden
? std::string(TR("chat_hide_hidden"))
: std::string(TR("chat_show_hidden")) + " (" + std::to_string(hiddenCount) + ")";
if (ImGui::SmallButton(lbl.c_str())) s_show_hidden = !s_show_hidden;
ImGui::PopFont();
} else {
s_show_hidden = false; // nothing hidden → keep the toggle off
}
const std::string search = s_search; const std::string search = s_search;
ImGui::Separator(); ImGui::Separator();
if (convs.empty()) { if (convs.empty()) {
@@ -335,8 +429,9 @@ void RenderChatTab(App* app)
} }
const float textX = avC.x + avR + pad; const float textX = avC.x + avR + pad;
// Name (top). // Name (top). Hidden conversations (shown via "Show hidden") render dimmed.
dl->AddText(nameFont, nameSz, ImVec2(textX, p.y + pad), material::OnSurface(), c.peerName.c_str()); dl->AddText(nameFont, nameSz, ImVec2(textX, p.y + pad),
c.hidden ? material::OnSurfaceMedium() : material::OnSurface(), c.peerName.c_str());
// Time (top-right, muted) — compact relative form (Q5). // Time (top-right, muted) — compact relative form (Q5).
const std::string when = relativeTime(c.lastTs); const std::string when = relativeTime(c.lastTs);
if (!when.empty()) { if (!when.empty()) {
@@ -440,15 +535,20 @@ void RenderChatTab(App* app)
app->settings()->setChatMuted(sel->cid, !muted); app->settings()->setChatMuted(sel->cid, !muted);
app->settings()->save(); app->settings()->save();
} }
// Hide conversation — drops it from the list (messages stay in the encrypted store; a // Hide / Unhide. Hiding drops it from the list (messages stay in the encrypted store);
// new incoming message brings it back). Deselect so the thread resets to the next one. // Unhide (shown only for a hidden conversation while "Show hidden" is on) restores it.
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::SmallButton(TR("chat_hide")) && app->settings()) { if (ImGui::SmallButton(sel->hidden ? TR("chat_unhide") : TR("chat_hide")) && app->settings()) {
app->markChatConversationSeen(sel->cid, sel->lastTs); // don't leave a phantom unread if (sel->hidden) {
app->settings()->setChatHidden(sel->cid, true); app->settings()->setChatHidden(sel->cid, false);
app->settings()->save(); app->settings()->save();
s_selected_cid.clear(); } else {
Notifications::instance().info(TR("chat_hidden_toast")); 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(); ImGui::Separator();
@@ -596,6 +696,13 @@ void RenderChatTab(App* app)
if (material::TactileButton(TR("chat_send"), ImVec2(sendW, composerBoxH))) submit = true; if (material::TactileButton(TR("chat_send"), ImVec2(sendW, composerBoxH))) submit = true;
ImGui::EndDisabled(); ImGui::EndDisabled();
ImGui::PopStyleColor(3); ImGui::PopStyleColor(3);
// Emoji picker button (left) + byte counter (right) on one row. The picker opens as an
// overlay over the conversation-list pane (rendered there), not a floating window.
ImGui::PushFont(nameFont); // Body2 has emoji merged, so the glyph renders
const bool emojiClicked = ImGui::SmallButton(u8"🙂");
ImGui::PopFont();
if (emojiClicked) { s_show_emoji_picker = !s_show_emoji_picker; s_emoji_search[0] = '\0'; }
ImGui::SameLine();
// Byte counter (Error tint over cap) with the over-cap label. // Byte counter (Error tint over cap) with the over-cap label.
ImGui::PushFont(metaFont); ImGui::PushFont(metaFont);
ImGui::PushStyleColor(ImGuiCol_Text, overCap ? material::Error() : material::OnSurfaceMedium()); ImGui::PushStyleColor(ImGuiCol_Text, overCap ? material::Error() : material::OnSurfaceMedium());

View File

@@ -253,6 +253,10 @@ void I18n::loadBuiltinEnglish()
strings_["chat_mute"] = "Mute"; strings_["chat_mute"] = "Mute";
strings_["chat_unmute"] = "Unmute"; strings_["chat_unmute"] = "Unmute";
strings_["chat_hide"] = "Hide"; strings_["chat_hide"] = "Hide";
strings_["chat_unhide"] = "Unhide";
strings_["chat_show_hidden"] = "Show hidden";
strings_["chat_emoji_search"] = "Search emoji";
strings_["chat_hide_hidden"] = "Hide hidden";
strings_["chat_hidden_toast"] = "Conversation hidden — a new message brings it back"; strings_["chat_hidden_toast"] = "Conversation hidden — a new message brings it back";
strings_["chat_pick_contact"] = "Choose from contacts\xE2\x80\xA6"; strings_["chat_pick_contact"] = "Choose from contacts\xE2\x80\xA6";
strings_["chat_no_z_contacts"] = "No shielded-address contacts yet"; strings_["chat_no_z_contacts"] = "No shielded-address contacts yet";