From ee5116837475410c9e03e7cf7ac4eecd81a6c7d9 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 10 Jul 2026 20:53:14 +0100 Subject: [PATCH] core, ui: improve name resolution (#7209) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ios: debug name resolution * remove log * ui: translations (#7210) * Translated using Weblate (French) Currently translated at 84.1% (2390 of 2840 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fr/ * Translated using Weblate (Italian) Currently translated at 98.4% (2795 of 2840 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Spanish) Currently translated at 98.2% (2790 of 2840 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Dutch) Currently translated at 83.9% (2385 of 2840 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Hindi) Currently translated at 9.6% (275 of 2840 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hi/ * Translated using Weblate (Czech) Currently translated at 95.4% (2710 of 2840 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/ * Translated using Weblate (Ukrainian) Currently translated at 87.2% (2477 of 2840 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Finnish) Currently translated at 50.3% (1430 of 2840 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fi/ * Translated using Weblate (Korean) Currently translated at 51.6% (1466 of 2840 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ko/ * Translated using Weblate (Polish) Currently translated at 88.7% (2520 of 2840 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pl/ * Translated using Weblate (Portuguese) Currently translated at 33.0% (939 of 2840 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/ * Translated using Weblate (Hebrew) Currently translated at 72.6% (2062 of 2840 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/ * Translated using Weblate (Thai) Currently translated at 43.5% (1236 of 2840 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/th/ * Translated using Weblate (Thai) Currently translated at 43.5% (1236 of 2840 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/th/ * Translated using Weblate (Bulgarian) Currently translated at 87.2% (2479 of 2840 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/bg/ * Translated using Weblate (Turkish) Currently translated at 86.0% (2445 of 2840 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/tr/ * Translated using Weblate (German) Currently translated at 100.0% (2843 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (German) Currently translated at 100.0% (2439 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (Italian) Currently translated at 100.0% (2843 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 98.5% (2803 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2843 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2439 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Arabic) Currently translated at 100.0% (2843 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Vietnamese) Currently translated at 84.2% (2396 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Italian) Currently translated at 100.0% (2439 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 99.9% (2842 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Vietnamese) Currently translated at 85.5% (2433 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (German) Currently translated at 100.0% (2843 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (German) Currently translated at 100.0% (2439 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (Hindi) Currently translated at 9.8% (281 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hi/ * Translated using Weblate (Indonesian) Currently translated at 89.5% (2547 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 88.1% (2149 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hans/ * Translated using Weblate (Russian) Currently translated at 97.4% (2771 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/ * Translated using Weblate (French) Currently translated at 78.9% (1926 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 89.3% (2180 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hans/ * Translated using Weblate (Spanish) Currently translated at 98.1% (2789 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Japanese) Currently translated at 49.5% (1209 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ja/ * Translated using Weblate (Hindi) Currently translated at 10.2% (292 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hi/ * Translated using Weblate (Czech) Currently translated at 95.5% (2717 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/ * Translated using Weblate (Romanian) Currently translated at 88.3% (2511 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ro/ * Translated using Weblate (Vietnamese) Currently translated at 86.1% (2449 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Indonesian) Currently translated at 94.3% (2683 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Translated using Weblate (Danish) Currently translated at 30.7% (875 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/da/ * Translated using Weblate (Spanish) Currently translated at 98.8% (2411 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Ukrainian) Currently translated at 89.9% (2557 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Ukrainian) Currently translated at 87.8% (2143 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/uk/ * Translated using Weblate (Vietnamese) Currently translated at 87.4% (2485 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Vietnamese) Currently translated at 87.4% (2485 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Ukrainian) Currently translated at 90.0% (2559 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Ukrainian) Currently translated at 93.7% (2664 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Ukrainian) Currently translated at 93.7% (2664 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Ukrainian) Currently translated at 93.7% (2664 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Ukrainian) Currently translated at 88.0% (2148 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/uk/ * Translated using Weblate (Greek) Currently translated at 1.0% (25 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/el/ * Translated using Weblate (Spanish) Currently translated at 99.7% (2837 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Spanish) Currently translated at 99.7% (2432 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 89.4% (2182 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hans/ * Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 47.0% (1148 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hant/ * Translated using Weblate (Spanish) Currently translated at 99.7% (2433 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Turkish) Currently translated at 88.4% (2514 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/tr/ * Translated using Weblate (Indonesian) Currently translated at 94.4% (2686 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Translated using Weblate (Russian) Currently translated at 97.5% (2774 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/ * Translated using Weblate (Russian) Currently translated at 97.6% (2382 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ru/ * Translated using Weblate (French) Currently translated at 80.8% (1972 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/ * Translated using Weblate (Italian) Currently translated at 100.0% (2439 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (2439 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hans/ * Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 98.7% (2808 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hant/ * Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 100.0% (2439 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hant/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2843 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2843 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2439 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2439 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2439 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Indonesian) Currently translated at 94.7% (2694 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Update translation files Updated by "Remove blank strings" hook in Weblate. Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ * Translated using Weblate (German) Currently translated at 100.0% (2439 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (French) Currently translated at 85.4% (2084 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/ * Translated using Weblate (Italian) Currently translated at 100.0% (2439 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2843 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2439 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Japanese) Currently translated at 99.0% (2815 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/ * Translated using Weblate (Indonesian) Currently translated at 95.4% (2713 of 2843 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Translated using Weblate (German) Currently translated at 100.0% (2439 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (French) Currently translated at 87.5% (2136 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/ * Translated using Weblate (French) Currently translated at 87.5% (2136 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/ * Translated using Weblate (French) Currently translated at 88.0% (2147 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/ * Translated using Weblate (French) Currently translated at 89.2% (2178 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/ * Translated using Weblate (Italian) Currently translated at 100.0% (2439 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (German) Currently translated at 100.0% (2866 of 2866 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (German) Currently translated at 100.0% (2439 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (German) Currently translated at 100.0% (2439 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (French) Currently translated at 94.9% (2316 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/ * Translated using Weblate (Italian) Currently translated at 100.0% (2866 of 2866 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 99.9% (2865 of 2866 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Japanese) Currently translated at 49.9% (1219 of 2439 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ja/ * Translated using Weblate (Arabic) Currently translated at 100.0% (2866 of 2866 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2866 of 2866 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Russian) Currently translated at 97.9% (2807 of 2866 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/ * Translated using Weblate (Russian) Currently translated at 97.9% (2807 of 2866 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/ * Translated using Weblate (Russian) Currently translated at 97.9% (2807 of 2866 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/ * Translated using Weblate (Turkish) Currently translated at 98.7% (2829 of 2866 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/tr/ * process localizations * fix android translations * ui: fix translation errors and revert Turkish vandalism (PR #7206 review) Corrects issues found reviewing the incoming translations across 20 languages. CRITICAL: - Turkish: revert 8 vandalized strings to faithful translations — the "Open external link?" security dialog, app-upgrade prompt, relay-status labels, and the "Privacy: for owners and subscribers" line had been replaced with joke/offensive text. - Ukrainian: fix a files/media privacy toggle that showed an unrelated "chat with admins" string. HIGH/MEDIUM: badge voice/tense inversions (uk, vi, ro, de), meaning mistranslations (fr, es, id, vi, zh-Hans, ja, nl, ru), the Danish relay-bar "/" separator loss, re-added role-name quotes (de, es, it, ar, ru, zh-Hans, zh-Hant), restored dropped \n line breaks and sentences (id), and typos (hu, es, uk, ro, cs, bg). Placeholders, XML well-formedness, and .strings validity verified intact. Co-Authored-By: Claude Opus 4.8 (1M context) * ui: revert residual Turkish vandalism (Owner→Emperor) Second-pass review caught vandalized strings missed in the first fix round: - member_info_section_title_owner: "İmparator" (Emperor) → "Sahip" (Owner) - channel_members_section_owners: "İmparatorlar ve katkıda bulunanlar" (Emperors and contributors) → "Sahipler ve katkıda bulunanlar" - migrate: "Göç" (emigration) → "Taşı" (correct verb for the Migrate action) Same vandal (mehmetksm@tuta.io) as the strings reverted in 9b5486bfa. Co-Authored-By: Claude Opus 4.8 (1M context) * ui: translate romanized Hindi connect-flow strings Second-pass reconciliation caught 6 Hindi strings left as broken "Hinglish" (English words in Devanagari / untranslated) that were missed in the first fix round — now proper Hindi: - connect_via_contact_link, connect_via_invitation_link, connect_use_current_profile, connect_use_new_incognito_profile, connect_use_incognito_profile, profile_will_be_sent_to_contact_sending_link Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com> Co-authored-by: Deleted User Co-authored-by: No name Co-authored-by: John m Co-authored-by: Deleted User Co-authored-by: zenobit Co-authored-by: Maksym Lukashenko Co-authored-by: petri Co-authored-by: okmepro Co-authored-by: B.O.S.S Co-authored-by: Paulo Alexandre Pereira Co-authored-by: Roee Hershberg Co-authored-by: Anonymous Co-authored-by: Titapa (PunPun) Chaiyakiturajai Co-authored-by: elgratea Co-authored-by: xe1st Co-authored-by: mlanp Co-authored-by: Random Co-authored-by: Hosted Weblate user 54392 Co-authored-by: summoner001 Co-authored-by: jonnysemon Co-authored-by: Thu Pham-Anh Co-authored-by: SquiralDot...🖤 Co-authored-by: andiasriefail Co-authored-by: Frank Yang Co-authored-by: Artemchik Dornikov Co-authored-by: cedev-1 Co-authored-by: cllih Co-authored-by: Aliet Expósito García Co-authored-by: koreirozzu Co-authored-by: Bharadwaj Co-authored-by: slrslr Co-authored-by: Filip Ionut Co-authored-by: Nguyễn Xuân Cảm Co-authored-by: Dimas Alfa Pratama Co-authored-by: Mathias Pedersen Co-authored-by: kudebug Co-authored-by: rider of the storm Co-authored-by: Nguyen Thanh Long Co-authored-by: Fill Vj Co-authored-by: Stonepilot Co-authored-by: Bugatsinho Co-authored-by: Darío Hereñú Co-authored-by: Loping151 Co-authored-by: pahiy Co-authored-by: M Yusup Hamdani Co-authored-by: lazizkhalilov Co-authored-by: Игорь Линчевский Co-authored-by: J. Lavoie Co-authored-by: Irnovi Albaweny Co-authored-by: Hosted Weblate Co-authored-by: SHE LINGZHAO Co-authored-by: Rafi Co-authored-by: ryokky3 Co-authored-by: Auri Co-authored-by: kkpanfilov Co-authored-by: echoloji Co-authored-by: Narasimha-sc <166327228+Narasimha-sc@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) * website: translations (#7207) * Translated using Weblate (French) Currently translated at 75.3% (281 of 373 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/fr/ * Translated using Weblate (French) Currently translated at 75.3% (281 of 373 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/fr/ * Translated using Weblate (Czech) Currently translated at 100.0% (373 of 373 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/cs/ * Translated using Weblate (Indonesian) Currently translated at 94.1% (351 of 373 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/id/ * Translated using Weblate (Spanish) Currently translated at 100.0% (373 of 373 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/es/ * Translated using Weblate (French) Currently translated at 75.3% (281 of 373 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/fr/ * Translated using Weblate (French) Currently translated at 75.3% (281 of 373 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/fr/ * Translated using Weblate (Czech) Currently translated at 100.0% (373 of 373 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/cs/ * Translated using Weblate (Indonesian) Currently translated at 94.1% (351 of 373 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/id/ * Translated using Weblate (Spanish) Currently translated at 100.0% (373 of 373 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/es/ * Translated using Weblate (Japanese) Currently translated at 79.6% (297 of 373 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/ja/ * Translated using Weblate (German) Currently translated at 100.0% (373 of 373 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/de/ * Translated using Weblate (Russian) Currently translated at 87.9% (328 of 373 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/ru/ * website: fix mistranslated security/feature terms (PR #7207 review) - Japanese: "Repudiation" (deniability) was rendered as 否認防止 (non-repudiation, the opposite property) — corrected to 否認可能性; "2-factor key exchange" was 2ファクタ認証 (authentication) — corrected to 2ファクタ鍵交換. - French: message padding ("Briar pads messages to ... bytes") was translated as a size limit ("limite la taille") — corrected to padding ("complète les messages à une taille arrondie à ..."). - Simplified Chinese: the "Community Credits" feature name was rendered as 社区声望 (community reputation) — restored to "Community Credits". Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: N4TH4NOT Co-authored-by: cedev-1 Co-authored-by: slrslr Co-authored-by: huzaifah Co-authored-by: kudebug Co-authored-by: ryokky3 Co-authored-by: mlanp Co-authored-by: Auri Co-authored-by: Narasimha-sc <166327228+Narasimha-sc@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) * dont change font for unverified names * better verified names view * better layout for verified status * shift icon * move icon * set domain on business chat * change padding * fix, test * add params to log * add domain to the usual group * add domain to BusinessChatInfo * bot types * remove comment * remove comment Co-authored-by: Evgeny * fix query --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com> Co-authored-by: Deleted User Co-authored-by: No name Co-authored-by: John m Co-authored-by: Deleted User Co-authored-by: zenobit Co-authored-by: Maksym Lukashenko Co-authored-by: petri Co-authored-by: okmepro Co-authored-by: B.O.S.S Co-authored-by: Paulo Alexandre Pereira Co-authored-by: Roee Hershberg Co-authored-by: Anonymous Co-authored-by: Titapa (PunPun) Chaiyakiturajai Co-authored-by: elgratea Co-authored-by: xe1st Co-authored-by: mlanp Co-authored-by: Random Co-authored-by: Hosted Weblate user 54392 Co-authored-by: summoner001 Co-authored-by: jonnysemon Co-authored-by: Thu Pham-Anh Co-authored-by: SquiralDot...🖤 Co-authored-by: andiasriefail Co-authored-by: Frank Yang Co-authored-by: Artemchik Dornikov Co-authored-by: cedev-1 Co-authored-by: cllih Co-authored-by: Aliet Expósito García Co-authored-by: koreirozzu Co-authored-by: Bharadwaj Co-authored-by: slrslr Co-authored-by: Filip Ionut Co-authored-by: Nguyễn Xuân Cảm Co-authored-by: Dimas Alfa Pratama Co-authored-by: Mathias Pedersen Co-authored-by: kudebug Co-authored-by: rider of the storm Co-authored-by: Nguyen Thanh Long Co-authored-by: Fill Vj Co-authored-by: Stonepilot Co-authored-by: Bugatsinho Co-authored-by: Darío Hereñú Co-authored-by: Loping151 Co-authored-by: pahiy Co-authored-by: M Yusup Hamdani Co-authored-by: lazizkhalilov Co-authored-by: Игорь Линчевский Co-authored-by: J. Lavoie Co-authored-by: Irnovi Albaweny Co-authored-by: Hosted Weblate Co-authored-by: SHE LINGZHAO Co-authored-by: Rafi Co-authored-by: ryokky3 Co-authored-by: Auri Co-authored-by: kkpanfilov Co-authored-by: echoloji Co-authored-by: Narasimha-sc <166327228+Narasimha-sc@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) Co-authored-by: N4TH4NOT Co-authored-by: huzaifah --- apps/ios/Shared/Views/Chat/ChatInfoView.swift | 57 ++++++++++++----- .../Views/Chat/Group/GroupChatInfoView.swift | 11 ++++ .../Views/UserSettings/UserAddressView.swift | 2 +- apps/ios/SimpleXChat/ChatTypes.swift | 1 + .../chat/simplex/common/model/ChatModel.kt | 1 + .../chat/simplex/common/model/SimpleXAPI.kt | 2 +- .../simplex/common/views/chat/ChatInfoView.kt | 2 +- .../common/views/chat/SimplexNameView.kt | 51 ++++++++++----- .../views/chat/group/GroupChatInfoView.kt | 11 ++++ .../views/usersettings/UserAddressView.kt | 2 +- bots/api/TYPES.md | 1 + .../types/typescript/src/types.ts | 1 + .../src/simplex_chat/types/_types.py | 1 + src/Simplex/Chat/Library/Commands.hs | 10 +-- src/Simplex/Chat/Library/Internal.hs | 2 +- src/Simplex/Chat/Store/Groups.hs | 63 +++++++++++++------ src/Simplex/Chat/Store/Shared.hs | 8 +-- src/Simplex/Chat/Types.hs | 4 +- tests/ChatTests/Names.hs | 22 ++++++- 19 files changed, 183 insertions(+), 69 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift index 2f76241c00..2078e9048a 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift @@ -396,7 +396,7 @@ struct ChatInfoView: View { if let domain = contact.profile.contactDomain, contact.profile.contactDomainVerified != nil || domain.proof != nil { SimplexNameView( - simplexName: "@\(domain.shortName)", + simplexName: "@\(domain.domain)", verified: contact.profile.contactDomainVerified, verify: { do { @@ -1384,28 +1384,51 @@ struct SimplexNameView: View { @State private var showSpinner = false var body: some View { - HStack(spacing: 6) { - Text(simplexName) - .font(verified == true ? .subheadline : .system(.subheadline, design: .monospaced)) - .foregroundColor(verified == true ? theme.colors.primary : theme.colors.secondary) - indicator() - } - .padding(.bottom, 2) - .onAppear { if autoVerify && verified == nil { runVerify(manual: false) } } + content + .padding(.bottom, 2) + .onAppear { if autoVerify && verified == nil { runVerify(manual: false) } } } - @ViewBuilder private func indicator() -> some View { + private var nameText: Text { + Text(simplexName) + .font(.subheadline) + .foregroundColor(verified == true ? theme.colors.primary : theme.colors.secondary) + } + + // Size the inline check/cross to the name's cap height so it reads like a capital letter, not an oversized glyph. + private var iconFont: Font { .system(size: UIFont.preferredFont(forTextStyle: .subheadline).capHeight) } + + @ViewBuilder private var content: some View { if showSpinner { - ProgressView() + HStack(spacing: 6) { + nameText + ProgressView() + } } else if verified == true { - Image(systemName: "checkmark") + HStack(alignment: .firstTextBaseline, spacing: 4) { + nameText + Image(systemName: "checkmark").font(iconFont).foregroundColor(theme.colors.primary) + .alignmentGuide(.firstTextBaseline) { $0[.bottom] - $0.height * 0.15 } + } + .contentShape(Rectangle()) + .onTapGesture { + UIPasteboard.general.string = simplexName + UIImpactFeedbackGenerator(style: .rigid).impactOccurred() + } } else if verified == false { - Image(systemName: "xmark") - .foregroundColor(.red) - .onTapGesture { runVerify(manual: true) } + HStack(alignment: .firstTextBaseline, spacing: 4) { + nameText + Image(systemName: "xmark").font(iconFont).foregroundColor(.red) + .alignmentGuide(.firstTextBaseline) { $0[.bottom] - $0.height * 0.15 } + } + .contentShape(Rectangle()) + .onTapGesture { runVerify(manual: true) } } else { - Button { runVerify(manual: true) } label: { - Text("Verify name").font(.subheadline).foregroundColor(theme.colors.primary) + HStack(spacing: 6) { + nameText + Button { runVerify(manual: true) } label: { + Text("Verify name").font(.subheadline).foregroundColor(theme.colors.primary) + } } } } diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index 358bf6c7d3..e5370bcf32 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -353,6 +353,17 @@ struct GroupChatInfoView: View { } ) } + if let claim = groupInfo.businessChat?.businessDomain, + groupInfo.groupDomainVerified != nil || claim.proof != nil { + // A business presents as a contact, so the name retains its .simplex suffix. The tick comes from + // groupDomainVerified (set at connect); its domain proof is not received on the wire yet, so + // re-verification is not wired. + SimplexNameView( + simplexName: "@\(claim.domain)", + verified: groupInfo.groupDomainVerified, + verify: { nil } + ) + } if let webPage = groupInfo.groupProfile.publicGroup?.publicGroupAccess?.groupWebPage, let url = URL(string: webPage) { Link(destination: url) { diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift index 13caf135e9..8dbadc9fd3 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift @@ -193,7 +193,7 @@ struct UserAddressView: View { Section { NavigationLink { - let simplexName = if let d = chatModel.currentUser?.profile.contactDomain?.shortName { "@\(d)" } else { "" } + let simplexName = if let d = chatModel.currentUser?.profile.contactDomain?.domain { "@\(d)" } else { "" } SetSimplexDomainView( title: "Your SimpleX name", footer: "Let people connect to you via name registered with your SimpleX address.", diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index d85999a7e7..eeda535994 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -2857,6 +2857,7 @@ public struct BusinessChatInfo: Decodable, Hashable { public var chatType: BusinessChatType public var businessId: String public var customerId: String + public var businessDomain: SimplexDomainClaim? } public enum BusinessChatType: String, Codable, Hashable { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index fd093f5761..b959b02272 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -2488,6 +2488,7 @@ data class BusinessChatInfo ( val chatType: BusinessChatType, val businessId: String, val customerId: String, + val businessDomain: SimplexDomainClaim? = null, ) @Serializable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 80b7d828ba..5c9993eae5 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -6896,7 +6896,7 @@ sealed class CR { is Invitation -> withUser(user, "connLinkInvitation: ${json.encodeToString(connLinkInvitation)}\nconnection: $connection") is ConnectionIncognitoUpdated -> withUser(user, json.encodeToString(toConnection)) is ConnectionUserChanged -> withUser(user, "fromConnection: ${json.encodeToString(fromConnection)}\ntoConnection: ${json.encodeToString(toConnection)}\nnewUser: ${json.encodeToString(newUser)}" ) - is CRConnectionPlan -> withUser(user, "connLink: ${json.encodeToString(connLink)}\nconnectionPlan: ${json.encodeToString(connectionPlan)}") + is CRConnectionPlan -> withUser(user, "connLink: ${json.encodeToString(connLink)}\nplanSimplexName: $planSimplexName\notherSimplexName: $otherSimplexName\nconnectionPlan: ${json.encodeToString(connectionPlan)}") is NewPreparedChat -> withUser(user, json.encodeToString(chat)) is ContactUserChanged -> withUser(user, "fromContact: ${json.encodeToString(fromContact)}\nnewUserId: ${json.encodeToString(newUser.userId)}\ntoContact: ${json.encodeToString(toContact)}") is GroupUserChanged -> withUser(user, "fromGroup: ${json.encodeToString(fromGroup)}\nnewUserId: ${json.encodeToString(newUser.userId)}\ntoGroup: ${json.encodeToString(toGroup)}") diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt index e43cc357a0..bfcbcdefac 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt @@ -760,7 +760,7 @@ fun ChatInfoHeader(cInfo: ChatInfo, contact: Contact) { val domain = contact.profile.contactDomain if (domain != null && (contact.profile.contactDomainVerified != null || domain.proof != null)) { SimplexNameView( - simplexName = "@${domain.shortName}", + simplexName = "@${domain.domain}", verified = contact.profile.contactDomainVerified, verify = { val rhId = chatModel.remoteHostId() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SimplexNameView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SimplexNameView.kt index ccee6a9035..397778b8ee 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SimplexNameView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SimplexNameView.kt @@ -7,8 +7,10 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.text.* import androidx.compose.ui.unit.dp +import dev.icerock.moko.resources.ImageResource import chat.simplex.common.model.SimplexNameInfo import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.DEFAULT_PADDING_HALF @@ -61,34 +63,53 @@ fun SimplexNameView( if (chatModel.controller.appPrefs.privacyVerifySimplexNames.get() && verified == null) runVerify(manual = false) } + val clipboard = LocalClipboardManager.current + val nameStyle = MaterialTheme.typography.body2.copy( + color = if (verified == true) MaterialTheme.colors.primary else MaterialTheme.colors.secondary + ) Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp), modifier = Modifier.padding(top = DEFAULT_PADDING_HALF) ) { - Text( - simplexName, - style = MaterialTheme.typography.body2.copy( - color = if (verified == true) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, - fontFamily = if (verified == true) FontFamily.Default else FontFamily.Monospace - ) - ) when { - showSpinner.value -> + showSpinner.value -> { + Text(simplexName, style = nameStyle) CircularProgressIndicator(Modifier.size(16.dp), strokeWidth = 2.dp, color = MaterialTheme.colors.secondary) + } verified == true -> - Icon(painterResource(MR.images.ic_check_filled), null, Modifier.size(18.dp), tint = MaterialTheme.colors.onBackground) + SimplexNameWithIcon(simplexName, nameStyle, MR.images.ic_check_filled, MaterialTheme.colors.primary) { + clipboard.setText(AnnotatedString(simplexName)) + showToast(generalGetString(MR.strings.copied)) + } verified == false -> - Icon( - painterResource(MR.images.ic_close), null, tint = Color.Red, - modifier = Modifier.size(18.dp).clickable { runVerify(manual = true) } - ) - else -> + SimplexNameWithIcon(simplexName, nameStyle, MR.images.ic_close, Color.Red) { runVerify(manual = true) } + else -> { + Text(simplexName, style = nameStyle) Text( stringResource(MR.strings.verify_simplex_name_action), color = MaterialTheme.colors.primary, modifier = Modifier.clickable { runVerify(manual = true) } ) + } } } } + +// The check/cross drawable is centered in its box with ~27% padding top and bottom, so its glyph bottom sits at +// ~73% of the box height. Align that line with the text baseline so the glyph rests on the baseline; the box is +// sized so the visible glyph is about the name's cap height. Only the icon is tinted, never the name. +@Composable +private fun SimplexNameWithIcon(name: String, style: TextStyle, icon: ImageResource, tint: Color, onClick: () -> Unit) { + Row( + horizontalArrangement = Arrangement.spacedBy(2.dp), + modifier = Modifier.clickable { onClick() } + ) { + Text(name, Modifier.alignByBaseline(), style = style) + Icon( + painterResource(icon), null, + Modifier.size(22.dp).alignBy { it.measuredHeight * 73 / 100 }, + tint = tint + ) + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt index c54ca148d4..4423f1dfbc 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt @@ -988,6 +988,17 @@ private fun GroupChatInfoHeader(cInfo: ChatInfo, groupInfo: GroupInfo) { } ) } + val businessClaim = groupInfo.businessChat?.businessDomain + if (businessClaim != null && (groupInfo.groupDomainVerified != null || businessClaim.proof != null)) { + // A business presents as a contact, so the name retains its .simplex suffix. The tick comes from + // groupDomainVerified (set at connect); its domain proof is not received on the wire yet, so + // re-verification is not wired. + SimplexNameView( + simplexName = "@${businessClaim.domain}", + verified = groupInfo.groupDomainVerified, + verify = { null } + ) + } val webPage = groupInfo.groupProfile.publicGroup?.publicGroupAccess?.groupWebPage if (webPage != null) { val uriHandler = LocalUriHandler.current diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt index 9d0efad152..7523ea40a0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt @@ -369,7 +369,7 @@ private fun UserAddressLayout( stringResource(MR.strings.your_simplex_name), click = { ModalManager.start.showCustomModal { close -> - val domain = user?.profile?.contactDomain?.shortName + val domain = user?.profile?.contactDomain?.domain SetSimplexDomainView( title = generalGetString(MR.strings.set_simplex_name), footer = generalGetString(MR.strings.set_user_simplex_name_footer), diff --git a/bots/api/TYPES.md b/bots/api/TYPES.md index ce5c834001..79af05066b 100644 --- a/bots/api/TYPES.md +++ b/bots/api/TYPES.md @@ -468,6 +468,7 @@ TIMEOUT: - chatType: [BusinessChatType](#businesschattype) - businessId: string - customerId: string +- businessDomain: [SimplexDomainClaim](#simplexdomainclaim)? --- diff --git a/packages/simplex-chat-client/types/typescript/src/types.ts b/packages/simplex-chat-client/types/typescript/src/types.ts index dcd9a5581f..63f74f5814 100644 --- a/packages/simplex-chat-client/types/typescript/src/types.ts +++ b/packages/simplex-chat-client/types/typescript/src/types.ts @@ -289,6 +289,7 @@ export interface BusinessChatInfo { chatType: BusinessChatType businessId: string customerId: string + businessDomain?: SimplexDomainClaim } export enum BusinessChatType { diff --git a/packages/simplex-chat-python/src/simplex_chat/types/_types.py b/packages/simplex-chat-python/src/simplex_chat/types/_types.py index 0856aaf772..b3edf77111 100644 --- a/packages/simplex-chat-python/src/simplex_chat/types/_types.py +++ b/packages/simplex-chat-python/src/simplex_chat/types/_types.py @@ -204,6 +204,7 @@ class BusinessChatInfo(TypedDict): chatType: "BusinessChatType" businessId: str customerId: str + businessDomain: NotRequired["SimplexDomainClaim"] BusinessChatType = Literal["business", "customer"] diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 18ca626a46..49e91704aa 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -2072,7 +2072,7 @@ processChatCommand cxt nm = \case groupPreferences = maybe defaultBusinessGroupPrefs businessGroupPrefs preferences groupProfile = businessGroupProfile profile groupPreferences gVar <- asks random - (gInfo, hostMember_) <- withStore $ \db -> createPreparedGroup db gVar cxt user groupProfile True ccLink welcomeSharedMsgId False GRMember Nothing Nothing + (gInfo, hostMember_) <- withStore $ \db -> createPreparedGroup db gVar cxt user groupProfile True ccLink welcomeSharedMsgId False GRMember Nothing verifiedDomain hostMember <- maybe (throwCmdError "no host member") pure hostMember_ void $ createChatItem user (CDGroupSnd gInfo Nothing) False CIChatBanner Nothing Nothing (Just epochStart) let cd = CDGroupRcv gInfo Nothing hostMember @@ -2100,7 +2100,7 @@ processChatCommand cxt nm = \case APIPrepareGroup userId ccLink direct verifiedDomain groupSLinkData -> withUserId userId $ \user -> do let GroupShortLinkData {groupProfile = GroupProfile {description}} = groupSLinkData welcomeSharedMsgId <- forM description $ \_ -> getSharedMsgId - (gInfo, hostMember_) <- preparedGroupFromLink user ccLink direct groupSLinkData welcomeSharedMsgId (True <$ verifiedDomain) + (gInfo, hostMember_) <- preparedGroupFromLink user ccLink direct groupSLinkData welcomeSharedMsgId verifiedDomain void $ createChatItem user (CDGroupSnd gInfo Nothing) False CIChatBanner Nothing Nothing (Just epochStart) let cd = maybe (CDChannelRcv gInfo Nothing) (CDGroupRcv gInfo Nothing) hostMember_ cInfo = GroupChat gInfo Nothing @@ -4813,14 +4813,14 @@ processChatCommand cxt nm = \case gInfo <- withFastStore $ \db -> getGroupInfo db cxt user gId a $ SRGroup gId scope (sendAsGroup' gInfo scope) _ -> throwCmdError "not supported" - preparedGroupFromLink :: User -> CreatedLinkContact -> DirectLink -> GroupShortLinkData -> Maybe SharedMsgId -> Maybe Bool -> CM (GroupInfo, Maybe GroupMember) - preparedGroupFromLink user ccLink direct groupSLinkData welcomeSharedMsgId nameVerified = do + preparedGroupFromLink :: User -> CreatedLinkContact -> DirectLink -> GroupShortLinkData -> Maybe SharedMsgId -> Maybe SimplexDomain -> CM (GroupInfo, Maybe GroupMember) + preparedGroupFromLink user ccLink direct groupSLinkData welcomeSharedMsgId verifiedDomain = do let GroupShortLinkData {groupProfile = gp, publicGroupData = publicGroupData_} = groupSLinkData publicMemberCount_ = (\PublicGroupData {publicMemberCount} -> publicMemberCount) <$> publicGroupData_ useRelays = not direct subRole <- if useRelays then asks $ channelSubscriberRole . config else pure GRMember gVar <- asks random - withStore $ \db -> createPreparedGroup db gVar cxt user gp False ccLink welcomeSharedMsgId useRelays subRole publicMemberCount_ nameVerified + withStore $ \db -> createPreparedGroup db gVar cxt user gp False ccLink welcomeSharedMsgId useRelays subRole publicMemberCount_ verifiedDomain getSharedMsgId :: CM SharedMsgId getSharedMsgId = do diff --git a/src/Simplex/Chat/Library/Internal.hs b/src/Simplex/Chat/Library/Internal.hs index 77edc99f05..894a17968b 100644 --- a/src/Simplex/Chat/Library/Internal.hs +++ b/src/Simplex/Chat/Library/Internal.hs @@ -1046,7 +1046,7 @@ acceptBusinessJoinRequestAsync -- This refers to the "title member" that defines the group name and profile. -- This coincides with fromMember to be current user when accepting the connecting user, -- but it will be different when inviting somebody else. - business = Just $ BusinessChatInfo {chatType = BCBusiness, businessId = userMemberId, customerId = memberId}, + business = Just $ BusinessChatInfo {chatType = BCBusiness, businessId = userMemberId, customerId = memberId, businessDomain = Nothing}, groupSize = Just 1 } subMode <- chatReadVar subscriptionMode diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index d6f51fadc3..b10b7ad239 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -238,7 +238,7 @@ import Simplex.Chat.Types.MemberRelations (IntroductionDirection (..), MemberRel import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Shared import Simplex.Chat.Types.UITheme -import Simplex.Messaging.Agent.Protocol (ConfirmationId, ConnId, CreatedConnLink (..), InvitationId, OwnerAuth (..), SimplexNameInfo (..), SimplexNameType (..), UserId) +import Simplex.Messaging.Agent.Protocol (ConfirmationId, ConnId, CreatedConnLink (..), InvitationId, OwnerAuth (..), SimplexDomain, SimplexNameInfo (..), SimplexNameType (..), UserId) import Simplex.Messaging.Agent.Store.AgentStore (firstRow, fromOnlyBI, maybeFirstRow) import qualified Simplex.FileTransfer.Description as FD import Simplex.Messaging.Encoding (smpDecode, smpEncode) @@ -647,8 +647,8 @@ deleteContactCardKeepConn db connId Contact {contactId, profile = LocalProfile { DB.execute db "DELETE FROM contacts WHERE contact_id = ?" (Only contactId) DB.execute db "DELETE FROM contact_profiles WHERE contact_profile_id = ?" (Only profileId) -createPreparedGroup :: DB.Connection -> TVar ChaChaDRG -> StoreCxt -> User -> GroupProfile -> Bool -> CreatedLinkContact -> Maybe SharedMsgId -> Bool -> GroupMemberRole -> Maybe Int64 -> Maybe Bool -> ExceptT StoreError IO (GroupInfo, Maybe GroupMember) -createPreparedGroup db gVar cxt user@User {userId, userContactId} groupProfile business connLinkToConnect welcomeSharedMsgId useRelays userMemberRole publicMemberCount_ verified_ = do +createPreparedGroup :: DB.Connection -> TVar ChaChaDRG -> StoreCxt -> User -> GroupProfile -> Bool -> CreatedLinkContact -> Maybe SharedMsgId -> Bool -> GroupMemberRole -> Maybe Int64 -> Maybe SimplexDomain -> ExceptT StoreError IO (GroupInfo, Maybe GroupMember) +createPreparedGroup db gVar cxt user@User {userId, userContactId} groupProfile business connLinkToConnect welcomeSharedMsgId useRelays userMemberRole publicMemberCount_ verifiedDomain = do currentTs <- liftIO getCurrentTime let prepared = Just (connLinkToConnect, welcomeSharedMsgId) (groupId, groupLDN) <- createGroup_ db userId groupProfile prepared Nothing useRelays Nothing publicMemberCount_ currentTs @@ -667,7 +667,11 @@ createPreparedGroup db gVar cxt user@User {userId, userContactId} groupProfile b forM_ hostMember_ $ \hostMember -> when business $ liftIO $ setGroupBusinessChatInfo groupId membership hostMember g <- getGroupInfo db cxt user groupId - g' <- liftIO $ maybe (pure g) (setGroupDomainVerified db user g) verified_ + -- a business has no domain in its profile, so set it out-of-band; a channel already has it (createGroup_), just verify + g' <- liftIO $ case verifiedDomain of + Just d | business -> setPreparedGroupDomain db user g d + Just _ -> setGroupDomainVerified db user g True + Nothing -> pure g pure (g', hostMember_) where insertHost_ currentTs groupId groupLDN = do @@ -691,7 +695,7 @@ createPreparedGroup db gVar cxt user@User {userId, userContactId} groupProfile b insertedRowId db setGroupBusinessChatInfo :: GroupId -> GroupMember -> GroupMember -> IO () setGroupBusinessChatInfo groupId membership hostMember = do - let businessChatInfo = Just BusinessChatInfo {chatType = BCBusiness, businessId = memberId' hostMember, customerId = memberId' membership} + let businessChatInfo = Just BusinessChatInfo {chatType = BCBusiness, businessId = memberId' hostMember, customerId = memberId' membership, businessDomain = Nothing} updateBusinessChatInfo db groupId businessChatInfo updateBusinessChatInfo :: DB.Connection -> GroupId -> Maybe BusinessChatInfo -> IO () @@ -2714,22 +2718,28 @@ updateGroupProfile db user@User {userId} g@GroupInfo {groupId, localDisplayName, (groupType_, groupLink_) = case publicGroup of Just PublicGroupProfile {groupType, groupLink} -> (Just groupType, Just groupLink) Nothing -> (Nothing, Nothing) + -- group_domain is owned by publicGroup; when the incoming profile has no publicGroup (a business, whose domain + -- is set out-of-band) the CASE leaves the stored group_domain unchanged instead of clearing it. updateGroupProfile_ currentTs = - DB.execute - db - [sql| - UPDATE group_profiles - SET display_name = ?, full_name = ?, short_descr = ?, description = ?, image = ?, - group_type = ?, group_link = ?, - group_web_page = ?, group_domain = ?, domain_web_page = ?, allow_embedding = ?, group_domain_proof = ?, - preferences = ?, member_admission = ?, updated_at = ? - WHERE group_profile_id IN ( - SELECT group_profile_id - FROM groups - WHERE user_id = ? AND group_id = ? - ) - |] - ((newName, fullName, shortDescr, description, image, groupType_, groupLink_) :. publicGroupAccessRow publicGroup :. (groupPreferences, memberAdmission, currentTs, userId, groupId)) + let (groupWebPage_, groupDomain_, domainWebPage_, allowEmbedding_, groupDomainProof_) = publicGroupAccessRow publicGroup + in DB.execute + db + [sql| + UPDATE group_profiles + SET display_name = ?, full_name = ?, short_descr = ?, description = ?, image = ?, + group_type = ?, group_link = ?, + group_web_page = ?, group_domain = CASE WHEN ? THEN ? ELSE group_domain END, domain_web_page = ?, allow_embedding = ?, group_domain_proof = ?, + preferences = ?, member_admission = ?, updated_at = ? + WHERE group_profile_id IN ( + SELECT group_profile_id + FROM groups + WHERE user_id = ? AND group_id = ? + ) + |] + ( (newName, fullName, shortDescr, description, image, groupType_, groupLink_) + :. (groupWebPage_, isJust publicGroup, groupDomain_, domainWebPage_, allowEmbedding_, groupDomainProof_) + :. (groupPreferences, memberAdmission, currentTs, userId, groupId) + ) updateGroup_ ldn currentTs = do DB.execute db @@ -2745,6 +2755,19 @@ setGroupDomainVerified db User {userId} g@GroupInfo {groupId} verified = do (BI verified, userId, groupId) pure g {groupDomainVerified = Just verified} +-- A business group has no publicGroup claim, so the domain it was connected by (from its address) is written +-- directly to group_domain and marked verified, so it is found by the local name search (getGroupToConnect). +setPreparedGroupDomain :: DB.Connection -> User -> GroupInfo -> SimplexDomain -> IO GroupInfo +setPreparedGroupDomain db user@User {userId} g@GroupInfo {groupId} domain = do + DB.execute + db + [sql| + UPDATE group_profiles SET group_domain = ? + WHERE group_profile_id IN (SELECT group_profile_id FROM groups WHERE user_id = ? AND group_id = ?) + |] + (domain, userId, groupId) + setGroupDomainVerified db user g True + updateGroupPreferences :: DB.Connection -> User -> GroupInfo -> GroupPreferences -> IO GroupInfo updateGroupPreferences db User {userId} g@GroupInfo {groupId, groupProfile = p} ps = do currentTs <- getCurrentTime diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index 0d01b2ac1d..36a3a0c545 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -699,7 +699,7 @@ toGroupInfo now cxt userContactId chatTags ((groupId, localDisplayName, displayN publicGroup = toPublicGroupProfile groupType_ groupLink_ publicGroupId_ (toPublicGroupAccess accessRow) groupKeys = toGroupKeys publicGroupId_ groupKeysRow groupProfile = GroupProfile {displayName, fullName, shortDescr, description, image, publicGroup, groupPreferences, memberAdmission} - businessChat = toBusinessChatInfo businessRow + businessChat = toBusinessChatInfo (toPublicGroupAccess accessRow >>= groupDomainClaim) businessRow preparedGroup = toPreparedGroup preparedGroupRow groupSummary = GroupSummary {currentMembers, publicMemberCount} in GroupInfo {groupId, useRelays = BoolDef useRelays, relayOwnStatus, localDisplayName, groupProfile, localAlias, businessChat, fullGroupPreferences, membership, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt, preparedGroup, chatTags, chatItemTTL, uiThemes, groupSummary, rosterVersion, customData, membersRequireAttention, viaGroupLinkUri, groupKeys, groupDomainVerified = unBI <$> groupDomainVerified} @@ -783,9 +783,9 @@ rowToLocalProfile :: UTCTime -> ProfileRow -> LocalProfile rowToLocalProfile now ((profileId, displayName, fullName, shortDescr, image, contactLink, peerType, localAlias, preferences) :. badgeRow :. domainRow) = LocalProfile {profileId, displayName, fullName, shortDescr, image, contactLink, contactDomain = rowToContactDomain domainRow, contactDomainVerified = rowToDomainVerified domainRow, peerType, localBadge = rowToBadge now badgeRow, localAlias, preferences} -toBusinessChatInfo :: BusinessChatInfoRow -> Maybe BusinessChatInfo -toBusinessChatInfo (Just chatType, Just businessId, Just customerId) = Just BusinessChatInfo {chatType, businessId, customerId} -toBusinessChatInfo _ = Nothing +toBusinessChatInfo :: Maybe SimplexDomainClaim -> BusinessChatInfoRow -> Maybe BusinessChatInfo +toBusinessChatInfo businessDomain (Just chatType, Just businessId, Just customerId) = Just BusinessChatInfo {chatType, businessId, customerId, businessDomain} +toBusinessChatInfo _ _ = Nothing groupInfoQuery :: Query groupInfoQuery = groupInfoQueryFields <> " " <> groupInfoQueryFrom diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index edf4aab33a..0dc27eb9e9 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -1050,7 +1050,9 @@ data MemberInfo = MemberInfo data BusinessChatInfo = BusinessChatInfo { chatType :: BusinessChatType, businessId :: MemberId, - customerId :: MemberId + customerId :: MemberId, + -- TODO [names] sent in protocol in GroupInvitation + businessDomain :: Maybe SimplexDomainClaim } deriving (Eq, Show) diff --git a/tests/ChatTests/Names.hs b/tests/ChatTests/Names.hs index cc46a65543..1de347e026 100644 --- a/tests/ChatTests/Names.hs +++ b/tests/ChatTests/Names.hs @@ -211,7 +211,7 @@ testConnectByNameBusinessAndChannel ps = withSmpServerAndNames $ \reg -> withNewTestChat ps "bob" bobProfile $ \bob -> do (channelLink, _) <- prepareChannel1Relay "biz" alice cath alice ##> "/ad" - (contactLink, _) <- getContactLinks alice True + (contactLink, fullLink) <- getContactLinks alice True registerName reg bizName (contactAndChannelNameRecord "biz" (T.pack contactLink) (T.pack channelLink)) alice ##> "/auto_accept on business" alice <## "auto_accept on, business" @@ -219,7 +219,25 @@ testConnectByNameBusinessAndChannel ps = withSmpServerAndNames $ \reg -> alice <## "new contact address set" bob ##> "/_connect plan 1 biz.simplex" bob <## "business address: ok to connect" - _ <- getTermLine bob -- contact short link data (JSON, printed in test view) + contactSLinkData <- getTermLine bob -- contact short link data (JSON, printed in test view) bob <## "You can also join channel #biz" + -- preparing the business by name saves its domain on the group, so it is then found by local name search + bob ##> ("/_prepare contact 1 " <> fullLink <> " " <> contactLink <> " domain=biz.simplex " <> contactSLinkData) + bob <## "#alice: group is prepared" + -- host changes its profile so the handshake's group-profile write fires; it must not wipe the saved domain + alice ##> "/p alice Alice Biz" + alice <## "user bio changed to Alice Biz (your 0 contacts are notified)" + bob ##> "/_connect plan 1 @biz.simplex resolve=never" + bob <## "business address: known prepared business #alice" + bob ##> "/_connect group #1" + bob <## "#alice: connection started" + alice <## "#bob (Bob): accepting business address request..." + bob <## "#alice: joining the group..." + alice <## "#bob: bob_1 joined the group" + bob <## "#alice: you joined the group" + -- after fully connecting, the business must still be found by local name search + bob ##> "/_connect plan 1 @biz.simplex resolve=never" + bob <## "business address: known business #alice" + bob <## "use #alice to send messages" where bizName = SimplexNameInfo NTContact (SimplexDomain TLDSimplex "biz" [])