From cef63f2c87801f05445ef484c71c7229636a7b55 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 21 May 2025 11:47:45 +0100 Subject: [PATCH 01/13] ui: translations (#5936) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (German) Currently translated at 100.0% (2404 of 2404 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (Russian) Currently translated at 98.3% (2365 of 2404 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/ * Translated using Weblate (German) Currently translated at 100.0% (2090 of 2090 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% (2404 of 2404 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Italian) Currently translated at 100.0% (2090 of 2090 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% (2404 of 2404 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Russian) Currently translated at 100.0% (2404 of 2404 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 100.0% (2090 of 2090 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ru/ * ios: process localizations * update --------- Co-authored-by: mlanp Co-authored-by: Vitaly Kanevsky Co-authored-by: Random Co-authored-by: 大王叫我来巡山 --- .../de.xcloc/Localized Contents/de.xliff | 37 +++ .../it.xcloc/Localized Contents/it.xliff | 37 +++ .../ru.xcloc/Localized Contents/ru.xliff | 103 +++++- .../SimpleX NSE/ru.lproj/Localizable.strings | 3 + apps/ios/bg.lproj/Localizable.strings | 2 +- apps/ios/cs.lproj/Localizable.strings | 2 +- apps/ios/de.lproj/Localizable.strings | 113 ++++++- apps/ios/es.lproj/Localizable.strings | 2 +- apps/ios/fi.lproj/Localizable.strings | 2 +- apps/ios/fr.lproj/Localizable.strings | 2 +- apps/ios/it.lproj/Localizable.strings | 113 ++++++- apps/ios/ja.lproj/Localizable.strings | 2 +- apps/ios/nl.lproj/Localizable.strings | 2 +- apps/ios/pl.lproj/Localizable.strings | 2 +- apps/ios/ru.lproj/Localizable.strings | 297 +++++++++++++++++- apps/ios/th.lproj/Localizable.strings | 2 +- apps/ios/tr.lproj/Localizable.strings | 2 +- apps/ios/uk.lproj/Localizable.strings | 2 +- apps/ios/zh-Hans.lproj/Localizable.strings | 2 +- .../commonMain/resources/MR/de/strings.xml | 21 +- .../commonMain/resources/MR/it/strings.xml | 23 +- .../commonMain/resources/MR/ru/strings.xml | 58 +++- .../resources/MR/zh-rCN/strings.xml | 17 +- 23 files changed, 819 insertions(+), 27 deletions(-) diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 94d7c0b81b..b2e404c141 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -567,10 +567,12 @@ swipe action Accept as member + Als Mitglied übernehmen alert action Accept as observer + Als Beobachter übernehmen alert action @@ -596,6 +598,7 @@ swipe action Accept member + Mitglied übernehmen alert title @@ -1596,10 +1599,12 @@ set passcode view Chat with admins + Chat mit Administratoren chat toolbar Chat with member + Chat mit einem Mitglied No comment provided by engineer. @@ -1609,6 +1614,7 @@ set passcode view Chats with members + Chats mit Mitgliedern No comment provided by engineer. @@ -2428,6 +2434,7 @@ swipe action Delete chat with member? + Chat mit dem Mitglied löschen? alert title @@ -3173,6 +3180,7 @@ chat item action Error accepting member + Fehler beim Übernehmen des Mitglieds alert title @@ -3272,6 +3280,7 @@ chat item action Error deleting chat with member + Fehler beim Löschen des Chats mit dem Mitglied alert title @@ -4768,6 +4777,7 @@ Das ist Ihr Link für die Gruppe %@! Member admission + Aufnahme von Mitgliedern No comment provided by engineer. @@ -4807,6 +4817,7 @@ Das ist Ihr Link für die Gruppe %@! Member will join the group, accept member? + Ein Mitglied wird der Gruppe beitreten. Übernehmen? alert message @@ -5231,6 +5242,7 @@ Das ist Ihr Link für die Gruppe %@! New member wants to join the group. + Ein neues Mitglied will der Gruppe beitreten. rcv group event chat item @@ -5275,6 +5287,7 @@ Das ist Ihr Link für die Gruppe %@! No chats with members + Keine Chats mit Mitgliedern No comment provided by engineer. @@ -5884,6 +5897,7 @@ Fehler: %@ Please wait for group moderators to review your request to join the group. + Bitte warten Sie auf die Überprüfung Ihrer Anfrage durch die Gruppen-Moderatoren, um der Gruppe beitreten zu können. snd group event chat item @@ -6341,6 +6355,7 @@ swipe action Reject member? + Mitglied ablehnen? alert title @@ -6455,6 +6470,7 @@ swipe action Report sent to moderators + Meldung wurde an die Moderatoren gesendet alert title @@ -6574,10 +6590,12 @@ swipe action Review members + Überprüfung der Mitglieder admission stage Review members before admitting ("knocking"). + Überprüfung der Mitglieder vor der Aufnahme ("Anklopfen"). admission stage description @@ -6638,6 +6656,7 @@ chat item action Save admission settings? + Speichern der Aufnahme-Einstellungen? alert title @@ -7157,6 +7176,7 @@ chat item action Set member admission + Aufnahme von Mitgliedern festlegen No comment provided by engineer. @@ -8923,6 +8943,7 @@ Verbindungsanfrage wiederholen? You can view your reports in Chat with admins. + Sie können Ihre Meldungen im Chat mit den Administratoren sehen. alert message @@ -9229,6 +9250,7 @@ Verbindungsanfrage wiederholen? accepted %@ + %@ übernommen rcv group event chat item @@ -9243,6 +9265,7 @@ Verbindungsanfrage wiederholen? accepted you + hat Sie übernommen rcv group event chat item @@ -9267,6 +9290,7 @@ Verbindungsanfrage wiederholen? all + alle member criteria value @@ -9357,6 +9381,7 @@ marked deleted chat item preview text can't send messages + Es können keine Nachrichten gesendet werden No comment provided by engineer. @@ -9466,10 +9491,12 @@ marked deleted chat item preview text contact deleted + Kontakt gelöscht No comment provided by engineer. contact disabled + Kontakt deaktiviert No comment provided by engineer. @@ -9484,6 +9511,7 @@ marked deleted chat item preview text contact not ready + Kontakt nicht bereit No comment provided by engineer. @@ -9659,6 +9687,7 @@ pref value group is deleted + Gruppe wird gelöscht No comment provided by engineer. @@ -9788,6 +9817,7 @@ pref value member has old version + Das Mitglied hat eine alte App-Version No comment provided by engineer. @@ -9857,6 +9887,7 @@ pref value not synchronized + Nicht synchronisiert No comment provided by engineer. @@ -9924,6 +9955,7 @@ time to disappear pending review + Ausstehende Überprüfung No comment provided by engineer. @@ -9968,6 +10000,7 @@ time to disappear removed from group + Von der Gruppe entfernt No comment provided by engineer. @@ -9982,6 +10015,7 @@ time to disappear request to join rejected + Beitrittsanfrage abgelehnt No comment provided by engineer. @@ -9991,10 +10025,12 @@ time to disappear review + Überprüfung No comment provided by engineer. reviewed by admins + Von Administratoren überprüft No comment provided by engineer. @@ -10188,6 +10224,7 @@ Zuletzt empfangene Nachricht: %2$@ you accepted this member + Sie haben dieses Mitglied übernommen snd group event chat item diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index d672a0da4f..d3c2a139cc 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -567,10 +567,12 @@ swipe action Accept as member + Accetta come membro alert action Accept as observer + Accetta come osservatore alert action @@ -596,6 +598,7 @@ swipe action Accept member + Accetta membro alert title @@ -1596,10 +1599,12 @@ set passcode view Chat with admins + Chat con amministratori chat toolbar Chat with member + Chatta con il membro No comment provided by engineer. @@ -1609,6 +1614,7 @@ set passcode view Chats with members + Chat con membri No comment provided by engineer. @@ -2428,6 +2434,7 @@ swipe action Delete chat with member? + Eliminare la chat con il membro? alert title @@ -3173,6 +3180,7 @@ chat item action Error accepting member + Errore di accettazione del membro alert title @@ -3272,6 +3280,7 @@ chat item action Error deleting chat with member + Errore di eliminazione della chat con il membro alert title @@ -4768,6 +4777,7 @@ Questo è il tuo link per il gruppo %@! Member admission + Ammissione del membro No comment provided by engineer. @@ -4807,6 +4817,7 @@ Questo è il tuo link per il gruppo %@! Member will join the group, accept member? + Il membro entrerà nel gruppo, accettarlo? alert message @@ -5231,6 +5242,7 @@ Questo è il tuo link per il gruppo %@! New member wants to join the group. + Un nuovo membro vuole entrare nel gruppo. rcv group event chat item @@ -5275,6 +5287,7 @@ Questo è il tuo link per il gruppo %@! No chats with members + Nessuna chat con membri No comment provided by engineer. @@ -5884,6 +5897,7 @@ Errore: %@ Please wait for group moderators to review your request to join the group. + Attendi che i moderatori del gruppo revisionino la tua richiesta di entrare nel gruppo. snd group event chat item @@ -6341,6 +6355,7 @@ swipe action Reject member? + Rifiutare il membro? alert title @@ -6455,6 +6470,7 @@ swipe action Report sent to moderators + Segnalazione inviata ai moderatori alert title @@ -6574,10 +6590,12 @@ swipe action Review members + Revisiona i membri admission stage Review members before admitting ("knocking"). + Revisiona i membri prima di ammetterli ("bussare"). admission stage description @@ -6638,6 +6656,7 @@ chat item action Save admission settings? + Salvare le impostazioni di ammissione? alert title @@ -7157,6 +7176,7 @@ chat item action Set member admission + Imposta l'ammissione del membro No comment provided by engineer. @@ -8923,6 +8943,7 @@ Ripetere la richiesta di ingresso? You can view your reports in Chat with admins. + Puoi vedere le tue segnalazioni nella chat con gli amministratori. alert message @@ -9229,6 +9250,7 @@ Ripetere la richiesta di connessione? accepted %@ + %@ accettato rcv group event chat item @@ -9243,6 +9265,7 @@ Ripetere la richiesta di connessione? accepted you + ti ha accettato/a rcv group event chat item @@ -9267,6 +9290,7 @@ Ripetere la richiesta di connessione? all + tutti member criteria value @@ -9357,6 +9381,7 @@ marked deleted chat item preview text can't send messages + impossibile inviare messaggi No comment provided by engineer. @@ -9466,10 +9491,12 @@ marked deleted chat item preview text contact deleted + contatto eliminato No comment provided by engineer. contact disabled + contatto disattivato No comment provided by engineer. @@ -9484,6 +9511,7 @@ marked deleted chat item preview text contact not ready + contatto non pronto No comment provided by engineer. @@ -9659,6 +9687,7 @@ pref value group is deleted + il gruppo è eliminato No comment provided by engineer. @@ -9788,6 +9817,7 @@ pref value member has old version + il membro ha una versione vecchia No comment provided by engineer. @@ -9857,6 +9887,7 @@ pref value not synchronized + non sincronizzato No comment provided by engineer. @@ -9924,6 +9955,7 @@ time to disappear pending review + in attesa di revisione No comment provided by engineer. @@ -9968,6 +10000,7 @@ time to disappear removed from group + rimosso dal gruppo No comment provided by engineer. @@ -9982,6 +10015,7 @@ time to disappear request to join rejected + richiesta di entrare rifiutata No comment provided by engineer. @@ -9991,10 +10025,12 @@ time to disappear review + revisiona No comment provided by engineer. reviewed by admins + revisionato dagli amministratori No comment provided by engineer. @@ -10188,6 +10224,7 @@ ultimo msg ricevuto: %2$@ you accepted this member + hai accettato questo membro snd group event chat item diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 651d9a7063..2ec1130718 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -167,7 +167,7 @@ %d hours - %d ч. + %d час. time interval @@ -567,10 +567,12 @@ swipe action Accept as member + Принять в группу alert action Accept as observer + Принять как читателя alert action @@ -596,6 +598,7 @@ swipe action Accept member + Принять члена alert title @@ -810,6 +813,7 @@ swipe action All servers + Все серверы No comment provided by engineer. @@ -1595,10 +1599,12 @@ set passcode view Chat with admins + Чат с админами chat toolbar Chat with member + Чат с членом группы No comment provided by engineer. @@ -1608,6 +1614,7 @@ set passcode view Chats with members + Чаты с членами группы No comment provided by engineer. @@ -2427,6 +2434,7 @@ swipe action Delete chat with member? + Удалить чат с членом группы? alert title @@ -2716,6 +2724,7 @@ swipe action Direct messages between members are prohibited. + Прямые сообщения между членами запрещены. No comment provided by engineer. @@ -2820,6 +2829,7 @@ swipe action Do not send history to new members. + Не отправлять историю новым членам. No comment provided by engineer. @@ -2945,6 +2955,7 @@ chat item action Enable Flux in Network & servers settings for better metadata privacy. + Включите Flux в настройках Сеть и серверы для лучшей конфиденциальности метаданных. No comment provided by engineer. @@ -3169,10 +3180,12 @@ chat item action Error accepting member + Ошибка вступления члена группы alert title Error adding member(s) + Ошибка при добавлении членов группы No comment provided by engineer. @@ -3237,6 +3250,7 @@ chat item action Error creating member contact + Ошибка при создании контакта No comment provided by engineer. @@ -3266,6 +3280,7 @@ chat item action Error deleting chat with member + Ошибка при удалении чата с членом группы alert title @@ -3375,6 +3390,7 @@ chat item action Error removing member + Ошибка при удалении члена группы alert title @@ -3439,6 +3455,7 @@ chat item action Error sending member contact invitation + Ошибка при отправке приглашения члену No comment provided by engineer. @@ -3774,6 +3791,7 @@ snd error text Fix not supported by group member + Починка не поддерживается членом группы. No comment provided by engineer. @@ -3907,6 +3925,7 @@ Error: %2$@ Fully decentralized – visible only to members. + Группа полностью децентрализована – она видна только членам. No comment provided by engineer. @@ -4016,6 +4035,7 @@ Error: %2$@ Group profile is stored on members' devices, not on the servers. + Профиль группы хранится на устройствах членов, а не на серверах. No comment provided by engineer. @@ -4025,6 +4045,7 @@ Error: %2$@ Group will be deleted for all members - this cannot be undone! + Группа будет удалена для всех членов - это действие нельзя отменить! No comment provided by engineer. @@ -4089,6 +4110,7 @@ Error: %2$@ History is not sent to new members. + История не отправляется новым членам. No comment provided by engineer. @@ -4436,6 +4458,7 @@ More improvements are coming soon! Invite members + Пригласить членов группы No comment provided by engineer. @@ -4748,14 +4771,17 @@ This is your link for group %@! Member + Член группы No comment provided by engineer. Member admission + Приём членов в группу No comment provided by engineer. Member inactive + Член неактивен item status text @@ -4770,58 +4796,72 @@ This is your link for group %@! Member role will be changed to "%@". All group members will be notified. + Роль члена будет изменена на "%@". Все члены группы получат уведомление. No comment provided by engineer. Member role will be changed to "%@". The member will receive a new invitation. + Роль члена будет изменена на "%@". Будет отправлено новое приглашение. No comment provided by engineer. Member will be removed from chat - this cannot be undone! + Член будет удален из разговора - это действие нельзя отменить! No comment provided by engineer. Member will be removed from group - this cannot be undone! + Член группы будет удален - это действие нельзя отменить! No comment provided by engineer. Member will join the group, accept member? + Участник хочет присоединиться к группе. Принять? alert message Members can add message reactions. + Члены могут добавлять реакции на сообщения. No comment provided by engineer. Members can irreversibly delete sent messages. (24 hours) + Члены могут необратимо удалять отправленные сообщения. (24 часа) No comment provided by engineer. Members can report messsages to moderators. + Члены группы могут пожаловаться модераторам. No comment provided by engineer. Members can send SimpleX links. + Члены могут отправлять ссылки SimpleX. No comment provided by engineer. Members can send direct messages. + Члены могут посылать прямые сообщения. No comment provided by engineer. Members can send disappearing messages. + Члены могут посылать исчезающие сообщения. No comment provided by engineer. Members can send files and media. + Члены могут слать файлы и медиа. No comment provided by engineer. Members can send voice messages. + Члены могут отправлять голосовые сообщения. No comment provided by engineer. Mention members 👋 + Упоминайте участников 👋 No comment provided by engineer. @@ -4856,6 +4896,7 @@ This is your link for group %@! Message may be delivered later if member becomes active. + Сообщение может быть доставлено позже, если член группы станет активным. item status description @@ -5195,10 +5236,12 @@ This is your link for group %@! New member role + Роль члена группы No comment provided by engineer. New member wants to join the group. + Новый участник хочет присоединиться к группе. rcv group event chat item @@ -5243,6 +5286,7 @@ This is your link for group %@! No chats with members + Нет чатов с членами группы No comment provided by engineer. @@ -5419,6 +5463,9 @@ This is your link for group %@! Now admins can: - delete members' messages. - disable members ("observer" role) + Теперь админы могут: +- удалять сообщения членов. +- приостанавливать членов (роль наблюдатель) No comment provided by engineer. @@ -5593,6 +5640,7 @@ Requires compatible VPN. Open link? + Открыть ссылку? alert title @@ -5709,6 +5757,7 @@ Requires compatible VPN. Past member %@ + Бывший член %@ past/unknown group member @@ -5847,6 +5896,7 @@ Error: %@ Please wait for group moderators to review your request to join the group. + Пожалуйста, подождите, пока модераторы группы рассмотрят ваш запрос на вступление. snd group event chat item @@ -6021,6 +6071,7 @@ Error: %@ Prohibit sending direct messages to members. + Запретить посылать прямые сообщения членам группы. No comment provided by engineer. @@ -6303,6 +6354,7 @@ swipe action Reject member? + Отклонить участника? alert title @@ -6332,10 +6384,12 @@ swipe action Remove member + Удалить члена группы No comment provided by engineer. Remove member? + Удалить члена группы? No comment provided by engineer. @@ -6415,6 +6469,7 @@ swipe action Report sent to moderators + Жалоба отправлена модераторам alert title @@ -6534,10 +6589,12 @@ swipe action Review members + Одобрять членов admission stage Review members before admitting ("knocking"). + Одобрять членов для вступления в группу. admission stage description @@ -6598,6 +6655,7 @@ chat item action Save admission settings? + Сохранить настройки вступления? alert title @@ -6607,6 +6665,7 @@ chat item action Save and notify group members + Сохранить и уведомить членов группы No comment provided by engineer. @@ -6891,6 +6950,7 @@ chat item action Send up to 100 last messages to new members. + Отправить до 100 последних сообщений новым членам. No comment provided by engineer. @@ -7115,6 +7175,7 @@ chat item action Set member admission + Приём членов в группу No comment provided by engineer. @@ -7139,6 +7200,7 @@ chat item action Set the message shown to new members! + Установить сообщение для новых членов группы! No comment provided by engineer. @@ -7229,6 +7291,7 @@ chat item action Short link + Короткая ссылка No comment provided by engineer. @@ -7333,6 +7396,7 @@ chat item action SimpleX channel link + SimpleX ссылка канала simplex link type @@ -7777,18 +7841,22 @@ It can happen because of some bug or when the connection is compromised. The message will be deleted for all members. + Сообщение будет удалено для всех членов группы. No comment provided by engineer. The message will be marked as moderated for all members. + Сообщение будет помечено как удаленное для всех членов группы. No comment provided by engineer. The messages will be deleted for all members. + Сообщения будут удалены для всех членов группы. No comment provided by engineer. The messages will be marked as moderated for all members. + Сообщения будут помечены как удаленные для всех членов группы. No comment provided by engineer. @@ -7898,6 +7966,7 @@ It can happen because of some bug or when the connection is compromised. This group has over %lld members, delivery receipts are not sent. + В этой группе более %lld членов, отчёты о доставке не отправляются. No comment provided by engineer. @@ -7917,6 +7986,7 @@ It can happen because of some bug or when the connection is compromised. This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. + Эта ссылка требует новую версию. Обновите приложение или попросите Ваш контакт прислать совместимую ссылку. No comment provided by engineer. @@ -8108,14 +8178,17 @@ You will be prompted to complete authentication before this feature is enabled.< Unblock member + Разблокировать члена группы No comment provided by engineer. Unblock member for all? + Разблокировать члена для всех? No comment provided by engineer. Unblock member? + Разблокировать члена группы? No comment provided by engineer. @@ -8217,10 +8290,12 @@ To connect, please ask your contact to create another connection link and check Unsupported connection link + Ссылка не поддерживается No comment provided by engineer. Up to 100 last messages are sent to new members. + До 100 последних сообщений отправляются новым членам. No comment provided by engineer. @@ -8315,6 +8390,7 @@ To connect, please ask your contact to create another connection link and check Use TCP port 443 for preset servers only. + Использовать TCP-порт 443 только для серверов по умолчанию. No comment provided by engineer. @@ -8384,6 +8460,7 @@ To connect, please ask your contact to create another connection link and check Use short links (BETA) + Короткие ссылки (БЕТА) No comment provided by engineer. @@ -8830,6 +8907,7 @@ Repeat join request? You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it. + Вы можете поделиться ссылкой или QR кодом - через них можно присоединиться к группе. Вы сможете удалить ссылку, сохранив членов группы, которые через нее соединились. No comment provided by engineer. @@ -8864,6 +8942,7 @@ Repeat join request? You can view your reports in Chat with admins. + Вы можете найти Ваши жалобы в Чате с админами. alert message @@ -8910,6 +8989,7 @@ Repeat connection request? You joined this group. Connecting to inviting group member. + Вы вступили в эту группу. Устанавливается соединение с пригласившим членом группы. No comment provided by engineer. @@ -8979,6 +9059,7 @@ Repeat connection request? You will connect to all group members. + Вы соединитесь со всеми членами группы. No comment provided by engineer. @@ -9168,6 +9249,7 @@ Repeat connection request? accepted %@ + принят %@ rcv group event chat item @@ -9182,6 +9264,7 @@ Repeat connection request? accepted you + Вы приняты rcv group event chat item @@ -9206,10 +9289,12 @@ Repeat connection request? all + все member criteria value all members + все члены feature role @@ -9295,6 +9380,7 @@ marked deleted chat item preview text can't send messages + нельзя отправлять No comment provided by engineer. @@ -9404,10 +9490,12 @@ marked deleted chat item preview text contact deleted + контакт удален No comment provided by engineer. contact disabled + контакт выключен No comment provided by engineer. @@ -9422,6 +9510,7 @@ marked deleted chat item preview text contact not ready + контакт не готов No comment provided by engineer. @@ -9597,6 +9686,7 @@ pref value group is deleted + группа удалена No comment provided by engineer. @@ -9711,10 +9801,12 @@ pref value member + член группы member role member %1$@ changed to %2$@ + член %1$@ изменился на %2$@ profile update event chat item @@ -9724,6 +9816,7 @@ pref value member has old version + член имеет старую версию No comment provided by engineer. @@ -9793,6 +9886,7 @@ pref value not synchronized + не синхронизирован No comment provided by engineer. @@ -9860,6 +9954,7 @@ time to disappear pending review + ожидает одобрения No comment provided by engineer. @@ -9904,6 +9999,7 @@ time to disappear removed from group + удален из группы No comment provided by engineer. @@ -9918,6 +10014,7 @@ time to disappear request to join rejected + запрос на вступление отклонён No comment provided by engineer. @@ -9927,10 +10024,12 @@ time to disappear review + рассмотрение No comment provided by engineer. reviewed by admins + одобрен админами No comment provided by engineer. @@ -10124,6 +10223,7 @@ last received msg: %2$@ you accepted this member + Вы приняли этого члена snd group event chat item @@ -10269,6 +10369,7 @@ last received msg: %2$@ From %d chat(s) + Из %d чатов notification body diff --git a/apps/ios/SimpleX NSE/ru.lproj/Localizable.strings b/apps/ios/SimpleX NSE/ru.lproj/Localizable.strings index 7205b37e7f..cf082a166d 100644 --- a/apps/ios/SimpleX NSE/ru.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/ru.lproj/Localizable.strings @@ -1,6 +1,9 @@ /* notification body */ "%d new events" = "%d новых сообщений"; +/* notification body */ +"From %d chat(s)" = "Из %d чатов"; + /* notification body */ "From: %@" = "От: %@"; diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index 47f1390e0b..1f4ff88f78 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -3158,7 +3158,7 @@ swipe action */ /* No comment provided by engineer. */ "Remove member" = "Острани член"; -/* alert title */ +/* No comment provided by engineer. */ "Remove member?" = "Острани член?"; /* No comment provided by engineer. */ diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index cc19af7a74..a3a6ca8215 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -2478,7 +2478,7 @@ swipe action */ /* No comment provided by engineer. */ "Remove member" = "Odstranit člena"; -/* alert title */ +/* No comment provided by engineer. */ "Remove member?" = "Odebrat člena?"; /* No comment provided by engineer. */ diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index 71cfaa841b..4be4ad96ba 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -345,6 +345,12 @@ accept incoming call via notification swipe action */ "Accept" = "Annehmen"; +/* alert action */ +"Accept as member" = "Als Mitglied übernehmen"; + +/* alert action */ +"Accept as observer" = "Als Beobachter übernehmen"; + /* No comment provided by engineer. */ "Accept conditions" = "Nutzungsbedingungen akzeptieren"; @@ -358,6 +364,12 @@ swipe action */ swipe action */ "Accept incognito" = "Inkognito akzeptieren"; +/* alert title */ +"Accept member" = "Mitglied übernehmen"; + +/* rcv group event chat item */ +"accepted %@" = "%@ übernommen"; + /* call status */ "accepted call" = "Anruf angenommen"; @@ -367,6 +379,9 @@ swipe action */ /* chat list item title */ "accepted invitation" = "Einladung angenommen"; +/* rcv group event chat item */ +"accepted you" = "hat Sie übernommen"; + /* No comment provided by engineer. */ "Acknowledged" = "Bestätigt"; @@ -463,6 +478,9 @@ swipe action */ /* chat item text */ "agreeing encryption…" = "Verschlüsselung zustimmen…"; +/* member criteria value */ +"all" = "alle"; + /* No comment provided by engineer. */ "All" = "Alle"; @@ -905,6 +923,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Can't message member" = "Mitglied kann nicht benachrichtigt werden"; +/* No comment provided by engineer. */ +"can't send messages" = "Es können keine Nachrichten gesendet werden"; + /* alert action alert button */ "Cancel" = "Abbrechen"; @@ -1042,9 +1063,18 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "Der Chat wird für Sie gelöscht. Dies kann nicht rückgängig gemacht werden!"; +/* chat toolbar */ +"Chat with admins" = "Chat mit Administratoren"; + +/* No comment provided by engineer. */ +"Chat with member" = "Chat mit einem Mitglied"; + /* No comment provided by engineer. */ "Chats" = "Chats"; +/* No comment provided by engineer. */ +"Chats with members" = "Chats mit Mitgliedern"; + /* No comment provided by engineer. */ "Check messages every 20 min." = "Alle 20min Nachrichten überprüfen."; @@ -1333,9 +1363,15 @@ set passcode view */ /* No comment provided by engineer. */ "Contact already exists" = "Der Kontakt ist bereits vorhanden"; +/* No comment provided by engineer. */ +"contact deleted" = "Kontakt gelöscht"; + /* No comment provided by engineer. */ "Contact deleted!" = "Kontakt gelöscht!"; +/* No comment provided by engineer. */ +"contact disabled" = "Kontakt deaktiviert"; + /* No comment provided by engineer. */ "contact has e2e encryption" = "Kontakt nutzt E2E-Verschlüsselung"; @@ -1354,6 +1390,9 @@ set passcode view */ /* No comment provided by engineer. */ "Contact name" = "Kontaktname"; +/* No comment provided by engineer. */ +"contact not ready" = "Kontakt nicht bereit"; + /* No comment provided by engineer. */ "Contact preferences" = "Kontakt-Präferenzen"; @@ -1602,6 +1641,9 @@ swipe action */ /* No comment provided by engineer. */ "Delete chat profile?" = "Chat-Profil löschen?"; +/* alert title */ +"Delete chat with member?" = "Chat mit dem Mitglied löschen?"; + /* No comment provided by engineer. */ "Delete chat?" = "Chat löschen?"; @@ -2116,6 +2158,9 @@ chat item action */ /* No comment provided by engineer. */ "Error accepting contact request" = "Fehler beim Annehmen der Kontaktanfrage"; +/* alert title */ +"Error accepting member" = "Fehler beim Übernehmen des Mitglieds"; + /* No comment provided by engineer. */ "Error adding member(s)" = "Fehler beim Hinzufügen von Mitgliedern"; @@ -2173,6 +2218,9 @@ chat item action */ /* No comment provided by engineer. */ "Error deleting chat database" = "Fehler beim Löschen der Chat-Datenbank"; +/* alert title */ +"Error deleting chat with member" = "Fehler beim Löschen des Chats mit dem Mitglied"; + /* No comment provided by engineer. */ "Error deleting chat!" = "Fehler beim Löschen des Chats!"; @@ -2610,6 +2658,9 @@ snd error text */ /* No comment provided by engineer. */ "Group invitation is no longer valid, it was removed by sender." = "Die Gruppeneinladung ist nicht mehr gültig, da sie vom Absender entfernt wurde."; +/* No comment provided by engineer. */ +"group is deleted" = "Gruppe wird gelöscht"; + /* No comment provided by engineer. */ "Group link" = "Gruppen-Link"; @@ -3138,9 +3189,15 @@ snd error text */ /* profile update event chat item */ "member %@ changed to %@" = "Der Mitgliedsname von %1$@ wurde auf %2$@ geändert"; +/* No comment provided by engineer. */ +"Member admission" = "Aufnahme von Mitgliedern"; + /* rcv group event chat item */ "member connected" = "ist der Gruppe beigetreten"; +/* No comment provided by engineer. */ +"member has old version" = "Das Mitglied hat eine alte App-Version"; + /* item status text */ "Member inactive" = "Mitglied inaktiv"; @@ -3162,6 +3219,9 @@ snd error text */ /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "Das Mitglied wird aus der Gruppe entfernt. Dies kann nicht rückgängig gemacht werden!"; +/* alert message */ +"Member will join the group, accept member?" = "Ein Mitglied wird der Gruppe beitreten. Übernehmen?"; + /* No comment provided by engineer. */ "Members can add message reactions." = "Gruppenmitglieder können eine Reaktion auf Nachrichten geben."; @@ -3432,6 +3492,9 @@ snd error text */ /* No comment provided by engineer. */ "New member role" = "Neue Mitgliedsrolle"; +/* rcv group event chat item */ +"New member wants to join the group." = "Ein neues Mitglied will der Gruppe beitreten."; + /* notification */ "new message" = "Neue Nachricht"; @@ -3471,6 +3534,9 @@ snd error text */ /* No comment provided by engineer. */ "No chats in list %@" = "Keine Chats in der Liste %@"; +/* No comment provided by engineer. */ +"No chats with members" = "Keine Chats mit Mitgliedern"; + /* No comment provided by engineer. */ "No contacts selected" = "Keine Kontakte ausgewählt"; @@ -3555,6 +3621,9 @@ snd error text */ /* No comment provided by engineer. */ "Not compatible!" = "Nicht kompatibel!"; +/* No comment provided by engineer. */ +"not synchronized" = "Nicht synchronisiert"; + /* No comment provided by engineer. */ "Notes" = "Anmerkungen"; @@ -3802,6 +3871,9 @@ alert button */ /* No comment provided by engineer. */ "pending approval" = "ausstehende Genehmigung"; +/* No comment provided by engineer. */ +"pending review" = "Ausstehende Überprüfung"; + /* No comment provided by engineer. */ "Periodic" = "Periodisch"; @@ -3871,6 +3943,9 @@ alert button */ /* token info */ "Please try to disable and re-enable notfications." = "Bitte versuchen Sie, die Benachrichtigungen zu deaktivieren und wieder zu aktivieren."; +/* snd group event chat item */ +"Please wait for group moderators to review your request to join the group." = "Bitte warten Sie auf die Überprüfung Ihrer Anfrage durch die Gruppen-Moderatoren, um der Gruppe beitreten zu können."; + /* token info */ "Please wait for token activation to complete." = "Bitte warten Sie, bis die Token-Aktivierung abgeschlossen ist."; @@ -4151,6 +4226,9 @@ swipe action */ /* No comment provided by engineer. */ "Reject contact request" = "Kontaktanfrage ablehnen"; +/* alert title */ +"Reject member?" = "Mitglied ablehnen?"; + /* No comment provided by engineer. */ "rejected" = "abgelehnt"; @@ -4175,7 +4253,7 @@ swipe action */ /* No comment provided by engineer. */ "Remove member" = "Mitglied entfernen"; -/* alert title */ +/* No comment provided by engineer. */ "Remove member?" = "Das Mitglied entfernen?"; /* No comment provided by engineer. */ @@ -4190,6 +4268,9 @@ swipe action */ /* profile update event chat item */ "removed contact address" = "Die Kontaktadresse wurde entfernt"; +/* No comment provided by engineer. */ +"removed from group" = "Von der Gruppe entfernt"; + /* profile update event chat item */ "removed profile picture" = "Das Profil-Bild wurde entfernt"; @@ -4238,6 +4319,9 @@ swipe action */ /* No comment provided by engineer. */ "Report reason?" = "Grund der Meldung?"; +/* alert title */ +"Report sent to moderators" = "Meldung wurde an die Moderatoren gesendet"; + /* report reason */ "Report spam: only group moderators will see it." = "Spam melden: Nur Gruppenmoderatoren werden es sehen."; @@ -4253,6 +4337,9 @@ swipe action */ /* No comment provided by engineer. */ "Reports" = "Meldungen"; +/* No comment provided by engineer. */ +"request to join rejected" = "Beitrittsanfrage abgelehnt"; + /* chat list item title */ "requested to connect" = "Zur Verbindung aufgefordert"; @@ -4307,9 +4394,21 @@ swipe action */ /* chat item action */ "Reveal" = "Aufdecken"; +/* No comment provided by engineer. */ +"review" = "Überprüfung"; + /* No comment provided by engineer. */ "Review conditions" = "Nutzungsbedingungen einsehen"; +/* admission stage */ +"Review members" = "Überprüfung der Mitglieder"; + +/* admission stage description */ +"Review members before admitting (\"knocking\")." = "Überprüfung der Mitglieder vor der Aufnahme (\"Anklopfen\")."; + +/* No comment provided by engineer. */ +"reviewed by admins" = "Von Administratoren überprüft"; + /* No comment provided by engineer. */ "Revoke" = "Widerrufen"; @@ -4338,6 +4437,9 @@ chat item action */ /* alert button */ "Save (and notify contacts)" = "Speichern (und Kontakte benachrichtigen)"; +/* alert title */ +"Save admission settings?" = "Speichern der Aufnahme-Einstellungen?"; + /* alert button */ "Save and notify contact" = "Speichern und Kontakt benachrichtigen"; @@ -4674,6 +4776,9 @@ chat item action */ /* No comment provided by engineer. */ "Set it instead of system authentication." = "Anstelle der System-Authentifizierung festlegen."; +/* No comment provided by engineer. */ +"Set member admission" = "Aufnahme von Mitgliedern festlegen"; + /* No comment provided by engineer. */ "Set message expiration in chats." = "Verfallsdatum von Nachrichten in Chats festlegen."; @@ -5714,6 +5819,9 @@ report reason */ /* No comment provided by engineer. */ "You accepted connection" = "Sie haben die Verbindung akzeptiert"; +/* snd group event chat item */ +"you accepted this member" = "Sie haben dieses Mitglied übernommen"; + /* No comment provided by engineer. */ "You allow" = "Sie erlauben"; @@ -5825,6 +5933,9 @@ report reason */ /* alert message */ "You can view invitation link again in connection details." = "Den Einladungslink können Sie in den Details der Verbindung nochmals sehen."; +/* alert message */ +"You can view your reports in Chat with admins." = "Sie können Ihre Meldungen im Chat mit den Administratoren sehen."; + /* No comment provided by engineer. */ "You can't send messages!" = "Sie können keine Nachrichten versenden!"; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index 9ae294f6ef..a8782c401f 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -4172,7 +4172,7 @@ swipe action */ /* No comment provided by engineer. */ "Remove member" = "Expulsar miembro"; -/* alert title */ +/* No comment provided by engineer. */ "Remove member?" = "¿Expulsar miembro?"; /* No comment provided by engineer. */ diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings index 3aa3bdbc42..8e489f7a71 100644 --- a/apps/ios/fi.lproj/Localizable.strings +++ b/apps/ios/fi.lproj/Localizable.strings @@ -2409,7 +2409,7 @@ swipe action */ /* No comment provided by engineer. */ "Remove member" = "Poista jäsen"; -/* alert title */ +/* No comment provided by engineer. */ "Remove member?" = "Poista jäsen?"; /* No comment provided by engineer. */ diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index 55a76aaa37..9b570a5ae9 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -4016,7 +4016,7 @@ swipe action */ /* No comment provided by engineer. */ "Remove member" = "Retirer le membre"; -/* alert title */ +/* No comment provided by engineer. */ "Remove member?" = "Retirer ce membre ?"; /* No comment provided by engineer. */ diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index 996484aea0..f36b35efc2 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -345,6 +345,12 @@ accept incoming call via notification swipe action */ "Accept" = "Accetta"; +/* alert action */ +"Accept as member" = "Accetta come membro"; + +/* alert action */ +"Accept as observer" = "Accetta come osservatore"; + /* No comment provided by engineer. */ "Accept conditions" = "Accetta le condizioni"; @@ -358,6 +364,12 @@ swipe action */ swipe action */ "Accept incognito" = "Accetta in incognito"; +/* alert title */ +"Accept member" = "Accetta membro"; + +/* rcv group event chat item */ +"accepted %@" = "%@ accettato"; + /* call status */ "accepted call" = "chiamata accettata"; @@ -367,6 +379,9 @@ swipe action */ /* chat list item title */ "accepted invitation" = "invito accettato"; +/* rcv group event chat item */ +"accepted you" = "ti ha accettato/a"; + /* No comment provided by engineer. */ "Acknowledged" = "Riconosciuto"; @@ -463,6 +478,9 @@ swipe action */ /* chat item text */ "agreeing encryption…" = "concordando la crittografia…"; +/* member criteria value */ +"all" = "tutti"; + /* No comment provided by engineer. */ "All" = "Tutte"; @@ -905,6 +923,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Can't message member" = "Impossibile inviare un messaggio al membro"; +/* No comment provided by engineer. */ +"can't send messages" = "impossibile inviare messaggi"; + /* alert action alert button */ "Cancel" = "Annulla"; @@ -1042,9 +1063,18 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "La chat verrà eliminata solo per te, non è reversibile!"; +/* chat toolbar */ +"Chat with admins" = "Chat con amministratori"; + +/* No comment provided by engineer. */ +"Chat with member" = "Chatta con il membro"; + /* No comment provided by engineer. */ "Chats" = "Chat"; +/* No comment provided by engineer. */ +"Chats with members" = "Chat con membri"; + /* No comment provided by engineer. */ "Check messages every 20 min." = "Controlla i messaggi ogni 20 min."; @@ -1333,9 +1363,15 @@ set passcode view */ /* No comment provided by engineer. */ "Contact already exists" = "Il contatto esiste già"; +/* No comment provided by engineer. */ +"contact deleted" = "contatto eliminato"; + /* No comment provided by engineer. */ "Contact deleted!" = "Contatto eliminato!"; +/* No comment provided by engineer. */ +"contact disabled" = "contatto disattivato"; + /* No comment provided by engineer. */ "contact has e2e encryption" = "il contatto ha la crittografia e2e"; @@ -1354,6 +1390,9 @@ set passcode view */ /* No comment provided by engineer. */ "Contact name" = "Nome del contatto"; +/* No comment provided by engineer. */ +"contact not ready" = "contatto non pronto"; + /* No comment provided by engineer. */ "Contact preferences" = "Preferenze del contatto"; @@ -1602,6 +1641,9 @@ swipe action */ /* No comment provided by engineer. */ "Delete chat profile?" = "Eliminare il profilo di chat?"; +/* alert title */ +"Delete chat with member?" = "Eliminare la chat con il membro?"; + /* No comment provided by engineer. */ "Delete chat?" = "Eliminare la chat?"; @@ -2116,6 +2158,9 @@ chat item action */ /* No comment provided by engineer. */ "Error accepting contact request" = "Errore nell'accettazione della richiesta di contatto"; +/* alert title */ +"Error accepting member" = "Errore di accettazione del membro"; + /* No comment provided by engineer. */ "Error adding member(s)" = "Errore di aggiunta membro/i"; @@ -2173,6 +2218,9 @@ chat item action */ /* No comment provided by engineer. */ "Error deleting chat database" = "Errore nell'eliminazione del database della chat"; +/* alert title */ +"Error deleting chat with member" = "Errore di eliminazione della chat con il membro"; + /* No comment provided by engineer. */ "Error deleting chat!" = "Errore nell'eliminazione della chat!"; @@ -2610,6 +2658,9 @@ snd error text */ /* No comment provided by engineer. */ "Group invitation is no longer valid, it was removed by sender." = "L'invito al gruppo non è più valido, è stato rimosso dal mittente."; +/* No comment provided by engineer. */ +"group is deleted" = "il gruppo è eliminato"; + /* No comment provided by engineer. */ "Group link" = "Link del gruppo"; @@ -3138,9 +3189,15 @@ snd error text */ /* profile update event chat item */ "member %@ changed to %@" = "il membro %1$@ è diventato %2$@"; +/* No comment provided by engineer. */ +"Member admission" = "Ammissione del membro"; + /* rcv group event chat item */ "member connected" = "si è connesso/a"; +/* No comment provided by engineer. */ +"member has old version" = "il membro ha una versione vecchia"; + /* item status text */ "Member inactive" = "Membro inattivo"; @@ -3162,6 +3219,9 @@ snd error text */ /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "Il membro verrà rimosso dal gruppo, non è reversibile!"; +/* alert message */ +"Member will join the group, accept member?" = "Il membro entrerà nel gruppo, accettarlo?"; + /* No comment provided by engineer. */ "Members can add message reactions." = "I membri del gruppo possono aggiungere reazioni ai messaggi."; @@ -3432,6 +3492,9 @@ snd error text */ /* No comment provided by engineer. */ "New member role" = "Nuovo ruolo del membro"; +/* rcv group event chat item */ +"New member wants to join the group." = "Un nuovo membro vuole entrare nel gruppo."; + /* notification */ "new message" = "messaggio nuovo"; @@ -3471,6 +3534,9 @@ snd error text */ /* No comment provided by engineer. */ "No chats in list %@" = "Nessuna chat nell'elenco %@"; +/* No comment provided by engineer. */ +"No chats with members" = "Nessuna chat con membri"; + /* No comment provided by engineer. */ "No contacts selected" = "Nessun contatto selezionato"; @@ -3555,6 +3621,9 @@ snd error text */ /* No comment provided by engineer. */ "Not compatible!" = "Non compatibile!"; +/* No comment provided by engineer. */ +"not synchronized" = "non sincronizzato"; + /* No comment provided by engineer. */ "Notes" = "Note"; @@ -3802,6 +3871,9 @@ alert button */ /* No comment provided by engineer. */ "pending approval" = "in attesa di approvazione"; +/* No comment provided by engineer. */ +"pending review" = "in attesa di revisione"; + /* No comment provided by engineer. */ "Periodic" = "Periodicamente"; @@ -3871,6 +3943,9 @@ alert button */ /* token info */ "Please try to disable and re-enable notfications." = "Prova a disattivare e riattivare le notifiche."; +/* snd group event chat item */ +"Please wait for group moderators to review your request to join the group." = "Attendi che i moderatori del gruppo revisionino la tua richiesta di entrare nel gruppo."; + /* token info */ "Please wait for token activation to complete." = "Attendi il completamento dell'attivazione del token."; @@ -4151,6 +4226,9 @@ swipe action */ /* No comment provided by engineer. */ "Reject contact request" = "Rifiuta la richiesta di contatto"; +/* alert title */ +"Reject member?" = "Rifiutare il membro?"; + /* No comment provided by engineer. */ "rejected" = "rifiutato"; @@ -4175,7 +4253,7 @@ swipe action */ /* No comment provided by engineer. */ "Remove member" = "Rimuovi membro"; -/* alert title */ +/* No comment provided by engineer. */ "Remove member?" = "Rimuovere il membro?"; /* No comment provided by engineer. */ @@ -4190,6 +4268,9 @@ swipe action */ /* profile update event chat item */ "removed contact address" = "indirizzo di contatto rimosso"; +/* No comment provided by engineer. */ +"removed from group" = "rimosso dal gruppo"; + /* profile update event chat item */ "removed profile picture" = "immagine del profilo rimossa"; @@ -4238,6 +4319,9 @@ swipe action */ /* No comment provided by engineer. */ "Report reason?" = "Motivo della segnalazione?"; +/* alert title */ +"Report sent to moderators" = "Segnalazione inviata ai moderatori"; + /* report reason */ "Report spam: only group moderators will see it." = "Segnala spam: solo i moderatori del gruppo lo vedranno."; @@ -4253,6 +4337,9 @@ swipe action */ /* No comment provided by engineer. */ "Reports" = "Segnalazioni"; +/* No comment provided by engineer. */ +"request to join rejected" = "richiesta di entrare rifiutata"; + /* chat list item title */ "requested to connect" = "richiesto di connettersi"; @@ -4307,9 +4394,21 @@ swipe action */ /* chat item action */ "Reveal" = "Rivela"; +/* No comment provided by engineer. */ +"review" = "revisiona"; + /* No comment provided by engineer. */ "Review conditions" = "Leggi le condizioni"; +/* admission stage */ +"Review members" = "Revisiona i membri"; + +/* admission stage description */ +"Review members before admitting (\"knocking\")." = "Revisiona i membri prima di ammetterli (\"bussare\")."; + +/* No comment provided by engineer. */ +"reviewed by admins" = "revisionato dagli amministratori"; + /* No comment provided by engineer. */ "Revoke" = "Revoca"; @@ -4338,6 +4437,9 @@ chat item action */ /* alert button */ "Save (and notify contacts)" = "Salva (e avvisa i contatti)"; +/* alert title */ +"Save admission settings?" = "Salvare le impostazioni di ammissione?"; + /* alert button */ "Save and notify contact" = "Salva e avvisa il contatto"; @@ -4674,6 +4776,9 @@ chat item action */ /* No comment provided by engineer. */ "Set it instead of system authentication." = "Impostalo al posto dell'autenticazione di sistema."; +/* No comment provided by engineer. */ +"Set member admission" = "Imposta l'ammissione del membro"; + /* No comment provided by engineer. */ "Set message expiration in chats." = "Imposta la scadenza dei messaggi nelle chat."; @@ -5714,6 +5819,9 @@ report reason */ /* No comment provided by engineer. */ "You accepted connection" = "Hai accettato la connessione"; +/* snd group event chat item */ +"you accepted this member" = "hai accettato questo membro"; + /* No comment provided by engineer. */ "You allow" = "Lo consenti"; @@ -5825,6 +5933,9 @@ report reason */ /* alert message */ "You can view invitation link again in connection details." = "Puoi vedere di nuovo il link di invito nei dettagli di connessione."; +/* alert message */ +"You can view your reports in Chat with admins." = "Puoi vedere le tue segnalazioni nella chat con gli amministratori."; + /* No comment provided by engineer. */ "You can't send messages!" = "Non puoi inviare messaggi!"; diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index 8e4b071f88..481c21eb8b 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -2628,7 +2628,7 @@ swipe action */ /* No comment provided by engineer. */ "Remove member" = "メンバーを除名する"; -/* alert title */ +/* No comment provided by engineer. */ "Remove member?" = "メンバーを除名しますか?"; /* No comment provided by engineer. */ diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index f0ca51fffb..5caea12ee2 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -4169,7 +4169,7 @@ swipe action */ /* No comment provided by engineer. */ "Remove member" = "Lid verwijderen"; -/* alert title */ +/* No comment provided by engineer. */ "Remove member?" = "Lid verwijderen?"; /* No comment provided by engineer. */ diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index e3582c7647..e3e860e329 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -3746,7 +3746,7 @@ swipe action */ /* No comment provided by engineer. */ "Remove member" = "Usuń członka"; -/* alert title */ +/* No comment provided by engineer. */ "Remove member?" = "Usunąć członka?"; /* No comment provided by engineer. */ diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index c14c7a7e9f..759a4c79f4 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -164,7 +164,7 @@ "%d file(s) were not downloaded." = "%d файлов не было загружено."; /* time interval */ -"%d hours" = "%d ч."; +"%d hours" = "%d час."; /* alert title */ "%d messages not forwarded" = "%d сообщений не переслано"; @@ -345,6 +345,12 @@ accept incoming call via notification swipe action */ "Accept" = "Принять"; +/* alert action */ +"Accept as member" = "Принять в группу"; + +/* alert action */ +"Accept as observer" = "Принять как читателя"; + /* No comment provided by engineer. */ "Accept conditions" = "Принять условия"; @@ -358,6 +364,12 @@ swipe action */ swipe action */ "Accept incognito" = "Принять инкогнито"; +/* alert title */ +"Accept member" = "Принять члена"; + +/* rcv group event chat item */ +"accepted %@" = "принят %@"; + /* call status */ "accepted call" = "принятый звонок"; @@ -367,6 +379,9 @@ swipe action */ /* chat list item title */ "accepted invitation" = "принятое приглашение"; +/* rcv group event chat item */ +"accepted you" = "Вы приняты"; + /* No comment provided by engineer. */ "Acknowledged" = "Подтверждено"; @@ -463,6 +478,9 @@ swipe action */ /* chat item text */ "agreeing encryption…" = "шифрование согласовывается…"; +/* member criteria value */ +"all" = "все"; + /* No comment provided by engineer. */ "All" = "Все"; @@ -484,6 +502,9 @@ swipe action */ /* No comment provided by engineer. */ "All group members will remain connected." = "Все члены группы останутся соединены."; +/* feature role */ +"all members" = "все члены"; + /* No comment provided by engineer. */ "All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "Все сообщения и файлы отправляются с **end-to-end шифрованием**, с постквантовой безопасностью в прямых разговорах."; @@ -502,6 +523,9 @@ swipe action */ /* No comment provided by engineer. */ "All reports will be archived for you." = "Все сообщения о нарушениях будут заархивированы для вас."; +/* No comment provided by engineer. */ +"All servers" = "Все серверы"; + /* No comment provided by engineer. */ "All your contacts will remain connected." = "Все контакты, которые соединились через этот адрес, сохранятся."; @@ -899,6 +923,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Can't message member" = "Не удаётся отправить сообщение члену группы"; +/* No comment provided by engineer. */ +"can't send messages" = "нельзя отправлять"; + /* alert action alert button */ "Cancel" = "Отменить"; @@ -1036,9 +1063,18 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "Разговор будет удален для Вас - это действие нельзя отменить!"; +/* chat toolbar */ +"Chat with admins" = "Чат с админами"; + +/* No comment provided by engineer. */ +"Chat with member" = "Чат с членом группы"; + /* No comment provided by engineer. */ "Chats" = "Чаты"; +/* No comment provided by engineer. */ +"Chats with members" = "Чаты с членами группы"; + /* No comment provided by engineer. */ "Check messages every 20 min." = "Проверять сообщения каждые 20 минут."; @@ -1327,9 +1363,15 @@ set passcode view */ /* No comment provided by engineer. */ "Contact already exists" = "Существующий контакт"; +/* No comment provided by engineer. */ +"contact deleted" = "контакт удален"; + /* No comment provided by engineer. */ "Contact deleted!" = "Контакт удален!"; +/* No comment provided by engineer. */ +"contact disabled" = "контакт выключен"; + /* No comment provided by engineer. */ "contact has e2e encryption" = "у контакта есть e2e шифрование"; @@ -1348,6 +1390,9 @@ set passcode view */ /* No comment provided by engineer. */ "Contact name" = "Имена контактов"; +/* No comment provided by engineer. */ +"contact not ready" = "контакт не готов"; + /* No comment provided by engineer. */ "Contact preferences" = "Предпочтения контакта"; @@ -1596,6 +1641,9 @@ swipe action */ /* No comment provided by engineer. */ "Delete chat profile?" = "Удалить профиль?"; +/* alert title */ +"Delete chat with member?" = "Удалить чат с членом группы?"; + /* No comment provided by engineer. */ "Delete chat?" = "Удалить разговор?"; @@ -1782,6 +1830,9 @@ swipe action */ /* No comment provided by engineer. */ "Direct messages between members are prohibited in this chat." = "Личные сообщения запрещены в этой группе."; +/* No comment provided by engineer. */ +"Direct messages between members are prohibited." = "Прямые сообщения между членами запрещены."; + /* No comment provided by engineer. */ "Disable (keep overrides)" = "Выключить (кроме исключений)"; @@ -1836,6 +1887,9 @@ swipe action */ /* No comment provided by engineer. */ "Do it later" = "Отложить"; +/* No comment provided by engineer. */ +"Do not send history to new members." = "Не отправлять историю новым членам."; + /* No comment provided by engineer. */ "Do NOT send messages directly, even if your or destination server does not support private routing." = "Не отправлять сообщения напрямую, даже если сервер получателя не поддерживает конфиденциальную доставку."; @@ -1933,6 +1987,9 @@ chat item action */ /* No comment provided by engineer. */ "Enable camera access" = "Включить доступ к камере"; +/* No comment provided by engineer. */ +"Enable Flux in Network & servers settings for better metadata privacy." = "Включите Flux в настройках Сеть и серверы для лучшей конфиденциальности метаданных."; + /* No comment provided by engineer. */ "Enable for all" = "Включить для всех"; @@ -2101,6 +2158,12 @@ chat item action */ /* No comment provided by engineer. */ "Error accepting contact request" = "Ошибка при принятии запроса на соединение"; +/* alert title */ +"Error accepting member" = "Ошибка вступления члена группы"; + +/* No comment provided by engineer. */ +"Error adding member(s)" = "Ошибка при добавлении членов группы"; + /* alert title */ "Error adding server" = "Ошибка добавления сервера"; @@ -2137,6 +2200,9 @@ chat item action */ /* alert title */ "Error creating list" = "Ошибка создания списка"; +/* No comment provided by engineer. */ +"Error creating member contact" = "Ошибка при создании контакта"; + /* No comment provided by engineer. */ "Error creating message" = "Ошибка создания сообщения"; @@ -2152,6 +2218,9 @@ chat item action */ /* No comment provided by engineer. */ "Error deleting chat database" = "Ошибка при удалении данных чата"; +/* alert title */ +"Error deleting chat with member" = "Ошибка при удалении чата с членом группы"; + /* No comment provided by engineer. */ "Error deleting chat!" = "Ошибка при удалении чата!"; @@ -2215,6 +2284,9 @@ chat item action */ /* alert title */ "Error registering for notifications" = "Ошибка регистрации для уведомлений"; +/* alert title */ +"Error removing member" = "Ошибка при удалении члена группы"; + /* alert title */ "Error reordering lists" = "Ошибка сортировки списков"; @@ -2251,6 +2323,9 @@ chat item action */ /* No comment provided by engineer. */ "Error sending email" = "Ошибка отправки email"; +/* No comment provided by engineer. */ +"Error sending member contact invitation" = "Ошибка при отправке приглашения члену"; + /* No comment provided by engineer. */ "Error sending message" = "Ошибка при отправке сообщения"; @@ -2451,6 +2526,9 @@ snd error text */ /* No comment provided by engineer. */ "Fix not supported by contact" = "Починка не поддерживается контактом"; +/* No comment provided by engineer. */ +"Fix not supported by group member" = "Починка не поддерживается членом группы."; + /* No comment provided by engineer. */ "For all moderators" = "Для всех модераторов"; @@ -2529,6 +2607,9 @@ snd error text */ /* No comment provided by engineer. */ "Full name (optional)" = "Полное имя (не обязательно)"; +/* No comment provided by engineer. */ +"Fully decentralized – visible only to members." = "Группа полностью децентрализована – она видна только членам."; + /* No comment provided by engineer. */ "Fully re-implemented - work in background!" = "Полностью обновлены - работают в фоне!"; @@ -2577,6 +2658,9 @@ snd error text */ /* No comment provided by engineer. */ "Group invitation is no longer valid, it was removed by sender." = "Приглашение в группу больше не действительно, оно было удалено отправителем."; +/* No comment provided by engineer. */ +"group is deleted" = "группа удалена"; + /* No comment provided by engineer. */ "Group link" = "Ссылка группы"; @@ -2595,12 +2679,18 @@ snd error text */ /* No comment provided by engineer. */ "Group profile" = "Профиль группы"; +/* No comment provided by engineer. */ +"Group profile is stored on members' devices, not on the servers." = "Профиль группы хранится на устройствах членов, а не на серверах."; + /* snd group event chat item */ "group profile updated" = "профиль группы обновлен"; /* No comment provided by engineer. */ "Group welcome message" = "Приветственное сообщение группы"; +/* No comment provided by engineer. */ +"Group will be deleted for all members - this cannot be undone!" = "Группа будет удалена для всех членов - это действие нельзя отменить!"; + /* No comment provided by engineer. */ "Group will be deleted for you - this cannot be undone!" = "Группа будет удалена для Вас - это действие нельзя отменить!"; @@ -2637,6 +2727,9 @@ snd error text */ /* No comment provided by engineer. */ "History" = "История"; +/* No comment provided by engineer. */ +"History is not sent to new members." = "История не отправляется новым членам."; + /* time unit */ "hours" = "часов"; @@ -2871,6 +2964,9 @@ snd error text */ /* No comment provided by engineer. */ "Invite friends" = "Пригласить друзей"; +/* No comment provided by engineer. */ +"Invite members" = "Пригласить членов группы"; + /* No comment provided by engineer. */ "Invite to chat" = "Пригласить в разговор"; @@ -3084,15 +3180,75 @@ snd error text */ /* blur media */ "Medium" = "Среднее"; +/* member role */ +"member" = "член группы"; + +/* No comment provided by engineer. */ +"Member" = "Член группы"; + +/* profile update event chat item */ +"member %@ changed to %@" = "член %1$@ изменился на %2$@"; + +/* No comment provided by engineer. */ +"Member admission" = "Приём членов в группу"; + /* rcv group event chat item */ "member connected" = "соединен(а)"; +/* No comment provided by engineer. */ +"member has old version" = "член имеет старую версию"; + +/* item status text */ +"Member inactive" = "Член неактивен"; + /* chat feature */ "Member reports" = "Сообщения о нарушениях"; /* No comment provided by engineer. */ "Member role will be changed to \"%@\". All chat members will be notified." = "Роль участника будет изменена на \"%@\". Все участники разговора получат уведомление."; +/* No comment provided by engineer. */ +"Member role will be changed to \"%@\". All group members will be notified." = "Роль члена будет изменена на \"%@\". Все члены группы получат уведомление."; + +/* No comment provided by engineer. */ +"Member role will be changed to \"%@\". The member will receive a new invitation." = "Роль члена будет изменена на \"%@\". Будет отправлено новое приглашение."; + +/* No comment provided by engineer. */ +"Member will be removed from chat - this cannot be undone!" = "Член будет удален из разговора - это действие нельзя отменить!"; + +/* No comment provided by engineer. */ +"Member will be removed from group - this cannot be undone!" = "Член группы будет удален - это действие нельзя отменить!"; + +/* alert message */ +"Member will join the group, accept member?" = "Участник хочет присоединиться к группе. Принять?"; + +/* No comment provided by engineer. */ +"Members can add message reactions." = "Члены могут добавлять реакции на сообщения."; + +/* No comment provided by engineer. */ +"Members can irreversibly delete sent messages. (24 hours)" = "Члены могут необратимо удалять отправленные сообщения. (24 часа)"; + +/* No comment provided by engineer. */ +"Members can report messsages to moderators." = "Члены группы могут пожаловаться модераторам."; + +/* No comment provided by engineer. */ +"Members can send direct messages." = "Члены могут посылать прямые сообщения."; + +/* No comment provided by engineer. */ +"Members can send disappearing messages." = "Члены могут посылать исчезающие сообщения."; + +/* No comment provided by engineer. */ +"Members can send files and media." = "Члены могут слать файлы и медиа."; + +/* No comment provided by engineer. */ +"Members can send SimpleX links." = "Члены могут отправлять ссылки SimpleX."; + +/* No comment provided by engineer. */ +"Members can send voice messages." = "Члены могут отправлять голосовые сообщения."; + +/* No comment provided by engineer. */ +"Mention members 👋" = "Упоминайте участников 👋"; + /* No comment provided by engineer. */ "Menus" = "Меню"; @@ -3114,6 +3270,9 @@ snd error text */ /* item status text */ "Message forwarded" = "Сообщение переслано"; +/* item status description */ +"Message may be delivered later if member becomes active." = "Сообщение может быть доставлено позже, если член группы станет активным."; + /* No comment provided by engineer. */ "Message queue info" = "Информация об очереди сообщений"; @@ -3330,6 +3489,12 @@ snd error text */ /* No comment provided by engineer. */ "New media options" = "Новые медиа-опции"; +/* No comment provided by engineer. */ +"New member role" = "Роль члена группы"; + +/* rcv group event chat item */ +"New member wants to join the group." = "Новый участник хочет присоединиться к группе."; + /* notification */ "new message" = "новое сообщение"; @@ -3369,6 +3534,9 @@ snd error text */ /* No comment provided by engineer. */ "No chats in list %@" = "Нет чатов в списке %@"; +/* No comment provided by engineer. */ +"No chats with members" = "Нет чатов с членами группы"; + /* No comment provided by engineer. */ "No contacts selected" = "Контакты не выбраны"; @@ -3453,6 +3621,9 @@ snd error text */ /* No comment provided by engineer. */ "Not compatible!" = "Несовместимая версия!"; +/* No comment provided by engineer. */ +"not synchronized" = "не синхронизирован"; + /* No comment provided by engineer. */ "Notes" = "Заметки"; @@ -3477,6 +3648,9 @@ snd error text */ /* alert title */ "Notifications status" = "Статус уведомлений"; +/* No comment provided by engineer. */ +"Now admins can:\n- delete members' messages.\n- disable members (\"observer\" role)" = "Теперь админы могут:\n- удалять сообщения членов.\n- приостанавливать членов (роль наблюдатель)"; + /* member role */ "observer" = "читатель"; @@ -3592,6 +3766,9 @@ alert button */ /* No comment provided by engineer. */ "Open group" = "Открыть группу"; +/* alert title */ +"Open link?" = "Открыть ссылку?"; + /* authentication reason */ "Open migration to another device" = "Открытие миграции на другое устройство"; @@ -3667,6 +3844,9 @@ alert button */ /* No comment provided by engineer. */ "Password to show" = "Пароль чтобы раскрыть"; +/* past/unknown group member */ +"Past member %@" = "Бывший член %@"; + /* No comment provided by engineer. */ "Paste desktop address" = "Вставить адрес компьютера"; @@ -3691,6 +3871,9 @@ alert button */ /* No comment provided by engineer. */ "pending approval" = "ожидает утверждения"; +/* No comment provided by engineer. */ +"pending review" = "ожидает одобрения"; + /* No comment provided by engineer. */ "Periodic" = "Периодически"; @@ -3760,6 +3943,9 @@ alert button */ /* token info */ "Please try to disable and re-enable notfications." = "Попробуйте выключить и снова включить уведомления."; +/* snd group event chat item */ +"Please wait for group moderators to review your request to join the group." = "Пожалуйста, подождите, пока модераторы группы рассмотрят ваш запрос на вступление."; + /* token info */ "Please wait for token activation to complete." = "Пожалуйста, дождитесь завершения активации токена."; @@ -3859,6 +4045,9 @@ alert button */ /* No comment provided by engineer. */ "Prohibit reporting messages to moderators." = "Запретить жаловаться модераторам группы."; +/* No comment provided by engineer. */ +"Prohibit sending direct messages to members." = "Запретить посылать прямые сообщения членам группы."; + /* No comment provided by engineer. */ "Prohibit sending disappearing messages." = "Запретить посылать исчезающие сообщения."; @@ -4037,6 +4226,9 @@ swipe action */ /* No comment provided by engineer. */ "Reject contact request" = "Отклонить запрос"; +/* alert title */ +"Reject member?" = "Отклонить участника?"; + /* No comment provided by engineer. */ "rejected" = "отклонён"; @@ -4058,6 +4250,12 @@ swipe action */ /* No comment provided by engineer. */ "Remove image" = "Удалить изображение"; +/* No comment provided by engineer. */ +"Remove member" = "Удалить члена группы"; + +/* No comment provided by engineer. */ +"Remove member?" = "Удалить члена группы?"; + /* No comment provided by engineer. */ "Remove passphrase from keychain?" = "Удалить пароль из Keychain?"; @@ -4070,6 +4268,9 @@ swipe action */ /* profile update event chat item */ "removed contact address" = "удалён адрес контакта"; +/* No comment provided by engineer. */ +"removed from group" = "удален из группы"; + /* profile update event chat item */ "removed profile picture" = "удалена картинка профиля"; @@ -4118,6 +4319,9 @@ swipe action */ /* No comment provided by engineer. */ "Report reason?" = "Причина сообщения?"; +/* alert title */ +"Report sent to moderators" = "Жалоба отправлена модераторам"; + /* report reason */ "Report spam: only group moderators will see it." = "Пожаловаться на спам: увидят только модераторы группы."; @@ -4133,6 +4337,9 @@ swipe action */ /* No comment provided by engineer. */ "Reports" = "Сообщения о нарушениях"; +/* No comment provided by engineer. */ +"request to join rejected" = "запрос на вступление отклонён"; + /* chat list item title */ "requested to connect" = "запрошено соединение"; @@ -4187,9 +4394,21 @@ swipe action */ /* chat item action */ "Reveal" = "Показать"; +/* No comment provided by engineer. */ +"review" = "рассмотрение"; + /* No comment provided by engineer. */ "Review conditions" = "Посмотреть условия"; +/* admission stage */ +"Review members" = "Одобрять членов"; + +/* admission stage description */ +"Review members before admitting (\"knocking\")." = "Одобрять членов для вступления в группу."; + +/* No comment provided by engineer. */ +"reviewed by admins" = "одобрен админами"; + /* No comment provided by engineer. */ "Revoke" = "Отозвать"; @@ -4218,9 +4437,15 @@ chat item action */ /* alert button */ "Save (and notify contacts)" = "Сохранить (и уведомить контакты)"; +/* alert title */ +"Save admission settings?" = "Сохранить настройки вступления?"; + /* alert button */ "Save and notify contact" = "Сохранить и уведомить контакт"; +/* No comment provided by engineer. */ +"Save and notify group members" = "Сохранить и уведомить членов группы"; + /* No comment provided by engineer. */ "Save and reconnect" = "Сохранить и переподключиться"; @@ -4413,6 +4638,9 @@ chat item action */ /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "Отправьте из галереи или из дополнительных клавиатур."; +/* No comment provided by engineer. */ +"Send up to 100 last messages to new members." = "Отправить до 100 последних сообщений новым членам."; + /* alert message */ "Sender cancelled file transfer." = "Отправитель отменил передачу файла."; @@ -4548,6 +4776,9 @@ chat item action */ /* No comment provided by engineer. */ "Set it instead of system authentication." = "Установите код вместо системной аутентификации."; +/* No comment provided by engineer. */ +"Set member admission" = "Приём членов в группу"; + /* No comment provided by engineer. */ "Set message expiration in chats." = "Установите срок хранения сообщений в чатах."; @@ -4566,6 +4797,9 @@ chat item action */ /* No comment provided by engineer. */ "Set passphrase to export" = "Установите пароль"; +/* No comment provided by engineer. */ +"Set the message shown to new members!" = "Установить сообщение для новых членов группы!"; + /* No comment provided by engineer. */ "Set timeouts for proxy/VPN" = "Установить таймауты для прокси/VPN"; @@ -4618,6 +4852,9 @@ chat item action */ /* No comment provided by engineer. */ "Share with contacts" = "Поделиться с контактами"; +/* No comment provided by engineer. */ +"Short link" = "Короткая ссылка"; + /* No comment provided by engineer. */ "Show → on messages sent via private routing." = "Показать → на сообщениях доставленных конфиденциально."; @@ -4660,6 +4897,9 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX address or 1-time link?" = "Адрес SimpleX или одноразовая ссылка?"; +/* simplex link type */ +"SimpleX channel link" = "SimpleX ссылка канала"; + /* No comment provided by engineer. */ "SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app." = "SimpleX Chat и Flux заключили соглашение добавить серверы под управлением Flux в приложение."; @@ -4955,6 +5195,18 @@ report reason */ /* No comment provided by engineer. */ "The ID of the next message is incorrect (less or equal to the previous).\nIt can happen because of some bug or when the connection is compromised." = "Неправильный ID предыдущего сообщения (меньше или равен предыдущему).\nЭто может произойти из-за ошибки программы, или когда соединение компроментировано."; +/* No comment provided by engineer. */ +"The message will be deleted for all members." = "Сообщение будет удалено для всех членов группы."; + +/* No comment provided by engineer. */ +"The message will be marked as moderated for all members." = "Сообщение будет помечено как удаленное для всех членов группы."; + +/* No comment provided by engineer. */ +"The messages will be deleted for all members." = "Сообщения будут удалены для всех членов группы."; + +/* No comment provided by engineer. */ +"The messages will be marked as moderated for all members." = "Сообщения будут помечены как удаленные для всех членов группы."; + /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Предыдущая версия данных чата не удалена при перемещении, её можно удалить."; @@ -5021,6 +5273,9 @@ report reason */ /* No comment provided by engineer. */ "This display name is invalid. Please choose another name." = "Ошибка имени профиля. Пожалуйста, выберите другое имя."; +/* No comment provided by engineer. */ +"This group has over %lld members, delivery receipts are not sent." = "В этой группе более %lld членов, отчёты о доставке не отправляются."; + /* No comment provided by engineer. */ "This group no longer exists." = "Эта группа больше не существует."; @@ -5030,6 +5285,9 @@ report reason */ /* No comment provided by engineer. */ "This is your own SimpleX address!" = "Это ваш собственный адрес SimpleX!"; +/* No comment provided by engineer. */ +"This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." = "Эта ссылка требует новую версию. Обновите приложение или попросите Ваш контакт прислать совместимую ссылку."; + /* No comment provided by engineer. */ "This link was used with another mobile device, please create a new link on the desktop." = "Эта ссылка была использована на другом мобильном, пожалуйста, создайте новую ссылку на компьютере."; @@ -5141,6 +5399,15 @@ report reason */ /* No comment provided by engineer. */ "Unblock for all" = "Разблокировать для всех"; +/* No comment provided by engineer. */ +"Unblock member" = "Разблокировать члена группы"; + +/* No comment provided by engineer. */ +"Unblock member for all?" = "Разблокировать члена для всех?"; + +/* No comment provided by engineer. */ +"Unblock member?" = "Разблокировать члена группы?"; + /* rcv group event chat item */ "unblocked %@" = "%@ разблокирован"; @@ -5213,6 +5480,12 @@ report reason */ /* swipe action */ "Unread" = "Не прочитано"; +/* No comment provided by engineer. */ +"Unsupported connection link" = "Ссылка не поддерживается"; + +/* No comment provided by engineer. */ +"Up to 100 last messages are sent to new members." = "До 100 последних сообщений отправляются новым членам."; + /* No comment provided by engineer. */ "Update" = "Обновить"; @@ -5303,6 +5576,9 @@ report reason */ /* No comment provided by engineer. */ "Use servers" = "Использовать серверы"; +/* No comment provided by engineer. */ +"Use short links (BETA)" = "Короткие ссылки (БЕТА)"; + /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "Использовать серверы предосталенные SimpleX Chat?"; @@ -5312,6 +5588,9 @@ report reason */ /* No comment provided by engineer. */ "Use TCP port %@ when no port is specified." = "Использовать TCP-порт %@, когда порт не указан."; +/* No comment provided by engineer. */ +"Use TCP port 443 for preset servers only." = "Использовать TCP-порт 443 только для серверов по умолчанию."; + /* No comment provided by engineer. */ "Use the app while in the call." = "Используйте приложение во время звонка."; @@ -5540,6 +5819,9 @@ report reason */ /* No comment provided by engineer. */ "You accepted connection" = "Вы приняли приглашение соединиться"; +/* snd group event chat item */ +"you accepted this member" = "Вы приняли этого члена"; + /* No comment provided by engineer. */ "You allow" = "Вы разрешаете"; @@ -5630,6 +5912,9 @@ report reason */ /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Вы можете установить просмотр уведомлений на экране блокировки в настройках."; +/* No comment provided by engineer. */ +"You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it." = "Вы можете поделиться ссылкой или QR кодом - через них можно присоединиться к группе. Вы сможете удалить ссылку, сохранив членов группы, которые через нее соединились."; + /* No comment provided by engineer. */ "You can share this address with your contacts to let them connect with **%@**." = "Вы можете поделиться этим адресом с Вашими контактами, чтобы они могли соединиться с **%@**."; @@ -5648,6 +5933,9 @@ report reason */ /* alert message */ "You can view invitation link again in connection details." = "Вы можете увидеть ссылку-приглашение снова открыв соединение."; +/* alert message */ +"You can view your reports in Chat with admins." = "Вы можете найти Ваши жалобы в Чате с админами."; + /* No comment provided by engineer. */ "You can't send messages!" = "Вы не можете отправлять сообщения!"; @@ -5684,6 +5972,9 @@ report reason */ /* No comment provided by engineer. */ "You joined this group" = "Вы вступили в эту группу"; +/* No comment provided by engineer. */ +"You joined this group. Connecting to inviting group member." = "Вы вступили в эту группу. Устанавливается соединение с пригласившим членом группы."; + /* snd group event chat item */ "you left" = "Вы покинули группу"; @@ -5738,6 +6029,9 @@ report reason */ /* No comment provided by engineer. */ "You will be required to authenticate when you start or resume the app after 30 seconds in background." = "Вы будете аутентифицированы при запуске и возобновлении приложения, которое было 30 секунд в фоновом режиме."; +/* No comment provided by engineer. */ +"You will connect to all group members." = "Вы соединитесь со всеми членами группы."; + /* No comment provided by engineer. */ "You will still receive calls and notifications from muted profiles when they are active." = "Вы все равно получите звонки и уведомления в профилях без звука, когда они активные."; @@ -5833,4 +6127,3 @@ report reason */ /* No comment provided by engineer. */ "Your SimpleX address" = "Ваш адрес SimpleX"; - diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings index d6e48caf86..a6ef88d0d4 100644 --- a/apps/ios/th.lproj/Localizable.strings +++ b/apps/ios/th.lproj/Localizable.strings @@ -2340,7 +2340,7 @@ swipe action */ /* No comment provided by engineer. */ "Remove member" = "ลบสมาชิกออก"; -/* alert title */ +/* No comment provided by engineer. */ "Remove member?" = "ลบสมาชิกออก?"; /* No comment provided by engineer. */ diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index 2433c9ae58..3d44c895ec 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -3782,7 +3782,7 @@ swipe action */ /* No comment provided by engineer. */ "Remove member" = "Kişiyi sil"; -/* alert title */ +/* No comment provided by engineer. */ "Remove member?" = "Kişi silinsin mi?"; /* No comment provided by engineer. */ diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index 6e6197cdb7..932c29d368 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -3854,7 +3854,7 @@ swipe action */ /* No comment provided by engineer. */ "Remove member" = "Видалити учасника"; -/* alert title */ +/* No comment provided by engineer. */ "Remove member?" = "Видалити учасника?"; /* No comment provided by engineer. */ diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index 9df0e04717..19d7c268d4 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -4109,7 +4109,7 @@ swipe action */ /* No comment provided by engineer. */ "Remove member" = "删除成员"; -/* alert title */ +/* No comment provided by engineer. */ "Remove member?" = "删除成员吗?"; /* No comment provided by engineer. */ diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index fbb9f94a64..53f31ecba9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -1014,7 +1014,7 @@ Moderieren Diese Nachricht wird für alle Mitglieder als moderiert gekennzeichnet. Sie sind Beobachter - Sie können keine Nachrichten versenden! + Sie sind Beobachter Beobachter Anfängliche Rolle Nachricht des Mitglieds löschen\? @@ -2466,7 +2466,7 @@ %d Chat(s) Meldung wurde an die Moderatoren gesendet Sie haben dieses Mitglied übernommen - Überprüfung der Mitglieder vor der Aufnahme (Anklopfen). + Überprüfung der Mitglieder vor der Aufnahme (\"Anklopfen\"). Überprüfung der Mitglieder alle Aus @@ -2489,10 +2489,25 @@ Chat mit Administratoren %1$s übernommen Als Mitglied übernehmen - Fehler beim Übernehmen eines Mitglieds + Fehler beim Übernehmen des Mitglieds Aufnahme von Mitgliedern Ausstehende Überprüfung Chat mit einem Mitglied Übernehmen Bitte warten Sie auf die Überprüfung Ihrer Anfrage durch die Gruppen-Moderatoren, um der Gruppe beitreten zu können. + Gruppe wird gelöscht + Beitrittsanfrage abgelehnt + Von der Gruppe entfernt + Sie haben die Gruppe verlassen + Kontakt deaktiviert + Nicht synchronisiert + Fehler beim Löschen des Chats mit dem Mitglied + Kontakt nicht bereit + Sie können keine Nachrichten senden! + Chat löschen + Chat mit dem Mitglied löschen? + Mitglied ablehnen? + Es können keine Nachrichten gesendet werden + Kontakt gelöscht + Das Mitglied hat eine alte App-Version diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index bf920e2fca..d15729be18 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -940,7 +940,7 @@ Errore nell\'aggiornamento del link del gruppo osservatore Contatta l\'amministratore del gruppo. - Non puoi inviare messaggi! + sei un osservatore Sistema Aggiungi messaggio di benvenuto Messaggio di benvenuto @@ -2414,14 +2414,29 @@ Accetta membro Chatta con gli amministratori Accetta come osservatore - Il nuovo membro vuole entrare nel gruppo. + Un nuovo membro vuole entrare nel gruppo. tutti Chatta con il membro Chat con membri Errore di accettazione del membro Accetta come membro - Imposta l\'ammissione del membro + Imposta l\'ammissione dei membri Segnalazione inviata ai moderatori in attesa di revisione - Puoi vedere i tuoi resoconti nella chat con gli amministratori. + Puoi vedere le tue segnalazioni nella chat con gli amministratori. + Non puoi inviare messaggi! + contatto non pronto + contatto eliminato + contatto disattivato + non sincronizzato + richiesta di entrare rifiutata + impossibile inviare messaggi + il gruppo è eliminato + il membro ha una versione vecchia + rimosso dal gruppo + sei uscito/a + Eliminare la chat con il membro? + Rifiutare il membro? + Elimina chat + Errore di eliminazione della chat con il membro diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml index 97742f82a8..dae44e06af 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml @@ -1019,7 +1019,7 @@ Сообщение будет удалено для всех членов группы. Сообщение будет помечено как удаленное для всех членов группы. Пожалуйста, свяжитесь с админом группы. - Вы не можете отправлять сообщения! + Вы \"читатель\" только чтение сообщений читатель Роль при вступлении @@ -1132,7 +1132,7 @@ Ошибка расшифровки Блокировка SimpleX не включена! Ошибка хэш сообщения - Хэш предыдущего сообщения отличается\" + Хэш предыдущего сообщения отличается. Подтвердить код Неправильный код Заблокировать через @@ -2467,4 +2467,58 @@ Частные разговоры, группы и Ваши контакты недоступны для операторов серверов. Настроить операторов серверов Политика конфиденциальности и условия использования. + все + Принять + Участник хочет присоединиться к группе. Принять? + группа удалена + удален из группы + %d чата(ов) + контакт не готов + контакт удален + не синхронизирован + запрос на вступление отклонён + Новый участник хочет присоединиться к группе. + Пожалуйста, подождите, пока модераторы группы рассмотрят ваш запрос на вступление. + ожидает одобрения + Отклонить + Отклонить участника? + Ошибка при удалении чата с членом группы + Полная ссылка + Ошибка вступления члена группы + Ссылка не поддерживается + Эта ссылка требует новую версию. Обновите приложение или попросите Ваш контакт прислать совместимую ссылку. + %d сообщений + Вы можете найти Ваши жалобы в Чате с админами. + Чат с админами + Чат с членом группы + выключено + Одобрять членов + Чаты с членами группы + Приём членов в группу + Одобрять членов для вступления в группу. + Нет чатов с членами группы + Принять как читателя + Принять в группу + Принять члена + одобрен админами + Жалоба отправлена модераторам + Вы вышли + нельзя отправлять + %d чатов с членами группы + контакт выключен + член имеет старую версию + Вы не можете отправлять сообщения! + Короткая ссылка + Сохранить настройки вступления? + Вы приняли этого члена + рассмотрение + Установить вступление в группу + Удалить чат с членом группы? + Удалить разговор + принят %1$s + Чат с админами + Вы приняты + 1 чат с членом группы + SimpleX ссылка канала + Короткие ссылки (БЕТА) diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml index a72a4938c2..e78878000c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml @@ -929,7 +929,7 @@ 观察员 你是观察者 更新群链接错误 - 你无法发送消息! + 你是观察员 初始角色 请联系群管理员。 系统 @@ -2408,4 +2408,19 @@ 你可以在和管理员和聊天中查看你的举报。 接受为观察员 和一名成员的一个聊天 + 无法发送消息 + 你离开了 + 删除和成员的聊天出错 + 你无法发送消息! + 禁用了联系人 + 群被删除了 + 从群被删除了 + 加入请求被拒绝 + 删除聊天 + 删除和成员的聊天吗? + 未同步 + 成员有旧版本 + 删除了联系人 + 联系人未就绪 + 拒绝成员? From dd2dc16fbf9fa95a9f8cdd8e6831a0d8abcd7f53 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 21 May 2025 11:49:58 +0100 Subject: [PATCH 02/13] website: translations (#5937) * Translated using Weblate (Hungarian) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ * Translated using Weblate (Russian) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/ru/ --------- Co-authored-by: summoner001 Co-authored-by: Ghost of Sparta --- website/langs/hu.json | 10 +++++----- website/langs/ru.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/website/langs/hu.json b/website/langs/hu.json index a55136d26a..f512cfa7cf 100644 --- a/website/langs/hu.json +++ b/website/langs/hu.json @@ -16,7 +16,7 @@ "simplex-explained-tab-2-p-1": "Minden kapcsolathoz két különböző üzenetküldési sorbaállítást használ a különböző kiszolgálókon keresztül történő üzenetküldéshez és -fogadáshoz.", "simplex-explained-tab-2-p-2": "A kiszolgálók csak egyetlen irányba továbbítják az üzeneteket, anélkül, hogy teljes képet kapnának a felhasználók beszélgetéseiről vagy kapcsolatairól.", "simplex-explained-tab-3-p-1": "A kiszolgálók minden egyes üzenetsorbaállításhoz külön névtelen hitelesítő-adatokkal rendelkeznek, és nem tudják, hogy melyik felhasználóhoz tartoznak.", - "simplex-explained-tab-3-p-2": "A felhasználók tovább fokozhatják a metaadatok adatvédelmét, ha a Tor segítségével férnek hozzá a kiszolgálókhoz, így megakadályozva az IP-cím szerinti korrelációt.", + "simplex-explained-tab-3-p-2": "A felhasználók tovább fokozhatják a metaadatok adatvédelmét, ha a Tor-hálózat használatával férnek hozzá a kiszolgálókhoz, így megakadályozva az IP-cím szerinti korrelációt.", "smp-protocol": "SMP-protokoll", "chat-protocol": "Csevegési protokoll", "donate": "Adományozás", @@ -59,7 +59,7 @@ "simplex-private-card-3-point-1": "A kliens és a kiszolgálók közötti kapcsolatokhoz csak az erős algoritmusokkal rendelkező TLS 1.2/1.3 protokollt használja.", "simplex-private-card-3-point-2": "A kiszolgáló ujjlenyomata és a csatornakötés megakadályozza a MITM- és a visszajátszási támadásokat.", "simplex-private-card-3-point-3": "Az újrakapcsolódás le van tiltva a munkamenet elleni támadások megelőzése érdekében.", - "simplex-private-card-4-point-1": "Az IP-címe védelme érdekében a kiszolgálókat a Tor hálózaton vagy más átvitelátfedő hálózaton keresztül is elérheti.", + "simplex-private-card-4-point-1": "Az IP-címe védelme érdekében a kiszolgálókat a Tor-hálózaton vagy más átvitelátfedő hálózaton keresztül is elérheti.", "simplex-private-card-6-point-1": "Számos kommunikációs hálózat sebezhető a kiszolgálók vagy a hálózat-szolgáltatók MITM-támadásaival szemben.", "simplex-private-card-6-point-2": "Ennek megakadályozása érdekében a SimpleX-alkalmazások egyszeri kulcsokat adnak át sávon kívül, amikor egy címet hivatkozásként vagy QR-kódként oszt meg.", "simplex-private-card-7-point-1": "Az integritás garantálása érdekében az üzenetek sorszámozással vannak ellátva, és tartalmazzák az előző üzenet hasítóértékét.", @@ -87,7 +87,7 @@ "simplex-unique-4-overlay-1-title": "Teljesen decentralizált — a SimpleX-hálózat a felhasználóké", "hero-overlay-card-1-p-1": "Sok felhasználó kérdezte: ha a SimpleXnek nincsenek felhasználói azonosítói, honnan tudja, hogy hová kell eljuttatni az üzeneteket?", "hero-overlay-card-1-p-2": "Az üzenetek kézbesítéséhez az összes többi hálózat által használt felhasználói azonosítók helyett a SimpleX az üzenetek sorbaállításához ideiglenes, névtelen, páros azonosítókat használ, külön-külön minden egyes kapcsolathoz — nincsenek hosszú távú azonosítók.", - "hero-overlay-card-1-p-4": "Ez a kialakítás megakadályozza a felhasználók metaadatainak kiszivárgását az alkalmazás szintjén. Az adatvédelem további javítása és az IP-cím védelme érdekében az üzenetküldő kiszolgálókhoz Tor hálózaton keresztül is kapcsolódhat.", + "hero-overlay-card-1-p-4": "Ez a kialakítás megakadályozza a felhasználók metaadatainak kiszivárgását az alkalmazás szintjén. Az adatvédelem további javítása és az IP-cím védelme érdekében az üzenetküldő kiszolgálókhoz Tor-hálózaton keresztül is kapcsolódhat.", "hero-overlay-card-1-p-5": "Csak a kliensek tárolják a felhasználói profilokat, kapcsolatokat és csoportokat; az üzenetek küldése 2 rétegű végpontok közötti titkosítással történik.", "hero-overlay-card-1-p-6": "További leírást a SimpleX ismertetőben olvashat.", "hero-overlay-card-2-p-1": "Ha a felhasználók állandó azonosítóval rendelkeznek, még akkor is, ha ez csak egy véletlenszerű szám, például egy munkamenet-azonosító, fennáll annak a veszélye, hogy a szolgáltató vagy egy támadó megfigyelheti, azt hogy hogyan kapcsolódnak a felhasználók egymáshoz, és hány üzenetet küldenek egymásnak.", @@ -114,7 +114,7 @@ "privacy-matters-overlay-card-3-p-4": "Nem elég csak egy végpontok között titkosított üzenetváltó-alkalmazást használnunk, mindannyiunknak olyan üzenetváltó-alkalmazásokat kell használnunk, amelyek védik a személyes partnereink magánéletét — akikkel kapcsolatban állunk.", "simplex-unique-overlay-card-1-p-1": "Más üzenetküldő hálózatoktól eltérően a SimpleX nem rendel azonosítókat a felhasználókhoz. Nem támaszkodik telefonszámokra, tartomány-alapú címekre (mint az e-mail, XMPP vagy a Matrix), felhasználónevekre, nyilvános kulcsokra vagy akár véletlenszerű számokra a felhasználók azonosításához — a SimpleX-kiszolgálók üzemeltetői nem tudják, hogy hányan használják a kiszolgálóikat.", "simplex-unique-overlay-card-1-p-2": "Az üzenetek kézbesítéséhez a SimpleX az egyirányú üzenet várakoztatást használ páronkénti névtelen címekkel, külön a fogadott és külön az elküldött üzenetek számára, általában különböző kiszolgálókon keresztül.", - "simplex-unique-overlay-card-1-p-3": "Ez a kialakítás megvédi annak titkosságát, hogy kivel kommunikál, elrejtve azt a SimpleX-hálózat kiszolgálói és a megfigyelők elől. IP-címének a kiszolgálók elől való elrejtéséhez azt teheti meg, hogy Toron keresztül kapcsolódik a SimpleX-kiszolgálókhoz.", + "simplex-unique-overlay-card-1-p-3": "Ez a kialakítás védi partnerének adatait, elrejtve azt a SimpleX-hálózat kiszolgálói és a külső megfigyelők elől. Az IP-címe elrejtésének érdekében aTor-hálózaton keresztül is kapcsolódhat a SimpleX-kiszolgálókhoz.", "simplex-unique-overlay-card-2-p-1": "Mivel ön nem rendelkezik azonosítóval a SimpleX-hálózaton, senki sem tud kapcsolatba lépni önnel, hacsak nem oszt meg egy egyszeri vagy ideiglenes felhasználói címet, például QR-kódot vagy hivatkozást.", "simplex-unique-overlay-card-2-p-2": "Még a felhasználói cím használata esetén is, aminek használata nem kötelező – ugyanakkor ez a kéretlen kapcsolatkérelmek küldésére is használható – módosíthatja vagy teljesen törölheti anélkül, hogy elveszítené a meglévő kapcsolatait.", "simplex-unique-overlay-card-3-p-1": "A SimpleX Chat az összes felhasználói adatot kizárólag a klienseken tárolja egy hordozható titkosított adatbázis-formátumban, amely exportálható és átvihető bármely más támogatott eszközre.", @@ -256,4 +256,4 @@ "stable-and-beta-versions-built-by-developers": "A fejlesztők által készített stabil és béta verziók", "hero-overlay-card-3-p-3": "A Trail of Bits 2024 júliusában ismét auditálta a SimpleX-protokollok kriptográfiai és hálózati komponenseit. További információk.", "docs-dropdown-14": "SimpleX üzleti célra" -} \ No newline at end of file +} diff --git a/website/langs/ru.json b/website/langs/ru.json index 7f8e191ccd..ea7766db1b 100644 --- a/website/langs/ru.json +++ b/website/langs/ru.json @@ -218,7 +218,7 @@ "simplex-network-overlay-card-1-li-6": "Сети P2P могут быть уязвимы для DRDoS атаки, когда клиенты могут ретранслировать и увеличивать трафик, что приводит к отказу всей сети. Клиенты SimpleX ретранслируют трафик только из известного соединения и не могут быть использованы злоумышленником для создания трафика во всей сети.", "if-you-already-installed-simplex-chat-for-the-terminal": "Если Вы уже установили SimpleX Chat для терминала", "docs-dropdown-8": "Служба Каталогов SimpleX", - "simplex-private-card-1-point-1": "Протокол двойного обновления ключей —
\"отрицаемые\" сообщения с идеальной прямой секретностью и восстановлением после взлома", + "simplex-private-card-1-point-1": "Протокол двойного обновления ключей —
\"отрицаемые\" сообщения с идеальной прямой секретностью и восстановлением после взлома.", "simplex-private-card-8-point-1": "Серверы SimpleX действуют как узлы-миксеры с низкой задержкой — входящие и исходящие сообщения имеют разный порядок.", "simplex-unique-overlay-card-2-p-1": "Поскольку у Вас нет идентификатора в сети SimpleX, никто не сможет связаться с Вами, если Вы сами не предоставите одноразовый или временный адрес в виде QR-кода или ссылки.", "sign-up-to-receive-our-updates": "Подпишитесь на нашу рассылку новостей", From 1b75ca5258333b6d222cdbceaad17308c3c5cb42 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Wed, 21 May 2025 13:33:55 +0100 Subject: [PATCH 03/13] 6.4-beta.1: ios 279, android 291, desktop 103 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 36 +++++++++++----------- apps/multiplatform/gradle.properties | 8 ++--- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 0a0d5b4458..42e36a78c9 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -179,8 +179,8 @@ 64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; }; 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; }; 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; }; - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.1-7Y3Lr8U6bNmEaeIx88dGf7-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.1-7Y3Lr8U6bNmEaeIx88dGf7-ghc9.6.3.a */; }; - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.1-7Y3Lr8U6bNmEaeIx88dGf7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.1-7Y3Lr8U6bNmEaeIx88dGf7.a */; }; + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F-ghc9.6.3.a */; }; + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F.a */; }; 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; }; 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; }; 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; }; @@ -543,8 +543,8 @@ 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = ""; }; 64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.1-7Y3Lr8U6bNmEaeIx88dGf7-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.0.1-7Y3Lr8U6bNmEaeIx88dGf7-ghc9.6.3.a"; sourceTree = ""; }; - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.1-7Y3Lr8U6bNmEaeIx88dGf7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.0.1-7Y3Lr8U6bNmEaeIx88dGf7.a"; sourceTree = ""; }; + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F-ghc9.6.3.a"; sourceTree = ""; }; + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F.a"; sourceTree = ""; }; 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; }; 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = ""; }; @@ -702,8 +702,8 @@ 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */, 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */, 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */, - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.1-7Y3Lr8U6bNmEaeIx88dGf7-ghc9.6.3.a in Frameworks */, - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.1-7Y3Lr8U6bNmEaeIx88dGf7.a in Frameworks */, + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F-ghc9.6.3.a in Frameworks */, + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -788,8 +788,8 @@ 64C829992D54AEEE006B9E89 /* libffi.a */, 64C829982D54AEED006B9E89 /* libgmp.a */, 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */, - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.1-7Y3Lr8U6bNmEaeIx88dGf7-ghc9.6.3.a */, - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.1-7Y3Lr8U6bNmEaeIx88dGf7.a */, + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F-ghc9.6.3.a */, + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F.a */, ); path = Libraries; sourceTree = ""; @@ -1991,7 +1991,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 278; + CURRENT_PROJECT_VERSION = 279; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2041,7 +2041,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 278; + CURRENT_PROJECT_VERSION = 279; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2083,7 +2083,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 278; + CURRENT_PROJECT_VERSION = 279; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2103,7 +2103,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 278; + CURRENT_PROJECT_VERSION = 279; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2128,7 +2128,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 278; + CURRENT_PROJECT_VERSION = 279; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2165,7 +2165,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 278; + CURRENT_PROJECT_VERSION = 279; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2202,7 +2202,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 278; + CURRENT_PROJECT_VERSION = 279; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2253,7 +2253,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 278; + CURRENT_PROJECT_VERSION = 279; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2304,7 +2304,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 278; + CURRENT_PROJECT_VERSION = 279; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2338,7 +2338,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 278; + CURRENT_PROJECT_VERSION = 279; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 4623752b32..db12d47dbd 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -24,11 +24,11 @@ android.nonTransitiveRClass=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.4-beta.0 -android.version_code=290 +android.version_name=6.4-beta.1 +android.version_code=291 -desktop.version_name=6.4-beta.0 -desktop.version_code=102 +desktop.version_name=6.4-beta.1 +desktop.version_code=103 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From e8968653efa5eeaaa961e7ec02cd4ee93a904ce7 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 23 May 2025 10:40:04 +0100 Subject: [PATCH 04/13] core: add custom chat event --- src/Simplex/Chat/Controller.hs | 1 + src/Simplex/Chat/View.hs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 97bf26fd84..061fbfc8d6 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -854,6 +854,7 @@ data ChatEvent | CEvtChatErrors {chatErrors :: [ChatError]} | CEvtTimedAction {action :: String, durationMilliseconds :: Int64} | CEvtTerminalEvent TerminalEvent + | CEvtCustomChatEvent {user_ :: Maybe User, response :: Text} deriving (Show) data TerminalEvent diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index c13b164693..8a6e6037af 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -532,6 +532,7 @@ chatEventToView hu ChatConfig {logLevel, showReactions, showReceipts, testView} ttyUser u ["sent file " <> sShow fileId <> " (" <> plain fileName <> ") error: " <> sShow e] TERcvFileSubError u RcvFileTransfer {fileId, fileInvitation = FileInvitation {fileName}} e -> ttyUser u ["received file " <> sShow fileId <> " (" <> plain fileName <> ") error: " <> sShow e] + CEvtCustomChatEvent u r -> ttyUser' u $ map plain $ T.lines r where ttyUser :: User -> [StyledString] -> [StyledString] ttyUser user@User {showNtfs, activeUser, viewPwdHash} ss From 6acd239339baf828e7e4fd111ae22850d1df9a21 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 23 May 2025 13:16:12 +0000 Subject: [PATCH 05/13] core: allow to add short link to existing contact link (#5940) --- apps/ios/Shared/Model/AppAPITypes.swift | 6 ++ apps/ios/Shared/Model/SimpleXAPI.swift | 13 +++++ .../Views/Chat/Group/GroupLinkView.swift | 58 ++++++++++++++----- .../Views/UserSettings/UserAddressView.swift | 27 +++++++++ .../chat/simplex/common/model/SimpleXAPI.kt | 21 +++++++ .../common/views/chat/group/GroupLinkView.kt | 28 +++++++++ .../views/usersettings/UserAddressView.kt | 30 ++++++++++ .../commonMain/resources/MR/base/strings.xml | 1 + src/Simplex/Chat/Controller.hs | 2 + src/Simplex/Chat/Library/Commands.hs | 28 ++++++++- src/Simplex/Chat/Store/Profiles.hs | 49 +++++++++------- .../SQLite/Migrations/chat_query_plans.txt | 26 ++++----- 12 files changed, 239 insertions(+), 50 deletions(-) diff --git a/apps/ios/Shared/Model/AppAPITypes.swift b/apps/ios/Shared/Model/AppAPITypes.swift index 2ddaf1d2af..9d359d9aa6 100644 --- a/apps/ios/Shared/Model/AppAPITypes.swift +++ b/apps/ios/Shared/Model/AppAPITypes.swift @@ -80,6 +80,7 @@ enum ChatCommand: ChatCmdProtocol { case apiGroupLinkMemberRole(groupId: Int64, memberRole: GroupMemberRole) case apiDeleteGroupLink(groupId: Int64) case apiGetGroupLink(groupId: Int64) + case apiAddShortLinkGroupLink(groupId: Int64) case apiCreateMemberContact(groupId: Int64, groupMemberId: Int64) case apiSendMemberContactInvitation(contactId: Int64, msg: MsgContent) case apiTestProtoServer(userId: Int64, server: String) @@ -134,6 +135,7 @@ enum ChatCommand: ChatCmdProtocol { case apiCreateMyAddress(userId: Int64, short: Bool) case apiDeleteMyAddress(userId: Int64) case apiShowMyAddress(userId: Int64) + case apiAddShortLinkMyAddress(userId: Int64) case apiSetProfileAddress(userId: Int64, on: Bool) case apiAddressAutoAccept(userId: Int64, autoAccept: AutoAccept?) case apiAcceptContact(incognito: Bool, contactReqId: Int64) @@ -262,6 +264,7 @@ enum ChatCommand: ChatCmdProtocol { case let .apiGroupLinkMemberRole(groupId, memberRole): return "/_set link role #\(groupId) \(memberRole)" case let .apiDeleteGroupLink(groupId): return "/_delete link #\(groupId)" case let .apiGetGroupLink(groupId): return "/_get link #\(groupId)" + case let .apiAddShortLinkGroupLink(groupId): return "/_short link #\(groupId)" case let .apiCreateMemberContact(groupId, groupMemberId): return "/_create member contact #\(groupId) \(groupMemberId)" case let .apiSendMemberContactInvitation(contactId, mc): return "/_invite member contact @\(contactId) \(mc.cmdString)" case let .apiTestProtoServer(userId, server): return "/_server test \(userId) \(server)" @@ -326,6 +329,7 @@ enum ChatCommand: ChatCmdProtocol { case let .apiCreateMyAddress(userId, short): return "/_address \(userId) short=\(onOff(short))" case let .apiDeleteMyAddress(userId): return "/_delete_address \(userId)" case let .apiShowMyAddress(userId): return "/_show_address \(userId)" + case let .apiAddShortLinkMyAddress(userId): return "/_short_link_address \(userId)" case let .apiSetProfileAddress(userId, on): return "/_profile_address \(userId) \(onOff(on))" case let .apiAddressAutoAccept(userId, autoAccept): return "/_auto_accept \(userId) \(AutoAccept.cmdString(autoAccept))" case let .apiAcceptContact(incognito, contactReqId): return "/_accept incognito=\(onOff(incognito)) \(contactReqId)" @@ -438,6 +442,7 @@ enum ChatCommand: ChatCmdProtocol { case .apiGroupLinkMemberRole: return "apiGroupLinkMemberRole" case .apiDeleteGroupLink: return "apiDeleteGroupLink" case .apiGetGroupLink: return "apiGetGroupLink" + case .apiAddShortLinkGroupLink: return "apiAddShortLinkGroupLink" case .apiCreateMemberContact: return "apiCreateMemberContact" case .apiSendMemberContactInvitation: return "apiSendMemberContactInvitation" case .apiTestProtoServer: return "apiTestProtoServer" @@ -491,6 +496,7 @@ enum ChatCommand: ChatCmdProtocol { case .apiCreateMyAddress: return "apiCreateMyAddress" case .apiDeleteMyAddress: return "apiDeleteMyAddress" case .apiShowMyAddress: return "apiShowMyAddress" + case .apiAddShortLinkMyAddress: return "apiAddShortLinkMyAddress" case .apiSetProfileAddress: return "apiSetProfileAddress" case .apiAddressAutoAccept: return "apiAddressAutoAccept" case .apiAcceptContact: return "apiAcceptContact" diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 6b938aaa4d..952702b8a8 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1210,6 +1210,13 @@ private func userAddressResponse(_ r: APIResult) throws -> UserCo } } +func apiAddShortLinkMyAddress() async throws -> UserContactLink { + let userId = try currentUserId("apiAddShortLinkMyAddress") + let r: ChatResponse1 = try await chatSendCmd(.apiAddShortLinkMyAddress(userId: userId)) + if case let .userContactLink(_, contactLink) = r { return contactLink } + throw r.unexpected +} + func userAddressAutoAccept(_ autoAccept: AutoAccept?) async throws -> UserContactLink? { let userId = try currentUserId("userAddressAutoAccept") let r: APIResult = await chatApiSendCmd(.apiAddressAutoAccept(userId: userId, autoAccept: autoAccept)) @@ -1736,6 +1743,12 @@ func apiGetGroupLink(_ groupId: Int64) throws -> (CreatedConnLink, GroupMemberRo } } +func apiAddShortLinkGroupLink(_ groupId: Int64) async throws -> (CreatedConnLink, GroupMemberRole) { + let r: ChatResponse2 = try await chatSendCmd(.apiAddShortLinkGroupLink(groupId: groupId)) + if case let .groupLink(_, _, connLink, memberRole) = r { return (connLink, memberRole) } + throw r.unexpected +} + func apiCreateMemberContact(_ groupId: Int64, _ groupMemberId: Int64) async throws -> Contact { let r: ChatResponse2 = try await chatSendCmd(.apiCreateMemberContact(groupId: groupId, groupMemberId: groupMemberId)) if case let .newMemberContact(_, contact, _, _) = r { return contact } diff --git a/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift b/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift index a11c073a42..bfc7c19fa7 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift @@ -35,16 +35,23 @@ struct GroupLinkView: View { } var body: some View { - if creatingGroup { - groupLinkView() - .navigationBarBackButtonHidden() - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - Button ("Continue") { linkCreatedCb?() } + ZStack { + if creatingGroup { + groupLinkView() + .navigationBarBackButtonHidden() + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button ("Continue") { linkCreatedCb?() } + } } - } - } else { - groupLinkView() + } else { + groupLinkView() + } + if creatingLink { + ProgressView() + .scaleEffect(2) + .frame(maxWidth: .infinity) + } } } @@ -79,6 +86,14 @@ struct GroupLinkView: View { Label("Share link", systemImage: "square.and.arrow.up") } + if (groupLink.connShortLink == nil && UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_SHORT_LINKS)) { + Button { + addShortLink() + } label: { + Label("Add short link", systemImage: "plus") + } + } + if !creatingGroup { Button(role: .destructive) { alert = .deleteLink } label: { Label("Delete link", systemImage: "trash") @@ -89,11 +104,6 @@ struct GroupLinkView: View { Label("Create link", systemImage: "link.badge.plus") } .disabled(creatingLink) - if creatingLink { - ProgressView() - .scaleEffect(2) - .frame(maxWidth: .infinity) - } } } header: { if let groupLink, groupLink.connShortLink != nil { @@ -160,6 +170,26 @@ struct GroupLinkView: View { } } } + + private func addShortLink() { + Task { + do { + creatingLink = true + let link = try await apiAddShortLinkGroupLink(groupId) + await MainActor.run { + creatingLink = false + (groupLink, groupLinkMemberRole) = link + } + } catch let error { + logger.error("apiAddShortLinkGroupLink: \(responseError(error))") + await MainActor.run { + creatingLink = false + let a = getErrorAlert(error, "Error adding short link") + alert = .error(title: a.title, error: a.message) + } + } + } + } } struct GroupLinkView_Previews: PreviewProvider { diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift index 4813edf96c..1140dcf0fc 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift @@ -153,6 +153,9 @@ struct UserAddressView: View { } } addressSettingsButton(userAddress) + if (userAddress.connLinkContact.connShortLink == nil && UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_SHORT_LINKS)) { + addShortLinkButton() + } } header: { ToggleShortLinkHeader(text: Text("For social media"), link: userAddress.connLinkContact, short: $showShortLink) } footer: { @@ -209,6 +212,30 @@ struct UserAddressView: View { } } + private func addShortLinkButton() -> some View { + Button { + addShortLink() + } label: { + Label("Add short link", systemImage: "plus") + } + } + + private func addShortLink() { + progressIndicator = true + Task { + do { + let userAddress = try await apiAddShortLinkMyAddress() + await MainActor.run { + chatModel.userAddress = userAddress + } + await MainActor.run { progressIndicator = false } + } catch let error { + logger.error("apiAddShortLinkMyAddress: \(responseError(error))") + await MainActor.run { progressIndicator = false } + } + } + } + private func createOneTimeLinkButton() -> some View { NavigationLink { NewChatView(selection: .invite) 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 7cb2d9fe5e..a334bb9a06 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 @@ -1570,6 +1570,14 @@ object ChatController { return null } + suspend fun apiAddShortLinkMyAddress(rh: Long?): UserContactLinkRec? { + val userId = kotlin.runCatching { currentUserId("apiAddShortLinkMyAddress") }.getOrElse { return null } + val r = sendCmd(rh, CC.ApiAddShortLinkMyAddress(userId)) + if (r is API.Result && r.res is CR.UserContactLink) return r.res.contactLink + Log.e(TAG, "apiAddShortLinkMyAddress bad response: ${r.responseType} ${r.details}") + return null + } + suspend fun userAddressAutoAccept(rh: Long?, autoAccept: AutoAccept?): UserContactLinkRec? { val userId = kotlin.runCatching { currentUserId("userAddressAutoAccept") }.getOrElse { return null } val r = sendCmd(rh, CC.ApiAddressAutoAccept(userId, autoAccept)) @@ -2016,6 +2024,13 @@ object ChatController { return null } + suspend fun apiAddShortLinkGroupLink(rh: Long?, groupId: Long): Pair? { + val r = sendCmd(rh, CC.APIAddShortLinkGroupLink(groupId)) + if (r is API.Result && r.res is CR.GroupLink) return r.res.connLinkContact to r.res.memberRole + Log.e(TAG, "apiAddShortLinkGroupLink bad response: ${r.responseType} ${r.details}") + return null + } + suspend fun apiCreateMemberContact(rh: Long?, groupId: Long, groupMemberId: Long): Contact? { val r = sendCmd(rh, CC.APICreateMemberContact(groupId, groupMemberId)) if (r is API.Result && r.res is CR.NewMemberContact) return r.res.contact @@ -3368,6 +3383,7 @@ sealed class CC { class APIGroupLinkMemberRole(val groupId: Long, val memberRole: GroupMemberRole): CC() class APIDeleteGroupLink(val groupId: Long): CC() class APIGetGroupLink(val groupId: Long): CC() + class APIAddShortLinkGroupLink(val groupId: Long): CC() class APICreateMemberContact(val groupId: Long, val groupMemberId: Long): CC() class APISendMemberContactInvitation(val contactId: Long, val mc: MsgContent): CC() class APITestProtoServer(val userId: Long, val server: String): CC() @@ -3422,6 +3438,7 @@ sealed class CC { class ApiCreateMyAddress(val userId: Long, val short: Boolean): CC() class ApiDeleteMyAddress(val userId: Long): CC() class ApiShowMyAddress(val userId: Long): CC() + class ApiAddShortLinkMyAddress(val userId: Long): CC() class ApiSetProfileAddress(val userId: Long, val on: Boolean): CC() class ApiAddressAutoAccept(val userId: Long, val autoAccept: AutoAccept?): CC() class ApiGetCallInvitations: CC() @@ -3555,6 +3572,7 @@ sealed class CC { is APIGroupLinkMemberRole -> "/_set link role #$groupId ${memberRole.name.lowercase()}" is APIDeleteGroupLink -> "/_delete link #$groupId" is APIGetGroupLink -> "/_get link #$groupId" + is APIAddShortLinkGroupLink -> "/_short link #$groupId" is APICreateMemberContact -> "/_create member contact #$groupId $groupMemberId" is APISendMemberContactInvitation -> "/_invite member contact @$contactId ${mc.cmdString}" is APITestProtoServer -> "/_server test $userId $server" @@ -3609,6 +3627,7 @@ sealed class CC { is ApiCreateMyAddress -> "/_address $userId short=${onOff(short)}" is ApiDeleteMyAddress -> "/_delete_address $userId" is ApiShowMyAddress -> "/_show_address $userId" + is ApiAddShortLinkMyAddress -> "/_short_link_address $userId" is ApiSetProfileAddress -> "/_profile_address $userId ${onOff(on)}" is ApiAddressAutoAccept -> "/_auto_accept $userId ${AutoAccept.cmdString(autoAccept)}" is ApiAcceptContact -> "/_accept incognito=${onOff(incognito)} $contactReqId" @@ -3720,6 +3739,7 @@ sealed class CC { is APIGroupLinkMemberRole -> "apiGroupLinkMemberRole" is APIDeleteGroupLink -> "apiDeleteGroupLink" is APIGetGroupLink -> "apiGetGroupLink" + is APIAddShortLinkGroupLink -> "apiAddShortLinkGroupLink" is APICreateMemberContact -> "apiCreateMemberContact" is APISendMemberContactInvitation -> "apiSendMemberContactInvitation" is APITestProtoServer -> "testProtoServer" @@ -3774,6 +3794,7 @@ sealed class CC { is ApiCreateMyAddress -> "apiCreateMyAddress" is ApiDeleteMyAddress -> "apiDeleteMyAddress" is ApiShowMyAddress -> "apiShowMyAddress" + is ApiAddShortLinkMyAddress -> "apiAddShortLinkMyAddress" is ApiSetProfileAddress -> "apiSetProfileAddress" is ApiAddressAutoAccept -> "apiAddressAutoAccept" is ApiAcceptContact -> "apiAcceptContact" diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt index 6e1b9a731d..7e3fd839e2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt @@ -49,6 +49,18 @@ fun GroupLinkView( creatingLink = false } } + fun addShortLink() { + creatingLink = true + withBGApi { + val link = chatModel.controller.apiAddShortLinkGroupLink(rhId, groupInfo.groupId) + if (link != null) { + groupLink = link.first + groupLinkMemberRole.value = link.second + onGroupLinkUpdated?.invoke(link) + } + creatingLink = false + } + } LaunchedEffect(Unit) { if (groupLink == null && !creatingLink) { createLink() @@ -60,6 +72,7 @@ fun GroupLinkView( groupLinkMemberRole, creatingLink, createLink = ::createLink, + addShortLink = ::addShortLink, updateLink = { val role = groupLinkMemberRole.value if (role != null) { @@ -105,6 +118,7 @@ fun GroupLinkLayout( groupLinkMemberRole: MutableState, creatingLink: Boolean, createLink: () -> Unit, + addShortLink: () -> Unit, updateLink: () -> Unit, deleteLink: () -> Unit, creatingGroup: Boolean = false, @@ -182,12 +196,26 @@ fun GroupLinkLayout( ) } } + if (groupLink.connShortLink == null && appPreferences.privacyShortLinks.get()) { + AddShortLinkButton(addShortLink) + } } } SectionBottomSpacer() } } +@Composable +private fun AddShortLinkButton(onClick: () -> Unit) { + SettingsActionItem( + painterResource(MR.images.ic_add), + stringResource(MR.strings.add_short_link), + onClick, + iconColor = MaterialTheme.colors.primary, + textColor = MaterialTheme.colors.primary, + ) +} + @Composable private fun RoleSelectionRow(groupInfo: GroupInfo, selectedRole: MutableState, enabled: Boolean = true) { Row( 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 8c7c2d8416..53237f48ac 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 @@ -43,6 +43,7 @@ fun UserAddressView( KeyChangeEffect(user.value?.remoteHostId, user.value?.userId) { close() } + fun setProfileAddress(on: Boolean) { progressIndicator = true withBGApi { @@ -81,6 +82,17 @@ fun UserAddressView( } } + fun addShortLink() { + withBGApi { + progressIndicator = true + val userAddress = chatModel.controller.apiAddShortLinkMyAddress(user.value?.remoteHostId) + if (userAddress != null) { + chatModel.userAddress.value = userAddress + } + progressIndicator = false + } + } + LaunchedEffect(autoCreateAddress) { if (chatModel.userAddress.value == null && autoCreateAddress) { createAddress() @@ -95,6 +107,7 @@ fun UserAddressView( userAddress = userAddress.value, shareViaProfile, createAddress = { createAddress() }, + addShortLink = { addShortLink() }, learnMore = { ModalManager.start.showModal { UserAddressLearnMore() @@ -169,6 +182,7 @@ private fun UserAddressLayout( userAddress: UserContactLinkRec?, shareViaProfile: MutableState, createAddress: () -> Unit, + addShortLink: () -> Unit, learnMore: () -> Unit, share: (String) -> Unit, sendEmail: (UserContactLinkRec) -> Unit, @@ -211,6 +225,9 @@ private fun UserAddressLayout( // ShareViaEmailButton { sendEmail(userAddress) } BusinessAddressToggle(autoAcceptState) { saveAas(autoAcceptState.value, autoAcceptStateSaved) } AddressSettingsButton(user, userAddress, shareViaProfile, setProfileAddress, saveAas) + if (userAddress.connLinkContact.connShortLink == null && appPreferences.privacyShortLinks.get()) { + AddShortLinkButton(addShortLink) + } if (autoAcceptState.value.business) { SectionTextFooter(stringResource(MR.strings.add_your_team_members_to_conversations)) @@ -248,6 +265,17 @@ private fun CreateAddressButton(onClick: () -> Unit) { ) } +@Composable +private fun AddShortLinkButton(onClick: () -> Unit) { + SettingsActionItem( + painterResource(MR.images.ic_add), + stringResource(MR.strings.add_short_link), + onClick, + iconColor = MaterialTheme.colors.primary, + textColor = MaterialTheme.colors.primary, + ) +} + @Composable private fun CreateOneTimeLinkButton() { val closeAll = { ModalManager.start.closeModals() } @@ -559,6 +587,7 @@ fun PreviewUserAddressLayoutNoAddress() { user = User.sampleData, userAddress = null, createAddress = {}, + addShortLink = {}, share = { _ -> }, deleteAddress = {}, saveAas = { _, _ -> }, @@ -592,6 +621,7 @@ fun PreviewUserAddressLayoutAddressCreated() { user = User.sampleData, userAddress = UserContactLinkRec(CreatedConnLink("https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", null)), createAddress = {}, + addShortLink = {}, share = { _ -> }, deleteAddress = {}, saveAas = { _, _ -> }, diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index b5bf2efaff..7af75e64e8 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1058,6 +1058,7 @@ Address settings Business address Add your team members to the conversations. + Add short link Continue diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 061fbfc8d6..5e15c6fb7a 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -373,6 +373,7 @@ data ChatCommand | APIGroupLinkMemberRole GroupId GroupMemberRole | APIDeleteGroupLink GroupId | APIGetGroupLink GroupId + | APIAddShortLinkGroupLink GroupId | APICreateMemberContact GroupId GroupMemberId | APISendMemberContactInvitation {contactId :: ContactId, msgContent_ :: Maybe MsgContent} | GetUserProtoServers AProtocolType @@ -461,6 +462,7 @@ data ChatCommand | DeleteMyAddress | APIShowMyAddress UserId | ShowMyAddress + | APIAddShortLinkMyAddress UserId | APISetProfileAddress UserId Bool | SetProfileAddress Bool | APIAddressAutoAccept UserId (Maybe AutoAccept) diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 20f9468cd6..a565d90696 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -1796,9 +1796,9 @@ processChatCommand' vr = \case CreateMyAddress short -> withUser $ \User {userId} -> processChatCommand $ APICreateMyAddress userId short APIDeleteMyAddress userId -> withUserId userId $ \user@User {profile = p} -> do - conns <- withFastStore $ \db -> getUserAddressConnections db vr user + conn <- withFastStore $ \db -> getUserAddressConnection db vr user withChatLock "deleteMyAddress" $ do - deleteAgentConnectionsAsync $ map aConnId conns + deleteAgentConnectionAsync $ aConnId conn withFastStore' (`deleteUserAddress` user) let p' = (fromLocalProfile p :: Profile) {contactLink = Nothing} r <- updateProfile_ user p' $ withFastStore' $ \db -> setUserProfileContactLink db user Nothing @@ -1812,6 +1812,17 @@ processChatCommand' vr = \case CRUserContactLink user <$> withFastStore (`getUserAddress` user) ShowMyAddress -> withUser' $ \User {userId} -> processChatCommand $ APIShowMyAddress userId + APIAddShortLinkMyAddress userId -> withUserId' userId $ \user -> do + (ucl@UserContactLink {connLinkContact = CCLink connFullLink sLnk_}, conn) <- + withFastStore $ \db -> (,) <$> getUserAddress db user <*> getUserAddressConnection db vr user + when (isJust sLnk_) $ throwCmdError "address already has short link" + sLnk <- withAgent $ \a -> setContactShortLink a (aConnId conn) "" + case entityId conn of + Just uclId -> do + withFastStore' $ \db -> setUserContactLinkShortLink db uclId sLnk + let ucl' = (ucl :: UserContactLink) {connLinkContact = CCLink connFullLink (Just sLnk)} + pure $ CRUserContactLink user ucl' + Nothing -> throwChatError $ CEException "no user contact link id" APISetProfileAddress userId False -> withUserId userId $ \user@User {profile = p} -> do let p' = (fromLocalProfile p :: Profile) {contactLink = Nothing} updateProfile_ user p' $ withFastStore' $ \db -> setUserProfileContactLink db user Nothing @@ -2403,6 +2414,17 @@ processChatCommand' vr = \case gInfo <- withFastStore $ \db -> getGroupInfo db vr user groupId (_, groupLink, mRole) <- withFastStore $ \db -> getGroupLink db user gInfo pure $ CRGroupLink user gInfo groupLink mRole + APIAddShortLinkGroupLink groupId -> withUser $ \user -> do + (gInfo, (uclId, _gLink@(CCLink connFullLink sLnk_), mRole), conn) <- withFastStore $ \db -> do + gInfo <- getGroupInfo db vr user groupId + gLink <- getGroupLink db user gInfo + conn <- getGroupLinkConnection db vr user gInfo + pure (gInfo, gLink, conn) + when (isJust sLnk_) $ throwCmdError "group link already has short link" + sLnk <- withAgent $ \a -> setContactShortLink a (aConnId conn) "" + withFastStore' $ \db -> setUserContactLinkShortLink db uclId sLnk + let groupLink' = CCLink connFullLink (Just sLnk) + pure $ CRGroupLink user gInfo groupLink' mRole APICreateMemberContact gId gMemberId -> withUser $ \user -> do (g, m) <- withFastStore $ \db -> (,) <$> getGroupInfo db vr user gId <*> getGroupMember db vr user gId gMemberId assertUserGroupRole g GRAuthor @@ -4222,6 +4244,7 @@ chatCommandP = "/_set link role #" *> (APIGroupLinkMemberRole <$> A.decimal <*> memberRole), "/_delete link #" *> (APIDeleteGroupLink <$> A.decimal), "/_get link #" *> (APIGetGroupLink <$> A.decimal), + "/_short link #" *> (APIAddShortLinkGroupLink <$> A.decimal), "/create link #" *> (CreateGroupLink <$> displayNameP <*> (memberRole <|> pure GRMember) <*> shortP), "/set link role #" *> (GroupLinkMemberRole <$> displayNameP <*> memberRole), "/delete link #" *> (DeleteGroupLink <$> displayNameP), @@ -4277,6 +4300,7 @@ chatCommandP = ("/delete_address" <|> "/da") $> DeleteMyAddress, "/_show_address " *> (APIShowMyAddress <$> A.decimal), ("/show_address" <|> "/sa") $> ShowMyAddress, + "/_short_link_address " *> (APIAddShortLinkMyAddress <$> A.decimal), "/_profile_address " *> (APISetProfileAddress <$> A.decimal <* A.space <*> onOffP), ("/profile_address " <|> "/pa ") *> (SetProfileAddress <$> onOffP), "/_auto_accept " *> (APIAddressAutoAccept <$> A.decimal <* A.space <*> autoAcceptP), diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index a7dc154d9d..4986ed5140 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -43,7 +43,7 @@ module Simplex.Chat.Store.Profiles setUserProfileContactLink, getUserContactProfiles, createUserContactLink, - getUserAddressConnections, + getUserAddressConnection, getUserContactLinks, deleteUserAddress, getUserAddress, @@ -51,6 +51,7 @@ module Simplex.Chat.Store.Profiles getGroupLinkInfo, getUserContactLinkByConnReq, getUserContactLinkViaShortLink, + setUserContactLinkShortLink, getContactWithoutConnViaAddress, updateUserAddressAutoAccept, getProtocolServers, @@ -363,26 +364,21 @@ createUserContactLink db User {userId} agentConnId (CCLink cReq shortLink) subMo userContactLinkId <- insertedRowId db void $ createConnection_ db userId ConnUserContact (Just userContactLinkId) agentConnId ConnNew initialChatVersion chatInitialVRange Nothing Nothing Nothing 0 currentTs subMode CR.PQSupportOff -getUserAddressConnections :: DB.Connection -> VersionRangeChat -> User -> ExceptT StoreError IO [Connection] -getUserAddressConnections db vr User {userId} = do - cs <- liftIO getUserAddressConnections_ - if null cs then throwError SEUserContactLinkNotFound else pure cs - where - getUserAddressConnections_ :: IO [Connection] - getUserAddressConnections_ = - map (toConnection vr) - <$> DB.query - db - [sql| - SELECT c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, - c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, - c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter, - c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version - FROM connections c - JOIN user_contact_links uc ON c.user_contact_link_id = uc.user_contact_link_id - WHERE c.user_id = ? AND uc.user_id = ? AND uc.local_display_name = '' AND uc.group_id IS NULL - |] - (userId, userId) +getUserAddressConnection :: DB.Connection -> VersionRangeChat -> User -> ExceptT StoreError IO Connection +getUserAddressConnection db vr User {userId} = do + ExceptT . firstRow (toConnection vr) SEUserContactLinkNotFound $ + DB.query + db + [sql| + SELECT c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, + c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, + c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter, + c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version + FROM connections c + JOIN user_contact_links uc ON c.user_contact_link_id = uc.user_contact_link_id + WHERE c.user_id = ? AND uc.user_id = ? AND uc.local_display_name = '' AND uc.group_id IS NULL + |] + (userId, userId) getUserContactLinks :: DB.Connection -> VersionRangeChat -> User -> IO [(Connection, UserContact)] getUserContactLinks db vr User {userId} = @@ -531,6 +527,17 @@ userContactLinkQuery = FROM user_contact_links |] +setUserContactLinkShortLink :: DB.Connection -> Int64 -> ShortLinkContact -> IO () +setUserContactLinkShortLink db userContactLinkId shortLink = + DB.execute + db + [sql| + UPDATE user_contact_links + SET short_link_contact = ? + WHERE user_contact_link_id = ? + |] + (shortLink, userContactLinkId) + getContactWithoutConnViaAddress :: DB.Connection -> VersionRangeChat -> User -> (ConnReqContact, ConnReqContact) -> IO (Maybe Contact) getContactWithoutConnViaAddress db vr user@User {userId} (cReqSchema1, cReqSchema2) = do ctId_ <- diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt index b0e98856d9..36833ddd3d 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt @@ -501,19 +501,6 @@ Query: Plan: SEARCH messages USING INDEX idx_messages_group_id_shared_msg_id (group_id=? AND shared_msg_id=?) -Query: - SELECT c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, - c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, - c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter, - c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version - FROM connections c - JOIN user_contact_links uc ON c.user_contact_link_id = uc.user_contact_link_id - WHERE c.user_id = ? AND uc.user_id = ? AND uc.local_display_name = '' AND uc.group_id IS NULL - -Plan: -SEARCH uc USING INDEX sqlite_autoindex_user_contact_links_1 (user_id=? AND local_display_name=?) -SEARCH c USING INDEX idx_connections_user_contact_link_id (user_contact_link_id=?) - Query: SELECT chat_item_id, contact_id, group_id, group_scope_tag, group_scope_group_member_id, note_folder_id FROM chat_items @@ -2823,6 +2810,19 @@ Plan: SEARCH uc USING INDEX idx_user_contact_links_group_id (group_id=?) SEARCH c USING INDEX idx_connections_user_contact_link_id (user_contact_link_id=?) +Query: + SELECT c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, + c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, + c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter, + c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version + FROM connections c + JOIN user_contact_links uc ON c.user_contact_link_id = uc.user_contact_link_id + WHERE c.user_id = ? AND uc.user_id = ? AND uc.local_display_name = '' AND uc.group_id IS NULL + +Plan: +SEARCH uc USING INDEX sqlite_autoindex_user_contact_links_1 (user_id=? AND local_display_name=?) +SEARCH c USING INDEX idx_connections_user_contact_link_id (user_contact_link_id=?) + Query: SELECT c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, From b2bc4d6a5c364a2b11480444f79813ef9873ed2e Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Sat, 24 May 2025 13:34:03 +0000 Subject: [PATCH 06/13] core: fix short link type when adding for group link (#5942) * core: fix short link type when adding for group link * shorten, rename * update setContactShortLink * update * fix * plans * simplexmq * ui: show error when setting short link --------- Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Model/AppAPITypes.swift | 12 ++++---- apps/ios/Shared/Model/SimpleXAPI.swift | 10 +++---- .../Views/Chat/Group/GroupLinkView.swift | 4 +-- .../Views/UserSettings/UserAddressView.swift | 6 ++-- .../chat/simplex/common/model/SimpleXAPI.kt | 30 +++++++++++-------- .../common/views/chat/group/GroupLinkView.kt | 2 +- .../views/usersettings/UserAddressView.kt | 2 +- .../src/Directory/Service.hs | 4 +-- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat/Controller.hs | 4 +-- src/Simplex/Chat/Library/Commands.hs | 30 ++++++++++--------- src/Simplex/Chat/Store/Groups.hs | 6 ++-- .../SQLite/Migrations/chat_query_plans.txt | 2 +- 14 files changed, 62 insertions(+), 54 deletions(-) diff --git a/apps/ios/Shared/Model/AppAPITypes.swift b/apps/ios/Shared/Model/AppAPITypes.swift index 9d359d9aa6..6c47d654e0 100644 --- a/apps/ios/Shared/Model/AppAPITypes.swift +++ b/apps/ios/Shared/Model/AppAPITypes.swift @@ -80,7 +80,7 @@ enum ChatCommand: ChatCmdProtocol { case apiGroupLinkMemberRole(groupId: Int64, memberRole: GroupMemberRole) case apiDeleteGroupLink(groupId: Int64) case apiGetGroupLink(groupId: Int64) - case apiAddShortLinkGroupLink(groupId: Int64) + case apiAddGroupShortLink(groupId: Int64) case apiCreateMemberContact(groupId: Int64, groupMemberId: Int64) case apiSendMemberContactInvitation(contactId: Int64, msg: MsgContent) case apiTestProtoServer(userId: Int64, server: String) @@ -135,7 +135,7 @@ enum ChatCommand: ChatCmdProtocol { case apiCreateMyAddress(userId: Int64, short: Bool) case apiDeleteMyAddress(userId: Int64) case apiShowMyAddress(userId: Int64) - case apiAddShortLinkMyAddress(userId: Int64) + case apiAddMyAddressShortLink(userId: Int64) case apiSetProfileAddress(userId: Int64, on: Bool) case apiAddressAutoAccept(userId: Int64, autoAccept: AutoAccept?) case apiAcceptContact(incognito: Bool, contactReqId: Int64) @@ -264,7 +264,7 @@ enum ChatCommand: ChatCmdProtocol { case let .apiGroupLinkMemberRole(groupId, memberRole): return "/_set link role #\(groupId) \(memberRole)" case let .apiDeleteGroupLink(groupId): return "/_delete link #\(groupId)" case let .apiGetGroupLink(groupId): return "/_get link #\(groupId)" - case let .apiAddShortLinkGroupLink(groupId): return "/_short link #\(groupId)" + case let .apiAddGroupShortLink(groupId): return "/_short link #\(groupId)" case let .apiCreateMemberContact(groupId, groupMemberId): return "/_create member contact #\(groupId) \(groupMemberId)" case let .apiSendMemberContactInvitation(contactId, mc): return "/_invite member contact @\(contactId) \(mc.cmdString)" case let .apiTestProtoServer(userId, server): return "/_server test \(userId) \(server)" @@ -329,7 +329,7 @@ enum ChatCommand: ChatCmdProtocol { case let .apiCreateMyAddress(userId, short): return "/_address \(userId) short=\(onOff(short))" case let .apiDeleteMyAddress(userId): return "/_delete_address \(userId)" case let .apiShowMyAddress(userId): return "/_show_address \(userId)" - case let .apiAddShortLinkMyAddress(userId): return "/_short_link_address \(userId)" + case let .apiAddMyAddressShortLink(userId): return "/_short_link_address \(userId)" case let .apiSetProfileAddress(userId, on): return "/_profile_address \(userId) \(onOff(on))" case let .apiAddressAutoAccept(userId, autoAccept): return "/_auto_accept \(userId) \(AutoAccept.cmdString(autoAccept))" case let .apiAcceptContact(incognito, contactReqId): return "/_accept incognito=\(onOff(incognito)) \(contactReqId)" @@ -442,7 +442,7 @@ enum ChatCommand: ChatCmdProtocol { case .apiGroupLinkMemberRole: return "apiGroupLinkMemberRole" case .apiDeleteGroupLink: return "apiDeleteGroupLink" case .apiGetGroupLink: return "apiGetGroupLink" - case .apiAddShortLinkGroupLink: return "apiAddShortLinkGroupLink" + case .apiAddGroupShortLink: return "apiAddGroupShortLink" case .apiCreateMemberContact: return "apiCreateMemberContact" case .apiSendMemberContactInvitation: return "apiSendMemberContactInvitation" case .apiTestProtoServer: return "apiTestProtoServer" @@ -496,7 +496,7 @@ enum ChatCommand: ChatCmdProtocol { case .apiCreateMyAddress: return "apiCreateMyAddress" case .apiDeleteMyAddress: return "apiDeleteMyAddress" case .apiShowMyAddress: return "apiShowMyAddress" - case .apiAddShortLinkMyAddress: return "apiAddShortLinkMyAddress" + case .apiAddMyAddressShortLink: return "apiAddMyAddressShortLink" case .apiSetProfileAddress: return "apiSetProfileAddress" case .apiAddressAutoAccept: return "apiAddressAutoAccept" case .apiAcceptContact: return "apiAcceptContact" diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 952702b8a8..4f9d75bada 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1210,9 +1210,9 @@ private func userAddressResponse(_ r: APIResult) throws -> UserCo } } -func apiAddShortLinkMyAddress() async throws -> UserContactLink { - let userId = try currentUserId("apiAddShortLinkMyAddress") - let r: ChatResponse1 = try await chatSendCmd(.apiAddShortLinkMyAddress(userId: userId)) +func apiAddMyAddressShortLink() async throws -> UserContactLink { + let userId = try currentUserId("apiAddMyAddressShortLink") + let r: ChatResponse1 = try await chatSendCmd(.apiAddMyAddressShortLink(userId: userId)) if case let .userContactLink(_, contactLink) = r { return contactLink } throw r.unexpected } @@ -1743,8 +1743,8 @@ func apiGetGroupLink(_ groupId: Int64) throws -> (CreatedConnLink, GroupMemberRo } } -func apiAddShortLinkGroupLink(_ groupId: Int64) async throws -> (CreatedConnLink, GroupMemberRole) { - let r: ChatResponse2 = try await chatSendCmd(.apiAddShortLinkGroupLink(groupId: groupId)) +func apiAddGroupShortLink(_ groupId: Int64) async throws -> (CreatedConnLink, GroupMemberRole) { + let r: ChatResponse2 = try await chatSendCmd(.apiAddGroupShortLink(groupId: groupId)) if case let .groupLink(_, _, connLink, memberRole) = r { return (connLink, memberRole) } throw r.unexpected } diff --git a/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift b/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift index bfc7c19fa7..49e4d49e86 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift @@ -175,13 +175,13 @@ struct GroupLinkView: View { Task { do { creatingLink = true - let link = try await apiAddShortLinkGroupLink(groupId) + let link = try await apiAddGroupShortLink(groupId) await MainActor.run { creatingLink = false (groupLink, groupLinkMemberRole) = link } } catch let error { - logger.error("apiAddShortLinkGroupLink: \(responseError(error))") + logger.error("apiAddGroupShortLink: \(responseError(error))") await MainActor.run { creatingLink = false let a = getErrorAlert(error, "Error adding short link") diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift index 1140dcf0fc..f374995f78 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift @@ -224,13 +224,15 @@ struct UserAddressView: View { progressIndicator = true Task { do { - let userAddress = try await apiAddShortLinkMyAddress() + let userAddress = try await apiAddMyAddressShortLink() await MainActor.run { chatModel.userAddress = userAddress } await MainActor.run { progressIndicator = false } } catch let error { - logger.error("apiAddShortLinkMyAddress: \(responseError(error))") + logger.error("apiAddMyAddressShortLink: \(responseError(error))") + let a = getErrorAlert(error, "Error creating address") + alert = .error(title: a.title, error: a.message) await MainActor.run { progressIndicator = false } } } 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 a334bb9a06..0f65783152 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 @@ -1570,11 +1570,13 @@ object ChatController { return null } - suspend fun apiAddShortLinkMyAddress(rh: Long?): UserContactLinkRec? { - val userId = kotlin.runCatching { currentUserId("apiAddShortLinkMyAddress") }.getOrElse { return null } - val r = sendCmd(rh, CC.ApiAddShortLinkMyAddress(userId)) + suspend fun apiAddMyAddressShortLink(rh: Long?): UserContactLinkRec? { + val userId = kotlin.runCatching { currentUserId("apiAddMyAddressShortLink") }.getOrElse { return null } + val r = sendCmd(rh, CC.ApiAddMyAddressShortLink(userId)) if (r is API.Result && r.res is CR.UserContactLink) return r.res.contactLink - Log.e(TAG, "apiAddShortLinkMyAddress bad response: ${r.responseType} ${r.details}") + if (!(networkErrorAlert(r))) { + apiErrorAlert("apiAddMyAddressShortLink", generalGetString(MR.strings.error_creating_address), r) + } return null } @@ -2024,10 +2026,12 @@ object ChatController { return null } - suspend fun apiAddShortLinkGroupLink(rh: Long?, groupId: Long): Pair? { - val r = sendCmd(rh, CC.APIAddShortLinkGroupLink(groupId)) + suspend fun apiAddGroupShortLink(rh: Long?, groupId: Long): Pair? { + val r = sendCmd(rh, CC.ApiAddGroupShortLink(groupId)) if (r is API.Result && r.res is CR.GroupLink) return r.res.connLinkContact to r.res.memberRole - Log.e(TAG, "apiAddShortLinkGroupLink bad response: ${r.responseType} ${r.details}") + if (!(networkErrorAlert(r))) { + apiErrorAlert("apiAddGroupShortLink", generalGetString(MR.strings.error_creating_link_for_group), r) + } return null } @@ -3383,7 +3387,7 @@ sealed class CC { class APIGroupLinkMemberRole(val groupId: Long, val memberRole: GroupMemberRole): CC() class APIDeleteGroupLink(val groupId: Long): CC() class APIGetGroupLink(val groupId: Long): CC() - class APIAddShortLinkGroupLink(val groupId: Long): CC() + class ApiAddGroupShortLink(val groupId: Long): CC() class APICreateMemberContact(val groupId: Long, val groupMemberId: Long): CC() class APISendMemberContactInvitation(val contactId: Long, val mc: MsgContent): CC() class APITestProtoServer(val userId: Long, val server: String): CC() @@ -3438,7 +3442,7 @@ sealed class CC { class ApiCreateMyAddress(val userId: Long, val short: Boolean): CC() class ApiDeleteMyAddress(val userId: Long): CC() class ApiShowMyAddress(val userId: Long): CC() - class ApiAddShortLinkMyAddress(val userId: Long): CC() + class ApiAddMyAddressShortLink(val userId: Long): CC() class ApiSetProfileAddress(val userId: Long, val on: Boolean): CC() class ApiAddressAutoAccept(val userId: Long, val autoAccept: AutoAccept?): CC() class ApiGetCallInvitations: CC() @@ -3572,7 +3576,7 @@ sealed class CC { is APIGroupLinkMemberRole -> "/_set link role #$groupId ${memberRole.name.lowercase()}" is APIDeleteGroupLink -> "/_delete link #$groupId" is APIGetGroupLink -> "/_get link #$groupId" - is APIAddShortLinkGroupLink -> "/_short link #$groupId" + is ApiAddGroupShortLink -> "/_short link #$groupId" is APICreateMemberContact -> "/_create member contact #$groupId $groupMemberId" is APISendMemberContactInvitation -> "/_invite member contact @$contactId ${mc.cmdString}" is APITestProtoServer -> "/_server test $userId $server" @@ -3627,7 +3631,7 @@ sealed class CC { is ApiCreateMyAddress -> "/_address $userId short=${onOff(short)}" is ApiDeleteMyAddress -> "/_delete_address $userId" is ApiShowMyAddress -> "/_show_address $userId" - is ApiAddShortLinkMyAddress -> "/_short_link_address $userId" + is ApiAddMyAddressShortLink -> "/_short_link_address $userId" is ApiSetProfileAddress -> "/_profile_address $userId ${onOff(on)}" is ApiAddressAutoAccept -> "/_auto_accept $userId ${AutoAccept.cmdString(autoAccept)}" is ApiAcceptContact -> "/_accept incognito=${onOff(incognito)} $contactReqId" @@ -3739,7 +3743,7 @@ sealed class CC { is APIGroupLinkMemberRole -> "apiGroupLinkMemberRole" is APIDeleteGroupLink -> "apiDeleteGroupLink" is APIGetGroupLink -> "apiGetGroupLink" - is APIAddShortLinkGroupLink -> "apiAddShortLinkGroupLink" + is ApiAddGroupShortLink -> "apiAddGroupShortLink" is APICreateMemberContact -> "apiCreateMemberContact" is APISendMemberContactInvitation -> "apiSendMemberContactInvitation" is APITestProtoServer -> "testProtoServer" @@ -3794,7 +3798,7 @@ sealed class CC { is ApiCreateMyAddress -> "apiCreateMyAddress" is ApiDeleteMyAddress -> "apiDeleteMyAddress" is ApiShowMyAddress -> "apiShowMyAddress" - is ApiAddShortLinkMyAddress -> "apiAddShortLinkMyAddress" + is ApiAddMyAddressShortLink -> "apiAddMyAddressShortLink" is ApiSetProfileAddress -> "apiSetProfileAddress" is ApiAddressAutoAccept -> "apiAddressAutoAccept" is ApiAcceptContact -> "apiAcceptContact" diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt index 7e3fd839e2..30c16db6a4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt @@ -52,7 +52,7 @@ fun GroupLinkView( fun addShortLink() { creatingLink = true withBGApi { - val link = chatModel.controller.apiAddShortLinkGroupLink(rhId, groupInfo.groupId) + val link = chatModel.controller.apiAddGroupShortLink(rhId, groupInfo.groupId) if (link != null) { groupLink = link.first groupLinkMemberRole.value = link.second 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 53237f48ac..d77c4bc7f6 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 @@ -85,7 +85,7 @@ fun UserAddressView( fun addShortLink() { withBGApi { progressIndicator = true - val userAddress = chatModel.controller.apiAddShortLinkMyAddress(user.value?.remoteHostId) + val userAddress = chatModel.controller.apiAddMyAddressShortLink(user.value?.remoteHostId) if (userAddress != null) { chatModel.userAddress.value = userAddress } diff --git a/apps/simplex-directory-service/src/Directory/Service.hs b/apps/simplex-directory-service/src/Directory/Service.hs index 4909189d40..13510b4d2b 100644 --- a/apps/simplex-directory-service/src/Directory/Service.hs +++ b/apps/simplex-directory-service/src/Directory/Service.hs @@ -713,7 +713,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName case mRole_ of Nothing -> getGroupLinkRole cc user g >>= \case - Just (_, CCLink gLink _, mRole) -> do + Just (_, CCLink gLink _, _, mRole) -> do let anotherRole = case mRole of GRObserver -> GRMember; _ -> GRObserver sendReply $ initialRole n mRole @@ -1045,7 +1045,7 @@ vr :: ChatController -> VersionRangeChat vr ChatController {config = ChatConfig {chatVRange}} = chatVRange {-# INLINE vr #-} -getGroupLinkRole :: ChatController -> User -> GroupInfo -> IO (Maybe (Int64, CreatedLinkContact, GroupMemberRole)) +getGroupLinkRole :: ChatController -> User -> GroupInfo -> IO (Maybe (Int64, CreatedLinkContact, GroupLinkId, GroupMemberRole)) getGroupLinkRole cc user gInfo = withDB "getGroupLink" cc $ \db -> getGroupLink db user gInfo diff --git a/cabal.project b/cabal.project index 48b75a86cd..c067ba2be0 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: d352d518c2b3a42bc7a298954dde799422e1457f + tag: 18e73b8aa7996562f0f92db3967da02d960cb805 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 68a6054ef0..e25c99ebd1 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."d352d518c2b3a42bc7a298954dde799422e1457f" = "1rha84pfpaqx3mf218szkfra334vhijqf17hanxqmp1sicfbf1x3"; + "https://github.com/simplex-chat/simplexmq.git"."18e73b8aa7996562f0f92db3967da02d960cb805" = "0sglm2i5gq34fmcm06q13452yapx7k40rc9b4q6qlfzjavq3jgld"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 5e15c6fb7a..3b59f56021 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -373,7 +373,7 @@ data ChatCommand | APIGroupLinkMemberRole GroupId GroupMemberRole | APIDeleteGroupLink GroupId | APIGetGroupLink GroupId - | APIAddShortLinkGroupLink GroupId + | APIAddGroupShortLink GroupId | APICreateMemberContact GroupId GroupMemberId | APISendMemberContactInvitation {contactId :: ContactId, msgContent_ :: Maybe MsgContent} | GetUserProtoServers AProtocolType @@ -462,7 +462,7 @@ data ChatCommand | DeleteMyAddress | APIShowMyAddress UserId | ShowMyAddress - | APIAddShortLinkMyAddress UserId + | APIAddMyAddressShortLink UserId | APISetProfileAddress UserId Bool | SetProfileAddress Bool | APIAddressAutoAccept UserId (Maybe AutoAccept) diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index a565d90696..744bb2ba26 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -1812,11 +1812,11 @@ processChatCommand' vr = \case CRUserContactLink user <$> withFastStore (`getUserAddress` user) ShowMyAddress -> withUser' $ \User {userId} -> processChatCommand $ APIShowMyAddress userId - APIAddShortLinkMyAddress userId -> withUserId' userId $ \user -> do + APIAddMyAddressShortLink userId -> withUserId' userId $ \user -> do (ucl@UserContactLink {connLinkContact = CCLink connFullLink sLnk_}, conn) <- withFastStore $ \db -> (,) <$> getUserAddress db user <*> getUserAddressConnection db vr user when (isJust sLnk_) $ throwCmdError "address already has short link" - sLnk <- withAgent $ \a -> setContactShortLink a (aConnId conn) "" + sLnk <- shortenShortLink' =<< withAgent (\a -> setContactShortLink a (aConnId conn) "" Nothing) case entityId conn of Just uclId -> do withFastStore' $ \db -> setUserContactLinkShortLink db uclId sLnk @@ -2401,7 +2401,7 @@ processChatCommand' vr = \case pure $ CRGroupLinkCreated user gInfo ccLink' mRole APIGroupLinkMemberRole groupId mRole' -> withUser $ \user -> withGroupLock "groupLinkMemberRole" groupId $ do gInfo <- withFastStore $ \db -> getGroupInfo db vr user groupId - (groupLinkId, groupLink, mRole) <- withFastStore $ \db -> getGroupLink db user gInfo + (groupLinkId, groupLink, _, mRole) <- withFastStore $ \db -> getGroupLink db user gInfo assertUserGroupRole gInfo GRAdmin when (mRole' > GRMember) $ throwChatError $ CEGroupMemberInitialRole gInfo mRole' when (mRole' /= mRole) $ withFastStore' $ \db -> setGroupLinkMemberRole db user groupLinkId mRole' @@ -2412,16 +2412,17 @@ processChatCommand' vr = \case pure $ CRGroupLinkDeleted user gInfo APIGetGroupLink groupId -> withUser $ \user -> do gInfo <- withFastStore $ \db -> getGroupInfo db vr user groupId - (_, groupLink, mRole) <- withFastStore $ \db -> getGroupLink db user gInfo + (_, groupLink, _, mRole) <- withFastStore $ \db -> getGroupLink db user gInfo pure $ CRGroupLink user gInfo groupLink mRole - APIAddShortLinkGroupLink groupId -> withUser $ \user -> do - (gInfo, (uclId, _gLink@(CCLink connFullLink sLnk_), mRole), conn) <- withFastStore $ \db -> do + APIAddGroupShortLink groupId -> withUser $ \user -> do + (gInfo, (uclId, _gLink@(CCLink connFullLink sLnk_), gLinkId, mRole), conn) <- withFastStore $ \db -> do gInfo <- getGroupInfo db vr user groupId gLink <- getGroupLink db user gInfo conn <- getGroupLinkConnection db vr user gInfo pure (gInfo, gLink, conn) when (isJust sLnk_) $ throwCmdError "group link already has short link" - sLnk <- withAgent $ \a -> setContactShortLink a (aConnId conn) "" + let crClientData = encodeJSON $ CRDataGroup gLinkId + sLnk <- shortenShortLink' =<< toShortGroupLink <$> withAgent (\a -> setContactShortLink a (aConnId conn) "" (Just crClientData)) withFastStore' $ \db -> setUserContactLinkShortLink db uclId sLnk let groupLink' = CCLink connFullLink (Just sLnk) pure $ CRGroupLink user gInfo groupLink' mRole @@ -3295,13 +3296,14 @@ processChatCommand' vr = \case CSLContact _ ct srv linkKey -> CSLContact SLSServer ct srv linkKey restoreShortLink' l = (`restoreShortLink` l) <$> asks (shortLinkPresetServers . config) shortLinkUserData short = if short then Just "" else Nothing + shortenShortLink' :: ConnShortLink m -> CM (ConnShortLink m) + shortenShortLink' l = (`shortenShortLink` l) <$> asks (shortLinkPresetServers . config) shortenCreatedLink :: CreatedConnLink m -> CM (CreatedConnLink m) - shortenCreatedLink (CCLink cReq sLnk) = CCLink cReq <$> mapM (\l -> (`shortenShortLink` l) <$> asks (shortLinkPresetServers . config)) sLnk + shortenCreatedLink (CCLink cReq sLnk) = CCLink cReq <$> mapM shortenShortLink' sLnk createdGroupLink :: CreatedLinkContact -> CreatedLinkContact - createdGroupLink (CCLink cReq shortLink) = CCLink cReq (toGroupLink <$> shortLink) - where - toGroupLink :: ShortLinkContact -> ShortLinkContact - toGroupLink (CSLContact sch _ srv k) = CSLContact sch CCTGroup srv k + createdGroupLink (CCLink cReq shortLink) = CCLink cReq (toShortGroupLink <$> shortLink) + toShortGroupLink :: ShortLinkContact -> ShortLinkContact + toShortGroupLink (CSLContact sch _ srv k) = CSLContact sch CCTGroup srv k updateCIGroupInvitationStatus :: User -> GroupInfo -> CIGroupInvitationStatus -> CM () updateCIGroupInvitationStatus user GroupInfo {groupId} newStatus = do AChatItem _ _ cInfo ChatItem {content, meta = CIMeta {itemId}} <- withFastStore $ \db -> getChatItemByGroupId db vr user groupId @@ -4244,7 +4246,7 @@ chatCommandP = "/_set link role #" *> (APIGroupLinkMemberRole <$> A.decimal <*> memberRole), "/_delete link #" *> (APIDeleteGroupLink <$> A.decimal), "/_get link #" *> (APIGetGroupLink <$> A.decimal), - "/_short link #" *> (APIAddShortLinkGroupLink <$> A.decimal), + "/_short link #" *> (APIAddGroupShortLink <$> A.decimal), "/create link #" *> (CreateGroupLink <$> displayNameP <*> (memberRole <|> pure GRMember) <*> shortP), "/set link role #" *> (GroupLinkMemberRole <$> displayNameP <*> memberRole), "/delete link #" *> (DeleteGroupLink <$> displayNameP), @@ -4300,7 +4302,7 @@ chatCommandP = ("/delete_address" <|> "/da") $> DeleteMyAddress, "/_show_address " *> (APIShowMyAddress <$> A.decimal), ("/show_address" <|> "/sa") $> ShowMyAddress, - "/_short_link_address " *> (APIAddShortLinkMyAddress <$> A.decimal), + "/_short_link_address " *> (APIAddMyAddressShortLink <$> A.decimal), "/_profile_address " *> (APISetProfileAddress <$> A.decimal <* A.space <*> onOffP), ("/profile_address " <|> "/pa ") *> (SetProfileAddress <$> onOffP), "/_auto_accept " *> (APIAddressAutoAccept <$> A.decimal <* A.space <*> autoAcceptP), diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index 6c66ea0e64..f3661699cb 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -256,12 +256,12 @@ deleteGroupLink db User {userId} GroupInfo {groupId} = do (userId, groupId) DB.execute db "DELETE FROM user_contact_links WHERE user_id = ? AND group_id = ?" (userId, groupId) -getGroupLink :: DB.Connection -> User -> GroupInfo -> ExceptT StoreError IO (Int64, CreatedLinkContact, GroupMemberRole) +getGroupLink :: DB.Connection -> User -> GroupInfo -> ExceptT StoreError IO (Int64, CreatedLinkContact, GroupLinkId, GroupMemberRole) getGroupLink db User {userId} gInfo@GroupInfo {groupId} = ExceptT . firstRow groupLink (SEGroupLinkNotFound gInfo) $ - DB.query db "SELECT user_contact_link_id, conn_req_contact, short_link_contact, group_link_member_role FROM user_contact_links WHERE user_id = ? AND group_id = ? LIMIT 1" (userId, groupId) + DB.query db "SELECT user_contact_link_id, conn_req_contact, short_link_contact, group_link_id, group_link_member_role FROM user_contact_links WHERE user_id = ? AND group_id = ? LIMIT 1" (userId, groupId) where - groupLink (linkId, cReq, shortLink, mRole_) = (linkId, CCLink cReq shortLink, fromMaybe GRMember mRole_) + groupLink (linkId, cReq, shortLink, gLinkId, mRole_) = (linkId, CCLink cReq shortLink, gLinkId, fromMaybe GRMember mRole_) getGroupLinkId :: DB.Connection -> User -> GroupInfo -> IO (Maybe GroupLinkId) getGroupLinkId db User {userId} GroupInfo {groupId} = diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt index 36833ddd3d..ea91bced4d 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt @@ -5794,7 +5794,7 @@ Query: SELECT user_contact_link_id FROM contact_requests WHERE contact_request_i Plan: SEARCH contact_requests USING INTEGER PRIMARY KEY (rowid=?) -Query: SELECT user_contact_link_id, conn_req_contact, short_link_contact, group_link_member_role FROM user_contact_links WHERE user_id = ? AND group_id = ? LIMIT 1 +Query: SELECT user_contact_link_id, conn_req_contact, short_link_contact, group_link_id, group_link_member_role FROM user_contact_links WHERE user_id = ? AND group_id = ? LIMIT 1 Plan: SEARCH user_contact_links USING INDEX idx_user_contact_links_group_id (group_id=?) From 96dcf16cc3b13301f06f734678f0a534f0146433 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 24 May 2025 16:49:45 +0100 Subject: [PATCH 07/13] core: update simplexmq (#5941) * core: update simplexmq * update types --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat/Controller.hs | 6 +++--- src/Simplex/Chat/Remote.hs | 6 +++--- src/Simplex/Chat/Remote/RevHTTP.hs | 6 +++--- src/Simplex/Chat/Remote/Types.hs | 10 +++++----- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/cabal.project b/cabal.project index c067ba2be0..c6abdfbbde 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 18e73b8aa7996562f0f92db3967da02d960cb805 + tag: ffecd4a17af68677dedf05c95a80dc0f5c584236 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index e25c99ebd1..954f2f0341 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."18e73b8aa7996562f0f92db3967da02d960cb805" = "0sglm2i5gq34fmcm06q13452yapx7k40rc9b4q6qlfzjavq3jgld"; + "https://github.com/simplex-chat/simplexmq.git"."ffecd4a17af68677dedf05c95a80dc0f5c584236" = "09c4yjn1329844f7dxw5fklxxh6jmn8d5g72mw113bs99sp9mcf7"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 3b59f56021..016a7f3dd2 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -87,7 +87,7 @@ import Simplex.Messaging.Notifications.Protocol (DeviceToken (..), NtfTknStatus) import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, parseAll, parseString, sumTypeJSON) import Simplex.Messaging.Protocol (AProtoServerWithAuth, AProtocolType (..), MsgId, NMsgMeta (..), NtfServer, ProtocolType (..), QueueId, SMPMsgMeta (..), SubscriptionMode (..), XFTPServer) import Simplex.Messaging.TMap (TMap) -import Simplex.Messaging.Transport (TLS, simplexMQVersion) +import Simplex.Messaging.Transport (TLS, TransportPeer (..), simplexMQVersion) import Simplex.Messaging.Transport.Client (SocksProxyWithAuth, TransportHost) import Simplex.Messaging.Util (allFinally, catchAllErrors, catchAllErrors', tryAllErrors, tryAllErrors', (<$$>)) import Simplex.RemoteControl.Client @@ -1415,7 +1415,7 @@ data RemoteCtrlSession { remoteCtrlId_ :: Maybe RemoteCtrlId, ctrlDeviceName :: Text, rcsClient :: RCCtrlClient, - tls :: TLS, + tls :: TLS 'TClient, sessionCode :: Text, rcsWaitSession :: Async (), rcsWaitConfirmation :: TMVar (Either RCErrorType (RCCtrlSession, RCCtrlPairing)) @@ -1423,7 +1423,7 @@ data RemoteCtrlSession | RCSessionConnected { remoteCtrlId :: RemoteCtrlId, rcsClient :: RCCtrlClient, - tls :: TLS, + tls :: TLS 'TClient, rcsSession :: RCCtrlSession, http2Server :: Async (), remoteOutputQ :: TBQueue (Either ChatError ChatEvent) diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index f6c94badfe..92b499d8ef 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -58,7 +58,7 @@ import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import qualified Simplex.Messaging.Crypto.File as CF import Simplex.Messaging.Encoding.String (StrEncoding (..)) import qualified Simplex.Messaging.TMap as TM -import Simplex.Messaging.Transport (TLS, closeConnection, tlsUniq) +import Simplex.Messaging.Transport (TLS, TransportPeer (..), closeConnection, tlsUniq) import Simplex.Messaging.Transport.HTTP2.Client (HTTP2ClientError, closeHTTP2Client) import Simplex.Messaging.Transport.HTTP2.Server (HTTP2Request (..)) import Simplex.Messaging.Util @@ -184,7 +184,7 @@ startRemoteHost rh_ rcAddrPrefs_ port_ = do action `catchChatError` \err -> do logError $ "startRemoteHost.waitForHostSession crashed: " <> tshow err readTVarIO rhKeyVar >>= cancelRemoteHostSession (Just (sessSeq, RHSRCrashed err)) - waitForHostSession :: Maybe RemoteHostInfo -> RHKey -> SessionSeq -> Maybe RCCtrlAddress -> TVar RHKey -> RCStepTMVar (ByteString, TLS, RCStepTMVar (RCHostSession, RCHostHello, RCHostPairing)) -> CM () + waitForHostSession :: Maybe RemoteHostInfo -> RHKey -> SessionSeq -> Maybe RCCtrlAddress -> TVar RHKey -> RCStepTMVar (ByteString, TLS 'TServer, RCStepTMVar (RCHostSession, RCHostHello, RCHostPairing)) -> CM () waitForHostSession remoteHost_ rhKey sseq rcAddr_ rhKeyVar vars = do (sessId, tls, vars') <- timeoutThrow (ChatErrorRemoteHost rhKey RHETimeout) 60000000 $ takeRCStep vars let sessionCode = verificationCode sessId @@ -474,7 +474,7 @@ connectRemoteCtrl verifiedInv@(RCVerifiedInvitation inv@RCInvitation {ca, app}) where validateRemoteCtrl RCInvitation {idkey} RemoteCtrl {ctrlPairing = RCCtrlPairing {idPubKey}} = unless (idkey == idPubKey) $ throwError $ ChatErrorRemoteCtrl $ RCEProtocolError $ PRERemoteControl RCEIdentity - waitForCtrlSession :: Maybe RemoteCtrl -> Text -> RCCtrlClient -> RCStepTMVar (ByteString, TLS, RCStepTMVar (RCCtrlSession, RCCtrlPairing)) -> CM () + waitForCtrlSession :: Maybe RemoteCtrl -> Text -> RCCtrlClient -> RCStepTMVar (ByteString, TLS 'TClient, RCStepTMVar (RCCtrlSession, RCCtrlPairing)) -> CM () waitForCtrlSession rc_ ctrlName rcsClient vars = do (uniq, tls, rcsWaitConfirmation) <- timeoutThrow (ChatErrorRemoteCtrl RCETimeout) networkIOTimeout $ takeRCStep vars let sessionCode = verificationCode uniq diff --git a/src/Simplex/Chat/Remote/RevHTTP.hs b/src/Simplex/Chat/Remote/RevHTTP.hs index 4df5bcac2a..20484c0b10 100644 --- a/src/Simplex/Chat/Remote/RevHTTP.hs +++ b/src/Simplex/Chat/Remote/RevHTTP.hs @@ -8,18 +8,18 @@ module Simplex.Chat.Remote.RevHTTP where -import Simplex.Messaging.Transport (TLS) +import Simplex.Messaging.Transport (TLS, TransportPeer (..)) import Simplex.Messaging.Transport.HTTP2 (defaultHTTP2BufferSize, getHTTP2Body) import Simplex.Messaging.Transport.HTTP2.Client (HTTP2Client, HTTP2ClientError (..), attachHTTP2Client, bodyHeadSize, connTimeout, defaultHTTP2ClientConfig) import Simplex.Messaging.Transport.HTTP2.Server (HTTP2Request (..), runHTTP2ServerWith) import Simplex.RemoteControl.Discovery -attachRevHTTP2Client :: IO () -> TLS -> IO (Either HTTP2ClientError HTTP2Client) +attachRevHTTP2Client :: IO () -> TLS 'TServer -> IO (Either HTTP2ClientError HTTP2Client) attachRevHTTP2Client disconnected = attachHTTP2Client config ANY_ADDR_V4 "0" disconnected defaultHTTP2BufferSize where config = defaultHTTP2ClientConfig {bodyHeadSize = doNotPrefetchHead, connTimeout = maxBound} -attachHTTP2Server :: TLS -> (HTTP2Request -> IO ()) -> IO () +attachHTTP2Server :: TLS 'TClient -> (HTTP2Request -> IO ()) -> IO () attachHTTP2Server tls processRequest = runHTTP2ServerWith defaultHTTP2BufferSize ($ tls) $ \sessionId sessionALPN r sendResponse -> do reqBody <- getHTTP2Body r doNotPrefetchHead diff --git a/src/Simplex/Chat/Remote/Types.hs b/src/Simplex/Chat/Remote/Types.hs index a5396e7945..defbe7e72c 100644 --- a/src/Simplex/Chat/Remote/Types.hs +++ b/src/Simplex/Chat/Remote/Types.hs @@ -25,7 +25,7 @@ import Simplex.Chat.Types (verificationCode) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile) import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, sumTypeJSON) -import Simplex.Messaging.Transport (TLS (..), TSbChainKeys (..)) +import Simplex.Messaging.Transport (TLS (..), TSbChainKeys (..), TransportPeer (..)) import Simplex.Messaging.Transport.HTTP2.Client (HTTP2Client) import qualified Simplex.Messaging.TMap as TM import Simplex.RemoteControl.Client @@ -102,11 +102,11 @@ data RHPendingSession = RHPendingSession data RemoteHostSession = RHSessionStarting | RHSessionConnecting {invitation :: Text, rhPendingSession :: RHPendingSession} - | RHSessionPendingConfirmation {sessionCode :: Text, tls :: TLS, rhPendingSession :: RHPendingSession} - | RHSessionConfirmed {tls :: TLS, rhPendingSession :: RHPendingSession} + | RHSessionPendingConfirmation {sessionCode :: Text, tls :: TLS 'TServer, rhPendingSession :: RHPendingSession} + | RHSessionConfirmed {tls :: TLS 'TServer, rhPendingSession :: RHPendingSession} | RHSessionConnected { rchClient :: RCHostClient, - tls :: TLS, + tls :: TLS 'TServer, rhClient :: RemoteHostClient, pollAction :: Async (), storePath :: FilePath @@ -128,7 +128,7 @@ rhsSessionState = \case RHSessionConfirmed {tls} -> RHSConfirmed {sessionCode = tlsSessionCode tls} RHSessionConnected {tls} -> RHSConnected {sessionCode = tlsSessionCode tls} -tlsSessionCode :: TLS -> Text +tlsSessionCode :: TLS p -> Text tlsSessionCode = verificationCode . tlsUniq data RemoteProtocolError From ee2ea152dc95053bdbb966536da430555a993663 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 24 May 2025 21:13:10 +0100 Subject: [PATCH 08/13] core: refactor types for DB entity (#5945) --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 2 +- src/Simplex/Chat.hs | 1 + src/Simplex/Chat/Library/Commands.hs | 5 ++- src/Simplex/Chat/Operators.hs | 63 +-------------------------- src/Simplex/Chat/Operators/Presets.hs | 1 + src/Simplex/Chat/Store/Profiles.hs | 5 ++- src/Simplex/Chat/Store/Remote.hs | 5 ++- src/Simplex/Chat/View.hs | 1 + tests/OperatorTests.hs | 1 + 11 files changed, 18 insertions(+), 70 deletions(-) diff --git a/cabal.project b/cabal.project index c6abdfbbde..012eb62618 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: ffecd4a17af68677dedf05c95a80dc0f5c584236 + tag: 56ea2fdd56af5f5a5da41642486aa086d7371823 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 954f2f0341..fa637db5bc 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."ffecd4a17af68677dedf05c95a80dc0f5c584236" = "09c4yjn1329844f7dxw5fklxxh6jmn8d5g72mw113bs99sp9mcf7"; + "https://github.com/simplex-chat/simplexmq.git"."56ea2fdd56af5f5a5da41642486aa086d7371823" = "1ninimiccsk0ba4wls2i9rqxj6g057m2k30zw19jvzma4xbalzg8"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 553f2ec6cd..d35e76f344 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -256,6 +256,7 @@ library , constraints >=0.12 && <0.14 , containers ==0.6.* , crypton ==0.34.* + , crypton-x509 ==1.7.* , data-default ==0.7.* , directory ==1.3.* , email-validate ==2.3.* @@ -271,7 +272,6 @@ library , optparse-applicative >=0.15 && <0.17 , random >=1.1 && <1.3 , record-hasfield ==1.0.* - , scientific ==0.3.7.* , simple-logger ==0.1.* , simplexmq >=6.3 , socks ==0.6.* diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 6b554d29c4..e14275b75c 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -44,6 +44,7 @@ import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), InitialAgentServers import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.Store.Common (DBStore (dbNew)) import qualified Simplex.Messaging.Agent.Store.DB as DB +import Simplex.Messaging.Agent.Store.Entity import Simplex.Messaging.Agent.Store.Shared (MigrationConfirmation (..), MigrationError) import Simplex.Messaging.Client (defaultNetworkConfig) import qualified Simplex.Messaging.Crypto as C diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 744bb2ba26..1653c33aaa 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -88,6 +88,7 @@ import Simplex.FileTransfer.Description (FileDescriptionURI (..), maxFileSize, m import Simplex.Messaging.Agent as Agent import Simplex.Messaging.Agent.Env.SQLite (ServerCfg (..), ServerRoles (..), allRoles) import Simplex.Messaging.Agent.Protocol +import Simplex.Messaging.Agent.Store.Entity import Simplex.Messaging.Agent.Store.Interface (execSQL) import Simplex.Messaging.Agent.Store.Shared (upMigration) import qualified Simplex.Messaging.Agent.Store.DB as DB @@ -197,7 +198,7 @@ startChatController mainApp enableSndFiles = do startExpireCIThread user setExpireCIFlag user True where - shouldExpireChats = + shouldExpireChats = fmap (fromRight False) $ runExceptT $ withStore' $ \db -> do ttl <- getChatItemTTL db user ttlCount <- getChatTTLCount db user @@ -3683,7 +3684,7 @@ startExpireCIThread user@User {userId} = do liftIO $ threadDelay' interval setChatItemsExpiration :: User -> Int64 -> Int -> CM' () -setChatItemsExpiration user newTTL ttlCount +setChatItemsExpiration user newTTL ttlCount | newTTL > 0 || ttlCount > 0 = do startExpireCIThread user whenM chatStarted $ setExpireCIFlag user True diff --git a/src/Simplex/Chat/Operators.hs b/src/Simplex/Chat/Operators.hs index 8c4490a2c4..c9b5e020d9 100644 --- a/src/Simplex/Chat/Operators.hs +++ b/src/Simplex/Chat/Operators.hs @@ -37,7 +37,6 @@ import qualified Data.List.NonEmpty as L import Data.Map.Strict (Map) import qualified Data.Map.Strict as M import Data.Maybe (fromMaybe, isNothing, mapMaybe) -import Data.Scientific (floatingOrInteger) import Data.Set (Set) import qualified Data.Set as S import Data.Text (Text) @@ -46,11 +45,11 @@ import Data.Time (addUTCTime) import Data.Time.Clock (UTCTime, nominalDay) import Language.Haskell.TH.Syntax (lift) import Simplex.Chat.Operators.Conditions -import Simplex.Chat.Options.DB (FromField (..), ToField (..)) import Simplex.Chat.Types (User) import Simplex.Chat.Types.Util (textParseJSON) import Simplex.Messaging.Agent.Env.SQLite (ServerCfg (..), ServerRoles (..), allRoles) -import Simplex.Messaging.Agent.Store.DB (fromTextField_) +import Simplex.Messaging.Agent.Store.DB (FromField (..), ToField (..), fromTextField_) +import Simplex.Messaging.Agent.Store.Entity import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, sumTypeJSON) import Simplex.Messaging.Protocol (AProtocolType (..), ProtoServerWithAuth (..), ProtocolServer (..), ProtocolType (..), ProtocolTypeI, SProtocolType (..), UserProtocol) @@ -69,32 +68,6 @@ usageConditionsText = in [|stripFrontMatter $(lift (safeDecodeUtf8 s))|] ) -data DBStored = DBStored | DBNew - -data SDBStored (s :: DBStored) where - SDBStored :: SDBStored 'DBStored - SDBNew :: SDBStored 'DBNew - -deriving instance Show (SDBStored s) - -class DBStoredI s where sdbStored :: SDBStored s - -instance DBStoredI 'DBStored where sdbStored = SDBStored - -instance DBStoredI 'DBNew where sdbStored = SDBNew - -data DBEntityId' (s :: DBStored) where - DBEntityId :: Int64 -> DBEntityId' 'DBStored - DBNewEntity :: DBEntityId' 'DBNew - -deriving instance Show (DBEntityId' s) - -deriving instance Eq (DBEntityId' s) - -type DBEntityId = DBEntityId' 'DBStored - -type DBNewEntity = DBEntityId' 'DBNew - data OperatorTag = OTSimplex | OTFlux deriving (Eq, Ord, Show) @@ -118,19 +91,6 @@ instance TextEncoding OperatorTag where OTSimplex -> "simplex" OTFlux -> "flux" --- this and other types only define instances of serialization for known DB IDs only, --- entities without IDs cannot be serialized to JSON -instance FromField DBEntityId -#if defined(dbPostgres) - where - fromField f dat = DBEntityId <$> fromField f dat -#else - where - fromField f = DBEntityId <$> fromField f -#endif - -instance ToField DBEntityId where toField (DBEntityId i) = toField i - data UsageConditions = UsageConditions { conditionsId :: Int64, conditionsCommit :: Text, @@ -486,25 +446,6 @@ validateUserServers curr others = currUserErrs <> concatMap otherUserErrs others userServers :: (UserServersClass u, UserProtocol p) => SProtocolType p -> [u] -> [AUserServer p] userServers p = map aUserServer' . concatMap (servers' p) -instance ToJSON (DBEntityId' s) where - toEncoding = \case - DBEntityId i -> toEncoding i - DBNewEntity -> JE.null_ - toJSON = \case - DBEntityId i -> toJSON i - DBNewEntity -> J.Null - -instance DBStoredI s => FromJSON (DBEntityId' s) where - parseJSON v = case (v, sdbStored @s) of - (J.Null, SDBNew) -> pure DBNewEntity - (J.Number n, SDBStored) -> case floatingOrInteger n of - Left (_ :: Double) -> fail "bad DBEntityId" - Right i -> pure $ DBEntityId (fromInteger i) - _ -> fail "bad DBEntityId" - omittedField = case sdbStored @s of - SDBStored -> Nothing - SDBNew -> Just DBNewEntity - $(JQ.deriveJSON defaultJSON ''UsageConditions) $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CA") ''ConditionsAcceptance) diff --git a/src/Simplex/Chat/Operators/Presets.hs b/src/Simplex/Chat/Operators/Presets.hs index 06c2e19fab..18a0b7ebed 100644 --- a/src/Simplex/Chat/Operators/Presets.hs +++ b/src/Simplex/Chat/Operators/Presets.hs @@ -9,6 +9,7 @@ import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as L import Simplex.Chat.Operators import Simplex.Messaging.Agent.Env.SQLite (ServerRoles (..), allRoles) +import Simplex.Messaging.Agent.Store.Entity import Simplex.Messaging.Protocol (ProtocolType (..), SMPServer) operatorSimpleXChat :: NewServerOperator diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index 4986ed5140..38b2615686 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -111,6 +111,7 @@ import qualified Simplex.Messaging.Crypto.Ratchet as CR import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON) import Simplex.Messaging.Protocol (BasicAuth (..), ProtoServerWithAuth (..), ProtocolServer (..), ProtocolType (..), ProtocolTypeI (..), SProtocolType (..), SubscriptionMode) +import Simplex.Messaging.Agent.Store.Entity import Simplex.Messaging.Transport.Client (TransportHost) import Simplex.Messaging.Util (eitherToMaybe, safeDecodeUtf8) #if defined(dbPostgres) @@ -500,14 +501,14 @@ toGroupLinkInfo (groupId_, mRole_) = getGroupLinkInfo :: DB.Connection -> UserId -> GroupId -> IO (Maybe GroupLinkInfo) getGroupLinkInfo db userId groupId = - fmap join $ maybeFirstRow toGroupLinkInfo $ + fmap join $ maybeFirstRow toGroupLinkInfo $ DB.query db [sql| SELECT group_id, group_link_member_role FROM user_contact_links WHERE user_id = ? AND group_id = ? - |] + |] (userId, groupId) getUserContactLinkByConnReq :: DB.Connection -> User -> (ConnReqContact, ConnReqContact) -> IO (Maybe UserContactLink) diff --git a/src/Simplex/Chat/Store/Remote.hs b/src/Simplex/Chat/Store/Remote.hs index 4921369b10..cc626ddcf0 100644 --- a/src/Simplex/Chat/Store/Remote.hs +++ b/src/Simplex/Chat/Store/Remote.hs @@ -10,6 +10,7 @@ import Control.Monad.Except import Data.Int (Int64) import Data.Text (Text) import Data.Text.Encoding (decodeASCII, encodeUtf8) +import qualified Data.X509 as X import Data.Word (Word16) import Simplex.Chat.Remote.Types import Simplex.Chat.Store.Shared @@ -66,7 +67,7 @@ remoteHostQuery = FROM remote_hosts |] -toRemoteHost :: (Int64, Text, FilePath, C.APrivateSignKey, C.SignedObject C.Certificate, C.PrivateKeyEd25519, C.KeyHash, C.PublicKeyX25519, Maybe Text, Maybe Text, Maybe Word16) -> RemoteHost +toRemoteHost :: (Int64, Text, FilePath, C.APrivateSignKey, C.SignedObject X.Certificate, C.PrivateKeyEd25519, C.KeyHash, C.PublicKeyX25519, Maybe Text, Maybe Text, Maybe Word16) -> RemoteHost toRemoteHost (remoteHostId, hostDeviceName, storePath, caKey, C.SignedObject caCert, idPrivKey, hostFingerprint, hostDhPubKey, ifaceName_, ifaceAddr_, bindPort_) = RemoteHost {remoteHostId, hostDeviceName, storePath, hostPairing, bindAddress_, bindPort_} where @@ -133,7 +134,7 @@ toRemoteCtrl :: ( RemoteCtrlId, Text, C.APrivateSignKey, - C.SignedObject C.Certificate, + C.SignedObject X.Certificate, C.KeyHash, C.PublicKeyEd25519, C.PrivateKeyX25519, diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 8a6e6037af..54e888ea86 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -60,6 +60,7 @@ import qualified Simplex.FileTransfer.Transport as XFTP import Simplex.Messaging.Agent.Client (ProtocolTestFailure (..), ProtocolTestStep (..), SubscriptionsInfo (..)) import Simplex.Messaging.Agent.Env.SQLite (NetworkConfig (..), ServerRoles (..)) import Simplex.Messaging.Agent.Protocol +import Simplex.Messaging.Agent.Store.Entity import Simplex.Messaging.Client (SMPProxyFallback, SMPProxyMode (..), SocksMode (..)) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) diff --git a/tests/OperatorTests.hs b/tests/OperatorTests.hs index dbfde6a03d..656f0ae0e2 100644 --- a/tests/OperatorTests.hs +++ b/tests/OperatorTests.hs @@ -23,6 +23,7 @@ import Simplex.Chat.Operators.Presets import Simplex.Chat.Types import Simplex.FileTransfer.Client.Presets (defaultXFTPServers) import Simplex.Messaging.Agent.Env.SQLite (ServerRoles (..), allRoles) +import Simplex.Messaging.Agent.Store.Entity import Simplex.Messaging.Protocol import Test.Hspec From b848f735ce4e8e48c4fc0ddd4a47f2d3bae80ab8 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 25 May 2025 11:56:00 +0100 Subject: [PATCH 09/13] ui: smaller QR code for short links (#5946) * ui: smaller QR code for short links * more small * size * translations --- apps/ios/Shared/Views/NewChat/QRCode.swift | 35 ++++++++++++------- .../NetworkAndServers/NewServerView.swift | 2 +- .../ProtocolServerView.swift | 2 +- apps/ios/ru.lproj/Localizable.strings | 4 +-- .../simplex/common/platform/Images.android.kt | 6 ++-- .../chat/simplex/common/platform/Images.kt | 2 +- .../simplex/common/views/newchat/QRCode.kt | 18 ++++++---- .../networkAndServers/ProtocolServerView.kt | 2 +- .../simplex/common/platform/Images.desktop.kt | 6 ++-- 9 files changed, 46 insertions(+), 31 deletions(-) diff --git a/apps/ios/Shared/Views/NewChat/QRCode.swift b/apps/ios/Shared/Views/NewChat/QRCode.swift index 453149198b..c9054f30da 100644 --- a/apps/ios/Shared/Views/NewChat/QRCode.swift +++ b/apps/ios/Shared/Views/NewChat/QRCode.swift @@ -12,11 +12,12 @@ import SimpleXChat struct MutableQRCode: View { @Binding var uri: String + var small: Bool = false var withLogo: Bool = true var tintColor = UIColor(red: 0.023, green: 0.176, blue: 0.337, alpha: 1) var body: some View { - QRCode(uri: uri, withLogo: withLogo, tintColor: tintColor) + QRCode(uri: uri, small: small, withLogo: withLogo, tintColor: tintColor) .id("simplex-qrcode-view-for-\(uri)") } } @@ -27,7 +28,7 @@ struct SimpleXCreatedLinkQRCode: View { var onShare: (() -> Void)? = nil var body: some View { - QRCode(uri: link.simplexChatUri(short: short), onShare: onShare) + QRCode(uri: link.simplexChatUri(short: short), small: short && link.connShortLink != nil, onShare: onShare) } } @@ -38,50 +39,57 @@ struct SimpleXLinkQRCode: View { var onShare: (() -> Void)? = nil var body: some View { - QRCode(uri: simplexChatLink(uri), withLogo: withLogo, tintColor: tintColor, onShare: onShare) + QRCode(uri: simplexChatLink(uri), small: uri.count < 200, withLogo: withLogo, tintColor: tintColor, onShare: onShare) } } +private let smallQRRatio: CGFloat = 0.63 + struct QRCode: View { let uri: String + var small: Bool = false var withLogo: Bool = true var tintColor = UIColor(red: 0.023, green: 0.176, blue: 0.337, alpha: 1) var onShare: (() -> Void)? = nil @State private var image: UIImage? = nil @State private var makeScreenshotFunc: () -> Void = {} + @State private var width: CGFloat = .infinity var body: some View { ZStack { if let image = image { - qrCodeImage(image) - GeometryReader { geo in + qrCodeImage(image).frame(width: width, height: width) + GeometryReader { g in + let w = g.size.width * (small ? smallQRRatio : 1) + let l = w * (small ? 0.195 : 0.16) + let m = w * 0.005 ZStack { if withLogo { - let w = geo.size.width Image("icon-light") .resizable() .scaledToFit() - .frame(width: w * 0.16, height: w * 0.16) - .frame(width: w * 0.165, height: w * 0.165) + .frame(width: l, height: l) + .frame(width: l + m, height: l + m) .background(.white) .clipShape(Circle()) } } .onAppear { + width = w makeScreenshotFunc = { let size = CGSizeMake(1024 / UIScreen.main.scale, 1024 / UIScreen.main.scale) - showShareSheet(items: [makeScreenshot(geo.frame(in: .local).origin, size)]) + showShareSheet(items: [makeScreenshot(g.frame(in: .local).origin, size)]) onShare?() } } - .frame(width: geo.size.width, height: geo.size.height) + .frame(width: g.size.width, height: g.size.height) } } else { - Color.clear.aspectRatio(1, contentMode: .fit) + Color.clear.aspectRatio(small ? 1 / smallQRRatio : 1, contentMode: .fit) } } .onTapGesture(perform: makeScreenshotFunc) - .task { image = await generateImage(uri, tintColor: tintColor) } + .task { image = await generateImage(uri, tintColor: tintColor, errorLevel: small ? "M" : "L") } .frame(maxWidth: .infinity, maxHeight: .infinity) } } @@ -94,10 +102,11 @@ private func qrCodeImage(_ image: UIImage) -> some View { .textSelection(.enabled) } -private func generateImage(_ uri: String, tintColor: UIColor) async -> UIImage? { +private func generateImage(_ uri: String, tintColor: UIColor, errorLevel: String) async -> UIImage? { let context = CIContext() let filter = CIFilter.qrCodeGenerator() filter.message = Data(uri.utf8) + filter.correctionLevel = errorLevel if let outputImage = filter.outputImage, let cgImage = context.createCGImage(outputImage, from: outputImage.extent) { return UIImage(cgImage: cgImage).replaceColor(UIColor.black, tintColor) diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NewServerView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NewServerView.swift index 17a0ffdd1c..c8cb2349e7 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NewServerView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NewServerView.swift @@ -65,7 +65,7 @@ struct NewServerView: View { useServerSection(valid) if valid { Section(header: Text("Add to another device").foregroundColor(theme.colors.secondary)) { - MutableQRCode(uri: $serverToEdit.server) + MutableQRCode(uri: $serverToEdit.server, small: true) .listRowInsets(EdgeInsets(top: 12, leading: 12, bottom: 12, trailing: 12)) } } diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServerView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServerView.swift index 13d01874ed..97bfd360cb 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServerView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServerView.swift @@ -110,7 +110,7 @@ struct ProtocolServerView: View { useServerSection(valid) if valid { Section(header: Text("Add to another device").foregroundColor(theme.colors.secondary)) { - MutableQRCode(uri: $serverToEdit.server) + MutableQRCode(uri: $serverToEdit.server, small: true) .listRowInsets(EdgeInsets(top: 12, leading: 12, bottom: 12, trailing: 12)) } } diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index 759a4c79f4..a6025879eb 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -3797,7 +3797,7 @@ alert button */ "Or securely share this file link" = "Или передайте эту ссылку"; /* No comment provided by engineer. */ -"Or show this code" = "Или покажите этот код"; +"Or show this code" = "Или покажите код"; /* No comment provided by engineer. */ "Or to share privately" = "Или поделиться конфиденциально"; @@ -4844,7 +4844,7 @@ chat item action */ "Share SimpleX address on social media." = "Поделитесь SimpleX адресом в социальных сетях."; /* No comment provided by engineer. */ -"Share this 1-time invite link" = "Поделиться одноразовой ссылкой-приглашением"; +"Share this 1-time invite link" = "Поделитесь одноразовой ссылкой"; /* No comment provided by engineer. */ "Share to SimpleX" = "Поделиться в SimpleX"; diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Images.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Images.android.kt index fc323f6ffd..4f47fda130 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Images.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Images.android.kt @@ -101,13 +101,13 @@ actual fun GrayU8.toImageBitmap(): ImageBitmap = ConvertBitmap.grayToBitmap(this actual fun ImageBitmap.hasAlpha(): Boolean = hasAlpha -actual fun ImageBitmap.addLogo(): ImageBitmap = asAndroidBitmap().applyCanvas { - val radius = (width * 0.16f) / 2 +actual fun ImageBitmap.addLogo(size: Float): ImageBitmap = asAndroidBitmap().applyCanvas { + val radius = (width * size) / 2 val paint = android.graphics.Paint() paint.color = android.graphics.Color.WHITE drawCircle(width / 2f, height / 2f, radius, paint) val logo = androidAppContext.resources.getDrawable(R.drawable.icon_foreground_android_common, null).toBitmap() - val logoSize = (width * 0.24).toInt() + val logoSize = (width * size * 1.5).toInt() translate((width - logoSize) / 2f, (height - logoSize) / 2f) drawBitmap(logo, null, android.graphics.Rect(0, 0, logoSize, logoSize), null) }.asImageBitmap() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Images.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Images.kt index fca69d5398..19e40ab0a2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Images.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Images.kt @@ -16,7 +16,7 @@ expect fun compressImageData(bitmap: ImageBitmap, usePng: Boolean): ByteArrayOut expect fun GrayU8.toImageBitmap(): ImageBitmap expect fun ImageBitmap.hasAlpha(): Boolean -expect fun ImageBitmap.addLogo(): ImageBitmap +expect fun ImageBitmap.addLogo(size: Float): ImageBitmap expect fun ImageBitmap.scale(width: Int, height: Int): ImageBitmap expect fun isImage(uri: URI): Boolean diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt index bacb5ab802..0ed32f845b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt @@ -31,6 +31,7 @@ fun SimpleXCreatedLinkQRCode( ) { QRCode( connLink.simplexChatUri(short), + small = short && connLink.connShortLink != null, modifier, padding, tintColor, @@ -50,6 +51,7 @@ fun SimpleXLinkQRCode( ) { QRCode( simplexChatLink(connReq), + small = connReq.count() < 200, modifier, padding, tintColor, @@ -61,6 +63,7 @@ fun SimpleXLinkQRCode( @Composable fun QRCode( connReq: String, + small: Boolean = false, modifier: Modifier = Modifier, padding: PaddingValues = PaddingValues(horizontal = DEFAULT_PADDING * 2f, vertical = DEFAULT_PADDING_HALF), tintColor: Color = Color(0xff062d56), @@ -68,9 +71,11 @@ fun QRCode( onShare: (() -> Unit)? = null, ) { val scope = rememberCoroutineScope() + val logoSize = if (small) 0.21f else 0.16f + val errorLevel = if (small) QrCode.ErrorLevel.M else QrCode.ErrorLevel.L val qr = remember(connReq, tintColor, withLogo) { - qrCodeBitmap(connReq, 1024).replaceColor(Color.Black.toArgb(), tintColor.toArgb()) - .let { if (withLogo) it.addLogo() else it } + qrCodeBitmap(connReq, 1024, errorLevel).replaceColor(Color.Black.toArgb(), tintColor.toArgb()) + .let { if (withLogo) it.addLogo(logoSize) else it } } Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { Image( @@ -79,12 +84,13 @@ fun QRCode( Modifier .padding(padding) .widthIn(max = 400.dp) + .fillMaxWidth(if (small) 0.67f else 1f) .aspectRatio(1f) .then(modifier) .clickable { scope.launch { - val image = qrCodeBitmap(connReq, 1024).replaceColor(Color.Black.toArgb(), tintColor.toArgb()) - .let { if (withLogo) it.addLogo() else it } + val image = qrCodeBitmap(connReq, 1024, errorLevel).replaceColor(Color.Black.toArgb(), tintColor.toArgb()) + .let { if (withLogo) it.addLogo(logoSize) else it } val file = saveTempImageUncompressed(image, true) if (file != null) { shareFile("", CryptoFile.plain(file.absolutePath)) @@ -96,8 +102,8 @@ fun QRCode( } } -fun qrCodeBitmap(content: String, size: Int = 1024): ImageBitmap { - val qrCode = QrCodeEncoder().addAutomatic(content).setError(QrCode.ErrorLevel.L).fixate() +fun qrCodeBitmap(content: String, size: Int = 1024, errorLevel: QrCode.ErrorLevel): ImageBitmap { + val qrCode = QrCodeEncoder().addAutomatic(content).setError(errorLevel).fixate() /** See [QrCodeGeneratorImage.initialize] and [FiducialImageEngine.configure] for size calculation */ val numModules = QrCode.totalModules(qrCode.version) // Hide border on light themes to make it fit to the same place as camera in QRCodeScanner. diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServerView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServerView.kt index bebc96a28c..8626fd3143 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServerView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServerView.kt @@ -189,7 +189,7 @@ fun CustomServer( if (valid.value) { SectionDividerSpaced() SectionView(stringResource(MR.strings.smp_servers_add_to_another_device).uppercase()) { - QRCode(serverAddress.value) + QRCode(serverAddress.value, small = true) } } } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt index 53f3301507..d0ba082adf 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt @@ -133,9 +133,9 @@ actual fun ImageBitmap.hasAlpha(): Boolean { return false } -actual fun ImageBitmap.addLogo(): ImageBitmap { - val radius = (width * 0.16f).toInt() - val logoSize = (width * 0.24).toInt() +actual fun ImageBitmap.addLogo(size: Float): ImageBitmap { + val radius = (width * size).toInt() + val logoSize = (width * size * 1.5).toInt() val logo: BufferedImage = MR.images.icon_foreground_common.image val original = toAwtImage() val withLogo = BufferedImage(width, height, original.type) From cbaab06975152f432225f26e6bcecfcfc4317cc6 Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Mon, 26 May 2025 08:10:13 +0000 Subject: [PATCH 10/13] scripts/prepare-vlc: switch to using simplex vlc builds (#5939) --- scripts/desktop/prepare-vlc-linux.sh | 4 +++- scripts/desktop/prepare-vlc-mac.sh | 6 ++++-- scripts/desktop/prepare-vlc-windows.sh | 5 ++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/scripts/desktop/prepare-vlc-linux.sh b/scripts/desktop/prepare-vlc-linux.sh index dae1c9255d..a798fd6fc9 100755 --- a/scripts/desktop/prepare-vlc-linux.sh +++ b/scripts/desktop/prepare-vlc-linux.sh @@ -10,11 +10,13 @@ vlc_dir=$root_dir/apps/multiplatform/common/src/commonMain/cpp/desktop/libs/linu mkdir $vlc_dir || exit 0 +vlc_tag='v3.0.21-1' +vlc_url="https://github.com/simplex-chat/vlc/releases/download/${vlc_tag}/vlc-linux-x86_64.appimage" cd /tmp mkdir tmp 2>/dev/null || true cd tmp -curl --tlsv1.2 https://github.com/cmatomic/VLCplayer-AppImage/releases/download/3.0.11.1/VLC_media_player-3.0.11.1-x86_64.AppImage -L -o appimage +curl --tlsv1.2 "${vlc_url}" -L -o appimage chmod +x appimage ./appimage --appimage-extract cp -r squashfs-root/usr/lib/* $vlc_dir diff --git a/scripts/desktop/prepare-vlc-mac.sh b/scripts/desktop/prepare-vlc-mac.sh index 288b5a1a7d..4db2983f67 100755 --- a/scripts/desktop/prepare-vlc-mac.sh +++ b/scripts/desktop/prepare-vlc-mac.sh @@ -9,7 +9,9 @@ if [ "$ARCH" == "arm64" ]; then else vlc_arch=intel64 fi -vlc_version=3.0.19 + +vlc_tag='v3.0.21-1' +vlc_url="https://github.com/simplex-chat/vlc/releases/download/${vlc_tag}/vlc-macos-${ARCH}.zip" function readlink() { echo "$(cd "$(dirname "$1")"; pwd -P)" @@ -23,7 +25,7 @@ mkdir -p $vlc_dir/vlc || exit 0 cd /tmp mkdir tmp 2>/dev/null || true cd tmp -curl --tlsv1.2 https://github.com/simplex-chat/vlc/releases/download/v$vlc_version/vlc-macos-$ARCH.zip -L -o vlc +curl --tlsv1.2 "${vlc_url}" -L -o vlc unzip -oqq vlc install_name_tool -add_rpath "@loader_path/VLC.app/Contents/MacOS/lib" vlc-cache-gen cd VLC.app/Contents/MacOS/lib diff --git a/scripts/desktop/prepare-vlc-windows.sh b/scripts/desktop/prepare-vlc-windows.sh index 7d2a71a952..4e65528ca0 100644 --- a/scripts/desktop/prepare-vlc-windows.sh +++ b/scripts/desktop/prepare-vlc-windows.sh @@ -10,10 +10,13 @@ vlc_dir=$root_dir/apps/multiplatform/common/src/commonMain/cpp/desktop/libs/wind rm -rf $vlc_dir mkdir -p $vlc_dir/vlc || exit 0 +vlc_tag='v3.0.21-1' +vlc_url="https://github.com/simplex-chat/vlc/releases/download/${vlc_tag}/vlc-win-x86_64.zip" + cd /tmp mkdir tmp 2>/dev/null || true cd tmp -curl --tlsv1.2 https://irltoolkit.mm.fcix.net/videolan-ftp/vlc/3.0.18/win64/vlc-3.0.18-win64.zip -L -o vlc +curl --tlsv1.2 "${vlc_url}" -L -o vlc $WINDIR\\System32\\tar.exe -xf vlc cd vlc-* # Setting the same date as the date that will be on the file after extraction from JAR to make VLC cache checker happy From 686145ba3681cf08092e61dbeac900b7a3f5f99b Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 26 May 2025 16:57:18 +0100 Subject: [PATCH 11/13] ui: smaller QR code for verify code view, change iOS layout (#5948) * ui: smaller QR code for verify code view, change iOS layout * ios: fix layout for editing group profile --- .../Views/Chat/Group/GroupChatInfoView.swift | 3 - .../Views/Chat/Group/GroupProfileView.swift | 100 ++++++++------- .../Shared/Views/Chat/VerifyCodeView.swift | 119 ++++++++---------- .../Shared/Views/Database/DatabaseView.swift | 16 +-- .../Views/NewChat/NewChatMenuButton.swift | 2 +- .../Views/UserSettings/UserProfile.swift | 93 +++++++------- apps/ios/ru.lproj/Localizable.strings | 10 +- .../common/views/chat/VerifyCodeView.kt | 2 +- .../simplex/common/views/newchat/QRCode.kt | 2 +- 9 files changed, 167 insertions(+), 180 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index 4218e94224..55d7b626fa 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -633,9 +633,6 @@ struct GroupChatInfoView: View { groupInfo: $groupInfo, groupProfile: groupInfo.groupProfile ) - .navigationBarTitle("Group profile") - .modifier(ThemedBackground()) - .navigationBarTitleDisplayMode(.large) } label: { Label("Edit group profile", systemImage: "pencil") } diff --git a/apps/ios/Shared/Views/Chat/Group/GroupProfileView.swift b/apps/ios/Shared/Views/Chat/Group/GroupProfileView.swift index 1617edd11f..4de3608735 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupProfileView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupProfileView.swift @@ -26,6 +26,7 @@ struct GroupProfileView: View { @Environment(\.dismiss) var dismiss: DismissAction @Binding var groupInfo: GroupInfo @State var groupProfile: GroupProfile + @State private var currentProfileHash: Int? @State private var showChooseSource = false @State private var showImagePicker = false @State private var showTakePhoto = false @@ -34,60 +35,40 @@ struct GroupProfileView: View { @FocusState private var focusDisplayName var body: some View { - return VStack(alignment: .leading) { - Text("Group profile is stored on members' devices, not on the servers.") - .padding(.vertical) + List { + EditProfileImage(profileImage: $groupProfile.image, showChooseSource: $showChooseSource) + .if(!focusDisplayName) { $0.padding(.top) } - ZStack(alignment: .center) { - ZStack(alignment: .topTrailing) { - profileImageView(groupProfile.image) - if groupProfile.image != nil { - Button { - groupProfile.image = nil - } label: { - Image(systemName: "multiply") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 12) - } - } - } - - editImageButton { showChooseSource = true } - } - .frame(maxWidth: .infinity, alignment: .center) - - VStack(alignment: .leading) { - ZStack(alignment: .topLeading) { - if !validNewProfileName() { + Section { + HStack { + TextField("Group display name", text: $groupProfile.displayName) + .focused($focusDisplayName) + if !validNewProfileName { Button { alert = .invalidName(validName: mkValidName(groupProfile.displayName)) } label: { Image(systemName: "exclamationmark.circle").foregroundColor(.red) } - } else { - Image(systemName: "exclamationmark.circle").foregroundColor(.clear) } - profileNameTextEdit("Group display name", $groupProfile.displayName) - .focused($focusDisplayName) } - .padding(.bottom) let fullName = groupInfo.groupProfile.fullName if fullName != "" && fullName != groupProfile.displayName { - profileNameTextEdit("Group full name (optional)", $groupProfile.fullName) - .padding(.bottom) - } - HStack(spacing: 20) { - Button("Cancel") { dismiss() } - Button("Save group profile") { saveProfile() } - .disabled(!canUpdateProfile()) + TextField("Group full name (optional)", text: $groupProfile.fullName) } + } footer: { + Text("Group profile is stored on members' devices, not on the servers.") } - .frame(maxWidth: .infinity, minHeight: 120, alignment: .leading) + Section { + Button("Reset") { + groupProfile = groupInfo.groupProfile + currentProfileHash = groupProfile.hashValue + } + .disabled(currentProfileHash == groupProfile.hashValue) + Button("Save group profile", action: saveProfile) + .disabled(!canUpdateProfile) + } } - .padding() - .frame(maxHeight: .infinity, alignment: .top) .confirmationDialog("Group image", isPresented: $showChooseSource, titleVisibility: .visible) { Button("Take picture") { showTakePhoto = true @@ -95,6 +76,11 @@ struct GroupProfileView: View { Button("Choose from library") { showImagePicker = true } + if UIPasteboard.general.hasImages { + Button("Paste image") { + chosenImage = UIPasteboard.general.image + } + } } .fullScreenCover(isPresented: $showTakePhoto) { ZStack { @@ -120,8 +106,20 @@ struct GroupProfileView: View { } } .onAppear { + currentProfileHash = groupProfile.hashValue DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - focusDisplayName = true + withAnimation { focusDisplayName = true } + } + } + .onDisappear { + if canUpdateProfile { + showAlert( + title: NSLocalizedString("Save group profile?", comment: "alert title"), + message: NSLocalizedString("Group profile was changed. If you save it, the updated profile will be sent to group members.", comment: "alert message"), + buttonTitle: NSLocalizedString("Save (and notify members)", comment: "alert button"), + buttonAction: saveProfile, + cancelButton: true + ) } } .alert(item: $alert) { a in @@ -135,30 +133,30 @@ struct GroupProfileView: View { return createInvalidNameAlert(name, $groupProfile.displayName) } } - .contentShape(Rectangle()) - .onTapGesture { hideKeyboard() } + .navigationBarTitle("Group profile") + .modifier(ThemedBackground(grouped: true)) + .navigationBarTitleDisplayMode(focusDisplayName ? .inline : .large) } - private func canUpdateProfile() -> Bool { - groupProfile.displayName.trimmingCharacters(in: .whitespaces) != "" && validNewProfileName() + private var canUpdateProfile: Bool { + currentProfileHash != groupProfile.hashValue && + groupProfile.displayName.trimmingCharacters(in: .whitespaces) != "" && + validNewProfileName } - private func validNewProfileName() -> Bool { + private var validNewProfileName: Bool { groupProfile.displayName == groupInfo.groupProfile.displayName || validDisplayName(groupProfile.displayName.trimmingCharacters(in: .whitespaces)) } - func profileNameTextEdit(_ label: LocalizedStringKey, _ name: Binding) -> some View { - TextField(label, text: name) - .padding(.leading, 32) - } - func saveProfile() { Task { do { groupProfile.displayName = groupProfile.displayName.trimmingCharacters(in: .whitespaces) + groupProfile.fullName = groupProfile.fullName.trimmingCharacters(in: .whitespaces) let gInfo = try await apiUpdateGroup(groupInfo.groupId, groupProfile) await MainActor.run { + currentProfileHash = groupProfile.hashValue groupInfo = gInfo chatModel.updateGroup(gInfo) dismiss() diff --git a/apps/ios/Shared/Views/Chat/VerifyCodeView.swift b/apps/ios/Shared/Views/Chat/VerifyCodeView.swift index 7b01fe0300..373311073a 100644 --- a/apps/ios/Shared/Views/Chat/VerifyCodeView.swift +++ b/apps/ios/Shared/Views/Chat/VerifyCodeView.swift @@ -24,85 +24,70 @@ struct VerifyCodeView: View { } private func verifyCodeView(_ code: String) -> some View { - ScrollView { - let splitCode = splitToParts(code, length: 24) - VStack(alignment: .leading) { - Group { + let splitCode = splitToParts(code, length: 24) + return List { + Section { + QRCode(uri: code, small: true) + + Text(splitCode) + .multilineTextAlignment(.leading) + .font(.body.monospaced()) + .lineLimit(20) + .frame(maxWidth: .infinity, alignment: .center) + } header: { + if connectionVerified { HStack { - if connectionVerified { - Image(systemName: "checkmark.shield") - .foregroundColor(theme.colors.secondary) - Text("\(displayName) is verified") - } else { - Text("\(displayName) is not verified") - } + Image(systemName: "checkmark.shield").foregroundColor(theme.colors.secondary) + Text("\(displayName) is verified").textCase(.none) } - .frame(height: 24) - - QRCode(uri: code) - .padding(.horizontal) - - Text(splitCode) - .multilineTextAlignment(.leading) - .font(.body.monospaced()) - .lineLimit(20) - .padding(.bottom, 8) + } else { + Text("\(displayName) is not verified").textCase(.none) } - .frame(maxWidth: .infinity, alignment: .center) - + } footer: { Text("To verify end-to-end encryption with your contact compare (or scan) the code on your devices.") - .padding(.bottom) + } - Group { - if connectionVerified { - Button { - verifyCode(nil) - } label: { - Label("Clear verification", systemImage: "shield") - } - .padding() - } else { - HStack { - NavigationLink { - ScanCodeView(connectionVerified: $connectionVerified, verify: verify) - .navigationBarTitleDisplayMode(.large) - .navigationTitle("Scan code") - .modifier(ThemedBackground()) - } label: { - Label("Scan code", systemImage: "qrcode") - } - .padding() - Button { - verifyCode(code) { verified in - if !verified { showCodeError = true } - } - } label: { - Label("Mark verified", systemImage: "checkmark.shield") - } - .padding() - .alert(isPresented: $showCodeError) { - Alert(title: Text("Incorrect security code!")) - } - } - } - } - .frame(maxWidth: .infinity, alignment: .center) - } - .padding() - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { + Section { + if connectionVerified { Button { - showShareSheet(items: [splitCode]) + verifyCode(nil) } label: { - Image(systemName: "square.and.arrow.up") + Label("Clear verification", systemImage: "shield") + } + } else { + NavigationLink { + ScanCodeView(connectionVerified: $connectionVerified, verify: verify) + .navigationBarTitleDisplayMode(.large) + .navigationTitle("Scan code") + .modifier(ThemedBackground()) + } label: { + Label("Scan code", systemImage: "qrcode") + } + Button { + verifyCode(code) { verified in + if !verified { showCodeError = true } + } + } label: { + Label("Mark verified", systemImage: "checkmark.shield") + } + .alert(isPresented: $showCodeError) { + Alert(title: Text("Incorrect security code!")) } } } - .onChange(of: connectionVerified) { _ in - if connectionVerified { dismiss() } + } + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button { + showShareSheet(items: [splitCode]) + } label: { + Image(systemName: "square.and.arrow.up") + } } } + .onChange(of: connectionVerified) { _ in + if connectionVerified { dismiss() } + } } private func verifyCode(_ code: String?, _ cb: ((Bool) -> Void)? = nil) { diff --git a/apps/ios/Shared/Views/Database/DatabaseView.swift b/apps/ios/Shared/Views/Database/DatabaseView.swift index 59eee1338b..a7e61b3105 100644 --- a/apps/ios/Shared/Views/Database/DatabaseView.swift +++ b/apps/ios/Shared/Views/Database/DatabaseView.swift @@ -21,7 +21,7 @@ enum DatabaseAlert: Identifiable { case deleteLegacyDatabase case deleteFilesAndMedia case setChatItemTTL(ttl: ChatItemTTL) - case error(title: LocalizedStringKey, error: String = "") + case error(title: String, error: String = "") var id: String { switch self { @@ -456,7 +456,7 @@ struct DatabaseView: View { } } catch let error { await MainActor.run { - alert = .error(title: "Error exporting chat database", error: responseError(error)) + alert = .error(title: NSLocalizedString("Error exporting chat database", comment: "alert title"), error: responseError(error)) progressIndicator = false } } @@ -492,10 +492,10 @@ struct DatabaseView: View { return migration } } catch let error { - await operationEnded(.error(title: "Error importing chat database", error: responseError(error)), progressIndicator, alert) + await operationEnded(.error(title: NSLocalizedString("Error importing chat database", comment: "alert title"), error: responseError(error)), progressIndicator, alert) } } catch let error { - await operationEnded(.error(title: "Error deleting chat database", error: responseError(error)), progressIndicator, alert) + await operationEnded(.error(title: NSLocalizedString("Error deleting chat database", comment: "alert title"), error: responseError(error)), progressIndicator, alert) } } else { showAlert("Error accessing database file") @@ -513,7 +513,7 @@ struct DatabaseView: View { await DatabaseView.operationEnded(.chatDeleted, $progressIndicator, $alert) return true } catch let error { - await DatabaseView.operationEnded(.error(title: "Error deleting database", error: responseError(error)), $progressIndicator, $alert) + await DatabaseView.operationEnded(.error(title: NSLocalizedString("Error deleting database", comment: "alert title"), error: responseError(error)), $progressIndicator, $alert) return false } } @@ -522,7 +522,7 @@ struct DatabaseView: View { if removeLegacyDatabaseAndFiles() { legacyDatabase = false } else { - alert = .error(title: "Error deleting old database") + alert = .error(title: NSLocalizedString("Error deleting old database", comment: "alert title")) } } @@ -546,7 +546,7 @@ struct DatabaseView: View { let (title, message) = chatDeletedAlertText() showAlert(title, message: message, actions: { [okAlertActionWaiting] }) } else if case let .error(title, error) = dbAlert { - showAlert("\(title)", message: error, actions: { [okAlertActionWaiting] }) + showAlert(title, message: error, actions: { [okAlertActionWaiting] }) } else { alert.wrappedValue = dbAlert cont.resume() @@ -567,7 +567,7 @@ struct DatabaseView: View { } } catch { await MainActor.run { - alert = .error(title: "Error changing setting", error: responseError(error)) + alert = .error(title: NSLocalizedString("Error changing setting", comment: "alert title"), error: responseError(error)) chatItemTTL = currentChatItemTTL afterSetCiTTL() } diff --git a/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift b/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift index e5263813fa..98f43f49b2 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift @@ -125,7 +125,7 @@ struct NewChatSheet: View { } NavigationLink { AddGroupView() - .navigationTitle("Create secret group") + .navigationTitle("Create group") .modifier(ThemedBackground(grouped: true)) .navigationBarTitleDisplayMode(.large) } label: { diff --git a/apps/ios/Shared/Views/UserSettings/UserProfile.swift b/apps/ios/Shared/Views/UserSettings/UserProfile.swift index 9aa42930bf..aa2c04ccaa 100644 --- a/apps/ios/Shared/Views/UserSettings/UserProfile.swift +++ b/apps/ios/Shared/Views/UserSettings/UserProfile.swift @@ -25,28 +25,8 @@ struct UserProfile: View { var body: some View { List { - Group { - if profile.image != nil { - ZStack(alignment: .bottomTrailing) { - ZStack(alignment: .topTrailing) { - profileImageView(profile.image) - .onTapGesture { showChooseSource = true } - overlayButton("multiply", edge: .top) { profile.image = nil } - } - overlayButton("camera", edge: .bottom) { showChooseSource = true } - } - } else { - ZStack(alignment: .center) { - profileImageView(profile.image) - editImageButton { showChooseSource = true } - } - } - } - .frame(maxWidth: .infinity, alignment: .center) - .listRowBackground(Color.clear) - .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) - .padding(.top) - .contentShape(Rectangle()) + EditProfileImage(profileImage: $profile.image, showChooseSource: $showChooseSource) + .padding(.top) Section { HStack { @@ -133,25 +113,6 @@ struct UserProfile: View { .alert(item: $alert) { a in userProfileAlert(a, $profile.displayName) } } - private func overlayButton( - _ systemName: String, - edge: Edge.Set, - action: @escaping () -> Void - ) -> some View { - Image(systemName: systemName) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(height: 12) - .foregroundColor(theme.colors.primary) - .padding(6) - .frame(width: 36, height: 36, alignment: .center) - .background(radius >= 20 ? Color.clear : theme.colors.background.opacity(0.5)) - .clipShape(Circle()) - .contentShape(Circle()) - .padding([.trailing, edge], -12) - .onTapGesture(perform: action) - } - private func showFullName(_ user: User) -> Bool { user.profile.fullName != "" && user.profile.fullName != user.profile.displayName } @@ -189,8 +150,54 @@ struct UserProfile: View { } } -func profileImageView(_ imageStr: String?) -> some View { - ProfileImage(imageStr: imageStr, size: 192) +struct EditProfileImage: View { + @EnvironmentObject var theme: AppTheme + @AppStorage(DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) private var radius = defaultProfileImageCorner + @Binding var profileImage: String? + @Binding var showChooseSource: Bool + + var body: some View { + Group { + if profileImage != nil { + ZStack(alignment: .bottomTrailing) { + ZStack(alignment: .topTrailing) { + ProfileImage(imageStr: profileImage, size: 160) + .onTapGesture { showChooseSource = true } + overlayButton("multiply", edge: .top) { profileImage = nil } + } + overlayButton("camera", edge: .bottom) { showChooseSource = true } + } + } else { + ZStack(alignment: .center) { + ProfileImage(imageStr: profileImage, size: 160) + editImageButton { showChooseSource = true } + } + } + } + .frame(maxWidth: .infinity, alignment: .center) + .listRowBackground(Color.clear) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + .contentShape(Rectangle()) + } + + private func overlayButton( + _ systemName: String, + edge: Edge.Set, + action: @escaping () -> Void + ) -> some View { + Image(systemName: systemName) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(height: 12) + .foregroundColor(theme.colors.primary) + .padding(6) + .frame(width: 36, height: 36, alignment: .center) + .background(radius >= 20 ? Color.clear : theme.colors.background.opacity(0.5)) + .clipShape(Circle()) + .contentShape(Circle()) + .padding([.trailing, edge], -12) + .onTapGesture(perform: action) + } } func editImageButton(action: @escaping () -> Void) -> some View { diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index a6025879eb..70cd739531 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -1596,13 +1596,13 @@ set passcode view */ /* delete after time pref value */ -"default (%@)" = "по умолчанию (%@)"; +"default (%@)" = "базовый (%@)"; /* No comment provided by engineer. */ -"default (no)" = "по умолчанию (нет)"; +"default (no)" = "базовый (нет)"; /* No comment provided by engineer. */ -"default (yes)" = "по умолчанию (да)"; +"default (yes)" = "базовый (да)"; /* alert action swipe action */ @@ -1705,7 +1705,7 @@ swipe action */ "Delete messages" = "Удалить сообщения"; /* No comment provided by engineer. */ -"Delete messages after" = "Удалять сообщения через"; +"Delete messages after" = "Удалять сообщения"; /* No comment provided by engineer. */ "Delete old database" = "Удалить предыдущую версию данных"; @@ -4633,7 +4633,7 @@ chat item action */ "Send questions and ideas" = "Отправьте вопросы и идеи"; /* No comment provided by engineer. */ -"Send receipts" = "Отправлять отчёты о доставке"; +"Send receipts" = "Отчёты о доставке"; /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "Отправьте из галереи или из дополнительных клавиатур."; diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt index e670fae5ef..91f7af2b95 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt @@ -68,7 +68,7 @@ private fun VerifyCodeLayout( } } - QRCode(connectionCode, padding = PaddingValues(vertical = DEFAULT_PADDING_HALF)) + QRCode(connectionCode, small = true, padding = PaddingValues(vertical = DEFAULT_PADDING_HALF)) Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { Spacer(Modifier.weight(2f)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt index 0ed32f845b..74c9a55ecf 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt @@ -84,7 +84,7 @@ fun QRCode( Modifier .padding(padding) .widthIn(max = 400.dp) - .fillMaxWidth(if (small) 0.67f else 1f) + .fillMaxWidth(if (small) 0.63f else 1f) .aspectRatio(1f) .then(modifier) .clickable { From dfe94b6e28d937e8d446744acd1a3eeeda7f203c Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 29 May 2025 10:52:33 +0100 Subject: [PATCH 12/13] core: update simplexmq (short links API) (#5953) * core: update simplexmq (short links API) * simplexmq --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat/Library/Commands.hs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cabal.project b/cabal.project index 012eb62618..611243d5ba 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 56ea2fdd56af5f5a5da41642486aa086d7371823 + tag: a94ca62624b98830fac4a4401571854f3e798f47 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index fa637db5bc..905ecc5514 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."56ea2fdd56af5f5a5da41642486aa086d7371823" = "1ninimiccsk0ba4wls2i9rqxj6g057m2k30zw19jvzma4xbalzg8"; + "https://github.com/simplex-chat/simplexmq.git"."a94ca62624b98830fac4a4401571854f3e798f47" = "1ax818z7ka18mq5jnz0xhdl20pgpx3vqbn03r0mz0s28454za5jx"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 1653c33aaa..5d1709a468 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -1817,7 +1817,7 @@ processChatCommand' vr = \case (ucl@UserContactLink {connLinkContact = CCLink connFullLink sLnk_}, conn) <- withFastStore $ \db -> (,) <$> getUserAddress db user <*> getUserAddressConnection db vr user when (isJust sLnk_) $ throwCmdError "address already has short link" - sLnk <- shortenShortLink' =<< withAgent (\a -> setContactShortLink a (aConnId conn) "" Nothing) + sLnk <- shortenShortLink' =<< withAgent (\a -> setConnShortLink a (aConnId conn) SCMContact "" Nothing) case entityId conn of Just uclId -> do withFastStore' $ \db -> setUserContactLinkShortLink db uclId sLnk @@ -2423,7 +2423,7 @@ processChatCommand' vr = \case pure (gInfo, gLink, conn) when (isJust sLnk_) $ throwCmdError "group link already has short link" let crClientData = encodeJSON $ CRDataGroup gLinkId - sLnk <- shortenShortLink' =<< toShortGroupLink <$> withAgent (\a -> setContactShortLink a (aConnId conn) "" (Just crClientData)) + sLnk <- shortenShortLink' =<< toShortGroupLink <$> withAgent (\a -> setConnShortLink a (aConnId conn) SCMContact "" (Just crClientData)) withFastStore' $ \db -> setUserContactLinkShortLink db uclId sLnk let groupLink' = CCLink connFullLink (Just sLnk) pure $ CRGroupLink user gInfo groupLink' mRole From 6ff42ad58586988709ac08abb523f649663f5089 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Thu, 29 May 2025 10:56:23 +0000 Subject: [PATCH 13/13] core: update simplexmq (AConnShortLink instances) (#5954) --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cabal.project b/cabal.project index 611243d5ba..56cafa6111 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: a94ca62624b98830fac4a4401571854f3e798f47 + tag: 4c33d8ac43874660c342c00cb078bd3814d5d9eb source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 905ecc5514..bac315a1f4 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."a94ca62624b98830fac4a4401571854f3e798f47" = "1ax818z7ka18mq5jnz0xhdl20pgpx3vqbn03r0mz0s28454za5jx"; + "https://github.com/simplex-chat/simplexmq.git"."4c33d8ac43874660c342c00cb078bd3814d5d9eb" = "0223qr6vz3vigcx9279kmfxjwb7vwfsqgd7qz271wz86sfxg9hip"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl";