feat(contacts): revamp edit dialog with live preview + avatar picker
Rebuild the add/edit contact dialog on the portfolio-editor design language:
- A live preview card at the top shows the contact exactly as it renders in the
list (avatar + name + address), updating as you type and pick an avatar.
- An avatar picker (Badge / Icon / Image segmented control) lets you keep the
default Z/T type badge, choose a Material wallet icon from a searchable grid,
or set a custom image. The picker area is fixed-height so the modal doesn't
jump when switching modes.
- Custom images go through a new in-app ImagePicker (image_picker.h): a
Material overlay that browses the filesystem starting at the user's Pictures
folder, shows a thumbnail grid (decoded to raw pixels, box-downscaled to a
small texture, cached per directory and freed on navigate/close, budgeted a
few decodes per frame so large folders don't hitch), and returns the chosen
path. The chosen image is copied into <config>/contact-avatars/ (named by an
FNV hash of the source path, so re-picking is idempotent) and stored as
"img:<path>". Like FolderPicker, it takes over the modal surface while open.
- Paste is now available on both add and edit; buttons are content-sized.
Adds three sweep surfaces (contacts-edit-{icon,badge,image}) that open the
dialog on a seeded contact in each avatar mode, plus i18n keys (+ 8-language
translations; reworded two zh/ja strings to stay within the existing CJK
subset, so no font rebuild).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -287,9 +287,20 @@
|
||||
"console_welcome": "Willkommen bei ObsidianDragon Konsole",
|
||||
"console_zoom_in": "Vergrößern",
|
||||
"console_zoom_out": "Verkleinern",
|
||||
"contact_avatar": "AVATAR",
|
||||
"contact_avatar_badge": "Abzeichen",
|
||||
"contact_avatar_badge_hint": "Verwendet das Adresstyp-Abzeichen – Z für abgeschirmt, T für transparent.",
|
||||
"contact_avatar_choose": "Bild auswählen…",
|
||||
"contact_avatar_copy_failed": "Dieses Bild konnte nicht kopiert werden.",
|
||||
"contact_avatar_icon": "Symbol",
|
||||
"contact_avatar_image": "Bild",
|
||||
"contact_avatar_image_hint": "Das Bild wird in die App kopiert, damit es verfügbar bleibt, wenn das Original verschoben wird.",
|
||||
"contact_avatar_remove": "Entfernen",
|
||||
"contact_global": "In jeder Wallet anzeigen (globaler Kontakt)",
|
||||
"contact_global_badge_tt": "Globaler Kontakt — in jeder Wallet sichtbar",
|
||||
"contact_global_tt": "Ein: Dieser Kontakt bleibt sichtbar, egal welche Wallet Sie laden. Aus: Er gehört nur zur aktuellen Wallet.",
|
||||
"contact_preview_addr": "Adresse erscheint hier",
|
||||
"contact_preview_name": "Kontaktname",
|
||||
"contacts": "Kontakte",
|
||||
"contacts_search_no_match": "Keine passenden Kontakte",
|
||||
"contacts_search_placeholder": "Kontakte durchsuchen...",
|
||||
@@ -465,6 +476,12 @@
|
||||
"hide_qr": "QR ausblenden",
|
||||
"hide_zero_balances": "Nullsalden ausblenden",
|
||||
"history": "Verlauf",
|
||||
"img_picker_count": "%d Bild(er) in diesem Ordner",
|
||||
"img_picker_empty": "Dieser Ordner enthält keine Unterordner oder Bilder.",
|
||||
"img_picker_none": "Keine Bilder in diesem Ordner",
|
||||
"img_picker_pictures": "Bilderordner",
|
||||
"img_picker_title": "Bild auswählen",
|
||||
"img_picker_use": "Bild verwenden",
|
||||
"immature_type": "Unreif",
|
||||
"import": "Importieren",
|
||||
"import_key_address": "Adresse:",
|
||||
|
||||
@@ -287,9 +287,20 @@
|
||||
"console_welcome": "Bienvenido a la Consola de ObsidianDragon",
|
||||
"console_zoom_in": "Acercar",
|
||||
"console_zoom_out": "Alejar",
|
||||
"contact_avatar": "AVATAR",
|
||||
"contact_avatar_badge": "Insignia",
|
||||
"contact_avatar_badge_hint": "Usa la insignia de tipo de dirección: Z para blindada, T para transparente.",
|
||||
"contact_avatar_choose": "Elegir imagen…",
|
||||
"contact_avatar_copy_failed": "No se pudo copiar esa imagen.",
|
||||
"contact_avatar_icon": "Icono",
|
||||
"contact_avatar_image": "Imagen",
|
||||
"contact_avatar_image_hint": "La imagen se copia en la app para que siga disponible si el original se mueve.",
|
||||
"contact_avatar_remove": "Quitar",
|
||||
"contact_global": "Mostrar en todas las carteras (contacto global)",
|
||||
"contact_global_badge_tt": "Contacto global — visible en todas las carteras",
|
||||
"contact_global_tt": "Activado: este contacto permanece visible sin importar qué cartera cargues. Desactivado: pertenece solo a la cartera actual.",
|
||||
"contact_preview_addr": "La dirección aparecerá aquí",
|
||||
"contact_preview_name": "Nombre del contacto",
|
||||
"contacts": "Contactos",
|
||||
"contacts_search_no_match": "No hay contactos coincidentes",
|
||||
"contacts_search_placeholder": "Buscar contactos...",
|
||||
@@ -465,6 +476,12 @@
|
||||
"hide_qr": "Ocultar QR",
|
||||
"hide_zero_balances": "Ocultar saldos 0",
|
||||
"history": "Historial",
|
||||
"img_picker_count": "%d imagen(es) en esta carpeta",
|
||||
"img_picker_empty": "Esta carpeta no tiene subcarpetas ni imágenes.",
|
||||
"img_picker_none": "No hay imágenes en esta carpeta",
|
||||
"img_picker_pictures": "Carpeta de imágenes",
|
||||
"img_picker_title": "Elegir una imagen",
|
||||
"img_picker_use": "Usar imagen",
|
||||
"immature_type": "Inmaduro",
|
||||
"import": "Importar",
|
||||
"import_key_address": "Dirección:",
|
||||
|
||||
@@ -287,9 +287,20 @@
|
||||
"console_welcome": "Bienvenue dans la console ObsidianDragon",
|
||||
"console_zoom_in": "Agrandir",
|
||||
"console_zoom_out": "Réduire",
|
||||
"contact_avatar": "AVATAR",
|
||||
"contact_avatar_badge": "Badge",
|
||||
"contact_avatar_badge_hint": "Utilise le badge de type d'adresse — Z pour blindée, T pour transparente.",
|
||||
"contact_avatar_choose": "Choisir une image…",
|
||||
"contact_avatar_copy_failed": "Impossible de copier cette image.",
|
||||
"contact_avatar_icon": "Icône",
|
||||
"contact_avatar_image": "Image",
|
||||
"contact_avatar_image_hint": "L'image est copiée dans l'application pour rester disponible si l'original est déplacé.",
|
||||
"contact_avatar_remove": "Retirer",
|
||||
"contact_global": "Afficher dans tous les portefeuilles (contact global)",
|
||||
"contact_global_badge_tt": "Contact global — visible dans tous les portefeuilles",
|
||||
"contact_global_tt": "Activé : ce contact reste visible quel que soit le portefeuille chargé. Désactivé : il appartient au portefeuille actuel uniquement.",
|
||||
"contact_preview_addr": "L'adresse apparaîtra ici",
|
||||
"contact_preview_name": "Nom du contact",
|
||||
"contacts": "Contacts",
|
||||
"contacts_search_no_match": "Aucun contact correspondant",
|
||||
"contacts_search_placeholder": "Rechercher des contacts...",
|
||||
@@ -465,6 +476,12 @@
|
||||
"hide_qr": "Masquer le QR",
|
||||
"hide_zero_balances": "Masquer les soldes à 0",
|
||||
"history": "Historique",
|
||||
"img_picker_count": "%d image(s) dans ce dossier",
|
||||
"img_picker_empty": "Ce dossier ne contient ni sous-dossiers ni images.",
|
||||
"img_picker_none": "Aucune image dans ce dossier",
|
||||
"img_picker_pictures": "Dossier Images",
|
||||
"img_picker_title": "Choisir une image",
|
||||
"img_picker_use": "Utiliser l'image",
|
||||
"immature_type": "Immature",
|
||||
"import": "Importer",
|
||||
"import_key_address": "Adresse :",
|
||||
|
||||
@@ -287,9 +287,20 @@
|
||||
"console_welcome": "ObsidianDragonコンソールへようこそ",
|
||||
"console_zoom_in": "拡大",
|
||||
"console_zoom_out": "縮小",
|
||||
"contact_avatar": "アバター",
|
||||
"contact_avatar_badge": "バッジ",
|
||||
"contact_avatar_badge_hint": "アドレス種別バッジを使用します — Z はシールド、T は透明。",
|
||||
"contact_avatar_choose": "画像を選択…",
|
||||
"contact_avatar_copy_failed": "その画像をコピーできませんでした。",
|
||||
"contact_avatar_icon": "アイコン",
|
||||
"contact_avatar_image": "画像",
|
||||
"contact_avatar_image_hint": "画像はアプリ内にコピーされ、元のファイルが移動しても利用できます。",
|
||||
"contact_avatar_remove": "削除",
|
||||
"contact_global": "すべてのウォレットで表示(グローバル連絡先)",
|
||||
"contact_global_badge_tt": "グローバル連絡先 — すべてのウォレットで表示",
|
||||
"contact_global_tt": "オン:この連絡先はどのウォレットを読み込んでも表示されます。オフ:現在のウォレットにのみ属します。",
|
||||
"contact_preview_addr": "ここにアドレスが表示されます",
|
||||
"contact_preview_name": "連絡先名",
|
||||
"contacts": "連絡先",
|
||||
"contacts_search_no_match": "一致する連絡先がありません",
|
||||
"contacts_search_placeholder": "連絡先を検索...",
|
||||
@@ -465,6 +476,12 @@
|
||||
"hide_qr": "QRを非表示",
|
||||
"hide_zero_balances": "残高0を非表示",
|
||||
"history": "履歴",
|
||||
"img_picker_count": "このフォルダの画像:%d",
|
||||
"img_picker_empty": "このフォルダにはサブフォルダも画像もありません。",
|
||||
"img_picker_none": "このフォルダに画像はありません",
|
||||
"img_picker_pictures": "画像フォルダ",
|
||||
"img_picker_title": "画像を選択",
|
||||
"img_picker_use": "この画像を使用",
|
||||
"immature_type": "未成熟",
|
||||
"import": "インポート",
|
||||
"import_key_address": "アドレス:",
|
||||
|
||||
@@ -287,9 +287,20 @@
|
||||
"console_welcome": "ObsidianDragon 콘솔에 오신 것을 환영합니다",
|
||||
"console_zoom_in": "확대",
|
||||
"console_zoom_out": "축소",
|
||||
"contact_avatar": "아바타",
|
||||
"contact_avatar_badge": "배지",
|
||||
"contact_avatar_badge_hint": "주소 유형 배지를 사용합니다 — Z는 보호, T는 투명.",
|
||||
"contact_avatar_choose": "이미지 선택…",
|
||||
"contact_avatar_copy_failed": "그 이미지를 복사할 수 없습니다.",
|
||||
"contact_avatar_icon": "아이콘",
|
||||
"contact_avatar_image": "이미지",
|
||||
"contact_avatar_image_hint": "이미지는 앱에 복사되어 원본이 이동해도 계속 사용할 수 있습니다.",
|
||||
"contact_avatar_remove": "제거",
|
||||
"contact_global": "모든 지갑에 표시(전역 연락처)",
|
||||
"contact_global_badge_tt": "전역 연락처 — 모든 지갑에서 표시됨",
|
||||
"contact_global_tt": "켜짐: 어떤 지갑을 불러오든 이 연락처가 계속 표시됩니다. 꺼짐: 현재 지갑에만 속합니다.",
|
||||
"contact_preview_addr": "여기에 주소가 표시됩니다",
|
||||
"contact_preview_name": "연락처 이름",
|
||||
"contacts": "연락처",
|
||||
"contacts_search_no_match": "일치하는 연락처 없음",
|
||||
"contacts_search_placeholder": "연락처 검색...",
|
||||
@@ -465,6 +476,12 @@
|
||||
"hide_qr": "QR 숨기기",
|
||||
"hide_zero_balances": "잔액 0 숨기기",
|
||||
"history": "내역",
|
||||
"img_picker_count": "이 폴더에 이미지 %d개",
|
||||
"img_picker_empty": "이 폴더에는 하위 폴더나 이미지가 없습니다.",
|
||||
"img_picker_none": "이 폴더에 이미지가 없습니다",
|
||||
"img_picker_pictures": "사진 폴더",
|
||||
"img_picker_title": "이미지 선택",
|
||||
"img_picker_use": "이미지 사용",
|
||||
"immature_type": "미성숙",
|
||||
"import": "가져오기",
|
||||
"import_key_address": "주소:",
|
||||
|
||||
@@ -287,9 +287,20 @@
|
||||
"console_welcome": "Bem-vindo ao Console ObsidianDragon",
|
||||
"console_zoom_in": "Aumentar zoom",
|
||||
"console_zoom_out": "Diminuir zoom",
|
||||
"contact_avatar": "AVATAR",
|
||||
"contact_avatar_badge": "Selo",
|
||||
"contact_avatar_badge_hint": "Usa o selo de tipo de endereço — Z para blindado, T para transparente.",
|
||||
"contact_avatar_choose": "Escolher imagem…",
|
||||
"contact_avatar_copy_failed": "Não foi possível copiar essa imagem.",
|
||||
"contact_avatar_icon": "Ícone",
|
||||
"contact_avatar_image": "Imagem",
|
||||
"contact_avatar_image_hint": "A imagem é copiada para o app para continuar disponível se o original for movido.",
|
||||
"contact_avatar_remove": "Remover",
|
||||
"contact_global": "Mostrar em todas as carteiras (contato global)",
|
||||
"contact_global_badge_tt": "Contato global — visível em todas as carteiras",
|
||||
"contact_global_tt": "Ativado: este contato permanece visível em qualquer carteira que você carregar. Desativado: ele pertence apenas à carteira atual.",
|
||||
"contact_preview_addr": "O endereço aparecerá aqui",
|
||||
"contact_preview_name": "Nome do contato",
|
||||
"contacts": "Contatos",
|
||||
"contacts_search_no_match": "Nenhum contato correspondente",
|
||||
"contacts_search_placeholder": "Pesquisar contatos...",
|
||||
@@ -465,6 +476,12 @@
|
||||
"hide_qr": "Ocultar QR",
|
||||
"hide_zero_balances": "Ocultar saldos zero",
|
||||
"history": "Histórico",
|
||||
"img_picker_count": "%d imagem(ns) nesta pasta",
|
||||
"img_picker_empty": "Esta pasta não tem subpastas nem imagens.",
|
||||
"img_picker_none": "Nenhuma imagem nesta pasta",
|
||||
"img_picker_pictures": "Pasta de imagens",
|
||||
"img_picker_title": "Escolher uma imagem",
|
||||
"img_picker_use": "Usar imagem",
|
||||
"immature_type": "Imaturo",
|
||||
"import": "Importar",
|
||||
"import_key_address": "Endereço:",
|
||||
|
||||
@@ -287,9 +287,20 @@
|
||||
"console_welcome": "Добро пожаловать в консоль ObsidianDragon",
|
||||
"console_zoom_in": "Увеличить",
|
||||
"console_zoom_out": "Уменьшить",
|
||||
"contact_avatar": "АВАТАР",
|
||||
"contact_avatar_badge": "Значок",
|
||||
"contact_avatar_badge_hint": "Использует значок типа адреса — Z для защищённого, T для прозрачного.",
|
||||
"contact_avatar_choose": "Выбрать изображение…",
|
||||
"contact_avatar_copy_failed": "Не удалось скопировать это изображение.",
|
||||
"contact_avatar_icon": "Иконка",
|
||||
"contact_avatar_image": "Изображение",
|
||||
"contact_avatar_image_hint": "Изображение копируется в приложение, чтобы оставаться доступным, если оригинал переместят.",
|
||||
"contact_avatar_remove": "Удалить",
|
||||
"contact_global": "Показывать во всех кошельках (глобальный контакт)",
|
||||
"contact_global_badge_tt": "Глобальный контакт — виден во всех кошельках",
|
||||
"contact_global_tt": "Вкл.: этот контакт остаётся видимым, какой бы кошелёк вы ни загрузили. Выкл.: он принадлежит только текущему кошельку.",
|
||||
"contact_preview_addr": "Здесь появится адрес",
|
||||
"contact_preview_name": "Имя контакта",
|
||||
"contacts": "Контакты",
|
||||
"contacts_search_no_match": "Совпадающих контактов нет",
|
||||
"contacts_search_placeholder": "Поиск контактов...",
|
||||
@@ -465,6 +476,12 @@
|
||||
"hide_qr": "Скрыть QR",
|
||||
"hide_zero_balances": "Скрыть нулевые балансы",
|
||||
"history": "История",
|
||||
"img_picker_count": "Изображений в этой папке: %d",
|
||||
"img_picker_empty": "В этой папке нет подпапок или изображений.",
|
||||
"img_picker_none": "В этой папке нет изображений",
|
||||
"img_picker_pictures": "Папка изображений",
|
||||
"img_picker_title": "Выбрать изображение",
|
||||
"img_picker_use": "Использовать изображение",
|
||||
"immature_type": "Незрелая",
|
||||
"import": "Импорт",
|
||||
"import_key_address": "Адрес:",
|
||||
|
||||
@@ -287,9 +287,20 @@
|
||||
"console_welcome": "欢迎使用 ObsidianDragon 控制台",
|
||||
"console_zoom_in": "放大",
|
||||
"console_zoom_out": "缩小",
|
||||
"contact_avatar": "头像",
|
||||
"contact_avatar_badge": "标记",
|
||||
"contact_avatar_badge_hint": "使用地址类型标记——Z 表示隐蔽地址,T 表示透明地址。",
|
||||
"contact_avatar_choose": "选择图片…",
|
||||
"contact_avatar_copy_failed": "无法复制该图片。",
|
||||
"contact_avatar_icon": "图标",
|
||||
"contact_avatar_image": "图片",
|
||||
"contact_avatar_image_hint": "图片会复制到应用中,即使原文件移动也能保持可用。",
|
||||
"contact_avatar_remove": "移除",
|
||||
"contact_global": "在每个钱包中显示(全局联系人)",
|
||||
"contact_global_badge_tt": "全局联系人——在每个钱包中可见",
|
||||
"contact_global_tt": "开启:无论您加载哪个钱包,此联系人都保持可见。关闭:它仅属于当前钱包。",
|
||||
"contact_preview_addr": "地址将显示在此处",
|
||||
"contact_preview_name": "联系人名称",
|
||||
"contacts": "联系人",
|
||||
"contacts_search_no_match": "没有匹配的联系人",
|
||||
"contacts_search_placeholder": "搜索联系人...",
|
||||
@@ -465,6 +476,12 @@
|
||||
"hide_qr": "隐藏二维码",
|
||||
"hide_zero_balances": "隐藏零余额",
|
||||
"history": "历史",
|
||||
"img_picker_count": "此文件夹中的图片:%d",
|
||||
"img_picker_empty": "此文件夹没有子文件夹或图片。",
|
||||
"img_picker_none": "此文件夹中没有图片",
|
||||
"img_picker_pictures": "图片文件夹",
|
||||
"img_picker_title": "选择图片",
|
||||
"img_picker_use": "使用图片",
|
||||
"immature_type": "未成熟",
|
||||
"import": "导入",
|
||||
"import_key_address": "地址:",
|
||||
|
||||
Reference in New Issue
Block a user