From 5b52d0e173c29741075f0f36b70ddc8a885ea7e0 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Thu, 28 Dec 2023 22:53:37 +0700 Subject: [PATCH 01/16] android, desktop: localization script enchancement (#3616) --- apps/multiplatform/common/build.gradle.kts | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts index 4d2eeca2ee..3b6975638a 100644 --- a/apps/multiplatform/common/build.gradle.kts +++ b/apps/multiplatform/common/build.gradle.kts @@ -154,8 +154,10 @@ afterEvaluate { val endStringRegex = Regex("[ ]*") val endTagRegex = Regex("]*>.*(<|>).*|[^>]*>.*(<|>).*") + val fontLtGtRegex = Regex("[^>]*>.*<font[^>]*>.*</font>.*") + val unbracketedColorRegex = Regex("color=#[abcdefABCDEF0-9]{3,6}") val correctHtmlRegex = Regex("[^>]*>.*.*.*|[^>]*>.*.*.*|[^>]*>.*.*.*|[^>]*>.*]*>.*.*") - val possibleFormat = listOf("s", "d", "1\$s", "1\$d", "2s", "f") + val possibleFormat = listOf("s", "d", "1\$s", "2\$s", "3\$s", "4\$s", "1\$d", "2\$d", "3\$d", "4\$d", "2s", "f") fun String.id(): String = replace("' } - if (countOfStartTag != countOfEndTag || countOfStartTag != endTagRegex.findAll(this).count() * 2 || !correctHtmlRegex.matches(this)) { + val prepared = if (fontLtGtRegex.matches(this) || unbracketedColorRegex.containsMatchIn(this)) { + replace("<", "<").replace(">", ">").replace(unbracketedColorRegex) { it.value.replace("color=#", "color=\"#") + "\"" } + } else this + val countOfStartTag = prepared.count { it == '<' } + val countOfEndTag = prepared.count { it == '>' } + if (countOfStartTag != countOfEndTag || countOfStartTag != endTagRegex.findAll(prepared).count() * 2 || !correctHtmlRegex.matches(prepared)) { if (debug) { println("Wrong string:") println(this) @@ -206,7 +215,7 @@ afterEvaluate { throw Exception("Wrong string: $this \nin $filepath") } } - val res = replace(startStringRegex) { it.value + "" + it.value } + val res = prepared.replace(startStringRegex) { it.value + "" + it.value } if (debug) { println("Changed string:") println(this) From 5ff6bd15f6716cdec2ed381adbff84f71367dfc6 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Thu, 28 Dec 2023 17:31:40 +0000 Subject: [PATCH 02/16] ui: translations (#3615) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (Italian) Currently translated at 100.0% (1500 of 1500 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% (1346 of 1346 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Chinese (Traditional)) Currently translated at 82.9% (1116 of 1346 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hant/ * Translated using Weblate (Turkish) Currently translated at 9.2% (125 of 1346 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/tr/ * Translated using Weblate (Turkish) Currently translated at 80.1% (1202 of 1500 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/tr/ * Translated using Weblate (Hungarian) Currently translated at 38.0% (571 of 1500 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Update translation files Updated by "Remove blank strings" hook in Weblate. Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (1500 of 1500 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (1500 of 1500 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Ukrainian) Currently translated at 93.0% (1252 of 1346 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/uk/ * Translated using Weblate (Arabic) Currently translated at 94.4% (1416 of 1500 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Hungarian) Currently translated at 41.2% (619 of 1500 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Italian) Currently translated at 100.0% (1500 of 1500 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% (1346 of 1346 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Arabic) Currently translated at 99.7% (1496 of 1500 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Italian) Currently translated at 100.0% (1500 of 1500 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% (1346 of 1346 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Japanese) Currently translated at 90.9% (1224 of 1346 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ja/ * Translated using Weblate (Czech) Currently translated at 93.8% (1407 of 1500 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/ * Translated using Weblate (French) Currently translated at 100.0% (1503 of 1503 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fr/ * Translated using Weblate (Italian) Currently translated at 100.0% (1503 of 1503 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1503 of 1503 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (German) Currently translated at 100.0% (1504 of 1504 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (Czech) Currently translated at 93.8% (1411 of 1504 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/ * Translated using Weblate (Czech) Currently translated at 93.8% (1411 of 1504 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/ * Translated using Weblate (Czech) Currently translated at 93.8% (1411 of 1504 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/ * Translated using Weblate (Czech) Currently translated at 93.8% (1411 of 1504 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/ * Translated using Weblate (French) Currently translated at 100.0% (1504 of 1504 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fr/ * Translated using Weblate (Italian) Currently translated at 100.0% (1504 of 1504 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1504 of 1504 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Czech) Currently translated at 93.8% (1412 of 1504 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/ * Translated using Weblate (Czech) Currently translated at 93.8% (1412 of 1504 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/ * Translated using Weblate (Turkish) Currently translated at 81.7% (1230 of 1504 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/tr/ * Translated using Weblate (Dutch) Currently translated at 100.0% (1504 of 1504 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Dutch) Currently translated at 100.0% (1346 of 1346 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/ * Translated using Weblate (Turkish) Currently translated at 83.2% (1252 of 1504 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/tr/ * Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ * Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1507 of 1507 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Spanish) Currently translated at 100.0% (1507 of 1507 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (German) Currently translated at 100.0% (1507 of 1507 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (Italian) Currently translated at 100.0% (1508 of 1508 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1508 of 1508 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (German) Currently translated at 100.0% (1508 of 1508 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (Dutch) Currently translated at 100.0% (1508 of 1508 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Dutch) Currently translated at 100.0% (1346 of 1346 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/ * Translated using Weblate (Hungarian) Currently translated at 42.4% (640 of 1508 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 55.0% (830 of 1508 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Russian) Currently translated at 99.8% (1506 of 1508 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/ * Translated using Weblate (Arabic) Currently translated at 99.7% (1504 of 1508 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (French) Currently translated at 100.0% (1508 of 1508 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fr/ * Translated using Weblate (Spanish) Currently translated at 99.9% (1507 of 1508 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Spanish) Currently translated at 98.5% (1326 of 1346 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Dutch) Currently translated at 100.0% (1508 of 1508 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Dutch) Currently translated at 100.0% (1346 of 1346 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/ * Translated using Weblate (Hungarian) Currently translated at 80.4% (1213 of 1508 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Spanish) Currently translated at 100.0% (1508 of 1508 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (1346 of 1346 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Hungarian) Currently translated at 80.6% (1216 of 1508 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Bulgarian) Currently translated at 100.0% (1508 of 1508 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/bg/ * Translated using Weblate (Hungarian) Currently translated at 82.4% (1244 of 1508 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 86.4% (1304 of 1508 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Update translation files Updated by "Remove blank strings" hook in Weblate. Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ * Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1508 of 1508 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 99.9% (1507 of 1508 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/ * Translated using Weblate (Greek) Currently translated at 6.2% (94 of 1508 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/el/ * Translated using Weblate (Hungarian) Currently translated at 97.6% (1473 of 1508 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 97.6% (1473 of 1508 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Update translation files Updated by "Remove blank strings" hook in Weblate. Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ * Translated using Weblate (Greek) Currently translated at 9.3% (141 of 1508 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/el/ * Update translation files Updated by "Remove blank strings" hook in Weblate. Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ * Translated using Weblate (Greek) Currently translated at 13.3% (201 of 1508 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/el/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1508 of 1508 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (French) Currently translated at 100.0% (1510 of 1510 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fr/ * Translated using Weblate (Arabic) Currently translated at 99.7% (1506 of 1510 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Greek) Currently translated at 14.2% (215 of 1510 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/el/ * Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1510 of 1510 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1510 of 1510 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Greek) Currently translated at 14.8% (224 of 1510 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/el/ * Translated using Weblate (Greek) Currently translated at 1.1% (16 of 1346 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/el/ * Translated using Weblate (Italian) Currently translated at 100.0% (1510 of 1510 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% (1346 of 1346 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Dutch) Currently translated at 100.0% (1510 of 1510 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Spanish) Currently translated at 100.0% (1510 of 1510 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (French) Currently translated at 100.0% (1510 of 1510 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fr/ * Translated using Weblate (French) Currently translated at 100.0% (1346 of 1346 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/ * Translated using Weblate (Spanish) Currently translated at 100.0% (1510 of 1510 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (1346 of 1346 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1510 of 1510 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (German) Currently translated at 100.0% (1510 of 1510 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (Dutch) Currently translated at 100.0% (1346 of 1346 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/ * fix kotlin strings, import/export ios --------- Co-authored-by: Random Co-authored-by: Corey Lin Co-authored-by: xe1st Co-authored-by: Istvan Novak Co-authored-by: Hosted Weblate Co-authored-by: Maksym Lukashenko Co-authored-by: jonnysemon Co-authored-by: 小林照幸 Co-authored-by: inson1 Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com> Co-authored-by: Eric Co-authored-by: mlanp Co-authored-by: zenobit Co-authored-by: M1K4 Co-authored-by: No name Co-authored-by: summoner001 Co-authored-by: v1s7 Co-authored-by: elgratea Co-authored-by: diodepon --- .../bg.xcloc/Localized Contents/bg.xliff | 60 +- .../cs.xcloc/Localized Contents/cs.xliff | 60 +- .../de.xcloc/Localized Contents/de.xliff | 60 +- .../el.xcloc/Localized Contents/el.xliff | 66 +- .../en.xcloc/Localized Contents/en.xliff | 70 +- .../es.xcloc/Localized Contents/es.xliff | 186 +- .../fi.xcloc/Localized Contents/fi.xliff | 60 +- .../fr.xcloc/Localized Contents/fr.xliff | 64 +- .../it.xcloc/Localized Contents/it.xliff | 78 +- .../ja.xcloc/Localized Contents/ja.xliff | 63 +- .../nl.xcloc/Localized Contents/nl.xliff | 70 +- .../pl.xcloc/Localized Contents/pl.xliff | 60 +- .../ru.xcloc/Localized Contents/ru.xliff | 60 +- .../th.xcloc/Localized Contents/th.xliff | 60 +- .../tr.xcloc/Localized Contents/tr.xliff | 138 +- .../uk.xcloc/Localized Contents/uk.xliff | 98 +- .../Localized Contents/zh-Hans.xliff | 60 +- .../Localized Contents/zh-Hant.xliff | 30 + apps/ios/bg.lproj/Localizable.strings | 15 +- apps/ios/cs.lproj/Localizable.strings | 15 +- apps/ios/de.lproj/Localizable.strings | 15 +- apps/ios/es.lproj/Localizable.strings | 346 ++- .../es.lproj/SimpleX--iOS--InfoPlist.strings | 3 + apps/ios/fi.lproj/Localizable.strings | 15 +- apps/ios/fr.lproj/Localizable.strings | 21 +- apps/ios/it.lproj/Localizable.strings | 45 +- apps/ios/ja.lproj/Localizable.strings | 24 +- apps/ios/nl.lproj/Localizable.strings | 25 +- apps/ios/pl.lproj/Localizable.strings | 15 +- apps/ios/ru.lproj/Localizable.strings | 15 +- apps/ios/th.lproj/Localizable.strings | 15 +- apps/ios/uk.lproj/Localizable.strings | 111 +- apps/ios/zh-Hans.lproj/Localizable.strings | 15 +- .../commonMain/resources/MR/ar/strings.xml | 143 +- .../commonMain/resources/MR/bg/strings.xml | 146 +- .../commonMain/resources/MR/cs/strings.xml | 180 +- .../commonMain/resources/MR/de/strings.xml | 12 +- .../commonMain/resources/MR/el/strings.xml | 164 ++ .../commonMain/resources/MR/es/strings.xml | 166 +- .../commonMain/resources/MR/fr/strings.xml | 14 +- .../commonMain/resources/MR/hu/strings.xml | 1125 +++++++++- .../commonMain/resources/MR/it/strings.xml | 33 +- .../commonMain/resources/MR/nl/strings.xml | 20 +- .../commonMain/resources/MR/ru/strings.xml | 7 + .../commonMain/resources/MR/tr/strings.xml | 240 ++- .../commonMain/resources/MR/uk/strings.xml | 1878 +++++++++-------- .../resources/MR/zh-rCN/strings.xml | 11 + 47 files changed, 4514 insertions(+), 1663 deletions(-) diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index 7a2afea082..44f2d878e6 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -1072,6 +1072,10 @@ Чатът е спрян No comment provided by engineer. + + Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat. + No comment provided by engineer. + Chat preferences Чат настройки @@ -2015,6 +2019,10 @@ This cannot be undone! Криптирано съобщение или друго събитие notification + + Encrypted message: app is stopped + notification + Encrypted message: database error Криптирано съобщение: грешка в базата данни @@ -2240,6 +2248,10 @@ This cannot be undone! Грешка при зареждане на %@ сървъри No comment provided by engineer. + + Error opening chat + No comment provided by engineer. + Error receiving file Грешка при получаване на файл @@ -2918,6 +2930,10 @@ This cannot be undone! Invalid name! No comment provided by engineer. + + Invalid response + No comment provided by engineer. + Invalid server address! Невалиден адрес на сървъра! @@ -3118,6 +3134,11 @@ This is your link for group %@! Съобщения на живо No comment provided by engineer. + + Local + Локално + No comment provided by engineer. + Local name Локално име @@ -3485,11 +3506,6 @@ This is your link for group %@! Изключено No comment provided by engineer. - - Off (Local) - Изключено (Локално) - No comment provided by engineer. - Ok Ок @@ -3629,9 +3645,8 @@ This is your link for group %@! Протокол и код с отворен код – всеки може да оперира собствени сървъри. No comment provided by engineer. - - Opening database… - Отваряне на база данни… + + Opening app… No comment provided by engineer. @@ -3733,6 +3748,11 @@ This is your link for group %@! Моля, проверете вашите настройки и тези вашия за контакт. No comment provided by engineer. + + Please contact developers. +Error: %@ + No comment provided by engineer. + Please contact group admin. Моля, свържете се с груповия администартор. @@ -4707,6 +4727,10 @@ This is your link for group %@! Започни чат No comment provided by engineer. + + Start chat? + No comment provided by engineer. + Start migration Започни миграция @@ -5106,11 +5130,6 @@ You will be prompted to complete authentication before this feature is enabled.< Изключи No comment provided by engineer. - - Turn off notifications? - Изключи известията? - No comment provided by engineer. - Turn on Включи @@ -5307,6 +5326,10 @@ To connect, please ask your contact to create another connection link and check Използвай нов инкогнито профил No comment provided by engineer. + + Use only local notifications? + No comment provided by engineer. + Use server Използвай сървър @@ -5581,6 +5604,10 @@ Repeat join request? Можете да скриете или заглушите известията за потребителски профил - плъзнете надясно. No comment provided by engineer. + + You can make it visible to your SimpleX contacts via Settings. + No comment provided by engineer. + You can now send messages to %@ Вече можете да изпращате съобщения до %@ @@ -5805,13 +5832,6 @@ You can cancel this connection and remove the contact (and try later with a new Вашите контакти могат да позволят пълното изтриване на съобщението. No comment provided by engineer. - - Your contacts in SimpleX will see it. -You can change it in Settings. - Вашите контакти в SimpleX ще го видят. -Можете да го промените в Настройки. - No comment provided by engineer. - Your contacts will remain connected. Вашите контакти ще останат свързани. diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index be8b23658b..f0abba7bb2 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -1072,6 +1072,10 @@ Chat je zastaven No comment provided by engineer. + + Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat. + No comment provided by engineer. + Chat preferences Předvolby chatu @@ -2015,6 +2019,10 @@ This cannot be undone! Šifrovaná zpráva nebo jiná událost notification + + Encrypted message: app is stopped + notification + Encrypted message: database error Šifrovaná zpráva: chyba databáze @@ -2240,6 +2248,10 @@ This cannot be undone! Chyba načítání %@ serverů No comment provided by engineer. + + Error opening chat + No comment provided by engineer. + Error receiving file Chyba při příjmu souboru @@ -2918,6 +2930,10 @@ This cannot be undone! Invalid name! No comment provided by engineer. + + Invalid response + No comment provided by engineer. + Invalid server address! Neplatná adresa serveru! @@ -3118,6 +3134,11 @@ This is your link for group %@! Živé zprávy No comment provided by engineer. + + Local + Místní + No comment provided by engineer. + Local name Místní název @@ -3485,11 +3506,6 @@ This is your link for group %@! Vypnout No comment provided by engineer. - - Off (Local) - Vypnuto (místní) - No comment provided by engineer. - Ok Ok @@ -3629,9 +3645,8 @@ This is your link for group %@! Protokol a kód s otevřeným zdrojovým kódem - servery může provozovat kdokoli. No comment provided by engineer. - - Opening database… - Otvírání databáze… + + Opening app… No comment provided by engineer. @@ -3733,6 +3748,11 @@ This is your link for group %@! Zkontrolujte prosím nastavení své i svého kontaktu. No comment provided by engineer. + + Please contact developers. +Error: %@ + No comment provided by engineer. + Please contact group admin. Kontaktujte prosím správce skupiny. @@ -4707,6 +4727,10 @@ This is your link for group %@! Začít chat No comment provided by engineer. + + Start chat? + No comment provided by engineer. + Start migration Zahájit přenesení @@ -5106,11 +5130,6 @@ Před zapnutím této funkce budete vyzváni k dokončení ověření. Vypnout No comment provided by engineer. - - Turn off notifications? - Vypnout upozornění? - No comment provided by engineer. - Turn on Zapnout @@ -5307,6 +5326,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Použít nový inkognito profil No comment provided by engineer. + + Use only local notifications? + No comment provided by engineer. + Use server Použít server @@ -5581,6 +5604,10 @@ Repeat join request? Profil uživatele můžete skrýt nebo ztlumit - přejeďte prstem doprava. No comment provided by engineer. + + You can make it visible to your SimpleX contacts via Settings. + No comment provided by engineer. + You can now send messages to %@ Nyní můžete posílat zprávy %@ @@ -5805,13 +5832,6 @@ Toto připojení můžete zrušit a kontakt odebrat (a zkusit to později s nov Vaše kontakty mohou povolit úplné mazání zpráv. No comment provided by engineer. - - Your contacts in SimpleX will see it. -You can change it in Settings. - Vaše kontakty v SimpleX ji uvidí. -Můžete ji změnit v Nastavení. - No comment provided by engineer. - Your contacts will remain connected. Vaše kontakty zůstanou připojeny. 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 75f70f7ad1..dda89f5c0c 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -1094,6 +1094,10 @@ Der Chat ist beendet No comment provided by engineer. + + Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat. + No comment provided by engineer. + Chat preferences Chat-Präferenzen @@ -2063,6 +2067,10 @@ Das kann nicht rückgängig gemacht werden! Verschlüsselte Nachricht oder ein anderes Ereignis notification + + Encrypted message: app is stopped + notification + Encrypted message: database error Verschlüsselte Nachricht: Datenbankfehler @@ -2293,6 +2301,10 @@ Das kann nicht rückgängig gemacht werden! Fehler beim Laden von %@ Servern No comment provided by engineer. + + Error opening chat + No comment provided by engineer. + Error receiving file Fehler beim Empfangen der Datei @@ -2980,6 +2992,10 @@ Das kann nicht rückgängig gemacht werden! Ungültiger Name! No comment provided by engineer. + + Invalid response + No comment provided by engineer. + Invalid server address! Ungültige Serveradresse! @@ -3188,6 +3204,11 @@ Das ist Ihr Link für die Gruppe %@! Live Nachrichten No comment provided by engineer. + + Local + Lokal + No comment provided by engineer. + Local name Lokaler Name @@ -3557,11 +3578,6 @@ Das ist Ihr Link für die Gruppe %@! Aus No comment provided by engineer. - - Off (Local) - Aus (Lokal) - No comment provided by engineer. - Ok Ok @@ -3702,9 +3718,8 @@ Das ist Ihr Link für die Gruppe %@! Open-Source-Protokoll und -Code – Jede Person kann ihre eigenen Server aufsetzen und nutzen. No comment provided by engineer. - - Opening database… - Öffne Datenbank … + + Opening app… No comment provided by engineer. @@ -3807,6 +3822,11 @@ Das ist Ihr Link für die Gruppe %@! Bitte überprüfen sie sowohl Ihre, als auch die Präferenzen Ihres Kontakts. No comment provided by engineer. + + Please contact developers. +Error: %@ + No comment provided by engineer. + Please contact group admin. Bitte kontaktieren Sie den Gruppen-Administrator. @@ -4787,6 +4807,10 @@ Das ist Ihr Link für die Gruppe %@! Starten Sie den Chat No comment provided by engineer. + + Start chat? + No comment provided by engineer. + Start migration Starten Sie die Migration @@ -5191,11 +5215,6 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt Abschalten No comment provided by engineer. - - Turn off notifications? - Benachrichtigungen abschalten? - No comment provided by engineer. - Turn on Einschalten @@ -5398,6 +5417,10 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Nutzen Sie das neue Inkognito-Profil No comment provided by engineer. + + Use only local notifications? + No comment provided by engineer. + Use server Server nutzen @@ -5685,6 +5708,10 @@ Verbindungsanfrage wiederholen? Sie können ein Benutzerprofil verbergen oder stummschalten - wischen Sie es nach rechts. No comment provided by engineer. + + You can make it visible to your SimpleX contacts via Settings. + No comment provided by engineer. + You can now send messages to %@ Sie können nun Nachrichten an %@ versenden @@ -5914,13 +5941,6 @@ Sie können diese Verbindung abbrechen und den Kontakt entfernen (und es später Ihre Kontakte können die unwiederbringliche Löschung von Nachrichten erlauben. No comment provided by engineer. - - Your contacts in SimpleX will see it. -You can change it in Settings. - Ihre Kontakte in SimpleX werden es sehen. -Sie können es in den Einstellungen ändern. - No comment provided by engineer. - Your contacts will remain connected. Ihre Kontakte bleiben verbunden. diff --git a/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff b/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff index 7649b595cd..c6dcc84e99 100644 --- a/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff +++ b/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff @@ -31,32 +31,39 @@ Available in v5.1 ( No comment provided by engineer. - + (can be copied) + (μπορεί να αντιγραφή) No comment provided by engineer. - + !1 colored! + !1 έγχρωμο! No comment provided by engineer. - + #secret# + #μυστικό# No comment provided by engineer. - + %@ + %@ No comment provided by engineer. - + %@ %@ + %@ %@ No comment provided by engineer. - + %@ / %@ + %@ / %@ No comment provided by engineer. - + %@ is connected! + %@ είναι συνδεδεμένο! notification title @@ -4162,6 +4169,51 @@ SimpleX servers cannot see your profile. \~strike~ No comment provided by engineer. + + %@ connected + %@ συνδεδεμένο + No comment provided by engineer. + + + # %@ + # %@ + copied message info title, # <title> + + + %@ and %@ + %@ και %@ + No comment provided by engineer. + + + %1$@ at %2$@: + %1$@ στις %2$@: + copied message info, <sender> at <time> + + + ## History + ## Ιστορικό + copied message info + + + ## In reply to + ## Ως απαντηση σε + copied message info + + + %@ (current) + %@ (τωρινό) + No comment provided by engineer. + + + %@ (current): + %@ (τωρινό): + copied message info + + + %@ and %@ connected + %@ και %@ συνδεδεμένο + No comment provided by engineer. + diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 23498b2128..4544b823f7 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -1094,6 +1094,11 @@ Chat is stopped No comment provided by engineer. + + Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat. + Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat. + No comment provided by engineer. + Chat preferences Chat preferences @@ -2063,6 +2068,11 @@ This cannot be undone! Encrypted message or another event notification + + Encrypted message: app is stopped + Encrypted message: app is stopped + notification + Encrypted message: database error Encrypted message: database error @@ -2293,6 +2303,11 @@ This cannot be undone! Error loading %@ servers No comment provided by engineer. + + Error opening chat + Error opening chat + No comment provided by engineer. + Error receiving file Error receiving file @@ -2980,6 +2995,11 @@ This cannot be undone! Invalid name! No comment provided by engineer. + + Invalid response + Invalid response + No comment provided by engineer. + Invalid server address! Invalid server address! @@ -3188,6 +3208,11 @@ This is your link for group %@! Live messages No comment provided by engineer. + + Local + Local + No comment provided by engineer. + Local name Local name @@ -3557,11 +3582,6 @@ This is your link for group %@! Off No comment provided by engineer. - - Off (Local) - Off (Local) - No comment provided by engineer. - Ok Ok @@ -3702,9 +3722,9 @@ This is your link for group %@! Open-source protocol and code – anybody can run the servers. No comment provided by engineer. - - Opening database… - Opening database… + + Opening app… + Opening app… No comment provided by engineer. @@ -3807,6 +3827,13 @@ This is your link for group %@! Please check yours and your contact preferences. No comment provided by engineer. + + Please contact developers. +Error: %@ + Please contact developers. +Error: %@ + No comment provided by engineer. + Please contact group admin. Please contact group admin. @@ -4787,6 +4814,11 @@ This is your link for group %@! Start chat No comment provided by engineer. + + Start chat? + Start chat? + No comment provided by engineer. + Start migration Start migration @@ -5191,11 +5223,6 @@ You will be prompted to complete authentication before this feature is enabled.< Turn off No comment provided by engineer. - - Turn off notifications? - Turn off notifications? - No comment provided by engineer. - Turn on Turn on @@ -5398,6 +5425,11 @@ To connect, please ask your contact to create another connection link and check Use new incognito profile No comment provided by engineer. + + Use only local notifications? + Use only local notifications? + No comment provided by engineer. + Use server Use server @@ -5685,6 +5717,11 @@ Repeat join request? You can hide or mute a user profile - swipe it to the right. No comment provided by engineer. + + You can make it visible to your SimpleX contacts via Settings. + You can make it visible to your SimpleX contacts via Settings. + No comment provided by engineer. + You can now send messages to %@ You can now send messages to %@ @@ -5914,13 +5951,6 @@ You can cancel this connection and remove the contact (and try later with a new Your contacts can allow full message deletion. No comment provided by engineer. - - Your contacts in SimpleX will see it. -You can change it in Settings. - Your contacts in SimpleX will see it. -You can change it in Settings. - No comment provided by engineer. - Your contacts will remain connected. Your contacts will remain connected. diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index ccc7ee3446..5b3d820d6f 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -89,6 +89,7 @@ %@ and %@ + %@ y %@ No comment provided by engineer. @@ -103,6 +104,7 @@ %@ connected + %@ conectado No comment provided by engineer. @@ -132,6 +134,7 @@ %@, %@ and %lld members + %@, %@ y %lld miembro(s) más No comment provided by engineer. @@ -201,6 +204,7 @@ %lld group events + %lld evento(s) de grupo No comment provided by engineer. @@ -210,14 +214,17 @@ %lld messages blocked + %lld mensaje(s) bloqueado(s) No comment provided by engineer. %lld messages marked deleted + %lld mensaje(s) marcado(s) eliminado(s) No comment provided by engineer. %lld messages moderated by %@ + %lld mensaje(s) moderado(s) por %@ No comment provided by engineer. @@ -292,10 +299,12 @@ (new) + (nuevo) No comment provided by engineer. (this device v%@) + (este dispositivo v%@) No comment provided by engineer. @@ -390,6 +399,9 @@ - optionally notify deleted contacts. - profile names with spaces. - and more! + - notificar opcionalmente a los contactos eliminados. +- nombres de perfil con espacios. +- ¡...y más! No comment provided by engineer. @@ -408,6 +420,7 @@ 0 sec + 0 seg time to disappear @@ -637,6 +650,7 @@ All new messages from %@ will be hidden! + ¡Los mensajes nuevos de %@ estarán ocultos! No comment provided by engineer. @@ -746,10 +760,12 @@ Already connecting! + ¡Ya en proceso de conexión! No comment provided by engineer. Already joining the group! + ¡Ya en proceso de unirse al grupo! No comment provided by engineer. @@ -874,6 +890,7 @@ Bad desktop address + Dirección ordenador incorrecta No comment provided by engineer. @@ -888,6 +905,7 @@ Better groups + Grupos mejorados No comment provided by engineer. @@ -897,18 +915,22 @@ Block + Bloquear No comment provided by engineer. Block group members + Bloquear miembros del grupo No comment provided by engineer. Block member + Bloquear miembro No comment provided by engineer. Block member? + ¿Bloquear miembro? No comment provided by engineer. @@ -1072,6 +1094,10 @@ Chat está detenido No comment provided by engineer. + + Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat. + No comment provided by engineer. + Chat preferences Preferencias de Chat @@ -1174,6 +1200,7 @@ Connect automatically + Conectar automáticamente No comment provided by engineer. @@ -1183,24 +1210,31 @@ Connect to desktop + Conectar con ordenador No comment provided by engineer. Connect to yourself? + ¿Conectarte a tí mismo? No comment provided by engineer. Connect to yourself? This is your own SimpleX address! + ¿Conectarte a tí mismo? +¡Esta es tu propia dirección SimpleX! No comment provided by engineer. Connect to yourself? This is your own one-time link! + ¿Conectarte a tí mismo? +¡Este es tu propio enlace de un solo uso! No comment provided by engineer. Connect via contact address + Conectar mediante dirección de contacto No comment provided by engineer. @@ -1220,14 +1254,17 @@ This is your own one-time link! Connect with %@ + Conectar con %@ No comment provided by engineer. Connected desktop + Ordenador conectado No comment provided by engineer. Connected to desktop + Conectado con ordenador No comment provided by engineer. @@ -1242,6 +1279,7 @@ This is your own one-time link! Connecting to desktop + Conectando con ordenador No comment provided by engineer. @@ -1266,6 +1304,7 @@ This is your own one-time link! Connection terminated + Conexión finalizada No comment provided by engineer. @@ -1335,6 +1374,7 @@ This is your own one-time link! Correct name to %@? + ¿Corregir el nombre a %@? No comment provided by engineer. @@ -1344,11 +1384,12 @@ This is your own one-time link! Create SimpleX address - Crear tu dirección SimpleX + Crear dirección SimpleX No comment provided by engineer. Create a group using a random profile. + Crear grupo usando perfil aleatorio. No comment provided by engineer. @@ -1363,6 +1404,7 @@ This is your own one-time link! Create group + Crear grupo No comment provided by engineer. @@ -1387,6 +1429,7 @@ This is your own one-time link! Create profile + Crear perfil No comment provided by engineer. @@ -1549,6 +1592,7 @@ This is your own one-time link! Delete %lld messages? + ¿Elimina %lld mensajes? No comment provided by engineer. @@ -1578,6 +1622,7 @@ This is your own one-time link! Delete and notify contact + Eliminar y notificar contacto No comment provided by engineer. @@ -1613,6 +1658,8 @@ This is your own one-time link! Delete contact? This cannot be undone! + ¿Eliminar contacto? +¡No podrá deshacerse! No comment provided by engineer. @@ -1757,14 +1804,17 @@ This cannot be undone! Desktop address + Dirección ordenador No comment provided by engineer. Desktop app version %@ is not compatible with this app. + La versión de aplicación del ordenador %" no es compatible con esta aplicación. No comment provided by engineer. Desktop devices + Ordenadores No comment provided by engineer. @@ -1859,6 +1909,7 @@ This cannot be undone! Disconnect desktop? + ¿Desconectar ordenador? No comment provided by engineer. @@ -1868,6 +1919,7 @@ This cannot be undone! Discover via local network + Descubrir en red local No comment provided by engineer. @@ -2015,6 +2067,10 @@ This cannot be undone! Mensaje cifrado u otro evento notification + + Encrypted message: app is stopped + notification + Encrypted message: database error Mensaje cifrado: error base de datos @@ -2042,10 +2098,12 @@ This cannot be undone! Encryption re-negotiation error + Error de renegociación de cifrado message decrypt error item Encryption re-negotiation failed. + Renegociación de cifrado fallida. No comment provided by engineer. @@ -2060,6 +2118,7 @@ This cannot be undone! Enter group name… + Nombre del grupo… No comment provided by engineer. @@ -2079,6 +2138,7 @@ This cannot be undone! Enter this device name… + Nombre de este dispositivo… No comment provided by engineer. @@ -2093,6 +2153,7 @@ This cannot be undone! Enter your name… + Introduce tu nombre… No comment provided by engineer. @@ -2240,6 +2301,10 @@ This cannot be undone! Error al cargar servidores %@ No comment provided by engineer. + + Error opening chat + No comment provided by engineer. + Error receiving file Error al recibir archivo @@ -2372,6 +2437,7 @@ This cannot be undone! Expand + Expandir chat item action @@ -2406,6 +2472,7 @@ This cannot be undone! Faster joining and more reliable messages. + Mensajería más segura y conexión más rápida. No comment provided by engineer. @@ -2505,6 +2572,7 @@ This cannot be undone! Found desktop + Ordenador encontrado No comment provided by engineer. @@ -2529,6 +2597,7 @@ This cannot be undone! Fully decentralized – visible only to members. + Completamente descentralizado: sólo visible a los miembros. No comment provided by engineer. @@ -2553,10 +2622,12 @@ This cannot be undone! Group already exists + El grupo ya existe No comment provided by engineer. Group already exists! + ¡El grupo ya existe! No comment provided by engineer. @@ -2831,6 +2902,7 @@ This cannot be undone! Incognito groups + Grupos incógnito No comment provided by engineer. @@ -2865,6 +2937,7 @@ This cannot be undone! Incompatible version + Versión incompatible No comment provided by engineer. @@ -2916,6 +2989,11 @@ This cannot be undone! Invalid name! + ¡Nombre no válido! + No comment provided by engineer. + + + Invalid response No comment provided by engineer. @@ -3011,6 +3089,7 @@ This cannot be undone! Join group? + ¿Unirse al grupo? No comment provided by engineer. @@ -3020,11 +3099,14 @@ This cannot be undone! Join with current profile + Unirte con el perfil actual No comment provided by engineer. Join your group? This is your link for group %@! + ¿Unirse a tu grupo? +¡Este es tu enlace para el grupo %@! No comment provided by engineer. @@ -3034,6 +3116,7 @@ This is your link for group %@! Keep the app open to use it from desktop + Mantén la aplicación abierta para usarla desde el ordenador No comment provided by engineer. @@ -3098,14 +3181,17 @@ This is your link for group %@! Link mobile and desktop apps! 🔗 + ¡Enlazar aplicación móvil con ordenador! 🔗 No comment provided by engineer. Linked desktop options + Opciones ordenador enlazado No comment provided by engineer. Linked desktops + Ordenadores enlazados No comment provided by engineer. @@ -3118,6 +3204,11 @@ This is your link for group %@! Mensajes en vivo No comment provided by engineer. + + Local + Local + No comment provided by engineer. + Local name Nombre local @@ -3260,6 +3351,7 @@ This is your link for group %@! Messages from %@ will be shown! + ¡Los mensajes de %@ serán mostrados! No comment provided by engineer. @@ -3459,6 +3551,7 @@ This is your link for group %@! Not compatible! + ¡No compatible! No comment provided by engineer. @@ -3485,11 +3578,6 @@ This is your link for group %@! Desactivado No comment provided by engineer. - - Off (Local) - Desactivado (Local) - No comment provided by engineer. - Ok Ok @@ -3617,6 +3705,7 @@ This is your link for group %@! Open group + Grupo abierto No comment provided by engineer. @@ -3629,9 +3718,8 @@ This is your link for group %@! Protocolo y código abiertos: cualquiera puede usar los servidores. No comment provided by engineer. - - Opening database… - Abriendo base de datos… + + Opening app… No comment provided by engineer. @@ -3681,6 +3769,7 @@ This is your link for group %@! Paste desktop address + Pegar dirección de ordenador No comment provided by engineer. @@ -3733,6 +3822,11 @@ This is your link for group %@! Comprueba tus preferencias y las de tu contacto. No comment provided by engineer. + + Please contact developers. +Error: %@ + No comment provided by engineer. + Please contact group admin. Póngase en contacto con el administrador del grupo. @@ -3830,10 +3924,12 @@ This is your link for group %@! Profile name + Nombre del perfil No comment provided by engineer. Profile name: + Nombre del perfil: No comment provided by engineer. @@ -4083,10 +4179,12 @@ This is your link for group %@! Repeat connection request? + ¿Repetir solicitud de conexión? No comment provided by engineer. Repeat join request? + ¿Repetir solicitud de admisión? No comment provided by engineer. @@ -4276,6 +4374,7 @@ This is your link for group %@! Scan QR code from desktop + Escanear código QR desde ordenador No comment provided by engineer. @@ -4500,6 +4599,7 @@ This is your link for group %@! Session code + Código de sesión No comment provided by engineer. @@ -4707,6 +4807,10 @@ This is your link for group %@! Iniciar chat No comment provided by engineer. + + Start chat? + No comment provided by engineer. + Start migration Iniciar migración @@ -4814,6 +4918,7 @@ This is your link for group %@! Tap to Connect + Pulsa para conectar No comment provided by engineer. @@ -5000,6 +5105,7 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. This device name + Nombre del dispositivo No comment provided by engineer. @@ -5014,10 +5120,12 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. This is your own SimpleX address! + ¡Esta es tu propia dirección SimpleX! No comment provided by engineer. This is your own one-time link! + ¡Este es tu propio enlace de un solo uso! No comment provided by engineer. @@ -5037,6 +5145,7 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. To hide unwanted messages. + Para ocultar mensajes no deseados. No comment provided by engineer. @@ -5046,7 +5155,7 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - Para proteger la privacidad, en lugar de los identificadores de usuario que usan el resto de plataformas, SimpleX dispone de identificadores para las colas de mensajes, independientes para cada uno de tus contactos. + Para proteger tu privacidad, en lugar de los identificadores de usuario que usan el resto de plataformas, SimpleX dispone de identificadores para las colas de mensajes, independientes para cada uno de tus contactos. No comment provided by engineer. @@ -5057,7 +5166,7 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. To protect your information, turn on SimpleX Lock. You will be prompted to complete authentication before this feature is enabled. - Para proteger tu información, activa Bloqueo SimpleX. + Para proteger tu información, activa el Bloqueo SimpleX. Se te pedirá que completes la autenticación antes de activar esta función. No comment provided by engineer. @@ -5106,11 +5215,6 @@ Se te pedirá que completes la autenticación antes de activar esta función.Desactivar No comment provided by engineer. - - Turn off notifications? - ¿Desactivar notificaciones? - No comment provided by engineer. - Turn on Activar @@ -5123,14 +5227,17 @@ Se te pedirá que completes la autenticación antes de activar esta función. Unblock + Desbloquear No comment provided by engineer. Unblock member + Desbloquear miembro No comment provided by engineer. Unblock member? + ¿Desbloquear miembro? No comment provided by engineer. @@ -5198,10 +5305,12 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb Unlink + Desenlazar No comment provided by engineer. Unlink desktop? + ¿Desenlazar ordenador? No comment provided by engineer. @@ -5296,6 +5405,7 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb Use from desktop + Usar desde ordenador No comment provided by engineer. @@ -5308,6 +5418,10 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb Usar nuevo perfil incógnito No comment provided by engineer. + + Use only local notifications? + No comment provided by engineer. + Use server Usar servidor @@ -5330,10 +5444,12 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb Verify code with desktop + Verificar código con ordenador No comment provided by engineer. Verify connection + Verificar conexión No comment provided by engineer. @@ -5343,6 +5459,7 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb Verify connections + Verificar conexiones No comment provided by engineer. @@ -5357,6 +5474,7 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb Via secure quantum resistant protocol. + Mediante protocolo seguro de resistencia cuántica. No comment provided by engineer. @@ -5411,6 +5529,7 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb Waiting for desktop... + Esperando ordenador... No comment provided by engineer. @@ -5515,31 +5634,39 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb You are already connecting to %@. + Ya estás conectando con %@. No comment provided by engineer. You are already connecting via this one-time link! + ¡Ya estás conectando mediante este enlace de un solo uso! No comment provided by engineer. You are already in group %@. + Ya estás en el grupo %@. No comment provided by engineer. You are already joining the group %@. + Ya estás uniéndote al grupo %@. No comment provided by engineer. You are already joining the group via this link! + ¡Ya estás uniéndote al grupo mediante este enlace! No comment provided by engineer. You are already joining the group via this link. + Ya estás uniéndote al grupo mediante este enlace. No comment provided by engineer. You are already joining the group! Repeat join request? + ¡En proceso de unirte al grupo! +¿Repetir solicitud de admisión? No comment provided by engineer. @@ -5582,6 +5709,10 @@ Repeat join request? Puedes ocultar o silenciar un perfil deslizándolo a la derecha. No comment provided by engineer. + + You can make it visible to your SimpleX contacts via Settings. + No comment provided by engineer. + You can now send messages to %@ Ya puedes enviar mensajes a %@ @@ -5639,11 +5770,14 @@ Repeat join request? You have already requested connection via this address! + ¡Ya has solicitado la conexión mediante esta dirección! No comment provided by engineer. You have already requested connection! Repeat connection request? + Ya has solicitado la conexión +¿Repetir solicitud? No comment provided by engineer. @@ -5698,6 +5832,7 @@ Repeat connection request? You will be connected when group link host's device is online, please wait or check later! + Te conectarás cuando el dispositivo propietario del grupo esté en línea, por favor espera o compruébalo más tarde. No comment provided by engineer. @@ -5717,6 +5852,7 @@ Repeat connection request? You will connect to all group members. + Te conectarás con todos los miembros del grupo. No comment provided by engineer. @@ -5806,13 +5942,6 @@ Puedes cancelar esta conexión y eliminar el contacto (e intentarlo más tarde c Tus contactos pueden permitir la eliminación completa de mensajes. No comment provided by engineer. - - Your contacts in SimpleX will see it. -You can change it in Settings. - Tus contactos en SimpleX lo verán. -Puedes cambiarlo en Configuración. - No comment provided by engineer. - Your contacts will remain connected. Tus contactos permanecerán conectados. @@ -5840,6 +5969,7 @@ Puedes cambiarlo en Configuración. Your profile + Tu perfil No comment provided by engineer. @@ -5936,6 +6066,7 @@ Los servidores de SimpleX no pueden ver tu perfil. and %lld other events + y %lld evento(s) más No comment provided by engineer. @@ -5945,6 +6076,7 @@ Los servidores de SimpleX no pueden ver tu perfil. author + autor member role @@ -5959,6 +6091,7 @@ Los servidores de SimpleX no pueden ver tu perfil. blocked + bloqueado No comment provided by engineer. @@ -6133,6 +6266,7 @@ Los servidores de SimpleX no pueden ver tu perfil. deleted contact + contacto eliminado rcv direct event chat item @@ -6317,7 +6451,7 @@ Los servidores de SimpleX no pueden ver tu perfil. invited to connect - invitado a conectarse + invitación a conectarse chat list item title @@ -6529,6 +6663,7 @@ Los servidores de SimpleX no pueden ver tu perfil. v%@ + v%@ No comment provided by engineer. @@ -6628,7 +6763,7 @@ Los servidores de SimpleX no pueden ver tu perfil. you shared one-time link - has compartido un enlace de un uso + enlace de un solo uso chat list item description @@ -6670,6 +6805,7 @@ Los servidores de SimpleX no pueden ver tu perfil. SimpleX uses local network access to allow using user chat profile via desktop app on the same network. + SimpleX utiliza el acceso a la red local para abrir el perfil de chat en la aplicación de ordenador en la misma red. Privacy - Local Network Usage Description diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index cf161efae4..928666ddad 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -1067,6 +1067,10 @@ Chat on pysäytetty No comment provided by engineer. + + Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat. + No comment provided by engineer. + Chat preferences Chat-asetukset @@ -2009,6 +2013,10 @@ This cannot be undone! Salattu viesti tai muu tapahtuma notification + + Encrypted message: app is stopped + notification + Encrypted message: database error Salattu viesti: tietokantavirhe @@ -2233,6 +2241,10 @@ This cannot be undone! Virhe %@-palvelimien lataamisessa No comment provided by engineer. + + Error opening chat + No comment provided by engineer. + Error receiving file Virhe tiedoston vastaanottamisessa @@ -2910,6 +2922,10 @@ This cannot be undone! Invalid name! No comment provided by engineer. + + Invalid response + No comment provided by engineer. + Invalid server address! Virheellinen palvelinosoite! @@ -3110,6 +3126,11 @@ This is your link for group %@! Live-viestit No comment provided by engineer. + + Local + Paikallinen + No comment provided by engineer. + Local name Paikallinen nimi @@ -3476,11 +3497,6 @@ This is your link for group %@! Pois No comment provided by engineer. - - Off (Local) - Pois (Paikallinen) - No comment provided by engineer. - Ok Ok @@ -3619,9 +3635,8 @@ This is your link for group %@! Avoimen lähdekoodin protokolla ja koodi - kuka tahansa voi käyttää palvelimia. No comment provided by engineer. - - Opening database… - Avataan tietokantaa… + + Opening app… No comment provided by engineer. @@ -3723,6 +3738,11 @@ This is your link for group %@! Tarkista omasi ja kontaktin asetukset. No comment provided by engineer. + + Please contact developers. +Error: %@ + No comment provided by engineer. + Please contact group admin. Ota yhteyttä ryhmän ylläpitäjään. @@ -4695,6 +4715,10 @@ This is your link for group %@! Aloita keskustelu No comment provided by engineer. + + Start chat? + No comment provided by engineer. + Start migration Aloita siirto @@ -5093,11 +5117,6 @@ Sinua kehotetaan suorittamaan todennus loppuun, ennen kuin tämä ominaisuus ote Sammuta No comment provided by engineer. - - Turn off notifications? - Kytke ilmoitukset pois päältä? - No comment provided by engineer. - Turn on Kytke päälle @@ -5294,6 +5313,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Käytä uutta incognito-profiilia No comment provided by engineer. + + Use only local notifications? + No comment provided by engineer. + Use server Käytä palvelinta @@ -5568,6 +5591,10 @@ Repeat join request? Voit piilottaa tai mykistää käyttäjäprofiilin pyyhkäisemällä sitä oikealle. No comment provided by engineer. + + You can make it visible to your SimpleX contacts via Settings. + No comment provided by engineer. + You can now send messages to %@ Voit nyt lähettää viestejä %@:lle @@ -5792,13 +5819,6 @@ Voit peruuttaa tämän yhteyden ja poistaa kontaktin (ja yrittää myöhemmin uu Kontaktisi voivat sallia viestien täydellisen poistamisen. No comment provided by engineer. - - Your contacts in SimpleX will see it. -You can change it in Settings. - Kontaktisi SimpleX:ssä näkevät sen. -Voit muuttaa sitä Asetuksista. - No comment provided by engineer. - Your contacts will remain connected. Kontaktisi pysyvät yhdistettyinä. diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 153d98be3c..6ec667ed91 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -1094,6 +1094,10 @@ Le chat est arrêté No comment provided by engineer. + + Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat. + No comment provided by engineer. + Chat preferences Préférences de chat @@ -2063,6 +2067,10 @@ Cette opération ne peut être annulée ! Message chiffrée ou autre événement notification + + Encrypted message: app is stopped + notification + Encrypted message: database error Message chiffrée : erreur de base de données @@ -2293,6 +2301,10 @@ Cette opération ne peut être annulée ! Erreur lors du chargement des serveurs %@ No comment provided by engineer. + + Error opening chat + No comment provided by engineer. + Error receiving file Erreur lors de la réception du fichier @@ -2980,6 +2992,10 @@ Cette opération ne peut être annulée ! Nom invalide ! No comment provided by engineer. + + Invalid response + No comment provided by engineer. + Invalid server address! Adresse de serveur invalide ! @@ -3188,6 +3204,11 @@ Voici votre lien pour le groupe %@ ! Messages dynamiques No comment provided by engineer. + + Local + Local + No comment provided by engineer. + Local name Nom local @@ -3557,11 +3578,6 @@ Voici votre lien pour le groupe %@ ! Off No comment provided by engineer. - - Off (Local) - Off (Local) - No comment provided by engineer. - Ok Ok @@ -3702,9 +3718,8 @@ Voici votre lien pour le groupe %@ ! Protocole et code open-source – n'importe qui peut heberger un serveur. No comment provided by engineer. - - Opening database… - Ouverture de la base de données… + + Opening app… No comment provided by engineer. @@ -3807,6 +3822,11 @@ Voici votre lien pour le groupe %@ ! Veuillez vérifier vos préférences ainsi que celles de votre contact. No comment provided by engineer. + + Please contact developers. +Error: %@ + No comment provided by engineer. + Please contact group admin. Veuillez contacter l'administrateur du groupe. @@ -4787,6 +4807,10 @@ Voici votre lien pour le groupe %@ ! Démarrer le chat No comment provided by engineer. + + Start chat? + No comment provided by engineer. + Start migration Démarrer la migration @@ -5191,11 +5215,6 @@ Vous serez invité à confirmer l'authentification avant que cette fonction ne s Désactiver No comment provided by engineer. - - Turn off notifications? - Désactiver les notifications ? - No comment provided by engineer. - Turn on Activer @@ -5398,6 +5417,10 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Utiliser un nouveau profil incognito No comment provided by engineer. + + Use only local notifications? + No comment provided by engineer. + Use server Utiliser ce serveur @@ -5685,6 +5708,10 @@ Répéter la demande d'adhésion ? Vous pouvez masquer ou mettre en sourdine un profil d'utilisateur - faites-le glisser vers la droite. No comment provided by engineer. + + You can make it visible to your SimpleX contacts via Settings. + No comment provided by engineer. + You can now send messages to %@ Vous pouvez maintenant envoyer des messages à %@ @@ -5914,13 +5941,6 @@ Vous pouvez annuler la connexion et supprimer le contact (et réessayer plus tar Vos contacts peuvent autoriser la suppression complète des messages. No comment provided by engineer. - - Your contacts in SimpleX will see it. -You can change it in Settings. - Vos contacts dans SimpleX la verront. -Vous pouvez modifier ce choix dans les Paramètres. - No comment provided by engineer. - Your contacts will remain connected. Vos contacts resteront connectés. @@ -6537,12 +6557,12 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. offered %@ - offert %@ + propose %@ feature offered item offered %1$@: %2$@ - offert %1$@ : %2$@ + propose %1$@ : %2$@ feature offered 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 bf1b1ee386..d1a17a3af2 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -94,7 +94,7 @@ %@ and %@ connected - %@ e %@ sono connessi/e + %@ e %@ si sono connessi/e No comment provided by engineer. @@ -139,7 +139,7 @@ %@, %@ and %lld other members connected - %@, %@ e altri %lld membri sono connessi + %@, %@ e altri %lld membri si sono connessi No comment provided by engineer. @@ -1094,6 +1094,10 @@ Chat fermata No comment provided by engineer. + + Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat. + No comment provided by engineer. + Chat preferences Preferenze della chat @@ -1181,7 +1185,7 @@ Confirm new passphrase… - Conferma password nuova… + Conferma nuova password… No comment provided by engineer. @@ -1196,6 +1200,7 @@ Connect automatically + Connetti automaticamente No comment provided by engineer. @@ -1914,6 +1919,7 @@ Non è reversibile! Discover via local network + Individua via rete locale No comment provided by engineer. @@ -2061,6 +2067,10 @@ Non è reversibile! Messaggio crittografato o altro evento notification + + Encrypted message: app is stopped + notification + Encrypted message: database error Messaggio crittografato: errore del database @@ -2291,6 +2301,10 @@ Non è reversibile! Errore nel caricamento dei server %@ No comment provided by engineer. + + Error opening chat + No comment provided by engineer. + Error receiving file Errore nella ricezione del file @@ -2558,6 +2572,7 @@ Non è reversibile! Found desktop + Desktop trovato No comment provided by engineer. @@ -2977,6 +2992,10 @@ Non è reversibile! Nome non valido! No comment provided by engineer. + + Invalid response + No comment provided by engineer. + Invalid server address! Indirizzo del server non valido! @@ -3185,6 +3204,11 @@ Questo è il tuo link per il gruppo %@! Messaggi in diretta No comment provided by engineer. + + Local + Locale + No comment provided by engineer. + Local name Nome locale @@ -3527,6 +3551,7 @@ Questo è il tuo link per il gruppo %@! Not compatible! + Non compatibile! No comment provided by engineer. @@ -3553,11 +3578,6 @@ Questo è il tuo link per il gruppo %@! Off No comment provided by engineer. - - Off (Local) - Off (Locale) - No comment provided by engineer. - Ok Ok @@ -3698,9 +3718,8 @@ Questo è il tuo link per il gruppo %@! Protocollo e codice open source: chiunque può gestire i server. No comment provided by engineer. - - Opening database… - Apertura del database… + + Opening app… No comment provided by engineer. @@ -3803,6 +3822,11 @@ Questo è il tuo link per il gruppo %@! Controlla le preferenze tue e del tuo contatto. No comment provided by engineer. + + Please contact developers. +Error: %@ + No comment provided by engineer. + Please contact group admin. Contatta l'amministratore del gruppo. @@ -4720,7 +4744,7 @@ Questo è il tuo link per il gruppo %@! SimpleX contact address - Indirizzo del contatto SimpleX + Indirizzo di contatto SimpleX simplex link type @@ -4783,6 +4807,10 @@ Questo è il tuo link per il gruppo %@! Avvia chat No comment provided by engineer. + + Start chat? + No comment provided by engineer. + Start migration Avvia la migrazione @@ -5187,11 +5215,6 @@ Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzio Spegni No comment provided by engineer. - - Turn off notifications? - Spegnere le notifiche? - No comment provided by engineer. - Turn on Attiva @@ -5394,6 +5417,10 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Usa nuovo profilo in incognito No comment provided by engineer. + + Use only local notifications? + No comment provided by engineer. + Use server Usa il server @@ -5501,6 +5528,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Waiting for desktop... + In attesa del desktop... No comment provided by engineer. @@ -5680,6 +5708,10 @@ Ripetere la richiesta di ingresso? Puoi nascondere o silenziare un profilo utente - scorrilo verso destra. No comment provided by engineer. + + You can make it visible to your SimpleX contacts via Settings. + No comment provided by engineer. + You can now send messages to %@ Ora puoi inviare messaggi a %@ @@ -5909,13 +5941,6 @@ Puoi annullare questa connessione e rimuovere il contatto (e riprovare più tard I tuoi contatti possono consentire l'eliminazione completa dei messaggi. No comment provided by engineer. - - Your contacts in SimpleX will see it. -You can change it in Settings. - I tuoi contatti in SimpleX lo vedranno. -Puoi modificarlo nelle impostazioni. - No comment provided by engineer. - Your contacts will remain connected. I tuoi contatti resteranno connessi. @@ -5995,7 +6020,7 @@ I server di SimpleX non possono vedere il tuo profilo. [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Stella su GitHub](https://github.com/simplex-chat/simplex-chat) + [Dai una stella su GitHub](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. @@ -6050,6 +6075,7 @@ I server di SimpleX non possono vedere il tuo profilo. author + autore member role @@ -6631,7 +6657,7 @@ I server di SimpleX non possono vedere il tuo profilo. updated group profile - profilo del gruppo aggiornato + ha aggiornato il profilo del gruppo rcv group event chat item diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index 11ca09ba3b..ac7f535e36 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -103,6 +103,7 @@ %@ connected + %@ 接続中 No comment provided by engineer. @@ -214,10 +215,12 @@ %lld messages marked deleted + %lld 件のメッセージが削除されました No comment provided by engineer. %lld messages moderated by %@ + %@ により%lld 件のメッセージが検閲されました No comment provided by engineer. @@ -1069,6 +1072,10 @@ チャットが停止してます No comment provided by engineer. + + Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat. + No comment provided by engineer. + Chat preferences チャット設定 @@ -2012,6 +2019,10 @@ This cannot be undone! 暗号化されたメッセージまたは別のイベント notification + + Encrypted message: app is stopped + notification + Encrypted message: database error 暗号化されたメッセージ : データベースエラー @@ -2236,6 +2247,10 @@ This cannot be undone! %@ サーバーのロード中にエラーが発生 No comment provided by engineer. + + Error opening chat + No comment provided by engineer. + Error receiving file ファイル受信にエラー発生 @@ -2913,6 +2928,10 @@ This cannot be undone! Invalid name! No comment provided by engineer. + + Invalid response + No comment provided by engineer. + Invalid server address! 無効なサーバアドレス! @@ -3113,6 +3132,11 @@ This is your link for group %@! ライブメッセージ No comment provided by engineer. + + Local + 自分のみ + No comment provided by engineer. + Local name ローカルネーム @@ -3479,11 +3503,6 @@ This is your link for group %@! オフ No comment provided by engineer. - - Off (Local) - オフ(自分のみ) - No comment provided by engineer. - Ok OK @@ -3623,9 +3642,8 @@ This is your link for group %@! プロトコル技術とコードはオープンソースで、どなたでもご自分のサーバを運用できます。 No comment provided by engineer. - - Opening database… - データベースを開いています… + + Opening app… No comment provided by engineer. @@ -3727,6 +3745,11 @@ This is your link for group %@! あなたと連絡先の設定を確認してください。 No comment provided by engineer. + + Please contact developers. +Error: %@ + No comment provided by engineer. + Please contact group admin. グループの管理者に連絡してください。 @@ -4692,6 +4715,10 @@ This is your link for group %@! チャットを開始する No comment provided by engineer. + + Start chat? + No comment provided by engineer. + Start migration 移行の開始 @@ -5089,11 +5116,6 @@ You will be prompted to complete authentication before this feature is enabled.< オフにする No comment provided by engineer. - - Turn off notifications? - 通知をオフにしますか? - No comment provided by engineer. - Turn on オンにする @@ -5290,6 +5312,10 @@ To connect, please ask your contact to create another connection link and check 新しいシークレットプロファイルを使用する No comment provided by engineer. + + Use only local notifications? + No comment provided by engineer. + Use server サーバを使う @@ -5564,6 +5590,10 @@ Repeat join request? ユーザープロファイルを右にスワイプすると、非表示またはミュートにすることができます。 No comment provided by engineer. + + You can make it visible to your SimpleX contacts via Settings. + No comment provided by engineer. + You can now send messages to %@ %@ にメッセージを送信できるようになりました @@ -5788,13 +5818,6 @@ You can cancel this connection and remove the contact (and try later with a new 連絡先がメッセージの完全削除を許可できます。 No comment provided by engineer. - - Your contacts in SimpleX will see it. -You can change it in Settings. - SimpleX の連絡先に表示されます。 -設定で変更できます。 - No comment provided by engineer. - Your contacts will remain connected. 連絡先は接続されたままになります。 diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 094a30677a..6b90e549b4 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -770,7 +770,7 @@ Always use relay - Verbinden via relais + Altijd relay gebruiken No comment provided by engineer. @@ -1094,6 +1094,10 @@ Chat is gestopt No comment provided by engineer. + + Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat. + No comment provided by engineer. + Chat preferences Gesprek voorkeuren @@ -1618,7 +1622,7 @@ Dit is uw eigen eenmalige link! Delete and notify contact - Contact verwijderen en op de hoogte stellen + Verwijderen en contact op de hoogte stellen No comment provided by engineer. @@ -2063,6 +2067,10 @@ Dit kan niet ongedaan gemaakt worden! Versleuteld bericht of een andere gebeurtenis notification + + Encrypted message: app is stopped + notification + Encrypted message: database error Versleuteld bericht: database fout @@ -2293,6 +2301,10 @@ Dit kan niet ongedaan gemaakt worden! Fout bij het laden van %@ servers No comment provided by engineer. + + Error opening chat + No comment provided by engineer. + Error receiving file Fout bij ontvangen van bestand @@ -2425,7 +2437,7 @@ Dit kan niet ongedaan gemaakt worden! Expand - Uitbreiden + Uitklappen chat item action @@ -2962,7 +2974,7 @@ Dit kan niet ongedaan gemaakt worden! Instantly - Meteen + Direct No comment provided by engineer. @@ -2980,6 +2992,10 @@ Dit kan niet ongedaan gemaakt worden! Ongeldige naam! No comment provided by engineer. + + Invalid response + No comment provided by engineer. + Invalid server address! Ongeldig server adres! @@ -3188,6 +3204,11 @@ Dit is jouw link voor groep %@! Live berichten No comment provided by engineer. + + Local + Lokaal + No comment provided by engineer. + Local name Lokale naam @@ -3557,11 +3578,6 @@ Dit is jouw link voor groep %@! Uit No comment provided by engineer. - - Off (Local) - Uit (lokaal) - No comment provided by engineer. - Ok OK @@ -3702,9 +3718,8 @@ Dit is jouw link voor groep %@! Open-source protocol en code. Iedereen kan de servers draaien. No comment provided by engineer. - - Opening database… - Database openen… + + Opening app… No comment provided by engineer. @@ -3807,6 +3822,11 @@ Dit is jouw link voor groep %@! Controleer de uwe en uw contact voorkeuren. No comment provided by engineer. + + Please contact developers. +Error: %@ + No comment provided by engineer. + Please contact group admin. Neem contact op met de groep beheerder. @@ -4787,6 +4807,10 @@ Dit is jouw link voor groep %@! Begin gesprek No comment provided by engineer. + + Start chat? + No comment provided by engineer. + Start migration Start migratie @@ -5191,11 +5215,6 @@ U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingesc Uitschakelen No comment provided by engineer. - - Turn off notifications? - Schakel meldingen uit? - No comment provided by engineer. - Turn on Zet aan @@ -5398,6 +5417,10 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Gebruik een nieuw incognitoprofiel No comment provided by engineer. + + Use only local notifications? + No comment provided by engineer. + Use server Gebruik server @@ -5685,6 +5708,10 @@ Deelnameverzoek herhalen? U kunt een gebruikers profiel verbergen of dempen - veeg het naar rechts. No comment provided by engineer. + + You can make it visible to your SimpleX contacts via Settings. + No comment provided by engineer. + You can now send messages to %@ Je kunt nu berichten sturen naar %@ @@ -5914,13 +5941,6 @@ U kunt deze verbinding verbreken en het contact verwijderen en later proberen me Uw contacten kunnen volledige verwijdering van berichten toestaan. No comment provided by engineer. - - Your contacts in SimpleX will see it. -You can change it in Settings. - Uw contacten in SimpleX kunnen het zien. -U kunt dit wijzigen in Instellingen. - No comment provided by engineer. - Your contacts will remain connected. Uw contacten blijven verbonden. @@ -6667,7 +6687,7 @@ SimpleX servers kunnen uw profiel niet zien. via relay - via relais + via relay No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index ad1924f4c4..1ab569e3c3 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -1094,6 +1094,10 @@ Czat jest zatrzymany No comment provided by engineer. + + Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat. + No comment provided by engineer. + Chat preferences Preferencje czatu @@ -2063,6 +2067,10 @@ To nie może być cofnięte! Zaszyfrowana wiadomość lub inne zdarzenie notification + + Encrypted message: app is stopped + notification + Encrypted message: database error Zaszyfrowana wiadomość: błąd bazy danych @@ -2293,6 +2301,10 @@ To nie może być cofnięte! Błąd ładowania %@ serwerów No comment provided by engineer. + + Error opening chat + No comment provided by engineer. + Error receiving file Błąd odbioru pliku @@ -2980,6 +2992,10 @@ To nie może być cofnięte! Nieprawidłowa nazwa! No comment provided by engineer. + + Invalid response + No comment provided by engineer. + Invalid server address! Nieprawidłowy adres serwera! @@ -3188,6 +3204,11 @@ To jest twój link do grupy %@! Wiadomości na żywo No comment provided by engineer. + + Local + Lokalnie + No comment provided by engineer. + Local name Nazwa lokalna @@ -3557,11 +3578,6 @@ To jest twój link do grupy %@! Wyłączony No comment provided by engineer. - - Off (Local) - Wyłączony (Lokalnie) - No comment provided by engineer. - Ok Ok @@ -3702,9 +3718,8 @@ To jest twój link do grupy %@! Otwarto źródłowy protokół i kod - każdy może uruchomić serwery. No comment provided by engineer. - - Opening database… - Otwieranie bazy danych… + + Opening app… No comment provided by engineer. @@ -3807,6 +3822,11 @@ To jest twój link do grupy %@! Proszę sprawdzić preferencje Twoje i Twojego kontaktu. No comment provided by engineer. + + Please contact developers. +Error: %@ + No comment provided by engineer. + Please contact group admin. Skontaktuj się z administratorem grupy. @@ -4787,6 +4807,10 @@ To jest twój link do grupy %@! Rozpocznij czat No comment provided by engineer. + + Start chat? + No comment provided by engineer. + Start migration Rozpocznij migrację @@ -5191,11 +5215,6 @@ Przed włączeniem tej funkcji zostanie wyświetlony monit uwierzytelniania.Wyłącz No comment provided by engineer. - - Turn off notifications? - Wyłączyć powiadomienia? - No comment provided by engineer. - Turn on Włącz @@ -5398,6 +5417,10 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Użyj nowego profilu incognito No comment provided by engineer. + + Use only local notifications? + No comment provided by engineer. + Use server Użyj serwera @@ -5685,6 +5708,10 @@ Powtórzyć prośbę dołączenia? Możesz ukryć lub wyciszyć profil użytkownika - przesuń palcem w prawo. No comment provided by engineer. + + You can make it visible to your SimpleX contacts via Settings. + No comment provided by engineer. + You can now send messages to %@ Możesz teraz wysyłać wiadomości do %@ @@ -5914,13 +5941,6 @@ Możesz anulować to połączenie i usunąć kontakt (i spróbować później z Twoje kontakty mogą zezwolić na pełne usunięcie wiadomości. No comment provided by engineer. - - Your contacts in SimpleX will see it. -You can change it in Settings. - Twoje kontakty w SimpleX będą to widzieć. -Możesz to zmienić w Ustawieniach. - No comment provided by engineer. - Your contacts will remain connected. Twoje kontakty pozostaną połączone. 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 f4970446ae..348b42c948 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -1094,6 +1094,10 @@ Чат остановлен No comment provided by engineer. + + Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat. + No comment provided by engineer. + Chat preferences Предпочтения @@ -2063,6 +2067,10 @@ This cannot be undone! Зашифрованное сообщение или событие чата notification + + Encrypted message: app is stopped + notification + Encrypted message: database error Зашифрованное сообщение: ошибка базы данных @@ -2293,6 +2301,10 @@ This cannot be undone! Ошибка загрузки %@ серверов No comment provided by engineer. + + Error opening chat + No comment provided by engineer. + Error receiving file Ошибка при получении файла @@ -2980,6 +2992,10 @@ This cannot be undone! Неверное имя! No comment provided by engineer. + + Invalid response + No comment provided by engineer. + Invalid server address! Ошибка в адресе сервера! @@ -3188,6 +3204,11 @@ This is your link for group %@! "Живые" сообщения No comment provided by engineer. + + Local + Локальные + No comment provided by engineer. + Local name Локальное имя @@ -3557,11 +3578,6 @@ This is your link for group %@! Выключено No comment provided by engineer. - - Off (Local) - Выключить (Локальные) - No comment provided by engineer. - Ok Ок @@ -3702,9 +3718,8 @@ This is your link for group %@! Открытый протокол и код - кто угодно может запустить сервер. No comment provided by engineer. - - Opening database… - Открытие базы данных… + + Opening app… No comment provided by engineer. @@ -3807,6 +3822,11 @@ This is your link for group %@! Проверьте предпочтения Вашего контакта. No comment provided by engineer. + + Please contact developers. +Error: %@ + No comment provided by engineer. + Please contact group admin. Пожалуйста, свяжитесь с админом группы. @@ -4787,6 +4807,10 @@ This is your link for group %@! Запустить чат No comment provided by engineer. + + Start chat? + No comment provided by engineer. + Start migration Запустить перемещение данных @@ -5191,11 +5215,6 @@ You will be prompted to complete authentication before this feature is enabled.< Выключить No comment provided by engineer. - - Turn off notifications? - Выключить уведомления? - No comment provided by engineer. - Turn on Включить @@ -5398,6 +5417,10 @@ To connect, please ask your contact to create another connection link and check Использовать новый Инкогнито профиль No comment provided by engineer. + + Use only local notifications? + No comment provided by engineer. + Use server Использовать сервер @@ -5685,6 +5708,10 @@ Repeat join request? Вы можете скрыть профиль или выключить уведомления - потяните его вправо. No comment provided by engineer. + + You can make it visible to your SimpleX contacts via Settings. + No comment provided by engineer. + You can now send messages to %@ Вы теперь можете отправлять сообщения %@ @@ -5914,13 +5941,6 @@ You can cancel this connection and remove the contact (and try later with a new Ваши контакты могут разрешить окончательное удаление сообщений. No comment provided by engineer. - - Your contacts in SimpleX will see it. -You can change it in Settings. - Ваши контакты в SimpleX получат этот адрес. -Вы можете изменить это в Настройках. - No comment provided by engineer. - Your contacts will remain connected. Ваши контакты сохранятся. diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index bcb39e6e03..c062999aa5 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -1059,6 +1059,10 @@ การแชทหยุดทํางานแล้ว No comment provided by engineer. + + Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat. + No comment provided by engineer. + Chat preferences ค่ากําหนดในการแชท @@ -1995,6 +1999,10 @@ This cannot be undone! ข้อความที่ encrypt หรือเหตุการณ์อื่น notification + + Encrypted message: app is stopped + notification + Encrypted message: database error ข้อความที่ encrypt: ความผิดพลาดในฐานข้อมูล @@ -2218,6 +2226,10 @@ This cannot be undone! โหลดเซิร์ฟเวอร์ %@ ผิดพลาด No comment provided by engineer. + + Error opening chat + No comment provided by engineer. + Error receiving file เกิดข้อผิดพลาดในการรับไฟล์ @@ -2894,6 +2906,10 @@ This cannot be undone! Invalid name! No comment provided by engineer. + + Invalid response + No comment provided by engineer. + Invalid server address! ที่อยู่เซิร์ฟเวอร์ไม่ถูกต้อง! @@ -3093,6 +3109,11 @@ This is your link for group %@! ข้อความสด No comment provided by engineer. + + Local + ในเครื่อง + No comment provided by engineer. + Local name ชื่อภายในเครื่องเท่านั้น @@ -3457,11 +3478,6 @@ This is your link for group %@! ปิด No comment provided by engineer. - - Off (Local) - ปิด (ในเครื่อง) - No comment provided by engineer. - Ok ตกลง @@ -3600,9 +3616,8 @@ This is your link for group %@! โปรโตคอลและโค้ดโอเพ่นซอร์ส – ใคร ๆ ก็สามารถเปิดใช้เซิร์ฟเวอร์ได้ No comment provided by engineer. - - Opening database… - กำลังเปิดฐานข้อมูล… + + Opening app… No comment provided by engineer. @@ -3703,6 +3718,11 @@ This is your link for group %@! โปรดตรวจสอบความต้องการของคุณและการตั้งค่าผู้ติดต่อ No comment provided by engineer. + + Please contact developers. +Error: %@ + No comment provided by engineer. + Please contact group admin. โปรดติดต่อผู้ดูแลกลุ่ม @@ -4669,6 +4689,10 @@ This is your link for group %@! เริ่มแชท No comment provided by engineer. + + Start chat? + No comment provided by engineer. + Start migration เริ่มการย้ายข้อมูล @@ -5066,11 +5090,6 @@ You will be prompted to complete authentication before this feature is enabled.< ปิด No comment provided by engineer. - - Turn off notifications? - ปิดการแจ้งเตือนไหม? - No comment provided by engineer. - Turn on เปิด @@ -5265,6 +5284,10 @@ To connect, please ask your contact to create another connection link and check Use new incognito profile No comment provided by engineer. + + Use only local notifications? + No comment provided by engineer. + Use server ใช้เซิร์ฟเวอร์ @@ -5539,6 +5562,10 @@ Repeat join request? คุณสามารถซ่อนหรือปิดเสียงโปรไฟล์ผู้ใช้ - ปัดไปทางขวา No comment provided by engineer. + + You can make it visible to your SimpleX contacts via Settings. + No comment provided by engineer. + You can now send messages to %@ ตอนนี้คุณสามารถส่งข้อความถึง %@ @@ -5762,13 +5789,6 @@ You can cancel this connection and remove the contact (and try later with a new ผู้ติดต่อของคุณสามารถอนุญาตให้ลบข้อความทั้งหมดได้ No comment provided by engineer. - - Your contacts in SimpleX will see it. -You can change it in Settings. - ผู้ติดต่อของคุณใน SimpleX จะเห็น -คุณสามารถเปลี่ยนได้ในการตั้งค่า - No comment provided by engineer. - Your contacts will remain connected. ผู้ติดต่อของคุณจะยังคงเชื่อมต่ออยู่ diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index 4b2ad1548a..9f2e489bf3 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -5,25 +5,31 @@ - + + + No comment provided by engineer. - + + No comment provided by engineer. - + + No comment provided by engineer. - + + No comment provided by engineer. - + ( + ( No comment provided by engineer. @@ -343,8 +349,9 @@ 30 saniye No comment provided by engineer. - + : + : No comment provided by engineer. @@ -390,8 +397,9 @@ Abort changing address? No comment provided by engineer. - + About SimpleX + SimpleX Hakkında No comment provided by engineer. @@ -406,8 +414,9 @@ Accent color No comment provided by engineer. - + Accept + Kabul et accept contact request via notification accept incoming call via notification @@ -435,48 +444,57 @@ Add profile No comment provided by engineer. - + Add servers by scanning QR codes. + Karekod taratarak sunucuları ekleyin. No comment provided by engineer. - + Add server… + Sunucu ekle… No comment provided by engineer. Add to another device No comment provided by engineer. - + Add welcome message + Karşılama mesajı ekleyin No comment provided by engineer. - + Address + Adres No comment provided by engineer. - + Address change will be aborted. Old receiving address will be used. + Adres değişikliği iptal edilecek. Eski alıcı adresi kullanılacaktır. No comment provided by engineer. Admins can create the links to join groups. No comment provided by engineer. - + Advanced network settings + GGelişmiş ağ ayarları No comment provided by engineer. - + All app data is deleted. + Tüm uygulama verileri silinir. No comment provided by engineer. - + All chats and messages will be deleted - this cannot be undone! + Tüm konuşmalar ve mesajlar silinecektir. Bu, geri alınamaz! No comment provided by engineer. - + All data is erased when it is entered. + Kullanıldığında bütün veriler silinir. No comment provided by engineer. @@ -484,24 +502,29 @@ Tüm grup üyeleri bağlı kalacaktır. No comment provided by engineer. - + All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you. + Tüm mesajlar silinecektir. Bu, geri alınamaz! Mesajlar, YALNIZCA senin için silinecektir. No comment provided by engineer. - + All your contacts will remain connected. + Konuştuğun kişilerin tümü bağlı kalacaktır. No comment provided by engineer. - + All your contacts will remain connected. Profile update will be sent to your contacts. + Tüm kişileriniz bağlı kalacaktır. Profil güncellemesi kişilerinize gönderilecektir. No comment provided by engineer. - + Allow + İzin ver No comment provided by engineer. - + Allow calls only if your contact allows them. + Yalnızca irtibat kişiniz izin veriyorsa aramalara izin verin. No comment provided by engineer. @@ -512,8 +535,9 @@ Allow irreversible message deletion only if your contact allows it to you. No comment provided by engineer. - + Allow message reactions only if your contact allows them. + Yalnızca kişin mesaj tepkilerine izin veriyorsa sen de ver. No comment provided by engineer. @@ -525,32 +549,39 @@ Üyelere direkt mesaj göndermeye izin ver. No comment provided by engineer. - + Allow sending disappearing messages. + Kendiliğinden yok olan mesajlar göndermeye izin ver. No comment provided by engineer. - + Allow to irreversibly delete sent messages. + Gönderilen mesajların kalıcı olarak silinmesine izin ver. No comment provided by engineer. - + Allow to send files and media. + Dosya ve medya göndermeye izin ver. No comment provided by engineer. - + Allow to send voice messages. + Sesli mesaj göndermeye izin ver. No comment provided by engineer. - + Allow voice messages only if your contact allows them. + Yalnızca kişiniz sesli mesaj göndermeye izin veriyorsa sen de ver. No comment provided by engineer. - + Allow voice messages? + Sesli mesajlara izin ver? No comment provided by engineer. - + Allow your contacts adding message reactions. + Konuştuğun kişilerin mesajlarına tepki eklemesine izin ver. No comment provided by engineer. @@ -577,24 +608,28 @@ Always use relay No comment provided by engineer. - + An empty chat profile with the provided name is created, and the app opens as usual. + Verilen adla boş bir sohbet profili oluşturulur ve uygulama her zamanki gibi açılır. No comment provided by engineer. - + Answer call + Aramayı cevapla No comment provided by engineer. App build: %@ No comment provided by engineer. - + App icon + Uygulama simgesi No comment provided by engineer. - + App passcode + Uygulama erişim kodu No comment provided by engineer. @@ -4893,6 +4928,41 @@ SimpleX servers cannot see your profile. \~strike~ No comment provided by engineer. + + Accept connection request? + Bağlantı isteğini kabul et? + No comment provided by engineer. + + + # %@ + # %@ + copied message info title, # <title> + + + Already connecting! + Zaten bağlanılıyor! + No comment provided by engineer. + + + A few more things + Birkaç şey daha + No comment provided by engineer. + + + ## History + ## Geçmiş + copied message info + + + A new random profile will be shared. + Yeni bir rastgele profil paylaşılacak. + No comment provided by engineer. + + + Already joining the group! + Zaten gruba bağlanılıyor! + No comment provided by engineer. + diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index abd58231a9..2bb43ba736 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -89,6 +89,7 @@ %@ and %@ + %@ та %@ No comment provided by engineer. @@ -103,6 +104,7 @@ %@ connected + %@ підключено No comment provided by engineer. @@ -132,6 +134,7 @@ %@, %@ and %lld members + %@, %@ та %lld учасників No comment provided by engineer. @@ -201,6 +204,7 @@ %lld group events + %lld групові заходи No comment provided by engineer. @@ -210,14 +214,17 @@ %lld messages blocked + %lld повідомлень заблоковано No comment provided by engineer. %lld messages marked deleted + %lld повідомлень позначено як видалені No comment provided by engineer. %lld messages moderated by %@ + %lld повідомлень модерує %@ No comment provided by engineer. @@ -227,6 +234,7 @@ %lld new interface languages + %lld нові мови інтерфейсу No comment provided by engineer. @@ -291,10 +299,12 @@ (new) + (новий) No comment provided by engineer. (this device v%@) + (цей пристрій v%@) No comment provided by engineer. @@ -371,6 +381,9 @@ - connect to [directory service](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)! - delivery receipts (up to 20 members). - faster and more stable. + - підключитися до [сервера каталогів](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex. im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id. цибуля) (БЕТА)! +- підтвердження доставлення (до 20 учасників). +- швидше і стабільніше. No comment provided by engineer. @@ -386,6 +399,9 @@ - optionally notify deleted contacts. - profile names with spaces. - and more! + - опція сповіщати про видалені контакти. +- імена профілів з пробілами. +- та багато іншого! No comment provided by engineer. @@ -404,6 +420,7 @@ 0 sec + 0 сек time to disappear @@ -633,6 +650,7 @@ All new messages from %@ will be hidden! + Всі нові повідомлення від %@ будуть приховані! No comment provided by engineer. @@ -742,10 +760,12 @@ Already connecting! + Вже підключаємось! No comment provided by engineer. Already joining the group! + Вже приєднуємося до групи! No comment provided by engineer. @@ -770,6 +790,7 @@ App encrypts new local files (except videos). + Додаток шифрує нові локальні файли (крім відео). No comment provided by engineer. @@ -869,6 +890,7 @@ Bad desktop address + Неправильна адреса робочого столу No comment provided by engineer. @@ -883,6 +905,7 @@ Better groups + Кращі групи No comment provided by engineer. @@ -892,18 +915,22 @@ Block + Блокувати No comment provided by engineer. Block group members + Учасники групи блокування No comment provided by engineer. Block member + Заблокувати користувача No comment provided by engineer. Block member? + Заблокувати користувача? No comment provided by engineer. @@ -933,6 +960,7 @@ Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! + Болгарською, фінською, тайською та українською мовами - завдяки користувачам та [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! No comment provided by engineer. @@ -1066,6 +1094,10 @@ Чат зупинено No comment provided by engineer. + + Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat. + No comment provided by engineer. + Chat preferences Налаштування чату @@ -1168,6 +1200,7 @@ Connect automatically + Підключення автоматично No comment provided by engineer. @@ -1177,24 +1210,31 @@ Connect to desktop + Підключення до комп'ютера No comment provided by engineer. Connect to yourself? + З'єднатися з самим собою? No comment provided by engineer. Connect to yourself? This is your own SimpleX address! + З'єднатися з самим собою? +Це ваша власна SimpleX-адреса! No comment provided by engineer. Connect to yourself? This is your own one-time link! + Підключитися до себе? +Це ваше власне одноразове посилання! No comment provided by engineer. Connect via contact address + Підключіться за контактною адресою No comment provided by engineer. @@ -1214,10 +1254,12 @@ This is your own one-time link! Connect with %@ + Підключитися до %@ No comment provided by engineer. Connected desktop + Підключений робочий стіл No comment provided by engineer. @@ -2005,6 +2047,10 @@ This cannot be undone! Зашифроване повідомлення або інша подія notification + + Encrypted message: app is stopped + notification + Encrypted message: database error Зашифроване повідомлення: помилка бази даних @@ -2228,6 +2274,10 @@ This cannot be undone! Помилка завантаження %@ серверів No comment provided by engineer. + + Error opening chat + No comment provided by engineer. + Error receiving file Помилка отримання файлу @@ -2905,6 +2955,10 @@ This cannot be undone! Invalid name! No comment provided by engineer. + + Invalid response + No comment provided by engineer. + Invalid server address! Неправильна адреса сервера! @@ -3105,6 +3159,11 @@ This is your link for group %@! Живі повідомлення No comment provided by engineer. + + Local + Локально + No comment provided by engineer. + Local name Місцева назва @@ -3471,11 +3530,6 @@ This is your link for group %@! Вимкнено No comment provided by engineer. - - Off (Local) - Вимкнено (локально) - No comment provided by engineer. - Ok Гаразд @@ -3614,9 +3668,8 @@ This is your link for group %@! Протокол і код з відкритим вихідним кодом - будь-хто може запускати сервери. No comment provided by engineer. - - Opening database… - Відкриття бази даних… + + Opening app… No comment provided by engineer. @@ -3718,6 +3771,11 @@ This is your link for group %@! Будь ласка, перевірте свої та контактні налаштування. No comment provided by engineer. + + Please contact developers. +Error: %@ + No comment provided by engineer. + Please contact group admin. Зверніться до адміністратора групи. @@ -4690,6 +4748,10 @@ This is your link for group %@! Почати чат No comment provided by engineer. + + Start chat? + No comment provided by engineer. + Start migration Почати міграцію @@ -5088,11 +5150,6 @@ You will be prompted to complete authentication before this feature is enabled.< Вимкнути No comment provided by engineer. - - Turn off notifications? - Вимкнути сповіщення? - No comment provided by engineer. - Turn on Ввімкнути @@ -5289,6 +5346,10 @@ To connect, please ask your contact to create another connection link and check Використовуйте новий профіль інкогніто No comment provided by engineer. + + Use only local notifications? + No comment provided by engineer. + Use server Використовувати сервер @@ -5563,6 +5624,10 @@ Repeat join request? Ви можете приховати або вимкнути звук профілю користувача - проведіть по ньому вправо. No comment provided by engineer. + + You can make it visible to your SimpleX contacts via Settings. + No comment provided by engineer. + You can now send messages to %@ Тепер ви можете надсилати повідомлення на адресу %@ @@ -5787,13 +5852,6 @@ You can cancel this connection and remove the contact (and try later with a new Ваші контакти можуть дозволити повне видалення повідомлень. No comment provided by engineer. - - Your contacts in SimpleX will see it. -You can change it in Settings. - Ваші контакти в SimpleX побачать це. -Ви можете змінити його в Налаштуваннях. - No comment provided by engineer. - Your contacts will remain connected. Ваші контакти залишаться на зв'язку. diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index d96537be3e..6a07b28157 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -1072,6 +1072,10 @@ 聊天已停止 No comment provided by engineer. + + Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat. + No comment provided by engineer. + Chat preferences 聊天偏好设置 @@ -2015,6 +2019,10 @@ This cannot be undone! 加密消息或其他事件 notification + + Encrypted message: app is stopped + notification + Encrypted message: database error 加密消息:数据库错误 @@ -2240,6 +2248,10 @@ This cannot be undone! 加载 %@ 服务器错误 No comment provided by engineer. + + Error opening chat + No comment provided by engineer. + Error receiving file 接收文件错误 @@ -2918,6 +2930,10 @@ This cannot be undone! Invalid name! No comment provided by engineer. + + Invalid response + No comment provided by engineer. + Invalid server address! 无效的服务器地址! @@ -3118,6 +3134,11 @@ This is your link for group %@! 实时消息 No comment provided by engineer. + + Local + 本地 + No comment provided by engineer. + Local name 本地名称 @@ -3485,11 +3506,6 @@ This is your link for group %@! 关闭 No comment provided by engineer. - - Off (Local) - 关闭(本地) - No comment provided by engineer. - Ok 好的 @@ -3629,9 +3645,8 @@ This is your link for group %@! 开源协议和代码——任何人都可以运行服务器。 No comment provided by engineer. - - Opening database… - 打开数据库中…… + + Opening app… No comment provided by engineer. @@ -3733,6 +3748,11 @@ This is your link for group %@! 请检查您和您的联系人偏好设置。 No comment provided by engineer. + + Please contact developers. +Error: %@ + No comment provided by engineer. + Please contact group admin. 请联系群组管理员。 @@ -4707,6 +4727,10 @@ This is your link for group %@! 开始聊天 No comment provided by engineer. + + Start chat? + No comment provided by engineer. + Start migration 开始迁移 @@ -5106,11 +5130,6 @@ You will be prompted to complete authentication before this feature is enabled.< 关闭 No comment provided by engineer. - - Turn off notifications? - 关闭通知? - No comment provided by engineer. - Turn on 打开 @@ -5307,6 +5326,10 @@ To connect, please ask your contact to create another connection link and check 使用新的隐身配置文件 No comment provided by engineer. + + Use only local notifications? + No comment provided by engineer. + Use server 使用服务器 @@ -5581,6 +5604,10 @@ Repeat join request? 您可以隐藏或静音用户个人资料——只需向右滑动。 No comment provided by engineer. + + You can make it visible to your SimpleX contacts via Settings. + No comment provided by engineer. + You can now send messages to %@ 您现在可以给 %@ 发送消息 @@ -5805,13 +5832,6 @@ You can cancel this connection and remove the contact (and try later with a new 您的联系人可以允许完全删除消息。 No comment provided by engineer. - - Your contacts in SimpleX will see it. -You can change it in Settings. - 您的 SimpleX 的联系人会看到它。 -您可以在设置中更改它。 - No comment provided by engineer. - Your contacts will remain connected. 与您的联系人保持连接。 diff --git a/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff b/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff index 821db2620f..03a108d112 100644 --- a/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff @@ -5868,6 +5868,36 @@ It can happen because of some bug or when the connection is compromised.你和你的聯絡人可以新增訊息互動。 No comment provided by engineer. + + %@ connected + %@ 已連接 + No comment provided by engineer. + + + # %@ + # %@ + copied message info title, # <title> + + + %@ and %@ + %@ 和 %@ + No comment provided by engineer. + + + ## History + 紀錄 + copied message info + + + ## In reply to + 回覆 + copied message info + + + %@ and %@ connected + %@ 和 %@ 已連接 + No comment provided by engineer. + diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index 5a704457d1..bf911b55f7 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -1959,6 +1959,9 @@ /* No comment provided by engineer. */ "Live messages" = "Съобщения на живо"; +/* No comment provided by engineer. */ +"Local" = "Локално"; + /* No comment provided by engineer. */ "Local name" = "Локално име"; @@ -2222,9 +2225,6 @@ /* No comment provided by engineer. */ "Off" = "Изключено"; -/* No comment provided by engineer. */ -"Off (Local)" = "Изключено (Локално)"; - /* feature offered item */ "offered %@" = "предлага %@"; @@ -2315,9 +2315,6 @@ /* No comment provided by engineer. */ "Open-source protocol and code – anybody can run the servers." = "Протокол и код с отворен код – всеки може да оперира собствени сървъри."; -/* No comment provided by engineer. */ -"Opening database…" = "Отваряне на база данни…"; - /* member role */ "owner" = "собственик"; @@ -3218,9 +3215,6 @@ /* No comment provided by engineer. */ "Turn off" = "Изключи"; -/* No comment provided by engineer. */ -"Turn off notifications?" = "Изключи известията?"; - /* No comment provided by engineer. */ "Turn on" = "Включи"; @@ -3641,9 +3635,6 @@ /* No comment provided by engineer. */ "Your contacts can allow full message deletion." = "Вашите контакти могат да позволят пълното изтриване на съобщението."; -/* No comment provided by engineer. */ -"Your contacts in SimpleX will see it.\nYou can change it in Settings." = "Вашите контакти в SimpleX ще го видят.\nМожете да го промените в Настройки."; - /* No comment provided by engineer. */ "Your contacts will remain connected." = "Вашите контакти ще останат свързани."; diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index 26469bea98..ef7eff477d 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -1956,6 +1956,9 @@ /* No comment provided by engineer. */ "Live messages" = "Živé zprávy"; +/* No comment provided by engineer. */ +"Local" = "Místní"; + /* No comment provided by engineer. */ "Local name" = "Místní název"; @@ -2219,9 +2222,6 @@ /* No comment provided by engineer. */ "Off" = "Vypnout"; -/* No comment provided by engineer. */ -"Off (Local)" = "Vypnuto (místní)"; - /* feature offered item */ "offered %@" = "nabídl %@"; @@ -2312,9 +2312,6 @@ /* No comment provided by engineer. */ "Open-source protocol and code – anybody can run the servers." = "Protokol a kód s otevřeným zdrojovým kódem - servery může provozovat kdokoli."; -/* No comment provided by engineer. */ -"Opening database…" = "Otvírání databáze…"; - /* member role */ "owner" = "vlastník"; @@ -3215,9 +3212,6 @@ /* No comment provided by engineer. */ "Turn off" = "Vypnout"; -/* No comment provided by engineer. */ -"Turn off notifications?" = "Vypnout upozornění?"; - /* No comment provided by engineer. */ "Turn on" = "Zapnout"; @@ -3638,9 +3632,6 @@ /* No comment provided by engineer. */ "Your contacts can allow full message deletion." = "Vaše kontakty mohou povolit úplné mazání zpráv."; -/* No comment provided by engineer. */ -"Your contacts in SimpleX will see it.\nYou can change it in Settings." = "Vaše kontakty v SimpleX ji uvidí.\nMůžete ji změnit v Nastavení."; - /* No comment provided by engineer. */ "Your contacts will remain connected." = "Vaše kontakty zůstanou připojeny."; diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index febd4c06a5..12e4e8de78 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -2163,6 +2163,9 @@ /* No comment provided by engineer. */ "Live messages" = "Live Nachrichten"; +/* No comment provided by engineer. */ +"Local" = "Lokal"; + /* No comment provided by engineer. */ "Local name" = "Lokaler Name"; @@ -2432,9 +2435,6 @@ /* No comment provided by engineer. */ "Off" = "Aus"; -/* No comment provided by engineer. */ -"Off (Local)" = "Aus (Lokal)"; - /* feature offered item */ "offered %@" = "angeboten %@"; @@ -2528,9 +2528,6 @@ /* No comment provided by engineer. */ "Open-source protocol and code – anybody can run the servers." = "Open-Source-Protokoll und -Code – Jede Person kann ihre eigenen Server aufsetzen und nutzen."; -/* No comment provided by engineer. */ -"Opening database…" = "Öffne Datenbank …"; - /* member role */ "owner" = "Eigentümer"; @@ -3467,9 +3464,6 @@ /* No comment provided by engineer. */ "Turn off" = "Abschalten"; -/* No comment provided by engineer. */ -"Turn off notifications?" = "Benachrichtigungen abschalten?"; - /* No comment provided by engineer. */ "Turn on" = "Einschalten"; @@ -3959,9 +3953,6 @@ /* No comment provided by engineer. */ "Your contacts can allow full message deletion." = "Ihre Kontakte können die unwiederbringliche Löschung von Nachrichten erlauben."; -/* No comment provided by engineer. */ -"Your contacts in SimpleX will see it.\nYou can change it in Settings." = "Ihre Kontakte in SimpleX werden es sehen.\nSie können es in den Einstellungen ändern."; - /* No comment provided by engineer. */ "Your contacts will remain connected." = "Ihre Kontakte bleiben verbunden."; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index d9b14eddbd..714692843d 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -25,6 +25,9 @@ /* No comment provided by engineer. */ "- more stable message delivery.\n- a bit better groups.\n- and more!" = "- entrega de mensajes más estable.\n- grupos un poco mejores.\n- ¡y más!"; +/* No comment provided by engineer. */ +"- optionally notify deleted contacts.\n- profile names with spaces.\n- and more!" = "- notificar opcionalmente a los contactos eliminados.\n- nombres de perfil con espacios.\n- ¡...y más!"; + /* No comment provided by engineer. */ "- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- mensajes de voz de hasta 5 minutos.\n- tiempo personalizado para mensajes temporales.\n- historial de edición."; @@ -43,6 +46,12 @@ /* No comment provided by engineer. */ "(" = "("; +/* No comment provided by engineer. */ +"(new)" = "(nuevo)"; + +/* No comment provided by engineer. */ +"(this device v%@)" = "(este dispositivo v%@)"; + /* No comment provided by engineer. */ ")" = ")"; @@ -118,12 +127,18 @@ /* No comment provided by engineer. */ "%@ %@" = "%@ %@"; +/* No comment provided by engineer. */ +"%@ and %@" = "%@ y %@"; + /* No comment provided by engineer. */ "%@ and %@ connected" = "%@ y %@ conectados"; /* copied message info, at odeslat přímou zprávu + smazaný kontakt + Chyba + Vytvořit profil + Připojený počítač + %s a %s + Připojit se automaticky + Adresa počítače + Připojit se do skupiny? + Skupina již existuje! + %s připojený + Toto zařízení + Počítač + Jméno tohoto zařízení + Počítač nalezen + Smazat %d zpráv? + Odebrat člena + Připojený telefon + Odebrat člena? + Otevřít skupinu + Spojení ukončeno + Chyba + Připojit se k počítači + Odpojit + Neplatné jméno! + Autor + Připojený k telefonu + Špatná adresa počítače + Zařízení + Odpojit počítač? + Vytvořit skupinu + Kód relace + Vložit adres počítače + Blokovaný + Lepší skupiny \ No newline at end of file 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 a36eca6663..7401c0d4d9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -271,7 +271,7 @@ Über Link verbinden Wenn Sie einen SimpleX-Chat-Einladungslink erhalten haben, können Sie ihn in Ihrem Browser öffnen: QR-Code scannen.]]> - In mobiler App öffnen“ und dann auf „Verbinden“.]]> + In mobiler App öffnen“ und dann auf „Verbinden“.]]> Die Verbindungsanfrage akzeptieren? Wenn Sie ablehnen, wird der Absender NICHT benachrichtigt. @@ -1603,4 +1603,14 @@ Gefundener Desktop Nicht kompatibel! Über das lokale Netzwerk auffindbar + Aktualisieren + Chat-Profil erstellen + Mobiltelefone trennen + Kein Mobiltelefon verbunden + Zufällig + Um einer Mobiltelefon-App die Verbindung zum Desktop zu ermöglichen, öffnen Sie diesen Port in Ihrer Firewall, falls Sie diese aktiviert haben + Port in Firewall öffnen + Ansicht abgestürzt + Fehler beim Anzeigen des Inhalts + Fehler beim Anzeigen der Nachricht \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml index 7063eb9007..d697fd4f64 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml @@ -56,4 +56,168 @@ Να χρησιμοποιείται πάντα αναμεταδότη Αναζήτηση Ανενεργό + "Το προφίλ σας %1$s θα μοιραστεί" + Η SimpleX διεύθυνση σας + Αντίγραφο δεδομένων εφαρμογής + 5 λεπτά + Θα συνδεθείτε όταν η συσκευή της επαφής σας είναι συνδεμένει, παρακαλώ περιμένετε ή ελέγξτε αργότερα! + Ο ICE διακομιστής σας + Έκδοση εφαρμογής + Στείλατε πρόσκληση ομάδας + 1 λεπτό + Ο διακομιστής σας + Διεύθυνση + Ακύρωση + Πίσω + 30 δευτερόλεπτα + Θα συνδεθείτε με όλα τα μέλη της ομάδας. + %1$s θέλει να συνδεθεί μαζί σου μέσω + Επιτρέπονται αντιδράσεις μηνύματος. + Ο διακομιστής XFTP σας + Η διεύθυνση του διακομιστή σας + Το προφίλ, επαφές και παραδομένα μηνύματα σας είναι αποθηκευμένα στην συσκευή σας. + Ο διακομιστής SMP σας + Επιτρέπεται να σταλούν αρχεία και μέσα. + Το τυχαίο προφίλ σας + %1$s ΜΕΛΗ + Επιτρέπεται + Οι προτιμήσεις σας + Συντάκτης + Ο ΙCE διακομιστής σας + Κωδικός εφαρμογής + Αίτημα σύνδεσης θα σταλεί σε αυτό το μέλος της ομάδας. + ΟΙΚΟΝΑ ΕΦΑΡΜΟΓΗΣ + Εφαρμογή + Οι ρυθμίσεις σας + Έκδοση εφαρμογής: v%s + σφάλμα κλήσης + "ακυρώθηκε %s" + ακύρωση πρόβλεψη συνδέσμου + Αλλαγή κωδικού πρόσβασης βάση δεδομένων? + Αλλαγή κωδικού πρόσβασης + Αλλαγή ρόλου του %s σε %s + Φόντο + Δεν είναι δυνατή η προετοιμασία της βάσης δεδομένων + Ένα νέο τυχαίο προφίλ θα μοιραστεί. + Δεν είναι δυνατή η πρόσκληση επαφών! + Αρχείο συνομιλίας + Αλλαγή διεύθυνσης λήψης + Πιστοποίηση μη διαθέσιμη + Αλλαγή + " +\nΔιαθέσιμο στην έκδοση 5.1" + Τέλος κλήσης + ΚΛΗΣΕΙΣ + Αυτόματη αποδοχή + %1$d αποτυχία κρυπτογράφησης μηνύματος + αλλαγή διεύθυνσης για %s… + Αλλαγή ρόλου ομάδας; + ΑΡΧΕΙΟ ΣΥΝΟΜΙΛΙΑΣ + Δεν είναι δυνατή η πρόσκληση επαφής! + Αυτόματη αποδοχή αιτήματος επαφής + Κλήση… + Ακύρωση πρόβλεψη αρχείου + Φραγή + Ακύρωση πρόβλεψη εικόνας + αλλαγή διεύθυνσης… + Μερικά παραπάνω πράγματα + Πιστοποίηση ακυρώθηκε + Αλλαγή κωδικού αυτοκαταστροφής + Αλλαγή ρολου + Ακύρωση ζωντανών μυνημάτων + Όλα τα δεδομένα της εφαρμογής διαγράφηκαν. + Εμφάνιση + Τέλος κλήσης %1$s + Ακύρωση + Αλλαγή διεύθυνσης λήψης; + αλλαγή διεύθυνσης… + Αλλαγή λειτουργίας αυτοκαταστροφής + Κάμερα + κλήση σε εξέλιξη + Αυτόματη αποδοχή εικόνων + Αλλαγή του ρόλου σας σε %s + Κλήση σε εξέλιξη + Πιστοποίηση απέτυχε + "σύνδεση %1$d" + Δημιουργία διεύθυνση SimpleX + επαφή έχει κρυπτογράφηση από άκρο σε άκρο + Δημιουργία μια ομάδας χρησιμοποιώντας ένα τυχαίο προφίλ. + Δημιουργία ομάδας + Δημιουργία προφίλ + Η επαφή και όλα τα μηνύματα θα διαγραφούν - αυτό δεν μπορεί να αναιρεθεί! + Δημιουργία προφίλ + Οι επαφές μπορούν να επισημάνουν μηνύματα προς διαγραφή; θα μπορείτε να τα δείτε. + Σύνδεση μέσω μιας εφάπαξ σύνδεσης; + Δημιουργία σύνδεσμου + Σύνδεση μέσω σύνδεσμο/κωδικό γρήγορης ανταπόκρισης + Σφάλμα σύνδεσης (πιστοποίηση) + συνδέεται + συνδέεται… + Όνομα επαφής + Δημιουργία διεύθυνσης + Αντιγραφή + Συνέχεια + Σύνδεση μέσω σύνδεσμο; + Επαφή υπάρχει ήδη + Σύνδεση στον εαυτό σας; + Δημιουργία μυστικής ομάδας + Συνδεδεμένη σε επιφάνεια εργασίας + δημιουργός + Αίτημα σύνδεσης στάλθηκε! + Συνδεμένο σε επιφάνεια εργασίας + Σύνδεση + Διόρθωση ονόματος σε %s; + Λήξη χρονικού ορίου σύνδεσης + σύνδεση (αποδεκτή) + Σύνδεση με %1$s? + Δημιουργία του προφίλ σου + συνδέεται… + Δημιουργία + Προτιμήσεις επαφής + Συνδεδεμένο κινητό + Σύνδεση + Δημιουργία προφίλ συνομιλίας + συνδέεται… + Σύνδεση τερματίστηκε + Συνδε + Σύνδεση ανώνυμης περιήγησης + σύνδεση επετεύχθη + επαφή δεν έχει κρυπτογράφηση από άκρο σε άκρο + Επαφή επιτρέπει + σύνδεση (ανακοινώθηκε) + Συνδεδεμένος + συνδέεται… + Δημιουργία σύνδεσμο ομάδας + Σύνδεση σε επιφάνεια εργασίας + Δημιουργήθηκε στις %1$s + Συνδεδεμένο στο κινητό + Σύνδεση μέσω σύνδεσμο + Επαφές + Δημιουργία αρχείου + Δημιουργία μυστικής ομάδας + Σφάλμα σύνδεσης + Η επαφή δεν είναι συνδράμει αυτή τη στιγμή! + Συνδεδεμένο απευθείας + Η ιδιωτικότητά σας + Η επαφή σας έστειλε ένα αρχείο το οποίο είναι μεγαλύτερο από το παρόν υποστηριζόμενο μέγεθος (%1$s). + Το προφίλ της συνομιλίας σας θα σταλεί στην επαφή σας + Χρήση νέου ανώνυμου προφίλ + Ήδη συνδέεται + Απορρίψατε την πρόσκληση της ομάδας + Σύνδεση μέσω διεύθυνση επαφής + Χρήση του τρέχων προφίλ + Σύνδεση + Το τρέχον προφίλ σας + αφαιρέσατε %1$s + "Συμμετοχή ομάδας;" + Οι επαφες σας θα παραμένουν συνδεδεμένες. + Προσπάθεια σύνδεσης με τον διακομιστή που χρησιμοποιείται για τη λήψη μηνυμάτων από αυτήν την επαφή. + Το προφίλ σου θα σταλεί στην επαφή από την οποία έλαβες αυτόν τον σύνδεσμο. + σφάλμα + Άνοιγμα βάση δεδομένων… + Η προβολή συνετρίβη + συνδεδεμένο + Κοινοποιήσατε μια μη έγκυρη διαδρομή αρχείου. Αναφέρετε το πρόβλημα στους προγραμματιστές της εφαρμογής. + Μη έγκυρη διαδρομή αρχείου + Είστε συνδεδεμένοι στον διακομιστή που χρησιμοποιείται για τη λήψη μηνυμάτων από αυτήν την επαφή. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index 246ed36859..a656b932de 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -3,8 +3,8 @@ Autenticación no disponible Aceptar Configuración avanzada de red - Mejor para la batería. Recibirás notificaciones sólo cuando la aplicación se esté ejecutando, SIN servicio en segundo plano.]]> - Bueno para la batería. El servicio en segundo plano comprueba si hay mensajes cada 10 minutos. Puedes perder llamadas o mensajes urgentes.]]> + Óptimo para la batería. Recibirás notificaciones sólo cuando la aplicación esté abierta (SIN servicio en segundo plano).]]> + Bueno para la batería. El servicio en segundo plano comprueba si hay mensajes cada 10 minutos. Podrías no recibir a tiempo llamadas o mensajes urgentes.]]> Aceptar Copia de seguridad de los datos de la aplicación un dia @@ -76,7 +76,7 @@ Solicita recibir la imagen Atención: NO podrás recuperar o cambiar la contraseña si la pierdes.]]> Tanto tú como tu contacto podéis enviar mensajes de voz. - ¡Consume más batería! El servicio en segundo plano se ejecuta siempre y las notificaciones se mostrarán en cuanto haya mensajes disponibles.]]> + ¡Consume más batería! El servicio en segundo plano se ejecuta continuamente y las notificaciones se mostrarán de inmediato.]]> Tanto tú como tu contacto podéis eliminar de forma irreversible los mensajes enviados. Tanto tú como tu contacto podéis enviar mensajes temporales. Escanear código QR: para conectar con tu contacto mediante su código QR.]]> @@ -162,7 +162,7 @@ Conectado Copiado en portapapeles Crea enlace de invitación de un uso. - Escanear código QR ]]> + Escanear código QR ]]> Eliminar Eliminar ¡El contacto aun no se ha conectado! @@ -184,13 +184,13 @@ Contacto oculto: Eliminar Editar - ¿Eliminar el mensaje de miembro\? + ¿Eliminar el mensaje del miembro? editado Error de decodificación Crear dirección Eliminar dirección ¿Eliminar la dirección\? - Nombre mostrado: + Nombre del perfil: conectando… Descentralizada La base de datos será cifrada. @@ -413,7 +413,7 @@ PARA CONSOLA Error al cambiar rol SERVIDORES - Introduce un nombre para el grupo: + Nombre del grupo: Preferencias de grupo Los miembros del grupo pueden enviar mensajes directos. Los miembros del grupo pueden eliminar mensajes de forma irreversible. @@ -452,7 +452,7 @@ Configuración de red No se usarán hosts .onion cifrado de extremo a extremo de 2 capas .]]> - Puede cambiarse más tarde en Configuración. + Puedes cambiar estos ajustes más tarde en Configuración. Instantánea Únete Únete en modo incógnito @@ -499,7 +499,7 @@ formato de mensaje no válido EN VIVO eliminado por el moderador - invitado a conectarse + invitación a conectarse ¡Las notificaciones instantáneas están desactivadas! mensaje nuevo Nueva solicitud de contacto @@ -553,7 +553,7 @@ expulsado ha sido invitado Sin contactos que añadir - Nuevo rol de miembro + Nuevo rol del miembro Rol inicial Nombre local Estado de la red @@ -713,7 +713,7 @@ Guardar y notificar a los miembros del grupo A menos que tu contacto haya eliminado la conexión o que este enlace ya se haya usado, podría ser un error. Por favor, notifícalo. \nPara conectarte, pide a tu contacto que cree otro enlace de conexión y comprueba que tienes buena conexión de red. - La aplicación recoge nuevos mensajes periódicamente por lo que usa un pequeño porcentaje de la batería al día. La aplicación no hace uso de notificaciones automáticas: los datos de tu dispositivo no se envían a los servidores. + La aplicación recoge nuevos mensajes periódicamente lo que consume un pequeño porcentaje de batería al día. La aplicación no usa notificaciones push por tanto los datos de tu dispositivo no se envían a los servidores push. Bloqueo SimpleX Desbloquear Este texto está disponible en Configuración @@ -722,7 +722,7 @@ Usando servidores SimpleX Chat. Aislamiento de transporte tachado - Usar Chat + Abrir SimpleX PROXY SOCKS TEMAS Detener @@ -764,8 +764,8 @@ Inciar chat nuevo Para exportar, importar o eliminar la base de datos debes detener Chat. Durante la parada no podrás recibir o enviar mensajes. Gracias por instalar SimpleX Chat! - Para proteger la privacidad, en lugar de los identificadores de usuario que usan el resto de plataformas, SimpleX dispone de identificadores para las colas de mensajes, independientes para cada uno de tus contactos. - Para proteger tu información, activa Bloqueo SimpleX. + Para proteger tu privacidad, en lugar de los identificadores de usuario que usan el resto de plataformas, SimpleX dispone de identificadores para las colas de mensajes, independientes para cada uno de tus contactos. + Para proteger tu información, activa el Bloqueo SimpleX. \nSe te pedirá que completes la autenticación antes de activar esta función. Al actualizar la configuración, el cliente se reconectará a todos los servidores. ¿Usar servidores SimpleX Chat\? @@ -838,7 +838,7 @@ Pulsa el botón Para iniciar un chat nuevo Cambiar servidor de recepción - Completamente descentralizado: sólo visible para los miembros. + Completamente descentralizado: sólo visible a los miembros. Para conectarte mediante enlace ¡Error en prueba del servidor! Algunos servidores no superaron la prueba: @@ -883,7 +883,7 @@ Tu contacto debe estar en línea para que se complete la conexión. \nPuedes cancelar esta conexión y eliminar el contacto (e intentarlo más tarde con un enlace nuevo). La base de datos actual será ELIMINADA y SUSTITUIDA por la importada. -\nEsta acción no se puede deshacer. Tu perfil, contactos, mensajes y archivos se perderán irreversiblemente. +\nEsta acción no podrá deshacerse. Tu perfil, contactos, mensajes y archivos actuales se perderán irreversiblemente. Tu perfil aleatorio Te conectarás cuando tu solicitud se acepte, por favor espera o compruébalo más tarde. Te conectarás cuando el dispositivo de tu contacto esté en línea, por favor espera o compruébalo más tarde. @@ -936,12 +936,12 @@ Permites SimpleX Tu perfil se enviará al contacto del que has recibido este enlace. - Te unirás al grupo al que hace referencia este enlace y te conectarás con sus miembros. + Te conectarás con todos los miembros del grupo. Estás conectado al servidor usado para recibir mensajes de este contacto. mediante enlace de dirección de contacto mediante enlace de grupo - has compartido enlace de un solo uso + enlace de un solo uso has compartido enlace de un solo uso en módo incógnito No tienes chats El contacto ha enviado un archivo mayor al máximo admitido (%1$s ). @@ -1002,7 +1002,7 @@ Puedes ocultar o silenciar un perfil. Mantenlo pulsado para abrir el menú. Seguirás recibiendo llamadas y notificaciones de los perfiles silenciados cuando estén activos. Ahora los administradores pueden: -\n- borrar mensajes de los miembros. +\n- eliminar mensajes de los miembros. \n- desactivar el rol miembro (a rol \"observador\") Para hacer visible tu perfil oculto, introduce la contraseña completa en el campo de búsqueda del menú Mis perfiles. Actualización de la base de datos @@ -1125,7 +1125,7 @@ Más información Si no puedes reunirte en persona, **muestra el código QR por videollamada**, o comparte el enlace. Para conectarse, tu contacto puede escanear el código QR o usar el enlace en la aplicación. - Crear tu dirección SimpleX + Crear dirección SimpleX Auto aceptar Vista previa Abriendo base de datos… @@ -1328,7 +1328,7 @@ ¡Confirmación de entrega de mensajes! - entrega de mensajes más estable. \n- grupos un poco mejores. -\n- ¡y más! +\n- ¡...y más! El envío de confirmaciones de entrega se activará para todos los contactos. ¡El doble check que nos faltaba! ✅ Puedes activar más tarde en Configuración @@ -1391,14 +1391,14 @@ Error al establecer contacto con el miembro Atención: los servidores de retransmisión están conectados mediante SOCKS proxy. Las llamadas y las previsualizaciones de enlaces usan conexión directa.]]> Cifra archivos locales - Nueva aplicación para PC! + Nueva aplicación para ordenador! 6 idiomas nuevos para el interfaz Cifrado de los nuevos archivos locales (excepto vídeos). Enviar mensaje directo para conectar Descubre y únete a grupos Modo incógnito simplificado Árabe, Búlgaro, Finlandés, Hebreo, Tailandés y Ucraniano - gracias a los usuarios y Weblate. - Crea perfil nuevo en la aplicación para PC. 💻 + Crea perfil nuevo en la aplicación para ordenador. 💻 Error al enviar invitación Activa incógnito al conectar. - conexión al servicio de directorio (BETA)! @@ -1407,62 +1407,130 @@ Enviar mensaje directo conectado directamente Expandir - Error en renegociación de cifrado + Error de renegociación de cifrado contacto eliminado Error Crear grupo Crear perfil - Escritorio conectado + Ordenador conectado Nuevo dispositivo móvil - Dirección desktop + Dirección ordenador Sólo un dispositivo puede funcionar al mismo tiempo ¿Unirse a tu grupo? - %d mensajes marcados como borrados + %d mensaje(s) marcado(s) eliminado(s) ¡El grupo ya existe! - ¡Ya está en proceso de conexión! + ¡Ya en proceso de conexión! Versión incompatible (nuevo)]]> - Opciones escritorio enlazado - Desktop enlazados - Descubrir en red - , y hay %d eventos más + Opciones ordenador enlazado + Ordenadores enlazados + Descubrir en red local + y %d evento(s) más ¿Conectar vía enlace? - ¡Ya está en proceso de unirse al grupo! - %d mensajes moderados por %s + ¡En proceso de unirte al grupo! + %d mensaje(s) moderado(s) por %s ¿Conectarte a tí mismo? Móviles enlazados - Desktop - Conectado al escritorio + Ordenador + Conectado con ordenador Cargando archivo - Conectando a desktop - La renegociación de cifrado ha fallado. - Dispositivo desktop + Conectando con ordenador + Renegociación de cifrado fallida. + Ordenadores ¿Corregir el nombre a %s? Elimina %d mensajes? Enlazar móvil ¿Conectar con %1$s? Bloquear - %d mensajes bloqueados + %d mensaje(s) bloqueado(s) Bloquear miembro Móvil conectado Eliminar y notificar contacto Grupo abierto - Conexión terminada + Conexión finalizada (este dispositivo v%s)]]> ¡Los mensajes de %s serán mostrados! - Introduce el nombre de este dispositivo… + Nombre de este dispositivo… Error - Conectar a desktop + Conectar con ordenador Desconectar - Bloquear miembro? - %d eventos de grupo + ¿Bloquear miembro? + %d evento(s) de grupo ¡Nombre no válido! Conectado a móvil - Dirección de escritorio incorrecta + Dirección ordenador incorrecta Dispositivo Ruta archivo no valida. - ¿Desconectar desktop? - Los mensajes nuevos de %s estarán ocultos! - La versión de aplicación del desktop %s no es compatible con esta aplicación. + ¿Desconectar ordenador? + ¡Los mensajes nuevos de %s estarán ocultos! + La versión de aplicación del ordenador %s no es compatible con esta aplicación. bloqueado + Bloquear miembros del grupo + ¿Repetir solicitud de conexión? + Ya estás conectando con %1$s. + Crear grupo usando perfil aleatorio. + Ya estás uniéndote al grupo mediante este enlace. + Recargar + %s y %s + Conectar automáticamente + Ya estás uniéndote al grupo %1$s. + ¡Enlazar aplicación móvil con ordenador! + ¡Este es tu propio enlace de un solo uso! + Mediante protocolo seguro de resistencia cuántica. + Usar desde ordenador en la aplicación móvil y escanea el código QR.]]> + Para ocultar mensajes no deseados. + ¿Desenlazar ordenador? + Grupos mejorados + El vídeo no puede ser decodificado. Por favor, prueba con otro vídeo o contacta con los desarrolladores. + %s conectado + Aleatorio + Grupos incógnito + %s, %s y %d miembros + Este dispositivo + Desbloquear miembro + %s desconectado]]> + Esperando ordenador… + Mensajería más segura y conexión más rápida. + Pulsa para conectar + Nombre del dispositivo + Ya estás en el grupo %1$s. + ¡Esta es tu propia dirección SimpleX! + Ordenador encontrado + ¡No compatible! + Esperando conexión móvil: + Eliminar miembro + ¿Desbloquear miembro? + Para permitir que la aplicación móvil se conecte al ordenador, abre este puerto en el firewall si está habilitado + Usar desde ordenador + Código de sesión + ¿Repetir solicitud de admisión? + Crear perfil de chat + ¿Eliminar miembro? + ¡Ya estás conectando mediante este enlace de un solo uso! + Desenlazar + El nombre del dispositivo será compartido con el cliente móvil conectado. + Verificar código en móvil + Abrir puerto en firewall + Has compartido una ruta no válida. Informa a los desarrolladores del problema. + Desconectar móviles + autor + Pegar dirección de ordenador + ¡Este es tu enlace para el grupo %1$s! + Verificar código con ordenador + Escanear código QR desde ordenador + Desbloquear + Detectable mediante red local + - notificar opcionalmente a los contactos eliminados. +\n- nombres de perfil con espacios. +\n- ¡...y más! + ¡Ya has solicitado la conexión mediante esta dirección! + Mostrar consola en ventana nueva + Escáner desde móvil + Verificar conexiones + Por favor, espere mientras el archivo se carga desde el móvil enlazado + Verificar conexión + Ningún móvil conectado + Error aplicación + error mostrando contenido + error mostrando mensaje \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml index 849daaeefe..5ad0b3cbfc 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml @@ -901,8 +901,8 @@ données invalides chat invalide Annuler le message dynamique - offert %s - offert %s: %2s + propose %s + propose %s : %2s annulé %s Version de l\'app simplexmq : v%s (%2s) @@ -1522,4 +1522,14 @@ Bureau trouvé Non compatible ! Accessible via le réseau local + Rafraîchir + Créer un profil de chat + Pas de mobile connecté + Déconnecter les mobiles + Aléatoire + Afin de permettre à une application mobile de se connecter à votre ordinateur, ouvrez ce port dans votre pare-feu, si vous l\'avez activé + Crash d\'aperçu + Ouvrir le port de votre pare-feu + erreur d\'affichage de contenu + erreur d\'affichage de message \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index 21a56d7e99..fac97864f7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -14,7 +14,7 @@ Megszakítás 30 másodperc Egyszer használatos link - %1$s szeretne kapcsolatba lépni veled + %1$s szeretne kapcsolatba lépni veled a A SimpleX chatről 1 nap Címváltoztatás megszakítása @@ -24,7 +24,7 @@ Kapcsolódás a szerverekhez SOCKS proxy segítségével a %d? porton? A proxyt el kell indítani mielőtt bekapcsolná ezt az opciót. Elfogad Elfogad - felül, majd pedig: + felül, utána: Elfogadás inkognítóban Elfogadod a kapcsolatfelvételt? Elfogad @@ -50,7 +50,7 @@ Háttér Tudnivaló: az üzenet- és fájl relay szerverek SOCKS proxy által vannak kapcsolatban. A hívások és URL link előnézetek közvetlen kapcsolatot használnak.]]> App adatmentés - Adatbázis inicializálása nem lehetséges + Az adatbázis inicializálása sikertelen A kapcsolat megmarad az összes Ismerősöddel. Profil változtatások frissítésre kerülnek az ismerőseidnél. Chat profile (alap beállítás) avagy kapcsolat által (BÉTA). Egy új véletlenszerű profil lesz megosztva. @@ -62,9 +62,9 @@ Hang- és videóhívások Az app titkosítja a helyi fájlokat (a videók kivételével). Hívás fogadása - Ismerősök küldhetnek eltűnő üzeneteket engedélyezve. + Ismerősök számára engedélyezve az eltűnő üzenetek küldése. Már kapcsolódik! - Fájl fogadás nem lehetséges + Nem tud fájlt fogadni Hitelesítés elérhetetlen App verzió Üdvözlőszöveg hozzáadása @@ -72,7 +72,7 @@ " \nElérhető a v5.1-ben" Mindketten, te és az ismerősöd is visszaállíthatatlanul törölhettek elküldött üzeneteket. - Jobb csoportok + Javított csoportok Minden üzenet törlésre kerül - ez visszafordíthatatlan! Az üzenetek csak NÁLAD törlődnek. Hívás befejeződött HÍVÁSOK @@ -92,7 +92,7 @@ Adminok létrehozhatnak linkeket csoporthoz való csatlakozáshoz. Hívások a lezárási képernyőn: titkosítás egyeztetése… - Ismerős meghívása sikertelen! + Ismerős meghívása tiltott! téves üzenet ID Ismerősnek jelölések automatikus elfogadása Tudnivaló: NEM fogod tudni helyreállítani vagy megváltoztatni a jelmondatot az esetben ha elveszíted.]]> @@ -106,7 +106,7 @@ Blokkolás admin Fénykép előnézet megszakítása - Minden adat törlődik amint bevitelre kerül. + Az adat törlődik miután a számkód bevitelre kerül. Kérte a videó elfogadását Tag blokkolása Néhány további dolog @@ -144,11 +144,11 @@ Jobb üzenetek A cím változtatás megszakításra kerül. A régi fogadó cím marad használatban. Engedélyez - Rossz asztal cím + Téves számítógép cím Adj hozzá profilt Csatolás - App számkód - Kérte, hogy fogfaja a képet + App PIN kódja (számkód) + Felkértek a kép fogadására Fényképező A Keystore-hoz nem sikerül hozzáférni az adatbázis jelszó elmentése végett hívás folyamatban @@ -175,8 +175,8 @@ Adatbázis jelmondat megváltoztatása? kapcsolódva Számkód megváltoztatása - %s to %s megváltozott szerepköre - Fogadó szerver cím megváltoztatása + a szerepkör %s -ról(-ről), %s -ra(-re) változott + A fogadó szerver címének megváltoztatása Változtatás Számkód megerősítése Jelszó megerősítése @@ -186,13 +186,13 @@ kapcsolódva Kapcsolódás kapcsolódva - Összekapcsolt telefon + Csatlakoztatott telefon kapcsolódva Szerepkör megváltoztatása Kapcsolódva Belépési adatok megerősítése - Fogadó szerver cím megváltoztatása - megváltozott azonosító számodra + Fogadó szerver cím megváltoztatása? + megváltozott az azonosító számodra Önmegsemmisítő mód megváltoztatása a szerepköröd megváltoztatva %s-ra(-re) Kapcsolódás @@ -204,7 +204,7 @@ Csoport létrehozása véletlenszerűen létrehozott profillal. Az ismerős és az összes üzenet törlésre kerül - ez visszafordíthatatlan! Ismerősök megjelölhetik az üzeneteket törlendőként; de láthatod azokat. - Kapcsolódás egy Egyszer használatos linkkel? + Kapcsolódás Egyszer használatos linkkel? Kapcsolódás egy link / QR-kód által Kapcsolódási hiba (AUTH) Ismerős neve @@ -242,7 +242,7 @@ \n- kézbesítési igazolások (20 tagig). \n- gyorsabb és stabilabb Hozzájárulás - kapcsolódás (meghívás bemutatkozásra) + csatlakozás (bemutatkozás meghívás) SimpleX azonosító létrehozása törölt ismerős Tag üzenetének törlése? @@ -254,7 +254,7 @@ Csoport létrehozása Chat profil Profil létrehozása - Csatlakoztatott asztal + Csatlakoztatott számítógép Törölve ekkor: %s Törölve ekkor Kínai és spanyol kezelőfelület. @@ -279,7 +279,7 @@ Jelenleg támogatott legnagyobb fájl méret: %1$s. Fájl törlése Hamarosan! - azonosító megváltoztatása %s számára… + azonosító megváltoztatása %s -ra/re… Chat adatbázis importálva CHAT ARCHÍVUM Üzenetek törlése? @@ -290,10 +290,10 @@ Színsémák személyreszabása és megosztása Chat profil törlése? Titkos csoport létrehozása - Kapcsolódva az asztalhoz + Csatlakozva a számítógéphez ICE sezrverek beállítása Csoport törlése - Chat hitelesításe + Hitelesítés törlése szerző Megerősítés Törlés nálam @@ -308,7 +308,7 @@ kapcsolódás… Hívás kapcsolása Fájlok illetve fotók/videók törlése? - befejezve + befejezett CHAT ADATBÁZIS Önmegsemmisító számkód megváltoztatása Várólista létrehozása @@ -335,7 +335,7 @@ Ismerős törlése Létrehozva: %1$s azonosító megváltoztatása… - Kapcsolódva a mobilhoz + Csatlakoztatva a mobilhoz Jelenlegi jelmondat… Fájl választása Egyszer használatos meghívó link létrehozása @@ -355,13 +355,13 @@ Chat kiürítése? Adatbázis downgrade? Chat kiürítése - Adatbázis titkosítási jelmondat meg lesz változtatva. + Adatbázis titkosítási jelmondat frissítve lesz. Kapcsolódás automatikusan Adatbázis hiba Adatbázis titkosítási jelmondat frissül és eltárolásra kerül a beállításokban. Adatbázis ID Adatbázis ID: %d - Adatbázis azonosítók és \"Transport Isolation\" opciók. + Adatbázis azonosítók és Átviteli izolációs beállítások. Az adatbázis titkosítás jelmondata megváltoztatásra és elmentésre kerül a Keystore-ban. Az adatbázis titkosításra kerül és a jelmondat eltárolásra a beállításokban. Szerver törlése @@ -371,9 +371,9 @@ Engedélyezve minden csoportnak engedélyezve az ismerősnek Eltűnő üzenetek tiltottak ebben a csoportban. - Azonosító törlés + Azonosító törlése %d hét - PC címe + Számítógép azonosítója %ds Kézbesítési izagolások! Eszközhitelesítés nincs bekapcsolva. Bekapcsolhatod a SimpleX zárat a Beállításokon keresztük, miután bekapcsoltad az eszközhitelesítést. @@ -407,7 +407,7 @@ ESZKÖZ e2e titkosított videóhívás közvetlen - PC + Számítógép %d perc %d ismerős(-ök) kiválasztva Engedélyez @@ -420,7 +420,7 @@ %d nap Chat archív törlése? Duplikálódott megjelenítési név! - Letiltás(felülírások megtartásával) + Letiltás (felülírások megtartásával) "Adatbázis upgrade" %d üzenet blokkolva Eltűnik ekkor @@ -437,13 +437,13 @@ Az adatbázis titkosításra kerül és a jelmondat eltárolásra a Keystore-ban. Automatikus üzenet törlés engedélyezve? Törlés - az adatbázis verzió újabb, mint az app, de nincs lefelé migráció eddig: %s + az adatbázis verzió újabb, mint az alkalmazás, de nincs lefelé migráció eddig: %s Leírás %d óra %dp Szétkapcsolás Szerkesztés - Letiltás(csoport felülírások megtartásával) + Letiltás (csoport felülírások megtartásával) %d csoportesemény %d hónap Csoport profil szerkesztése @@ -465,7 +465,1066 @@ Kézbesítés %d fájl %s összméretben Adatbázis jelmondat szükséges chat megnyitásához. - %dn + %dnap Engedélyeve mindenki számára Kézbesítési izagolások kikapcsolva! + Lenyit + Hiba az üzenet küldésekor + Add meg a Számkódot + Mindenkinek + Titkosítás újra egyeztetési hiba + Hiba az adatbázis titkosításakor + Hiba a csoport törlésekor + Kilépés mentés nélkül + Tárolt fájlok, képek és videók titkosítása + Hiba az azonosító beállításakor + A csoport meghívó lejárt + Hiba az ICE szerverek elmentésekor + Hiba + Hiba + Hiba az XFTP szerverek betöltésekor + Hiba az SMP szerverek betöltésekor + Hiba a hálózat konfigurációjának frissítésekor + TCP életbentartás engedélyezése + Kamera megfordítása + Szia! +\nKapcsólódj Connect to me via SimpleX Chat: %s + A megjelenített név nem tartalmazhat szóközöket. + Csoport + Add meg az üdvözlőszöveget… (opcionális) + Hiba a chat adatbázis exportálásakor + Hiba a fájl elmentésekor + Helyi fájlok titkosítása + titkosítás egyeztetve %s számára + %d üzenet megjelölve törlésre + titkosítás újra egyeztetése engedélyezve + Önmegsemmisítés engedélyezése + Olvasatlan és kedvenc chatekre szűrés. + Chatek betöltése sikertelen + A csoport már létezik! + Francia kezelőfelület + Csoport linkek + Végre, megvannak! 🚀 + Hiba a chat elindításakor + A csoport profil a tagok eszközein tárolódik, nem a szervereken. + Add meg a jelmondatot… + Hiba a felhasználói beállítások frissítésekor + Titkosít + Csoport nem található! + Hiba az SMP szerverek elmentésekor + Downgrade és chat megnyitása + A csoport inaktív + Gyors és nincs várakozás a küldő online állapotára! + Hiba a csoporthoz csatlakozáskor + Kedvenc + Csoport moderáció + Fájl + Csoport link + titkosítás újra egyeztetés szükséges %s számára + Hiba a profil váltásakor! + Kísérleti funkciók + Engedélyezés (felülírások megtartásával) + Helyes jelmondat bevitele. + A csoport törlésre kerül számodra -ez visszafordíthatatlan! + Adatbázis titkosítása? + Lezárási képernyőn a hívások engedélyezése a beállításokban. + titkosítás egyeztetve + Kézbesítési igazolások engedélyezése? + Hiba a csoport profil elmentésekor + hiba + A fájl törölve lesz a szerverekről. + Akkor is, ha le van tiltva a beszélgetésben. + Gyorsabb csatlakozás és megbízhatóbb üzenet kézbesítés. + Zár engedélyezése + SEGÍTSÉG + Teljesen decentralizált - kizárólag tagok számára látható. + Fájl: %s + Hívás befejezése + Hiba a csoport linkjének törlésekor + Fájl elmentve + Kapcsolat javítása? + Fájlok és médiatartalom + A KONZOL SZÁMÁRA + Titkosítás újra egyeztetés sikertelen. + Hiba a felhasználói profil törlésekor + A javítás nem támogatott a csoporttag által + Add meg az üdvözlőszöveget… + Titkosított adatbázis + Add meg a jelszót a keresőben + A fájl megérkezik amint az ismerősöd befejezi a feltöltését. + Fájl letöltése + Chat betöltése sikertelen + Szerver megadása kézileg + A fájl megérkezik amink az ismerősöd online lesz, kélrlek várj vagy nézz vissza később! + Hiba a csoport linkjének létrehozásakor + A Galériából + Engedélyezés (csoport felülírások megtartásával) + Hiba az ismerős törlésekor + A csoporttagok visszafordíthatatlanul törölhetnek elküldött üzeneteket. + Hiba a szerepkör megváltoztatásakor + Javítás + A csoporttagok küldhetnek eltünő üzeneteket. + Kapcsolat javítása + Hiba a profil létrehozásakor! + Hiba a tag(-ok) hozzáadásakor + Fájl + A csoporttagok küldhetbnek fájlokat és képeket/hangfájlokat. + Törlés miután + Hiba a beállítás megváltoztatásakor + Hiba a csoport link frissítésekor + a csoport törölve + csoport profil frissítve + Hiba a függőben lévő parner kapcsolatának törlésekor + Hiba a chat adatbázis importálásakor + Hiba a kézbesítési igazolások engedélyezésekor! + Hiba az XFTP szerverek mentésekor + A csoporttagok küldhetnek közvetlen üzeneteket. + Hiba a tag eltávolításakor + befejeződött + Csoport üdvözlő szöveg + Csoport neve: + Hiba a meghívó küldésekor + Add meg a nevedet: + Hiba a felhasználó jelszavának mentésekor + Színséma exportálása + Add meg ennek az eszköznek a nevét… + Hiba + A csoport meghívó már nem érvényes, el lett távolítva a küldője által. + A csoport teljes neve: + segítség + Önmegsemmisítő számkód engedélyezése + KÍSÉRLETI + Hiba az azonosító megváltoztatásának megszakításakor + Hiba a fájl fogadásakor + titkosítás rendben + Hiba az ismerős kérés törlésekor + Kézbesítési igazolások engedélyezése csoportok számára? + A javítás nem támogatott az ismerős által + Fájl nem található + Kapcsolat bontása + A csoporttagok reagálhatnak emocikonokkal az üzenetekre. + Adatbázis exportálása + Teljes név: + Tovább csökkentett akkumulátor használat + Hiba a chat megállításakor + titkosítás rendben %s számára + Csoport törlésre kerül minen tag számára - ez visszafordíthatatlan! + Titkosítás javítása az adatmentések helyreállítása után. + Hiba a chat adatbázis törlésekor + Teljes link + Hiba az azonosító megváltoztatásakor + A csoporttagok küldhetnek hangüzeneteket. + Csoport beállítások + Hiba: %s + Eltűnő üzenetek + SimpleX Zár engedélyezése + Hiba a kapcsolat szinkronizációjakor + Hiba az azonosító létrehozásakor + engedélyezve + Hiba a részletek betöltésekor + Hiba az ismerős kérés elfogadása + titkosítás újra egyeztetése engedélyezve %s számára + titkosítás újra egyeztetés szükséges + Rejtett chat profilok + Fájlok és képek/videók + Képek elmentve a fotóalbumba + Elrejt + Azonnal + Fájlok és képek/videók tiltottak! + Profil elrejtése + Hogyan használd a szervereidet + Találd meg a chat üzeneteket gyorsabban + Színséma importálása + Hiba a színséma importálásakor + Ismerős és üzenet elrejtése + Nem kompatibilis adatbázis verzió + Hogyan működik a SimpleX + Nem kompatibilis verzió + Elrejt + Bejövő videóhívás + Téves számkód + Azonnali + Inkognitó csoportok + Hogyan + Elrejt + Kép + Fejlesztett adatvédelm és biztonság + Figyelmen kívül hagyás + Kép elküldve + Rejtett + Házigazda + Kezdeti szerepkör + Érvénytelen chat + órák + Inkognitó + Hogyan használjuk + App képernyőjének elrejtése a gyakran használt appok között. + Javított szerver konfiguráció + Történet + Rejtett profil jelszó + Adatbázis importálása + Importálás + Azonnali értesítések + Inkognitó mód + Chat adatbázis importálása? + Azonnali értesítések kikapcsolva! + Azonnali értesítések! + Kép + Fájlok és képek/videók tiltottak ebben a csoportban. + Hogyan működik + Elrejt: + Hiba az ismerősöddel való kapcsolat létrehozásában + ICE-kiszolgálók (soronként egy) + kiszkennelheted a QR-kódot, vagy a pertnered megoszthat egy meghívó linket.]]> + Ha az alkalmazás megnyitásakor megadod ezt a jelszót, az alkalmazás minden adata visszavonhatatlanul törlődik! + Ha nem tudtok személyesen találkozni, mutassátok meg a QR-kódot egy videohívás során, vagy osszátok meg egymással a linket. + mutassátok meg a QR-kódot a videohívásban, vagy osszátok meg egymással a linket.]]> + Ha megerősíted, az üzenetküldő szerverek láthatják az IP-címedet és a szolgáltatódat, vagyis azt, hogy mely szerverekhez csatlakozol. + A kép akkor érkezik meg, amikor az ismerősöd befejezi a feltöltést. + QR kód beolvasásával]]> + Ha kaptál egy SimpleX Chat meghívó linket, akkor megnyithatod azt a böngészőben: + Ha az alkalmazás megnyitásakor megadod az önmegsemmisítő jelszót: + Megtalált asztali számítógép + Számítógépek + Hogyan használjuk a markdown-t + Csevegő profil létrehozása + Spam és visszaélések elleni védelem + Mobilok leválasztása + Különböző nevek, avatarok és átviteli izoláció. + Ha úgy döntesz, hogy elutasítod a küldő NEM kap értesítést. + Szerepkör kibővítése + A kép akkor érkezik meg, amikor az ismerősöd elérhető lesz, kérlek várj vagy nézd meg később! + meghívott + Érvénytelen kapcsolati hivatkozás + Elnémít + nincsenek részletek + Nem fogadott hívás + Világos + Az üzenet törlésre kerül - ezt nem lehet visszafordítani! + Segítség \"markdown\" funkcióhoz: Üzenetek formázása a szövegbe szúrt speciális karakterekkel + új üzenet + Régi adatbázis archívum + Hálózati beállítások + Nincs kézbesítési információ + moderált + A tag eltávolítása a csoportból - ezt nem lehet visszafordítani! + Győződj meg róla, hogy az XFTP-kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nem duplikáltak. + Nincsenek ismerősök kiválasztva + Nincsenek fogadott vagy küldött fájlok + Megnyitás mobil alkalmazásban, majd koppints a Csatlakozás \u0020gombra az alkalmazásban.]]> + Markdown az üzenetekben + meghívás a %1$s csoportba + Lezárás mód + Új mobil eszköz + Kapcsolatok megtartása + Tagok meghívása + Üzenet reakciók + Egyszerre csak egy eszköz működhet + Csatlakozol a csoportodhoz? + Nagy fájl! + Helyi név + Hálózat és szerverek + Értesítés előnézet + Társítsd össze a mobil és az asztali alkalmazásokat! 🔗! + közvetett (%1$s) + Hamarosan további fejlesztések érkeznek! + Az üzenetreakciók ebben a csevegésben tilosak. + Helytelen biztonsági kód! + Ez akkor fordulhat elő, ha Te vagy az ismerősöd a régi adatbázis biztonsági másolatát használta. + Új asztali alkalmazás! + Most már az adminok is: +\n- törölhetik a tagok üzeneteit. +\n- letiltani a tagokat (\"megfigyelő\" szerepkör) + %1$s meghívott + Az üzenetreakciók ebben a csoportban tilosak. + Nem + nincs szöveg + TAG + Ez később a beállításokon keresztül módosítható. + Új tag szerepköre + Ki + Érvénytelen hivatkozás! + A csatlakozáshoz Onion host-okra lesz szükség. + Változások a %s verzióban + Onion host-okat használ, ha azok rendelkezésre állnak. + Érvénytelen szervercím! + k + soha + (új)]]> + Győződj meg róla, hogy az SMP-kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nincsenek duplikálva. + Onion host-ok nem lesznek használva. + perc + Tudj meg többet + Új kapcsolattartási kérelem + Csatlakozás a csoporthoz + Összekapcsolt asztali eszköz beállítások + meghívva a csoport hivatkozásodon keresztül + elhagyta + Összekapcsolt asztali eszközök + Nincs alkalmazás jelszó + Némítás, ha inaktív! + A meghívó lejárt! + (csak a csoporttagok tárolják) + Moderál + be + Japán és Portugál kezelőfelület + Ebben a csoportban tilos az üzenetek visszafordíthatatlan törlése. + Onion host-ok nem lesznek használva. + %s eszközzel megszakadt a kapcsolat]]> + hónap + Üzenet vázlat + Egy üzenet eltüntetése + Visszafordíthatatlan üzenettörlés + Egyszerre csak 10 videó küldhető el + Csak te adhatsz hozzá üzenetreakciókat. + elhagyta + Ebben a csevegésben tilos az üzenetek visszafordíthatatlan törlése. + Max 40 másodperc, azonnal fogadható. + inkognitó a kapcsolattartási címen keresztül + A csatlakozáshoz Onion host-okra lesz szükség. +\nKérjük, vedd figyelembe: .onion cím nélkül nem fogsz tudni csatlakozni a szerverekhez. + Olasz kezelőfelület + Nincsenek háttérhívások + Üzenetek + Összekapcsolt mobil eszközök + Lehetővé teszi, hogy egyetlen csevegőprofilon belül több anonim kapcsolat legyen, anélkül, hogy megosztott adatok lennének közöttük. + Az üzenet törlésre lesz jelölve. A címzett(ek) képes(ek) lesz(ek) felfedni ezt az üzenetet. + Elhagy + Rendben + Nincsenek szűrt csevegések + Érvénytelen adat + Győződj meg róla, hogy a WebRTC ICE-kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nem duplikáltak. + Csak a csoporttulajdonosok engedélyezhetik a fájlokat és a médiát. + A fájl betöltése + Nincs hozzáadandó ismerős + Üzenet vázlat + meghívott, hogy csatlakozz + Egyszer használatos meghívó link + Értesítések + Egyszerre csak 10 kép küldhető el + ajánlott %s: %2s + Nem kompatibilis! + Tedd priváttá a profilodat! + Üzenet kézbesítési hiba + Több csevegőprofil + töröltnek jelölve + Elnémít + Egy mobil összekapcsolása + Értesítési szolgáltatás + Csak a csoporttulajdonosok engedélyezhetik a hangüzeneteket. + 2 rétegű végponttól-végpontig titkosítással küldött üzeneteidet.]]> + Érvénytelen migrációs visszaigazolás + Csak a csoporttulajdonosok módosíthatják a csoportbeállításokat. + Nincsenek előzmények + Érvénytelen QR-kód + Olvasottnak jelölve + ÉLŐ + Olvasatlannak jelölve + Több + Jelentkezz be a hitelesítő adataiddal + érvénytelen üzenet formátum + Csatlakozás + Az értesítések az alkalmazás elindításáig nem fognak működni. + ki` + (ez az eszköz) v%s)]]> + ajánlott %s + Csoport elhagyása + A %s-től érkező üzenetek megjelennek! + Ha a SimpleX Chat-nek nincs felhasználói azonosítója, hogyan lehet mégis üzeneteket küldeni?]]> + Ez akkor fordulhat elő, ha: +\n1. Az üzenetek az ismerősödnél 2 nap után, vagy a kiszolgálón 30 nap után lejártak. +\n2. Az üzenet visszafejtése sikertelen volt, mert Te vagy az ismerősöd régi adatbázis biztonsági mentést használt. +\n3. A kapcsolat sérült. + megfigyelő + inkognitó a csoportos hivatkozáson keresztül + Onion host-okat használ, ha azok rendelkezésre állnak. + Barátok meghívása + Menük és figyelmeztetések + Tagok meghívása + Csatlakozás mint %s + Nincs kiválasztott csevegés + Csak helyi profiladatok + inkognitó Egyszer használatos link által + Moderálva: %s + Egyszer használatos meghívó link + Érvénytelen név! + Beszélgessünk a SimpleX Chat-ben + Moderálva + Élő üzenetek + Ellenőrzöttként jelölve + Üzenet kézbesítési bizonylatok! + hivatkozás előnézeti képe + Elhagyod a csoportot? + nem + Hamarosan további fejlesztések érkeznek! + ki + Telepítse a SimpleX Chat-et a terminálhoz + Új megjelenített név: + Új jelmondat… + nem fogadott hívás + Migrációk: %s + Válasz neki + Üzenet szövege + Az értesítések csak az alkalmazás bezárásáig érkeznek! + Információ + ÜZENETEK ÉS FÁJLOK + tag + Privát kapcsolat létrehozása + Moderálva %s által + Győződj meg róla, hogy a fájl helyes YAML-szintaxist tartalmaz. Exportáld a témát, hogy legyen egy példa a téma-fájl szerkezetére. + dőlt + Érvénytelen fájl elérési útvonal + Csatlakozol a csoporthoz? + nincs e2e titkosítás + Új adatbázis-archívum + Élő üzenet! + Meghívás a csoportba + Lezárás miután + Bejövő hanghívás + Kulcstartó hiba + Csatlakozol a csoporthoz? + Az inkognitó mód úgy védi a magánéletét, hogy minden egyes kapcsolathoz új, véletlenszerű profilt használ. + - stabilabb üzenetkézbesítés. +\n- egy kicsit jobb csoportok. +\n- és még sok más! + Üzenet reakciók + Nincs csatlakoztatott mobil eszköz + Hálózat állapota + Új jelszó + Valószínűleg ez az ismerősöd törölte a kapcsolatát veled. + Csatlakozás inkognitóban + Chat megnyitása + elutasított hívás + Rendszeres + fogadott, tiltott + Kapcsolódási kérés megismétlése? + Kizárólag te tudsz véglegesen törölni üzeneteket (az ismerősöd csak megjelölheti azokat törlendőként) + Szerepkör + SimpleX ismerős azonosítója + Megállítás + Előre beállított szerver + Új chat kezdése + Nyílt forráskódú protokoll és program - bárki üzemeltethet szervereket. + Megnyitás + Protokoll időtúllépés + titok + Előnézet mutatása + várakozás visszaigazolásra… + Fájl megállítása + csoport linken keresztül + PING időköze + Eltűnő üzenet küldése + Önmegsemmisítési számkód + Mentés és a csoport profil frissítése + A te adatvédelmed + A te SimpleX azonosítód + Kérlek jelentsd a fejlesztőknek! + Az emberek kizárólag az általad megosztott link alapján kapcsolódhatnak hozzád. + Eltűnő üzenetek küldésének tiltása. + Kizárólag te tudsz hangüzeneteket küldeni. + Frissítés + Videó elküldve + Adatbázis jelmondat megváltoztatása + App beállítások megnyitása + Számkód nem változott! + Frissítés + Választás + Kizárólag te tudsz hívásokat indítani. + Biztonságos várólista + Értékeld az appot + Egyszer használatos link megosztása + Hiba az adatbázis visszaállításakor + %s és %s + Te engedélyezed + Takarékos akkumulátor használat + Mentés és az ismerősök értesítése + Előnézet + Használd a chatet + Megosztás + Fogadott üzenet + Üdvözlő szöveg + %s, %s és %d további tagok csatlakozva + Kizárólag az ismerősöd tud hívást indítani. + SZÍNSÉMÁK + Túl sok videó! + Chat szolgáltatás megállítása az adatbázis műveletek elvégzéséhez. + Üdvözöllek! + Önmegsemmisítési számkód + (beolvasás vagy vágólapról beillesztés) + Videóra várakozás + Válasz + Ez a te egyszer használatos linked! + SimpleX Chat hívások + Az új inkognító profil használata + Kérlek frissítsd az appot és jelentsd a fejlesztőknek! + SimpleX + Link előnézet küldése + biztonsági kód megváltozott + Kizárólag az ismerős mutatása + Hangszóró bekapcsolva + Indítsd újra az appot, hogy importált chat adatbázist használj. + jogosulatlan küldés + Kizárólag az ismerősöd tud hangüzeneteket küldeni. + Beállítások + Kapcsolat létrehozásához az ismerősöd beolvassa a QR-kódodat vagy a linket használja az appban. + fogadott visszaigazolás… + A biztonsági kód beolvasása az ismerősöd appjából. + Kérlek vedd fel a kapcsolatot a csoport adminnal! + Videó bekapcsolva + Profil neve: + Beillesztés + Köszönjük, hogy telepítetted a SimpleX Chatet! + Csillag a GitHub-on + Eltávolítás + Keresés + Titkosítás újraegyeztetése? + Önmegsemmisítési számkód engedélyezve! + Biztonsági kiértékelés + Cím + Üzenet Elküldése + Adatbázis mentés visszaállítása + Visszavon + Kérd meg az ismerősödet, hogy engedélyezze a hangüzenetek küldését! + egyszer használatos linket osztottál meg + A link megnyitása böngészőben gyengítheti az adatvédelem és biztonság szintjét. A megbízhatatlan SimpleX linkek vörössel vannak kiemelve. + Az ICE szervereid + A kapcsolódást elfogadtad + Elutasítás + Ismerős és üzenet mutatása + BEÁLLÍTÁSOK + Felhasználói fiók jelszavának elmentése + Fájl küldés megszakítása? + Számítőgép leválasztása? + A hangüzenetek le vannak tilva! + A kapcsolódáshoz közvetlen üzenet küldése + PING számláló + Fejlesztői beállítások mutatása + %s csatlakozva + Rendszer + Amikor elérhető + Hangüzenet (%1$s) + %s (jelenlegi) + A te szervered + Véletlen + Megosztás ismerősökkel... + te + Nincsenek chat üzeneteid + Küldés + %s másodperc + %s: %s + A SimpleX nem tud a háttérben futni. Csak az app futása közben fogod az üzeneteket megkapni. + Túl sok kép! + Archív mentése + "%s, %s és %d tagok" + Chat szolgáltatás megállítása + SimpleX linkek + Az elküldött üzenetek törlésre kerülnek a beállított idő után. + Némítás feloldása + Elküldve ekkor: %s + Jelenlegi profil használata + Ez az eszköz + "Azonosító megosztása az ismerősökkel?" + Profil jelszó + Színséma + Jelmondat eltávolítása a beállításokból? + SimpleX csoport link + Képre várakozás + Önmegsemmisítés + várakozás válaszra… + Ismerős nevének beállítása… + Tag feloldása + QR-kód beolvasása + Szerver tesztelése + Írj nekünk e-mailben! + SZERVEREK + Szerverek tesztelése + Számkód bevitel + Rendszer + Eljuttat + Biztonsági kód + Kérlek írd be a helyes jelmondatot! + Végleges üzenet törlés tiltása. + Üzenetekre adott emoji reakciók tiltása. + Véletlenszerű jelmondat használata + ponttól-pontig + CHAT SZOLGÁLTATÁS INDÍTÁSA + Fogadott link beillesztése + Szerverek elmentése? + A SimpleX Chat biztonsága a Trail of Bits által lett auditálva. + módosított csoport profil + TÁMOGASD A SIMPLEX CHATET! + SimpleX Chat szolgáltatás + Nem tudsz üzeneteket küldeni! + %s ellenőrzött + Jelszó mutatása + Adatvédelem és biztonság + Tag eltávolítása + Számkód beállítva! + Elküldött üzenet + Ismerősök kiválasztása + ismeretlen üzenet formátum + Szerverek elmentése + Üdvözlő üzenet + másodperc + Profil változtatása frissítésre kerül az ismerőseidnél. + Egyszerűsített inkognító mód + Üdvözlőszöbeg elmentése? + Indítsd újra az appot, hogy új felhasználói fiókot hozz létre. + Engedély megtagadva! + Főggőben lévő hívás + Adatbázis megnyitása… + Leállítás? + Jelmondat szükséges + Privát értesítések + Meghívtál egy ismerőst + %s nincs ellenőrizve + A csatlakozáshoz érintsd meg + Ennek az eszköznek a neve + A te jelenlegi profilod + Fájl feltöltése + Hang- és videóhívások tiltása. + Megkövetelt + SimpleX chat üzenetek + Visszaállítás + Adatbázis jelmondat beállítása + Elküldött üzenet + Rendszeresen elindul + Ez a te saját SimpleX azonosítód! + eltávolítva + Link megosztása + SimpleX Csapat + profilkép + A te chat profiljaid + tulajdonos + Bekapcsolás + %s, %s és %s csatlakozva + SimpleX egyszer használatos meghívó + A te hívásaid + küldés sikertelen + SZÍNSÉMA SZÍNEK + Visszaállít + Kérlek írd be az előző jelszót az adatbázis visszaállítása után. Ez a művelet visszafordíthatatlan. + Másodlagos + SOCKS PROXY + Mentés + Újraindítás + Üzenetküldő (SMP) szerverek + Videó + Automatikus elfogadási beállítások mentése + Újraegyzetetés + Videóra várakozás + A te Simplex fájl küldő (XFTP) szervereid + Videó kikapcsolva + Privát fájl nevek + Beállítások elmentése? + Számkód + Ismeretlen hiba + A te szerver címed + Chat konzol megnyitása + Tag eltávolítása + Beállított adatbázis jelmondat + Biztonsági kód megtekintése + Tag feloldása? + A küldő törölhette a kapcsolódási kérelmet. + Téves adatbázis jelmondat + A te Simplex chat (SMP) szervereid + Visszaigazolások letiltva + Adatbázis mappa megnyitása + egyszer használatos linken keresztül + Csoportbeállítások megadása + %1$s keresztül + igen + Hangüzenet + Számítógépről használat + TE + port %d + Kapcsolódás link által + Azonosító megosztása + Szerver QR-kód beolvasása + Megállítás + Azonosítód megosztásának szüneteltetése? + Chat profilok megnyitása + Csatlakozási kérés megismétlése? + Képre várakozás + Hangüzenetek + Tag eltávolítása? + Biztonsági kód megtekintése + eltávolított téged + SimpleX azonosító + Mutat: + fogadott válasz… + Adatbázis mentés visszaállítása? + Üzenetek fogadása… + %s és %s csatlakozva + megfigyelő vagy + Port + Számkód beállítása + Milyen újdonságok vannak + Csoport megnyitása + Elküldve ekkor + Hangüzenetek küldésének tiltása. + Utolsó üzenetek megjelenítése + Előre beállított szerver címe + Rendszeres értesítések letiltva! + Számkód megváltozott! + Akkor fut, amikor az app nyitva van + Ez a QR-kód nem egy link! + Fájlra várakozás + simplexmq: v%s (%2s) + Szétkapcsolás + A te véletlenszerű profilod + Téves jelmondat! + Üzenetekre adott emoji reakciók tiltása. + Rendszer + olvasatlan + Függő + Üdvözöllek %1$s! + Jelmondat eltávolítása a Keystrore-ból? + Feloldás + Eltűnő üzenetek küldésének tiltása. + Videó + Frissítés + Megnyitás + Rendszeres értesítések + Kihagyott üzenetek + Hangüzenetek küldésének tiltása. + Ismerős nevének beállítása... + Kizárólag te tudsz eltűnő üzeneteket küldeni. + Kép/videó megoszása… + te: %1$s + A te beállításaid + Színek alaphelyzetbe állítása + Mentés + Váltás + Illeszd be a kapott linket az ismerősödhöz való kapcsolódáshoz… + Kód beolvasása + Port megnyitása a tűzfalon + indítás… + Szín elmentése + Leállítás + elküldve + SOCKS proxy használata + Élő üzenet küldése + Adatvédelem újraértelmezve + Hangüzenet… + App képernyőjének védelme + QR-kód mutatása + videóhívás + Nem kedvenc + Küldési visszaigazolások + SimpleX Azonosító + Érintsd meg a gombot + Mentés és az ismerős értesítése + Elutasított hívás + SOCKS proxy beállítások + QR-kód + Titkosítás újraegyeztetése + Eltávolítás + TOR .onion hostok használata + Megmutat + SimpleX Zár mód + Fájl visszavonása + Fájl küldő/fogadó (XFTP) SimpleX szerverek + Fájlok, képek és videók küldésének tiltása. + Fájl megosztása… + Mentés + közvetítő szerveren keresztül + Megosztás leállítása + te eltávolítottad %1$s + Jelmondat elmentése és chat megnyitása + Beállítások elmentése? + Az első chat rendszer bármiféle felhasználó azonosító nélkül - privátra lett tervezre. + Tagoknak való közvetlen üzenetküldés tiltása. + SOCKS proxy használata? + Hangszóró kikapcsolva + hét + Mutasd + WebRTC ICE szerverek + Fájl visszavonása? + Közvetlen üzenet küldése + Elutasítás + Küldés + Rendszer hitelesítés + Böngészőn keresztül + A chat profiljaid védelme jelszóval! + Kizárólag az ismerősöd tud eltűnő üzeneteket küldeni. + A te ICE szervereid + QR-kód beolvasása asztali számítógépről + SimpleX Logo + Feloldás + Némítás feloldása + SimpleX chat megnyitása a hívás fogadásához + Fájl fogadás megszakítása? + - opcionális értesítés a törölt ismerősök számára +\n- profil nevek szóközökkel +\n- és továbbiak! + Lengyel kezelőfelület + Használd a szervert + Fogadva ekkor: %s + SimpleX Zár + Mentés és a csoporttagok értesítése + Alaphelyzetbe álítás + Kizárólag az ismerősöd tud emoji reakciókat adni az üzenetekre. + Hangüzenetek + te távoztál + Hangüzenet rögzítése + SimpleX Zár bekapcsolva + közvetlen üzenet küldése + Telefonről beolvasás + Kapcsolatok ellenőrzése + Üzenet megosztása… + másodperc + SimpleX Zár nincs engedélyezve! + SimpleX Zár + A te beállításaid + Chat adatbázis használata + eltávolítva %1$s + Szerver teszt sikertelen! + Kapcsolat ellenőrzése + Tudj meg többet + A küldő megszakította a fájl átvitelt. + Chat szolgáltatás megállítása? + Fogadva ekkor + Beállítva 1 nap + Felfedés + Fogadott üzenet + Kizárólag az ismerősöd tud véglegesen törölni üzeneteket (te csak törlendőként tudod megjelölni azokat). + Önmegsemmisítési számkód megváltozott + SimpleX Chat szerverek használatban. + SimpleX Chat szerverek használata? + Chat profil felfedése + Videók és fájlok 1Gb méretig + TCP kapcsolat időtúllépés + A te %1$s SimpleX azonosítód megosztásra kerül. + Már csatlakozva vagy hozzá: %1$s. + A jelenlegi chat adatbázisod TÖRLÉSRE és FELCSERÉLÉSRE kerül az importált által! +\nEz a művelet nem visszavonható - a profilod, az ismerőseid, a chat üzeneteid és fájljaid mind véglegesen elvesznek! + Ötletek és kérdések beküldése + Figyelem: néhány adatot elveszíthetsz! + Új chat kezdése + Várakozás a számítógépre… + A privát chatelés új generációja + Hálózati beállítások megváltoztatása? + Várakozás a mobiltelefon csatlakozására: + Kapcsolat biztonságának ellenőrzése + fájlok küldése egyelőre még nem támogatott + Megváltoztattad az azonosítót erre: %s + fájlok fogadása egyelőre még nem támogatott + Csoport profil elmentése + Alaphelyzetbe állítás + Hacsaknem az ismerősöd megszakította a kapcsolatot avagy ez a link már használva volt, ez egy programhiba lehet - kérlek jelentsd be. +\nA kapcsolatfelvételhez kérd meg az ismerősödet, hogy készítsen egy új linket a kapcsolatfelvételhez és ellenőrizd az internet kapcsolatodat. + videóhívás (nem e2e titkosított) + Alkalmazás új kapcsolatokhoz + Az app rendszeresen lekéri az új üzeneteket - ez naponta néhány százalék akkumulátort használ. Az app nem használja a Google push értesítési rendszert — az eszközödön lévő adat nem kerül megküldésre a szervereknek. + Számítógép címének beillesztése + ismerős azonosítójának linkjén keresztül + SimpleX háttérszolgáltatást használja - ez naponta néhány százalék akkumulátort használ.]]> + Az ismerősöd online kell legyen ahhoz, hogy a kapcsolat létrejöjjön. +\nMegszakíthatod ezt a kapcsolatfelvételt és törölheted az ismerőst (és később ismét megpróbálhatod egy új linkkel) + Jelmondat nem található a Keystore-ban, kérlek írd be kézileg. Ez akkor történhet, ha helyreállítottad az appot a backup funkcióval. Ha nem így történt, az esetben lépj kapcsolatba a fejlesztőkkel! + Az ismerőseid továbbra is megmaradnak + A szervernek engedélyre van szüksége a várólisták létrehozásához, ellenőrizd a jelszavadat + Az adatbázis nem működik megfelelően. Koppints a további információkért + A fájl küldése leállt. + Csatlakozási kísérlet a kapcsolat üzeneteinek fogadására használt kiszolgálóhoz ettől az ismerősödtől. + Nem sikerült ellenőrizni téged; kérjük, próbáld meg újra. + Az üzenet minden tag számára moderáltként lesz megjelölve. + Ha szeretnél értesítéseket kapni, kérjük, add meg az adatbázis jelszavát. + A teszt a(z) %s lépésnél sikertelen volt. + Az alkalmazás indításakor, vagy 30 másodpercnyi háttérben töltött idő után az alkalmazáshoz visszatérve hitelesítened kell magad. + Az üzenet minden tag számára törlésre kerül. + A videó nem dekódolható. Kérjük, próbálj meg egy másik videót, vagy lépj kapcsolatba a fejlesztőkkel. + Ez a szöveg a beállítások között érhető el + A profilodat elküldjük annak az ismerősödnek, akitől ezt a linket kaptad. + Az alkalmazás 1 perc után bezárható a háttérben. + meg lettél hívva a csoportba + engedélyezd a SimpleX háttérben történő futtatását a következő párbeszédpanelen. Ellenkező esetben az értesítések le lesznek tiltva.]]> + A kiszolgálónak engedélyre van szüksége a várólisták létrehozásához, ellenőrízd a jelszavadat + Csatlakozni fogsz a csoport összes tagjához. + Lehetséges, hogy a kiszolgáló címében szereplő tanúsítvány-ujjlenyomat helytelen + Az adatok védelme érdekében kapcsold be a SimpleX Lock funkciót. +\nA funkció engedélyezése előtt a rendszer felszólít téged a hitelesítés befejezésére. + A videó akkor érkezik, amikor az ismerősöd elérhető, kérlek várj vagy nézd meg később! + "Kérjük, ellenőrizd hálózati kapcsolatodat a(z) %1$s segítségével, és próbáld meg újra." + A SimpleX Lock-ot a Beállításokon keresztül kapcsolhatod be. + Az alkalmazás összeomlott + Kérjük, ellenőrizd, hogy a megfelelő linket használtad-e, vagy kérd meg az ismerősödet, hogy küldjön egy másikat. + A kép nem dekódolható. Kérjük, próbálj meg egy másik képet, vagy lépj kapcsolatba a fejlesztőkkel. + Érvénytelen fájl elérési útvonalat osztottál meg. Jelentsd a problémát az alkalmazás fejlesztőinek. + Önnek már van egy chat-profilja ugyanazzal a megjelenített névvel. Kérjük, válassz másik nevet. + Csatlakozási kísérlet a kapcsolat üzeneteinek fogadására használt szerverhez ettől az ismerősödtől (hiba: %1$s). + A fájl fogadása leállt. + Kérjük, jegyezd meg vagy tárold biztonságosan - az elveszett jelszót nem lehet visszaállítani! + A videó akkor érkezik meg, amikor az ismerősöd befejezi a feltöltést. + egyszeri linket osztottál meg inkognitóban + Csatlakozol ahhoz a kiszolgálóhoz, amely az adott ismerősödtől érkező üzenetek fogadására szolgál. + Később engedélyezheted a Beállításokban + Csatlakozni fogsz a csoporthoz amikor a csoport tulajdonosának az eszköze online lesz. Kérlek várj vagy nézz vissza később! + eltérő migráció az appban/adatbázisban: %s / %s + Már kapcsolatban vagy vele: %1$s. + Profil felfedése + Ez a link nem érvényes meghívó link! + A felek közötti titkosítás (e2e) ellenőrzéséhez hasonlítsd össze (vagy olvasd be) a kódot az ismerősöddel együtt az eszközeiteken! + A legfrissebb chat adatbázis verziódat kell használd, kizárólag egyetlen eszközön, máskülönben leállhat az üzenetek fogadása néhény ismerőstől! + Ez a beállítás a jelenlegi chat profilodban lévő üzenetekre érvényes + Meg vagy hívva a csoportba. Csatlakozz és lépj kapcsolatba a csoporttagokkal! + Ez a csoport többé nem létezik. + A csatlakozásod már folyamatban van a csoporthoz ezzel a linkkel! + Meg vagy hívva a csoportba + Az ismerősöd a jelenleg megengedett maximálisan méretű (%1$s) fájlnál nagyobbat küldött. + Nem tároljuk egyetlen üzenetedet illetve ismerőseidet (kézbesítás után) a SimpleX szervereken. + Üzenetek formázása a szövegbe szúrt speciális karakterekkel: + Mobil appban megnyitás gombra.]]> + A chat profilod megküldésre kerül +\naz ismerősöd számára + Egy olyan ismerőst próbálsz meghívni, akivel inkognító profilt osztottál meg abban a csoportban, amelyben a saját fő profilodat használod + Már folyamatban van a csatlakozásod a %1$s csoporthoz. + Amikor az app fut + Inkognító profilt használsz ehhez a csoporthoz - a fő profilod megosztásának elkerülése érdekében meghívók küldése tiltott + Kapcsolat izolációs mód + Csatlakoztok amikor a meghívód elfogadásra kerül. Kérlek várj vagy nézz vissza később! + A hangüzenetek tiltottak ebben a csoportban. + App akkumulátor használat / Korlátlan módot az app beállításaiban.]]> + Biztonságos kvantum ellenálló protokoll által. + - hangüzenetek 5 percig. +\n- egyedi eltűnési időhatár +\n- előzmény szerkesztése + Használd a számítógépről gombra a mobil appban és olvasd be a QR-kódot!]]> + %s at %s + Csatlakoztok amikor az ismerősöd eszköze online lesz. Kérlek várj vagy nézz vissza később! + Kéretlen üzenetek elrejtése. + Használd az .onion hostokat \u0020NEM-re ha a SOCKS proxy nem támogatja.]]> + Megoszthatod a SimpleX azonosítódat linkben vagy QR-kódban - bárki kapcsolatfelvételt kezdeményezhet veled. + Később létrehozhatod + A profilod a te eszközödön van tárolva és csak az ismerőseiddel kerül megosztásra! A SimpleX chat szerverek nem láthatják a profilodat! + %s szerepkörét megváltoztattad erre: %s + Elutasítottad a meghívót a csoportba + Az adatvédelem érdekében, a más chat platformokon megszokott felhasználói azonosítók helyett, a SimpleX üzenetsorokhoz rendel azonosítókat, minden egyes ismerősödhöz egy különbözőt. + (megosztás az ismerősöddel) + Csoport meghívót küldtél + Kapcsolat izolációs mód frissítése? + Kapcsolat izolációs mód + Ettől a csoporttól nem fogsz értesítéseket kapni. A chat előzmény megmarad. + A chat adatbázisod nem titkosított - állíts be jelmondatot a megvédéséhez! + Közvetlen internet kapcsolat használata? + Továbbra is kapsz hívásokat és értesítéseket az elnémított profilokból amikor azok aktívak. + A saját fő chat profilod megküldésre kerül a csoporttagok számára. + Később engedélyezheted az app Adatvédelmi és Biztonsági beállításaiban. + A rejtett profilod felfedéséhez gépeld be a jelszót a kereső mezőbe a Chat profiljaid oldalon! + A chat frissítése és megnyitása + Ahhoz, hogy hangüzeneteket küldhess, engedélyezned kell az ismerőseidnek is azok küldését. + fogadodaz üzeneteket, míg az ismerőseid a szervereket amelyeken át üzensz nekik.]]> + Már a %1$s csoportban vagy. + megváltoztattad az azonosítót + Az ismerőseid engedélyezhetik a teljes üzenet törlést. + Be kell írd a jelmondatodat a SimpleX app minden indulásakor - nem az eszközön lesz tárolva. + Ahhoz, hogy engedélyezd a mobil app csatlakozását a számítógépedhez, nyisd meg ezt a portot a tűzfaladon, ha az engedélyezve van + A te profilod, ismerőseid és az elküldött üzeneteid a te eszközödön vannak tárolva (a SimpleX chat szerverekről kézbesítés illetve a TTL időkorlát után törlődnek). + App akkumulátor használat / Korlátlan módot az app beállításaiban.]]> + Ez a karakterlánc nem meghívó link! + Új chat kezdése + Csatlakozásod már folyamatban van ezzel az Egyszer használatos link-el! + Nem fogod leveszíteni az ismerőseidet ha később törlöd a SimpleX azonosítódat. + A beállítások frissítése a szerverekhez újra kapcsolódással jár. + kapcsolatba akar lépni veled! + a saját szerepkörödet megváltoztattad erre: %s + A chat szolgáltatást elindíthatod a beállítások / adatbázis pontban vagy az app újraindításával. + Ellenőrizd a kódot a mobilon! + Csatlakoztál ehhez a csoporthoz. Kapcsolódás a meghívó csoporttaghoz. + a SimpleX Chat fejlesztőivel és kérdezhetsz bármit és értesülhetsz az újdonságokról.]]> + Opcionális üdvözlő szöveggel. + Ismeretlen adatbázis hiba: %s + Elrejtheted vagy némíthatod egy felhasználó profilját - tartsd lenyomva a menühöz! + Inkognító mód csatlakozáskor + TOR .onion host beállítások frissítése? + Megoszthatsz egy linket vagy QR-kódot - így bárki csatlakozhat a csoporthoz. Csoporttagokat nem fogsz elveszíteni, az esetben sem ha kásőbb törlöd a csoportot. + Csatlakoztál ehhez a csoporthoz + Ez a linked a %1$s csoporthoz! + A hangüzenetek tiltottak ebben a chatben. + Te tartod kézben chatedet! + Kód ellenőrzése a számítógépen + Az időzóna, kép/hang fájlok megvédése érdekében használd az UTC-t! + A meghívó elküldésre kerül a csoporttag számára. + Amikor megosztasz egy inkognító profilt valakivel, az a profil lesz használva a csoporthoz is amibe meghív. + Már küldtél meghívót ezen az azonosítón keresztül! + Megoszthatod ezt a SimpleX azonosítót az ismerőseiddel, hogy kapcsolatba léphessenek %s-el . + Amikor meghívót kapsz emberektől, elfogadhatod vagy elutasíthatod azokat! + Állítsd be az új tagoknak megjelenő üzenetet! + Köszönet a felhasználóknak - hozzájárulás a Weblaten! + A kézbesítési jelentés küldése minden kapcsolat számára engedélyezve lesz. + Protokoll időkorlát KB-onként + Az adatbázis jelmondatának megváltoztatására tett kísérlet nem fejeződött be. + Ez a művelet nem vonható vissza - a kiválasztottnál korábban küldött és fogadott üzenetek törlésre kerülnek. Ez több percet is igénybe vehet. + A profilod csak az ismerőseid számára kerül megosztásra. + Néhány szerver megbukott a teszten: + Érintsd meg a csatlakozáshoz + Ez a művelet nem vonható vissza - az összes fogadott és küldött fájl a médiatartalommal együtt törlésre kerülnek. Az alacsony felbontású képek viszont megmaradnak. + A kézbesítési jelentés engedélyezve van %d ismerősnél + Küldés ezen keresztül: + Köszönet a felhasználóknak - hozzájárulás a Weblaten! + A kézbesítési jelentés küldése az összes látható csevegőprofilban lévő összes ismerős számára engedélyezve lesz. + Bluetooth támogatás és egyéb fejlesztések. + Ez a funkció még nem támogatott. Próbáld meg a következő kiadásban. + A bejegyzés frissítve: %s + Tagok meghívásának kihagyása + Ezek felülbírálhatóak az ismerős- és csoportbeállításokban. + Az ismerős, akivel megosztottad ezt a linket, NEM fog tudni csatlakozni! + A véletlenszerű jelmondat egyszerű szövegként van tárolva a beállításokban. +\nKésőbb megváltoztathatod. + Érintsd meg az inkognitóban való csatlakozáshoz + Jelmondat beállítása az exportáláshoz + A kézbesítési jelentés le van tiltva %d csoportnál + Néhány nem végzetes hiba történt az importálás során - további részletekért lásd a Chat-konzolt. + Köszönet a felhasználóknak - hozzájárulás a Weblaten! + A relé szerver csak végszükség esetén használatos. Egy másik fél megfigyelheti az IP-címedet. + Állítsa be a rendszerhitelesítés helyett. + A fogadó cím egy másik szerverre változik. A címváltoztatás a feladó online állapotba kerülése után fejeződik be. + A csevegés leállítása a csevegőadatbázis exportálásához, importálásához vagy törléséhez. A csevegés leállítása alatt nem tudsz üzeneteket fogadni és küldeni. + Jelmondat mentése a Kulcstárban + Köszönet a felhasználóknak - hozzájárulás a Weblaten! + Jelmondat mentése a beállításokban + Ennek a csoportnak több mint %1$d tagja van, a kézbesítési jelentések nem kerülnek elküldésre. + A második jelölés, amit kihagytunk! ✅ + A relés szerver védi az IP-címedet, de megfigyelheti a hívás időtartamát. + További információ a GitHub tárolónkban. + Az utolsó üzenet tervezetének megőrzése a mellékletekkel együtt. + A mentett WebRTC ICE-kiszolgálók eltávolításra kerülnek. + A kézbesítési jelentés engedélyezve van %d csoportnál + A szerepkör \"%s\"-re fog változni. A csoportban mindenki értesítést kap. + Profil és szerverkapcsolatok + A Te adatvédelmedet és biztonságodat védő üzenetküldő és alkalmazásplatform. + Érintsd meg a profil aktiválásához. + A kézbesítési jelentés le van tiltva %d ismerősnél + Legalább egy felhasználói profilnak kell lennie. + Munkamenet kód + Köszönet a felhasználóknak - hozzájárulás a Weblaten! + Kis csoportok (max. 20 tag) + Az általad elfogadott kapcsolat törlésre kerül! + Élő üzenet küldése - a címzett(ek) számára frissül, miközben írod az üzenetet. + A KÉZBESÍTÉSI JELENTÉSEKET A KÖVETKEZŐ CÍMRE KELL KÜLDENI + A következő üzenet azonosítója hibás (kisebb vagy egyenlő az előzővel). +\nEz valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. + Az eszköz neve megosztásra kerül a csatlakoztatott mobil klienssel. + A címzettek a beírás közben látják a frissítéseket. + Kérjük, hogy a jelmondatot biztonságosan tárold, ha elveszíted, NEM tudod megváltoztatni. + A jelmondat a beállításokban egyszerű szövegként tárolódik, miután megváltoztattad vagy újraindítottad az alkalmazást. + Jelenlenlegi profilod új ismerőseinek kiszolgálói + Fogadás a + Kérjük, hogy a jelmondatot biztonságosan tárold, ha elveszíted, NEM fogsz tudni hozzáférni a chathez. + A szerepkör \"%s\"-re fog változni. A tag új meghívót kap. + Legalább egy látható felhasználói profilnak kell lennie. + profilkép helyőrző + A titkosítás működik, és új titkosítási egyezményre nincs szükség. Ez kapcsolati hibákat eredményezhet! + Ez a művelet nem vonható vissza - profilod, ismerőseid, üzeneteid és fájljaid visszafordíthatatlanul törlésre kerülnek. + A bejegyzés frissítve + Felhasználói útmutatóban olvasható.]]> + A jelmondat a beállításokban egyszerű szövegként van tárolva. + Konzol megjelenítése új ablakban + Az előző üzenet hash-e más. + Ezek a beállítások a jelenlegi profilodra vonatkoznak + Kérjük, várj, amíg a fájl betöltődik az összekapcsolt mobilról. + GitHub tárolónkban.]]> + hiba a tartalom megjelenítése közben + hiba az üzenet megjelenítésekor \ No newline at end of file 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 79c1897153..f01568c03b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -41,7 +41,7 @@ incognito via link indirizzo del contatto via link una tantum incognito via link una tantum - Indirizzo del contatto SimpleX + Indirizzo di contatto SimpleX Invito SimpleX una tantum Link gruppo SimpleX Link completo @@ -508,7 +508,7 @@ Android Keystore verrà usato per memorizzare in modo sicuro la password dopo il riavvio dell\'app o la modifica della password; consentirà di ricevere le notifiche. Nota bene: NON potrai recuperare o cambiare la password se la perdi.]]> Cambiare password del database\? - Conferma password nuova… + Conferma nuova password… Password attuale… Database crittografato! La password di crittografia del database verrà aggiornata. @@ -623,7 +623,7 @@ Salva I server WebRTC ICE salvati verranno rimossi. Condividi link - Stella su GitHub + Dai una stella su GitHub Aggiornare l\'impostazione degli host .onion\? Usare una connessione internet diretta\? Usa gli host .onion @@ -775,7 +775,7 @@ Tocca per entrare Toccare per entrare in incognito Questo gruppo non esiste più. - profilo del gruppo aggiornato + ha aggiornato il profilo del gruppo Sei stato/a invitato/a al gruppo hai cambiato indirizzo hai cambiato l\'indirizzo per %s @@ -1368,9 +1368,9 @@ SimpleX non può funzionare in secondo piano. Riceverai le notifiche solo quando l\'app è aperta. Utilizzo batteria dell\'app / Senza restrizioni nelle impostazioni.]]> Utilizzo batteria dell\'app / Senza restrizioni nelle impostazioni.]]> - %s e %s sono connessi/e - %s, %s e altri %d membri sono connessi - %s, %s e %s sono connessi/e + %s e %s si sono connessi/e + %s, %s e altri %d membri si sono connessi + %s, %s e %s si sono connessi/e Bozza Mostra gli ultimi messaggi Il database verrà crittografato e la password conservata nelle impostazioni. @@ -1380,7 +1380,7 @@ Rimuovere la password dalle impostazioni\? Usa password casuale Salva password nelle impostazioni - Configura password del database + Configura la password del database Imposta password del database Apri cartella del database La password verrà conservata nelle impostazioni come testo normale dopo averla cambiata o il riavvio dell\'app. @@ -1469,7 +1469,7 @@ Scollegare il desktop? Opzioni del desktop collegato Desktop collegati - Trova nella rete + Individua via rete locale Questo dispositivo Cellulari collegati Desktop @@ -1517,4 +1517,19 @@ \n- e molto altro! In attesa che il cellulare si connette: autore + Connetti automaticamente + In attesa del desktop… + Desktop trovato + Non compatibile! + individuabile via rete locale + Ricarica + Crea profilo di chat + Nessun cellulare connesso + Disconnetti cellulari + Casuale + Per consentire a un\'app mobile di connettersi al desktop, apri questa porta nel tuo firewall, se è attivo + Vedi crash + Apri porta nel firewall + errore di visualizzazione del contenuto + errore di visualizzazione del messaggio \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index 5f45304aac..bb5226e3d4 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -209,7 +209,7 @@ Maak Maak een profiel aan Adres verwijderen - Verbinden via relais + Altijd relay gebruiken contact heeft e2e-codering contact heeft geen e2e versleuteling De database is versleuteld met een willekeurige wachtwoord. Wijzig dit voordat u exporteert. @@ -741,7 +741,7 @@ Relay server wordt alleen gebruikt als dat nodig is. Een andere partij kan uw IP-adres zien. Uw ICE servers Overgeslagen berichten - via relais + via relay Video uit App scherm verbergen Uw privacy @@ -1383,7 +1383,7 @@ Database map openen Het wachtwoord wordt als platte tekst in de instellingen opgeslagen nadat u deze hebt gewijzigd of de app opnieuw hebt opgestart. Het wachtwoord wordt als leesbare tekst in de instellingen opgeslagen. - Let op: bericht en bestands relais zijn verbonden via SOCKS-proxy. Voor oproepen en het verzenden van link voorbeelden wordt gebruik gemaakt van een directe verbinding.]]> + Let op: bericht en bestands relays zijn verbonden via SOCKS-proxy. Voor oproepen en het verzenden van link voorbeelden wordt gebruik gemaakt van een directe verbinding.]]> Versleutel lokale bestanden Versleutel opgeslagen bestanden en media Nieuwe desktop app! @@ -1402,7 +1402,7 @@ Stuur een direct bericht om verbinding te maken stuur een direct bericht direct verbonden - Uitbreiden + Uitklappen Verbindingsverzoek herhalen? verwijderd contact Fout @@ -1431,7 +1431,7 @@ Lid blokkeren Deelnameverzoek herhalen? Lid verwijderen? - Contact verwijderen en op de hoogte stellen + Verwijderen en contact op de hoogte stellen Open groep Berichten van %s worden getoond! Fout bij verzenden van uitnodiging @@ -1520,4 +1520,14 @@ Desktop gevonden Niet compatibel! Vindbaar via lokaal netwerk + Vernieuwen + Chatprofiel aanmaken + Mobiele telefoons loskoppelen + Geen verbonden mobiel + willekeurig + Om een mobiele app verbinding te laten maken met de desktop, opent u deze poort in uw firewall, als u deze hebt ingeschakeld + Weergave gecrasht + Open poort in firewall + Fout bij het tonen van inhoud + fout bij weergeven bericht \ No newline at end of file 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 9e8bd0af8c..76a49f678d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml @@ -1605,4 +1605,11 @@ Несовместимая версия! автор Найти через локальную сеть + Обновить + Случайный + Чтобы разрешить мобильному приложению подключаться к компьютеру, откройте этот порт в брандмауэре, если он включен + Создать профиль чата + Открыть порт в брандмауэре + Отключить мобильные + Нет подключённых мобильных \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml index ff9febd088..8a3eeef61d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml @@ -48,7 +48,7 @@ Uygulama sürümü: v%s sesli arama sesli arama (uçtan uca şifreli değil) - Aramaya cevap ver + Aramayı cevapla Uygulama veri yedekleme Tüm uygulama verileri silinir. UYGULAMA @@ -242,7 +242,7 @@ Geri çevrilmiş çağrı Görüşme bitti. %1$d mesaj atlanıldı. - Konuşma veri tabanı silindi + Sohbet veri tabanı silindi Hata: %s Bilinmeyen hata bağlanıldı @@ -257,10 +257,10 @@ evet Onayla Konuştuğun kişiler, kendiliğinden yok olan mesajlara izin veriyorsa sen de ver. - Konuştuğun kişilerin gönderilen mesajları kalıcı olarak silmesine izin ver. + Kişilerinin gönderilen mesajları kalıcı olarak silmesine izin ver. Kendiliğinden yok olan measj gönderimini engelle. Konuştuğun kişi, kalıcı olarak silinebilen mesajlara izin veriyorsa sen de ver. - Konuştuğun kişi, mesaj tepkilerine izin veriyorsa sen de ver. + Yalnızca kişin mesaj tepkilerine izin veriyorsa sen de ver. Sadece sen kendiliğinden yok olan mesaj gönderebilirsin. Sadece konuştuğun kişi kendiliğinden yok olan mesaj gönderebilir. Konuştuğun kişi de sen de kendiliğinden yok olan mesaj gönderebilirsiniz. @@ -269,17 +269,17 @@ Gönderilen mesajlar, önceden belirlenmiş bir süre sonra silecektir. Kullanıldığında bütün veriler silinir. Kendiliğinden yok olan mesajlar - Konuştuğun kişilerin sana, kendiğinden yok olan mesajlar göndermesine izin ver. + Kişilerinin sana, kendiğinden yok olan mesajlar göndermesine izin ver. Bu grupta kendiliğinden yok olan mesajlara izin verilmiyor. %1$d mesaj deşifrelenemedi. %1$s ÜYE %1$d atlanılmış mesaj - Konuştuğun kişi, aramalara izin veriyorsa sen de ver. + Yalnızca irtibat kişiniz izin veriyorsa aramalara izin verin. Konuştuğun kişilerin mesajlarına tepki eklemesine izin ver. - Konuştuğun kişilerin seni aramasına izin ver. - Konuştuğun kişilerin tümü bağlı kalacaktır. + Kişilerinin seni aramasına izin ver. + Kişilerin tümü bağlı kalacaktır. Dosya ön izlemesini iptal et - Konuşma veri tabanı içe aktarıldı + Sohbet veri tabanı içe aktarıldı Görsel ön izlemesini iptal et Konuşmayı temizle\? Çince ve İspanyolca arayüz @@ -296,7 +296,7 @@ ARAMALAR KONUŞMALAR SEN - KONUŞMA VERİ TABANI + SOHBET VERİ TABANI Kaldır Yanlış parola! Veri tabanının yapısal ilerlemelerini onayla @@ -309,8 +309,8 @@ ÜYE Grup üyeleri kendiliğinden yok olan mesajlar gönderebilir. Kendiliğinden yok olan mesaj gönderimini engelle. - Konuştuğun kişi, sesli mesajlara izin veriyorsa sen de ver. - Konuştuğun kişilerin sesli mesaj göndermesine izin ver. + Yalnızca kişiniz sesli mesaj göndermeye izin veriyorsa sen de ver. + Kişilerinin sesli mesaj göndermesine izin ver. Konuştuğun kişi de sen de mesajlara tepki ekleyebilirsinsiz. Konuştuğun kişi de sen de sesli mesaj gönderebilirsiniz. Kendiliğinden yok olan mesajlar @@ -410,7 +410,7 @@ Özeksiz Erişim kodunu onayla Yanlış erişim kodu - Yeni Şifre + Yeni erişim kodu Gönder Erişim kodu Erişim kodu değişti! @@ -877,9 +877,9 @@ Kişileriniz bağlı kalacaktır. Daha sonra oluşturabilirsiniz Mesajları biçimlendirmek için markdown kullanabilirsiniz: - Mesaj veri tabanınız + Sohbet veri tabanınız Mevcut sohbet veritabanınız SİLİNECEK ve içe aktarılan veritabanıyla DEĞİŞTİRİLECEKTİR. -\nBu işlem geri alınamaz - profiliniz, kişileriniz, mesajlarınız ve dosyalarınız geri alınamaz şekilde kaybolacaktır. +\nBu eylem geri alınamaz - profiliniz, kişileriniz, mesajlarınız ve dosyalarınız geri alınamaz şekilde kaybolacaktır. Bu gruba katıldınız. Davet eden grup üyesine bağlanılıyor. Gruba davetlisiniz. Grup üyeleriyle bağlantı kurmak için katılın. Bu grup için gizli bir profil kullanıyorsunuz - ana profilinizi paylaşmayı önlemek için kişileri davet etmeye izin verilmiyor @@ -1004,7 +1004,7 @@ Alınan linki yapıştır Bilinmeyen veri tabanı hatası: %s Sohbeti aç - Veritabanı yedeğini geri yükledikten sonra önceki şifreyi girin. Bu işlem geri alınamaz. + Veritabanı yedeğini geri yükledikten sonra önceki şifreyi girin. Bu eylem geri alınamaz. PING aralığı KB başına protokol zaman aşımı Sohbet profilini gizlemeyi kaldır @@ -1069,4 +1069,212 @@ Paylaşmayı durdur Dosya almayı durdur? Sohbeti durdur? + Genişlet + Bağlantı isteğini tekrarla? + detay yok + Veri tabanı düzgün çalışmıyor. Daha fazla bilgi için dokunun + Açık kaynaklı protokol ve kod - sunucuları herkes çalıştırabilir. + + Önizlemeyi göster + Depolanan dosyaları ve medyayı şifrele + Sadece sen sesli mesaj gönderebilirsin. + Güncelle + Veritabanı parolasını değiştirme girişimi tamamlanmadı. + Veri tabanı parolasını güncelle + Uygulama ayarlarını aç + Bu eylem geri alınamaz - seçilenden daha önce gönderilen ve alınan mesajlar silinecektir. Birkaç dakika sürebilir. + Grup oluştur + Seç + Sadece sen arama yapabilirsin. + Yeni mobil cihaz + Sohbeti kullan + Otomatik olarak bağlan + Paylaş + %s, %s ve %d diğer üye bağlandı + Sadece senin kişin arama yapabilir. + Masaüstü adresi + Bazı sunucular testi geçemedi: + Sorularınızı ve fikirlerinizi gönderin + Bu eylem geri alınamaz - tüm alınan ve gönderilen dosyalar ve medyalar silinecek. Düşük çözünürlükteki resimlar kalacaktır. + Yerel dosyaları şifrele + SimpleX Chat aramaları + Yeni masaüstü uygulaması! + 6 yeni arayüz dili + Sadece kişiyi göster + Grup zaten mevcut! + Hoparlör açık + Sadece senin kişin sesli mesaj gönderebilir. + Bu özellik henüz desteklenmiyor. Bir sonraki sürümü deneyin. + Masaüstünden kullan seçeneğini aç ve karekodu okut.]]> + SimpleX Chat\'i yüklediğiniz için teşekkürler! + Üyeleri davet etmeyi atla + Zaten bağlanılıyor! + Mesaj Gönder + Bu bağlantıyı paylaştığınız kişi bağlanamayacak! + Uyumsuz sürüm + (yeni)]]> + Kişiyi ve mesajı göster + Daha iyi gruplar + Videonun kodu çözülemiyor. Lütfen farklı bir video deneyin veya geliştiricilerle iletişime geçin. + İçe aktarma sırasında bir takım hatlar oluştu - daha fazla detay için sohbet konsoluna bakabilirsiniz. + Geliştirici seçeneklerini göster + %s bağlandı + Onaylarsanız, mesajlaşma sunucuları IP adresinizi ve sağlayıcınızı - hangi sunuculara bağlandığınızı - görebilecektir. + Kişilerle paylaş + %s saniye (sn) + %s: %s + SimpleX arka planda çalışamaz. Bildirimleri sadece uygulama çalışırken alırsınız. + Bağlantı ile bağlan? + Veri tabanı şifreleme parolası güncellenecek ve ayarlarda depolanacaktır. + Zaten gruba bağlanılıyor! + %s, %s ve %d üye + Bu cihaz + Adresi kişilerle paylaş? + Uygulama arka planda 1 dakika kaldıktan sonra kapatılabilir. + Kişi ismini ayarla… + Bize e-posta gönder + SimpleX Chat\'in güvenliği Trail of Bits tarafından denetlenmiştir. + SimpleX Chat hizmeti + %s onaylı + Göserilecek parola + Tek bir sohbet profilinde, aralarında herhangi bir veri paylaşımı olmadan birden fazla anonim bağlantı kurmaya izin verir. + Kişileri seç + Kullanıcılara teşekkürler - Weblate aracılığıyla katkıda bulunun! + Basitleştirilmiş gizli mod + Ağ ayarlarını güncelle? + Kapat? + Bir kişiyi davet ettin + %s onaylı değil + Bu cihazın ismi + Dosya yükle + Gerekli + SimpleX Chat mesajlar + Bağlantı paylaş + SimpleX Ekibi + %s, %s ve %s bağlandı + Geri al + SOCKS PROXY + Masaüstür cihazlar + SMP sunuclar + Uyumlu değil! + Bağlantı güvenliğini onayla + %1$s ile bağlan? + Üyeyi çıkar + Veri tabanı dosyasını aç + Masaüstünden kullan + port %d + Adres paylaş + Bağlantı talebini tekrarla? + Üyeyi çıkar? + Güvenlik kodunu onayla + Göster: + Küçük gruplar (maks 20) + Arapça, Bulgarca, Fince, İbranice, Tayca ve Ukraynaca - kullanıcılar ve Weblate sayesinde. + Port + Kabul ettiğiniz bağlantı iptal edilecek! + Grubu aç + Son mesajları göster + bu cihaz s%s]]> + simplexmq: v%s (%2s) + Güncelle + Cihaz ismi bağlı olduğu mobil istemci ile paylaşılacak. + Masaüstü uygulamasında yeni bir profil oluştur. 💻 + Kişi ismini ayarla + Kodu mobilde onayla + Medya paylaş… + Parola, siz onu değiştirdikten veya uygulamayı yeniden başlattıktan sonra ayarlarda düz metin olarak depolanacak. + Kişinizle bağlantı kurmak için aldığınız bağlantıyı yapıştırın… + Kapat + gönderildi + Canlı mesaj gönder + Bu cihazın adını girin… + Mevcut sohbet profilinizin yeni bağlantıları için sunucular + QR kodu göster + SOCKS proxy ayarları + %d grup etkinlikleri + Geçersiz isim! + SimpleX Kilit modu + Dosya paylaş… + Uygulama yeni mesajları periyodik olarak alır - günde pilin yüzde birkaçını kullanır. Uygulama anlık bildirimleri kullanmaz - cihazınızdan gelen veriler sunuculara gönderilmez. + Herhangi bir kullanıcı tanımlayıcısı olmayan ilk platform - tasarım gereği gizli. + Hoparlör kapalı + Şifreleme çalışıyor ve yeni bir şifreleme anlaşması gerekli değil. Yoksa bağlantı hataları ortaya çıkabilir! + Göster + Gönder + Masaüstü adresini yapıştır + Kodu masaüstü ile onayla + Masaüstünden karekodu tara + SimpleX logo + Cihazlar + Bu eylem geri alınamaz - profiliniz, kişileriniz, mesajlarınız ve dosyalarınız geri döndürülemez şekilde kaybolacaktır. + - isteğe bağlı olarak silinen kişileri bildirme. +\n- boşluklu profil adları. +\n- ve daha fazlası! + SimpleX Kilit + Bu grup üyesine bağlantı isteği gönderilecek. + Sadece senin kişin mesaj tepkileri ekleyebilir. + Parola, ayarlarda düz metin olarak saklanır. + SimpleX Kilit aktif + Yeni pencerede konsolu göster + Mobilden tara + Bağlantıları onayla + Mesaj paylaş… + Önceki mesajın hash\'i farklı. + SimpleX Kilit aktif değil! + SimpleX Kilit + Direkt bağlan? + Bu ayarlar mevcut profiliniz içindir + Sunucu testi başarısız! + Bağlantıyı onayla + Yeni sohbet başlat + Profiliniz %1$s paylaşılacaktır. + Rastgele bir profil kullanarak grup oluştur. + Gruba zaten bu bağlantı üzerinden katılıyorsunuz. + Sohbet profiliniz kişinize +\ngönderilecek + Gizli bir profil paylaştığınız kişiyi ana profilinizi kullandığınız gruba davet etmeye çalışıyorsunuz + güvenlik kodu değiştirildi + Bluetooth desteği ve diğer iyileştirmeler. + Ayarlar + AYARLAR + Bağlanmak için direkt mesaj gönderin + Güvenlik kodu + Daha hızlı gruplara katılma ve daha güvenilir mesajlar. + Sohbet profiliniz grup üyelerine gönderilecek + Kendine bağlan? + Periyodik olarak başlar + Bu sizin kendi SimpleX adresiniz! + Kişileriniz tam mesaj silme işlemine izin verebilir. + gönderme başarısız + Gönderen kişi bağlantı isteğini silmiş olabilir. + Sohbet profili oluştur + Davetiye gönderirken hata + Sohbeti uygulama Ayarları/Veritabanı üzerinden veya uygulamayı yeniden başlatarak başlatabilirsiniz. + Masaüstüne bağlan + Direkt mesaj gönder + Bu adres üzerinden zaten bağlantı talebinde bulundunuz! + direkt mesaj gönder + Gönderen kişi dosya aktarımını iptal etti. + Mesajları yalnızca siz geri döndürülemez şekilde silebilirsiniz (kişiniz bunları silinmek üzere işaretleyebilir). + Zaten %1$s\'ye bağlanıyorsunuz. + Kullanıcılara teşekkürler - Weblate aracılığıyla katkıda bulunun! + Kilit modu + Aynı anda yalnızca bir cihaz çalışabilir + Zaten %1$s grubuna katılıyorsunuz. + Katılmak için dokun + Kullanıcılara teşekkürler - Weblate aracılığıyla katkıda bulunun! + Güvenli kuantum dirençli protokol ile. + Bize Github\'da yıldız verin + Arka planda 30 saniye kaldıktan sonra uygulamayı başlattığınızda veya devam ettirdiğinizde kimlik doğrulaması yapmanız gerekecektir. + Kullanıcılara teşekkürler - Weblate aracılığıyla katkıda bulunun! + Masaüstü bekleniyor… + Sohbet veri tabanınız şifreli değildir - korumak için parola ayarlayın. + Aktif olduklarında sessize alınmış profillerden arama ve bildirim almaya devam edersiniz. + Zaten %1$s grubundasınız. + Kullanıcılara teşekkürler - Weblate aracılığıyla katkıda bulunun! + Bu tek seferlik bağlantı üzerinden zaten bağlanıyorsunuz! + Geçersiz bir dosya yolu paylaştınız. Sorunu uygulama geliştiricilerine bildirin. + Bildirimleri devre dışı bırak + Geçersiz dosya yolu + Yalnızca kişiniz mesajları geri alınamaz şekilde silebilir (silinmeleri için işaretleyebilirsiniz). \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml index d9d49aef7d..faf2895827 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml @@ -2,8 +2,8 @@ 1 хвилина Про SimpleX - Прийняти запит на підключення\? - вище, тоді: + Прийняти запит на з\'єднання? + вище, а потім: Прийняти Прийняти Акцент @@ -14,564 +14,564 @@ Прийняти 5 хвилин 1 місяць - Про адресу SimpleX + Про SimpleX-адресу 1-разове посилання Про SimpleX Chat - Додайте сервери, відсканувавши QR-коди. - Всі чати та повідомлення будуть видалені - це неможливо скасувати! - Дозволяйте дзвінки, тільки якщо ваш контакт дозволяє їх. - Дозволяйте безповоротне видалення повідомлень, тільки якщо контакт дозволяє вам це зробити. + Додавайте сервери, скануючи QR-коди. + Всі чати і повідомлення будуть видалені - цю дію неможливо скасувати! + Дозволяйте дзвінки тільки в разі дозволу вашого контакту. + Дозволяйте невідворотне видалення повідомлень тільки в разі дозволу вашого контакту. Дозволити голосові повідомлення\? - Код доступу до програми замінено на пароль самознищення. - Додавання попередньо встановлених серверів - Резервне копіювання даних програми - Додати до іншого пристрою - Android Keystore використовується для безпечного зберігання парольної фрази - це дозволяє сервісу сповіщень працювати. - Адміни можуть створювати посилання для приєднання до груп. - Збірка програми: %s - Дозволяйте голосові повідомлення, тільки якщо ваш контакт дозволяє їх. - Дозволити відправляти зникаючі повідомлення. - прийнято виклик - Завжди використовуйте реле + Пароль застосунку замінено паролем самознищення. + Додати попередньо встановлені сервери + Резервне копіювання даних застосунку + Додати на інший пристрій + Сховище ключів Android використовується для безпечного збереження ключової фрази - це дозволяє службі сповіщень працювати. + Адміністратори можуть створювати посилання для приєднання до групи. + Збірка додатку: %s + Дозволяйте голосові повідомлення тільки в разі дозволу вашого контакту. + Дозволяйте надсилати повідомлення, які зникають. + прийнятий виклик + Завжди використовувати реле ДОДАТОК - Дозволяє надсилати прямі повідомлення користувачам. - Дозволяє безповоротно видаляти надіслані повідомлення. - Дозволити надсилати голосові повідомлення. - Дозволити реакцію на повідомлення. - Всі дані стираються при введенні. - Пароль додатку - ЗНАЧОК ДОДАТКУ - Дозволяйте зникати повідомленням, тільки якщо ваш контакт дозволяє це робити. + Дозволяйте надсилати прямі повідомлення учасникам. + Дозволяйте невідворотне видалення відправлених повідомлень. + Дозволяйте надсилати голосові повідомлення. + Дозволити реакції на повідомлення. + Вся інформація стирається при його введенні. + Пароль для додатка + ІКОНКА ДОДАТКУ + Дозволяйте повідомлення, які зникають, тільки якщо ваш контакт дозволяє їх. Дозвольте вашим контактам додавати реакції на повідомлення. - Дозволяйте реакції на повідомлення, тільки якщо ваш контакт дозволяє їх. - Створюється порожній профіль чату з вказаним ім\'ям, і додаток відкривається у звичайному режимі. + Дозволяйте реакції на повідомлення тільки в разі дозволу вашого контакту. + Створений порожній профіль чату з наданим ім\'ям, і застосунок відкривається, як завжди. Додатковий акцент - Дозвольте вашим контактам безповоротно видаляти надіслані повідомлення. - Дозвольте + Дозвольте вашим контактам невідворотно видаляти відправлені повідомлення. + Дозволити Розширені налаштування мережі - Доступ до серверів через SOCKS проксі на порту %d\? Перед увімкненням цієї опції проксі має бути запущено. - Всі ваші контакти залишаться на зв\'язку. - Всі дані програми видаляються. - Android Keystore буде використовуватися для безпечного зберігання пароля після перезапуску програми або зміни пароля - це дозволить отримувати сповіщення. - Дозвольте своїм контактам надсилати голосові повідомлення. + Отримувати доступ до серверів через SOCKS-проксі на порті %d? Проксі має бути запущено до активації цієї опції. + Всі ваші контакти залишаться підключеними. + Всі дані застосунку буде видалено. + Після перезапуску додатка або зміни ключової фрази буде використано сховище ключів Android для безпечного збереження ключової фрази - це дозволить отримувати сповіщення. + Дозвольте вашим контактам надсилати голосові повідомлення. Прийняти інкогніто Додати сервер… - адмін - Додати вітальне повідомлення - Всі учасники групи залишаться на зв\'язку. - Дозвольте своїм контактам надсилати зникаючі повідомлення. - Всі повідомлення будуть видалені - це неможливо скасувати! Повідомлення будуть видалені ТІЛЬКИ для вас. - Версія програми - Додайте адресу до свого профілю, щоб ваші контакти могли поділитися нею з іншими людьми. Повідомлення про оновлення профілю буде надіслано вашим контактам. - Всі ваші контакти залишаться на зв\'язку. Повідомлення про оновлення профілю буде надіслано вашим контактам. - Відповісти на дзвінок + адміністратор + Додати привітання + Всі члени групи залишаться підключеними. + Дозвольте вашим контактам надсилати повідомлення, які зникають. + Всі повідомлення будуть видалені - цю дію неможливо скасувати! Повідомлення будуть видалені ЛИШЕ для вас. + Версія додатку + Додайте адресу до свого профілю, щоб ваші контакти могли поділитися нею з іншими людьми. Оновлення профілю буде відправлено вашим контактам. + Всі ваші контакти залишаться підключеними. Оновлення профілю буде відправлено вашим контактам. + Відповісти на виклик Адреса Додати профіль - Додатковий вторинний - Завжди увімкнено - Додаток може отримувати сповіщення лише під час роботи, жодні фонові служби не запускаються + Додатковий другорядний + Завжди включено + Додаток може отримувати сповіщення тільки тоді, коли він працює, фоновий сервіс не буде запущено завжди - Дозвольте вашим контактам телефонувати вам. - Для кожного профілю чату, який ви маєте в додатку, буде використовуватися окреме TCP-з\'єднання (і SOCKS-обліковий запис).]]> - Зовнішній вигляд - Версія програми: v%s - Для кожного контакту і члена групи буде використовуватися окреме TCP-з\'єднання (і SOCKS-обліковий запис). -\nЗверніть увагу: якщо у вас багато з\'єднань, споживання заряду акумулятора і трафіку може бути значно вищим, а деякі з\'єднання можуть обірватися. - Активна оптимізація заряду батареї, яка вимикає фоновий сервіс і періодичні запити на нові повідомлення. Ви можете знову увімкнути їх через налаштування. + Дозвольте вашим контактам дзвонити вам. + для кожного профілю чату, який у вас є в додатку.]]> + Вигляд + Версія додатку: v%s + Окреме TCP-підключення (і обліковий запис SOCKS) буде використовуватися для кожного контакту та учасника групи. +\nЗверніть увагу: якщо у вас багато підключень, споживання заряду батареї та трафіку може значно збільшитися, і деякі підключення можуть бути невдалими. + Активована оптимізація батареї, вимикається фоновий сервіс і періодичні запити нових повідомлень. Ви можете знову увімкнути їх у налаштуваннях. Назад жирний - Корисно для батареї. Фонова служба перевіряє повідомлення кожні 10 хвилин. Ви можете пропустити дзвінки або термінові повідомлення.]]> - Аудіо та відео виклики - Звук вимкнено - Автентифікація недоступна - Попросили отримати відео - Автоматичне прийняття запитів на контакт + Добре для акумулятора. Сервіс фонового запуску перевіряє повідомлення кожні 10 хвилин. Ви можете пропустити виклики чи важливі повідомлення.]]> + Аудіо та відеовиклики + Аудіо вимкнено + Аутентифікація недоступна + Запит на отримання відео + Автоприйняття запитів на контакт аудіовиклик (не зашифрований e2e) - І ви, і ваш контакт можете безповоротно видалити надіслані повідомлення. + Як ви, так і ваш контакт можете невідворотно видаляти відправлені повідомлення. Поганий хеш повідомлення Неправильний ідентифікатор повідомлення - Автоматичне прийняття зображень + Автоматично приймати зображення Кращі повідомлення - Звук увімкнено - Реакції на повідомлення можете додавати як ви, так і ваш контакт. + Аудіо увімкнено + Як ви, так і ваш контакт можуть додавати реакції на повідомлення. аудіовиклик - Найкраще для батареї. Ви отримуватимете сповіщення лише тоді, коли додаток працює (БЕЗ фонового сервісу).]]> - Аудіо та відеодзвінки - Автентифікація - Не вдалося пройти автентифікацію - Аутентифікацію скасовано - Автоприйняття + Найкраще для акумулятора. Ви отримуєте сповіщення тільки тоді, коли додаток працює (НЕТЛЕ фоновий сервіс).]]> + Аудіо та відеовиклики + Аутентифікація + Помилка аутентифікації + Аутентифікація відмінена + Автоприйом поганий хеш повідомлення - помилковий ідентифікатор повідомлення + поганий ідентифікатор повідомлення Фон - Додайте новий контакт: для створення одноразового QR-коду для вашого контакту.]]> - Його можна вимкнути в налаштуваннях - сповіщення все одно будуть показуватися під час роботи програми.]]> - Фоновий сервіс працює постійно - сповіщення з\'являтимуться, щойно повідомлення стануть доступними. - Попросили отримати зображення + Додайте новий контакт: щоб створити одноразовий QR-код для вашого контакту.]]> + Це можна вимкнути у налаштуваннях – сповіщення все одно будуть відображатися, коли програма працює. + Служба фонового режиму завжди активна – сповіщення відображатимуться, як тільки повідомлення будуть доступні. + Запит на отримання зображення Прикріпити Аудіо/відео дзвінки " -\nДоступно у v5.1" +\nДоступно в версії 5.1" Аудіо/відео дзвінки заборонені. SimpleX - Дзвонити можете як ви, так і ваш контакт. - k - Підключитися через контактну адресу? - Підключитися за одноразовим посиланням? + Як ви, так і ваш контакт можуть здійснювати дзвінки. + тис. + Підключитися через адресу контакту? + Підключитися через одноразове посилання? Приєднатися до групи? - Ваш профіль буде надіслано контакту, від якого ви отримали це посилання. - Ви з\'єднаєтеся з усіма учасниками групи. - Підключіться - підключений + Ваш профіль буде відправлено контакту, з якого ви отримали це посилання. + Ви приєднаєтеся до всіх учасників групи. + Підключити + підключено помилка підключення - Ви підключені до сервера, який використовується для отримання повідомлень від цього контакту. - Спроба з\'єднатися з сервером, який використовується для отримання повідомлень від цього контакту (помилка: %1$s ). + Ви підключені до сервера для отримання повідомлень від цього контакту. + Спроба підключитися до сервера для отримання повідомлень від цього контакту (помилка: %1$s). видалено - Спроба з\'єднатися з сервером, який використовується для отримання повідомлень від цього контакту. - з позначкою видалено - модерується %s + Спроба підключитися до сервера для отримання повідомлень від цього контакту. + відзначено як видалено + модеровано %s надсилання файлів поки що не підтримується - отримання файлів поки що не підтримується + приймання файлів поки що не підтримується ви невідомий формат повідомлення - невірний формат повідомлення - НАЖИВО - модерується - недійсний чат - невірні дані - з\'єднання встановлено - запрошено до підключення + неправильний формат повідомлення + LIVE + модеровано + неправильний чат + неправильні дані + підключення встановлено + запрошення на підключення підключення… ви поділилися одноразовим посиланням - ви поділилися одноразовим посиланням анонімно - через групове посилання - інкогніто через групове посилання - за посиланням на контактну адресу - інкогніто за посиланням на контактну адресу - за одноразовим посиланням - інкогніто за одноразовим посиланням - Контактна адреса SimpleX - Одноразове запрошення SimpleX - Посилання на групу SimpleX + ви поділилися одноразовим посиланням в інкогніто + через посилання групи + інкогніто через посилання групи + через посилання адреси контакту + інкогніто через посилання адреси контакту + через одноразове посилання + інкогніто через одноразове посилання + Адреса контакту у SimpleX + Одноразове запрошення у SimpleX + Посилання на групу у SimpleX через %1$s Посилання SimpleX Опис Повне посилання Через браузер - Відкриття посилання в браузері може знизити конфіденційність і безпеку з\'єднання. Ненадійні посилання SimpleX будуть червоного кольору. + Відкриття посилання в браузері може зменшити конфіденційність та безпеку з\'єднання. Ненадійні посилання SimpleX будуть виділені червоним кольором. Не вдалося завантажити чати - Помилка збереження SMP-серверів - Переконайтеся, що адреси SMP-серверів мають правильний формат, розділені рядками і не дублюються. + Помилка збереження серверів SMP + Переконайтеся, що адреси серверів SMP вірного формату, розділені переносами рядків і не дублюються. Помилка оновлення конфігурації мережі Не вдалося завантажити чат - Будь ласка, оновіть додаток та зв\'яжіться з розробниками. - Помилка при створенні профілю! - Ви вже маєте профіль у чаті з таким самим іменем. Будь ласка, виберіть інше ім\'я. - Помилка перемикання профілю! - Тайм-аут з\'єднання + Будь ласка, оновіть додаток і зверніться до розробників. + Помилка створення профілю! + Ви вже маєте профіль чату з таким самим ім\'ям відображення. Будь ласка, виберіть інше ім\'я. + Помилка переключення профілю! + Тайм-аут підключення Помилка підключення - Будь ласка, перевірте своє мережеве з\'єднання за допомогою %1$s і спробуйте ще раз. + Будь ласка, перевірте ваше мережеве підключення з %1$s та спробуйте ще раз. Контакт вже існує Помилка підключення (AUTH) - Можливо, відправник видалив запит на підключення. + Відправник, можливо, видалив запит на з\'єднання. Помилка видалення запиту на контакт Помилка зміни адреси - Помилка тесту на кроці %s. + Тест не пройшов на кроці %s. Сервер вимагає авторизації для створення черг, перевірте пароль - Можливо, в адресі сервера неправильно вказано відбиток сертифіката + Можливо, відбиток цифрового підпису сертифіката в адресі сервера невірний Створити чергу Миттєві сповіщення! Миттєві сповіщення вимкнено! Отримання повідомлень… - Приховати - Повідомлення SimpleX Chat - Показати тільки контакт + Сховати + Повідомлення чату SimpleX + Показати лише контакт Новий запит на контакт Підключено - SimpleX Lock увімкнено + Блокування SimpleX увімкнено Розблокувати - Вимкнути SimpleX Lock + Вимкнути блокування SimpleX Відповісти Зберегти Видалити - Повідомлення буде позначено для видалення. Отримувач(и) зможуть відкрити це повідомлення. - Видалити повідомлення користувача\? - Видалити для мене + Повідомлення буде позначено для видалення. Однак одержувач(і) зможуть розкрити це повідомлення. + Видалити повідомлення учасника? + Видалити лише для мене Для всіх - відредаговано - не вдалося відправити - не прочитано + редаговано + помилка відправки + непрочитане приєднатися як %s Скасувати попередній перегляд зображення Скасувати попередній перегляд файлу - Очікування зображення - Зображення надіслано - Очікування зображення + Очікування на зображення + Зображення відправлене + Очікування на зображення Великий файл! Файл збережено Голосове повідомлення Голосове повідомлення… - Контакт і всі повідомлення будуть видалені - це неможливо скасувати! - В очікуванні - Змінити адресу отримання\? + Контакт і всі повідомлення будуть видалені - цього не можна скасувати! + Очікує + Змінити адресу для отримання? Переглянути код безпеки - Щоб мати змогу надсилати голосові повідомлення, вам потрібно дозволити контакту надсилати їх. + Ви повинні дозволити вашому контакту надсилати голосові повідомлення, щоб мати змогу надсилати їх. Скасувати - ГАРАЗД + OK Скопійовано в буфер обміну - Підключитися за посиланням - Відкрийте в мобільному додатку, потім торкніться Підключіть в додатку.]]> - Вимкнути звук - Увімкнути звук - Ви запросили свого контакта - Контакт, якому ви надали це посилання, НЕ зможе підключитися! + Для підключення через посилання + Відкрити у мобільному додатку, а потім торкніться Підключити в додатку.]]> + Приглушити + Скасувати приглушення + Ви запросили контакт + Контакт, якому ви поділилися посиланням, НЕ зможе підключитися! заповнювач зображення профілю QR-код - допомога - покажіть QR-код у відеодзвінку або поділіться посиланням.]]> - Ваш профіль чату буде надіслано -\nдо вашого контакту - Посилання на одноразове запрошення - Неправильний код безпеки! - Щоб перевірити наскрізне шифрування з вашим контактом, порівняйте (або відскануйте) код на ваших пристроях. + довідка + покажіть QR-код у відеовиклику, або поділіться посиланням.]]> + Ваш профіль чату буде відправлено +\nвашому контакту + Одноразове запрошення + Невірний код безпеки! + Для перевірки end-to-end шифрування порівняйте (або скануйте) код на своїх пристроях. Ваші налаштування - Ваша адреса SimpleX - Допомога з уцінкою - SimpleX Lock + Ваша SimpleX-адреса + Допомога з Markdown + Блокування SimpleX Консоль чату Сервери SMP - Відскануйте QR-код сервера - Використовуйте для нових з\'єднань + Сканувати QR-код сервера + Використовувати для нових підключень Видалити сервер - Зірка на GitHub - Як користуватися вашими серверами - Збережені сервери WebRTC ICE буде видалено. - Налаштування серверів ICE + Оцінити на GitHub + Як використовувати ваші сервери + Збережені сервери WebRTC ICE будуть видалені. + Налаштувати сервери ICE Мережа та сервери Налаштування мережі - Використовувати SOCKS проксі\? - Використовуєте пряме підключення до Інтернету\? - Оновити налаштування хостів .onion\? - Використовуйте хости .onion - При наявності + Використовувати SOCKS-проксі? + Використовувати прямий підключення до Інтернету? + Оновити налаштування .onion-хостів? + Використовувати .onion-хости + Якщо доступно Ні - Onion хости будуть використовуватися за наявності. - Оновити режим транспортної ізоляції\? + .Onion-хости будуть використовуватися, якщо доступні. + Оновити режим ізоляції транспорту? simplexmq: v%s (%2s) Створити адресу - Поділіться посиланням + Поділитися посиланням Видалити адресу Повне ім\'я: Ви керуєте своїм чатом! - Платформа для обміну повідомленнями та додатків, що захищає вашу конфіденційність і безпеку. + Платформа обміну повідомленнями і застосунок, які захищають вашу конфіденційність та безпеку. Введіть своє ім\'я: - виклик у процесі - починаючи… - Перша платформа без жодних ідентифікаторів користувачів – приватна за дизайном. - Децентралізований - Використовуйте чат - Пізніше його можна змінити через налаштування. + дзвінок в процесі + запуск… + Перша платформа без ідентифікаторів користувачів – приватна за конструкцією. + Децентралізована + Використовувати чат + Це можна змінити пізніше в налаштуваннях. Миттєво - Дзвінок вже закінчився! - Ваші дзвінки + Виклик вже завершено! + Ваші виклики Ваші сервери ICE Відкрити через реле Динамік увімкнено - Перевернути камеру - Відхилений дзвінок - %1$d пропущено повідомлення(і) + Повернути камеру + Відхилений виклик + %1$d пропущено повідомлень ЧАТИ - SOCKS PROXY - Помилка запуску чату + SOCKS-ПРОКСІ + Помилка при запуску чату Зупинити - Імпорт + Імпортувати Файли та медіа Повідомлення - Налаштування зміни помилки + Помилка зміни налаштувань База даних зашифрована! - Новий пароль… - Підтвердіть нову парольну фразу… + Нова ключова фраза… + Підтвердіть нову ключову фразу… Зашифрувати базу даних\? - Неправильна парольна фраза! - Введіть правильну парольну фразу. + Неправильна ключова фраза! + Введіть правильну ключову фразу. Архів чату АРХІВ ЧАТУ - підключений - змінив свою роль на %s - ви змінили роль для себе на %s + підключив(лась) + змінив(ла) вашу роль на %s + ви змінили свою роль на %s ви змінили адресу - Не вибрано жодного контакту + Не вибрано контактів Видалити групу\? - Видалити + Вилучити Підключення - прямий - Оновлення + пряме + Оновити Контакт дозволяє Налаштування чату - Заборонити надсилання зникаючих повідомлень. - Заборонити надсилання голосових повідомлень. + Заборонити надсилання повідомлень, які зникають. + Забороняйте надсилання голосових повідомлень. Французький інтерфейс - Помилка збереження XFTP-серверів - Переконайтеся, що адреси XFTP-серверів мають правильний формат, розділені рядками і не дублюються. - Помилка завантаження XFTP-серверів - Помилка додавання користувача(ів) + Помилка збереження серверів XFTP + Переконайтеся, що адреси серверів XFTP вірного формату, розділені переносами рядків і не дублюються. + Помилка завантаження серверів XFTP + Помилка додавання учасників Помилка приєднання до групи - Не вдається отримати файл + Неможливо отримати файл Порівняти файл Видалити файл Періодичні сповіщення вимкнено! - Потрібна парольна фраза + Потрібен пароль Увімкнути - Автентифікація пристрою не ввімкнена. Ви можете увімкнути SimpleX Lock у Налаштуваннях, коли увімкнете автентифікацію пристрою. - Автентифікацію пристрою вимкнено. Вимкнення SimpleX Lock. + Аутентифікація пристрою не увімкнена. Ви можете увімкнути блокування SimpleX через налаштування, якщо увімкнете аутентифікацію пристрою. + Аутентифікація пристрою вимкнена. Вимикається блокування SimpleX. Зупинити чат - Відкрийте консоль чату + Відкрити консоль чату Повідомлення буде позначено як модероване для всіх учасників. Чати підключення… підключення… - Натисніть, щоб почати новий чат - Чат з розробниками + Торкніться, щоб розпочати новий чат + Чат із розробниками У вас немає чатів - Ви не можете надсилати повідомлення! - Будь ласка, зверніться до адміністратора групи. - Файл буде отримано, коли ваш контакт буде онлайн, будь ласка, зачекайте або перевірте пізніше! - Відео надіслано - В очікуванні відео + Ви не можете відправляти повідомлення! + Будь ласка, зв\'яжіться з адміністратором групи. + Файл буде отримано, коли ваш контакт буде в мережі, будь ласка, зачекайте або перевірте пізніше! + Відео відправлене + Очікування на відео Файл Підключено Відключено Живе повідомлення! - Будь ласка, попросіть вашого контакту увімкнути відправку голосових повідомлень. - Надіслати повідомлення в прямому ефірі - Надішліть повідомлення в реальному часі - воно буде оновлюватися для одержувача (одержувачів), поки ви його вводите + Будь ласка, попросіть вашого контакту увімкнути надсилання голосових повідомлень. + Надіслати живе повідомлення + Надішліть живе повідомлення - воно буде оновлюватися для одержувача(-ів), коли ви його набираєте Надіслати - Щоб почати новий чат + Щоб розпочати новий чат Натисніть кнопку - Якщо ви вирішите відхилити, відправник НЕ отримає сповіщення. + Якщо ви виберете відхилити, відправник НЕ буде повідомлений. Очистити чат - Невірне посилання! - Це посилання не є дійсним посиланням для підключення! - Запит на підключення відправлено! - Відкрити в мобільному додатку.]]> - Створіть одноразове посилання-запрошення + Неправильне посилання! + Це посилання не є дійсним з\'єднувальним посиланням! + Запит на з\'єднання відправлено! + Відкрити у мобільному додатку.]]> + Створити одноразове запрошення Сканувати код - Відскануйте код безпеки з додатку вашого контакту. - Неправильна адреса сервера! - Перевірте адресу сервера та спробуйте ще раз. + Скануйте код безпеки з додатка вашого контакту. + Невірна адреса сервера! + Перевірте адресу сервера і спробуйте ще раз. Сервери XFTP - Встановіть SimpleX Chat для терміналу - Зробити внесок - Оцініть програму + Встановити SimpleX Chat для терміналу + Внести вклад + Оцініть додаток Ваші сервери SMP Ваші сервери XFTP Використання серверів SimpleX Chat. - Як зробити - телефоную… - пропущений дзвінок + Як користуватися + дзвінок… + пропущений виклик відхилений виклик - Вхідний відеодзвінок + Вхідний відеовиклик Вхідний аудіовиклик - відеодзвінок (без шифрування e2e) - Дзвінки на екрані блокування: - одноранговий - Покласти слухавку - Створено на %1$s - Розширити вибір ролей - Так - Налаштування контактів + відеовиклик (не зашифрований e2e) + Виклики на екрані блокування: + від абонента до абонента + Завершити дзвінок + Створено %1$s + Розгорнути вибір ролі + так + Налаштування контакту Безпека SimpleX Chat була перевірена компанією Trail of Bits. Ваші контакти можуть дозволити повне видалення повідомлень. База даних буде зашифрована. - Помилка ланцюжка ключів + Помилка сховища ключів Невідома помилка - Відновлення помилки бази даних - ви залишили + Помилка відновлення бази даних + ви залишили групу підключення (прийнято) Запросити учасників Покинути групу - Зміна - Відправлення через + Змінити + Надсилання через Стан мережі Оновити налаштування мережі\? - Тільки локальні дані профілю + Локальні дані профілю тільки Ваш випадковий профіль - увімкнено - увімкнено для вас - Заборонити надсилати прямі повідомлення учасникам. + ввімкнено + ввімкнено для вас + Забороняйте надсилання прямих повідомлень учасникам. Видалити після Оцінка безпеки - Посилання на групи - Покращена конфіденційність та безпека + Посилання на групу + Покращена конфіденційність і безпека Живі повідомлення - Перевірте безпеку підключення - Порівняйте коди безпеки зі своїми контактами. - Кілька профілів чату - Основна версія: v%s + Перевірка безпеки підключення + Порівнюйте коди безпеки із своїми контактами. + Декілька профілів чату + Версія ядра: v%s Видалити адресу\? Ім\'я профілю: - чекаємо на підтвердження… - Переосмислення конфіденційності - Люди можуть підключатися до вас лише за посиланнями, якими ви ділитеся. + очікування підтвердження… + Приватність перевизначена + Люди можуть підключатися до вас лише за допомогою посилань, які ви надаєте. Як працює SimpleX - Детальніше читайте в нашому репозиторії GitHub. - e2e зашифрований аудіодзвінок - Відкрийте SimpleX Chat, щоб прийняти дзвінок - e2e зашифрований + Докладніше читайте в нашому репозиторії на GitHub. + зашифрований e2e аудіовиклик + Відкрийте SimpleX Chat для прийняття виклику + e2e зашифровано Динамік вимкнено - Дзвінок в очікуванні - Пропущений дзвінок - З\'єднувальний дзвінок + Очікування виклику + Пропущений виклик + Підключення виклику Конфіденційність і безпека Ваша конфіденційність НАЛАШТУВАННЯ ДОПОМОГА - ПІДТРИМКА SIMPLEX CHAT + ПІДТРИМАЙТЕ SIMPLEX CHAT Зупиніть чат, щоб експортувати, імпортувати або видалити базу даних чату. Ви не зможете отримувати та надсилати повідомлення, поки чат зупинено. Помилка видалення бази даних чату - Сповіщення будуть надходити лише до моменту зупинки програми! - Видалити + Сповіщення будуть доставлятися лише до зупинки додатка! + Вилучити Зашифрувати - Оновлення - Поточна парольна фраза… - База даних буде зашифрована, а пароль зберігатиметься у сховищі ключів. - Ключову фразу шифрування бази даних буде оновлено і збережено у сховищі ключів. - запрошені %1$s - запрошені за посиланням у вашій групі + Оновити + Поточна ключова фраза… + База даних буде зашифрована, і ключова фраза буде збережена в сховищі ключів. + Ключова фраза шифрування бази даних буде оновлена і збережена в сховищі ключів. + запросив(ла) %1$s + запросив(ла) через ваше посилання на групу ви змінили роль %s на %s ви змінили адресу для %s - зміна адреси для %s… - Не вдається запросити контакт! - %1$s УЧАСНИКИ + змінює адресу для %s… + Неможливо запросити контакт! + %1$s УЧАСНИКІВ Видалити групу Посилання на групу - Редагування профілю групи - Створити групове посилання + Редагувати профіль групи + Створити посилання на групу Змінити роль у групі\? - Помилка видалення учасника - Ваш профіль у чаті буде надіслано учасникам групи + Помилка при вилученні учасника + Ваш профіль чату буде відправлений учасникам групи Зберегти колір - Видалити для всіх + Видалення для всіх Голосові повідомлення - Голосові повідомлення в цьому чаті заборонені. - Максимум 40 секунд, отримується миттєво. - Встановіть його замість системної автентифікації. + Голосові повідомлення заборонені в цьому чаті. + Максимум 40 секунд, надходять миттєво. + Встановіть його замість системної аутентифікації. Вимкнути\? - Поділіться з контактами - Ваш профіль зберігається на вашому пристрої та доступний лише вашим контактам. Сервери SimpleX не бачать ваш профіль. - Збереження та сповіщення контактів - Збереження та сповіщення учасників групи - Вихід без збереження - Приховати профіль - Показати пароль + Поділитися з контактами + Ваш профіль зберігається на вашому пристрої і обмінюється лише з ваших контактів. Сервери SimpleX не можуть його бачити. + Зберегти і повідомити контакти + Зберегти і повідомити учасників групи + Вийти без збереження + Сховати профіль + Пароль для відображення Створити - немає шифрування e2e - контакт має шифрування e2e + без зашифрування e2e + контакт має зашифрування e2e Хеш попереднього повідомлення відрізняється. - Підтвердьте пароль + Підтвердити пароль Новий пароль - Перезавантажити + Перезапустити Ваша база даних чату Чат зупинено БАЗА ДАНИХ ЧАТУ Новий архів бази даних Зупинити чат\? - Вашу поточну базу даних чату буде ВИДАЛЕНО та ЗАМІНЕНО імпортованою. -\nЦю дію неможливо скасувати – ваш профіль, контакти, повідомлення та файли будуть безповоротно втрачені. + Ваша поточна база даних чату буде ВИДАЛЕНА та ЗАМІНЕНА імпортованою. +\nЦя дія незворотня - ваш профіль, контакти, повідомлення та файли буде втрачено безповоротно. ніколи %s секунд(и) - Пониження та відкритий чат + Знизити версію та відкрити чат Нова роль учасника Видалити посилання\? Видалити посилання - Перемикач - Помилка, що змінює роль + Перемкнути + Помилка при зміні ролі Введіть назву групи: Повна назва групи: - Помилка збереження профілю групи - Скидання до налаштувань за замовчуванням + Помилка при збереженні профілю групи + Скинути на замовчування сек Тайм-аут протоколу Зберегти - Оновлення налаштувань призведе до перепідключення клієнта до всіх серверів. - Вимкнути звук - Введіть пароль у пошуку - Ви і ваш контакт можете надсилати зникаючі повідомлення. - Тільки ви можете безповоротно видалити повідомлення (ваш контакт може позначити їх для видалення). + Оновлення налаштувань призведе до повторного підключення клієнта до всіх серверів. + Приглушити + Введіть пароль для пошуку + Як ви, так і ваш контакт можуть надсилати повідомлення, які зникають. + Тільки ви можете невідворотно видаляти повідомлення (ваш контакт може позначати їх для видалення). Тільки ваш контакт може додавати реакції на повідомлення. - Тільки ваш контакт може безповоротно видалити повідомлення (ви можете позначити їх для видалення). + Тільки ваш контакт може невідворотно видаляти повідомлення (ви можете позначати їх для видалення). Тільки ваш контакт може надсилати голосові повідомлення. - Заборонити надсилання зникаючих повідомлень. - Заборонити безповоротне видалення повідомлень. + Забороняйте надсилання повідомлень, які зникають. + Забороняйте невідворотне видалення повідомлень. Учасники групи можуть надсилати голосові повідомлення. - %dм + %dm Нове в %s - Пароль самознищення + Самознищуючий пароль Італійський інтерфейс - годин - днів - Виберіть - нестандартний - таємниця - Приєднуйтесь + години + дні + Вибрати + інше + прихований + Приєднатися Роль - непрямі (%1$s) - Увімкнути звук + непряме (%1$s) + Відглушити Повинен бути принаймні один профіль користувача. Зробіть профіль приватним! - запропонований %s + запропоновано %s Чернетка повідомлення - Зберегти чернетку останнього повідомлення з вкладеннями. - Повідомлення зникає - Надіслати зникаюче повідомлення + Зберігайте останню чернетку повідомлення із вкладеннями. + Зникне повідомлення + Надіслати зникне повідомлення зображення профілю Більше Створити профіль - GitHub.]]> - Відео включено - Це може статися, коли ви або ваше підключення використовували стару резервну копію бази даних. - Відновлення резервної копії бази даних + GitHub.]]> + Відео увімкнено + Це може трапитися, якщо ви або ваше з\'єднання використовували застарілу резервну копію бази даних. + Відновити резервну копію бази даних Зберегти архів запрошення до групи %1$s - Вас запрошено до групи. Приєднуйтесь, щоб спілкуватися з учасниками групи. + Вас запрошено в групу. Приєднуйтесь, щоб спілкуватися з учасниками групи. Реакції на повідомлення - Встановлюється 1 день - Заборонити реакцію на повідомлення. - Реакції на повідомлення в цьому чаті заборонені. + Встановити на 1 день + Забороняйте реакції на повідомлення. + Реакції на повідомлення заборонені в цьому чаті. %ds хвилини - Інтерфейс китайською та іспанською мовами + Китайський та іспанський інтерфейс підключення %1$d Помилка завантаження серверів SMP - Дублююче ім\'я користувача! - Помилка надсилання повідомлення + Дубль імені відображення! + Помилка відправлення повідомлення Відправник скасував передачу файлу. Помилка отримання файлу Помилка створення адреси - Неправильне посилання для підключення - Помилка при прийнятті запиту на контакт + Неправильне посилання на підключення + Помилка прийняття запиту на контакт Створити файл - Помилка видалення профілю користувача + Помилка видалення користувача Помилка оновлення конфіденційності користувача - SimpleX фонову службу – вона використовує кілька відсотків заряду акумулятора на день.]]> + фоновий сервіс SimpleX – він використовує кілька відсотків батареї щодня.]]> Періодичні сповіщення - Сервіс SimpleX Chat + Служба чату SimpleX Перевіряє нові повідомлення кожні 10 хвилин протягом 1 хвилини Приховано - Показати контакт та повідомлення - Приховати контакт і повідомлення - SimpleX Lock - Щоб захистити вашу інформацію, увімкніть SimpleX Lock. -\nПеред увімкненням цієї функції вам буде запропоновано пройти автентифікацію. - Увійдіть, використовуючи свій обліковий запис - Увімкнути SimpleX Lock - SimpleX Lock не ввімкнено! - Поділіться + Показати контакт і повідомлення + Сховати контакт і повідомлення + Блокування SimpleX + Щоб захистити вашу інформацію, увімкніть блокування SimpleX. +\nВам буде запропоновано завершити аутентифікацію перед увімкненням цієї функції. + Увійти за допомогою своїх облікових даних + Увімкнути блокування SimpleX + Блокування SimpleX не увімкнено! + Поділитися Копіювати Повідомлення буде видалено для всіх учасників. - Надсилання файлу буде зупинено. + Відправлення файлу буде зупинено. Зупинити отримання файлу\? Отримання файлу буде зупинено. Зупинити @@ -580,299 +580,299 @@ Файл буде видалено з серверів. Відкликати несанкціонована відправка - Ласкаво просимо %1$s! + Ласкаво просимо, %1$s! Ласкаво просимо! Цей текст доступний у налаштуваннях - вас запрошують до групи + вас запрошено в групу Поділитися повідомленням… - Поділитися медіафайлами… - Поділіться файлом… - Одночасно можна надіслати лише 10 зображень - Одночасно можна надіслати лише 10 відео - Помилка декодування - Зображення не може бути декодовано. Будь ласка, спробуйте інше зображення або зверніться до розробників. + Поділитися медіа… + Поділитися файлом… + Одночасно можна відправити лише 10 зображень + Одночасно можна відправити лише 10 відео + Помилка декодування зображення + Неможливо декодувати зображення. Спробуйте інше зображення або зв\'яжіться з розробниками. Зображення - Зображення буде отримано, коли ваш контакт завершить завантаження. - Зображення буде отримано, коли ваш контакт буде онлайн, будь ласка, зачекайте або перевірте пізніше! - Зображення збережено до Галереї + Зображення буде отримано, коли ваш контакт завершить його вивантаження. + Зображення буде отримано, коли ваш контакт буде в мережі, будь ласка, зачекайте або перевірте пізніше! + Зображення збережено в галереї Відео - Ваш контакт надіслав файл, розмір якого перевищує дозволений максимальний розмір (%1$s). - Наразі максимальний підтримуваний розмір файлу: %1$s . - Ця функція є експериментальною! Вона працюватиме, тільки якщо на іншому клієнті встановлено версію 4.2. Після завершення зміни адреси ви побачите повідомлення в бесіді - будь ласка, перевірте, чи можете ви отримувати повідомлення від цього контакту (або члена групи). - Підтвердіть код безпеки - Відправити повідомлення + Ваш контакт відправив файл, розмір якого більший, ніж поточно підтримуваний максимальний розмір (%1$s). + Поточно максимально підтримуваний розмір файлу - %1$s. + Адреса для отримання буде змінена на інший сервер. Зміна адреси завершиться після включення відправника. + Перевірити код безпеки + Надіслати повідомлення Записати голосове повідомлення - (зберігається тільки учасниками групи) - У дозволі відмовлено! + (зберігається лише на пристроях учасників групи) + Доступ відхилено! Камера - Дякуємо, що встановили SimpleX Chat! - Відскануйте QR-код: щоб з\'єднатися з вашим контактом, який покаже вам QR-код.]]> - Якщо ви отримали посилання на запрошення SimpleX Chat, ви можете відкрити його у своєму браузері: - Чисто + Дякуємо за установку SimpleX Chat! + Сканувати QR-код: щоб підключитися до вашого контакту, який вам показує QR-код.]]> + Якщо ви отримали запрошення від SimpleX Chat, ви можете відкрити його у вашому браузері: + Очистити Видалити Видалити - Позначити прочитано - Позначити як непрочитане + Позначити прочитаним + Позначити непрочитаним Встановити ім\'я контакту - Ви прийняли підключення - Видалити очікуване з\'єднання\? - Ваш контакт має бути онлайн, щоб з’єднання завершилося. -\nВи можете скасувати це з’єднання та видалити контакт (і спробувати пізніше з новим посиланням). - SimpleX Логотип + Ви прийняли з\'єднання + Видалити очікуюче з\'єднання? + Ваш контакт повинен бути в мережі, щоб завершити з\'єднання. +\nВи можете скасувати це з\'єднання і видалити контакт (і спробувати пізніше за допомогою нового посилання). + Логотип SimpleX Електронна пошта Цей QR-код не є посиланням! - Вас буде підключено до групи, коли пристрій хоста групи буде онлайн, зачекайте або перевірте пізніше! - Ви будете підключені, коли ваш запит на підключення буде прийнято, будь ласка, зачекайте або перевірте пізніше! - Поділіться одноразовим посиланням - Детальніше - Щоб підключитися, ваш контакт може відсканувати QR-код або скористатися посиланням у додатку. - Якщо ви не можете зустрітися особисто, покажіть QR-код у відеодзвінку або поділіться посиланням. - Ви можете поділитися своєю адресою у вигляді посилання або QR-коду - будь-хто зможе зв\'язатися з вами. + Ви будете підключені до групи, коли пристрій господаря групи буде в мережі, зачекайте або перевірте пізніше! + Вас підключать, коли ваш запит на з\'єднання буде прийнятий, зачекайте або перевірте пізніше! + Поділитися 1-разовим посиланням + Дізнатися більше + Щоб підключитися, ваш контакт може сканувати QR-код або використовувати посилання у додатку. + Якщо ви не можете зустрітися особисто, покажіть QR-код у відеовиклику або поділіться посиланням. + Ви можете поділитися своєю адресою в якості посилання або QR-коду - кожен може підключитися до вас. Вставити - Цей рядок не є посиланням для з\'єднання! + Цей рядок не є з\'єднувальним посиланням! Код безпеки - Позначку перевірено + Позначити, що перевірено Ваші профілі чату - Ключова фраза бази даних та експорт - Націнка в повідомленнях - Надсилайте запитання та ідеї - Увійдіть на сервер вручну + Пароль бази даних та експорт + Markdown у повідомленнях + Надсилайте питання та ідеї + Ввести адресу сервера вручну Попередньо встановлений сервер - Ваша адреса сервера + Адреса вашого сервера Сервери для нових підключень вашого поточного профілю чату - Використовуєте сервери SimpleX Chat\? - Сервери ICE (по одному на лінію) + Використовувати сервери SimpleX Chat? + Сервери ICE (один на рядок) Помилка збереження серверів ICE - Onion hosts will be required for connection. -\nPlease note: you will not be able to connect to the servers without .onion address. - Onion хости будуть використовуватися за наявності. - Для з\'єднання будуть потрібні хости onion. - Показати опції розробника + .Onion-хости будуть обов\'язковими для підключення. +\nЗверніть увагу: ви не зможете підключитися до серверів без адреси .onion. + Хости .onion будуть використовуватися, якщо доступні. + Хости .onion будуть обов\'язковими для підключення. + Показати параметри розробника Ідентифікатори бази даних та опція ізоляції транспорту. Сповіщення перестануть працювати, поки ви не перезапустите додаток Ви можете створити його пізніше Ваш поточний профіль Видалити зображення Зберегти налаштування\? - Зберегти та повідомити контакт + Зберегти і повідомити контакт Зберегти пароль профілю - Прихований пароль профілю - Профіль доступний лише вашим контактам. - Ім\'я не може містити пробілів. - Як використовувати націнку - Ви можете використовувати розмітку для форматування повідомлень: + Пароль схованого профілю + Профіль обмінюється лише з вашими контактами. + Ім\'я для відображення не може містити пробіли. + Як використовувати markdown + Ви можете використовувати markdown для форматування повідомлень: Створіть свій профіль - Створіть приватне з\'єднання - якщо SimpleX не має ідентифікаторів користувачів, як він може доставляти повідомлення\?]]> - отримувати повідомлення, ваші контакти - сервери, які ви використовуєте для надсилання їм повідомлень.]]> - дворівневого наскрізного шифрування.]]> + Створіть приватне підключення + як в SimpleX можливо доставляти повідомлення, якщо він не має ідентифікаторів користувачів?]]> + отримувати повідомлення, ваші контакти – сервери, які ви використовуєте для надсилання повідомлень їм.]]> + шифрування на двох рівнях.]]> Приватні сповіщення - Використовує більше заряду акумулятора! Завжди працює фоновий сервіс - сповіщення показуються, як тільки з\'являються повідомлення.]]> + Споживає більше заряду акумулятора! Фоновий сервіс завжди працює – сповіщення відображаються, як тільки повідомлення доступні.]]> Вставте отримане посилання Відео вимкнено - Дзвінок завершено - Ідентифікатор наступного повідомлення неправильний (менше або дорівнює попередньому). -\nЦе може статися через помилку або коли з\'єднання скомпрометовано. - Пароль самознищення ввімкнено! + Завершено виклик + Ідентифікатор наступного повідомлення є неправильним (менший або рівний попередньому). +\nЦе може трапитися через якусь помилку або коли з\'єднання скомпрометоване. + Пароль самознищення увімкнено! Пароль самознищення змінено! Ваш профіль, контакти та доставлені повідомлення зберігаються на вашому пристрої. ВИ ПРИСТРІЙ - Вимкнення + Вимкнути ТЕМИ ПОВІДОМЛЕННЯ ТА ФАЙЛИ - Чат запущено - Імпорт бази даних + Чат працює + Імпортувати базу даних Старий архів бази даних Видалити базу даних - Встановіть парольну фразу для експорту - База даних зашифрована за допомогою випадкової парольної фрази. Будь ласка, змініть його перед експортом. + Встановити пароль для експорту + База даних зашифрована випадковим паролем. Змініть його перед експортом. Помилка експорту бази даних чату Імпортувати базу даних чату\? Помилка імпорту бази даних чату Видалити профіль чату\? - Цю дію неможливо скасувати - всі отримані та надіслані файли і медіа будуть видалені. Зображення з низькою роздільною здатністю залишаться. - Видалення повідомлень - Оновити парольну фразу бази даних - База даних зашифрована за допомогою випадкової парольної фрази, яку ви можете змінити. - Парольну фразу шифрування бази даних буде оновлено. - Будь ласка, зберігайте пароль надійно, ви НЕ зможете змінити його, якщо втратите. - Неправильна парольна фраза бази даних - Пароль до бази даних відрізняється від збереженого у сховищі ключів. - Спроба змінити пароль бази даних не була завершена. + Цю дію неможливо відмінити - всі отримані та надіслані файли та медіа будуть видалені. Зображення низької роздільної здатності залишаться. + Видалити повідомлення + Оновити ключову фразу бази даних + База даних зашифрована випадковою ключовою фразою, яку можна змінити. + Ключова фраза шифрування бази даних буде оновлена. + Будь ласка, зберігайте ключову фразу надійно, ви НЕ зможете її змінити, якщо втратите її. + Неправильна ключова фраза бази даних + Ключова фраза бази даних відрізняється від збереженої в сховищі ключів. + Спроба змінити ключову фразу бази даних не була завершена. Відновити резервну копію бази даних\? - Будь ласка, введіть попередній пароль після відновлення резервної копії бази даних. Ця дія не може бути скасована. - Приєднуйтесь інкогніто - ліворуч - змінили роль %s на %s - видалена група + Будь ласка, введіть попередній пароль після відновлення резервної копії бази даних. Цю дію неможливо скасувати. + Приєднатися анонімно + вийшов(ла) + змінив(ла) роль %s на %s + групу видалено оновлено профіль групи - змінили для вас адресу + змінив(ла) адресу для вас змінює адресу… учасник - ліворуч + залишено групу видалено - запрошені + запрошено підключення (введено) Запросити до групи Пропустити запрошення учасників - %d вибрано контакт(и) + %d вибрано контактів ви: %1$s - Група буде видалена для всіх учасників - це неможливо скасувати! - Група буде видалена для вас - це не може бути скасовано! + Група буде видалена для всіх учасників - цю дію неможливо скасувати! + Група буде видалена для вас - цю дію неможливо скасувати! Створити посилання - Помилка оновлення посилання на групу - Помилка видалення посилання на групу - Тільки власники груп можуть змінювати налаштування групи. - Запис оновлено за - Надіслано на - Модерується на - Зникає за + Помилка при оновленні посилання на групу + Помилка при видаленні посилання на групу + Змінювати налаштування групи можуть лише її власники. + Запис оновлено + Надіслано + Модеровано о + Зникає о Ідентифікатор бази даних: %d - Запис оновлено за: %s - Модерується на: %s - Зникає в: %s - (поточний) - Видалити учасника - Роль буде змінено на \"%s\". Всі учасники групи будуть повідомлені про це. + Запис оновлено о: %s + Модеровано о: %s + Зникає о: %s + (поточне) + Вилучити учасника + Роль буде змінено на \"%s\". Всі учасники групи будуть сповіщені. Роль буде змінено на \"%s\". Учасник отримає нове запрошення. Група - Вітальне повідомлення + Ласкаво просимо Профіль групи зберігається на пристроях учасників, а не на серверах. Зберегти профіль групи - Підключення профілю та сервера + Профіль і підключення до серверів Показати - Ви можете приховати або вимкнути звук профілю користувача - утримуйте його для виклику меню. + Ви можете приховати або вимкнути звук профілю користувача - утримуйте його для меню. Показати профіль Показати профіль чату - Коли ви ділитеся з кимось своїм профілем інкогніто, цей профіль буде використовуватися для груп, до яких вони вас запрошують. - Світлий + Коли ви ділитесь анонімним профілем з кимось, цей профіль буде використовуватися для груп, до яких вас запрошують. + Світла Помилка імпорту теми Налаштування групи - Зникнення повідомлень - увімкнено для контакту + Повідомлення зникнення + ввімкнено для контакту вимкнено отримано, заборонено - Контакти можуть позначати повідомлення для видалення; ви зможете їх переглянути. - Заборонити аудіо/відеодзвінки. - Тільки ви можете надсилати зникаючі повідомлення. - Зникаючі повідомлення в цьому чаті заборонені. - У цьому чаті заборонено безповоротне видалення повідомлень. - Надсилати голосові повідомлення можете як ви, так і ваш контакт. + Контакти можуть позначати повідомлення для видалення; ви зможете їх переглядати. + Забороняйте аудіо/відео дзвінки. + Тільки ви можете надсилати повідомлення, які зникають. + Зникнення повідомлень заборонене в цьому чаті. + Невідворотне видалення повідомлень заборонено в цьому чаті. + Як ви, так і ваш контакт можуть надсилати голосові повідомлення. Тільки ви можете надсилати голосові повідомлення. Тільки ви можете додавати реакції на повідомлення. Заборонити реакції на повідомлення. - У цій групі заборонено зникаючі повідомлення. - Учасники групи можуть надсилати прямі повідомлення. - У цій групі заборонені прямі повідомлення між учасниками. - Учасники групи можуть безповоротно видаляти надіслані повідомлення. - У цій групі заборонено безповоротне видалення повідомлень. - Голосові повідомлення в цій групі заборонені. + Самознищувальні повідомлення заборонені в цій групі. + Учасники групи можуть надсилати приватні повідомлення. + Приватні повідомлення між учасниками заборонені в цій групі. + Учасники групи можуть назавжди видаляти відправлені повідомлення. + Назавжди видалення повідомлень заборонене в цій групі. + Голосові повідомлення заборонені в цій групі. Учасники групи можуть додавати реакції на повідомлення. - Реакції на повідомлення в цій групі заборонені. + Реакції на повідомлення заборонені в цій групі. %d година %d тиждень - %d тижнів + %d тижні %dw - запропонував %s: %2s - З необов’язковим вітальним повідомленням. - Приховати екран програми в останніх програмах. - Зникнення повідомлень + запропоновано %s: %2s + З опційним вітанням. + Приховуйте екран додатка в останніх програмах. + Самознищувальні повідомлення Одержувачі бачать оновлення, коли ви їх вводите. - Транспортна ізоляція - Для захисту часового поясу у файлах зображень/голосу використовується UTC. - Дякуємо користувачам - зробіть свій внесок через Weblate! + Ізоляція транспорту + Для захисту часового поясу файли зображень/голосу використовують UTC. + Дякуємо користувачам – приєднуйтеся через Weblate! Тепер адміністратори можуть: -\n- видалити повідомлення учасників. -\n- відключити учасників (роль \"спостерігач\") - Налаштуйте повідомлення, яке показуватиметься новим користувачам! - Подальше зменшення використання акумулятора - Незабаром буде ще більше покращень! - Відео та файли до 1 Гб +\n- видаляти повідомлення учасників. +\n- вимикати учасників (роль спостерігач) + Встановіть повідомлення, яке показується новим учасникам! + Додатково зменшено використання батареї + Більше поліпшень незабаром! + Відео та файли до 1 ГБ Польський інтерфейс - Дякуємо користувачам - зробіть свій внесок через Weblate! + Дякуємо користувачам – приєднуйтеся через Weblate! Реакції на повідомлення - Нарешті, вони у нас є! 🚀 + Нарешті, ми їх маємо! 🚀 Налаштовуйте та діліться кольоровими темами. - Дякуємо користувачам - зробіть свій внесок через Weblate! - секунд - тижнів + Дякуємо користувачам – приєднуйтеся через Weblate! + секунди + тижні місяці - Ви вже підключені до %1$s. + Ви вже підключені до %1$s через це посилання. Режим інкогніто СЕРВЕРИ - Зберегти привітальне повідомлення\? + Зберегти ласкаво просимо? Отримання через - Вимкнено, коли неактивний! + Приглушено, коли неактивно! Видалити профіль Інкогніто - Це дозволяє мати багато анонімних з\'єднань без будь-яких спільних даних між ними в одному профілі чату. + Це дозволяє мати багато анонімних з\'єднань без будь-яких загальних даних між ними в одному чат-профілі. SimpleX - Тільки ваш контакт може надсилати зникаючі повідомлення. - увімкнути - %d годин - Помилка видалення очікуваного з\'єднання контакту + Тільки ваш контакт може надсилати повідомлення, які зникають. + увімк + %d години + Помилка видалення очікуючого з\'єднання з контактом Помилка завантаження деталей - Якщо ваш контакт не видалив з\'єднання або якщо це посилання вже використовувалося, це може бути помилкою - будь ласка, повідомте про це. -\nЩоб підключитися, попросіть вашого контакта створити інше посилання і перевірте, чи маєте ви стабільне з\'єднання з мережею. + Якщо ваш контакт не видалив з\'єднання або це посилання вже використано, це може бути помилкою - будь ласка, повідомте про це. +\nДля підключення попросіть вашого контакту створити інше посилання на з\'єднання та перевірте стабільність мережевого підключення. Помилка видалення контакту Помилка видалення групи - Від\'єднати + Відключити Безпечна черга Видалити чергу - Будь ласка, переконайтеся, що ви використали правильне посилання, або попросіть свого контакта надіслати вам інше. - дозвольте SimpleX працювати у фоновому режимі у наступному діалоговому вікні. \u0020В іншому випадку сповіщення буде вимкнено.]]> + Будь ласка, перевірте, що ви використали правильне посилання або попросіть вашого контакту вислати інше. + дозвольте SimpleX працювати в фоновому режимі в наступному діалозі. В іншому випадку сповіщення будуть вимкнені.]]> Миттєві сповіщення - Контакт приховано: + Контакт прихований: нове повідомлення Редагувати Інформація - Надіслано повідомлення - Вам потрібно буде пройти автентифікацію при запуску або відновленні програми після 30 секунд роботи у фоновому режимі. - Отримано повідомлення + Відправлене повідомлення + Вам буде потрібно пройти аутентифікацію при запуску або відновленні програми через 30 секунд у фоновому режимі. + Отримане повідомлення Історія Розкрити Приховати - Редагування + Модерувати Видалити повідомлення\? - Повідомлення буде видалено - це неможливо скасувати! - надісланий - Голосові повідомлення заборонені! + Повідомлення буде видалено - цю дію неможливо скасувати! + відправлено + Заборонено голосові повідомлення! Надіслати Підтвердити - Індивідуальний час - Створіть одноразове посилання-запрошення - Відскануйте QR-код + Інший час + Створити одноразове запрошення + Сканувати QR-код Зображення Відео - Прийняте вами з\'єднання буде скасовано! - Контакт ще не підключено! + Прийняте вами з\'єднання буде скасоване! + Контакт ще не підключений! Тестові сервери Зберегти сервери Ваш сервер - Тест сервера завершився невдало! + Тест сервера не вдався! Деякі сервери не пройшли тест: Використовувати сервер - Переконайтеся, що адреси серверів WebRTC ICE мають правильний формат, розділені рядками та не дублюються. - Якщо ви підтвердите, сервери обміну повідомленнями зможуть бачити вашу IP-адресу, а ваш провайдер - до яких серверів ви підключаєтеся. - Onion хости не використовуватимуться. - Транспортна ізоляція - Налаштувати тему - Поділіться адресою з контактами\? - З\'єднувальний дзвінок… - Ми не зберігаємо жодних ваших контактів чи повідомлень (після доставки) на серверах. - в очікуванні відповіді… + Переконайтеся, що адреси серверів WebRTC ICE вказані в правильному форматі, розділені по рядках і не повторюються. + Якщо ви підтвердите, сервери обміну повідомленнями матимуть можливість бачити ваш IP-адресу, а ваш постачальник - які саме сервери ви використовуєте для підключення. + .Onion-хости не будуть використовуватися. + Ізоляція транспорту + Налаштування теми + Поділитися адресою з контактами? + підключення дзвінка… + Ми не зберігаємо жодні з ваших контактів чи повідомлень (після доставки) на серверах. + очікування відповіді… Як це працює - відеодзвінок + відеовиклик Показати Вимкнути - Сервер ретрансляції використовується лише за необхідності. Інша сторона може спостерігати за вашою IP-адресою. - %1$d повідомлення не вдалося розшифрувати. - %1$d повідомлення пропущені. - Будь ласка, повідомте про це розробникам. - Надіслати попередній перегляд за посиланням - Блокування після - Представити + Реле-сервер використовується лише у необхідних випадках. Інша сторона може спостерігати за вашою IP-адресою. + %1$d повідомлень не вдалося розшифрувати. + %1$d пропущено повідомлень. + Будь ласка, повідомте розробникам про це. + Надсилати попередні перегляди посилань + Блокувати через + Підтвердити Увімкнути пароль самознищення Змінити режим самознищення Неправильний пароль @@ -880,581 +880,643 @@ Змінити пароль самознищення Пароль самознищення Увімкнути самознищення - Нове ім\'я для відображення: - Якщо ви введете пароль самознищення під час відкриття програми: - Якщо ви введете цей пароль при відкритті програми, всі дані програми будуть безповоротно видалені! + Нове ім\'я профілю: + Якщо ви введете пароль самознищення при відкритті застосунку: + Якщо ви введете цей пароль при відкритті застосунку, всі дані застосунку буде неможливо відновити! Встановити пароль - Цю дію неможливо скасувати - ваш профіль, контакти, повідомлення та файли будуть безповоротно втрачені. - Видалено базу даних чату - Немає отриманих або відправлених файлів + Цю дію неможливо відмінити - ваш профіль, контакти, повідомлення та файли буде втрачено безповоротно. + Базу даних чату видалено + Немає отриманих або відісланих файлів Отримано о - Видалено за - Отримано за: %s - Надіслано: %s - Видалено за: %s - %s (поточний) + Видалено о + Отримано о: %s + Надіслано о: %s + Видалено о: %s + %s (поточне) %dh %d день %d днів скасовано %s - ЗАПУСТИТИ ЧАТ - Ключова фраза бази даних - Експорт бази даних + ЗАПУСК ЧАТУ + Пароль бази даних + Експортувати базу даних Видалити всі файли Видаляйте повідомлення після Увімкнути автоматичне видалення повідомлень\? Повинен бути принаймні один видимий профіль користувача. Приховані профілі чату - Привітальне повідомлення групи - Дякуємо користувачам - зробіть свій внесок через Weblate! - Режим SimpleX Lock - Аутентифікація системи - Для захисту конфіденційності, замість ідентифікаторів користувачів, які використовуються на всіх інших платформах, SimpleX має ідентифікатори для черг повідомлень, окремі для кожного з ваших контактів. - Коли додаток працює - Періодичні - контакт не має шифрування e2e + Повідомлення вітання групи + Дякуємо користувачам – приєднуйтеся через Weblate! + Режим блокування SimpleX + Системна аутентифікація + Для захисту приватності, замість ідентифікаторів користувачів, які використовуються всіма іншими платформами, у SimpleX є ідентифікатори черг повідомлень, окремі для кожного з ваших контактів. + Коли додаток запущено + Періодично + контакт не має зашифрування e2e Увімкнути блокування Пароль не змінено! - Зміна режиму блокування + Змінити режим блокування Зупиніть чат, щоб увімкнути дії з базою даних. Перезапустіть додаток, щоб створити новий профіль чату. - Видалення файлів для всіх профілів чату - Видаляти файли та медіа\? - %d файл(и) загальним розміром %s - Ваша база даних чату не зашифрована - встановіть парольну фразу, щоб захистити її. - Зверніть увагу: ви НЕ зможете відновити або змінити парольну фразу, якщо її втратите.]]> + Видалити файли для всіх профілів чату + Видалити файли та медіа? + %d файл(ів) обсягом %s + Ваша база даних чату не зашифрована - встановіть ключову фразу для її захисту. + Зверніть увагу: ви НЕ зможете відновити або змінити ключову фразу, якщо ви її втратите.]]> Система Несумісна версія бази даних Підтвердити оновлення бази даних - Недійсне підтвердження перенесення - версія бази даних новіша, ніж додаток, але без міграції вниз: %s - інша міграція в додатку/базі даних: %s / %s + Недійсне підтвердження міграції + Версія бази даних новіша, ніж додаток, але немає можливості знизити до: %s + Різна міграція в додатку/базі даних: %s / %s Міграції: %s - Термін дії запрошення закінчився! - Ви більше не будете отримувати повідомлення від цієї групи. Історія чату буде збережена. + Запрошення закінчилось! + Ви перестанете отримувати повідомлення від цієї групи. Історія чату буде збережена. Запросити учасників Група неактивна ви видалили %1$s - Натисніть, щоб активувати профіль. - Не можу видалити профіль користувача! - Заборонити надсилання голосових повідомлень. - Учасники групи можуть надсилати зникаючі повідомлення. + Торкніться для активації профілю. + Не вдається видалити профіль користувача! + Забороняйте надсилання голосових повідомлень. + Учасники групи можуть надсилати самознищувальні повідомлення. %d хв - Зменшення використання акумулятора + Зменшене споживання енергії батареї Редагувати зображення - дублююче повідомлення - Невідома помилка в базі даних: %s - Введіть пароль… + дубльоване повідомлення + Невідома помилка бази даних: %s + Введіть ключову фразу… Відкрити чат - Ви приєдналися до цієї групи. Підключення до запрошеного учасника групи. - оновлений профіль групи + Ви приєдналися до цієї групи. З\'єднання з учасником, який вас запрошував. + оновлено профіль групи змінює адресу… Залишити спостерігач УЧАСНИК Режим інкогніто захищає вашу конфіденційність, використовуючи новий випадковий профіль для кожного контакту. - Незабаром буде ще більше покращень! - Тільки власники груп можуть вмикати голосові повідомлення. + Більше поліпшень незабаром! + Тільки власники груп можуть увімкнути голосові повідомлення. Відхилити Очистити чат\? - посилання для попереднього перегляду зображення + зображення попереднього перегляду посилання скасувати попередній перегляд посилання Налаштування - Onion хости не використовуватимуться. + Хости .onion не будуть використовуватися. Виклик у процесі - Помилка в базі даних + Помилка бази даних Відновити - підключений + підключено творець підключення (оголошено) %d місяць - Різні імена, аватарки та транспортна ізоляція. + Різні імена, аватари та ізоляція транспорту. Приватні імена файлів Покращена конфігурація сервера Помилка збереження пароля користувача - Ви використовуєте профіль анонімного перегляду для цієї групи – щоб запобігти спільному доступу до вашого основного профілю, запрошення контактів заборонено + Ви використовуєте анонімний профіль для цієї групи - для запобігання розголошенню вашого основного профілю запрошення контактів не дозволено. Ви приєдналися до цієї групи - Вас не вдалося перевірити; будь ласка спробуйте ще раз. - Немає пароля додатку - Введіть пароль - Поточний пароль - Введення пароля - Змінити пароль + Вашу особу не вдалося підтвердити; спробуйте ще раз. + Немає коду доступу до додатка + Введіть код доступу + Поточний код доступу + Введення коду доступу + Змінити код доступу Негайно - %d секунд - %d хвилин + %d секунд(и) + %d хвилин(и) Помилка доставки повідомлення Профіль чату Відхилити - Сервер ретрансляції захищає вашу IP-адресу, але він може спостерігати за тривалістю дзвінка. + Реле-сервер захищає ваш IP-адресу, але може відслідковувати тривалість виклику. Режим блокування Пароль змінено! Система Пароль Пароль встановлено! - Оновіть і відкрийте чат - Попередження: ви можете втратити деякі дані! - Ви можете розпочати чат через Налаштування програми / База даних або перезапустивши програму. - Ви надіслали запрошення до групи - Вас запросили до групи - Термін дії групового запрошення закінчився - Контакт перевірено - Ви намагаєтеся запросити контакт, з яким ви поділилися анонімним профілем, до групи, у якій ви використовуєте свій основний профіль - Помилка створення посилання на групу + Оновити та відкрити чат + Попередження: можливо, ви втратите деякі дані! + Ви можете запустити чат через налаштування програми або перезапустивши додаток. + Ви відправили запрошення в групу + Вас запрошено в групу + Запрошення в групу закінчилось + Контакт відмічено + Ви намагаєтеся запросити контакт, з яким ви поділилися інкогніто-профілем, до групи, в якій ви використовуєте основний профіль + Помилка при створенні посилання на групу ДЛЯ КОНСОЛІ - Учасник буде видалений з групи - це неможливо скасувати! + Учасника буде вилучено з групи - цю дію неможливо скасувати! Змінити роль - Повернути - Ви все одно отримуватимете дзвінки та сповіщення від вимкнених профілів, якщо вони активні. - %d місяців - %d міс - Надіслані повідомлення будуть видалені через встановлений час. + Відновити + Ви все ще отримуватимете дзвінки та сповіщення від приглушених профілів, коли вони активні. + %d місяці + %dmth + Надіслані повідомлення будуть видалені після встановленого часу. Відкриття бази даних… - Помилка налаштування адреси - Підключіться - Будь ласка, запам\'ятайте або надійно збережіть його - втрачений пароль неможливо відновити! + Помилка встановлення адреси + Підключити + Будь ласка, запам\'ятайте або збережіть його надійно - немає можливості відновлення втраченого пароля! Відкрити профілі чату - В очікуванні відео - Очікування файлу + Очікування на відео + Очікування на файл Голосове повідомлення (%1$s) Сповіщення Видалити контакт\? - Скасувати повідомлення в прямому ефірі + Скасувати живе повідомлення Скинути - без подробиць + без деталей Підключитися за посиланням / QR-кодом - Чисто + Очистити Неправильний QR-код - Ви будете з\'єднані, коли пристрій вашого контакту буде в мережі, будь ласка, зачекайте або перевірте пізніше! - Ви не втратите свої контакти, якщо згодом видалите свою адресу. - Коли люди звертаються із запитом на підключення, ви можете прийняти або відхилити його. - Посібнику користувача.]]> - Адреса SimpleX - Очистити верифікацію + Вас підключать, коли пристрій вашого контакту буде в мережі, зачекайте або перевірте пізніше! + Ви не втратите свої контакти, якщо ви пізніше видалите свою адресу. + Коли люди просять про з\'єднання, ви можете його прийняти чи відхилити. + Посібнику користувача.]]> + SimpleX-адреса + Очистити перевірку %s перевірено %s не перевірено - Надішліть нам електронний лист + Надішліть нам електронного листа Тестовий сервер Зберегти сервери\? Ваші сервери ICE Зберегти - Налаштування проксі SOCKS - Використовуйте проксі SOCKS + Налаштування SOCKS-проксі + Використовувати SOCKS-проксі порт %d Хост Порт - Потрібно - КОЛЬОРИ ТЕМИ - Створіть адресу, щоб люди могли з вами зв\'язатися. - Ваші контакти залишаться на зв’язку. - Створіть адресу SimpleX - Оновлення профілю буде надіслано вашим контактам. - Припинити ділитися адресою\? - Припиніть ділитися - Введіть вітальне повідомлення... (необов\'язково) + Обов\'язково + КОЛОРИ ТЕМИ + Створіть адресу, щоб дозволити людям підключатися до вас. + Ваші контакти залишаться підключеними. + Створити SimpleX-адресу + Оновлення профілю буде відправлено вашим контактам. + Зупинити поділ адреси? + Зупинити поділ + Введіть текст привітання... (необов\'язково) Зберегти налаштування\? - Зберегти налаштування автоприйняття + Зберегти налаштування автоприйому Привіт! -\nЗв\'яжіться зі мною через SimpleX Chat: %s +\nПриєднуйтесь до мене через SimpleX Chat: %s Запросити друзів - Давайте поговоримо в SimpleX Chat + Давайте говорити в SimpleX Chat Продовжити Не створювати адресу - отримав відповідь… - отримав підтвердження… + отримано відповідь… + отримано підтвердження… підключення… - підключений - закінчився - Імунітет до спаму та зловживань - %1$s хоче зв\'язатися з вами через - e2e зашифрований відеодзвінок + підключено + завершено + Стійка до спаму та зловживань + %1$s хоче підключитися до вас через + зашифрований e2e відеовиклик Ігнорувати - Це може статися, коли: -\n1. Термін дії повідомлень закінчився в клієнті-відправнику через 2 дні або на сервері через 30 днів. -\n2. Не вдалося розшифрувати повідомлення, тому що ви або ваш контакт використовували стару резервну копію бази даних. -\n3. З\'єднання було скомпрометовано. + Це може трапитися, коли: +\n1. Повідомлення застаріли відправником через 2 дні або на сервері через 30 днів. +\n2. Помилка розшифровки повідомлення, оскільки ви або ваш контакт використовували застарілу резервну копію бази даних. +\n3. З\'єднання було компрометоване. Пропущені повідомлення - Захистіть екран програми - Помилка зупинки чату - Імпорт бази даних чату - Перезапустіть програму, щоб використовувати імпортовану базу даних чату. + Захист екрану застосунку + Помилка при зупинці чату + База даних чату імпортована + Перезапустіть додаток, щоб використовувати імпортовану базу даних чату. Файл: %s - Для відкриття чату потрібно ввести пароль до бази даних. + Для відкриття чату потрібна ключова фраза бази даних. Приєднатися до групи\? Оновлення бази даних Видалити архів Видалити архів чату\? - Покинути групу\? + Вийти з групи? Групу не знайдено! - Цієї групи більше не існує. - Неможливо запросити контакти! + Ця група більше не існує. + Неможливо запрошувати контакти! завершено - Вітальне повідомлення - Виберіть контакти + Привітання + Вибрати контакти Поділитися адресою - Ви можете поділитися посиланням або QR-кодом - будь-хто зможе приєднатися до групи. Ви не втратите учасників групи, якщо пізніше видалите її. - Ви можете поділитися цією адресою зі своїми контактами, щоб вони могли зв\'язатися з %s. - Місцева назва + Ви можете поділитися посиланням або QR-кодом - будь-хто зможе приєднатися до групи. Ви не втратите членів групи, якщо потім видалите її. + Ви можете поділитися цією адресою зі своїми контактами, щоб вони могли підключитися до %s. + Локальна назва Ідентифікатор бази даних Попередній перегляд - Введіть вітальне повідомлення… + Введіть ласкаво просимо… Змінити адресу отримання Створити секретну групу - Повністю децентралізована - видима лише для учасників. - Тайм-аут підключення TCP - Більше не показувати - Вторинний - Меню та сповіщення - Назва - Надіслано повідомлення - Отримано повідомлення + Повністю децентралізовано - видимо тільки для учасників. + Тайм-аут з\'єднання TCP + Не показувати знову + Другорядний + Меню та повідомлення + Заголовок + Надіслане повідомлення + Отримане повідомлення Ви дозволяєте ні - вимкнено - Встановіть налаштування групи - Ваші уподобання + вимк + Встановити налаштування групи + Ваші налаштування Прямі повідомлення Помилка - Посилання на одноразове запрошення - Почніть новий чат + Одноразове запрошення + Створити новий чат Кнопка закриття Попередньо встановлена адреса сервера Чат зупинено - Збереження та оновлення профілю групи + Зберегти та оновити профіль групи Тема - Підтримка Bluetooth та інші покращення. - Модерація груп + Підтримка Bluetooth та інші поліпшення. + Модерація групи Пароль профілю - Відео буде отримано, коли ваш контакт завершить завантаження. - Відео буде отримано, коли ваш контакт буде онлайн, будь ласка, зачекайте або перевірте пізніше! - Файл буде отримано, коли ваш контакт завершить завантаження. + Відео буде отримано, коли ваш контакт завершить його вивантаження. + Відео буде отримано, коли ваш контакт буде в мережі, будь ласка, зачекайте або перевірте пізніше! + Файл буде отримано, коли ваш контакт завершить його вивантаження. Помилка збереження файлу Видалити контакт Встановити ім\'я контакту… Файл З галереї - SimpleX Команда - хоче зв\'язатися з вами! - ЕКСПЕРИМЕНТАЛЬНИЙ - Ви повинні використовувати найновішу версію вашої бази даних чату ЛИШЕ на одному пристрої, інакше ви можете перестати отримувати повідомлення від деяких контактів. - Це налаштування застосовується до повідомлень у вашому поточному профілі чату + Команда SimpleX + хоче підключитися до вас! + ЕКСПЕРИМЕНТАЛЬНІ ФУНКЦІЇ + Ви повинні використовувати найновішу версію бази даних чату лише на одному пристрої, інакше ви можете припинити отримання повідомлень від деяких контактів. + Цей параметр застосовується до повідомлень у вашому поточному профілі чату Зашифрована база даних Помилка: %s - Пониження бази даних - Групове запрошення більше не дійсне, воно було видалено відправником. - видалено %1$s - підключення (вступне запрошення) + Зниження версії бази даних + Запрошення в групу більше не дійсне, його видалив відправник. + видалив(ла) %1$s + підключення (запрошення для введення) підключення Немає контактів для додавання Початкова роль - Чисто + Очистити Читати далі - Безповоротне видалення повідомлення - Дякуємо користувачам - зробіть свій внесок через Weblate! - Щоб отримувати повідомлення, будь ласка, введіть пароль до бази даних + Незворотне видалення повідомлень + Дякуємо користувачам – приєднуйтеся через Weblate! + Для отримання сповіщень, будь ласка, введіть пароль бази даних Файл не знайдено - Підключіться за посиланням - Підключіться - Щоб відкрити свій прихований профіль, введіть повний пароль у поле пошуку на сторінці \"Ваші профілі чату\". - Підтвердьте пароль + Підключитися через посилання + Підключитися + Щоб показати ваш схований профіль, введіть повний пароль у поле пошуку на сторінці Ваші профілі чату. + Підтвердити пароль %dd - Захистіть свої профілі чату паролем! - Помилка розшифровки + Захистіть свої чат-профілі паролем! + Помилка дешифрування Сервер вимагає авторизації для завантаження, перевірте пароль Завантажити файл Завантажити файл Не вдається ініціалізувати базу даних База даних працює некоректно. Натисніть, щоб дізнатися більше - Додаток періодично отримує нові повідомлення - він використовує кілька відсотків заряду акумулятора на день. Додаток не використовує push-сповіщення - дані з вашого пристрою не надсилаються на сервери. - Дзвінки SimpleX Chat + Програма періодично отримує нові повідомлення — це використовує кілька відсотків батареї щодня. Програма не використовує торкання сповіщень — дані з вашого пристрою не відправляються на сервери. + Дзвінки чату SimpleX Служба сповіщень Показати попередній перегляд Попередній перегляд сповіщень - Запускається, коли програма відкрита - Періодично запускається + Запускається, коли додаток відкритий + Запускається періодично Текст повідомлення - Ім\'я контактної особи - Підтвердіть свій обліковий запис - Увімкнути SimpleX Lock можна в Налаштуваннях. - Швидше за все, цей контакт видалив зв\'язок з вами. - Контекстна піктограма + Ім\'я контакту + Підтвердіть свої облікові дані + Ви можете увімкнути блокування SimpleX через налаштування. + Ймовірно, цей контакт видалив з вами зв\'язок. + Значок контексту Забагато зображень! Забагато відео! ви спостерігач - кольорові - дзвінок закінчився %1$s - помилка виклику - Наступне покоління приватних повідомлень - Протокол і код з відкритим вихідним кодом - будь-хто може запускати сервери. - Інструменти для розробників - Експериментальні особливості + кольоровий + дзвінок завершено %1$s + помилка дзвінка + Наступне покоління приватного обміну повідомленнями + Відкритий протокол та код – кожен може запустити сервери. + Інструменти розробника + Експериментальні функції ДЗВІНКИ - Збережіть парольну фразу у сховищі ключів + Зберегти ключову фразу в сховищі ключів Помилка шифрування бази даних - Видалити парольну фразу з Keystore\? - Будь ласка, введіть правильний поточний пароль. - Будь ласка, зберігайте пароль надійно, ви НЕ зможете отримати доступ до чату, якщо втратите його. + Вилучити ключову фразу із сховища ключів? + Будь ласка, введіть правильну поточну ключову фразу. + Будь ласка, зберігайте ключову фразу надійно, ви НЕ зможете отримати доступ до чату, якщо втратите її. Видалити профіль чату\? - Зупинити файл - Припинити надсилати файл\? + Зупинити відправлення файлу + Зупинити відправлення файлу? Створити секретну групу (щоб поділитися з вашим контактом) - (відсканувати або вставити з буфера обміну) - зв\'язатися з розробниками SimpleX Chat, щоб задати будь-які питання та отримати оновлення.]]> - Відскануйте QR-код.]]> - SimpleX Адреса + (сканувати або вставити з буферу обміну) + підключитися до розробників SimpleX Chat, щоб задати будь-які питання і отримувати оновлення.]]> + Сканувати QR-код.]]> + Адреса SimpleX Показати QR-код Приєднання до групи курсив - Сервери WebRTC ICE - Увімкніть дзвінки з екрана блокування через Налаштування. - Швидко і без очікування, поки відправник буде онлайн! + WebRTC сервери ICE + Увімкніть прийом викликів з екрану блокування через Налаштування. + Швидко та без очікування, поки відправник у мережі! немає тексту - Надішліть пряме повідомлення + Надіслати пряме повідомлення Інтервал PING Кількість PING Увімкнути TCP keep-alive Видалити профіль чату для Приховати - Темний - Система + Темна + Системна Темна тема - Імпорт теми - Експорт теми + Імпортувати тему + Експортувати тему Переконайтеся, що файл має правильний синтаксис YAML. Експортуйте тему, щоб мати приклад структури файлу теми. Скинути кольори за замовчуванням (%s) - Дзвонити можете тільки ви. + Тільки ви можете здійснювати дзвінки. Тільки ваш контакт може здійснювати дзвінки. Що нового Голосові повідомлення - За профілем чату (за замовчуванням) або за з\'єднанням (BETA). - Користувацькі теми + За профілем чату (типово) або за підключенням (BETA). + Власні теми - голосові повідомлення до 5 хвилин. -\n- користувальницький час зникнення. +\n- власний час на зникнення. \n- історія редагування. Японський та португальський інтерфейс Натисніть, щоб приєднатися - Натисніть, щоб приєднатися інкогніто - Ви відхилили запрошення до групи - видалив вас + Натисніть, щоб приєднатися анонімно + Ви відхилили запрошення в групу + вас видалили власник видалено - відсканувати QR-код у відеодзвінку, або ваш контакт може поділитися посиланням на запрошення.]]> - Як ним користуватися + сканувати QR-код у відеовиклику, або ваш контакт може поділитися посиланням на запрошення.]]> + Як користуватися Підключення - Використовувати .onion хости на Ні, якщо SOCKS проксі не підтримує їх.]]> - Покажи: - Приховати: - Під час імпорту виникли деякі нефатальні помилки – ви можете переглянути консоль чату, щоб дізнатися більше. - Цю дію неможливо скасувати - повідомлення, надіслані та отримані раніше, ніж вибрані, будуть видалені. Це може зайняти кілька хвилин. - Вам доведеться вводити парольну фразу щоразу при запуску програми - вона не зберігається на пристрої. - Змінити пароль до бази даних\? - Не вдається отримати доступ до сховища ключів, щоб зберегти пароль до бази даних - Збережіть пароль і відкрийте чат - Пароль не знайдено у сховищі ключів, будь ласка, введіть його вручну. Це могло статися, якщо ви відновили дані програми за допомогою інструменту резервного копіювання. Якщо це не так, зверніться до розробників. + Використовувати .onion-хости на Ні, якщо SOCKS-проксі їх не підтримує.]]> + Показати: + Сховати: + Під час імпорту сталися деякі невідновні помилки - ви можете переглянути консоль чату для отримання більше деталей. + Цю дію неможливо відмінити - будуть видалені повідомлення, відправлені та отримані раніше вибраного часу. Це може зайняти декілька хвилин. + Вам потрібно вводити ключову фразу кожен раз при запуску додатка - вона не зберігається на пристрої. + Змінити ключову фразу бази даних? + Не вдається отримати доступ до сховища ключів для збереження пароля бази даних + Зберегти ключову фразу і відкрити чат + Ключова фраза не знайдена в сховищі ключів, будь ласка, введіть її вручну. Це може трапитися, якщо ви відновили дані додатка за допомогою інструменту резервного копіювання. Якщо це не так, зверніться до розробників. Видалити профіль чату %d сек Пошук - Вимкнено - Відправлення підтвердження доставлення вимкнено для контакту %d - Вимкнути (зберегти перевизначення) + Вимк. + Надсилання повідомлень про доставку вимкнено для %d контактів + Вимкнути (зберегти заміни) Увімкнути для всіх - шифрування ok + шифрування в порядку узгодження шифрування… узгодження шифрування для %s… - %s в %s - Для всіх контактів буде ввімкнено відправку підтвердження доставки. - Відправлення підтвердження доставлення буде ввімкнено для всіх контактів у всіх видимих профілях чату. - Ви можете увімкнути пізніше в Налаштуваннях - Не вмикати - Помилка активації підтвердження доставлення! - Помилка переривання зміни адреси - Скасувати зміну адреси - Дозволяє надсилати файли та медіа. - Файли та медіа в цій групі заборонені. - Підключайтеся інкогніто + %s о %s + Відправка квитанцій про доставку буде увімкнена для всіх контактів. + Відправка квитанцій про доставку буде увімкнена для всіх контактів у всіх видимих чат-профілях. + Ви можете увімкнути їх пізніше через налаштування + Не увімкнювати + Помилка увімкнення квитанцій про доставку! + Помилка відміни зміни адреси + Перервати зміну адреси + Дозволити надсилання файлів та медіафайлів. + Файли та медіафайли заборонені в цій групі. + Підключити інкогніто Використовувати поточний профіль - Дозвольте + Дозволити Вимкнути сповіщення - Відкрийте налаштування програми - SimpleX не може працювати у фоновому режимі. Ви будете отримувати сповіщення лише під час роботи програми. - Ніяких фонових дзвінків - Додаток можна закрити після 1 хвилини роботи у фоновому режимі. + Відкрити налаштування додатка + SimpleX не може працювати в фоновому режимі. Ви отримуватимете сповіщення лише тоді, коли програма працює. + Обмеженість фонових дзвінків + Додаток може бути закритий після 1 хвилини в фоновому режимі. У відповідь на Немає історії - Файли та медіа заборонені! - Ваш профіль %1$s буде опублікований. + Заборонено файли та медіа! + Буде відправлено ваш профіль %1$s. Вимкнути для всіх груп - НАДІШЛІТЬ ПІДТВЕРДЖЕННЯ ДОСТАВКИ НА АДРЕСУ - Підключатися напряму\? + НАДСИЛАТИ ПОВІДОМЛЕННЯ ПРО ДОСТАВКУ + Підключитися безпосередньо? %s: %s - Запит на підключення буде надіслано цьому учаснику групи. - Виправлення + Запит на підключення буде відправлено учаснику групи. + Виправити Файли та медіа Фільтруйте непрочитані та улюблені чати. Швидше знаходьте чати - Зробити так, щоб одне повідомлення зникло - Немає вибраного чату + Зникайте одне повідомлення + Не обрано жодного чату Скасувати Виберіть файл Контакти Ці налаштування стосуються вашого поточного профілю - Вимкнути підтвердження\? - Активувати підтвердження\? - переузгодження шифрування дозволено - узгоджене шифрування для %s - переузгодження шифрування дозволено для %s - потрібно переузгодження шифрування для %s - змінено код безпеки + Вимкнути повідомлення про доставку? + Увімкнути повідомлення про доставку? + можлива перезапис шифрування + шифрування узгоджено для %s + можлива перезапис шифрування для %s + перезапис шифрування обов\'язковий для %s + код безпеки змінено Виправлення не підтримується контактом - Надсилати звіти про доставку - Заборонити надсилання файлів і медіа. - Виправити шифрування після відновлення резервних копій. - Зберігайте свої зв\'язки - - стабільніше доставлення повідомлень. -\n- трохи кращі групи. -\n- і багато іншого! - Вже скоро! + Надсилати звіти + Заборонити надсилання файлів та медіафайлів. + Виправте шифрування після відновлення резервних копій. + Зберігайте ваші підключення + - стабільніша доставка повідомлень. +\n- трошки кращі групи. +\n- та багато іншого! + Скоро! Доставка - Немає інформації про доставлення + Немає інформації про доставку Немає фільтрованих чатів - Переузгодьте - Неулюблене + Узгодити повторно + Забрати з улюблених Вимкнути для всіх - Увімкнути (зберегти перевизначення) - Підтвердження надсилання ввімкнено для контакту %d - Невеликі групи (максимум 20 осіб) - Вимкнути підтвердження доставлення для груп\? - Активувати підтвердження доставлення для груп\? - Надсилання підтвердження доставлення дозволено для груп %d - Вимкнути (зберегти групові перевизначення) + Увімкнути (зберегти заміни) + Надсилання повідомлень про доставку увімкнено для %d контактів + Невеликі групи (макс. 20) + Вимкнути повідомлення про доставку для груп? + Увімкнути повідомлення про доставку для груп? + Надсилання повідомлень про доставку увімкнено для %d груп + Вимкнути (зберегти заміни для груп) Увімкнути для всіх груп - Увімкнути (зберегти перевизначення групи) - потрібне повторне узгодження шифрування - Підтвердження доставки вимкнено + Увімкнути (зберегти заміни для груп) + перезапис шифрування обов\'язковий + Звіти вимкнено вимкнено - У цій групі більше %1$d учасників, підтвердження доставлення не буде надіслано. - Ця функція поки що не підтримується. Спробуйте наступну версію. - Виправити з\'єднання - Виправити з\'єднання\? - Переузгодьте шифрування - Тайм-аут протоколу на КБ - узгоджено шифрування - %s і %s під\'єднано + У цій групі понад %1$d учасників, звіти про доставку не відправляються. + Ця функція ще не підтримується. Спробуйте наступний випуск. + Виправити підключення + Виправити підключення? + Переговорити щодо шифрування + Тайм-аут протоколу за КБ + шифрування узгоджено + %s і %s підключилися Виправлення не підтримується учасником групи - Підтвердження доставлення повідомлення! - Другу галочку ми пропустили! ✅ - Улюблений - Ще кілька моментів - Буде створено новий випадковий профіль. - Підтвердження доставлення! - Підтвердження доставлення вимкнено! + Підтвердження доставки повідомлень! + Друга галочка, яку ми пропустили! ✅ + Улюблені + Ще кілька речей + Буде відправлено новий випадковий профіль. + Квитанції про доставку! + Квитанції про доставку вимкнено! Увімкнути - шифрування ok для %s - Навіть коли вимкнений у розмові. - Чернетка повідомлення - Тільки власники груп можуть вмикати файли та медіа. - Вставте отримане посилання для зв\'язку з вашим контактом… - Відправлення підтвердження доставки вимкнено для груп %d - %s, %s і %s підключено - Їх можна перевизначити в налаштуваннях контактів і груп. - Використання акумулятора програми / Необмежено в налаштуваннях програми.]]> - Використання акумулятора програми / Без обмежень у налаштуваннях програми.]]> - Використовуйте новий профіль інкогніто - Ви можете увімкнути їх пізніше в налаштуваннях конфіденційності та безпеки програми. - %s, %s та %d інших учасників під\'єднано - Показати останні повідомлення + шифрування в порядку для %s + Навіть коли вимкнено в розмові. + Чорновик повідомлення + Тільки власники групи можуть включити файли та медіа. + Вставте посилання, яке ви отримали, щоб підключитися до свого контакту… + Надсилання повідомлень про доставку вимкнено для %d груп + %s, %s і %s підключилися + Їх можна замінити в налаштуваннях контактів і груп. + Використання батареї додатком / Без обмежень у налаштуваннях додатка.]]> + Використання батареї додатком / Без обмежень у налаштуваннях додатка.]]> + Використовувати новий інкогніто-профіль + Ви зможете увімкнути їх пізніше через налаштування конфіденційності та безпеки додатка. + %s, %s і ще %d учасників підключилися + Показувати останні повідомлення Помилка синхронізації з\'єднання - Скасувати зміну адреси\? - Зміна адреси буде скасована. Буде використано стару адресу отримання. - Переузгодьте шифрування\? - Шифрування працює і нова угода про шифрування не потрібна. Це може призвести до помилок з\'єднання! - Учасники групи можуть надсилати файли та медіа. - База даних буде зашифрована, а ключова фраза збережена в налаштуваннях. + Скасувати зміну адреси для отримання? + Зміна адреси буде скасована. Буде використовуватися стара адреса для отримання. + Повторно узгодити шифрування? + Шифрування працює і нова угода про шифрування не потрібна. Це може призвести до помилок підключення! + Учасники групи можуть надсилати файли та медіафайли. + База даних буде зашифрована, і ключова фраза буде збережена в налаштуваннях. Розгорнути Повторити запит на підключення? - Помилка повторного узгодження шифрування - видалений контакт - Ви вже з\'єднані з %1$s. + Помилка переговорів щодо шифрування + видалено контакт + Ви вже підключаєтеся до %1$s. Відкрити - Шифрування збережених файлів і носіїв + Шифрування збережених файлів та медіа Помилка - Помилка при створенні контакту користувача - Ви вже приєдналися до групи за цим посиланням. + Помилка при створенні контакту учасника + Ви вже приєднуєтеся до групи за цим посиланням. Створити групу - Зверніть увагу: ретранслятори повідомлень і файлів підключаються через проксі SOCKS. Дзвінки та надсилання попередніх переглядів посилань використовують пряме з’єднання.]]> + Зверніть увагу: ретрансляція повідомлень та файлів підключається через SOCKS-проксі. Дзвінки та відправлення переглядів посилань використовують пряме підключення.]]> Створити профіль - %s та %s - Приєднатися до групи? - Ви вже приєдналися до групи %1$s. - Шифрування локальних файлів - Це ваше власне одноразове посилання! - %d повідомлень позначено як видалені - Новий десктопний застосунок! + %s і %s + Приєднатися до вашої групи? + Ви вже приєднуєтеся до групи %1$s. + Шифрувати локальні файли + Це ваш власний одноразовий посилання! + %d повідомлень відзначено як видалені + Новий додаток для комп\'ютера! 6 нових мов інтерфейсу Група вже існує! - Застосунок шифрує нові локальні файли (крім відео). - Вже під\'єднуємося! - Випадкова фраза зберігається у налаштуваннях у вигляді відкритого тексту. + Додаток шифрує нові локальні файли (крім відео). + Вже підключено! + Випадковий пароль зберігається в налаштуваннях у відкритому вигляді. \nВи можете змінити його пізніше. - Надішліть пряме повідомлення, щоб підключитися - Відео не може бути декодовано. Будь ласка, спробуйте інше відео або зверніться до розробників. - %s підключено - ще інших подій - %d - Знаходьте та приєднуйтесь до груп - Підключитися за посиланням? - Пароль для шифрування бази даних буде оновлено і збережено в налаштуваннях. - Вже долучаємось до групи! - члени %s, %s та %d - %d повідомлень модерує %s - Видалити парольну фразу з налаштувань? - Розблокувати - Використовуйте випадкову парольну фразу + Відправте приватне повідомлення для підключення + Неможливо декодувати відео. Спробуйте інше відео або зв\'яжіться з розробниками. + %s підключився + і ще %d подій + Відкривайте та приєднуйтесь до груп + Підключитися за допомогою посилання? + Ключова фраза шифрування бази даних буде оновлена і збережена в налаштуваннях. + Вже приєднано до групи! + %s, %s і %d учасників + %d повідомлень модеровано %s + Вилучити ключову фразу із налаштувань? + Розблокувати учасника + Використовувати випадковий пароль Підключитися до себе? - Зберегти парольну фразу в налаштуваннях + Зберегти ключову фразу в налаштуваннях Спрощений режим інкогніто - Натисніть, щоб підключитися - Ключова фраза для налаштування бази даних + Торкніться, щоб підключитися + Налаштування паролю бази даних Ви вже в групі %1$s. - Це ваша власна SimpleX адреса! - Повторне узгодження шифрування не вдалося. + Це ваш власний адреса SimpleX! + Не вдалося виконати переговори щодо шифрування. Виправити ім\'я на %s? - Видалити %d повідомлень? + Видалити %d повідомлення? Підключитися до %1$s? - Видалити учасника - Встановити пароль до бази даних + Вилучити учасника + Встановити ключову фразу бази даних Заблокувати Розблокувати учасника? %d повідомлень заблоковано Заблокувати учасника - Відкрийте теку з базою даних + Відкрити папку бази даних Повторити запит на приєднання? - Видалити учасника? - Видалити та повідомити контакт - Арабська, Болгарська, Фінська, Іврит, Тайська та Українська – завдяки користувачам і Weblate. - Ви вже підключаєтеся за цим одноразовим посиланням! + Вилучити учасника? + Видалити та сповістити контакт + Арабська, болгарська, фінська, іврит, тайська та українська - завдяки користувачам і Weblate. + Ви вже підключаєтеся через це одноразове посилання! Відкрити групу - Будуть показані повідомлення від %s! - Створіть новий профіль у десктопному застосунку. 💻 - Помилка надсилання запрошення - Після зміни пароля або перезапуску програми він буде збережений у налаштуваннях у вигляді відкритого тексту. - Ви надали невірний шлях до файлу. Повідомте про проблему розробникам програми. + Повідомлення від %s будуть відображені! + Створіть новий профіль у додатку для комп\'ютера. 💻 + Помилка при відправці запрошення + Після зміни ключової фрази або перезапуску додатка ключова фраза буде збережена в налаштуваннях як звичайний текст. + Ви поділилися неправильним шляхом до файлу. Повідомте про цю проблему розробникам додатку. Заблокувати учасника? - %d групових подій - Неправильне ім\'я! - Увімкніть інкогніто при підключенні. + %d подій в групі + Невірне ім\'я! + Перемикайте інкогніто під час підключення. Це ваше посилання для групи %1$s! Розблокувати Неправильний шлях до файлу - - підключитися до служби каталогів (БЕТА)! + - підключайтесь до служби каталогів (BETA)! \n- квитанції про доставку (до 20 учасників). -\n- швидше і стабільніше. - Пароль зберігається у налаштуваннях у вигляді відкритого тексту. - Ви вже надсилали запит на підключення за цією адресою! - надіслати пряме повідомлення - Показати консоль у новому вікні - Всі повідомлення від %s будуть приховані - підключений безпосередньо +\n- швидше та надійніше. + Ключова фраза зберігається в налаштуваннях як звичайний текст. + Ви вже подали запит на підключення за цією адресою! + надіслати приватне повідомлення + Показувати консоль в новому вікні + Всі нові повідомлення від %s будуть приховані! + підключив(лась) безпосередньо заблоковано + Блокувати учасників групи + Створіть групу, використовуючи випадковий профіль. + Підключений робочий стіл + Новий мобільний пристрій + Підключати автоматично + Адреса робочого столу + Одночасно може працювати лише один пристрій + Посилання на мобільний та комп\'ютерний додатки! 🔗 + Через безпечний квантовостійкий протокол. + Використовувати з робочого столу у мобільному додатку і скануйте QR-код.]]> + Щоб приховати небажані повідомлення. + Несумісна версія + (новий)]]> + Відсунути відсилання до робочого столу? + Кращі групи + Параметри пов\'язаних робочих столів + Пов\'язані робочі столи + Виявити через локальну мережу + Інкогніто групи + Цей пристрій + %s був відключений]]> + Очікування робочого столу… + Швидше приєднуйтесь та надійшовні повідомлення. + Пов\'язані мобільні + Робочий стіл + Підключено до робочого столу + Назва цього пристрою + Завантаження файлу + Підключення до робочого столу + Знайдено робочий стіл + Пристрої робочого столу + Не сумісно! + Зв\'язати з мобільним + Використовувати зі стаціонарного комп\'ютера + Підключений мобільний + Код сеансу + Підключення завершено + (цей пристрій v%s)]]> + Відсунути відсилання + Назва пристрою буде надіслана підключеному мобільному клієнту. + Перевірте код на мобільному + Введіть назву цього пристрою… + Помилка + Підключитися до робочого столу + Відключити + автор + Підключено до мобільного + Некоректна адреса робочого столу + Вставити адресу робочого столу + Перевірити код з робочим столом + Сканувати QR-код з робочого столу + Пристрої + Виявлено через локальну мережу + - за бажанням повідомляйте про видалених контактів. +\n- імена профілю з пробілами. +\n- та інше! + Сканувати з мобільного + Перевірити підключення + Відключити робочий стіл? + Будь ласка, зачекайте, поки файл завантажується з підключеного мобільного + Версія робочого столу %s не сумісна з цим додатком. + Перевірити підключення \ No newline at end of file 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 c19a3960f7..7b8d8ec7eb 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 @@ -1521,4 +1521,15 @@ 找到了桌面 不兼容! 可通过本地网络发现 + 刷新 + 创建聊天资料 + 没有已连接的移动设备 + 断开移动设备连接 + 随机 + 要允许移动应用连接到桌面,请在防火墙中打开此端口,如果你已启用了它 + 在防火墙中打开端口 + 查看崩溃 + %d 条消息由 %s 主持 + 显示内容出错 + 显示消息出错 \ No newline at end of file From d637714963340967f2bc0b7eafab9814370723dd Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Thu, 28 Dec 2023 17:52:20 +0000 Subject: [PATCH 03/16] website: translations (#3617) * Translated using Weblate (Ukrainian) Currently translated at 100.0% (252 of 252 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/uk/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (252 of 252 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/uk/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (252 of 252 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/uk/ * Translated using Weblate (Spanish) Currently translated at 100.0% (252 of 252 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/es/ --------- Co-authored-by: Maksym Lukashenko Co-authored-by: No name --- website/langs/es.json | 6 +- website/langs/uk.json | 368 +++++++++++++++++++++--------------------- 2 files changed, 191 insertions(+), 183 deletions(-) diff --git a/website/langs/es.json b/website/langs/es.json index 7c418b7e25..9b116b4215 100644 --- a/website/langs/es.json +++ b/website/langs/es.json @@ -20,7 +20,7 @@ "hero-overlay-1-title": "¿Cómo funciona SimpleX?", "hero-overlay-2-title": "¿Por qué los ID de usuario son perjudiciales para la privacidad?", "feature-1-title": "Mensajes cifrados E2E con sintáxis markdown y edición", - "feature-3-title": "Grupos secretos descentralizados —
sólo los usuarios saben de su existencia", + "feature-3-title": "Grupos descentralizados cifrados E2E — sólo los usuarios conocen su existencia", "feature-6-title": "Llamadas y videollamadas con cifrado E2E", "simplex-network-overlay-1-title": "Comparativa con protocolos de mensajería P2P", "developers": "Desarrolladores", @@ -30,7 +30,7 @@ "simplex-explained-tab-3-p-1": "Para cada cola los servidores disponen de credenciales separadas y anónimas, por lo que desconocen a qué usuarios pertenecen.", "hero-p-1": "Las demás aplicaciones usan ID de usuario: Signal, Matrix, Session, Briar, Jami, Cwtch, etc.
SimpleX no los tiene, ni siquiera números aleatorios.
Esto mejora radicalmente su privacidad.", "hero-2-header-desc": "El video muestra cómo se conecta con sus amistades a través del código QR de un solo uso, en persona o a través de videollamada. También puede conectarse compartiendo un enlace de invitación.", - "feature-7-title": "Base de datos portable cifrada — transfiera su perfil a otro dispositivo", + "feature-7-title": "Almacenamiento portable y cifrado — podrá transferir su perfil a otro dispositivo", "simplex-private-card-4-point-2": "Para usar SimpleX a través de Tor, instala la aplicación Orbot y activa el proxy SOCKS5 (o VPN en iOS).", "simplex-private-card-3-point-1": "Para las conexiones cliente servidor se usan exclusivamente el protocolo TLS 1.2/1.3 con algoritmos robustos.", "simplex-private-card-4-point-1": "Para proteger tu dirección IP puedes acceder a los servidores a través de la red Tor u otras redes de transporte superpuesto.", @@ -48,7 +48,7 @@ "chat-protocol": "Protocolo Chat", "terminal-cli": "Terminal CLI", "hero-subheader": "La primera aplicación de mensajería
sin ID de usuario", - "feature-2-title": "Cifrado E2E
de imágenes y archivos", + "feature-2-title": "Cifrado E2E
de imágenes, vídeos y archivos", "feature-8-title": "Modo incógnito —
exclusivo de SimpleX Chat", "simplex-private-1-title": "Doble capa de
cifrado de extremo a extremo", "simplex-private-2-title": "Capa de cifrado
adicional en el servidor", diff --git a/website/langs/uk.json b/website/langs/uk.json index 6a48cc5640..e71b7cef8d 100644 --- a/website/langs/uk.json +++ b/website/langs/uk.json @@ -2,245 +2,253 @@ "features": "Особливості", "simplex-explained-tab-3-text": "3. Що бачать сервери", "terms-and-privacy-policy": "Умови та політика конфіденційності", - "feature-4-title": "E2E-зашифровані голосові повідомлення", - "feature-5-title": "Зникаючі повідомлення", - "simplex-private-card-3-point-3": "Відновлення підключення вимкнено для запобігання атакам сеансів.", - "simplex-private-card-7-point-1": "Щоб гарантувати цілісність, повідомлення послідовно нумеруються і включають хеш попереднього повідомлення.", - "simplex-private-card-10-point-2": "Він дозволяє доставляти повідомлення без ідентифікаторів профілю користувача, забезпечуючи кращу конфіденційність метаданих, ніж альтернативи.", - "privacy-matters-2-overlay-1-linkText": "Конфіденційність дає вам силу", - "hero-overlay-card-1-p-5": "Тільки клієнтські пристрої зберігають профілі користувачів, контакти та групи; повідомлення надсилаються з двошаровим наскрізним шифруванням.", - "hero-overlay-card-2-p-1": "Коли користувачі мають постійні ідентифікатори, навіть якщо це просто випадкове число, наприклад, ідентифікатор сеансу, існує ризик, що провайдер або зловмисник може спостерігати, як користувачі підключаються і скільки повідомлень вони надсилають.", - "simplex-network-overlay-card-1-li-5": "Всі відомі P2P-мережі можуть бути вразливими до атаки Sybil, оскільки кожен вузол можна виявити, а мережа працює як єдине ціле. Відомі заходи для її пом'якшення вимагають або централізованого компоненту, або дорогого доказу роботи. Мережа SimpleX не має можливості виявлення серверів, вона фрагментована і працює як кілька ізольованих підмереж, що унеможливлює атаки на всю мережу.", - "simplex-network-overlay-card-1-li-6": "P2P-мережі можуть бути вразливими до DRDoS-атаки, коли клієнти можуть повторно транслювати та збільшувати трафік, що призводить до відмови в обслуговуванні всієї мережі. Клієнти SimpleX лише ретранслюють трафік із відомого з’єднання, і зловмисники не можуть використовувати їх для посилення трафіку в усій мережі.", - "privacy-matters-overlay-card-1-p-2": "Інтернет-магазини знають, що люди з нижчими доходами більш схильні до термінових покупок, тому вони можуть встановлювати вищі ціни або скасовувати знижки.", - "privacy-matters-overlay-card-1-p-3": "Деякі фінансові та страхові компанії використовують соціальні графіки для визначення процентних ставок і премій. Це часто змушує людей з нижчими доходами платити більше —, він відомий як ' премія за бідність'.", - "privacy-matters-overlay-card-1-p-4": "Платформа SimpleX захищає конфіденційність ваших з'єднань краще, ніж будь-яка інша альтернатива, повністю запобігаючи тому, щоб ваш соціальний графік став доступним будь-яким компаніям або організаціям. Навіть коли люди використовують сервери, надані SimpleX Chat, ми не знаємо кількість користувачів та їхніх з'єднань.", - "privacy-matters-overlay-card-2-p-1": "Не так давно ми стали свідками маніпуляцій на великих виборах з боку авторитетної консалтингової компанії, яка використовувала наші соціальні графіки, щоб спотворити наше уявлення про реальний світ і маніпулювати нашими голосами.", - "privacy-matters-overlay-card-2-p-2": "Щоб бути об'єктивним і приймати незалежні рішення, необхідно контролювати свій інформаційний простір. Це можливо лише за умови використання приватної комунікаційної платформи, яка не має доступу до вашого соціального профілю.", - "privacy-matters-overlay-card-2-p-3": "SimpleX - це перша платформа, яка не має жодних ідентифікаторів користувачів, таким чином захищаючи ваш граф зв'язків краще, ніж будь-яка інша відома альтернатива.", - "privacy-matters-overlay-card-3-p-1": "Кожен має дбати про конфіденційність і безпеку своїх комунікацій — нешкідливі розмови можуть наражати вас на небезпеку, навіть якщо вам нема чого приховувати.", - "privacy-matters-overlay-card-3-p-2": "Однією з найбільш шокуючих історій є досвід Мохамеду Ульд Салахі, описаний у його мемуарах та показаний у фільмі \"Мавританець\". Його без суду і слідства помістили в табір Гуантанамо і катували там протягом 15 років після телефонного дзвінка родичу в Афганістані за підозрою в причетності до терактів 11 вересня, хоча він жив у Німеччині протягом останніх 10 років.", - "privacy-matters-overlay-card-3-p-4": "Недостатньо використовувати наскрізний зашифрований месенджер, ми всі повинні використовувати месенджери, які захищають конфіденційність наших особистих мереж — тих, з ким ми пов'язані.", - "simplex-unique-overlay-card-1-p-1": "На відміну від інших платформ обміну повідомленнями, SimpleX не має ніяких ідентифікаторів, призначених користувачам. Він не покладається на телефонні номери, доменні адреси (наприклад, електронну пошту або XMPP), імена користувачів, відкриті ключі або навіть випадкові числа для ідентифікації своїх користувачів — ми не ' знаємо, скільки людей користуються нашими серверами SimpleX.", - "simplex-unique-overlay-card-3-p-3": "На відміну від серверів об'єднаних мереж (електронна пошта, XMPP або Matrix), сервери SimpleX не зберігають облікові записи користувачів, вони лише пересилають повідомлення, захищаючи конфіденційність обох сторін.", - "simplex-unique-card-3-p-2": "Наскрізні зашифровані повідомлення тимчасово зберігаються на релейних серверах SimpleX до моменту отримання, після чого вони назавжди видаляються.", - "simplex-unique-card-4-p-1": "Мережа SimpleX повністю децентралізована і не залежить від будь-якої криптовалюти або будь-якої іншої платформи, окрім Інтернету.", - "simplex-network-overlay-card-1-li-3": "P2P не вирішує атаки MITM проблема, і більшість існуючих реалізацій не використовують позасмугові повідомлення для початкового обміну ключами. SimpleX використовує позасмугові повідомлення або, в деяких випадках, уже існуючі безпечні та надійні з’єднання для початкового обміну ключами.", - "simplex-network-overlay-card-1-li-4": "Реалізації P2P можуть бути заблоковані деякими інтернет-провайдерами (наприклад, BitTorrent). SimpleX є транспортно-агностичним - він може працювати через стандартні веб-протоколи, наприклад, WebSockets.", - "simplex-unique-card-4-p-2": "Ви можете використовувати SimpleX з власними серверами або з серверами, наданими нами — і при цьому підключатися до будь-якого користувача.", - "contact-hero-p-1": "Відкриті ключі та адреса черги повідомлень у цьому посиланні НЕ передаються мережею при перегляді цієї сторінки — вони містяться в хеш-фрагменті URL-адреси посилання.", - "scan-qr-code-from-mobile-app": "Відскануйте QR-код з мобільного додатку", - "to-make-a-connection": "Щоб встановити зв'язок:", - "install-simplex-app": "Встановіть додаток SimpleX", + "feature-4-title": "Голосові повідомлення з шифруванням від кінця до кінця", + "feature-5-title": "Зникнення повідомлень", + "simplex-private-card-3-point-3": "Відновлення з'єднання вимкнено для запобігання атакам на сесію.", + "simplex-private-card-7-point-1": "Для гарантії цілісності повідомлення пронумеровані послідовно та містять хеш попереднього повідомлення.", + "simplex-private-card-10-point-2": "Це дозволяє доставляти повідомлення без ідентифікаторів профілю користувача та забезпечує кращу конфіденційність метаданих, ніж альтернативи.", + "privacy-matters-2-overlay-1-linkText": "Конфіденційність дає вам владу", + "hero-overlay-card-1-p-5": "Тільки пристрої клієнта зберігають профілі користувачів, контакти та групи; повідомлення відправляються із шифруванням від кінця до кінця з двома шарами.", + "hero-overlay-card-2-p-1": "Коли у користувачів є постійні ідентифікатори, навіть якщо це просто випадковий номер, як, наприклад, ідентифікатор сеансу, існує ризик того, що провайдер чи зловмисник може спостерігати, як користувачі підключені та скільки повідомлень вони відправляють.", + "simplex-network-overlay-card-1-li-5": "Всі відомі P2P-мережі можуть бути вразливими до атаки Sybil, оскільки кожен вузол є відкритим, і мережа працює як ціле. Відомі заходи для пом'якшення цієї атаки вимагають або централізованого компонента, або дорогого доказу роботи. Мережа SimpleX не має виявності сервера, вона роздроблена і працює як кілька ізольованих підмереж, що робить неможливими атаки на всю мережу.", + "simplex-network-overlay-card-1-li-6": "P2P-мережі можуть бути вразливими до атаки DRDoS, коли клієнти можуть ребродкастити та збільшувати трафік, що призводить до відмови в обслуговуванні на рівні всієї мережі. Клієнти SimpleX лише пересилають трафік від відомого підключення і не можуть бути використані зловмисником для збільшення трафіку в усій мережі.", + "privacy-matters-overlay-card-1-p-2": "Інтернет-роздрібники знають, що люди з низьким доходом частіше роблять термінові покупки, тому вони можуть встановлювати вищі ціни або скасовувати знижки.", + "privacy-matters-overlay-card-1-p-3": "Деякі фінансові та страхові компанії використовують соціальні графи для визначення ставок та страхових премій. Це часто змушує людей з низькими доходами платити більше — це відомо як 'поширений преміум'.", + "privacy-matters-overlay-card-1-p-4": "Платформа SimpleX захищає конфіденційність ваших з'єднань краще, ніж будь-яка альтернатива, повністю запобігаючи доступу вашого соціального графа будь-яким компаніям чи організаціям. Навіть коли люди використовують сервери, надані SimpleX Chat, ми не знаємо кількість користувачів чи їх зв'язки.", + "privacy-matters-overlay-card-2-p-1": "Не так давно ми спостерігали, як великі вибори маніпулювалися поважною консалтинговою компанією, яка використовувала наші соціальні графи для спотворення нашого уявлення про реальний світ та маніпулювання нашими голосами.", + "privacy-matters-overlay-card-2-p-2": "Щоб бути об'єктивним та приймати незалежні рішення, вам потрібно контролювати ваш інформаційний простір. Це можливо лише за умови використання приватної платформи для спілкування, яка не має доступу до вашого соціального графа.", + "privacy-matters-overlay-card-2-p-3": "SimpleX - це перша платформа, яка не має жодних ідентифікаторів користувачів за своїм дизайном, таким чином, захищаючи ваш графік з'єднань краще, ніж будь-яка відома альтернатива.", + "privacy-matters-overlay-card-3-p-1": "Кожен повинен турбуватися про конфіденційність та безпеку свого спілкування — безпечні розмови можуть поставити вас під загрозу, навіть якщо у вас немає чого приховувати.", + "privacy-matters-overlay-card-3-p-2": "Однією з найшокуючих історій є досвід Мохамеду Ульд Слахі, описаний в його мемуарах і показаний у фільмі \"Мавританець\". Його посадили в табір Гуантанамо без суду і мукили там 15 років після телефонного дзвінка йому родичеві в Афганістані, під підозрою в причетності до атак 11 вересня, навіть не дивлячись на те, що він проживав у Німеччині протягом попередніх 10 років.", + "privacy-matters-overlay-card-3-p-4": "Недостатньо використовувати зашифрований від кінця до кінця месенджер; ми всі повинні використовувати месенджери, які захищають конфіденційність наших особистих мереж — з ким ми з'єднані.", + "simplex-unique-overlay-card-1-p-1": "На відміну від інших платформ обміну повідомленнями, у SimpleX не присвоюються ідентифікатори користувачів. Він не покладається на номери телефонів, адреси на основі домену (наприклад, електронна пошта або XMPP), імена користувачів, публічні ключі або навіть випадкові номери для ідентифікації своїх користувачів — ми не знаємо, скільки людей використовує наші сервери SimpleX.", + "simplex-unique-overlay-card-3-p-3": "На відміну від федеративних мереж серверів (електронна пошта, XMPP або Matrix), сервери SimpleX не зберігають облікові записи користувачів, вони лише ретранслюють повідомлення, захищаючи приватність обох сторін.", + "simplex-unique-card-3-p-2": "Зашифровані повідомлення від кінця до кінця тимчасово зберігаються на ретрансляційних серверах SimpleX до їх отримання, після чого вони назавжди видаляються.", + "simplex-unique-card-4-p-1": "Мережа SimpleX є повністю децентралізованою та незалежною від будь-якої криптовалюти чи іншої платформи, крім Інтернету.", + "simplex-network-overlay-card-1-li-3": "P2P не вирішує проблему атаки MITM, і більшість існуючих реалізацій не використовують поза каналом повідомлень для початкового обміну ключами. SimpleX використовує поза каналом повідомлень або, у деяких випадках, передбачені і безпечні з'єднання для початкового обміну ключами.", + "simplex-network-overlay-card-1-li-4": "Реалізації P2P можуть бути блоковані деякими інтернет-провайдерами (наприклад, BitTorrent). SimpleX є транспортно-агностичним - він може працювати через стандартні веб-протоколи, наприклад, WebSockets.", + "simplex-unique-card-4-p-2": "Ви можете використовувати SimpleX з власними серверами або з серверами, які ми надаємо — і все одно підключатися до будь-якого користувача.", + "contact-hero-p-1": "Публічні ключі та адреса черги повідомлень в цьому посиланні НЕ відправляються по мережі під час перегляду цієї сторінки — вони містяться в хеш-фрагменті URL-посилання.", + "scan-qr-code-from-mobile-app": "Сканувати QR-код з мобільного додатка", + "to-make-a-connection": "Щоб здійснити підключення:", + "install-simplex-app": "Встановити додаток SimpleX", "github-repository": "Репозиторій GitHub", - "no-federated": "Ні - федеративний", - "comparison-section-list-point-1": "Зазвичай на основі номера телефону, в деяких випадках на основі імені користувача", - "comparison-section-list-point-2": "Адреси на основі DNS", - "comparison-section-list-point-3": "Відкритий ключ або інший глобально унікальний ідентифікатор", - "comparison-section-list-point-7": "Мережі P2P або мають центральний орган, або вся мережа може бути скомпрометована", + "no-federated": "Ні - федеративно", + "comparison-section-list-point-1": "Зазвичай базується на номері телефону, у деяких випадках на іменах користувачів", + "comparison-section-list-point-2": "Адреси, засновані на DNS", + "comparison-section-list-point-3": "Відкритий ключ чи який-небудь інший глобально унікальний ідентифікатор", + "comparison-section-list-point-7": "Мережі P2P мають або центральний орган управління, або всю мережу можна порушити", "see-here": "дивіться тут", "guide-dropdown-4": "Профілі чату", - "guide-dropdown-5": "Керування даними", - "guide-dropdown-6": "Аудіо і відео дзвінки", - "guide-dropdown-7": "Конфіденційність і безпека", - "guide-dropdown-8": "Налаштування програми", - "guide-dropdown-9": "Встановлення зв'язків", - "guide": "Путівник", + "guide-dropdown-5": "Управління даними", + "guide-dropdown-6": "Аудіо та відеовиклики", + "guide-dropdown-7": "Конфіденційність та безпека", + "guide-dropdown-8": "Налаштування додатка", + "guide-dropdown-9": "Створення підключень", + "guide": "Посібник", "docs-dropdown-1": "Платформа SimpleX", "docs-dropdown-2": "Доступ до файлів Android", "docs-dropdown-3": "Доступ до бази даних чату", - "docs-dropdown-4": "Хост SMP-сервер", - "docs-dropdown-5": "Хост XFTP-сервер", + "docs-dropdown-4": "Хостинг сервера SMP", + "docs-dropdown-5": "Хостинг сервера XFTP", "docs-dropdown-6": "Сервери WebRTC", - "docs-dropdown-7": "Перекласти чат SimpleX", - "newer-version-of-eng-msg": "Існує новіша версія цієї сторінки англійською мовою.", + "docs-dropdown-7": "Переклад SimpleX Chat", + "newer-version-of-eng-msg": "Є нова версія цієї сторінки англійською мовою.", "click-to-see": "Натисніть, щоб побачити", "menu": "Меню", "on-this-page": "На цій сторінці", - "back-to-top": "Повернутися до початку", + "back-to-top": "Повернутися наверх", "home": "Головна", "developers": "Розробники", "reference": "Посилання", "blog": "Блог", "why-simplex": "Чому SimpleX", - "simplex-privacy": "Конфіденційність SimpleX", + "simplex-privacy": "Приватність у SimpleX", "simplex-network": "Мережа SimpleX", - "simplex-explained": "Simplex пояснення", + "simplex-explained": "Пояснення SimpleX", "simplex-explained-tab-1-text": "1. Що відчувають користувачі", "simplex-explained-tab-2-text": "2. Як це працює", - "simplex-explained-tab-1-p-1": "Ви можете створювати контакти та групи, а також вести двосторонні розмови, як і в будь-якому іншому месенджері.", - "simplex-explained-tab-1-p-2": "Як він може працювати з односпрямованими чергами та без ідентифікаторів профілю користувача?", - "simplex-explained-tab-2-p-1": "Для кожного підключення використовуються дві окремі черги обміну повідомленнями для надсилання та отримання повідомлень через різні сервери.", - "simplex-explained-tab-2-p-2": "Сервери передають повідомлення лише в один бік, не маючи повної картини розмови або з'єднань користувача.", - "simplex-explained-tab-3-p-1": "Сервери мають окремі анонімні облікові дані для кожної черги і не знають, яким користувачам вони належать.", - "simplex-explained-tab-3-p-2": "Користувачі можуть ще більше підвищити конфіденційність метаданих, використовуючи Tor для доступу до серверів, запобігаючи кореляції за IP-адресою.", + "simplex-explained-tab-1-p-1": "Ви можете створювати контакти та групи і вести двосторонні розмови, як у будь-якому іншому месенджері.", + "simplex-explained-tab-1-p-2": "Як це може працювати з однобічними чергами та без ідентифікаторів профілю користувача?", + "simplex-explained-tab-2-p-1": "Для кожного з'єднання ви використовуєте дві окремі черги обміну повідомленнями для відправки та отримання повідомлень через різні сервери.", + "simplex-explained-tab-2-p-2": "Сервери передають повідомлення тільки в одному напрямку, не маючи повної картини розмови або підключень користувача.", + "simplex-explained-tab-3-p-1": "Сервери мають окремі анонімні облікові дані для кожної черги і не знають, які користувачі до них відносяться.", + "simplex-explained-tab-3-p-2": "Користувачі можуть додатково підвищити конфіденційність метаданих, використовуючи Tor для доступу до серверів, що запобігає кореляції за IP-адресою.", "chat-bot-example": "Приклад чат-бота", - "smp-protocol": "SMP протокол", + "smp-protocol": "Протокол SMP", "chat-protocol": "Протокол чату", - "donate": "Пожертвуйте", - "copyright-label": "© 2020-2023 SimpleX | Проект з відкритим вихідним кодом", + "donate": "Пожертвувати", + "copyright-label": "© 2020-2023 SimpleX | Проект з відкритим кодом", "simplex-chat-protocol": "Протокол чату SimpleX", "terminal-cli": "Термінал CLI", - "hero-header": "Конфіденційність переглянута", + "hero-header": "Приватність переосмислена", "hero-subheader": "Перший месенджер
без ідентифікаторів користувачів", - "hero-p-1": "Інші додатки мають ідентифікатори користувачів: Signal, Matrix, Session, Briar, Jami, Cwtch тощо.
SimpleX цього не робить, навіть випадкових чисел.
Це радикально покращує вашу конфіденційність.", - "hero-overlay-1-textlink": "Чому ідентифікатори користувачів шкодять конфіденційності?", + "hero-p-1": "У інших додатках є ідентифікатори користувачів: Signal, Matrix, Session, Briar, Jami, Cwtch, тощо.
У SimpleX немає жодних, навіть випадкових номерів.
Це радикально покращує вашу конфіденційність.", + "hero-overlay-1-textlink": "Чому ідентифікатори користувачів шкідливі для конфіденційності?", "hero-overlay-2-textlink": "Як працює SimpleX?", "hero-2-header": "Створіть приватне з'єднання", - "hero-2-header-desc": "У відео показано, як з'єднатися з другом за допомогою одноразового QR-коду, особисто або за допомогою відеозв'язку. Ви також можете підключитися, поділившись посиланням-запрошенням.", + "hero-2-header-desc": "У відео показано, як ви можете підключитися до свого друга за його одноразовим QR-кодом, особисто або через відеозв'язок. Ви також можете підключитися, поділившись посиланням на запрошення.", "hero-overlay-1-title": "Як працює SimpleX?", - "hero-overlay-2-title": "Чому ідентифікатори користувачів шкодять конфіденційності?", - "feature-1-title": "E2E-зашифровані повідомлення з розміткою і редагуванням", - "feature-2-title": "E2E-зашифровані
зображення та файли", - "feature-3-title": "Децентралізовані секретні групи —
лише користувачі знають про їх існування", - "feature-6-title": "E2E-шифрується
audio і відеодзвінки", - "feature-7-title": "Портативна зашифрована база даних — перенесіть свій профіль на інший пристрій", + "hero-overlay-2-title": "Чому ідентифікатори користувачів шкідливі для конфіденційності?", + "feature-1-title": "Повідомлення з шифруванням від кінця до кінця з можливістю використання Markdown та редагування", + "feature-2-title": "Зображення, відео та файли з шифруванням від кінця до кінця", + "feature-3-title": "Децентралізовані групи з шифруванням від кінця до кінця — тільки користувачі знають про їх існування", + "feature-6-title": "Дзвінки з шифруванням від кінця до кінця для аудіо та відео", + "feature-7-title": "Переносне зашифроване зберігання додатку — переміщуйте профіль на інший пристрій", "feature-8-title": "Режим інкогніто —
унікальний для SimpleX Chat", - "simplex-network-overlay-1-title": "Порівняння з протоколами обміну повідомленнями P2P", - "simplex-private-1-title": "2-рівневе наскрізне шифрування", + "simplex-network-overlay-1-title": "Порівняння з протоколами P2P-повідомлень", + "simplex-private-1-title": "2 рівні шифрування від кінця до кінця", "simplex-private-2-title": "Додатковий рівень
шифрування сервера", - "simplex-private-3-title": "Безпечне автентифіковане
транспортування TLS", - "simplex-private-4-title": "Додатковий
доступ через Tor", - "simplex-private-5-title": "Кілька шарів
заповнення вмісту", - "simplex-private-6-title": "Позадіапазонний
обмін ключами", + "simplex-private-3-title": "Безпечний аутентифікований
транспорт TLS", + "simplex-private-4-title": "Опційний
доступ через Tor", + "simplex-private-5-title": "Кілька рівнів
наповнення вмісту", + "simplex-private-6-title": "Позаканальний
обмін ключами", "simplex-private-7-title": "Перевірка
цілісності повідомлення", - "simplex-private-9-title": "Односпрямовані
черги повідомлень", + "simplex-private-9-title": "Однобічні
черги повідомлень", "simplex-private-8-title": "Змішування повідомлень
для зменшення кореляції", "simplex-private-10-title": "Тимчасові анонімні парні ідентифікатори", - "simplex-private-card-1-point-1": "Протокол double-ratchet —
OTR messaging з ідеальною конфіденційністю та відновленням зламів.", - "simplex-private-card-1-point-2": "Криптобокс NaCL у кожній черзі для запобігання кореляції трафіку між чергами повідомлень, якщо TLS скомпрометовано.", - "simplex-private-card-2-point-1": "Додатковий рівень серверного шифрування для доставки одержувачу, щоб запобігти кореляції між отриманим і відправленим серверним трафіком, якщо TLS скомпрометовано.", - "simplex-private-card-3-point-1": "Для клієнт-серверних з'єднань використовується тільки TLS 1.2/1.3 з сильними алгоритмами.", - "simplex-private-card-3-point-2": "Відбитки пальців сервера та прив'язка каналів запобігають MITM-атакам та повторному відтворенню.", - "simplex-private-card-4-point-1": "Щоб захистити свою IP-адресу, ви можете отримати доступ до серверів через Tor або іншу транспортну мережу.", - "simplex-private-card-4-point-2": "Щоб використовувати SimpleX через Tor, установіть програму Orbot і ввімкніть проксі-сервер SOCKS5 (або VPN на iOS).", - "simplex-private-card-5-point-1": "SimpleX використовує заповнення вмісту для кожного рівня шифрування, щоб запобігти атакам на розмір повідомлення.", - "simplex-private-card-5-point-2": "Це дозволяє повідомленням різного розміру виглядати однаково для серверів і мережевих спостерігачів.", - "simplex-private-card-6-point-1": "Багато комунікаційних платформ вразливі до MITM-атак з боку серверів або мережевих провайдерів.", - "simplex-private-card-6-point-2": "Щоб запобігти цьому, програми SimpleX передають одноразові ключі поза смугою, коли ви надаєте адресу як посилання або QR-код.", - "simplex-private-card-7-point-2": "Якщо будь-яке повідомлення буде додано, видалено або змінено, одержувач отримає сповіщення.", - "simplex-private-card-8-point-1": "Сервери SimpleX працюють як вузли змішування з низькою затримкою - вхідні та вихідні повідомлення йдуть в різному порядку.", - "simplex-private-card-9-point-1": "Кожна черга повідомлень передає повідомлення в одному напрямку, з різними адресами відправлення та отримання.", - "simplex-private-card-9-point-2": "У порівнянні з традиційними брокерами повідомлень, він зменшує кількість векторів атак і доступних мета-даних.", - "simplex-private-card-10-point-1": "SimpleX використовує тимчасові анонімні попарні адреси та облікові дані для кожного контакту користувача або члена групи.", - "privacy-matters-1-title": "Реклама та цінова дискримінація", - "privacy-matters-1-overlay-1-title": "Конфіденційність економить ваші гроші", - "privacy-matters-1-overlay-1-linkText": "Конфіденційність економить ваші гроші", + "simplex-private-card-1-point-1": "Протокол подвійної рейки — OTR-повідомлення з ідеальною передачею секрету та відновленням злому.", + "simplex-private-card-1-point-2": "NaCL криптокоробка в кожній черзі для запобігання кореляції трафіку між чергами повідомлень у разі компрометації TLS.", + "simplex-private-card-2-point-1": "Додатковий рівень шифрування сервера для доставки отримувачу для запобігання кореляції між отриманим та відісланим трафіком сервера у разі компрометації TLS.", + "simplex-private-card-3-point-1": "Використовується лише TLS 1.2/1.3 із сильними алгоритмами для з'єднань клієнт-сервер.", + "simplex-private-card-3-point-2": "Відбиток сервера та зв'язування каналу запобігають MITM та атакам повтору.", + "simplex-private-card-4-point-1": "Для захисту вашої IP-адреси ви можете отримати доступ до серверів через Tor чи іншу транспортну оверлейну мережу.", + "simplex-private-card-4-point-2": "Для використання SimpleX через Tor, будь ласка, встановіть додаток Orbot та активуйте SOCKS5-проксі (або VPN на iOS).", + "simplex-private-card-5-point-1": "SimpleX використовує наповнення вмісту для кожного шару шифрування для ускладнення атак за розміром повідомлень.", + "simplex-private-card-5-point-2": "Це забезпечує, що повідомлення різних розмірів виглядають однаково для серверів та спостерігачів мережі.", + "simplex-private-card-6-point-1": "Багато комунікаційних платформ вразливі до MITM-атак серверів чи постачальників мережі.", + "simplex-private-card-6-point-2": "Щоб запобігти цьому, програми SimpleX передають одноразові ключі позаканально, коли ви ділитесь адресою як посиланням або QR-кодом.", + "simplex-private-card-7-point-2": "Якщо будь-яке повідомлення додається, вилучається чи змінюється, отримувач буде проінформований.", + "simplex-private-card-8-point-1": "Сервери SimpleX виступають як вузли низької затримки для змішування — вхідні та вихідні повідомлення мають різний порядок.", + "simplex-private-card-9-point-1": "Кожна черга повідомлень передає повідомлення в одному напрямку, з різними адресами для відправки та отримання.", + "simplex-private-card-9-point-2": "Це зменшує вектори атак порівняно із традиційними брокерами повідомлень та доступними метаданими.", + "simplex-private-card-10-point-1": "SimpleX використовує тимчасові анонімні парні адреси та облікові дані для кожного користувача, контакту чи учасника групи.", + "privacy-matters-1-title": "Реклама та дискримінація ціни", + "privacy-matters-1-overlay-1-title": "Конфіденційність зекономить вам гроші", + "privacy-matters-1-overlay-1-linkText": "Конфіденційність зекономить вам гроші", "privacy-matters-2-title": "Маніпулювання виборами", - "privacy-matters-2-overlay-1-title": "Конфіденційність дає вам силу", - "privacy-matters-3-title": "Переслідування через невинну асоціацію", + "privacy-matters-2-overlay-1-title": "Конфіденційність дає вам владу", + "privacy-matters-3-title": "Переслідування за невинним зв'язком", "privacy-matters-3-overlay-1-title": "Конфіденційність захищає вашу свободу", "privacy-matters-3-overlay-1-linkText": "Конфіденційність захищає вашу свободу", "simplex-unique-1-title": "Ви маєте повну конфіденційність", - "simplex-unique-1-overlay-1-title": "Повна конфіденційність вашої особи, профілю, контактів і метаданих", - "simplex-unique-2-title": "Ви захищені
від спаму та зловживань", + "simplex-unique-1-overlay-1-title": "Повна конфіденційність вашої ідентичності, профілю, контактів та метаданих", + "simplex-unique-2-title": "Вас захищено
від спаму та зловживань", "simplex-unique-2-overlay-1-title": "Найкращий захист від спаму та зловживань", "simplex-unique-3-title": "Ви контролюєте свої дані", - "simplex-unique-3-overlay-1-title": "Право власності, контроль та безпека ваших даних", - "simplex-unique-4-title": "Ви володієте мережею SimpleX", + "simplex-unique-3-overlay-1-title": "Власність, контроль та безпека ваших даних", + "simplex-unique-4-title": "Ви власник мережі SimpleX", "simplex-unique-4-overlay-1-title": "Повністю децентралізована — користувачі володіють мережею SimpleX", - "hero-overlay-card-1-p-1": "Багато користувачів запитували: якщо SimpleX не має ідентифікаторів користувачів, як він може знати, куди доставляти повідомлення?", - "hero-overlay-card-1-p-2": "Для доставки повідомлень замість ідентифікаторів користувачів, які використовуються всіма іншими платформами, SimpleX використовує тимчасові анонімні парні ідентифікатори черг повідомлень, окремі для кожного з ваших з'єднань — немає ніяких довгострокових ідентифікаторів.", - "hero-overlay-card-1-p-3": "Ви визначаєте, який сервер(и) використовувати для отримання повідомлень, ваші контакти — сервери, які ви використовуєте для надсилання їм повідомлень. У кожній розмові, швидше за все, будуть використовуватися два різних сервери.", - "hero-overlay-card-1-p-4": "Такий дизайн запобігає витоку будь-яких метаданих користувачів на рівні програми. Щоб ще більше підвищити конфіденційність і захистити свою IP-адресу, ви можете підключитися до серверів обміну повідомленнями через Tor.", - "hero-overlay-card-1-p-6": "Читайте більше в Пропонуємо ознайомитися з технічним документом SimpleX.", - "hero-overlay-card-2-p-2": "Потім вони могли співвіднести цю інформацію з існуючими публічними соціальними мережами і встановити деякі реальні особи.", - "hero-overlay-card-2-p-3": "Навіть у найбільш приватних додатках, які використовують сервіси Tor v3, якщо ви розмовляєте з двома різними контактами через один і той самий профіль, вони можуть довести, що пов'язані з однією і тією ж людиною.", - "hero-overlay-card-2-p-4": "SimpleX захищає від цих атак, не використовуючи жодних ідентифікаторів користувачів. А якщо ви використовуєте режим \"Інкогніто\", то для кожного контакту ви будете мати окреме ім'я для відображення, що дозволить уникнути обміну даними між ними.", - "simplex-network-overlay-card-1-p-1": "Протоколи та програми обміну повідомленнями P2P мають різні проблеми, які роблять їх менш надійними, ніж SimpleX, складнішими для аналізу та вразливими до кількох типів атак.", - "simplex-network-overlay-card-1-li-1": "Мережі P2P покладаються на певний варіант DHT для маршрутизації повідомлень. Дизайн DHT має балансувати між гарантією доставки та затримкою. SimpleX має кращу гарантію доставки і меншу затримку, ніж P2P, тому що повідомлення може бути передано через кілька серверів паралельно, використовуючи сервери, обрані одержувачем. У P2P-мережах повідомлення передається через O(log N) вузлів послідовно, використовуючи вузли, обрані алгоритмом.", - "simplex-network-overlay-card-1-li-2": "Дизайн SimpleX, на відміну від більшості мереж P2P, не має жодних глобальних ідентифікаторів користувачів, навіть тимчасових, а використовує тимчасові ідентифікатори лише парами, забезпечуючи кращу анонімність і захист метаданих.", - "privacy-matters-overlay-card-1-p-1": "Багато великих компаній використовують інформацію про те, з ким ви пов'язані, щоб оцінити ваш дохід, продати вам товари, які вам насправді не потрібні, і визначити ціни.", - "privacy-matters-overlay-card-3-p-3": "Звичайних людей заарештовують за те, чим вони діляться в Інтернеті, навіть через свої 'анонімні' облікові записи, навіть у демократичних країнах.", - "simplex-unique-overlay-card-1-p-2": "Для доставки повідомлень SimpleX використовує парні анонімні адреси односпрямованих черг повідомлень, окремо для отриманих і відправлених повідомлень, зазвичай через різні сервери. Використання SimpleX - це все одно, що мати різну “конфорку” електронну пошту або телефон для кожного контакту, і не мати клопоту з управлінням ними.", - "simplex-unique-overlay-card-1-p-3": "Такий дизайн захищає конфіденційність того, з ким ви спілкуєтеся, приховуючи її від серверів платформи SimpleX і від будь-яких спостерігачів. Щоб приховати свою IP-адресу від серверів, ви можете підключитися до серверів SimpleX через Tor.", - "simplex-unique-overlay-card-2-p-1": "Оскільки у вас немає ідентифікатора на платформі SimpleX, ніхто не може зв'язатися з вами, якщо ви не надасте одноразову або тимчасову адресу користувача у вигляді QR-коду або посилання.", - "simplex-unique-overlay-card-2-p-2": "Навіть якщо необов'язкова адреса користувача може бути використана для надсилання спам-повідомлень, ви можете змінити або повністю видалити її, не втрачаючи при цьому жодного з ваших зв'язків.", - "simplex-unique-overlay-card-3-p-1": "SimpleX Chat зберігає всі дані користувача лише на клієнтських пристроях за допомогою портативного формату зашифрованої бази даних, який можна експортувати та передавати на будь-який підтримуваний пристрій.", - "simplex-unique-overlay-card-3-p-2": "Наскрізні зашифровані повідомлення тимчасово зберігаються на релейних серверах SimpleX до моменту отримання, після чого вони назавжди видаляються.", - "simplex-unique-overlay-card-3-p-4": "Між відправленим і отриманим трафіком сервера немає спільних ідентифікаторів або зашифрованого тексту — якщо хтось спостерігає за ним, він не зможе легко визначити, хто з ким спілкується, навіть якщо TLS скомпрометований.", - "simplex-unique-overlay-card-4-p-1": "Ви можете використовувати SimpleX з власними серверами і при цьому спілкуватися з людьми, які використовують попередньо налаштовані сервери, надані нами.", - "simplex-unique-overlay-card-4-p-2": "Платформа SimpleX використовує відкритий протокол та надає SDK для створення чат-ботів, що дозволяє реалізовувати сервіси, з якими користувачі можуть взаємодіяти через додатки SimpleX Chat —. Ми'з нетерпінням чекаємо на те, які сервіси SimpleX ви можете створити.", - "simplex-unique-overlay-card-4-p-3": "Якщо ви розглядаєте можливість розробки для платформи SimpleX, наприклад, чат-бота для користувачів додатків SimpleX або інтеграції бібліотеки SimpleX Chat у ваші мобільні додатки, будь ласка, зв'яжіться з нами для отримання будь-якої консультації та підтримки.", - "simplex-unique-card-1-p-1": "SimpleX захищає конфіденційність вашого профілю, контактів і метаданих, приховуючи їх від серверів платформи SimpleX і будь-яких спостерігачів.", - "simplex-unique-card-1-p-2": "На відміну від будь-якої іншої існуючої платформи обміну повідомленнями, SimpleX не має ніяких ідентифікаторів, призначених користувачам — , навіть випадкових чисел.", - "simplex-unique-card-2-p-1": "Оскільки у вас немає ідентифікатора або фіксованої адреси на платформі SimpleX, ніхто не може зв'язатися з вами, якщо ви не надасте одноразову або тимчасову адресу користувача, наприклад, у вигляді QR-коду або посилання.", - "simplex-unique-card-3-p-1": "SimpleX зберігає всі дані користувача на клієнтських пристроях у портативному форматі зашифрованої бази даних —, його можна перенести на інший пристрій.", - "join": "Приєднуйтесь", - "we-invite-you-to-join-the-conversation": "Запрошуємо вас долучитися до розмови", + "hero-overlay-card-1-p-1": "Багато користувачів запитували: якщо у SimpleX немає ідентифікаторів користувачів, як він може знати, куди відправити повідомлення?", + "hero-overlay-card-1-p-2": "Для доставки повідомлень, замість ідентифікаторів користувачів, які використовують усі інші платформи, SimpleX використовує тимчасові анонімні парні ідентифікатори черг повідомлень, окремі для кожного вашого підключення — тут немає довгострокових ідентифікаторів.", + "hero-overlay-card-1-p-3": "Ви визначаєте, який(кі) сервер(и) використовувати для отримання повідомлень, ваші контакти — сервери, які ви використовуєте для відправки повідомлень їм. Кожна розмова, ймовірно, використовує два різних сервери.", + "hero-overlay-card-1-p-4": "Цей дизайн запобігає витоку метаданих будь-яких користувачів на рівні додатка. Для подальшого покращення конфіденційності та захисту вашої IP-адреси ви можете підключитися до серверів обміну повідомленнями через Tor.", + "hero-overlay-card-1-p-6": "Докладніше читайте у білетному запису SimpleX.", + "hero-overlay-card-2-p-2": "Вони можуть потім корелювати цю інформацію з існуючими громадськими соціальними мережами та визначати деякі реальні ідентифікатори.", + "hero-overlay-card-2-p-3": "Навіть з найбільш приватними додатками, які використовують служби Tor v3, якщо ви розмовляєте з двома різними контактами через той самий профіль, вони можуть довести, що вони підключені до однієї й тієї ж особи.", + "hero-overlay-card-2-p-4": "SimpleX захищає від цих атак, не маючи жодних ідентифікаторів користувачів в своєму дизайні. І, якщо ви використовуєте режим інкогніто, у вас буде різне відображення для кожного контакту, уникнення будь-яких спільних даних між ними.", + "simplex-network-overlay-card-1-p-1": "Протоколи та додатки для P2P-зв'язку мають різні проблеми, які роблять їх менш надійними порівняно із SimpleX, складнішими для аналізу та вразливими до кількох типів атак.", + "simplex-network-overlay-card-1-li-1": "P2P-мережі покладаються на якусь варіацію розподіленої хеш-таблиці (DHT) для маршрутизації повідомлень. Дизайни DHT повинні балансувати гарантію доставки та затримку. У SimpleX є як краща гарантія доставки, так і менша затримка порівняно із P2P, оскільки повідомлення може передаватися паралельно кількома серверами, використовуючи сервери, обрані отримувачем. У P2P-мережах повідомлення проходить через вузли O(log N) послідовно, використовуючи вузли, обрані алгоритмом.", + "simplex-network-overlay-card-1-li-2": "У дизайні SimpleX, на відміну від більшості P2P-мереж, немає жодних глобальних ідентифікаторів користувачів будь-якого виду, навіть тимчасових, та використовуються лише тимчасові парні ідентифікатори, що забезпечує кращу анонімність та захист метаданих.", + "privacy-matters-overlay-card-1-p-1": "Багато великих компаній використовують інформацію про те, з ким ви з'єднані, щоб оцінити ваш дохід, продавати вам продукти, які вам дійсно не потрібні, і визначати ціни.", + "privacy-matters-overlay-card-3-p-3": "Звичайних людей арештовують за те, що вони публікують онлайн, навіть через свої 'анонімні' облікові записи, навіть у демократичних країнах.", + "simplex-unique-overlay-card-1-p-2": "Для доставки повідомлень SimpleX використовує парні анонімні адреси однобічних черг повідомлень, окремо для отриманих та відправлених повідомлень, зазвичай через різні сервери. Використання SimpleX схоже на наявність різної “витратної” електронної пошти або телефону для кожного контакту, і немає неприємностей у їх управлінні.", + "simplex-unique-overlay-card-1-p-3": "Цей дизайн захищає конфіденційність осіб, з якими ви спілкуєтеся, приховуючи це від серверів платформи SimpleX та будь-яких спостерігачів. Щоб сховати свою IP-адресу від серверів, ви можете підключитися до серверів SimpleX через Tor.", + "simplex-unique-overlay-card-2-p-1": "Оскільки у вас немає ідентифікатора на платформі SimpleX, ніхто не може з вами зв'язатися, якщо ви не поділитеся одноразовою або тимчасовою адресою користувача, у вигляді QR-коду або посилання.", + "simplex-unique-overlay-card-2-p-2": "Навіть з необов'язковою адресою користувача, яка може бути використана для відправки спамових запитань на зв'язок, ви можете змінити або повністю видалити її, не втрачаючи жодного з ваших з'єднань.", + "simplex-unique-overlay-card-3-p-1": "SimpleX Chat зберігає всі дані користувачів лише на пристроях клієнтів за допомогою переносного зашифрованого формату бази даних, який можна експортувати і передавати на будь-який підтримуваний пристрій.", + "simplex-unique-overlay-card-3-p-2": "Зашифровані повідомлення від кінця до кінця тимчасово зберігаються на ретрансляційних серверах SimpleX до їх отримання, після чого вони назавжди видаляються.", + "simplex-unique-overlay-card-3-p-4": "Між надісланим і отриманим серверним трафіком немає спільних ідентифікаторів чи шифрованого тексту — якщо хтось його спостерігає, він не може легко визначити, хто спілкується з ким, навіть якщо TLS скомпрометовано.", + "simplex-unique-overlay-card-4-p-1": "Ви можете використовувати SimpleX з власними серверами і все одно спілкуватися з людьми, які використовують надані нам сервери заздалегідь налаштовані.", + "simplex-unique-overlay-card-4-p-2": "Платформа SimpleX використовує відкритий протокол та надає SDK для створення чат-ботів, що дозволяє впроваджувати сервіси, з якими користувачі можуть взаємодіяти через додатки SimpleX Chat — ми дійсно чекаємо, які сервіси SimpleX ви зможете побудувати.", + "simplex-unique-overlay-card-4-p-3": "Якщо ви розглядаєте можливість розробки для платформи SimpleX, наприклад, чат-бота для користувачів додатку SimpleX або інтеграції бібліотеки SimpleX Chat у свої мобільні додатки, будь ласка, зв'яжіться з нами для отримання порад та підтримки.", + "simplex-unique-card-1-p-1": "SimpleX захищає конфіденційність вашого профілю, контактів та метаданих, приховуючи їх від серверів платформи SimpleX та будь-яких спостерігачів.", + "simplex-unique-card-1-p-2": "На відміну від будь-якої іншої існуючої платформи обміну повідомленнями, SimpleX не має ідентифікаторів, призначених користувачам — навіть випадкових чисел.", + "simplex-unique-card-2-p-1": "Оскільки у вас немає ідентифікатора або фіксованої адреси на платформі SimpleX, ніхто не може з вами зв'язатися, якщо ви не поділитесь одноразовою або тимчасовою адресою користувача, як QR-код або посиланням.", + "simplex-unique-card-3-p-1": "SimpleX зберігає всі дані користувачів на пристроях клієнтів у переносному зашифрованому форматі бази даних — його можна передавати на інший пристрій.", + "join": "Приєднатися", + "we-invite-you-to-join-the-conversation": "Ми запрошуємо вас приєднатися до розмови", "join-the-REDDIT-community": "Приєднуйтесь до спільноти REDDIT", "join-us-on-GitHub": "Приєднуйтесь до нас на GitHub", "donate-here-to-help-us": "Пожертвуйте тут, щоб допомогти нам", - "sign-up-to-receive-our-updates": "Підпишіться на наші оновлення", - "enter-your-email-address": "Введіть адресу вашої електронної пошти", - "get-simplex": "Отримати SimpleX desktop app", - "why-simplex-is": "Чому SimpleX це", - "unique": "унікальний", - "learn-more": "Дізнайтеся більше", - "more-info": "Більше інформації", + "sign-up-to-receive-our-updates": "Підпишіться, щоб отримувати наші оновлення", + "enter-your-email-address": "Введіть свою електронну адресу", + "get-simplex": "Отримати SimpleX додаток для настільних комп'ютерів", + "why-simplex-is": "Чому SimpleX є", + "unique": "унікальним", + "learn-more": "Дізнатися більше", + "more-info": "Додаткова інформація", "hide-info": "Приховати інформацію", - "contact-hero-header": "Ви отримали адресу для підключення до чату SimpleX", - "invitation-hero-header": "Ви отримали одноразове посилання для підключення до чату SimpleX", - "contact-hero-subheader": "Відскануйте QR-код за допомогою програми SimpleX Chat на телефоні або планшеті.", + "contact-hero-header": "Ви отримали адресу для підключення в SimpleX Chat", + "invitation-hero-header": "Ви отримали 1-разове посилання для підключення в SimpleX Chat", + "contact-hero-subheader": "Скануйте QR-код за допомогою додатка SimpleX Chat на своєму телефоні чи планшеті.", "contact-hero-p-2": "Ще не завантажили SimpleX Chat?", - "contact-hero-p-3": "Щоб завантажити додаток, скористайтеся посиланнями нижче.", - "connect-in-app": "Підключіться в додатку", - "open-simplex-app": "Відкрийте програму Simplex", - "tap-the-connect-button-in-the-app": "Натисніть кнопку ‘підключитися‘ у додатку", - "scan-the-qr-code-with-the-simplex-chat-app": "Відскануйте QR-код за допомогою програми SimpleX Chat", - "scan-the-qr-code-with-the-simplex-chat-app-description": "Відкриті ключі та адреса черги повідомлень за цим посиланням НЕ передаються мережею при перегляді цієї сторінки —
вони містяться в хеш-фрагменті URL-адреси посилання.", - "installing-simplex-chat-to-terminal": "Встановлення чату SimpleX на термінал", - "use-this-command": "Використовуй цю команду:", - "see-simplex-chat": "Дивіться Чат SimpleX", - "the-instructions--source-code": "інструкції, як завантажити або скомпілювати його з вихідного коду.", + "contact-hero-p-3": "Скористайтеся посиланнями нижче, щоб завантажити додаток.", + "connect-in-app": "Підключитися в додатку", + "open-simplex-app": "Відкрити додаток SimpleX", + "tap-the-connect-button-in-the-app": "Торкніться кнопки ‘підключити’ в додатку", + "scan-the-qr-code-with-the-simplex-chat-app": "Скануйте QR-код за допомогою додатка SimpleX Chat", + "scan-the-qr-code-with-the-simplex-chat-app-description": "Публічні ключі та адреса черги повідомлень в цьому посиланні НЕ відправляються по мережі під час перегляду цієї сторінки —
вони містяться в хеш-фрагменті URL-посилання.", + "installing-simplex-chat-to-terminal": "Встановлення SimpleX Chat для терміналу", + "use-this-command": "Використовуйте цю команду:", + "see-simplex-chat": "Дивіться SimpleX Chat", + "the-instructions--source-code": "інструкції з того, як його завантажити чи скомпілювати з вихідного коду.", "if-you-already-installed-simplex-chat-for-the-terminal": "Якщо ви вже встановили SimpleX Chat для терміналу", "if-you-already-installed": "Якщо ви вже встановили", "simplex-chat-for-the-terminal": "SimpleX Chat для терміналу", - "copy-the-command-below-text": "скопіюйте команду нижче і використовуйте її в чаті:", + "copy-the-command-below-text": "скопіюйте команду нижче та використовуйте її в чаті:", "privacy-matters-section-header": "Чому конфіденційність має значення", - "privacy-matters-section-subheader": "Збереження конфіденційності ваших метаданих — від кого ви спілкуєтеся — захищає вас:", - "privacy-matters-section-label": "Переконайтеся, що ваш месенджер не може отримати доступ до ваших даних!", - "simplex-private-section-header": "Що робить SimpleX приватним", - "tap-to-close": "Натисніть, щоб закрити", - "simplex-network-section-header": "SimpleX Мережа", - "simplex-network-section-desc": "Simplex Chat забезпечує найкращу конфіденційність, поєднуючи переваги P2P та об'єднаних мереж.", - "simplex-network-1-desc": "Всі повідомлення надсилаються через сервери, що забезпечує кращу конфіденційність метаданих і надійну асинхронну доставку повідомлень, уникаючи при цьому багатьох", - "simplex-network-1-header": "На відміну від P2P-мереж", - "simplex-network-1-overlay-linktext": "проблеми P2P-мереж", - "simplex-network-2-header": "На відміну від об'єднаних мереж", - "simplex-network-2-desc": "Сервери ретрансляції SimpleX НЕ зберігають профілі користувачів, контакти та доставлені повідомлення, НЕ з'єднуються один з одним і НЕ мають каталогу серверів.", + "privacy-matters-section-subheader": "Збереження конфіденційності ваших метаданих — з ким ви спілкуєтеся — захищає вас від:", + "privacy-matters-section-label": "Переконайтеся, що ваш месенджер не має доступу до ваших даних!", + "simplex-private-section-header": "Що робить SimpleX конфіденційним", + "tap-to-close": "Торкніться, щоб закрити", + "simplex-network-section-header": "SimpleX Network", + "simplex-network-section-desc": "Simplex Chat надає найкращу конфіденційність, поєднуючи переваги P2P та федеративних мереж.", + "simplex-network-1-desc": "Всі повідомлення відправляються через сервери, що забезпечує кращу конфіденційність метаданих та надійну асинхронну доставку повідомлень, уникаючи багатьох", + "simplex-network-1-header": "На відміну від мереж P2P", + "simplex-network-1-overlay-linktext": "проблем P2P-мереж", + "simplex-network-2-header": "На відміну від федеративних мереж", + "simplex-network-2-desc": "Сервери SimpleX Chat НЕ зберігають профілі користувачів, контакти та доставлені повідомлення, НЕ підключаються один до одного, і НЕ існує каталог серверів.", "simplex-network-3-header": "Мережа SimpleX", - "simplex-network-3-desc": "Сервери надають односпрямовані черги для підключення користувачів, але вони не мають видимості графу мережевих з'єднань —, його бачать лише користувачі.", + "simplex-network-3-desc": "сервери надають однобічні черги для з'єднання користувачів, але вони не мають видимості графа підключень в мережі — лише користувачі.", "comparison-section-header": "Порівняння з іншими протоколами", - "protocol-1-text": "Сигнал, великі платформи", - "protocol-2-text": "XMPP, Матрикс", + "protocol-1-text": "Signal, великі платформи", + "protocol-2-text": "XMPP, Matrix", "protocol-3-text": "Протоколи P2P", - "comparison-point-1-text": "Вимагає глобальної ідентичності", + "comparison-point-1-text": "Потребує глобального ідентифікатора", "comparison-point-2-text": "Можливість MITM", "comparison-point-3-text": "Залежність від DNS", - "comparison-point-4-text": "Окрема або централізована мережа", - "comparison-point-5-text": "Атака на центральний компонент або інша мережева атака", + "comparison-point-4-text": "Одна чи централізована мережа", + "comparison-point-5-text": "Центральний компонент чи інша атака на всю мережу", "yes": "Так", "no": "Ні", - "no-private": "Ні - приватний", + "no-private": "Ні - конфіденційно", "no-secure": "Ні - безпечно", - "no-resilient": "Ні - стійкий", + "no-resilient": "Ні - стійко", "no-decentralized": "Ні - децентралізовано", - "comparison-section-list-point-4": "Якщо сервери оператора скомпрометовані", - "comparison-section-list-point-5": "Не захищає метадані користувачів", - "comparison-section-list-point-6": "Хоча P2P розподілені, вони не об'єднані - вони працюють як єдина мережа", + "comparison-section-list-point-4": "Якщо сервери оператора порушені. Перевірте безпековий код в Signal та деяких інших додатках для зменшення ризику", + "comparison-section-list-point-5": "Не захищає конфіденційність метаданих користувачів", + "comparison-section-list-point-6": "Хоча P2P є розподіленими, вони не є федеративними - вони працюють як єдина мережа", "guide-dropdown-1": "Швидкий старт", - "guide-dropdown-2": "Надсилання повідомлень", + "guide-dropdown-2": "Відправлення повідомлень", "guide-dropdown-3": "Таємні групи", "glossary": "Глосарій", "docs-dropdown-8": "Служба каталогів SimpleX", - "f-droid-page-simplex-chat-repo-section-text": "Щоб додати його в клієнт F-Droid відскануйте QR-код або використовуйте цю URL-адресу:", + "f-droid-page-simplex-chat-repo-section-text": "Щоб додати його до свого клієнта F-Droid, скануйте QR-код або використовуйте цей URL:", "simplex-chat-via-f-droid": "SimpleX Chat через F-Droid", - "signing-key-fingerprint": "Відбиток ключа підпису (SHA-256)", - "stable-versions-built-by-f-droid-org": "Стабільні версії, зібрані на F-Droid.org", - "simplex-chat-repo": "Репо SimpleX Chat", - "f-droid-org-repo": "Репо F-Droid.org", - "releases-to-this-repo-are-done-1-2-days-later": "Релізи в це репо відбуваються на 1-2 дні пізніше", - "stable-and-beta-versions-built-by-developers": "Стабільні та бета-версії, створені розробниками", - "f-droid-page-f-droid-org-repo-section-text": "Репозиторії SimpleX Chat та F-Droid.org підписують збірки з різними ключами. Щоб перемикнутися, будь ласка експорт базу даних чату та перевстановіть додаток." + "signing-key-fingerprint": "Відбиток підпису ключа (SHA-256)", + "stable-versions-built-by-f-droid-org": "Стабільні версії, побудовані F-Droid.org", + "simplex-chat-repo": "Репозитарій SimpleX Chat", + "f-droid-org-repo": "Репозитарій F-Droid.org", + "releases-to-this-repo-are-done-1-2-days-later": "Релізи в цей репозитарій робляться за 1-2 дні пізніше", + "stable-and-beta-versions-built-by-developers": "Стабільні та бета-версії, побудовані розробниками", + "f-droid-page-f-droid-org-repo-section-text": "SimpleX Chat та репозитарії F-Droid.org підписують збірки різними ключами. Щоб переключитися, будь ласка, експортуйте базу даних чату та перевстановіть додаток.", + "hero-overlay-3-title": "Оцінка безпеки", + "hero-overlay-card-3-p-2": "Trail of Bits переглянувало криптографію та компоненти мережі платформи SimpleX у листопаді 2022 року.", + "hero-overlay-card-3-p-3": "Читайте більше в оголошенні.", + "jobs": "Приєднатися до команди", + "hero-overlay-3-textlink": "Оцінка безпеки", + "hero-overlay-card-3-p-1": "Trail of Bits є провідною консалтинговою фірмою з безпеки та технологій, клієнтами якої є великі технологічні компанії, урядові агенції та великі проекти у сфері блокчейну.", + "comparison-section-list-point-4a": "Ретранслятори SimpleX не можуть порушити e2e-шифрування. Перевірте безпековий код для зменшення ризику атаки на зовнішньобандовий канал", + "docs-dropdown-9": "Завантаження" } \ No newline at end of file From 2bacc00a062b1cb255ceca9b6a410439f317d6fb Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 29 Dec 2023 16:29:49 +0400 Subject: [PATCH 04/16] ios: rework UX of creating new connection (#3482) * ios: connection UI (wip) * custom search * rework invite * connect paste link ui * scan rework, process errors, other fixes * scan layout * clear link on cancel * improved search * further improve search * animation * connect on paste in search * layout * layout * layout * layout, add conn * delete unused invitation, create used invitation chat * remove old views * regular paste button * new chat menu * previews * increase spacing * animation, fix alerts * swipe * change text * less sensitive gesture * layout * search cancel button transition * slow down chat list animation (uses deprecated modifiers) * icons * update code scanner, layout * manage camera permissions * ask to delete unused invitation * comment * remove onDismiss * don't filter chats on link in search, allow to paste text with link * cleanup link after connection * filter chat by link * revert change * show link descr * disabled search * underline * filter own group * simplify * no animation * add delay, move createInvitation * update library * possible fix for ios 15 * add explicit frame to qr code * update library * Revert "add explicit frame to qr code" This reverts commit 95c7d31e47b3da39b5985cd57638885c45b77de1. * remove comment * fix pasteboardHasURLs, disable paste button based on it * align help texts with changed button names Co-authored-by: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> * update library * Revert "fix pasteboardHasURLs, disable paste button based on it" This reverts commit 46f63572e90dbf460faab9ce694181209712bd00. * remove unused var * restore disabled * export localizations --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Co-authored-by: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> --- apps/ios/Shared/AppDelegate.swift | 5 + apps/ios/Shared/Model/ChatModel.swift | 20 +- apps/ios/Shared/Model/SimpleXAPI.swift | 10 +- .../Views/Chat/ChatItem/MsgContentView.swift | 4 +- apps/ios/Shared/Views/Chat/ChatView.swift | 8 +- apps/ios/Shared/Views/ChatList/ChatHelp.swift | 9 +- .../Shared/Views/ChatList/ChatListView.swift | 181 +++- .../ChatList/ContactConnectionInfo.swift | 22 + .../Views/NewChat/AddContactLearnMore.swift | 14 +- .../Shared/Views/NewChat/AddContactView.swift | 129 --- .../Views/NewChat/ConnectViaLinkView.swift | 42 - .../Shared/Views/NewChat/CreateLinkView.swift | 94 -- .../Shared/Views/NewChat/NewChatButton.swift | 466 --------- .../Views/NewChat/NewChatMenuButton.swift | 52 + .../Shared/Views/NewChat/NewChatView.swift | 959 ++++++++++++++++++ .../Views/NewChat/PasteToConnectView.swift | 106 -- apps/ios/Shared/Views/NewChat/QRCode.swift | 5 +- .../Views/NewChat/ScanToConnectView.swift | 79 -- .../Views/UserSettings/IncognitoHelp.swift | 23 +- .../Views/UserSettings/SettingsView.swift | 6 + .../bg.xcloc/Localized Contents/bg.xliff | 153 +-- .../cs.xcloc/Localized Contents/cs.xliff | 153 +-- .../de.xcloc/Localized Contents/de.xliff | 153 +-- .../en.xcloc/Localized Contents/en.xliff | 178 ++-- .../es.xcloc/Localized Contents/es.xliff | 153 +-- .../fi.xcloc/Localized Contents/fi.xliff | 153 +-- .../fr.xcloc/Localized Contents/fr.xliff | 153 +-- .../it.xcloc/Localized Contents/it.xliff | 153 +-- .../ja.xcloc/Localized Contents/ja.xliff | 153 +-- .../nl.xcloc/Localized Contents/nl.xliff | 153 +-- .../pl.xcloc/Localized Contents/pl.xliff | 153 +-- .../ru.xcloc/Localized Contents/ru.xliff | 153 +-- .../th.xcloc/Localized Contents/th.xliff | 152 +-- .../uk.xcloc/Localized Contents/uk.xliff | 153 +-- .../Localized Contents/zh-Hans.xliff | 153 +-- apps/ios/SimpleX.xcodeproj/project.pbxproj | 42 +- apps/ios/SimpleXChat/ChatTypes.swift | 9 + 37 files changed, 2722 insertions(+), 1882 deletions(-) delete mode 100644 apps/ios/Shared/Views/NewChat/AddContactView.swift delete mode 100644 apps/ios/Shared/Views/NewChat/ConnectViaLinkView.swift delete mode 100644 apps/ios/Shared/Views/NewChat/CreateLinkView.swift delete mode 100644 apps/ios/Shared/Views/NewChat/NewChatButton.swift create mode 100644 apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift create mode 100644 apps/ios/Shared/Views/NewChat/NewChatView.swift delete mode 100644 apps/ios/Shared/Views/NewChat/PasteToConnectView.swift delete mode 100644 apps/ios/Shared/Views/NewChat/ScanToConnectView.swift diff --git a/apps/ios/Shared/AppDelegate.swift b/apps/ios/Shared/AppDelegate.swift index 145e362797..24c0eeb605 100644 --- a/apps/ios/Shared/AppDelegate.swift +++ b/apps/ios/Shared/AppDelegate.swift @@ -15,6 +15,7 @@ class AppDelegate: NSObject, UIApplicationDelegate { logger.debug("AppDelegate: didFinishLaunchingWithOptions") application.registerForRemoteNotifications() if #available(iOS 17.0, *) { trackKeyboard() } + NotificationCenter.default.addObserver(self, selector: #selector(pasteboardChanged), name: UIPasteboard.changedNotification, object: nil) return true } @@ -36,6 +37,10 @@ class AppDelegate: NSObject, UIApplicationDelegate { ChatModel.shared.keyboardHeight = 0 } + @objc func pasteboardChanged() { + ChatModel.shared.pasteboardHasStrings = UIPasteboard.general.hasStrings + } + func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { let token = deviceToken.map { String(format: "%02hhx", $0) }.joined() logger.debug("AppDelegate: didRegisterForRemoteNotificationsWithDeviceToken \(token)") diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 0cc281fda9..db0f138690 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -89,14 +89,15 @@ final class ChatModel: ObservableObject { @Published var showCallView = false // remote desktop @Published var remoteCtrlSession: RemoteCtrlSession? - // currently showing QR code - @Published var connReqInv: String? + // currently showing invitation + @Published var showingInvitation: ShowingInvitation? // audio recording and playback @Published var stopPreviousRecPlay: URL? = nil // coordinates currently playing source @Published var draft: ComposeState? @Published var draftChatId: String? // tracks keyboard height via subscription in AppDelegate @Published var keyboardHeight: CGFloat = 0 + @Published var pasteboardHasStrings: Bool = UIPasteboard.general.hasStrings var messageDelivery: Dictionary Void> = [:] @@ -620,14 +621,16 @@ final class ChatModel: ObservableObject { } func dismissConnReqView(_ id: String) { - if let connReqInv = connReqInv, - let c = getChat(id), - case let .contactConnection(contactConnection) = c.chatInfo, - connReqInv == contactConnection.connReqInv { + if id == showingInvitation?.connId { + markShowingInvitationUsed() dismissAllSheets() } } + func markShowingInvitationUsed() { + showingInvitation?.connChatUsed = true + } + func removeChat(_ id: String) { withAnimation { chats.removeAll(where: { $0.id == id }) @@ -704,6 +707,11 @@ final class ChatModel: ObservableObject { } } +struct ShowingInvitation { + var connId: String + var connChatUsed: Bool +} + struct NTFContactRequest { var incognito: Bool var chatId: String diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index eff3110962..ddac78c3da 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -581,15 +581,15 @@ func apiVerifyGroupMember(_ groupId: Int64, _ groupMemberId: Int64, connectionCo return nil } -func apiAddContact(incognito: Bool) async -> (String, PendingContactConnection)? { +func apiAddContact(incognito: Bool) async -> ((String, PendingContactConnection)?, Alert?) { guard let userId = ChatModel.shared.currentUser?.userId else { logger.error("apiAddContact: no current user") - return nil + return (nil, nil) } let r = await chatSendCmd(.apiAddContact(userId: userId, incognito: incognito), bgTask: false) - if case let .invitation(_, connReqInvitation, connection) = r { return (connReqInvitation, connection) } - AlertManager.shared.showAlert(connectionErrorAlert(r)) - return nil + if case let .invitation(_, connReqInvitation, connection) = r { return ((connReqInvitation, connection), nil) } + let alert = connectionErrorAlert(r) + return (nil, alert) } func apiSetConnectionIncognito(connId: Int64, incognito: Bool) async throws -> PendingContactConnection? { diff --git a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift index d0d2bdf3dd..cad6401cc0 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift @@ -9,7 +9,7 @@ import SwiftUI import SimpleXChat -private let uiLinkColor = UIColor(red: 0, green: 0.533, blue: 1, alpha: 1) +let uiLinkColor = UIColor(red: 0, green: 0.533, blue: 1, alpha: 1) private let noTyping = Text(" ") @@ -144,7 +144,7 @@ private func linkText(_ s: String, _ link: String, _ preview: Bool, prefix: Stri ]))).underline() } -private func simplexLinkText(_ linkType: SimplexLinkType, _ smpHosts: [String]) -> String { +func simplexLinkText(_ linkType: SimplexLinkType, _ smpHosts: [String]) -> String { linkType.description + " " + "(via \(smpHosts.first ?? "?"))" } diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 6e2c0c1555..a09c5643b6 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -250,8 +250,8 @@ struct ChatView: View { } private func searchToolbar() -> some View { - HStack { - HStack { + HStack(spacing: 12) { + HStack(spacing: 4) { Image(systemName: "magnifyingglass") TextField("Search", text: $searchText) .focused($searchFocussed) @@ -264,9 +264,9 @@ struct ChatView: View { Image(systemName: "xmark.circle.fill").opacity(searchText == "" ? 0 : 1) } } - .padding(EdgeInsets(top: 8, leading: 6, bottom: 8, trailing: 6)) + .padding(EdgeInsets(top: 7, leading: 7, bottom: 7, trailing: 7)) .foregroundColor(.secondary) - .background(Color(.secondarySystemBackground)) + .background(Color(.tertiarySystemFill)) .cornerRadius(10.0) Button ("Cancel") { diff --git a/apps/ios/Shared/Views/ChatList/ChatHelp.swift b/apps/ios/Shared/Views/ChatList/ChatHelp.swift index 7741512432..2435c9a4f5 100644 --- a/apps/ios/Shared/Views/ChatList/ChatHelp.swift +++ b/apps/ios/Shared/Views/ChatList/ChatHelp.swift @@ -11,7 +11,7 @@ import SwiftUI struct ChatHelp: View { @EnvironmentObject var chatModel: ChatModel @Binding var showSettings: Bool - @State private var showAddChat = false + @State private var newChatMenuOption: NewChatMenuOption? = nil var body: some View { ScrollView { chatHelp() } @@ -39,13 +39,12 @@ struct ChatHelp: View { HStack(spacing: 8) { Text("Tap button ") - NewChatButton(showAddChat: $showAddChat) + NewChatMenuButton(newChatMenuOption: $newChatMenuOption) Text("above, then choose:") } - Text("**Create link / QR code** for your contact to use.") - Text("**Paste received link** or open it in the browser and tap **Open in mobile app**.") - Text("**Scan QR code**: to connect to your contact in person or via video call.") + Text("**Add contact**: to create a new invitation link, or connect via a link you received.") + Text("**Create group**: to create a new group.") } .padding(.top, 24) diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 1d86733206..3d0551de66 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -12,8 +12,12 @@ import SimpleXChat struct ChatListView: View { @EnvironmentObject var chatModel: ChatModel @Binding var showSettings: Bool + @State private var searchMode = false + @FocusState private var searchFocussed @State private var searchText = "" - @State private var showAddChat = false + @State private var searchShowingSimplexLink = false + @State private var searchChatFilteredBySimplexLink: String? = nil + @State private var newChatMenuOption: NewChatMenuOption? = nil @State private var userPickerVisible = false @State private var showConnectDesktop = false @AppStorage(DEFAULT_SHOW_UNREAD_AND_FAVORITES) private var showUnreadAndFavorites = false @@ -62,11 +66,7 @@ struct ChatListView: View { private var chatListView: some View { VStack { - if chatModel.chats.count > 0 { - chatList.searchable(text: $searchText) - } else { - chatList - } + chatList } .onDisappear() { withAnimation { userPickerVisible = false } } .refreshable { @@ -85,9 +85,9 @@ struct ChatListView: View { secondaryButton: .cancel() )) } - .offset(x: -8) .listStyle(.plain) .navigationBarTitleDisplayMode(.inline) + .navigationBarHidden(searchMode) .toolbar { ToolbarItem(placement: .navigationBarLeading) { let user = chatModel.currentUser ?? User.sampleData @@ -124,7 +124,7 @@ struct ChatListView: View { } ToolbarItem(placement: .navigationBarTrailing) { switch chatModel.chatRunning { - case .some(true): NewChatButton(showAddChat: $showAddChat) + case .some(true): NewChatMenuButton(newChatMenuOption: $newChatMenuOption) case .some(false): chatStoppedIcon() case .none: EmptyView() } @@ -144,11 +144,25 @@ struct ChatListView: View { @ViewBuilder private var chatList: some View { let cs = filteredChats() ZStack { - List { - ForEach(cs, id: \.viewId) { chat in - ChatListNavLink(chat: chat) - .padding(.trailing, -16) - .disabled(chatModel.chatRunning != true) + VStack { + List { + if !chatModel.chats.isEmpty { + ChatListSearchBar( + searchMode: $searchMode, + searchFocussed: $searchFocussed, + searchText: $searchText, + searchShowingSimplexLink: $searchShowingSimplexLink, + searchChatFilteredBySimplexLink: $searchChatFilteredBySimplexLink + ) + .listRowSeparator(.hidden) + .frame(maxWidth: .infinity) + } + ForEach(cs, id: \.viewId) { chat in + ChatListNavLink(chat: chat) + .padding(.trailing, -16) + .disabled(chatModel.chatRunning != true) + } + .offset(x: -8) } } .onChange(of: chatModel.chatId) { _ in @@ -182,7 +196,7 @@ struct ChatListView: View { .padding(.trailing, 12) connectButton("Tap to start a new chat") { - showAddChat = true + newChatMenuOption = .newContact } Spacer() @@ -214,22 +228,25 @@ struct ChatListView: View { } private func filteredChats() -> [Chat] { - let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase - return s == "" && !showUnreadAndFavorites + if let linkChatId = searchChatFilteredBySimplexLink { + return chatModel.chats.filter { $0.id == linkChatId } + } else { + let s = searchString() + return s == "" && !showUnreadAndFavorites ? chatModel.chats : chatModel.chats.filter { chat in let cInfo = chat.chatInfo switch cInfo { case let .direct(contact): return s == "" - ? filtered(chat) - : (viewNameContains(cInfo, s) || - contact.profile.displayName.localizedLowercase.contains(s) || - contact.fullName.localizedLowercase.contains(s)) + ? filtered(chat) + : (viewNameContains(cInfo, s) || + contact.profile.displayName.localizedLowercase.contains(s) || + contact.fullName.localizedLowercase.contains(s)) case let .group(gInfo): return s == "" - ? (filtered(chat) || gInfo.membership.memberStatus == .memInvited) - : viewNameContains(cInfo, s) + ? (filtered(chat) || gInfo.membership.memberStatus == .memInvited) + : viewNameContains(cInfo, s) case .contactRequest: return s == "" || viewNameContains(cInfo, s) case let .contactConnection(conn): @@ -238,6 +255,11 @@ struct ChatListView: View { return false } } + } + + func searchString() -> String { + searchShowingSimplexLink ? "" : searchText.trimmingCharacters(in: .whitespaces).localizedLowercase + } func filtered(_ chat: Chat) -> Bool { (chat.chatInfo.chatSettings?.favorite ?? false) || chat.chatStats.unreadCount > 0 || chat.chatStats.unreadChat @@ -249,6 +271,121 @@ struct ChatListView: View { } } +struct ChatListSearchBar: View { + @EnvironmentObject var m: ChatModel + @Binding var searchMode: Bool + @FocusState.Binding var searchFocussed: Bool + @Binding var searchText: String + @Binding var searchShowingSimplexLink: Bool + @Binding var searchChatFilteredBySimplexLink: String? + @State private var ignoreSearchTextChange = false + @State private var showScanCodeSheet = false + @State private var alert: PlanAndConnectAlert? + @State private var sheet: PlanAndConnectActionSheet? + + var body: some View { + VStack(spacing: 12) { + HStack(spacing: 12) { + HStack(spacing: 4) { + Image(systemName: "magnifyingglass") + TextField("Search or paste SimpleX link", text: $searchText) + .disabled(searchShowingSimplexLink) + .focused($searchFocussed) + .frame(maxWidth: .infinity) + if searchFocussed || searchShowingSimplexLink { + Image(systemName: "xmark.circle.fill") + .opacity(searchText == "" ? 0 : 1) + .onTapGesture { + searchText = "" + } + } else if searchText == "" { + HStack(spacing: 24) { + if m.pasteboardHasStrings { + Image(systemName: "doc") + .onTapGesture { + if let str = UIPasteboard.general.string { + searchText = str + } + } + } + + Image(systemName: "qrcode") + .resizable() + .scaledToFit() + .frame(width: 20, height: 20) + .onTapGesture { + showScanCodeSheet = true + } + } + .padding(.trailing, 2) + } + } + .padding(EdgeInsets(top: 7, leading: 7, bottom: 7, trailing: 7)) + .foregroundColor(.secondary) + .background(Color(.tertiarySystemFill)) + .cornerRadius(10.0) + + if searchFocussed { + Text("Cancel") + .foregroundColor(.accentColor) + .onTapGesture { + searchText = "" + searchFocussed = false + } + } + } + Divider() + } + .sheet(isPresented: $showScanCodeSheet) { + NewChatView(selection: .connect, showQRCodeScanner: true) + .environment(\EnvironmentValues.refresh as! WritableKeyPath, nil) // fixes .refreshable in ChatListView affecting nested view + } + .onChange(of: searchFocussed) { sf in + withAnimation { searchMode = sf } + } + .onChange(of: searchText) { t in + if ignoreSearchTextChange { + ignoreSearchTextChange = false + } else { + if let link = strHasSingleSimplexLink(t.trimmingCharacters(in: .whitespaces)) { // if SimpleX link is pasted, show connection dialogue + searchFocussed = false + if case let .simplexLink(linkType, _, smpHosts) = link.format { + ignoreSearchTextChange = true + searchText = simplexLinkText(linkType, smpHosts) + } + searchShowingSimplexLink = true + searchChatFilteredBySimplexLink = nil + connect(link.text) + } else { + if t != "" { // if some other text is pasted, enter search mode + searchFocussed = true + } + searchShowingSimplexLink = false + searchChatFilteredBySimplexLink = nil + } + } + } + .alert(item: $alert) { a in + planAndConnectAlert(a, dismiss: true, cleanup: { searchText = "" }) + } + .actionSheet(item: $sheet) { s in + planAndConnectActionSheet(s, dismiss: true, cleanup: { searchText = "" }) + } + } + + private func connect(_ link: String) { + planAndConnect( + link, + showAlert: { alert = $0 }, + showActionSheet: { sheet = $0 }, + dismiss: false, + incognito: nil, + filterKnownContact: { searchChatFilteredBySimplexLink = $0.id }, + filterKnownGroup: { searchChatFilteredBySimplexLink = $0.id } + ) + } +} + func chatStoppedIcon() -> some View { Button { AlertManager.shared.showAlertMsg( diff --git a/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift b/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift index 6d2fba99c6..42e90232d6 100644 --- a/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift +++ b/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift @@ -164,6 +164,28 @@ struct ContactConnectionInfo: View { } } +private func shareLinkButton(_ connReqInvitation: String) -> some View { + Button { + showShareSheet(items: [simplexChatLink(connReqInvitation)]) + } label: { + settingsRow("square.and.arrow.up") { + Text("Share 1-time link") + } + } +} + +private func oneTimeLinkLearnMoreButton() -> some View { + NavigationLink { + AddContactLearnMore(showTitle: false) + .navigationTitle("One-time invitation link") + .navigationBarTitleDisplayMode(.large) + } label: { + settingsRow("info.circle") { + Text("Learn more") + } + } +} + struct ContactConnectionInfo_Previews: PreviewProvider { static var previews: some View { ContactConnectionInfo(contactConnection: PendingContactConnection.getSampleData()) diff --git a/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift b/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift index 182149cbde..45eb783326 100644 --- a/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift +++ b/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift @@ -9,8 +9,20 @@ import SwiftUI struct AddContactLearnMore: View { + var showTitle: Bool + var body: some View { List { + if showTitle { + Text("One-time invitation link") + .font(.largeTitle) + .bold() + .fixedSize(horizontal: false, vertical: true) + .padding(.vertical) + .listRowBackground(Color.clear) + .listRowSeparator(.hidden) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + } VStack(alignment: .leading, spacing: 18) { Text("To connect, your contact can scan QR code or use the link in the app.") Text("If you can't meet in person, show QR code in a video call, or share the link.") @@ -23,6 +35,6 @@ struct AddContactLearnMore: View { struct AddContactLearnMore_Previews: PreviewProvider { static var previews: some View { - AddContactLearnMore() + AddContactLearnMore(showTitle: true) } } diff --git a/apps/ios/Shared/Views/NewChat/AddContactView.swift b/apps/ios/Shared/Views/NewChat/AddContactView.swift deleted file mode 100644 index de8e35d2a6..0000000000 --- a/apps/ios/Shared/Views/NewChat/AddContactView.swift +++ /dev/null @@ -1,129 +0,0 @@ -// -// AddContactView.swift -// SimpleX -// -// Created by Evgeny Poberezkin on 29/01/2022. -// Copyright © 2022 SimpleX Chat. All rights reserved. -// - -import SwiftUI -import CoreImage.CIFilterBuiltins -import SimpleXChat - -struct AddContactView: View { - @EnvironmentObject private var chatModel: ChatModel - @Binding var contactConnection: PendingContactConnection? - var connReqInvitation: String - @AppStorage(GROUP_DEFAULT_INCOGNITO, store: groupDefaults) private var incognitoDefault = false - - var body: some View { - VStack { - List { - Section { - if connReqInvitation != "" { - SimpleXLinkQRCode(uri: connReqInvitation) - } else { - ProgressView() - .progressViewStyle(.circular) - .scaleEffect(2) - .frame(maxWidth: .infinity) - .padding(.vertical) - } - IncognitoToggle(incognitoEnabled: $incognitoDefault) - .disabled(contactConnection == nil) - shareLinkButton(connReqInvitation) - oneTimeLinkLearnMoreButton() - } header: { - Text("1-time link") - } footer: { - sharedProfileInfo(incognitoDefault) - } - } - } - .onAppear { chatModel.connReqInv = connReqInvitation } - .onChange(of: incognitoDefault) { incognito in - Task { - do { - if let contactConn = contactConnection, - let conn = try await apiSetConnectionIncognito(connId: contactConn.pccConnId, incognito: incognito) { - await MainActor.run { - contactConnection = conn - chatModel.updateContactConnection(conn) - } - } - } catch { - logger.error("apiSetConnectionIncognito error: \(responseError(error))") - } - } - } - } -} - -struct IncognitoToggle: View { - @Binding var incognitoEnabled: Bool - @State private var showIncognitoSheet = false - - var body: some View { - ZStack(alignment: .leading) { - Image(systemName: incognitoEnabled ? "theatermasks.fill" : "theatermasks") - .frame(maxWidth: 24, maxHeight: 24, alignment: .center) - .foregroundColor(incognitoEnabled ? Color.indigo : .secondary) - .font(.system(size: 14)) - Toggle(isOn: $incognitoEnabled) { - HStack(spacing: 6) { - Text("Incognito") - Image(systemName: "info.circle") - .foregroundColor(.accentColor) - .font(.system(size: 14)) - } - .onTapGesture { - showIncognitoSheet = true - } - } - .padding(.leading, 36) - } - .sheet(isPresented: $showIncognitoSheet) { - IncognitoHelp() - } - } -} - -func sharedProfileInfo(_ incognito: Bool) -> Text { - let name = ChatModel.shared.currentUser?.displayName ?? "" - return Text( - incognito - ? "A new random profile will be shared." - : "Your profile **\(name)** will be shared." - ) -} - -func shareLinkButton(_ connReqInvitation: String) -> some View { - Button { - showShareSheet(items: [simplexChatLink(connReqInvitation)]) - } label: { - settingsRow("square.and.arrow.up") { - Text("Share 1-time link") - } - } -} - -func oneTimeLinkLearnMoreButton() -> some View { - NavigationLink { - AddContactLearnMore() - .navigationTitle("One-time invitation link") - .navigationBarTitleDisplayMode(.large) - } label: { - settingsRow("info.circle") { - Text("Learn more") - } - } -} - -struct AddContactView_Previews: PreviewProvider { - static var previews: some View { - AddContactView( - contactConnection: Binding.constant(PendingContactConnection.getSampleData()), - connReqInvitation: "https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FFe5ICmvrm4wkrr6X1LTMii-lhBqLeB76%23MCowBQYDK2VuAyEAdhZZsHpuaAk3Hh1q0uNb_6hGTpuwBIrsp2z9U2T0oC0%3D&e2e=v%3D1%26x3dh%3DMEIwBQYDK2VvAzkAcz6jJk71InuxA0bOX7OUhddfB8Ov7xwQIlIDeXBRZaOntUU4brU5Y3rBzroZBdQJi0FKdtt_D7I%3D%2CMEIwBQYDK2VvAzkA-hDvk1duBi1hlOr08VWSI-Ou4JNNSQjseY69QyKm7Kgg1zZjbpGfyBqSZ2eqys6xtoV4ZtoQUXQ%3D" - ) - } -} diff --git a/apps/ios/Shared/Views/NewChat/ConnectViaLinkView.swift b/apps/ios/Shared/Views/NewChat/ConnectViaLinkView.swift deleted file mode 100644 index 9df767485e..0000000000 --- a/apps/ios/Shared/Views/NewChat/ConnectViaLinkView.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// ConnectViaLinkView.swift -// SimpleX (iOS) -// -// Created by Evgeny on 21/09/2022. -// Copyright © 2022 SimpleX Chat. All rights reserved. -// - -import SwiftUI - -enum ConnectViaLinkTab: String { - case scan - case paste -} - -struct ConnectViaLinkView: View { - @State private var selection: ConnectViaLinkTab = connectViaLinkTabDefault.get() - - var body: some View { - TabView(selection: $selection) { - ScanToConnectView() - .tabItem { - Label("Scan QR code", systemImage: "qrcode") - } - .tag(ConnectViaLinkTab.scan) - PasteToConnectView() - .tabItem { - Label("Paste received link", systemImage: "doc.plaintext") - } - .tag(ConnectViaLinkTab.paste) - } - .onChange(of: selection) { _ in - connectViaLinkTabDefault.set(selection) - } - } -} - -struct ConnectViaLinkView_Previews: PreviewProvider { - static var previews: some View { - ConnectViaLinkView() - } -} diff --git a/apps/ios/Shared/Views/NewChat/CreateLinkView.swift b/apps/ios/Shared/Views/NewChat/CreateLinkView.swift deleted file mode 100644 index 3be9e1c3b3..0000000000 --- a/apps/ios/Shared/Views/NewChat/CreateLinkView.swift +++ /dev/null @@ -1,94 +0,0 @@ -// -// CreateLinkView.swift -// SimpleX (iOS) -// -// Created by Evgeny on 21/09/2022. -// Copyright © 2022 SimpleX Chat. All rights reserved. -// - -import SwiftUI -import SimpleXChat - -enum CreateLinkTab { - case oneTime - case longTerm - - var title: LocalizedStringKey { - switch self { - case .oneTime: return "One-time invitation link" - case .longTerm: return "Your SimpleX address" - } - } -} - -struct CreateLinkView: View { - @EnvironmentObject var m: ChatModel - @State var selection: CreateLinkTab - @State var connReqInvitation: String = "" - @State var contactConnection: PendingContactConnection? = nil - @State private var creatingConnReq = false - var viaNavLink = false - - var body: some View { - if viaNavLink { - createLinkView() - } else { - NavigationView { - createLinkView() - } - } - } - - private func createLinkView() -> some View { - TabView(selection: $selection) { - AddContactView(contactConnection: $contactConnection, connReqInvitation: connReqInvitation) - .tabItem { - Label( - connReqInvitation == "" - ? "Create one-time invitation link" - : "One-time invitation link", - systemImage: "1.circle" - ) - } - .tag(CreateLinkTab.oneTime) - UserAddressView(viaCreateLinkView: true) - .tabItem { - Label("Your SimpleX address", systemImage: "infinity.circle") - } - .tag(CreateLinkTab.longTerm) - } - .onChange(of: selection) { _ in - if case .oneTime = selection, connReqInvitation == "", contactConnection == nil && !creatingConnReq { - createInvitation() - } - } - .onAppear { m.connReqInv = connReqInvitation } - .onDisappear { m.connReqInv = nil } - .navigationTitle(selection.title) - .navigationBarTitleDisplayMode(.large) - } - - private func createInvitation() { - creatingConnReq = true - Task { - if let (connReq, pcc) = await apiAddContact(incognito: incognitoGroupDefault.get()) { - await MainActor.run { - m.updateContactConnection(pcc) - connReqInvitation = connReq - contactConnection = pcc - m.connReqInv = connReq - } - } else { - await MainActor.run { - creatingConnReq = false - } - } - } - } -} - -struct CreateLinkView_Previews: PreviewProvider { - static var previews: some View { - CreateLinkView(selection: CreateLinkTab.oneTime) - } -} diff --git a/apps/ios/Shared/Views/NewChat/NewChatButton.swift b/apps/ios/Shared/Views/NewChat/NewChatButton.swift deleted file mode 100644 index 170805b488..0000000000 --- a/apps/ios/Shared/Views/NewChat/NewChatButton.swift +++ /dev/null @@ -1,466 +0,0 @@ -// -// NewChatButton.swift -// SimpleX -// -// Created by Evgeny Poberezkin on 31/01/2022. -// Copyright © 2022 SimpleX Chat. All rights reserved. -// - -import SwiftUI -import SimpleXChat - -enum NewChatAction: Identifiable { - case createLink(link: String, connection: PendingContactConnection) - case connectViaLink - case createGroup - - var id: String { - switch self { - case let .createLink(link, _): return "createLink \(link)" - case .connectViaLink: return "connectViaLink" - case .createGroup: return "createGroup" - } - } -} - -struct NewChatButton: View { - @Binding var showAddChat: Bool - @State private var actionSheet: NewChatAction? - - var body: some View { - Button { showAddChat = true } label: { - Image(systemName: "square.and.pencil") - .resizable() - .scaledToFit() - .frame(width: 24, height: 24) - } - .confirmationDialog("Start a new chat", isPresented: $showAddChat, titleVisibility: .visible) { - Button("Share one-time invitation link") { addContactAction() } - Button("Connect via link / QR code") { actionSheet = .connectViaLink } - Button("Create secret group") { actionSheet = .createGroup } - } - .sheet(item: $actionSheet) { sheet in - switch sheet { - case let .createLink(link, pcc): - CreateLinkView(selection: .oneTime, connReqInvitation: link, contactConnection: pcc) - case .connectViaLink: ConnectViaLinkView() - case .createGroup: AddGroupView() - } - } - } - - func addContactAction() { - Task { - if let (connReq, pcc) = await apiAddContact(incognito: incognitoGroupDefault.get()) { - await MainActor.run { - ChatModel.shared.updateContactConnection(pcc) - } - actionSheet = .createLink(link: connReq, connection: pcc) - } - } - } -} - -enum PlanAndConnectAlert: Identifiable { - case ownInvitationLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool) - case invitationLinkConnecting(connectionLink: String) - case ownContactAddressConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool) - case contactAddressConnectingConfirmReconnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool) - case groupLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool) - case groupLinkConnectingConfirmReconnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool) - case groupLinkConnecting(connectionLink: String, groupInfo: GroupInfo?) - - var id: String { - switch self { - case let .ownInvitationLinkConfirmConnect(connectionLink, _, _): return "ownInvitationLinkConfirmConnect \(connectionLink)" - case let .invitationLinkConnecting(connectionLink): return "invitationLinkConnecting \(connectionLink)" - case let .ownContactAddressConfirmConnect(connectionLink, _, _): return "ownContactAddressConfirmConnect \(connectionLink)" - case let .contactAddressConnectingConfirmReconnect(connectionLink, _, _): return "contactAddressConnectingConfirmReconnect \(connectionLink)" - case let .groupLinkConfirmConnect(connectionLink, _, _): return "groupLinkConfirmConnect \(connectionLink)" - case let .groupLinkConnectingConfirmReconnect(connectionLink, _, _): return "groupLinkConnectingConfirmReconnect \(connectionLink)" - case let .groupLinkConnecting(connectionLink, _): return "groupLinkConnecting \(connectionLink)" - } - } -} - -func planAndConnectAlert(_ alert: PlanAndConnectAlert, dismiss: Bool) -> Alert { - switch alert { - case let .ownInvitationLinkConfirmConnect(connectionLink, connectionPlan, incognito): - return Alert( - title: Text("Connect to yourself?"), - message: Text("This is your own one-time link!"), - primaryButton: .destructive( - Text(incognito ? "Connect incognito" : "Connect"), - action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito) } - ), - secondaryButton: .cancel() - ) - case .invitationLinkConnecting: - return Alert( - title: Text("Already connecting!"), - message: Text("You are already connecting via this one-time link!") - ) - case let .ownContactAddressConfirmConnect(connectionLink, connectionPlan, incognito): - return Alert( - title: Text("Connect to yourself?"), - message: Text("This is your own SimpleX address!"), - primaryButton: .destructive( - Text(incognito ? "Connect incognito" : "Connect"), - action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito) } - ), - secondaryButton: .cancel() - ) - case let .contactAddressConnectingConfirmReconnect(connectionLink, connectionPlan, incognito): - return Alert( - title: Text("Repeat connection request?"), - message: Text("You have already requested connection via this address!"), - primaryButton: .destructive( - Text(incognito ? "Connect incognito" : "Connect"), - action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito) } - ), - secondaryButton: .cancel() - ) - case let .groupLinkConfirmConnect(connectionLink, connectionPlan, incognito): - return Alert( - title: Text("Join group?"), - message: Text("You will connect to all group members."), - primaryButton: .default( - Text(incognito ? "Join incognito" : "Join"), - action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito) } - ), - secondaryButton: .cancel() - ) - case let .groupLinkConnectingConfirmReconnect(connectionLink, connectionPlan, incognito): - return Alert( - title: Text("Repeat join request?"), - message: Text("You are already joining the group via this link!"), - primaryButton: .destructive( - Text(incognito ? "Join incognito" : "Join"), - action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito) } - ), - secondaryButton: .cancel() - ) - case let .groupLinkConnecting(_, groupInfo): - if let groupInfo = groupInfo { - return Alert( - title: Text("Group already exists!"), - message: Text("You are already joining the group \(groupInfo.displayName).") - ) - } else { - return Alert( - title: Text("Already joining the group!"), - message: Text("You are already joining the group via this link.") - ) - } - } -} - -enum PlanAndConnectActionSheet: Identifiable { - case askCurrentOrIncognitoProfile(connectionLink: String, connectionPlan: ConnectionPlan?, title: LocalizedStringKey) - case askCurrentOrIncognitoProfileDestructive(connectionLink: String, connectionPlan: ConnectionPlan, title: LocalizedStringKey) - case askCurrentOrIncognitoProfileConnectContactViaAddress(contact: Contact) - case ownGroupLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool?, groupInfo: GroupInfo) - - var id: String { - switch self { - case let .askCurrentOrIncognitoProfile(connectionLink, _, _): return "askCurrentOrIncognitoProfile \(connectionLink)" - case let .askCurrentOrIncognitoProfileDestructive(connectionLink, _, _): return "askCurrentOrIncognitoProfileDestructive \(connectionLink)" - case let .askCurrentOrIncognitoProfileConnectContactViaAddress(contact): return "askCurrentOrIncognitoProfileConnectContactViaAddress \(contact.contactId)" - case let .ownGroupLinkConfirmConnect(connectionLink, _, _, _): return "ownGroupLinkConfirmConnect \(connectionLink)" - } - } -} - -func planAndConnectActionSheet(_ sheet: PlanAndConnectActionSheet, dismiss: Bool) -> ActionSheet { - switch sheet { - case let .askCurrentOrIncognitoProfile(connectionLink, connectionPlan, title): - return ActionSheet( - title: Text(title), - buttons: [ - .default(Text("Use current profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: false) }, - .default(Text("Use new incognito profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: true) }, - .cancel() - ] - ) - case let .askCurrentOrIncognitoProfileDestructive(connectionLink, connectionPlan, title): - return ActionSheet( - title: Text(title), - buttons: [ - .destructive(Text("Use current profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: false) }, - .destructive(Text("Use new incognito profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: true) }, - .cancel() - ] - ) - case let .askCurrentOrIncognitoProfileConnectContactViaAddress(contact): - return ActionSheet( - title: Text("Connect with \(contact.chatViewName)"), - buttons: [ - .default(Text("Use current profile")) { connectContactViaAddress_(contact, dismiss: dismiss, incognito: false) }, - .default(Text("Use new incognito profile")) { connectContactViaAddress_(contact, dismiss: dismiss, incognito: true) }, - .cancel() - ] - ) - case let .ownGroupLinkConfirmConnect(connectionLink, connectionPlan, incognito, groupInfo): - if let incognito = incognito { - return ActionSheet( - title: Text("Join your group?\nThis is your link for group \(groupInfo.displayName)!"), - buttons: [ - .default(Text("Open group")) { openKnownGroup(groupInfo, dismiss: dismiss, showAlreadyExistsAlert: nil) }, - .destructive(Text(incognito ? "Join incognito" : "Join with current profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito) }, - .cancel() - ] - ) - } else { - return ActionSheet( - title: Text("Join your group?\nThis is your link for group \(groupInfo.displayName)!"), - buttons: [ - .default(Text("Open group")) { openKnownGroup(groupInfo, dismiss: dismiss, showAlreadyExistsAlert: nil) }, - .destructive(Text("Use current profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: false) }, - .destructive(Text("Use new incognito profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: true) }, - .cancel() - ] - ) - } - } -} - -func planAndConnect( - _ connectionLink: String, - showAlert: @escaping (PlanAndConnectAlert) -> Void, - showActionSheet: @escaping (PlanAndConnectActionSheet) -> Void, - dismiss: Bool, - incognito: Bool? -) { - Task { - do { - let connectionPlan = try await apiConnectPlan(connReq: connectionLink) - switch connectionPlan { - case let .invitationLink(ilp): - switch ilp { - case .ok: - logger.debug("planAndConnect, .invitationLink, .ok, incognito=\(incognito?.description ?? "nil")") - if let incognito = incognito { - connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito) - } else { - showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect via one-time link")) - } - case .ownLink: - logger.debug("planAndConnect, .invitationLink, .ownLink, incognito=\(incognito?.description ?? "nil")") - if let incognito = incognito { - showAlert(.ownInvitationLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) - } else { - showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own one-time link!")) - } - case let .connecting(contact_): - logger.debug("planAndConnect, .invitationLink, .connecting, incognito=\(incognito?.description ?? "nil")") - if let contact = contact_ { - openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyConnectingAlert(contact)) } - } else { - showAlert(.invitationLinkConnecting(connectionLink: connectionLink)) - } - case let .known(contact): - logger.debug("planAndConnect, .invitationLink, .known, incognito=\(incognito?.description ?? "nil")") - openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyExistsAlert(contact)) } - } - case let .contactAddress(cap): - switch cap { - case .ok: - logger.debug("planAndConnect, .contactAddress, .ok, incognito=\(incognito?.description ?? "nil")") - if let incognito = incognito { - connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito) - } else { - showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect via contact address")) - } - case .ownLink: - logger.debug("planAndConnect, .contactAddress, .ownLink, incognito=\(incognito?.description ?? "nil")") - if let incognito = incognito { - showAlert(.ownContactAddressConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) - } else { - showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own SimpleX address!")) - } - case .connectingConfirmReconnect: - logger.debug("planAndConnect, .contactAddress, .connectingConfirmReconnect, incognito=\(incognito?.description ?? "nil")") - if let incognito = incognito { - showAlert(.contactAddressConnectingConfirmReconnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) - } else { - showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "You have already requested connection!\nRepeat connection request?")) - } - case let .connectingProhibit(contact): - logger.debug("planAndConnect, .contactAddress, .connectingProhibit, incognito=\(incognito?.description ?? "nil")") - openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyConnectingAlert(contact)) } - case let .known(contact): - logger.debug("planAndConnect, .contactAddress, .known, incognito=\(incognito?.description ?? "nil")") - openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyExistsAlert(contact)) } - case let .contactViaAddress(contact): - logger.debug("planAndConnect, .contactAddress, .contactViaAddress, incognito=\(incognito?.description ?? "nil")") - if let incognito = incognito { - connectContactViaAddress_(contact, dismiss: dismiss, incognito: incognito) - } else { - showActionSheet(.askCurrentOrIncognitoProfileConnectContactViaAddress(contact: contact)) - } - } - case let .groupLink(glp): - switch glp { - case .ok: - if let incognito = incognito { - showAlert(.groupLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) - } else { - showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Join group")) - } - case let .ownLink(groupInfo): - logger.debug("planAndConnect, .groupLink, .ownLink, incognito=\(incognito?.description ?? "nil")") - showActionSheet(.ownGroupLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito, groupInfo: groupInfo)) - case .connectingConfirmReconnect: - logger.debug("planAndConnect, .groupLink, .connectingConfirmReconnect, incognito=\(incognito?.description ?? "nil")") - if let incognito = incognito { - showAlert(.groupLinkConnectingConfirmReconnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) - } else { - showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "You are already joining the group!\nRepeat join request?")) - } - case let .connectingProhibit(groupInfo_): - logger.debug("planAndConnect, .groupLink, .connectingProhibit, incognito=\(incognito?.description ?? "nil")") - showAlert(.groupLinkConnecting(connectionLink: connectionLink, groupInfo: groupInfo_)) - case let .known(groupInfo): - logger.debug("planAndConnect, .groupLink, .known, incognito=\(incognito?.description ?? "nil")") - openKnownGroup(groupInfo, dismiss: dismiss) { AlertManager.shared.showAlert(groupAlreadyExistsAlert(groupInfo)) } - } - } - } catch { - logger.debug("planAndConnect, plan error") - if let incognito = incognito { - connectViaLink(connectionLink, connectionPlan: nil, dismiss: dismiss, incognito: incognito) - } else { - showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: nil, title: "Connect via link")) - } - } - } -} - -private func connectContactViaAddress_(_ contact: Contact, dismiss: Bool, incognito: Bool) { - Task { - if dismiss { - DispatchQueue.main.async { - dismissAllSheets(animated: true) - } - } - _ = await connectContactViaAddress(contact.contactId, incognito) - } -} - -private func connectViaLink(_ connectionLink: String, connectionPlan: ConnectionPlan?, dismiss: Bool, incognito: Bool) { - Task { - if let (connReqType, pcc) = await apiConnect(incognito: incognito, connReq: connectionLink) { - await MainActor.run { - ChatModel.shared.updateContactConnection(pcc) - } - let crt: ConnReqType - if let plan = connectionPlan { - crt = planToConnReqType(plan) - } else { - crt = connReqType - } - DispatchQueue.main.async { - if dismiss { - dismissAllSheets(animated: true) { - AlertManager.shared.showAlert(connReqSentAlert(crt)) - } - } else { - AlertManager.shared.showAlert(connReqSentAlert(crt)) - } - } - } else { - if dismiss { - DispatchQueue.main.async { - dismissAllSheets(animated: true) - } - } - } - } -} - -func openKnownContact(_ contact: Contact, dismiss: Bool, showAlreadyExistsAlert: (() -> Void)?) { - Task { - let m = ChatModel.shared - if let c = m.getContactChat(contact.contactId) { - DispatchQueue.main.async { - if dismiss { - dismissAllSheets(animated: true) { - m.chatId = c.id - showAlreadyExistsAlert?() - } - } else { - m.chatId = c.id - showAlreadyExistsAlert?() - } - } - } - } -} - -func openKnownGroup(_ groupInfo: GroupInfo, dismiss: Bool, showAlreadyExistsAlert: (() -> Void)?) { - Task { - let m = ChatModel.shared - if let g = m.getGroupChat(groupInfo.groupId) { - DispatchQueue.main.async { - if dismiss { - dismissAllSheets(animated: true) { - m.chatId = g.id - showAlreadyExistsAlert?() - } - } else { - m.chatId = g.id - showAlreadyExistsAlert?() - } - } - } - } -} - -func contactAlreadyConnectingAlert(_ contact: Contact) -> Alert { - mkAlert( - title: "Contact already exists", - message: "You are already connecting to \(contact.displayName)." - ) -} - -func groupAlreadyExistsAlert(_ groupInfo: GroupInfo) -> Alert { - mkAlert( - title: "Group already exists", - message: "You are already in group \(groupInfo.displayName)." - ) -} - -enum ConnReqType: Equatable { - case invitation - case contact - case groupLink - - var connReqSentText: LocalizedStringKey { - switch self { - case .invitation: return "You will be connected when your contact's device is online, please wait or check later!" - case .contact: return "You will be connected when your connection request is accepted, please wait or check later!" - case .groupLink: return "You will be connected when group link host's device is online, please wait or check later!" - } - } -} - -private func planToConnReqType(_ connectionPlan: ConnectionPlan) -> ConnReqType { - switch connectionPlan { - case .invitationLink: return .invitation - case .contactAddress: return .contact - case .groupLink: return .groupLink - } -} - -func connReqSentAlert(_ type: ConnReqType) -> Alert { - return mkAlert( - title: "Connection request sent!", - message: type.connReqSentText - ) -} - -struct NewChatButton_Previews: PreviewProvider { - static var previews: some View { - NewChatButton(showAddChat: Binding.constant(false)) - } -} diff --git a/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift b/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift new file mode 100644 index 0000000000..c3452ce18d --- /dev/null +++ b/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift @@ -0,0 +1,52 @@ +// +// NewChatMenuButton.swift +// SimpleX (iOS) +// +// Created by spaced4ndy on 28.11.2023. +// Copyright © 2023 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +enum NewChatMenuOption: Identifiable { + case newContact + case newGroup + + var id: Self { self } +} + +struct NewChatMenuButton: View { + @Binding var newChatMenuOption: NewChatMenuOption? + + var body: some View { + Menu { + Button { + newChatMenuOption = .newContact + } label: { + Text("Add contact") + } + Button { + newChatMenuOption = .newGroup + } label: { + Text("Create group") + } + } label: { + Image(systemName: "square.and.pencil") + .resizable() + .scaledToFit() + .frame(width: 24, height: 24) + } + .sheet(item: $newChatMenuOption) { opt in + switch opt { + case .newContact: NewChatView(selection: .invite) + case .newGroup: AddGroupView() + } + } + } +} + +#Preview { + NewChatMenuButton( + newChatMenuOption: Binding.constant(nil) + ) +} diff --git a/apps/ios/Shared/Views/NewChat/NewChatView.swift b/apps/ios/Shared/Views/NewChat/NewChatView.swift new file mode 100644 index 0000000000..b78d92ffc8 --- /dev/null +++ b/apps/ios/Shared/Views/NewChat/NewChatView.swift @@ -0,0 +1,959 @@ +// +// NewChatView.swift +// SimpleX (iOS) +// +// Created by spaced4ndy on 28.11.2023. +// Copyright © 2023 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat +import CodeScanner +import AVFoundation + +enum SomeAlert: Identifiable { + case someAlert(alert: Alert, id: String) + + var id: String { + switch self { + case let .someAlert(_, id): return id + } + } +} + +private enum NewChatViewAlert: Identifiable { + case planAndConnectAlert(alert: PlanAndConnectAlert) + case newChatSomeAlert(alert: SomeAlert) + + var id: String { + switch self { + case let .planAndConnectAlert(alert): return "planAndConnectAlert \(alert.id)" + case let .newChatSomeAlert(alert): return "newChatSomeAlert \(alert.id)" + } + } +} + +enum NewChatOption: Identifiable { + case invite + case connect + + var id: Self { self } +} + +struct NewChatView: View { + @EnvironmentObject var m: ChatModel + @State var selection: NewChatOption + @State var showQRCodeScanner = false + @State private var invitationUsed: Bool = false + @State private var contactConnection: PendingContactConnection? = nil + @State private var connReqInvitation: String = "" + @State private var creatingConnReq = false + @State private var pastedLink: String = "" + @State private var alert: NewChatViewAlert? + + var body: some View { + VStack(alignment: .leading) { + HStack { + Text("New chat") + .font(.largeTitle) + .bold() + .fixedSize(horizontal: false, vertical: true) + Spacer() + InfoSheetButton { + AddContactLearnMore(showTitle: true) + } + } + .padding() + .padding(.top) + + Picker("New chat", selection: $selection) { + Label("Add contact", systemImage: "link") + .tag(NewChatOption.invite) + Label("Connect via link", systemImage: "qrcode") + .tag(NewChatOption.connect) + } + .pickerStyle(.segmented) + .padding() + + VStack { + // it seems there's a bug in iOS 15 if several views in switch (or if-else) statement have different transitions + // https://developer.apple.com/forums/thread/714977?answerId=731615022#731615022 + if case .invite = selection { + prepareAndInviteView() + .transition(.move(edge: .leading)) + .onAppear { + createInvitation() + } + } + if case .connect = selection { + ConnectView(showQRCodeScanner: showQRCodeScanner, pastedLink: $pastedLink, alert: $alert) + .transition(.move(edge: .trailing)) + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background( + // Rectangle is needed for swipe gesture to work on mostly empty views (creatingLinkProgressView and retryButton) + Rectangle() + .fill(Color(uiColor: .systemGroupedBackground)) + ) + .animation(.easeInOut(duration: 0.3333), value: selection) + .gesture(DragGesture(minimumDistance: 20.0, coordinateSpace: .local) + .onChanged { value in + switch(value.translation.width, value.translation.height) { + case (...0, -30...30): // left swipe + if selection == .invite { + selection = .connect + } + case (0..., -30...30): // right swipe + if selection == .connect { + selection = .invite + } + default: () + } + } + ) + } + .background(Color(.systemGroupedBackground)) + .onChange(of: invitationUsed) { used in + if used && !(m.showingInvitation?.connChatUsed ?? true) { + m.markShowingInvitationUsed() + } + } + .onDisappear { + if !(m.showingInvitation?.connChatUsed ?? true), + let conn = contactConnection { + AlertManager.shared.showAlert(Alert( + title: Text("Keep unused invitation?"), + message: Text("You can view invitation link again in connection details."), + primaryButton: .default(Text("Keep")) {}, + secondaryButton: .destructive(Text("Delete")) { + Task { + await deleteChat(Chat( + chatInfo: .contactConnection(contactConnection: conn), + chatItems: [] + )) + } + } + )) + } + m.showingInvitation = nil + } + .alert(item: $alert) { a in + switch(a) { + case let .planAndConnectAlert(alert): + return planAndConnectAlert(alert, dismiss: true, cleanup: { pastedLink = "" }) + case let .newChatSomeAlert(.someAlert(alert, _)): + return alert + } + } + } + + private func prepareAndInviteView() -> some View { + ZStack { // ZStack is needed for views to not make transitions between each other + if connReqInvitation != "" { + InviteView( + invitationUsed: $invitationUsed, + contactConnection: $contactConnection, + connReqInvitation: connReqInvitation + ) + } else if creatingConnReq { + creatingLinkProgressView() + } else { + retryButton() + } + } + } + + private func createInvitation() { + if connReqInvitation == "" && contactConnection == nil && !creatingConnReq { + creatingConnReq = true + Task { + _ = try? await Task.sleep(nanoseconds: 250_000000) + let (r, apiAlert) = await apiAddContact(incognito: incognitoGroupDefault.get()) + if let (connReq, pcc) = r { + await MainActor.run { + m.updateContactConnection(pcc) + m.showingInvitation = ShowingInvitation(connId: pcc.id, connChatUsed: false) + connReqInvitation = connReq + contactConnection = pcc + } + } else { + await MainActor.run { + creatingConnReq = false + if let apiAlert = apiAlert { + alert = .newChatSomeAlert(alert: .someAlert(alert: apiAlert, id: "createInvitation error")) + } + } + } + } + } + } + + // Rectangle here and in retryButton are needed for gesture to work + private func creatingLinkProgressView() -> some View { + ProgressView("Creating link…") + .progressViewStyle(.circular) + } + + private func retryButton() -> some View { + Button(action: createInvitation) { + VStack(spacing: 6) { + Image(systemName: "arrow.counterclockwise") + Text("Retry") + } + } + } +} + +private struct InviteView: View { + @EnvironmentObject var chatModel: ChatModel + @Binding var invitationUsed: Bool + @Binding var contactConnection: PendingContactConnection? + var connReqInvitation: String + @AppStorage(GROUP_DEFAULT_INCOGNITO, store: groupDefaults) private var incognitoDefault = false + + var body: some View { + List { + Section("Share this 1-time invite link") { + shareLinkView() + } + .listRowInsets(EdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 10)) + + qrCodeView() + + Section { + IncognitoToggle(incognitoEnabled: $incognitoDefault) + } footer: { + sharedProfileInfo(incognitoDefault) + } + } + .onChange(of: incognitoDefault) { incognito in + Task { + do { + if let contactConn = contactConnection, + let conn = try await apiSetConnectionIncognito(connId: contactConn.pccConnId, incognito: incognito) { + await MainActor.run { + contactConnection = conn + chatModel.updateContactConnection(conn) + } + } + } catch { + logger.error("apiSetConnectionIncognito error: \(responseError(error))") + } + } + setInvitationUsed() + } + } + + private func shareLinkView() -> some View { + HStack { + let link = simplexChatLink(connReqInvitation) + linkTextView(link) + Button { + showShareSheet(items: [link]) + setInvitationUsed() + } label: { + Image(systemName: "square.and.arrow.up") + .padding(.top, -7) + } + } + .frame(maxWidth: .infinity) + } + + private func qrCodeView() -> some View { + Section("Or show this code") { + SimpleXLinkQRCode(uri: connReqInvitation, onShare: setInvitationUsed) + .padding() + .background( + RoundedRectangle(cornerRadius: 12, style: .continuous) + .fill(Color(uiColor: .secondarySystemGroupedBackground)) + ) + .padding(.horizontal) + .listRowBackground(Color.clear) + .listRowSeparator(.hidden) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + } + } + + private func setInvitationUsed() { + if !invitationUsed { + invitationUsed = true + } + } +} + +private struct ConnectView: View { + @Environment(\.dismiss) var dismiss: DismissAction + @State var showQRCodeScanner = false + @State private var cameraAuthorizationStatus: AVAuthorizationStatus? + @Binding var pastedLink: String + @Binding var alert: NewChatViewAlert? + @State private var sheet: PlanAndConnectActionSheet? + + var body: some View { + List { + Section("Paste the link you received") { + pasteLinkView() + } + + scanCodeView() + } + .actionSheet(item: $sheet) { s in + planAndConnectActionSheet(s, dismiss: true, cleanup: { pastedLink = "" }) + } + .onAppear { + let status = AVCaptureDevice.authorizationStatus(for: .video) + cameraAuthorizationStatus = status + if showQRCodeScanner { + switch status { + case .notDetermined: askCameraAuthorization() + case .restricted: showQRCodeScanner = false + case .denied: showQRCodeScanner = false + case .authorized: () + @unknown default: askCameraAuthorization() + } + } + } + } + + func askCameraAuthorization(_ cb: (() -> Void)? = nil) { + AVCaptureDevice.requestAccess(for: .video) { allowed in + cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: .video) + if allowed { cb?() } + } + } + + @ViewBuilder private func pasteLinkView() -> some View { + if pastedLink == "" { + Button { + if let str = UIPasteboard.general.string { + if let link = strHasSingleSimplexLink(str.trimmingCharacters(in: .whitespaces)) { + pastedLink = link.text + // It would be good to hide it, but right now it is not clear how to release camera in CodeScanner + // https://github.com/twostraws/CodeScanner/issues/121 + // No known tricks worked (changing view ID, wrapping it in another view, etc.) + // showQRCodeScanner = false + connect(pastedLink) + } else { + alert = .newChatSomeAlert(alert: .someAlert( + alert: mkAlert(title: "Invalid link", message: "The text you pasted is not a SimpleX link."), + id: "pasteLinkView: code is not a SimpleX link" + )) + } + } + } label: { + Text("Tap to paste link") + } + .disabled(!ChatModel.shared.pasteboardHasStrings) + .frame(maxWidth: .infinity, alignment: .center) + } else { + linkTextView(pastedLink) + } + } + + private func scanCodeView() -> some View { + Section("Or scan QR code") { + if showQRCodeScanner, case .authorized = cameraAuthorizationStatus { + CodeScannerView(codeTypes: [.qr], scanMode: .continuous, completion: processQRCode) + .aspectRatio(1, contentMode: .fit) + .cornerRadius(12) + .listRowBackground(Color.clear) + .listRowSeparator(.hidden) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + .padding(.horizontal) + } else { + Button { + switch cameraAuthorizationStatus { + case .notDetermined: askCameraAuthorization { showQRCodeScanner = true } + case .restricted: () + case .denied: UIApplication.shared.open(appSettingsURL) + case .authorized: showQRCodeScanner = true + default: askCameraAuthorization { showQRCodeScanner = true } + } + } label: { + ZStack { + Rectangle() + .aspectRatio(contentMode: .fill) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .foregroundColor(Color.clear) + switch cameraAuthorizationStatus { + case .restricted: Text("Camera not available") + case .denied: Label("Enable camera access", systemImage: "camera") + default: Label("Tap to scan", systemImage: "qrcode") + } + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) + .padding() + .background( + RoundedRectangle(cornerRadius: 12, style: .continuous) + .fill(Color(uiColor: .secondarySystemGroupedBackground)) + ) + .padding(.horizontal) + .listRowBackground(Color.clear) + .listRowSeparator(.hidden) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + .disabled(cameraAuthorizationStatus == .restricted) + } + } + } + + private func processQRCode(_ resp: Result) { + switch resp { + case let .success(r): + let link = r.string + if strIsSimplexLink(r.string) { + connect(link) + } else { + alert = .newChatSomeAlert(alert: .someAlert( + alert: mkAlert(title: "Invalid QR code", message: "The code you scanned is not a SimpleX link QR code."), + id: "processQRCode: code is not a SimpleX link" + )) + } + case let .failure(e): + logger.error("processQRCode QR code error: \(e.localizedDescription)") + alert = .newChatSomeAlert(alert: .someAlert( + alert: mkAlert(title: "Invalid QR code", message: "Error scanning code: \(e.localizedDescription)"), + id: "processQRCode: failure" + )) + } + } + + private func connect(_ link: String) { + planAndConnect( + link, + showAlert: { alert = .planAndConnectAlert(alert: $0) }, + showActionSheet: { sheet = $0 }, + dismiss: true, + incognito: nil + ) + } +} + +private func linkTextView(_ link: String) -> some View { + Text(link) + .lineLimit(1) + .font(.caption) + .truncationMode(.middle) +} + +struct InfoSheetButton: View { + @ViewBuilder let content: Content + @State private var showInfoSheet = false + + var body: some View { + Button { + showInfoSheet = true + } label: { + Image(systemName: "info.circle") + .resizable() + .scaledToFit() + .frame(width: 24, height: 24) + } + .sheet(isPresented: $showInfoSheet) { + content + } + } +} + +func strIsSimplexLink(_ str: String) -> Bool { + if let parsedMd = parseSimpleXMarkdown(str), + parsedMd.count == 1, + case .simplexLink = parsedMd[0].format { + return true + } else { + return false + } +} + +func strHasSingleSimplexLink(_ str: String) -> FormattedText? { + if let parsedMd = parseSimpleXMarkdown(str) { + let parsedLinks = parsedMd.filter({ $0.format?.isSimplexLink ?? false }) + if parsedLinks.count == 1 { + return parsedLinks[0] + } else { + return nil + } + } else { + return nil + } +} + +struct IncognitoToggle: View { + @Binding var incognitoEnabled: Bool + @State private var showIncognitoSheet = false + + var body: some View { + ZStack(alignment: .leading) { + Image(systemName: incognitoEnabled ? "theatermasks.fill" : "theatermasks") + .frame(maxWidth: 24, maxHeight: 24, alignment: .center) + .foregroundColor(incognitoEnabled ? Color.indigo : .secondary) + .font(.system(size: 14)) + Toggle(isOn: $incognitoEnabled) { + HStack(spacing: 6) { + Text("Incognito") + Image(systemName: "info.circle") + .foregroundColor(.accentColor) + .font(.system(size: 14)) + } + .onTapGesture { + showIncognitoSheet = true + } + } + .padding(.leading, 36) + } + .sheet(isPresented: $showIncognitoSheet) { + IncognitoHelp() + } + } +} + +func sharedProfileInfo(_ incognito: Bool) -> Text { + let name = ChatModel.shared.currentUser?.displayName ?? "" + return Text( + incognito + ? "A new random profile will be shared." + : "Your profile **\(name)** will be shared." + ) +} + +enum PlanAndConnectAlert: Identifiable { + case ownInvitationLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool) + case invitationLinkConnecting(connectionLink: String) + case ownContactAddressConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool) + case contactAddressConnectingConfirmReconnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool) + case groupLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool) + case groupLinkConnectingConfirmReconnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool) + case groupLinkConnecting(connectionLink: String, groupInfo: GroupInfo?) + + var id: String { + switch self { + case let .ownInvitationLinkConfirmConnect(connectionLink, _, _): return "ownInvitationLinkConfirmConnect \(connectionLink)" + case let .invitationLinkConnecting(connectionLink): return "invitationLinkConnecting \(connectionLink)" + case let .ownContactAddressConfirmConnect(connectionLink, _, _): return "ownContactAddressConfirmConnect \(connectionLink)" + case let .contactAddressConnectingConfirmReconnect(connectionLink, _, _): return "contactAddressConnectingConfirmReconnect \(connectionLink)" + case let .groupLinkConfirmConnect(connectionLink, _, _): return "groupLinkConfirmConnect \(connectionLink)" + case let .groupLinkConnectingConfirmReconnect(connectionLink, _, _): return "groupLinkConnectingConfirmReconnect \(connectionLink)" + case let .groupLinkConnecting(connectionLink, _): return "groupLinkConnecting \(connectionLink)" + } + } +} + +func planAndConnectAlert(_ alert: PlanAndConnectAlert, dismiss: Bool, cleanup: (() -> Void)? = nil) -> Alert { + switch alert { + case let .ownInvitationLinkConfirmConnect(connectionLink, connectionPlan, incognito): + return Alert( + title: Text("Connect to yourself?"), + message: Text("This is your own one-time link!"), + primaryButton: .destructive( + Text(incognito ? "Connect incognito" : "Connect"), + action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup) } + ), + secondaryButton: .cancel() { cleanup?() } + ) + case .invitationLinkConnecting: + return Alert( + title: Text("Already connecting!"), + message: Text("You are already connecting via this one-time link!"), + dismissButton: .default(Text("OK")) { cleanup?() } + ) + case let .ownContactAddressConfirmConnect(connectionLink, connectionPlan, incognito): + return Alert( + title: Text("Connect to yourself?"), + message: Text("This is your own SimpleX address!"), + primaryButton: .destructive( + Text(incognito ? "Connect incognito" : "Connect"), + action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup) } + ), + secondaryButton: .cancel() { cleanup?() } + ) + case let .contactAddressConnectingConfirmReconnect(connectionLink, connectionPlan, incognito): + return Alert( + title: Text("Repeat connection request?"), + message: Text("You have already requested connection via this address!"), + primaryButton: .destructive( + Text(incognito ? "Connect incognito" : "Connect"), + action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup) } + ), + secondaryButton: .cancel() { cleanup?() } + ) + case let .groupLinkConfirmConnect(connectionLink, connectionPlan, incognito): + return Alert( + title: Text("Join group?"), + message: Text("You will connect to all group members."), + primaryButton: .default( + Text(incognito ? "Join incognito" : "Join"), + action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup) } + ), + secondaryButton: .cancel() { cleanup?() } + ) + case let .groupLinkConnectingConfirmReconnect(connectionLink, connectionPlan, incognito): + return Alert( + title: Text("Repeat join request?"), + message: Text("You are already joining the group via this link!"), + primaryButton: .destructive( + Text(incognito ? "Join incognito" : "Join"), + action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup) } + ), + secondaryButton: .cancel() { cleanup?() } + ) + case let .groupLinkConnecting(_, groupInfo): + if let groupInfo = groupInfo { + return Alert( + title: Text("Group already exists!"), + message: Text("You are already joining the group \(groupInfo.displayName)."), + dismissButton: .default(Text("OK")) { cleanup?() } + ) + } else { + return Alert( + title: Text("Already joining the group!"), + message: Text("You are already joining the group via this link."), + dismissButton: .default(Text("OK")) { cleanup?() } + ) + } + } +} + +enum PlanAndConnectActionSheet: Identifiable { + case askCurrentOrIncognitoProfile(connectionLink: String, connectionPlan: ConnectionPlan?, title: LocalizedStringKey) + case askCurrentOrIncognitoProfileDestructive(connectionLink: String, connectionPlan: ConnectionPlan, title: LocalizedStringKey) + case askCurrentOrIncognitoProfileConnectContactViaAddress(contact: Contact) + case ownGroupLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool?, groupInfo: GroupInfo) + + var id: String { + switch self { + case let .askCurrentOrIncognitoProfile(connectionLink, _, _): return "askCurrentOrIncognitoProfile \(connectionLink)" + case let .askCurrentOrIncognitoProfileDestructive(connectionLink, _, _): return "askCurrentOrIncognitoProfileDestructive \(connectionLink)" + case let .askCurrentOrIncognitoProfileConnectContactViaAddress(contact): return "askCurrentOrIncognitoProfileConnectContactViaAddress \(contact.contactId)" + case let .ownGroupLinkConfirmConnect(connectionLink, _, _, _): return "ownGroupLinkConfirmConnect \(connectionLink)" + } + } +} + +func planAndConnectActionSheet(_ sheet: PlanAndConnectActionSheet, dismiss: Bool, cleanup: (() -> Void)? = nil) -> ActionSheet { + switch sheet { + case let .askCurrentOrIncognitoProfile(connectionLink, connectionPlan, title): + return ActionSheet( + title: Text(title), + buttons: [ + .default(Text("Use current profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: false, cleanup: cleanup) }, + .default(Text("Use new incognito profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: true, cleanup: cleanup) }, + .cancel() { cleanup?() } + ] + ) + case let .askCurrentOrIncognitoProfileDestructive(connectionLink, connectionPlan, title): + return ActionSheet( + title: Text(title), + buttons: [ + .destructive(Text("Use current profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: false, cleanup: cleanup) }, + .destructive(Text("Use new incognito profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: true, cleanup: cleanup) }, + .cancel() { cleanup?() } + ] + ) + case let .askCurrentOrIncognitoProfileConnectContactViaAddress(contact): + return ActionSheet( + title: Text("Connect with \(contact.chatViewName)"), + buttons: [ + .default(Text("Use current profile")) { connectContactViaAddress_(contact, dismiss: dismiss, incognito: false, cleanup: cleanup) }, + .default(Text("Use new incognito profile")) { connectContactViaAddress_(contact, dismiss: dismiss, incognito: true, cleanup: cleanup) }, + .cancel() { cleanup?() } + ] + ) + case let .ownGroupLinkConfirmConnect(connectionLink, connectionPlan, incognito, groupInfo): + if let incognito = incognito { + return ActionSheet( + title: Text("Join your group?\nThis is your link for group \(groupInfo.displayName)!"), + buttons: [ + .default(Text("Open group")) { openKnownGroup(groupInfo, dismiss: dismiss, showAlreadyExistsAlert: nil) }, + .destructive(Text(incognito ? "Join incognito" : "Join with current profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup) }, + .cancel() { cleanup?() } + ] + ) + } else { + return ActionSheet( + title: Text("Join your group?\nThis is your link for group \(groupInfo.displayName)!"), + buttons: [ + .default(Text("Open group")) { openKnownGroup(groupInfo, dismiss: dismiss, showAlreadyExistsAlert: nil) }, + .destructive(Text("Use current profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: false, cleanup: cleanup) }, + .destructive(Text("Use new incognito profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: true, cleanup: cleanup) }, + .cancel() { cleanup?() } + ] + ) + } + } +} + +func planAndConnect( + _ connectionLink: String, + showAlert: @escaping (PlanAndConnectAlert) -> Void, + showActionSheet: @escaping (PlanAndConnectActionSheet) -> Void, + dismiss: Bool, + incognito: Bool?, + cleanup: (() -> Void)? = nil, + filterKnownContact: ((Contact) -> Void)? = nil, + filterKnownGroup: ((GroupInfo) -> Void)? = nil +) { + Task { + do { + let connectionPlan = try await apiConnectPlan(connReq: connectionLink) + switch connectionPlan { + case let .invitationLink(ilp): + switch ilp { + case .ok: + logger.debug("planAndConnect, .invitationLink, .ok, incognito=\(incognito?.description ?? "nil")") + if let incognito = incognito { + connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup) + } else { + showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect via one-time link")) + } + case .ownLink: + logger.debug("planAndConnect, .invitationLink, .ownLink, incognito=\(incognito?.description ?? "nil")") + if let incognito = incognito { + showAlert(.ownInvitationLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) + } else { + showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own one-time link!")) + } + case let .connecting(contact_): + logger.debug("planAndConnect, .invitationLink, .connecting, incognito=\(incognito?.description ?? "nil")") + if let contact = contact_ { + if let f = filterKnownContact { + f(contact) + } else { + openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyConnectingAlert(contact)) } + } + } else { + showAlert(.invitationLinkConnecting(connectionLink: connectionLink)) + } + case let .known(contact): + logger.debug("planAndConnect, .invitationLink, .known, incognito=\(incognito?.description ?? "nil")") + if let f = filterKnownContact { + f(contact) + } else { + openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyExistsAlert(contact)) } + } + } + case let .contactAddress(cap): + switch cap { + case .ok: + logger.debug("planAndConnect, .contactAddress, .ok, incognito=\(incognito?.description ?? "nil")") + if let incognito = incognito { + connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup) + } else { + showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect via contact address")) + } + case .ownLink: + logger.debug("planAndConnect, .contactAddress, .ownLink, incognito=\(incognito?.description ?? "nil")") + if let incognito = incognito { + showAlert(.ownContactAddressConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) + } else { + showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own SimpleX address!")) + } + case .connectingConfirmReconnect: + logger.debug("planAndConnect, .contactAddress, .connectingConfirmReconnect, incognito=\(incognito?.description ?? "nil")") + if let incognito = incognito { + showAlert(.contactAddressConnectingConfirmReconnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) + } else { + showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "You have already requested connection!\nRepeat connection request?")) + } + case let .connectingProhibit(contact): + logger.debug("planAndConnect, .contactAddress, .connectingProhibit, incognito=\(incognito?.description ?? "nil")") + if let f = filterKnownContact { + f(contact) + } else { + openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyConnectingAlert(contact)) } + } + case let .known(contact): + logger.debug("planAndConnect, .contactAddress, .known, incognito=\(incognito?.description ?? "nil")") + if let f = filterKnownContact { + f(contact) + } else { + openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyExistsAlert(contact)) } + } + case let .contactViaAddress(contact): + logger.debug("planAndConnect, .contactAddress, .contactViaAddress, incognito=\(incognito?.description ?? "nil")") + if let incognito = incognito { + connectContactViaAddress_(contact, dismiss: dismiss, incognito: incognito, cleanup: cleanup) + } else { + showActionSheet(.askCurrentOrIncognitoProfileConnectContactViaAddress(contact: contact)) + } + } + case let .groupLink(glp): + switch glp { + case .ok: + if let incognito = incognito { + showAlert(.groupLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) + } else { + showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Join group")) + } + case let .ownLink(groupInfo): + logger.debug("planAndConnect, .groupLink, .ownLink, incognito=\(incognito?.description ?? "nil")") + if let f = filterKnownGroup { + f(groupInfo) + } + showActionSheet(.ownGroupLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito, groupInfo: groupInfo)) + case .connectingConfirmReconnect: + logger.debug("planAndConnect, .groupLink, .connectingConfirmReconnect, incognito=\(incognito?.description ?? "nil")") + if let incognito = incognito { + showAlert(.groupLinkConnectingConfirmReconnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) + } else { + showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "You are already joining the group!\nRepeat join request?")) + } + case let .connectingProhibit(groupInfo_): + logger.debug("planAndConnect, .groupLink, .connectingProhibit, incognito=\(incognito?.description ?? "nil")") + showAlert(.groupLinkConnecting(connectionLink: connectionLink, groupInfo: groupInfo_)) + case let .known(groupInfo): + logger.debug("planAndConnect, .groupLink, .known, incognito=\(incognito?.description ?? "nil")") + if let f = filterKnownGroup { + f(groupInfo) + } else { + openKnownGroup(groupInfo, dismiss: dismiss) { AlertManager.shared.showAlert(groupAlreadyExistsAlert(groupInfo)) } + } + } + } + } catch { + logger.debug("planAndConnect, plan error") + if let incognito = incognito { + connectViaLink(connectionLink, connectionPlan: nil, dismiss: dismiss, incognito: incognito, cleanup: cleanup) + } else { + showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: nil, title: "Connect via link")) + } + } + } +} + +private func connectContactViaAddress_(_ contact: Contact, dismiss: Bool, incognito: Bool, cleanup: (() -> Void)? = nil) { + Task { + if dismiss { + DispatchQueue.main.async { + dismissAllSheets(animated: true) + } + } + _ = await connectContactViaAddress(contact.contactId, incognito) + cleanup?() + } +} + +private func connectViaLink( + _ connectionLink: String, + connectionPlan: ConnectionPlan?, + dismiss: Bool, + incognito: Bool, + cleanup: (() -> Void)? +) { + Task { + if let (connReqType, pcc) = await apiConnect(incognito: incognito, connReq: connectionLink) { + await MainActor.run { + ChatModel.shared.updateContactConnection(pcc) + } + let crt: ConnReqType + if let plan = connectionPlan { + crt = planToConnReqType(plan) + } else { + crt = connReqType + } + DispatchQueue.main.async { + if dismiss { + dismissAllSheets(animated: true) { + AlertManager.shared.showAlert(connReqSentAlert(crt)) + } + } else { + AlertManager.shared.showAlert(connReqSentAlert(crt)) + } + } + } else { + if dismiss { + DispatchQueue.main.async { + dismissAllSheets(animated: true) + } + } + } + cleanup?() + } +} + +func openKnownContact(_ contact: Contact, dismiss: Bool, showAlreadyExistsAlert: (() -> Void)?) { + Task { + let m = ChatModel.shared + if let c = m.getContactChat(contact.contactId) { + DispatchQueue.main.async { + if dismiss { + dismissAllSheets(animated: true) { + m.chatId = c.id + showAlreadyExistsAlert?() + } + } else { + m.chatId = c.id + showAlreadyExistsAlert?() + } + } + } + } +} + +func openKnownGroup(_ groupInfo: GroupInfo, dismiss: Bool, showAlreadyExistsAlert: (() -> Void)?) { + Task { + let m = ChatModel.shared + if let g = m.getGroupChat(groupInfo.groupId) { + DispatchQueue.main.async { + if dismiss { + dismissAllSheets(animated: true) { + m.chatId = g.id + showAlreadyExistsAlert?() + } + } else { + m.chatId = g.id + showAlreadyExistsAlert?() + } + } + } + } +} + +func contactAlreadyConnectingAlert(_ contact: Contact) -> Alert { + mkAlert( + title: "Contact already exists", + message: "You are already connecting to \(contact.displayName)." + ) +} + +func groupAlreadyExistsAlert(_ groupInfo: GroupInfo) -> Alert { + mkAlert( + title: "Group already exists", + message: "You are already in group \(groupInfo.displayName)." + ) +} + +enum ConnReqType: Equatable { + case invitation + case contact + case groupLink + + var connReqSentText: LocalizedStringKey { + switch self { + case .invitation: return "You will be connected when your contact's device is online, please wait or check later!" + case .contact: return "You will be connected when your connection request is accepted, please wait or check later!" + case .groupLink: return "You will be connected when group link host's device is online, please wait or check later!" + } + } +} + +private func planToConnReqType(_ connectionPlan: ConnectionPlan) -> ConnReqType { + switch connectionPlan { + case .invitationLink: return .invitation + case .contactAddress: return .contact + case .groupLink: return .groupLink + } +} + +func connReqSentAlert(_ type: ConnReqType) -> Alert { + return mkAlert( + title: "Connection request sent!", + message: type.connReqSentText + ) +} + +#Preview { + NewChatView( + selection: .invite + ) +} diff --git a/apps/ios/Shared/Views/NewChat/PasteToConnectView.swift b/apps/ios/Shared/Views/NewChat/PasteToConnectView.swift deleted file mode 100644 index 7c272fb631..0000000000 --- a/apps/ios/Shared/Views/NewChat/PasteToConnectView.swift +++ /dev/null @@ -1,106 +0,0 @@ -// -// PasteToConnectView.swift -// SimpleX (iOS) -// -// Created by Ian Davies on 22/04/2022. -// Copyright © 2022 SimpleX Chat. All rights reserved. -// - -import SwiftUI -import SimpleXChat - -struct PasteToConnectView: View { - @Environment(\.dismiss) var dismiss: DismissAction - @State private var connectionLink: String = "" - @AppStorage(GROUP_DEFAULT_INCOGNITO, store: groupDefaults) private var incognitoDefault = false - @FocusState private var linkEditorFocused: Bool - @State private var alert: PlanAndConnectAlert? - @State private var sheet: PlanAndConnectActionSheet? - - var body: some View { - List { - Text("Connect via link") - .font(.largeTitle) - .bold() - .fixedSize(horizontal: false, vertical: true) - .listRowBackground(Color.clear) - .listRowSeparator(.hidden) - .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) - .onTapGesture { linkEditorFocused = false } - - Section { - linkEditor() - - Button { - if connectionLink == "" { - connectionLink = UIPasteboard.general.string ?? "" - } else { - connectionLink = "" - } - } label: { - if connectionLink == "" { - settingsRow("doc.plaintext") { Text("Paste") } - } else { - settingsRow("multiply") { Text("Clear") } - } - } - - Button { - connect() - } label: { - settingsRow("link") { Text("Connect") } - } - .disabled(connectionLink == "" || connectionLink.trimmingCharacters(in: .whitespaces).firstIndex(of: " ") != nil) - - IncognitoToggle(incognitoEnabled: $incognitoDefault) - } footer: { - VStack(alignment: .leading, spacing: 4) { - sharedProfileInfo(incognitoDefault) - Text("You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button.") - } - .frame(maxWidth: .infinity, alignment: .leading) - } - } - .alert(item: $alert) { a in planAndConnectAlert(a, dismiss: true) } - .actionSheet(item: $sheet) { s in planAndConnectActionSheet(s, dismiss: true) } - } - - private func linkEditor() -> some View { - ZStack { - Group { - if connectionLink.isEmpty { - TextEditor(text: Binding.constant(NSLocalizedString("Paste the link you received to connect with your contact.", comment: "placeholder"))) - .foregroundColor(.secondary) - .disabled(true) - } - TextEditor(text: $connectionLink) - .onSubmit(connect) - .textInputAutocapitalization(.never) - .disableAutocorrection(true) - .focused($linkEditorFocused) - } - .allowsTightening(false) - .padding(.horizontal, -5) - .padding(.top, -8) - .frame(height: 180, alignment: .topLeading) - .frame(maxWidth: .infinity, alignment: .leading) - } - } - - private func connect() { - let link = connectionLink.trimmingCharacters(in: .whitespaces) - planAndConnect( - link, - showAlert: { alert = $0 }, - showActionSheet: { sheet = $0 }, - dismiss: true, - incognito: incognitoDefault - ) - } -} - -struct PasteToConnectView_Previews: PreviewProvider { - static var previews: some View { - PasteToConnectView() - } -} diff --git a/apps/ios/Shared/Views/NewChat/QRCode.swift b/apps/ios/Shared/Views/NewChat/QRCode.swift index 3ddb85079c..e3bae9287a 100644 --- a/apps/ios/Shared/Views/NewChat/QRCode.swift +++ b/apps/ios/Shared/Views/NewChat/QRCode.swift @@ -24,9 +24,10 @@ struct SimpleXLinkQRCode: View { let uri: String var withLogo: Bool = true var tintColor = UIColor(red: 0.023, green: 0.176, blue: 0.337, alpha: 1) + var onShare: (() -> Void)? = nil var body: some View { - QRCode(uri: simplexChatLink(uri), withLogo: withLogo, tintColor: tintColor) + QRCode(uri: simplexChatLink(uri), withLogo: withLogo, tintColor: tintColor, onShare: onShare) } } @@ -40,6 +41,7 @@ struct QRCode: View { let uri: String 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 = {} @@ -65,6 +67,7 @@ struct QRCode: View { makeScreenshotFunc = { let size = CGSizeMake(1024 / UIScreen.main.scale, 1024 / UIScreen.main.scale) showShareSheet(items: [makeScreenshot(geo.frame(in: .local).origin, size)]) + onShare?() } } .frame(width: geo.size.width, height: geo.size.height) diff --git a/apps/ios/Shared/Views/NewChat/ScanToConnectView.swift b/apps/ios/Shared/Views/NewChat/ScanToConnectView.swift deleted file mode 100644 index 7f3f5e02f8..0000000000 --- a/apps/ios/Shared/Views/NewChat/ScanToConnectView.swift +++ /dev/null @@ -1,79 +0,0 @@ -// -// ConnectContactView.swift -// SimpleX -// -// Created by Evgeny Poberezkin on 29/01/2022. -// Copyright © 2022 SimpleX Chat. All rights reserved. -// - -import SwiftUI -import SimpleXChat -import CodeScanner - -struct ScanToConnectView: View { - @Environment(\.dismiss) var dismiss: DismissAction - @AppStorage(GROUP_DEFAULT_INCOGNITO, store: groupDefaults) private var incognitoDefault = false - @State private var alert: PlanAndConnectAlert? - @State private var sheet: PlanAndConnectActionSheet? - - var body: some View { - ScrollView { - VStack(alignment: .leading) { - Text("Scan QR code") - .font(.largeTitle) - .bold() - .fixedSize(horizontal: false, vertical: true) - .padding(.vertical) - - CodeScannerView(codeTypes: [.qr], scanMode: .continuous, completion: processQRCode) - .aspectRatio(1, contentMode: .fit) - .cornerRadius(12) - - IncognitoToggle(incognitoEnabled: $incognitoDefault) - .padding(.horizontal) - .padding(.vertical, 6) - .background( - RoundedRectangle(cornerRadius: 12, style: .continuous) - .fill(Color(uiColor: .systemBackground)) - ) - .padding(.top) - - VStack(alignment: .leading, spacing: 4) { - sharedProfileInfo(incognitoDefault) - Text("If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link.") - } - .frame(maxWidth: .infinity, alignment: .leading) - .font(.footnote) - .foregroundColor(.secondary) - .padding(.horizontal) - } - .padding() - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) - } - .background(Color(.systemGroupedBackground)) - .alert(item: $alert) { a in planAndConnectAlert(a, dismiss: true) } - .actionSheet(item: $sheet) { s in planAndConnectActionSheet(s, dismiss: true) } - } - - func processQRCode(_ resp: Result) { - switch resp { - case let .success(r): - planAndConnect( - r.string, - showAlert: { alert = $0 }, - showActionSheet: { sheet = $0 }, - dismiss: true, - incognito: incognitoDefault - ) - case let .failure(e): - logger.error("ConnectContactView.processQRCode QR code error: \(e.localizedDescription)") - dismiss() - } - } -} - -struct ConnectContactView_Previews: PreviewProvider { - static var previews: some View { - ScanToConnectView() - } -} diff --git a/apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift b/apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift index 20dadb7954..fc478596a9 100644 --- a/apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift +++ b/apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift @@ -10,24 +10,23 @@ import SwiftUI struct IncognitoHelp: View { var body: some View { - VStack(alignment: .leading) { + List { Text("Incognito mode") .font(.largeTitle) .bold() + .fixedSize(horizontal: false, vertical: true) .padding(.vertical) - ScrollView { - VStack(alignment: .leading) { - Group { - Text("Incognito mode protects your privacy by using a new random profile for each contact.") - Text("It allows having many anonymous connections without any shared data between them in a single chat profile.") - Text("When you share an incognito profile with somebody, this profile will be used for the groups they invite you to.") - } - .padding(.bottom) - } + .listRowBackground(Color.clear) + .listRowSeparator(.hidden) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + VStack(alignment: .leading, spacing: 18) { + Text("Incognito mode protects your privacy by using a new random profile for each contact.") + Text("It allows having many anonymous connections without any shared data between them in a single chat profile.") + Text("When you share an incognito profile with somebody, this profile will be used for the groups they invite you to.") + Text("Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode).") } + .listRowBackground(Color.clear) } - .frame(maxWidth: .infinity) - .padding() } } diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index f889d9c394..b73d6b867d 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -95,6 +95,12 @@ let appDefaults: [String: Any] = [ DEFAULT_CONNECT_REMOTE_VIA_MULTICAST_AUTO: true, ] +// not used anymore +enum ConnectViaLinkTab: String { + case scan + case paste +} + enum SimpleXLinkMode: String, Identifiable { case description case full diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index 44f2d878e6..506df8cf17 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -303,14 +303,17 @@ ) No comment provided by engineer. + + **Add contact**: to create a new invitation link, or connect via a link you received. + No comment provided by engineer. + **Add new contact**: to create your one-time QR Code or link for your contact. **Добави нов контакт**: за да създадете своя еднократен QR код или линк за вашия контакт. No comment provided by engineer. - - **Create link / QR code** for your contact to use. - **Създай линк / QR код**, който вашият контакт да използва. + + **Create group**: to create a new group. No comment provided by engineer. @@ -323,11 +326,6 @@ **Най-поверително**: не използвайте сървъра за известия SimpleX Chat, периодично проверявайте съобщенията във фонов режим (зависи от това колко често използвате приложението). No comment provided by engineer. - - **Paste received link** or open it in the browser and tap **Open in mobile app**. - **Поставете получения линк** или го отворете в браузъра и докоснете **Отваряне в мобилно приложение**. - No comment provided by engineer. - **Please note**: you will NOT be able to recover or change passphrase if you lose it. **Моля, обърнете внимание**: НЯМА да можете да възстановите или промените паролата, ако я загубите. @@ -338,11 +336,6 @@ **Препоръчително**: токенът на устройството и известията се изпращат до сървъра за уведомяване на SimpleX Chat, но не и съдържанието, размерът на съобщението или от кого е. No comment provided by engineer. - - **Scan QR code**: to connect to your contact in person or via video call. - **Сканирай QR код**: за да се свържете с вашия контакт лично или чрез видеообаждане. - No comment provided by engineer. - **Warning**: Instant push notifications require passphrase saved in Keychain. **Внимание**: Незабавните push известия изискват парола, запазена в Keychain. @@ -440,11 +433,6 @@ 1 седмица time interval - - 1-time link - Еднократен линк - No comment provided by engineer. - 5 minutes 5 минути @@ -560,6 +548,10 @@ Добавете адрес към вашия профил, така че вашите контакти да могат да го споделят с други хора. Актуализацията на профила ще бъде изпратена до вашите контакти. No comment provided by engineer. + + Add contact + No comment provided by engineer. + Add preset servers Добави предварително зададени сървъри @@ -956,6 +948,10 @@ Обаждания No comment provided by engineer. + + Camera not available + No comment provided by engineer. + Can't delete user profile! Потребителският профил не може да се изтрие! @@ -1212,11 +1208,6 @@ This is your own one-time link! Свърване чрез линк No comment provided by engineer. - - Connect via link / QR code - Свърване чрез линк/QR код - No comment provided by engineer. - Connect via one-time link Свързване чрез еднократен линк за връзка @@ -1384,11 +1375,6 @@ This is your own one-time link! Създайте нов профил в [настолното приложение](https://simplex.chat/downloads/). 💻 No comment provided by engineer. - - Create one-time invitation link - Създай линк за еднократна покана - No comment provided by engineer. - Create profile No comment provided by engineer. @@ -1413,6 +1399,10 @@ This is your own one-time link! Създаден на %@ No comment provided by engineer. + + Creating link… + No comment provided by engineer. + Current Passcode Текущ kод за достъп @@ -1954,6 +1944,10 @@ This cannot be undone! Активиране на автоматично изтриване на съобщения? No comment provided by engineer. + + Enable camera access + No comment provided by engineer. + Enable for all Активиране за всички @@ -2292,6 +2286,10 @@ This cannot be undone! Грешка при запазване на потребителска парола No comment provided by engineer. + + Error scanning code: %@ + No comment provided by engineer. + Error sending email Грешка при изпращане на имейл @@ -2761,11 +2759,6 @@ This cannot be undone! Ако не можете да се срещнете лично, покажете QR код във видеоразговора или споделете линка. No comment provided by engineer. - - If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link. - Ако не можете да се срещнете на живо, можете да **сканирате QR код във видеообаждането** или вашият контакт може да сподели линк за покана. - No comment provided by engineer. - If you enter this passcode when opening the app, all app data will be irreversibly removed! Ако въведете този kод за достъп, когато отваряте приложението, всички данни от приложението ще бъдат необратимо изтрити! @@ -2921,11 +2914,19 @@ This cannot be undone! Интерфейс No comment provided by engineer. + + Invalid QR code + No comment provided by engineer. + Invalid connection link Невалиден линк за връзка No comment provided by engineer. + + Invalid link + No comment provided by engineer. + Invalid name! No comment provided by engineer. @@ -3048,10 +3049,18 @@ This is your link for group %@! Присъединяване към групата No comment provided by engineer. + + Keep + No comment provided by engineer. + Keep the app open to use it from desktop No comment provided by engineer. + + Keep unused invitation? + No comment provided by engineer. + Keep your connections Запазете връзките си @@ -3378,6 +3387,10 @@ This is your link for group %@! Нов kод за достъп No comment provided by engineer. + + New chat + No comment provided by engineer. + New contact request Нова заявка за контакт @@ -3501,6 +3514,10 @@ This is your link for group %@! - да деактивират членове (роля "наблюдател") No comment provided by engineer. + + OK + No comment provided by engineer. + Off Изключено @@ -3649,6 +3666,14 @@ This is your link for group %@! Opening app… No comment provided by engineer. + + Or scan QR code + No comment provided by engineer. + + + Or show this code + No comment provided by engineer. + PING count PING бройка @@ -3689,11 +3714,6 @@ This is your link for group %@! Парола за показване No comment provided by engineer. - - Paste - Постави - No comment provided by engineer. - Paste desktop address No comment provided by engineer. @@ -3703,16 +3723,10 @@ This is your link for group %@! Постави изображение No comment provided by engineer. - - Paste received link - Постави получения линк + + Paste the link you received No comment provided by engineer. - - Paste the link you received to connect with your contact. - Поставете линка, който сте получили, за да се свържете с вашия контакт. - placeholder - People can connect to you only via the links you share. Хората могат да се свържат с вас само чрез ликовете, които споделяте. @@ -3956,6 +3970,10 @@ Error: %@ Прочетете повече в [Ръководство за потребителя](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). Прочетете повече в [Ръководство на потребителя](https://simplex.chat/docs/guide/readme.html#connect-to-friends). @@ -4164,6 +4182,10 @@ Error: %@ Грешка при възстановяване на базата данни No comment provided by engineer. + + Retry + No comment provided by engineer. + Reveal Покажи @@ -4318,6 +4340,10 @@ Error: %@ Търсене No comment provided by engineer. + + Search or paste SimpleX link + No comment provided by engineer. + Secure queue Сигурна опашка @@ -4592,9 +4618,8 @@ Error: %@ Сподели линк No comment provided by engineer. - - Share one-time invitation link - Сподели линк за еднократна покана + + Share this 1-time invite link No comment provided by engineer. @@ -4717,11 +4742,6 @@ Error: %@ Някой notification title - - Start a new chat - Започни нов чат - No comment provided by engineer. - Start chat Започни чат @@ -4855,6 +4875,14 @@ Error: %@ Докосни за инкогнито вход No comment provided by engineer. + + Tap to paste link + No comment provided by engineer. + + + Tap to scan + No comment provided by engineer. + Tap to start a new chat Докосни за започване на нов чат @@ -4917,6 +4945,10 @@ It can happen because of some bug or when the connection is compromised.Опитът за промяна на паролата на базата данни не беше завършен. No comment provided by engineer. + + The code you scanned is not a SimpleX link QR code. + No comment provided by engineer. + The connection you accepted will be cancelled! Връзката, която приехте, ще бъде отказана! @@ -4982,6 +5014,10 @@ It can happen because of some bug or when the connection is compromised.Сървърите за нови връзки на текущия ви чат профил **%@**. No comment provided by engineer. + + The text you pasted is not a SimpleX link. + No comment provided by engineer. + Theme Тема @@ -5579,11 +5615,6 @@ Repeat join request? Можете да приемате обаждания от заключен екран, без идентификация на устройство и приложението. No comment provided by engineer. - - You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button. - Можете също да се свържете, като натиснете върху линка. Ако се отвори в браузъра, натиснете върху бутона **Отваряне в мобилно приложение**. - No comment provided by engineer. - You can create it later Можете да го създадете по-късно @@ -5648,6 +5679,10 @@ Repeat join request? Можете да използвате markdown за форматиране на съобщенията: No comment provided by engineer. + + You can view invitation link again in connection details. + No comment provided by engineer. + You can't send messages! Не може да изпращате съобщения! diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index f0abba7bb2..076c2c97fb 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -303,14 +303,17 @@ ) No comment provided by engineer. + + **Add contact**: to create a new invitation link, or connect via a link you received. + No comment provided by engineer. + **Add new contact**: to create your one-time QR Code or link for your contact. **Přidat nový kontakt**: pro vytvoření jednorázového QR kódu nebo odkazu pro váš kontakt. No comment provided by engineer. - - **Create link / QR code** for your contact to use. - **Vytvořte odkaz / QR kód** pro váš kontakt. + + **Create group**: to create a new group. No comment provided by engineer. @@ -323,11 +326,6 @@ **Nejsoukromější**: nepoužívejte server oznámení SimpleX Chat, pravidelně kontrolujte zprávy na pozadí (závisí na tom, jak často aplikaci používáte). No comment provided by engineer. - - **Paste received link** or open it in the browser and tap **Open in mobile app**. - **Vložte přijatý odkaz** nebo jej otevřete v prohlížeči a klepněte na **Otevřít v mobilní aplikaci**. - No comment provided by engineer. - **Please note**: you will NOT be able to recover or change passphrase if you lose it. **Upozornění**: Pokud heslo ztratíte, NEBUDETE jej moci obnovit ani změnit. @@ -338,11 +336,6 @@ **Doporučeno**: Token zařízení a oznámení se odesílají na oznamovací server SimpleX Chat, ale nikoli obsah, velikost nebo od koho jsou zprávy. No comment provided by engineer. - - **Scan QR code**: to connect to your contact in person or via video call. - ** Naskenujte QR kód**: pro připojení ke kontaktu osobně nebo prostřednictvím videohovoru. - No comment provided by engineer. - **Warning**: Instant push notifications require passphrase saved in Keychain. **Upozornění**: Okamžitě doručovaná oznámení vyžadují přístupové heslo uložené v Klíčence. @@ -440,11 +433,6 @@ 1 týden time interval - - 1-time link - Jednorázový odkaz - No comment provided by engineer. - 5 minutes 5 minut @@ -560,6 +548,10 @@ Přidejte adresu do svého profilu, aby ji vaše kontakty mohly sdílet s dalšími lidmi. Aktualizace profilu bude zaslána vašim kontaktům. No comment provided by engineer. + + Add contact + No comment provided by engineer. + Add preset servers Přidejte přednastavené servery @@ -956,6 +948,10 @@ Hovory No comment provided by engineer. + + Camera not available + No comment provided by engineer. + Can't delete user profile! Nemohu smazat uživatelský profil! @@ -1212,11 +1208,6 @@ This is your own one-time link! Připojte se prostřednictvím odkazu No comment provided by engineer. - - Connect via link / QR code - Připojit se prostřednictvím odkazu / QR kódu - No comment provided by engineer. - Connect via one-time link Připojit se jednorázovým odkazem @@ -1384,11 +1375,6 @@ This is your own one-time link! Vytvořit nový profil v [desktop app](https://simplex.chat/downloads/). 💻 No comment provided by engineer. - - Create one-time invitation link - Vytvořit jednorázovou pozvánku - No comment provided by engineer. - Create profile No comment provided by engineer. @@ -1413,6 +1399,10 @@ This is your own one-time link! Vytvořeno na %@ No comment provided by engineer. + + Creating link… + No comment provided by engineer. + Current Passcode Aktuální heslo @@ -1954,6 +1944,10 @@ This cannot be undone! Povolit automatické mazání zpráv? No comment provided by engineer. + + Enable camera access + No comment provided by engineer. + Enable for all Povolit pro všechny @@ -2292,6 +2286,10 @@ This cannot be undone! Chyba ukládání hesla uživatele No comment provided by engineer. + + Error scanning code: %@ + No comment provided by engineer. + Error sending email Chyba odesílání e-mailu @@ -2761,11 +2759,6 @@ This cannot be undone! Pokud se nemůžete setkat osobně, zobrazte QR kód ve videohovoru nebo sdílejte odkaz. No comment provided by engineer. - - If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link. - Pokud se nemůžete setkat osobně, můžete **naskenovat QR kód během videohovoru**, nebo váš kontakt může sdílet odkaz na pozvánku. - No comment provided by engineer. - If you enter this passcode when opening the app, all app data will be irreversibly removed! Pokud tento přístupový kód zadáte při otevření aplikace, všechna data budou nenávratně smazána! @@ -2921,11 +2914,19 @@ This cannot be undone! Rozhranní No comment provided by engineer. + + Invalid QR code + No comment provided by engineer. + Invalid connection link Neplatný odkaz na spojení No comment provided by engineer. + + Invalid link + No comment provided by engineer. + Invalid name! No comment provided by engineer. @@ -3048,10 +3049,18 @@ This is your link for group %@! Připojování ke skupině No comment provided by engineer. + + Keep + No comment provided by engineer. + Keep the app open to use it from desktop No comment provided by engineer. + + Keep unused invitation? + No comment provided by engineer. + Keep your connections Zachovat vaše připojení @@ -3378,6 +3387,10 @@ This is your link for group %@! Nové heslo No comment provided by engineer. + + New chat + No comment provided by engineer. + New contact request Žádost o nový kontakt @@ -3501,6 +3514,10 @@ This is your link for group %@! - zakázat členy (role "pozorovatel") No comment provided by engineer. + + OK + No comment provided by engineer. + Off Vypnout @@ -3649,6 +3666,14 @@ This is your link for group %@! Opening app… No comment provided by engineer. + + Or scan QR code + No comment provided by engineer. + + + Or show this code + No comment provided by engineer. + PING count Počet PING @@ -3689,11 +3714,6 @@ This is your link for group %@! Heslo k zobrazení No comment provided by engineer. - - Paste - Vložit - No comment provided by engineer. - Paste desktop address No comment provided by engineer. @@ -3703,16 +3723,10 @@ This is your link for group %@! Vložit obrázek No comment provided by engineer. - - Paste received link - Vložení přijatého odkazu + + Paste the link you received No comment provided by engineer. - - Paste the link you received to connect with your contact. - Vložte odkaz, který jste obdrželi, do pole níže a spojte se se svým kontaktem. - placeholder - People can connect to you only via the links you share. Lidé se s vámi mohou spojit pouze prostřednictvím odkazů, které sdílíte. @@ -3956,6 +3970,10 @@ Error: %@ Další informace naleznete v [Uživatelské příručce](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). Přečtěte si více v [Uživatelské příručce](https://simplex.chat/docs/guide/readme.html#connect-to-friends). @@ -4164,6 +4182,10 @@ Error: %@ Chyba obnovení databáze No comment provided by engineer. + + Retry + No comment provided by engineer. + Reveal Odhalit @@ -4318,6 +4340,10 @@ Error: %@ Hledat No comment provided by engineer. + + Search or paste SimpleX link + No comment provided by engineer. + Secure queue Zabezpečit frontu @@ -4592,9 +4618,8 @@ Error: %@ Sdílet odkaz No comment provided by engineer. - - Share one-time invitation link - Jednorázový zvací odkaz + + Share this 1-time invite link No comment provided by engineer. @@ -4717,11 +4742,6 @@ Error: %@ Někdo notification title - - Start a new chat - Začít nový chat - No comment provided by engineer. - Start chat Začít chat @@ -4855,6 +4875,14 @@ Error: %@ Klepnutím se připojíte inkognito No comment provided by engineer. + + Tap to paste link + No comment provided by engineer. + + + Tap to scan + No comment provided by engineer. + Tap to start a new chat Klepnutím na zahájíte nový chat @@ -4917,6 +4945,10 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován Pokus o změnu přístupové fráze databáze nebyl dokončen. No comment provided by engineer. + + The code you scanned is not a SimpleX link QR code. + No comment provided by engineer. + The connection you accepted will be cancelled! Připojení, které jste přijali, bude zrušeno! @@ -4982,6 +5014,10 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován Servery pro nová připojení vašeho aktuálního chat profilu **%@**. No comment provided by engineer. + + The text you pasted is not a SimpleX link. + No comment provided by engineer. + Theme Téma @@ -5579,11 +5615,6 @@ Repeat join request? Můžete přijímat hovory z obrazovky zámku, bez ověření zařízení a aplikace. No comment provided by engineer. - - You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button. - Můžete se také připojit kliknutím na odkaz. Pokud se otevře v prohlížeči, klikněte na tlačítko **Otevřít v mobilní aplikaci**. - No comment provided by engineer. - You can create it later Můžete vytvořit později @@ -5648,6 +5679,10 @@ Repeat join request? K formátování zpráv můžete použít markdown: No comment provided by engineer. + + You can view invitation link again in connection details. + No comment provided by engineer. + You can't send messages! Nemůžete posílat zprávy! 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 dda89f5c0c..2b877c32d2 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -312,14 +312,17 @@ ) No comment provided by engineer. + + **Add contact**: to create a new invitation link, or connect via a link you received. + No comment provided by engineer. + **Add new contact**: to create your one-time QR Code or link for your contact. **Fügen Sie einen neuen Kontakt hinzu**: Erzeugen Sie einen Einmal-QR-Code oder -Link für Ihren Kontakt. No comment provided by engineer. - - **Create link / QR code** for your contact to use. - **Generieren Sie einen Einladungs-Link / QR code** für Ihren Kontakt. + + **Create group**: to create a new group. No comment provided by engineer. @@ -332,11 +335,6 @@ **Beste Privatsphäre**: Es wird kein SimpleX-Chat-Benachrichtigungs-Server genutzt, Nachrichten werden in periodischen Abständen im Hintergrund geprüft (dies hängt davon ab, wie häufig Sie die App nutzen). No comment provided by engineer. - - **Paste received link** or open it in the browser and tap **Open in mobile app**. - **Fügen Sie den von Ihrem Kontakt erhaltenen Link ein** oder öffnen Sie ihn im Browser und tippen Sie auf **In mobiler App öffnen**. - No comment provided by engineer. - **Please note**: you will NOT be able to recover or change passphrase if you lose it. **Bitte beachten Sie**: Das Passwort kann NICHT wiederhergestellt oder geändert werden, wenn Sie es vergessen haben oder verlieren. @@ -347,11 +345,6 @@ **Empfohlen**: Nur Ihr Geräte-Token und ihre Benachrichtigungen werden an den SimpleX-Chat-Benachrichtigungs-Server gesendet, aber weder der Nachrichteninhalt noch deren Größe oder von wem sie gesendet wurde. No comment provided by engineer. - - **Scan QR code**: to connect to your contact in person or via video call. - **Scannen Sie den QR-Code**, um sich während einem persönlichen Treffen oder per Videoanruf mit Ihrem Kontakt zu verbinden. - No comment provided by engineer. - **Warning**: Instant push notifications require passphrase saved in Keychain. **Warnung**: Sofortige Push-Benachrichtigungen erfordern die Eingabe eines Passworts, welches in Ihrem Schlüsselbund gespeichert ist. @@ -453,11 +446,6 @@ wöchentlich time interval - - 1-time link - Einmal-Link - No comment provided by engineer. - 5 minutes 5 Minuten @@ -573,6 +561,10 @@ Fügen Sie die Adresse zu Ihrem Profil hinzu, damit Ihre Kontakte sie mit anderen Personen teilen können. Es wird eine Profilaktualisierung an Ihre Kontakte gesendet. No comment provided by engineer. + + Add contact + No comment provided by engineer. + Add preset servers Füge voreingestellte Server hinzu @@ -978,6 +970,10 @@ Anrufe No comment provided by engineer. + + Camera not available + No comment provided by engineer. + Can't delete user profile! Das Benutzerprofil kann nicht gelöscht werden! @@ -1242,11 +1238,6 @@ Das ist Ihr eigener Einmal-Link! Über einen Link verbinden No comment provided by engineer. - - Connect via link / QR code - Über einen Link / QR-Code verbinden - No comment provided by engineer. - Connect via one-time link Über einen Einmal-Link verbinden @@ -1422,11 +1413,6 @@ Das ist Ihr eigener Einmal-Link! Neues Profil in der [Desktop-App] erstellen (https://simplex.chat/downloads/). 💻 No comment provided by engineer. - - Create one-time invitation link - Einmal-Einladungslink erstellen - No comment provided by engineer. - Create profile Profil erstellen @@ -1452,6 +1438,10 @@ Das ist Ihr eigener Einmal-Link! Erstellt am %@ No comment provided by engineer. + + Creating link… + No comment provided by engineer. + Current Passcode Aktueller Zugangscode @@ -2002,6 +1992,10 @@ Das kann nicht rückgängig gemacht werden! Automatisches Löschen von Nachrichten aktivieren? No comment provided by engineer. + + Enable camera access + No comment provided by engineer. + Enable for all Für Alle aktivieren @@ -2345,6 +2339,10 @@ Das kann nicht rückgängig gemacht werden! Fehler beim Speichern des Benutzer-Passworts No comment provided by engineer. + + Error scanning code: %@ + No comment provided by engineer. + Error sending email Fehler beim Senden der eMail @@ -2820,11 +2818,6 @@ Das kann nicht rückgängig gemacht werden! Falls Sie sich nicht persönlich treffen können, zeigen Sie den QR-Code in einem Videoanruf oder teilen Sie den Link. No comment provided by engineer. - - If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link. - Wenn Sie sich nicht persönlich treffen können, kann der **QR-Code während eines Videoanrufs gescannt werden**, oder Ihr Kontakt kann den Einladungslink über einen anderen Kanal mit Ihnen teilen. - No comment provided by engineer. - If you enter this passcode when opening the app, all app data will be irreversibly removed! Wenn Sie diesen Zugangscode während des Öffnens der App eingeben, werden alle App-Daten unwiederbringlich gelöscht! @@ -2982,11 +2975,19 @@ Das kann nicht rückgängig gemacht werden! Schnittstelle No comment provided by engineer. + + Invalid QR code + No comment provided by engineer. + Invalid connection link Ungültiger Verbindungslink No comment provided by engineer. + + Invalid link + No comment provided by engineer. + Invalid name! Ungültiger Name! @@ -3114,11 +3115,19 @@ Das ist Ihr Link für die Gruppe %@! Der Gruppe beitreten No comment provided by engineer. + + Keep + No comment provided by engineer. + Keep the app open to use it from desktop Die App muss geöffnet bleiben, um sie vom Desktop aus nutzen zu können No comment provided by engineer. + + Keep unused invitation? + No comment provided by engineer. + Keep your connections Ihre Verbindungen beibehalten @@ -3449,6 +3458,10 @@ Das ist Ihr Link für die Gruppe %@! Neuer Zugangscode No comment provided by engineer. + + New chat + No comment provided by engineer. + New contact request Neue Kontaktanfrage @@ -3573,6 +3586,10 @@ Das ist Ihr Link für die Gruppe %@! - Gruppenmitglieder deaktivieren ("Beobachter"-Rolle) No comment provided by engineer. + + OK + No comment provided by engineer. + Off Aus @@ -3722,6 +3739,14 @@ Das ist Ihr Link für die Gruppe %@! Opening app… No comment provided by engineer. + + Or scan QR code + No comment provided by engineer. + + + Or show this code + No comment provided by engineer. + PING count PING-Zähler @@ -3762,11 +3787,6 @@ Das ist Ihr Link für die Gruppe %@! Passwort anzeigen No comment provided by engineer. - - Paste - Einfügen - No comment provided by engineer. - Paste desktop address Desktop-Adresse einfügen @@ -3777,16 +3797,10 @@ Das ist Ihr Link für die Gruppe %@! Bild einfügen No comment provided by engineer. - - Paste received link - Fügen Sie den erhaltenen Link ein + + Paste the link you received No comment provided by engineer. - - Paste the link you received to connect with your contact. - Um sich mit Ihrem Kontakt zu verbinden, fügen Sie den erhaltenen Link in das Feld unten ein. - placeholder - People can connect to you only via the links you share. Verbindungen mit Kontakten sind nur über Links möglich, die Sie oder Ihre Kontakte untereinander teilen. @@ -4032,6 +4046,10 @@ Error: %@ Mehr dazu in der [Benutzeranleitung](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address) lesen. No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). Mehr dazu in der [Benutzeranleitung](https://simplex.chat/docs/guide/readme.html#connect-to-friends) lesen. @@ -4242,6 +4260,10 @@ Error: %@ Fehler bei der Wiederherstellung der Datenbank No comment provided by engineer. + + Retry + No comment provided by engineer. + Reveal Aufdecken @@ -4397,6 +4419,10 @@ Error: %@ Suche No comment provided by engineer. + + Search or paste SimpleX link + No comment provided by engineer. + Secure queue Sichere Warteschlange @@ -4672,9 +4698,8 @@ Error: %@ Link teilen No comment provided by engineer. - - Share one-time invitation link - Einmal-Einladungslink teilen + + Share this 1-time invite link No comment provided by engineer. @@ -4797,11 +4822,6 @@ Error: %@ Jemand notification title - - Start a new chat - Starten Sie einen neuen Chat - No comment provided by engineer. - Start chat Starten Sie den Chat @@ -4936,6 +4956,14 @@ Error: %@ Tippen, um Inkognito beizutreten No comment provided by engineer. + + Tap to paste link + No comment provided by engineer. + + + Tap to scan + No comment provided by engineer. + Tap to start a new chat Tippen, um einen neuen Chat zu starten @@ -4998,6 +5026,10 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Die Änderung des Datenbank-Passworts konnte nicht abgeschlossen werden. No comment provided by engineer. + + The code you scanned is not a SimpleX link QR code. + No comment provided by engineer. + The connection you accepted will be cancelled! Die von Ihnen akzeptierte Verbindung wird abgebrochen! @@ -5063,6 +5095,10 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Server der neuen Verbindungen von Ihrem aktuellen Chat-Profil **%@**. No comment provided by engineer. + + The text you pasted is not a SimpleX link. + No comment provided by engineer. + Theme Design @@ -5683,11 +5719,6 @@ Verbindungsanfrage wiederholen? Sie können Anrufe ohne Geräte- und App-Authentifizierung vom Sperrbildschirm aus annehmen. No comment provided by engineer. - - You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button. - Sie können sich auch verbinden, indem Sie auf den Link klicken. Wenn er im Browser geöffnet wird, klicken Sie auf die Schaltfläche **In mobiler App öffnen**. - No comment provided by engineer. - You can create it later Sie können dies später erstellen @@ -5752,6 +5783,10 @@ Verbindungsanfrage wiederholen? Um Nachrichteninhalte zu formatieren, können Sie Markdowns verwenden: No comment provided by engineer. + + You can view invitation link again in connection details. + No comment provided by engineer. + You can't send messages! Sie können keine Nachrichten versenden! diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 4544b823f7..7dd5db3b65 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -312,14 +312,19 @@ ) No comment provided by engineer. + + **Add contact**: to create a new invitation link, or connect via a link you received. + **Add contact**: to create a new invitation link, or connect via a link you received. + No comment provided by engineer. + **Add new contact**: to create your one-time QR Code or link for your contact. **Add new contact**: to create your one-time QR Code or link for your contact. No comment provided by engineer. - - **Create link / QR code** for your contact to use. - **Create link / QR code** for your contact to use. + + **Create group**: to create a new group. + **Create group**: to create a new group. No comment provided by engineer. @@ -332,11 +337,6 @@ **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). No comment provided by engineer. - - **Paste received link** or open it in the browser and tap **Open in mobile app**. - **Paste received link** or open it in the browser and tap **Open in mobile app**. - No comment provided by engineer. - **Please note**: you will NOT be able to recover or change passphrase if you lose it. **Please note**: you will NOT be able to recover or change passphrase if you lose it. @@ -347,11 +347,6 @@ **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. No comment provided by engineer. - - **Scan QR code**: to connect to your contact in person or via video call. - **Scan QR code**: to connect to your contact in person or via video call. - No comment provided by engineer. - **Warning**: Instant push notifications require passphrase saved in Keychain. **Warning**: Instant push notifications require passphrase saved in Keychain. @@ -453,11 +448,6 @@ 1 week time interval - - 1-time link - 1-time link - No comment provided by engineer. - 5 minutes 5 minutes @@ -573,6 +563,11 @@ Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. No comment provided by engineer. + + Add contact + Add contact + No comment provided by engineer. + Add preset servers Add preset servers @@ -978,6 +973,11 @@ Calls No comment provided by engineer. + + Camera not available + Camera not available + No comment provided by engineer. + Can't delete user profile! Can't delete user profile! @@ -1243,11 +1243,6 @@ This is your own one-time link! Connect via link No comment provided by engineer. - - Connect via link / QR code - Connect via link / QR code - No comment provided by engineer. - Connect via one-time link Connect via one-time link @@ -1423,11 +1418,6 @@ This is your own one-time link! Create new profile in [desktop app](https://simplex.chat/downloads/). 💻 No comment provided by engineer. - - Create one-time invitation link - Create one-time invitation link - No comment provided by engineer. - Create profile Create profile @@ -1453,6 +1443,11 @@ This is your own one-time link! Created on %@ No comment provided by engineer. + + Creating link… + Creating link… + No comment provided by engineer. + Current Passcode Current Passcode @@ -2003,6 +1998,11 @@ This cannot be undone! Enable automatic message deletion? No comment provided by engineer. + + Enable camera access + Enable camera access + No comment provided by engineer. + Enable for all Enable for all @@ -2348,6 +2348,11 @@ This cannot be undone! Error saving user password No comment provided by engineer. + + Error scanning code: %@ + Error scanning code: %@ + No comment provided by engineer. + Error sending email Error sending email @@ -2823,11 +2828,6 @@ This cannot be undone! If you can't meet in person, show QR code in a video call, or share the link. No comment provided by engineer. - - If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link. - If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link. - No comment provided by engineer. - If you enter this passcode when opening the app, all app data will be irreversibly removed! If you enter this passcode when opening the app, all app data will be irreversibly removed! @@ -2985,11 +2985,21 @@ This cannot be undone! Interface No comment provided by engineer. + + Invalid QR code + Invalid QR code + No comment provided by engineer. + Invalid connection link Invalid connection link No comment provided by engineer. + + Invalid link + Invalid link + No comment provided by engineer. + Invalid name! Invalid name! @@ -3118,11 +3128,21 @@ This is your link for group %@! Joining group No comment provided by engineer. + + Keep + Keep + No comment provided by engineer. + Keep the app open to use it from desktop Keep the app open to use it from desktop No comment provided by engineer. + + Keep unused invitation? + Keep unused invitation? + No comment provided by engineer. + Keep your connections Keep your connections @@ -3453,6 +3473,11 @@ This is your link for group %@! New Passcode No comment provided by engineer. + + New chat + New chat + No comment provided by engineer. + New contact request New contact request @@ -3577,6 +3602,11 @@ This is your link for group %@! - disable members ("observer" role) No comment provided by engineer. + + OK + OK + No comment provided by engineer. + Off Off @@ -3727,6 +3757,16 @@ This is your link for group %@! Opening app… No comment provided by engineer. + + Or scan QR code + Or scan QR code + No comment provided by engineer. + + + Or show this code + Or show this code + No comment provided by engineer. + PING count PING count @@ -3767,11 +3807,6 @@ This is your link for group %@! Password to show No comment provided by engineer. - - Paste - Paste - No comment provided by engineer. - Paste desktop address Paste desktop address @@ -3782,16 +3817,11 @@ This is your link for group %@! Paste image No comment provided by engineer. - - Paste received link - Paste received link + + Paste the link you received + Paste the link you received No comment provided by engineer. - - Paste the link you received to connect with your contact. - Paste the link you received to connect with your contact. - placeholder - People can connect to you only via the links you share. People can connect to you only via the links you share. @@ -4039,6 +4069,11 @@ Error: %@ Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). @@ -4249,6 +4284,11 @@ Error: %@ Restore database error No comment provided by engineer. + + Retry + Retry + No comment provided by engineer. + Reveal Reveal @@ -4404,6 +4444,11 @@ Error: %@ Search No comment provided by engineer. + + Search or paste SimpleX link + Search or paste SimpleX link + No comment provided by engineer. + Secure queue Secure queue @@ -4679,9 +4724,9 @@ Error: %@ Share link No comment provided by engineer. - - Share one-time invitation link - Share one-time invitation link + + Share this 1-time invite link + Share this 1-time invite link No comment provided by engineer. @@ -4804,11 +4849,6 @@ Error: %@ Somebody notification title - - Start a new chat - Start a new chat - No comment provided by engineer. - Start chat Start chat @@ -4944,6 +4984,16 @@ Error: %@ Tap to join incognito No comment provided by engineer. + + Tap to paste link + Tap to paste link + No comment provided by engineer. + + + Tap to scan + Tap to scan + No comment provided by engineer. + Tap to start a new chat Tap to start a new chat @@ -5006,6 +5056,11 @@ It can happen because of some bug or when the connection is compromised.The attempt to change database passphrase was not completed. No comment provided by engineer. + + The code you scanned is not a SimpleX link QR code. + The code you scanned is not a SimpleX link QR code. + No comment provided by engineer. + The connection you accepted will be cancelled! The connection you accepted will be cancelled! @@ -5071,6 +5126,11 @@ It can happen because of some bug or when the connection is compromised.The servers for new connections of your current chat profile **%@**. No comment provided by engineer. + + The text you pasted is not a SimpleX link. + The text you pasted is not a SimpleX link. + No comment provided by engineer. + Theme Theme @@ -5692,11 +5752,6 @@ Repeat join request? You can accept calls from lock screen, without device and app authentication. No comment provided by engineer. - - You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button. - You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button. - No comment provided by engineer. - You can create it later You can create it later @@ -5762,6 +5817,11 @@ Repeat join request? You can use markdown to format messages: No comment provided by engineer. + + You can view invitation link again in connection details. + You can view invitation link again in connection details. + No comment provided by engineer. + You can't send messages! You can't send messages! diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 5b3d820d6f..0f2accb893 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -312,14 +312,17 @@ ) No comment provided by engineer. + + **Add contact**: to create a new invitation link, or connect via a link you received. + No comment provided by engineer. + **Add new contact**: to create your one-time QR Code or link for your contact. **Añadir nuevo contacto**: para crear tu código QR o enlace de un uso para tu contacto. No comment provided by engineer. - - **Create link / QR code** for your contact to use. - **Crea enlace / código QR** para que tu contacto lo use. + + **Create group**: to create a new group. No comment provided by engineer. @@ -332,11 +335,6 @@ **Más privado**: no se usa el servidor de notificaciones de SimpleX Chat, los mensajes se comprueban periódicamente en segundo plano (dependiendo de la frecuencia con la que utilices la aplicación). No comment provided by engineer. - - **Paste received link** or open it in the browser and tap **Open in mobile app**. - **Pega el enlace recibido** o ábrelo en el navegador y pulsa **Abrir en aplicación móvil**. - No comment provided by engineer. - **Please note**: you will NOT be able to recover or change passphrase if you lose it. **Atención**: NO podrás recuperar o cambiar la contraseña si la pierdes. @@ -347,11 +345,6 @@ **Recomendado**: el token del dispositivo y las notificaciones se envían al servidor de notificaciones de SimpleX Chat, pero no el contenido del mensaje, su tamaño o su procedencia. No comment provided by engineer. - - **Scan QR code**: to connect to your contact in person or via video call. - **Escanear código QR**: en persona para conectarte con tu contacto, o por videollamada. - No comment provided by engineer. - **Warning**: Instant push notifications require passphrase saved in Keychain. **Advertencia**: Las notificaciones automáticas instantáneas requieren una contraseña guardada en Keychain. @@ -453,11 +446,6 @@ una semana time interval - - 1-time link - Enlace un uso - No comment provided by engineer. - 5 minutes 5 minutos @@ -573,6 +561,10 @@ Añade la dirección a tu perfil para que tus contactos puedan compartirla con otros. La actualización del perfil se enviará a tus contactos. No comment provided by engineer. + + Add contact + No comment provided by engineer. + Add preset servers Añadir servidores predefinidos @@ -978,6 +970,10 @@ Llamadas No comment provided by engineer. + + Camera not available + No comment provided by engineer. + Can't delete user profile! ¡No se puede eliminar el perfil! @@ -1242,11 +1238,6 @@ This is your own one-time link! Conectar mediante enlace No comment provided by engineer. - - Connect via link / QR code - Conecta vía enlace / Código QR - No comment provided by engineer. - Connect via one-time link Conectar mediante enlace de un sólo uso @@ -1422,11 +1413,6 @@ This is your own one-time link! Crea perfil nuevo en la [aplicación para PC](https://simplex.Descargas/de chat/). 💻 No comment provided by engineer. - - Create one-time invitation link - Crea enlace de invitación de un uso - No comment provided by engineer. - Create profile Crear perfil @@ -1452,6 +1438,10 @@ This is your own one-time link! Creado en %@ No comment provided by engineer. + + Creating link… + No comment provided by engineer. + Current Passcode Código de Acceso @@ -2002,6 +1992,10 @@ This cannot be undone! ¿Activar eliminación automática de mensajes? No comment provided by engineer. + + Enable camera access + No comment provided by engineer. + Enable for all Activar para todos @@ -2345,6 +2339,10 @@ This cannot be undone! Error al guardar contraseña de usuario No comment provided by engineer. + + Error scanning code: %@ + No comment provided by engineer. + Error sending email Error al enviar email @@ -2820,11 +2818,6 @@ This cannot be undone! Si no puedes reunirte en persona, muestra el código QR por videollamada, o comparte el enlace. No comment provided by engineer. - - If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link. - Si no puedes reunirte en persona, puedes **escanear el código QR por videollamada**, o tu contacto puede compartir un enlace de invitación. - No comment provided by engineer. - If you enter this passcode when opening the app, all app data will be irreversibly removed! ¡Si introduces este código al abrir la aplicación, todos los datos de la misma se eliminarán de forma irreversible! @@ -2982,11 +2975,19 @@ This cannot be undone! Interfaz No comment provided by engineer. + + Invalid QR code + No comment provided by engineer. + Invalid connection link Enlace de conexión no válido No comment provided by engineer. + + Invalid link + No comment provided by engineer. + Invalid name! ¡Nombre no válido! @@ -3114,11 +3115,19 @@ This is your link for group %@! Entrando al grupo No comment provided by engineer. + + Keep + No comment provided by engineer. + Keep the app open to use it from desktop Mantén la aplicación abierta para usarla desde el ordenador No comment provided by engineer. + + Keep unused invitation? + No comment provided by engineer. + Keep your connections Conserva tus conexiones @@ -3449,6 +3458,10 @@ This is your link for group %@! Código Nuevo No comment provided by engineer. + + New chat + No comment provided by engineer. + New contact request Nueva solicitud de contacto @@ -3573,6 +3586,10 @@ This is your link for group %@! - desactivar el rol miembro (a rol "observador") No comment provided by engineer. + + OK + No comment provided by engineer. + Off Desactivado @@ -3722,6 +3739,14 @@ This is your link for group %@! Opening app… No comment provided by engineer. + + Or scan QR code + No comment provided by engineer. + + + Or show this code + No comment provided by engineer. + PING count Contador PING @@ -3762,11 +3787,6 @@ This is your link for group %@! Contraseña para hacerlo visible No comment provided by engineer. - - Paste - Pegar - No comment provided by engineer. - Paste desktop address Pegar dirección de ordenador @@ -3777,16 +3797,10 @@ This is your link for group %@! Pegar imagen No comment provided by engineer. - - Paste received link - Pegar enlace recibido + + Paste the link you received No comment provided by engineer. - - Paste the link you received to connect with your contact. - Pega el enlace que has recibido en el recuadro para conectar con tu contacto. - placeholder - People can connect to you only via the links you share. Las personas pueden conectarse contigo solo mediante los enlaces que compartes. @@ -4032,6 +4046,10 @@ Error: %@ Más información en el [Manual de usuario](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). Más información en el [Manual de usuario](https://simplex.chat/docs/guide/readme.html#connect-to-friends). @@ -4242,6 +4260,10 @@ Error: %@ Error al restaurar base de datos No comment provided by engineer. + + Retry + No comment provided by engineer. + Reveal Revelar @@ -4397,6 +4419,10 @@ Error: %@ Buscar No comment provided by engineer. + + Search or paste SimpleX link + No comment provided by engineer. + Secure queue Cola segura @@ -4672,9 +4698,8 @@ Error: %@ Compartir enlace No comment provided by engineer. - - Share one-time invitation link - Compartir enlace de invitación de un uso + + Share this 1-time invite link No comment provided by engineer. @@ -4797,11 +4822,6 @@ Error: %@ Alguien notification title - - Start a new chat - Iniciar chat nuevo - No comment provided by engineer. - Start chat Iniciar chat @@ -4936,6 +4956,14 @@ Error: %@ Pulsa para unirte en modo incógnito No comment provided by engineer. + + Tap to paste link + No comment provided by engineer. + + + Tap to scan + No comment provided by engineer. + Tap to start a new chat Pulsa para iniciar chat nuevo @@ -4998,6 +5026,10 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. El intento de cambiar la contraseña de la base de datos no se ha completado. No comment provided by engineer. + + The code you scanned is not a SimpleX link QR code. + No comment provided by engineer. + The connection you accepted will be cancelled! ¡La conexión que has aceptado se cancelará! @@ -5063,6 +5095,10 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. Lista de servidores para las conexiones nuevas de tu perfil actual **%@**. No comment provided by engineer. + + The text you pasted is not a SimpleX link. + No comment provided by engineer. + Theme Tema @@ -5684,11 +5720,6 @@ Repeat join request? Puede aceptar llamadas desde la pantalla de bloqueo, sin autenticación de dispositivos y aplicaciones. No comment provided by engineer. - - You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button. - También puedes conectarte haciendo clic en el enlace. Si se abre en el navegador, haz clic en el botón **Abrir en aplicación móvil**. - No comment provided by engineer. - You can create it later Puedes crearla más tarde @@ -5753,6 +5784,10 @@ Repeat join request? Puedes usar la sintaxis markdown para dar formato a tus mensajes: No comment provided by engineer. + + You can view invitation link again in connection details. + No comment provided by engineer. + You can't send messages! ¡No puedes enviar mensajes! diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index 928666ddad..81bc19013b 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -303,14 +303,17 @@ ) No comment provided by engineer. + + **Add contact**: to create a new invitation link, or connect via a link you received. + No comment provided by engineer. + **Add new contact**: to create your one-time QR Code or link for your contact. **Lisää uusi kontakti**: luo kertakäyttöinen QR-koodi tai linkki kontaktille. No comment provided by engineer. - - **Create link / QR code** for your contact to use. - **Luo linkki / QR-koodi* kontaktille. + + **Create group**: to create a new group. No comment provided by engineer. @@ -323,11 +326,6 @@ **Yksityisin**: älä käytä SimpleX Chat -ilmoituspalvelinta, tarkista viestit ajoittain taustalla (riippuu siitä, kuinka usein käytät sovellusta). No comment provided by engineer. - - **Paste received link** or open it in the browser and tap **Open in mobile app**. - **Liitä vastaanotettu linkki** tai avaa se selaimessa ja napauta **Avaa mobiilisovelluksessa**. - No comment provided by engineer. - **Please note**: you will NOT be able to recover or change passphrase if you lose it. **Huomaa**: et voi palauttaa tai muuttaa tunnuslausetta, jos kadotat sen. @@ -338,11 +336,6 @@ **Suositus**: laitetunnus ja ilmoitukset lähetetään SimpleX Chat -ilmoituspalvelimelle, mutta ei viestin sisältöä, kokoa tai sitä, keneltä se on peräisin. No comment provided by engineer. - - **Scan QR code**: to connect to your contact in person or via video call. - **Skannaa QR-koodi**: muodosta yhteys kontaktiisi henkilökohtaisesti tai videopuhelun kautta. - No comment provided by engineer. - **Warning**: Instant push notifications require passphrase saved in Keychain. **Varoitus**: Välittömät push-ilmoitukset vaativat tunnuslauseen, joka on tallennettu Keychainiin. @@ -437,11 +430,6 @@ 1 viikko time interval - - 1-time link - Kertakäyttölinkki - No comment provided by engineer. - 5 minutes 5 minuuttia @@ -557,6 +545,10 @@ Lisää osoite profiiliisi, jotta kontaktisi voivat jakaa sen muiden kanssa. Profiilipäivitys lähetetään kontakteillesi. No comment provided by engineer. + + Add contact + No comment provided by engineer. + Add preset servers Lisää esiasetettuja palvelimia @@ -951,6 +943,10 @@ Puhelut No comment provided by engineer. + + Camera not available + No comment provided by engineer. + Can't delete user profile! Käyttäjäprofiilia ei voi poistaa! @@ -1207,11 +1203,6 @@ This is your own one-time link! Yhdistä linkin kautta No comment provided by engineer. - - Connect via link / QR code - Yhdistä linkillä / QR-koodilla - No comment provided by engineer. - Connect via one-time link Yhdistä kertalinkillä @@ -1379,11 +1370,6 @@ This is your own one-time link! Luo uusi profiili [työpöytäsovelluksessa](https://simplex.chat/downloads/). 💻 No comment provided by engineer. - - Create one-time invitation link - Luo kertakutsulinkki - No comment provided by engineer. - Create profile No comment provided by engineer. @@ -1408,6 +1394,10 @@ This is your own one-time link! Luotu %@ No comment provided by engineer. + + Creating link… + No comment provided by engineer. + Current Passcode Nykyinen pääsykoodi @@ -1949,6 +1939,10 @@ This cannot be undone! Ota automaattinen viestien poisto käyttöön? No comment provided by engineer. + + Enable camera access + No comment provided by engineer. + Enable for all Salli kaikille @@ -2285,6 +2279,10 @@ This cannot be undone! Virhe käyttäjän salasanan tallentamisessa No comment provided by engineer. + + Error scanning code: %@ + No comment provided by engineer. + Error sending email Virhe sähköpostin lähettämisessä @@ -2753,11 +2751,6 @@ This cannot be undone! Jos et voi tavata henkilökohtaisesti, näytä QR-koodi videopuhelussa tai jaa linkki. No comment provided by engineer. - - If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link. - Jos et voi tavata henkilökohtaisesti, voit **skannata QR-koodin videopuhelussa** tai kontaktisi voi jakaa kutsulinkin. - No comment provided by engineer. - If you enter this passcode when opening the app, all app data will be irreversibly removed! Jos syötät tämän pääsykoodin sovellusta avatessasi, kaikki sovelluksen tiedot poistetaan peruuttamattomasti! @@ -2913,11 +2906,19 @@ This cannot be undone! Käyttöliittymä No comment provided by engineer. + + Invalid QR code + No comment provided by engineer. + Invalid connection link Virheellinen yhteyslinkki No comment provided by engineer. + + Invalid link + No comment provided by engineer. + Invalid name! No comment provided by engineer. @@ -3040,10 +3041,18 @@ This is your link for group %@! Liittyy ryhmään No comment provided by engineer. + + Keep + No comment provided by engineer. + Keep the app open to use it from desktop No comment provided by engineer. + + Keep unused invitation? + No comment provided by engineer. + Keep your connections Pidä kontaktisi @@ -3370,6 +3379,10 @@ This is your link for group %@! Uusi pääsykoodi No comment provided by engineer. + + New chat + No comment provided by engineer. + New contact request Uusi kontaktipyyntö @@ -3492,6 +3505,10 @@ This is your link for group %@! - poista jäsenet käytöstä ("tarkkailija" rooli) No comment provided by engineer. + + OK + No comment provided by engineer. + Off Pois @@ -3639,6 +3656,14 @@ This is your link for group %@! Opening app… No comment provided by engineer. + + Or scan QR code + No comment provided by engineer. + + + Or show this code + No comment provided by engineer. + PING count PING-määrä @@ -3679,11 +3704,6 @@ This is your link for group %@! Salasana näytettäväksi No comment provided by engineer. - - Paste - Liitä - No comment provided by engineer. - Paste desktop address No comment provided by engineer. @@ -3693,16 +3713,10 @@ This is your link for group %@! Liitä kuva No comment provided by engineer. - - Paste received link - Liitä vastaanotettu linkki + + Paste the link you received No comment provided by engineer. - - Paste the link you received to connect with your contact. - Liitä saamasi linkki, jonka avulla voit muodostaa yhteyden kontaktiisi. - placeholder - People can connect to you only via the links you share. Ihmiset voivat ottaa sinuun yhteyttä vain jakamiesi linkkien kautta. @@ -3946,6 +3960,10 @@ Error: %@ Lue lisää [Käyttöoppaasta](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). Lue lisää [Käyttöoppaasta](https://simplex.chat/docs/guide/readme.html#connect-to-friends). @@ -4154,6 +4172,10 @@ Error: %@ Virhe tietokannan palauttamisessa No comment provided by engineer. + + Retry + No comment provided by engineer. + Reveal Paljasta @@ -4308,6 +4330,10 @@ Error: %@ Haku No comment provided by engineer. + + Search or paste SimpleX link + No comment provided by engineer. + Secure queue Turvallinen jono @@ -4581,9 +4607,8 @@ Error: %@ Jaa linkki No comment provided by engineer. - - Share one-time invitation link - Jaa kertakutsulinkki + + Share this 1-time invite link No comment provided by engineer. @@ -4705,11 +4730,6 @@ Error: %@ Joku notification title - - Start a new chat - Aloita uusi keskustelu - No comment provided by engineer. - Start chat Aloita keskustelu @@ -4843,6 +4863,14 @@ Error: %@ Napauta liittyäksesi incognito-tilassa No comment provided by engineer. + + Tap to paste link + No comment provided by engineer. + + + Tap to scan + No comment provided by engineer. + Tap to start a new chat Aloita uusi keskustelu napauttamalla @@ -4905,6 +4933,10 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.Tietokannan tunnuslauseen muuttamista ei suoritettu loppuun. No comment provided by engineer. + + The code you scanned is not a SimpleX link QR code. + No comment provided by engineer. + The connection you accepted will be cancelled! Hyväksymäsi yhteys peruuntuu! @@ -4970,6 +5002,10 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.Palvelimet nykyisen keskusteluprofiilisi uusille yhteyksille **%@**. No comment provided by engineer. + + The text you pasted is not a SimpleX link. + No comment provided by engineer. + Theme Teema @@ -5566,11 +5602,6 @@ Repeat join request? Voit vastaanottaa puheluita lukitusnäytöltä ilman laitteen ja sovelluksen todennusta. No comment provided by engineer. - - You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button. - Voit myös muodostaa yhteyden klikkaamalla linkkiä. Jos se avautuu selaimessa, napsauta **Avaa mobiilisovelluksessa**-painiketta. - No comment provided by engineer. - You can create it later Voit luoda sen myöhemmin @@ -5635,6 +5666,10 @@ Repeat join request? Voit käyttää markdownia viestien muotoiluun: No comment provided by engineer. + + You can view invitation link again in connection details. + No comment provided by engineer. + You can't send messages! Et voi lähettää viestejä! diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 6ec667ed91..4d08ee9652 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -312,14 +312,17 @@ ) No comment provided by engineer. + + **Add contact**: to create a new invitation link, or connect via a link you received. + No comment provided by engineer. + **Add new contact**: to create your one-time QR Code or link for your contact. **Ajouter un nouveau contact** : pour créer un lien ou code QR unique pour votre contact. No comment provided by engineer. - - **Create link / QR code** for your contact to use. - **Créer un lien / code QR** que votre contact pourra utiliser. + + **Create group**: to create a new group. No comment provided by engineer. @@ -332,11 +335,6 @@ **Confidentiel** : ne pas utiliser le serveur de notifications SimpleX, vérification de nouveaux messages periodiquement en arrière plan (dépend de l'utilisation de l'app). No comment provided by engineer. - - **Paste received link** or open it in the browser and tap **Open in mobile app**. - **Collez le lien reçu** ou ouvrez-le dans votre navigateur et appuyez sur **Open in mobile app**. - No comment provided by engineer. - **Please note**: you will NOT be able to recover or change passphrase if you lose it. **Veuillez noter** : vous NE pourrez PAS récupérer ou modifier votre phrase secrète si vous la perdez. @@ -347,11 +345,6 @@ **Recommandé** : le token de l'appareil et les notifications sont envoyés au serveur de notifications SimpleX, mais pas le contenu du message, sa taille ou son auteur. No comment provided by engineer. - - **Scan QR code**: to connect to your contact in person or via video call. - **Scanner le code QR** : pour vous connecter à votre contact en personne ou par appel vidéo. - No comment provided by engineer. - **Warning**: Instant push notifications require passphrase saved in Keychain. **Avertissement** : les notifications push instantanées nécessitent une phrase secrète enregistrée dans la keychain. @@ -453,11 +446,6 @@ 1 semaine time interval - - 1-time link - Lien à usage unique - No comment provided by engineer. - 5 minutes 5 minutes @@ -573,6 +561,10 @@ Ajoutez une adresse à votre profil, afin que vos contacts puissent la partager avec d'autres personnes. La mise à jour du profil sera envoyée à vos contacts. No comment provided by engineer. + + Add contact + No comment provided by engineer. + Add preset servers Ajouter des serveurs prédéfinis @@ -978,6 +970,10 @@ Appels No comment provided by engineer. + + Camera not available + No comment provided by engineer. + Can't delete user profile! Impossible de supprimer le profil d'utilisateur ! @@ -1242,11 +1238,6 @@ Il s'agit de votre propre lien unique ! Se connecter via un lien No comment provided by engineer. - - Connect via link / QR code - Se connecter via un lien / code QR - No comment provided by engineer. - Connect via one-time link Se connecter via un lien unique @@ -1422,11 +1413,6 @@ Il s'agit de votre propre lien unique ! Créer un nouveau profil sur [l'application de bureau](https://simplex.chat/downloads/). 💻 No comment provided by engineer. - - Create one-time invitation link - Créer un lien d'invitation unique - No comment provided by engineer. - Create profile Créer le profil @@ -1452,6 +1438,10 @@ Il s'agit de votre propre lien unique ! Créé le %@ No comment provided by engineer. + + Creating link… + No comment provided by engineer. + Current Passcode Code d'accès actuel @@ -2002,6 +1992,10 @@ Cette opération ne peut être annulée ! Activer la suppression automatique des messages ? No comment provided by engineer. + + Enable camera access + No comment provided by engineer. + Enable for all Activer pour tous @@ -2345,6 +2339,10 @@ Cette opération ne peut être annulée ! Erreur d'enregistrement du mot de passe de l'utilisateur No comment provided by engineer. + + Error scanning code: %@ + No comment provided by engineer. + Error sending email Erreur lors de l'envoi de l'e-mail @@ -2820,11 +2818,6 @@ Cette opération ne peut être annulée ! Si vous ne pouvez pas vous rencontrer en personne, montrez le code QR lors d'un appel vidéo ou partagez le lien. No comment provided by engineer. - - If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link. - Si vous ne pouvez pas voir la personne, vous pouvez **scanner le code QR dans un appel vidéo**, ou votre contact peut vous partager un lien d'invitation. - No comment provided by engineer. - If you enter this passcode when opening the app, all app data will be irreversibly removed! Si vous saisissez ce code à l'ouverture de l'application, toutes les données de l'application seront irréversiblement supprimées ! @@ -2982,11 +2975,19 @@ Cette opération ne peut être annulée ! Interface No comment provided by engineer. + + Invalid QR code + No comment provided by engineer. + Invalid connection link Lien de connection invalide No comment provided by engineer. + + Invalid link + No comment provided by engineer. + Invalid name! Nom invalide ! @@ -3114,11 +3115,19 @@ Voici votre lien pour le groupe %@ ! Entrain de rejoindre le groupe No comment provided by engineer. + + Keep + No comment provided by engineer. + Keep the app open to use it from desktop Garder l'application ouverte pour l'utiliser depuis le bureau No comment provided by engineer. + + Keep unused invitation? + No comment provided by engineer. + Keep your connections Conserver vos connexions @@ -3449,6 +3458,10 @@ Voici votre lien pour le groupe %@ ! Nouveau code d'accès No comment provided by engineer. + + New chat + No comment provided by engineer. + New contact request Nouvelle demande de contact @@ -3573,6 +3586,10 @@ Voici votre lien pour le groupe %@ ! - désactiver des membres (rôle "observateur") No comment provided by engineer. + + OK + No comment provided by engineer. + Off Off @@ -3722,6 +3739,14 @@ Voici votre lien pour le groupe %@ ! Opening app… No comment provided by engineer. + + Or scan QR code + No comment provided by engineer. + + + Or show this code + No comment provided by engineer. + PING count Nombre de PING @@ -3762,11 +3787,6 @@ Voici votre lien pour le groupe %@ ! Mot de passe à entrer No comment provided by engineer. - - Paste - Coller - No comment provided by engineer. - Paste desktop address Coller l'adresse du bureau @@ -3777,16 +3797,10 @@ Voici votre lien pour le groupe %@ ! Coller l'image No comment provided by engineer. - - Paste received link - Coller le lien reçu + + Paste the link you received No comment provided by engineer. - - Paste the link you received to connect with your contact. - Collez le lien que vous avez reçu dans le cadre ci-dessous pour vous connecter avec votre contact. - placeholder - People can connect to you only via the links you share. On ne peut se connecter à vous qu’avec les liens que vous partagez. @@ -4032,6 +4046,10 @@ Error: %@ Pour en savoir plus, consultez le [Guide de l'utilisateur](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). Pour en savoir plus, consultez le [Guide de l'utilisateur](https://simplex.chat/docs/guide/readme.html#connect-to-friends). @@ -4242,6 +4260,10 @@ Error: %@ Erreur de restauration de la base de données No comment provided by engineer. + + Retry + No comment provided by engineer. + Reveal Révéler @@ -4397,6 +4419,10 @@ Error: %@ Recherche No comment provided by engineer. + + Search or paste SimpleX link + No comment provided by engineer. + Secure queue File d'attente sécurisée @@ -4672,9 +4698,8 @@ Error: %@ Partager le lien No comment provided by engineer. - - Share one-time invitation link - Partager un lien d'invitation unique + + Share this 1-time invite link No comment provided by engineer. @@ -4797,11 +4822,6 @@ Error: %@ Quelqu'un notification title - - Start a new chat - Commencer une nouvelle conversation - No comment provided by engineer. - Start chat Démarrer le chat @@ -4936,6 +4956,14 @@ Error: %@ Appuyez pour rejoindre incognito No comment provided by engineer. + + Tap to paste link + No comment provided by engineer. + + + Tap to scan + No comment provided by engineer. + Tap to start a new chat Appuyez ici pour démarrer une nouvelle discussion @@ -4998,6 +5026,10 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. La tentative de modification de la phrase secrète de la base de données n'a pas abouti. No comment provided by engineer. + + The code you scanned is not a SimpleX link QR code. + No comment provided by engineer. + The connection you accepted will be cancelled! La connexion que vous avez acceptée sera annulée ! @@ -5063,6 +5095,10 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Les serveurs pour les nouvelles connexions de votre profil de chat actuel **%@**. No comment provided by engineer. + + The text you pasted is not a SimpleX link. + No comment provided by engineer. + Theme Thème @@ -5683,11 +5719,6 @@ Répéter la demande d'adhésion ? Vous pouvez accepter des appels à partir de l'écran de verrouillage, sans authentification de l'appareil ou de l'application. No comment provided by engineer. - - You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button. - Vous pouvez également vous connecter en cliquant sur le lien. S'il s'ouvre dans le navigateur, cliquez sur le bouton **Open in mobile app**. - No comment provided by engineer. - You can create it later Vous pouvez la créer plus tard @@ -5752,6 +5783,10 @@ Répéter la demande d'adhésion ? Vous pouvez utiliser le format markdown pour mettre en forme les messages : No comment provided by engineer. + + You can view invitation link again in connection details. + No comment provided by engineer. + You can't send messages! Vous ne pouvez pas envoyer de messages ! 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 d1a17a3af2..d9d48bd995 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -312,14 +312,17 @@ ) No comment provided by engineer. + + **Add contact**: to create a new invitation link, or connect via a link you received. + No comment provided by engineer. + **Add new contact**: to create your one-time QR Code or link for your contact. **Aggiungi un contatto**: per creare il tuo codice QR o link una tantum per il tuo contatto. No comment provided by engineer. - - **Create link / QR code** for your contact to use. - **Crea link / codice QR** da usare per il tuo contatto. + + **Create group**: to create a new group. No comment provided by engineer. @@ -332,11 +335,6 @@ **Il più privato**: non usare il server di notifica di SimpleX Chat, controlla i messaggi periodicamente in secondo piano (dipende da quanto spesso usi l'app). No comment provided by engineer. - - **Paste received link** or open it in the browser and tap **Open in mobile app**. - **Incolla il link ricevuto** o aprilo nel browser e tocca **Apri in app mobile**. - No comment provided by engineer. - **Please note**: you will NOT be able to recover or change passphrase if you lose it. **Nota bene**: NON potrai recuperare o cambiare la password se la perdi. @@ -347,11 +345,6 @@ **Consigliato**: vengono inviati il token del dispositivo e le notifiche al server di notifica di SimpleX Chat, ma non il contenuto del messaggio,la sua dimensione o il suo mittente. No comment provided by engineer. - - **Scan QR code**: to connect to your contact in person or via video call. - **Scansiona codice QR**: per connetterti al contatto di persona o via videochiamata. - No comment provided by engineer. - **Warning**: Instant push notifications require passphrase saved in Keychain. **Attenzione**: le notifiche push istantanee richiedono una password salvata nel portachiavi. @@ -453,11 +446,6 @@ 1 settimana time interval - - 1-time link - Link una tantum - No comment provided by engineer. - 5 minutes 5 minuti @@ -573,6 +561,10 @@ Aggiungi l'indirizzo al tuo profilo, in modo che i tuoi contatti possano condividerlo con altre persone. L'aggiornamento del profilo verrà inviato ai tuoi contatti. No comment provided by engineer. + + Add contact + No comment provided by engineer. + Add preset servers Aggiungi server preimpostati @@ -978,6 +970,10 @@ Chiamate No comment provided by engineer. + + Camera not available + No comment provided by engineer. + Can't delete user profile! Impossibile eliminare il profilo utente! @@ -1242,11 +1238,6 @@ Questo è il tuo link una tantum! Connetti via link No comment provided by engineer. - - Connect via link / QR code - Connetti via link / codice QR - No comment provided by engineer. - Connect via one-time link Connetti via link una tantum @@ -1422,11 +1413,6 @@ Questo è il tuo link una tantum! Crea un nuovo profilo nell'[app desktop](https://simplex.chat/downloads/). 💻 No comment provided by engineer. - - Create one-time invitation link - Crea link di invito una tantum - No comment provided by engineer. - Create profile Crea profilo @@ -1452,6 +1438,10 @@ Questo è il tuo link una tantum! Creato il %@ No comment provided by engineer. + + Creating link… + No comment provided by engineer. + Current Passcode Codice di accesso attuale @@ -2002,6 +1992,10 @@ Non è reversibile! Attivare l'eliminazione automatica dei messaggi? No comment provided by engineer. + + Enable camera access + No comment provided by engineer. + Enable for all Attiva per tutti @@ -2345,6 +2339,10 @@ Non è reversibile! Errore nel salvataggio della password utente No comment provided by engineer. + + Error scanning code: %@ + No comment provided by engineer. + Error sending email Errore nell'invio dell'email @@ -2820,11 +2818,6 @@ Non è reversibile! Se non potete incontrarvi di persona, mostra il codice QR in una videochiamata o condividi il link. No comment provided by engineer. - - If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link. - Se non potete incontrarvi di persona, puoi **scansionare il codice QR durante la videochiamata** oppure il tuo contatto può condividere un link di invito. - No comment provided by engineer. - If you enter this passcode when opening the app, all app data will be irreversibly removed! Se inserisci questo codice all'apertura dell'app, tutti i dati di essa verranno rimossi in modo irreversibile! @@ -2982,11 +2975,19 @@ Non è reversibile! Interfaccia No comment provided by engineer. + + Invalid QR code + No comment provided by engineer. + Invalid connection link Link di connessione non valido No comment provided by engineer. + + Invalid link + No comment provided by engineer. + Invalid name! Nome non valido! @@ -3114,11 +3115,19 @@ Questo è il tuo link per il gruppo %@! Ingresso nel gruppo No comment provided by engineer. + + Keep + No comment provided by engineer. + Keep the app open to use it from desktop Tieni aperta l'app per usarla dal desktop No comment provided by engineer. + + Keep unused invitation? + No comment provided by engineer. + Keep your connections Mantieni le tue connessioni @@ -3449,6 +3458,10 @@ Questo è il tuo link per il gruppo %@! Nuovo codice di accesso No comment provided by engineer. + + New chat + No comment provided by engineer. + New contact request Nuova richiesta di contatto @@ -3573,6 +3586,10 @@ Questo è il tuo link per il gruppo %@! - disattivare i membri (ruolo "osservatore") No comment provided by engineer. + + OK + No comment provided by engineer. + Off Off @@ -3722,6 +3739,14 @@ Questo è il tuo link per il gruppo %@! Opening app… No comment provided by engineer. + + Or scan QR code + No comment provided by engineer. + + + Or show this code + No comment provided by engineer. + PING count Conteggio PING @@ -3762,11 +3787,6 @@ Questo è il tuo link per il gruppo %@! Password per mostrare No comment provided by engineer. - - Paste - Incolla - No comment provided by engineer. - Paste desktop address Incolla l'indirizzo desktop @@ -3777,16 +3797,10 @@ Questo è il tuo link per il gruppo %@! Incolla immagine No comment provided by engineer. - - Paste received link - Incolla il link ricevuto + + Paste the link you received No comment provided by engineer. - - Paste the link you received to connect with your contact. - Incolla il link che hai ricevuto nella casella sottostante per connetterti con il tuo contatto. - placeholder - People can connect to you only via the links you share. Le persone possono connettersi a te solo tramite i link che condividi. @@ -4032,6 +4046,10 @@ Error: %@ Maggiori informazioni nella [Guida per l'utente](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). Maggiori informazioni nella [Guida per l'utente](https://simplex.chat/docs/guide/readme.html#connect-to-friends). @@ -4242,6 +4260,10 @@ Error: %@ Errore di ripristino del database No comment provided by engineer. + + Retry + No comment provided by engineer. + Reveal Rivela @@ -4397,6 +4419,10 @@ Error: %@ Cerca No comment provided by engineer. + + Search or paste SimpleX link + No comment provided by engineer. + Secure queue Coda sicura @@ -4672,9 +4698,8 @@ Error: %@ Condividi link No comment provided by engineer. - - Share one-time invitation link - Condividi link di invito una tantum + + Share this 1-time invite link No comment provided by engineer. @@ -4797,11 +4822,6 @@ Error: %@ Qualcuno notification title - - Start a new chat - Inizia una nuova chat - No comment provided by engineer. - Start chat Avvia chat @@ -4936,6 +4956,14 @@ Error: %@ Toccare per entrare in incognito No comment provided by engineer. + + Tap to paste link + No comment provided by engineer. + + + Tap to scan + No comment provided by engineer. + Tap to start a new chat Tocca per iniziare una chat @@ -4998,6 +5026,10 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.Il tentativo di cambiare la password del database non è stato completato. No comment provided by engineer. + + The code you scanned is not a SimpleX link QR code. + No comment provided by engineer. + The connection you accepted will be cancelled! La connessione che hai accettato verrà annullata! @@ -5063,6 +5095,10 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.I server per le nuove connessioni del profilo di chat attuale **%@**. No comment provided by engineer. + + The text you pasted is not a SimpleX link. + No comment provided by engineer. + Theme Tema @@ -5683,11 +5719,6 @@ Ripetere la richiesta di ingresso? Puoi accettare chiamate dalla schermata di blocco, senza l'autenticazione del dispositivo e dell'app. No comment provided by engineer. - - You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button. - Puoi anche connetterti cliccando il link. Se si apre nel browser, clicca il pulsante **Apri nell'app mobile**. - No comment provided by engineer. - You can create it later Puoi crearlo più tardi @@ -5752,6 +5783,10 @@ Ripetere la richiesta di ingresso? Puoi usare il markdown per formattare i messaggi: No comment provided by engineer. + + You can view invitation link again in connection details. + No comment provided by engineer. + You can't send messages! Non puoi inviare messaggi! diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index ac7f535e36..de25ffb08f 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -306,14 +306,17 @@ ) No comment provided by engineer. + + **Add contact**: to create a new invitation link, or connect via a link you received. + No comment provided by engineer. + **Add new contact**: to create your one-time QR Code or link for your contact. **新しい連絡先を追加**: 連絡先のワンタイム QR コードまたはリンクを作成します。 No comment provided by engineer. - - **Create link / QR code** for your contact to use. - 連絡先が使用する **リンク/QR コードを作成します**。 + + **Create group**: to create a new group. No comment provided by engineer. @@ -326,11 +329,6 @@ **最もプライベート**: SimpleX Chat 通知サーバーを使用せず、バックグラウンドで定期的にメッセージをチェックします (アプリの使用頻度によって異なります)。 No comment provided by engineer. - - **Paste received link** or open it in the browser and tap **Open in mobile app**. - **受信したリンク**を貼り付けるか、ブラウザーで開いて [**モバイル アプリで開く**] をタップします。 - No comment provided by engineer. - **Please note**: you will NOT be able to recover or change passphrase if you lose it. **注意**: パスフレーズを紛失すると、パスフレーズを復元または変更できなくなります。 @@ -341,11 +339,6 @@ **推奨**: デバイス トークンと通知は SimpleX Chat 通知サーバーに送信されますが、メッセージの内容、サイズ、送信者は送信されません。 No comment provided by engineer. - - **Scan QR code**: to connect to your contact in person or via video call. - **QR コードをスキャン**: 直接またはビデオ通話で連絡先に接続します。 - No comment provided by engineer. - **Warning**: Instant push notifications require passphrase saved in Keychain. **警告**: 即時の プッシュ通知には、キーチェーンに保存されたパスフレーズが必要です。 @@ -440,11 +433,6 @@ 1週間 time interval - - 1-time link - 使い捨てのリンク - No comment provided by engineer. - 5 minutes 5分 @@ -560,6 +548,10 @@ プロフィールにアドレスを追加し、連絡先があなたのアドレスを他の人と共有できるようにします。プロフィールの更新は連絡先に送信されます。 No comment provided by engineer. + + Add contact + No comment provided by engineer. + Add preset servers 既存サーバを追加 @@ -956,6 +948,10 @@ 通話 No comment provided by engineer. + + Camera not available + No comment provided by engineer. + Can't delete user profile! ユーザープロフィールが削除できません! @@ -1212,11 +1208,6 @@ This is your own one-time link! リンク経由で接続 No comment provided by engineer. - - Connect via link / QR code - リンク・QRコード経由で接続 - No comment provided by engineer. - Connect via one-time link 使い捨てリンク経由で接続しますか? @@ -1384,11 +1375,6 @@ This is your own one-time link! [デスクトップアプリ](https://simplex.chat/downloads/)で新しいプロファイルを作成します。 💻 No comment provided by engineer. - - Create one-time invitation link - 使い捨ての招待リンクを生成する - No comment provided by engineer. - Create profile No comment provided by engineer. @@ -1413,6 +1399,10 @@ This is your own one-time link! %@ によって作成されました No comment provided by engineer. + + Creating link… + No comment provided by engineer. + Current Passcode 現在のパスコード @@ -1954,6 +1944,10 @@ This cannot be undone! 自動メッセージ削除を有効にしますか? No comment provided by engineer. + + Enable camera access + No comment provided by engineer. + Enable for all すべて有効 @@ -2291,6 +2285,10 @@ This cannot be undone! ユーザーパスワード保存エラー No comment provided by engineer. + + Error scanning code: %@ + No comment provided by engineer. + Error sending email メールの送信にエラー発生 @@ -2759,11 +2757,6 @@ This cannot be undone! 直接会えない場合は、ビデオ通話で QR コードを表示するか、リンクを共有してください。 No comment provided by engineer. - - If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link. - 直接会えない場合は、**ビデオ通話で QR コードを表示する**か、リンクを共有してください。 - No comment provided by engineer. - If you enter this passcode when opening the app, all app data will be irreversibly removed! アプリを開くときにこのパスコードを入力すると、アプリのすべてのデータが元に戻せないように削除されます! @@ -2919,11 +2912,19 @@ This cannot be undone! インターフェース No comment provided by engineer. + + Invalid QR code + No comment provided by engineer. + Invalid connection link 無効な接続リンク No comment provided by engineer. + + Invalid link + No comment provided by engineer. + Invalid name! No comment provided by engineer. @@ -3046,10 +3047,18 @@ This is your link for group %@! グループに参加 No comment provided by engineer. + + Keep + No comment provided by engineer. + Keep the app open to use it from desktop No comment provided by engineer. + + Keep unused invitation? + No comment provided by engineer. + Keep your connections 接続を維持 @@ -3375,6 +3384,10 @@ This is your link for group %@! 新しいパスコード No comment provided by engineer. + + New chat + No comment provided by engineer. + New contact request 新しい繋がりのリクエスト @@ -3498,6 +3511,10 @@ This is your link for group %@! - メンバーを無効にする (メッセージの送信不可) No comment provided by engineer. + + OK + No comment provided by engineer. + Off オフ @@ -3646,6 +3663,14 @@ This is your link for group %@! Opening app… No comment provided by engineer. + + Or scan QR code + No comment provided by engineer. + + + Or show this code + No comment provided by engineer. + PING count PING回数 @@ -3686,11 +3711,6 @@ This is your link for group %@! パスワードを表示する No comment provided by engineer. - - Paste - 貼り付け - No comment provided by engineer. - Paste desktop address No comment provided by engineer. @@ -3700,16 +3720,10 @@ This is your link for group %@! 画像の貼り付け No comment provided by engineer. - - Paste received link - 頂いたリンクを貼り付ける + + Paste the link you received No comment provided by engineer. - - Paste the link you received to connect with your contact. - 連絡相手から頂いたリンクを以下の入力欄に貼り付けて繋がります。 - placeholder - People can connect to you only via the links you share. あなたと繋がることができるのは、あなたからリンクを頂いた方のみです。 @@ -3953,6 +3967,10 @@ Error: %@ 詳しくは[ユーザーガイド](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)をご覧ください。 No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). 詳しくは[ユーザーガイド](https://simplex.chat/docs/guide/readme.html#connect-to-friends)をご覧ください。 @@ -4160,6 +4178,10 @@ Error: %@ データベース復元エラー No comment provided by engineer. + + Retry + No comment provided by engineer. + Reveal 開示する @@ -4314,6 +4336,10 @@ Error: %@ 検索 No comment provided by engineer. + + Search or paste SimpleX link + No comment provided by engineer. + Secure queue 待ち行列セキュリティ確認 @@ -4580,9 +4606,8 @@ Error: %@ リンクを送る No comment provided by engineer. - - Share one-time invitation link - 使い捨ての招待リンクを共有 + + Share this 1-time invite link No comment provided by engineer. @@ -4705,11 +4730,6 @@ Error: %@ 誰か notification title - - Start a new chat - 新しいチャットを開始する - No comment provided by engineer. - Start chat チャットを開始する @@ -4843,6 +4863,14 @@ Error: %@ タップしてシークレットモードで参加 No comment provided by engineer. + + Tap to paste link + No comment provided by engineer. + + + Tap to scan + No comment provided by engineer. + Tap to start a new chat タップして新しいチャットを始める @@ -4905,6 +4933,10 @@ It can happen because of some bug or when the connection is compromised.データベースのパスフレーズ変更が完了してません。 No comment provided by engineer. + + The code you scanned is not a SimpleX link QR code. + No comment provided by engineer. + The connection you accepted will be cancelled! 承認済の接続がキャンセルされます! @@ -4970,6 +5002,10 @@ It can happen because of some bug or when the connection is compromised.現在のチャットプロフィールの新しい接続のサーバ **%@**。 No comment provided by engineer. + + The text you pasted is not a SimpleX link. + No comment provided by engineer. + Theme テーマ @@ -5565,11 +5601,6 @@ Repeat join request? デバイスやアプリの認証を行わずに、ロック画面から通話を受けることができます。 No comment provided by engineer. - - You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button. - リンクをクリックすることでも接続できます。ブラウザで開いた場合は、**モバイルアプリで開く**ボタンをクリックしてください。 - No comment provided by engineer. - You can create it later 後からでも作成できます @@ -5634,6 +5665,10 @@ Repeat join request? メッセージの書式にmarkdownを使用することができます: No comment provided by engineer. + + You can view invitation link again in connection details. + No comment provided by engineer. + You can't send messages! メッセージを送信できませんでした! diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 6b90e549b4..e8aef28d41 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -312,14 +312,17 @@ ) No comment provided by engineer. + + **Add contact**: to create a new invitation link, or connect via a link you received. + No comment provided by engineer. + **Add new contact**: to create your one-time QR Code or link for your contact. **Nieuw contact toevoegen**: om uw eenmalige QR-code of link voor uw contact te maken. No comment provided by engineer. - - **Create link / QR code** for your contact to use. - **Maak een link / QR-code aan** die uw contact kan gebruiken. + + **Create group**: to create a new group. No comment provided by engineer. @@ -332,11 +335,6 @@ **Meest privé**: gebruik geen SimpleX Chat-notificatie server, controleer berichten regelmatig op de achtergrond (afhankelijk van hoe vaak u de app gebruikt). No comment provided by engineer. - - **Paste received link** or open it in the browser and tap **Open in mobile app**. - **Plak de ontvangen link** of open deze in de browser en tik op **Openen in mobiele app**. - No comment provided by engineer. - **Please note**: you will NOT be able to recover or change passphrase if you lose it. **Let op**: u kunt het wachtwoord NIET herstellen of wijzigen als u het kwijtraakt. @@ -347,11 +345,6 @@ **Aanbevolen**: apparaattoken en meldingen worden naar de SimpleX Chat-meldingsserver gestuurd, maar niet de berichtinhoud, -grootte of van wie het afkomstig is. No comment provided by engineer. - - **Scan QR code**: to connect to your contact in person or via video call. - **Scan QR-code**: om persoonlijk of via een video gesprek verbinding te maken met uw contact. - No comment provided by engineer. - **Warning**: Instant push notifications require passphrase saved in Keychain. **Waarschuwing**: voor directe push meldingen is een wachtwoord vereist dat is opgeslagen in de Keychain. @@ -453,11 +446,6 @@ 1 week time interval - - 1-time link - Eenmalige link - No comment provided by engineer. - 5 minutes 5 minuten @@ -573,6 +561,10 @@ Voeg een adres toe aan uw profiel, zodat uw contacten het met andere mensen kunnen delen. Profiel update wordt naar uw contacten verzonden. No comment provided by engineer. + + Add contact + No comment provided by engineer. + Add preset servers Vooraf ingestelde servers toevoegen @@ -978,6 +970,10 @@ Oproepen No comment provided by engineer. + + Camera not available + No comment provided by engineer. + Can't delete user profile! Kan gebruikers profiel niet verwijderen! @@ -1242,11 +1238,6 @@ Dit is uw eigen eenmalige link! Maak verbinding via link No comment provided by engineer. - - Connect via link / QR code - Maak verbinding via link / QR-code - No comment provided by engineer. - Connect via one-time link Verbinden via een eenmalige link? @@ -1422,11 +1413,6 @@ Dit is uw eigen eenmalige link! Maak een nieuw profiel aan in [desktop-app](https://simplex.chat/downloads/). 💻 No comment provided by engineer. - - Create one-time invitation link - Maak een eenmalige uitnodiging link - No comment provided by engineer. - Create profile Maak een profiel aan @@ -1452,6 +1438,10 @@ Dit is uw eigen eenmalige link! Gemaakt op %@ No comment provided by engineer. + + Creating link… + No comment provided by engineer. + Current Passcode Huidige toegangscode @@ -2002,6 +1992,10 @@ Dit kan niet ongedaan gemaakt worden! Automatisch verwijderen van berichten aanzetten? No comment provided by engineer. + + Enable camera access + No comment provided by engineer. + Enable for all Inschakelen voor iedereen @@ -2345,6 +2339,10 @@ Dit kan niet ongedaan gemaakt worden! Fout bij opslaan gebruikers wachtwoord No comment provided by engineer. + + Error scanning code: %@ + No comment provided by engineer. + Error sending email Fout bij het verzenden van e-mail @@ -2820,11 +2818,6 @@ Dit kan niet ongedaan gemaakt worden! Als je elkaar niet persoonlijk kunt ontmoeten, laat dan de QR-code zien in een videogesprek of deel de link. No comment provided by engineer. - - If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link. - Als u elkaar niet persoonlijk kunt ontmoeten, kunt u **de QR-code scannen in het video gesprek**, of uw contact kan een uitnodiging link delen. - No comment provided by engineer. - If you enter this passcode when opening the app, all app data will be irreversibly removed! Als u deze toegangscode invoert bij het openen van de app, worden alle app-gegevens onomkeerbaar verwijderd! @@ -2982,11 +2975,19 @@ Dit kan niet ongedaan gemaakt worden! Interface No comment provided by engineer. + + Invalid QR code + No comment provided by engineer. + Invalid connection link Ongeldige verbinding link No comment provided by engineer. + + Invalid link + No comment provided by engineer. + Invalid name! Ongeldige naam! @@ -3114,11 +3115,19 @@ Dit is jouw link voor groep %@! Deel nemen aan groep No comment provided by engineer. + + Keep + No comment provided by engineer. + Keep the app open to use it from desktop Houd de app geopend om deze vanaf de desktop te gebruiken No comment provided by engineer. + + Keep unused invitation? + No comment provided by engineer. + Keep your connections Behoud uw verbindingen @@ -3449,6 +3458,10 @@ Dit is jouw link voor groep %@! Nieuwe toegangscode No comment provided by engineer. + + New chat + No comment provided by engineer. + New contact request Nieuw contactverzoek @@ -3573,6 +3586,10 @@ Dit is jouw link voor groep %@! - schakel leden uit ("waarnemer" rol) No comment provided by engineer. + + OK + No comment provided by engineer. + Off Uit @@ -3722,6 +3739,14 @@ Dit is jouw link voor groep %@! Opening app… No comment provided by engineer. + + Or scan QR code + No comment provided by engineer. + + + Or show this code + No comment provided by engineer. + PING count PING count @@ -3762,11 +3787,6 @@ Dit is jouw link voor groep %@! Wachtwoord om weer te geven No comment provided by engineer. - - Paste - Plakken - No comment provided by engineer. - Paste desktop address Desktopadres plakken @@ -3777,16 +3797,10 @@ Dit is jouw link voor groep %@! Afbeelding plakken No comment provided by engineer. - - Paste received link - Plak de ontvangen link + + Paste the link you received No comment provided by engineer. - - Paste the link you received to connect with your contact. - Plak de link die je hebt ontvangen in het vak hieronder om verbinding te maken met je contact. - placeholder - People can connect to you only via the links you share. Mensen kunnen alleen verbinding met u maken via de links die u deelt. @@ -4032,6 +4046,10 @@ Error: %@ Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/app-settings.html#uw-simplex-contactadres). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/readme.html#connect-to-friends). @@ -4242,6 +4260,10 @@ Error: %@ Database fout herstellen No comment provided by engineer. + + Retry + No comment provided by engineer. + Reveal Onthullen @@ -4397,6 +4419,10 @@ Error: %@ Zoeken No comment provided by engineer. + + Search or paste SimpleX link + No comment provided by engineer. + Secure queue Veilige wachtrij @@ -4672,9 +4698,8 @@ Error: %@ Deel link No comment provided by engineer. - - Share one-time invitation link - Eenmalige uitnodiging link delen + + Share this 1-time invite link No comment provided by engineer. @@ -4797,11 +4822,6 @@ Error: %@ Iemand notification title - - Start a new chat - Begin een nieuw gesprek - No comment provided by engineer. - Start chat Begin gesprek @@ -4936,6 +4956,14 @@ Error: %@ Tik om incognito lid te worden No comment provided by engineer. + + Tap to paste link + No comment provided by engineer. + + + Tap to scan + No comment provided by engineer. + Tap to start a new chat Tik om een nieuw gesprek te starten @@ -4998,6 +5026,10 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. De poging om het wachtwoord van de database te wijzigen is niet voltooid. No comment provided by engineer. + + The code you scanned is not a SimpleX link QR code. + No comment provided by engineer. + The connection you accepted will be cancelled! De door u geaccepteerde verbinding wordt geannuleerd! @@ -5063,6 +5095,10 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. De servers voor nieuwe verbindingen van uw huidige chat profiel **%@**. No comment provided by engineer. + + The text you pasted is not a SimpleX link. + No comment provided by engineer. + Theme Thema @@ -5683,11 +5719,6 @@ Deelnameverzoek herhalen? U kunt oproepen van het vergrendelingsscherm accepteren, zonder apparaat- en app-verificatie. No comment provided by engineer. - - You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button. - U kunt ook verbinding maken door op de link te klikken. Als het in de browser wordt geopend, klikt u op de knop **Openen in mobiele app**. - No comment provided by engineer. - You can create it later U kan het later maken @@ -5752,6 +5783,10 @@ Deelnameverzoek herhalen? U kunt markdown gebruiken voor opmaak in berichten: No comment provided by engineer. + + You can view invitation link again in connection details. + No comment provided by engineer. + You can't send messages! Je kunt geen berichten versturen! diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index 1ab569e3c3..0cdbb8c7e1 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -312,14 +312,17 @@ ) No comment provided by engineer. + + **Add contact**: to create a new invitation link, or connect via a link you received. + No comment provided by engineer. + **Add new contact**: to create your one-time QR Code or link for your contact. **Dodaj nowy kontakt**: aby stworzyć swój jednorazowy kod QR lub link dla kontaktu. No comment provided by engineer. - - **Create link / QR code** for your contact to use. - **Utwórz link / kod QR**, aby Twój kontakt mógł z niego skorzystać. + + **Create group**: to create a new group. No comment provided by engineer. @@ -332,11 +335,6 @@ **Najbardziej prywatny**: nie korzystaj z serwera powiadomień SimpleX Chat, sprawdzaj wiadomości okresowo w tle (zależy jak często korzystasz z aplikacji). No comment provided by engineer. - - **Paste received link** or open it in the browser and tap **Open in mobile app**. - **Wklej otrzymany link** lub otwórz go w przeglądarce i dotknij **Otwórz w aplikacji mobilnej**. - No comment provided by engineer. - **Please note**: you will NOT be able to recover or change passphrase if you lose it. **Uwaga**: NIE będziesz w stanie odzyskać lub zmienić hasła, jeśli je stracisz. @@ -347,11 +345,6 @@ **Zalecane**: token urządzenia i powiadomienia są wysyłane do serwera powiadomień SimpleX Chat, ale nie treść wiadomości, rozmiar lub od kogo jest. No comment provided by engineer. - - **Scan QR code**: to connect to your contact in person or via video call. - **Skanuj kod QR**: aby połączyć się z kontaktem osobiście lub za pomocą połączenia wideo. - No comment provided by engineer. - **Warning**: Instant push notifications require passphrase saved in Keychain. **Uwaga**: Natychmiastowe powiadomienia push wymagają hasła zapisanego w Keychain. @@ -453,11 +446,6 @@ 1 tydzień time interval - - 1-time link - 1-razowy link - No comment provided by engineer. - 5 minutes 5 minut @@ -573,6 +561,10 @@ Dodaj adres do swojego profilu, aby Twoje kontakty mogły go udostępnić innym osobom. Aktualizacja profilu zostanie wysłana do Twoich kontaktów. No comment provided by engineer. + + Add contact + No comment provided by engineer. + Add preset servers Dodaj gotowe serwery @@ -978,6 +970,10 @@ Połączenia No comment provided by engineer. + + Camera not available + No comment provided by engineer. + Can't delete user profile! Nie można usunąć profilu użytkownika! @@ -1242,11 +1238,6 @@ To jest twój jednorazowy link! Połącz się przez link No comment provided by engineer. - - Connect via link / QR code - Połącz się przez link / kod QR - No comment provided by engineer. - Connect via one-time link Połącz przez jednorazowy link @@ -1422,11 +1413,6 @@ To jest twój jednorazowy link! Utwórz nowy profil w [aplikacji desktopowej](https://simplex.chat/downloads/). 💻 No comment provided by engineer. - - Create one-time invitation link - Utwórz jednorazowy link do zaproszenia - No comment provided by engineer. - Create profile Utwórz profil @@ -1452,6 +1438,10 @@ To jest twój jednorazowy link! Utworzony w dniu %@ No comment provided by engineer. + + Creating link… + No comment provided by engineer. + Current Passcode Aktualny Pin @@ -2002,6 +1992,10 @@ To nie może być cofnięte! Czy włączyć automatyczne usuwanie wiadomości? No comment provided by engineer. + + Enable camera access + No comment provided by engineer. + Enable for all Włącz dla wszystkich @@ -2345,6 +2339,10 @@ To nie może być cofnięte! Błąd zapisu hasła użytkownika No comment provided by engineer. + + Error scanning code: %@ + No comment provided by engineer. + Error sending email Błąd wysyłania e-mail @@ -2820,11 +2818,6 @@ To nie może być cofnięte! Jeśli nie możesz spotkać się osobiście, pokaż kod QR w rozmowie wideo lub udostępnij link. No comment provided by engineer. - - If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link. - Jeśli nie możesz spotkać się osobiście, możesz **zeskanować kod QR w rozmowie wideo** lub Twój kontakt może udostępnić link z zaproszeniem. - No comment provided by engineer. - If you enter this passcode when opening the app, all app data will be irreversibly removed! Jeśli wprowadzisz ten pin podczas otwierania aplikacji, wszystkie dane aplikacji zostaną nieodwracalnie usunięte! @@ -2982,11 +2975,19 @@ To nie może być cofnięte! Interfejs No comment provided by engineer. + + Invalid QR code + No comment provided by engineer. + Invalid connection link Nieprawidłowy link połączenia No comment provided by engineer. + + Invalid link + No comment provided by engineer. + Invalid name! Nieprawidłowa nazwa! @@ -3114,11 +3115,19 @@ To jest twój link do grupy %@! Dołączanie do grupy No comment provided by engineer. + + Keep + No comment provided by engineer. + Keep the app open to use it from desktop Zostaw aplikację otwartą i używaj ją z komputera No comment provided by engineer. + + Keep unused invitation? + No comment provided by engineer. + Keep your connections Zachowaj swoje połączenia @@ -3449,6 +3458,10 @@ To jest twój link do grupy %@! Nowy Pin No comment provided by engineer. + + New chat + No comment provided by engineer. + New contact request Nowa prośba o kontakt @@ -3573,6 +3586,10 @@ To jest twój link do grupy %@! - wyłączyć członków (rola "obserwatora") No comment provided by engineer. + + OK + No comment provided by engineer. + Off Wyłączony @@ -3722,6 +3739,14 @@ To jest twój link do grupy %@! Opening app… No comment provided by engineer. + + Or scan QR code + No comment provided by engineer. + + + Or show this code + No comment provided by engineer. + PING count Liczba PINGÓW @@ -3762,11 +3787,6 @@ To jest twój link do grupy %@! Hasło do wyświetlenia No comment provided by engineer. - - Paste - Wklej - No comment provided by engineer. - Paste desktop address Wklej adres komputera @@ -3777,16 +3797,10 @@ To jest twój link do grupy %@! Wklej obraz No comment provided by engineer. - - Paste received link - Wklej otrzymany link + + Paste the link you received No comment provided by engineer. - - Paste the link you received to connect with your contact. - Wklej otrzymany link w pole poniżej, aby połączyć się z kontaktem. - placeholder - People can connect to you only via the links you share. Ludzie mogą się z Tobą połączyć tylko poprzez linki, które udostępniasz. @@ -4032,6 +4046,10 @@ Error: %@ Przeczytaj więcej w [Podręczniku Użytkownika](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). Przeczytaj więcej w [Podręczniku Użytkownika](https://simplex.chat/docs/guide/readme.html#connect-to-friends). @@ -4242,6 +4260,10 @@ Error: %@ Błąd przywracania bazy danych No comment provided by engineer. + + Retry + No comment provided by engineer. + Reveal Ujawnij @@ -4397,6 +4419,10 @@ Error: %@ Szukaj No comment provided by engineer. + + Search or paste SimpleX link + No comment provided by engineer. + Secure queue Bezpieczna kolejka @@ -4672,9 +4698,8 @@ Error: %@ Udostępnij link No comment provided by engineer. - - Share one-time invitation link - Jednorazowy link zaproszenia + + Share this 1-time invite link No comment provided by engineer. @@ -4797,11 +4822,6 @@ Error: %@ Ktoś notification title - - Start a new chat - Rozpocznij nowy czat - No comment provided by engineer. - Start chat Rozpocznij czat @@ -4936,6 +4956,14 @@ Error: %@ Dotnij, aby dołączyć w trybie incognito No comment provided by engineer. + + Tap to paste link + No comment provided by engineer. + + + Tap to scan + No comment provided by engineer. + Tap to start a new chat Dotknij, aby rozpocząć nowy czat @@ -4998,6 +5026,10 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Próba zmiany hasła bazy danych nie została zakończona. No comment provided by engineer. + + The code you scanned is not a SimpleX link QR code. + No comment provided by engineer. + The connection you accepted will be cancelled! Zaakceptowane przez Ciebie połączenie zostanie anulowane! @@ -5063,6 +5095,10 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Serwery dla nowych połączeń bieżącego profilu czatu **%@**. No comment provided by engineer. + + The text you pasted is not a SimpleX link. + No comment provided by engineer. + Theme Motyw @@ -5683,11 +5719,6 @@ Powtórzyć prośbę dołączenia? Możesz przyjmować połączenia z ekranu blokady, bez uwierzytelniania urządzenia i aplikacji. No comment provided by engineer. - - You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button. - Możesz też połączyć się klikając w link. Jeśli otworzy się on w przeglądarce, kliknij przycisk **Otwórz w aplikacji mobilnej**. - No comment provided by engineer. - You can create it later Możesz go utworzyć później @@ -5752,6 +5783,10 @@ Powtórzyć prośbę dołączenia? Możesz używać markdown do formatowania wiadomości: No comment provided by engineer. + + You can view invitation link again in connection details. + No comment provided by engineer. + You can't send messages! Nie możesz wysyłać wiadomości! 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 348b42c948..2129456374 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -312,14 +312,17 @@ ) No comment provided by engineer. + + **Add contact**: to create a new invitation link, or connect via a link you received. + No comment provided by engineer. + **Add new contact**: to create your one-time QR Code or link for your contact. **Добавить новый контакт**: чтобы создать одноразовый QR код или ссылку для Вашего контакта. No comment provided by engineer. - - **Create link / QR code** for your contact to use. - **Создать ссылку / QR код** для Вашего контакта. + + **Create group**: to create a new group. No comment provided by engineer. @@ -332,11 +335,6 @@ **Самый конфиденциальный**: не использовать сервер уведомлений SimpleX Chat, проверять сообщения периодически в фоновом режиме (зависит от того насколько часто Вы используете приложение). No comment provided by engineer. - - **Paste received link** or open it in the browser and tap **Open in mobile app**. - **Вставить полученную ссылку**, или откройте её в браузере и нажмите **Open in mobile app**. - No comment provided by engineer. - **Please note**: you will NOT be able to recover or change passphrase if you lose it. **Внимание**: Вы не сможете восстановить или поменять пароль, если Вы его потеряете. @@ -347,11 +345,6 @@ **Рекомендовано**: токен устройства и уведомления отправляются на сервер SimpleX Chat, но сервер не получает сами сообщения, их размер или от кого они. No comment provided by engineer. - - **Scan QR code**: to connect to your contact in person or via video call. - **Сканировать QR код**: соединиться с Вашим контактом при встрече или во время видеозвонка. - No comment provided by engineer. - **Warning**: Instant push notifications require passphrase saved in Keychain. **Внимание**: для работы мгновенных уведомлений пароль должен быть сохранен в Keychain. @@ -453,11 +446,6 @@ 1 неделю time interval - - 1-time link - Одноразовая ссылка - No comment provided by engineer. - 5 minutes 5 минут @@ -573,6 +561,10 @@ Добавьте адрес в свой профиль, чтобы Ваши контакты могли поделиться им. Профиль будет отправлен Вашим контактам. No comment provided by engineer. + + Add contact + No comment provided by engineer. + Add preset servers Добавить серверы по умолчанию @@ -978,6 +970,10 @@ Звонки No comment provided by engineer. + + Camera not available + No comment provided by engineer. + Can't delete user profile! Нельзя удалить профиль пользователя! @@ -1242,11 +1238,6 @@ This is your own one-time link! Соединиться через ссылку No comment provided by engineer. - - Connect via link / QR code - Соединиться через ссылку / QR код - No comment provided by engineer. - Connect via one-time link Соединиться через одноразовую ссылку @@ -1422,11 +1413,6 @@ This is your own one-time link! Создайте новый профиль в [приложении для компьютера](https://simplex.chat/downloads/). 💻 No comment provided by engineer. - - Create one-time invitation link - Создать ссылку-приглашение - No comment provided by engineer. - Create profile Создать профиль @@ -1452,6 +1438,10 @@ This is your own one-time link! Дата создания %@ No comment provided by engineer. + + Creating link… + No comment provided by engineer. + Current Passcode Текущий Код @@ -2002,6 +1992,10 @@ This cannot be undone! Включить автоматическое удаление сообщений? No comment provided by engineer. + + Enable camera access + No comment provided by engineer. + Enable for all Включить для всех @@ -2345,6 +2339,10 @@ This cannot be undone! Ошибка при сохранении пароля пользователя No comment provided by engineer. + + Error scanning code: %@ + No comment provided by engineer. + Error sending email Ошибка отправки email @@ -2820,11 +2818,6 @@ This cannot be undone! Если Вы не можете встретиться лично, покажите QR-код во время видеозвонка или поделитесь ссылкой. No comment provided by engineer. - - If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link. - Если Вы не можете встретиться лично, Вы можете **сосканировать QR код во время видеозвонка**, или Ваш контакт может отправить Вам ссылку. - No comment provided by engineer. - If you enter this passcode when opening the app, all app data will be irreversibly removed! Если Вы введете этот код при открытии приложения, все данные приложения будут безвозвратно удалены! @@ -2982,11 +2975,19 @@ This cannot be undone! Интерфейс No comment provided by engineer. + + Invalid QR code + No comment provided by engineer. + Invalid connection link Ошибка в ссылке контакта No comment provided by engineer. + + Invalid link + No comment provided by engineer. + Invalid name! Неверное имя! @@ -3114,11 +3115,19 @@ This is your link for group %@! Вступление в группу No comment provided by engineer. + + Keep + No comment provided by engineer. + Keep the app open to use it from desktop Оставьте приложение открытым, чтобы использовать его с компьютера No comment provided by engineer. + + Keep unused invitation? + No comment provided by engineer. + Keep your connections Сохраните Ваши соединения @@ -3449,6 +3458,10 @@ This is your link for group %@! Новый Код No comment provided by engineer. + + New chat + No comment provided by engineer. + New contact request Новый запрос на соединение @@ -3573,6 +3586,10 @@ This is your link for group %@! - приостанавливать членов (роль "наблюдатель") No comment provided by engineer. + + OK + No comment provided by engineer. + Off Выключено @@ -3722,6 +3739,14 @@ This is your link for group %@! Opening app… No comment provided by engineer. + + Or scan QR code + No comment provided by engineer. + + + Or show this code + No comment provided by engineer. + PING count Количество PING @@ -3762,11 +3787,6 @@ This is your link for group %@! Пароль чтобы раскрыть No comment provided by engineer. - - Paste - Вставить - No comment provided by engineer. - Paste desktop address Вставить адрес компьютера @@ -3777,16 +3797,10 @@ This is your link for group %@! Вставить изображение No comment provided by engineer. - - Paste received link - Вставить полученную ссылку + + Paste the link you received No comment provided by engineer. - - Paste the link you received to connect with your contact. - Чтобы соединиться, вставьте ссылку, полученную от Вашего контакта. - placeholder - People can connect to you only via the links you share. С Вами можно соединиться только через созданные Вами ссылки. @@ -4032,6 +4046,10 @@ Error: %@ Узнать больше в [Руководстве пользователя](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). Узнать больше в [Руководстве пользователя](https://simplex.chat/docs/guide/readme.html#connect-to-friends). @@ -4242,6 +4260,10 @@ Error: %@ Ошибка при восстановлении базы данных No comment provided by engineer. + + Retry + No comment provided by engineer. + Reveal Показать @@ -4397,6 +4419,10 @@ Error: %@ Поиск No comment provided by engineer. + + Search or paste SimpleX link + No comment provided by engineer. + Secure queue Защита очереди @@ -4672,9 +4698,8 @@ Error: %@ Поделиться ссылкой No comment provided by engineer. - - Share one-time invitation link - Поделиться ссылкой-приглашением + + Share this 1-time invite link No comment provided by engineer. @@ -4797,11 +4822,6 @@ Error: %@ Контакт notification title - - Start a new chat - Начать новый разговор - No comment provided by engineer. - Start chat Запустить чат @@ -4936,6 +4956,14 @@ Error: %@ Нажмите, чтобы вступить инкогнито No comment provided by engineer. + + Tap to paste link + No comment provided by engineer. + + + Tap to scan + No comment provided by engineer. + Tap to start a new chat Нажмите, чтобы начать чат @@ -4998,6 +5026,10 @@ It can happen because of some bug or when the connection is compromised.Попытка поменять пароль базы данных не была завершена. No comment provided by engineer. + + The code you scanned is not a SimpleX link QR code. + No comment provided by engineer. + The connection you accepted will be cancelled! Подтвержденное соединение будет отменено! @@ -5063,6 +5095,10 @@ It can happen because of some bug or when the connection is compromised.Серверы для новых соединений Вашего текущего профиля чата **%@**. No comment provided by engineer. + + The text you pasted is not a SimpleX link. + No comment provided by engineer. + Theme Тема @@ -5683,11 +5719,6 @@ Repeat join request? Вы можете принимать звонки на экране блокировки, без аутентификации. No comment provided by engineer. - - You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button. - Вы также можете соединиться, открыв ссылку. Если ссылка откроется в браузере, нажмите кнопку **Open in mobile app**. - No comment provided by engineer. - You can create it later Вы можете создать его позже @@ -5752,6 +5783,10 @@ Repeat join request? Вы можете форматировать сообщения: No comment provided by engineer. + + You can view invitation link again in connection details. + No comment provided by engineer. + You can't send messages! Вы не можете отправлять сообщения! diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index c062999aa5..b4520553c5 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -297,14 +297,17 @@ ) No comment provided by engineer. + + **Add contact**: to create a new invitation link, or connect via a link you received. + No comment provided by engineer. + **Add new contact**: to create your one-time QR Code or link for your contact. **เพิ่มผู้ติดต่อใหม่**: เพื่อสร้างคิวอาร์โค้ดแบบใช้ครั้งเดียวหรือลิงก์สำหรับผู้ติดต่อของคุณ No comment provided by engineer. - - **Create link / QR code** for your contact to use. - **สร้างลิงค์ / คิวอาร์โค้ด** เพื่อให้ผู้ติดต่อของคุณใช้ + + **Create group**: to create a new group. No comment provided by engineer. @@ -317,11 +320,6 @@ **ส่วนตัวที่สุด**: ไม่ใช้เซิร์ฟเวอร์การแจ้งเตือนของ SimpleX Chat ตรวจสอบข้อความเป็นระยะในพื้นหลัง (ขึ้นอยู่กับความถี่ที่คุณใช้แอป) No comment provided by engineer. - - **Paste received link** or open it in the browser and tap **Open in mobile app**. - **แปะลิงก์ที่ได้รับ** หรือเปิดในเบราว์เซอร์แล้วแตะ **เปิดในแอปมือถือ** - No comment provided by engineer. - **Please note**: you will NOT be able to recover or change passphrase if you lose it. **โปรดทราบ**: คุณจะไม่สามารถกู้คืนหรือเปลี่ยนรหัสผ่านได้หากคุณทำรหัสผ่านหาย @@ -332,11 +330,6 @@ **แนะนำ**: โทเค็นอุปกรณ์และการแจ้งเตือนจะถูกส่งไปยังเซิร์ฟเวอร์การแจ้งเตือนของ SimpleX Chat แต่ไม่ใช่เนื้อหาข้อความ ขนาด หรือผู้ที่ส่ง No comment provided by engineer. - - **Scan QR code**: to connect to your contact in person or via video call. - **สแกนคิวอาร์โค้ด**: เพื่อเชื่อมต่อกับผู้ติดต่อของคุณด้วยตนเองหรือผ่านการสนทนาทางวิดีโอ - No comment provided by engineer. - **Warning**: Instant push notifications require passphrase saved in Keychain. **คำเตือน**: การแจ้งเตือนแบบพุชทันทีจำเป็นต้องบันทึกรหัสผ่านไว้ใน Keychain @@ -431,11 +424,6 @@ 1 สัปดาห์ time interval - - 1-time link - ลิงก์สำหรับใช้ 1 ครั้ง - No comment provided by engineer. - 5 minutes 5 นาที @@ -549,6 +537,10 @@ เพิ่มที่อยู่ลงในโปรไฟล์ของคุณ เพื่อให้ผู้ติดต่อของคุณสามารถแชร์กับผู้อื่นได้ การอัปเดตโปรไฟล์จะถูกส่งไปยังผู้ติดต่อของคุณ No comment provided by engineer. + + Add contact + No comment provided by engineer. + Add preset servers เพิ่มเซิร์ฟเวอร์ที่ตั้งไว้ล่วงหน้า @@ -943,6 +935,10 @@ โทร No comment provided by engineer. + + Camera not available + No comment provided by engineer. + Can't delete user profile! ไม่สามารถลบโปรไฟล์ผู้ใช้ได้! @@ -1198,11 +1194,6 @@ This is your own one-time link! เชื่อมต่อผ่านลิงก์ No comment provided by engineer. - - Connect via link / QR code - เชื่อมต่อผ่านลิงค์ / คิวอาร์โค้ด - No comment provided by engineer. - Connect via one-time link No comment provided by engineer. @@ -1368,11 +1359,6 @@ This is your own one-time link! Create new profile in [desktop app](https://simplex.chat/downloads/). 💻 No comment provided by engineer. - - Create one-time invitation link - สร้างลิงก์เชิญแบบใช้ครั้งเดียว - No comment provided by engineer. - Create profile No comment provided by engineer. @@ -1397,6 +1383,10 @@ This is your own one-time link! สร้างเมื่อ %@ No comment provided by engineer. + + Creating link… + No comment provided by engineer. + Current Passcode รหัสผ่านปัจจุบัน @@ -1936,6 +1926,10 @@ This cannot be undone! เปิดใช้งานการลบข้อความอัตโนมัติ? No comment provided by engineer. + + Enable camera access + No comment provided by engineer. + Enable for all เปิดใช้งานสําหรับทุกคน @@ -2270,6 +2264,10 @@ This cannot be undone! เกิดข้อผิดพลาดในการบันทึกรหัสผ่านผู้ใช้ No comment provided by engineer. + + Error scanning code: %@ + No comment provided by engineer. + Error sending email เกิดข้อผิดพลาดในการส่งอีเมล @@ -2738,11 +2736,6 @@ This cannot be undone! หากคุณไม่สามารถพบกันในชีวิตจริงได้ ให้แสดงคิวอาร์โค้ดในวิดีโอคอล หรือแชร์ลิงก์ No comment provided by engineer. - - If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link. - หากคุณไม่สามารถพบปะด้วยตนเอง คุณสามารถ **สแกนคิวอาร์โค้ดผ่านการสนทนาทางวิดีโอ** หรือผู้ติดต่อของคุณสามารถแชร์ลิงก์เชิญได้ - No comment provided by engineer. - If you enter this passcode when opening the app, all app data will be irreversibly removed! หากคุณใส่รหัสผ่านนี้เมื่อเปิดแอป ข้อมูลแอปทั้งหมดจะถูกลบอย่างถาวร! @@ -2897,11 +2890,19 @@ This cannot be undone! อินเตอร์เฟซ No comment provided by engineer. + + Invalid QR code + No comment provided by engineer. + Invalid connection link ลิงค์เชื่อมต่อไม่ถูกต้อง No comment provided by engineer. + + Invalid link + No comment provided by engineer. + Invalid name! No comment provided by engineer. @@ -3023,10 +3024,18 @@ This is your link for group %@! กำลังจะเข้าร่วมกลุ่ม No comment provided by engineer. + + Keep + No comment provided by engineer. + Keep the app open to use it from desktop No comment provided by engineer. + + Keep unused invitation? + No comment provided by engineer. + Keep your connections รักษาการเชื่อมต่อของคุณ @@ -3352,6 +3361,10 @@ This is your link for group %@! รหัสผ่านใหม่ No comment provided by engineer. + + New chat + No comment provided by engineer. + New contact request คำขอติดต่อใหม่ @@ -3473,6 +3486,10 @@ This is your link for group %@! - ปิดการใช้งานสมาชิก (บทบาท "ผู้สังเกตการณ์") No comment provided by engineer. + + OK + No comment provided by engineer. + Off ปิด @@ -3620,6 +3637,14 @@ This is your link for group %@! Opening app… No comment provided by engineer. + + Or scan QR code + No comment provided by engineer. + + + Or show this code + No comment provided by engineer. + PING count จํานวน PING @@ -3660,11 +3685,6 @@ This is your link for group %@! รหัสผ่านที่จะแสดง No comment provided by engineer. - - Paste - แปะ - No comment provided by engineer. - Paste desktop address No comment provided by engineer. @@ -3674,15 +3694,10 @@ This is your link for group %@! แปะภาพ No comment provided by engineer. - - Paste received link - แปะลิงก์ที่ได้รับ + + Paste the link you received No comment provided by engineer. - - Paste the link you received to connect with your contact. - placeholder - People can connect to you only via the links you share. ผู้คนสามารถเชื่อมต่อกับคุณผ่านลิงก์ที่คุณแบ่งปันเท่านั้น @@ -3926,6 +3941,10 @@ Error: %@ อ่านเพิ่มเติมใน[คู่มือผู้ใช้](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address) No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). อ่านเพิ่มเติมใน[คู่มือผู้ใช้](https://simplex.chat/docs/guide/readme.html#connect-to-friends) @@ -4132,6 +4151,10 @@ Error: %@ กู้คืนข้อผิดพลาดของฐานข้อมูล No comment provided by engineer. + + Retry + No comment provided by engineer. + Reveal เปิดเผย @@ -4286,6 +4309,10 @@ Error: %@ ค้นหา No comment provided by engineer. + + Search or paste SimpleX link + No comment provided by engineer. + Secure queue คิวที่ปลอดภัย @@ -4557,9 +4584,8 @@ Error: %@ แชร์ลิงก์ No comment provided by engineer. - - Share one-time invitation link - แชร์ลิงก์เชิญแบบใช้ครั้งเดียว + + Share this 1-time invite link No comment provided by engineer. @@ -4679,11 +4705,6 @@ Error: %@ ใครบางคน notification title - - Start a new chat - เริ่มแชทใหม่ - No comment provided by engineer. - Start chat เริ่มแชท @@ -4817,6 +4838,14 @@ Error: %@ แตะเพื่อเข้าร่วมโหมดไม่ระบุตัวตน No comment provided by engineer. + + Tap to paste link + No comment provided by engineer. + + + Tap to scan + No comment provided by engineer. + Tap to start a new chat แตะเพื่อเริ่มแชทใหม่ @@ -4880,6 +4909,10 @@ It can happen because of some bug or when the connection is compromised.ความพยายามในการเปลี่ยนรหัสผ่านของฐานข้อมูลไม่เสร็จสมบูรณ์ No comment provided by engineer. + + The code you scanned is not a SimpleX link QR code. + No comment provided by engineer. + The connection you accepted will be cancelled! การเชื่อมต่อที่คุณยอมรับจะถูกยกเลิก! @@ -4945,6 +4978,10 @@ It can happen because of some bug or when the connection is compromised.เซิร์ฟเวอร์สำหรับการเชื่อมต่อใหม่ของโปรไฟล์การแชทปัจจุบันของคุณ **%@** No comment provided by engineer. + + The text you pasted is not a SimpleX link. + No comment provided by engineer. + Theme ธีม @@ -5537,11 +5574,6 @@ Repeat join request? คุณสามารถรับสายจากหน้าจอล็อกโดยไม่ต้องมีการตรวจสอบสิทธิ์อุปกรณ์และแอป No comment provided by engineer. - - You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button. - คุณสามารถเชื่อมต่อได้โดยคลิกที่ลิงค์ หากเปิดในเบราว์เซอร์ ให้คลิกปุ่ม **เปิดในแอปมือถือ** - No comment provided by engineer. - You can create it later คุณสามารถสร้างได้ในภายหลัง @@ -5606,6 +5638,10 @@ Repeat join request? คุณสามารถใช้มาร์กดาวน์เพื่อจัดรูปแบบข้อความ: No comment provided by engineer. + + You can view invitation link again in connection details. + No comment provided by engineer. + You can't send messages! คุณไม่สามารถส่งข้อความได้! diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index 2bb43ba736..2d219440f7 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -312,14 +312,17 @@ ) No comment provided by engineer. + + **Add contact**: to create a new invitation link, or connect via a link you received. + No comment provided by engineer. + **Add new contact**: to create your one-time QR Code or link for your contact. **Додати новий контакт**: щоб створити одноразовий QR-код або посилання для свого контакту. No comment provided by engineer. - - **Create link / QR code** for your contact to use. - **Створіть посилання / QR-код** для використання вашим контактом. + + **Create group**: to create a new group. No comment provided by engineer. @@ -332,11 +335,6 @@ **Найбільш приватний**: не використовуйте сервер сповіщень SimpleX Chat, періодично перевіряйте повідомлення у фоновому режимі (залежить від того, як часто ви користуєтесь додатком). No comment provided by engineer. - - **Paste received link** or open it in the browser and tap **Open in mobile app**. - **Вставте отримане посилання** або відкрийте його в браузері і натисніть **Відкрити в мобільному додатку**. - No comment provided by engineer. - **Please note**: you will NOT be able to recover or change passphrase if you lose it. **Зверніть увагу: ви НЕ зможете відновити або змінити пароль, якщо втратите його. @@ -347,11 +345,6 @@ **Рекомендується**: токен пристрою та сповіщення надсилаються на сервер сповіщень SimpleX Chat, але не вміст повідомлення, його розмір або від кого воно надійшло. No comment provided by engineer. - - **Scan QR code**: to connect to your contact in person or via video call. - **Відскануйте QR-код**: щоб з'єднатися з вашим контактом особисто або за допомогою відеодзвінка. - No comment provided by engineer. - **Warning**: Instant push notifications require passphrase saved in Keychain. **Попередження**: Для отримання миттєвих пуш-сповіщень потрібна парольна фраза, збережена у брелоку. @@ -453,11 +446,6 @@ 1 тиждень time interval - - 1-time link - 1-разове посилання - No comment provided by engineer. - 5 minutes 5 хвилин @@ -573,6 +561,10 @@ Додайте адресу до свого профілю, щоб ваші контакти могли поділитися нею з іншими людьми. Повідомлення про оновлення профілю буде надіслано вашим контактам. No comment provided by engineer. + + Add contact + No comment provided by engineer. + Add preset servers Додавання попередньо встановлених серверів @@ -978,6 +970,10 @@ Дзвінки No comment provided by engineer. + + Camera not available + No comment provided by engineer. + Can't delete user profile! Не можу видалити профіль користувача! @@ -1242,11 +1238,6 @@ This is your own one-time link! Підключіться за посиланням No comment provided by engineer. - - Connect via link / QR code - Підключитися за посиланням / QR-кодом - No comment provided by engineer. - Connect via one-time link Під'єднатися за одноразовим посиланням @@ -1415,11 +1406,6 @@ This is your own one-time link! Create new profile in [desktop app](https://simplex.chat/downloads/). 💻 No comment provided by engineer. - - Create one-time invitation link - Створіть одноразове посилання-запрошення - No comment provided by engineer. - Create profile No comment provided by engineer. @@ -1444,6 +1430,10 @@ This is your own one-time link! Створено %@ No comment provided by engineer. + + Creating link… + No comment provided by engineer. + Current Passcode Поточний пароль @@ -1984,6 +1974,10 @@ This cannot be undone! Увімкнути автоматичне видалення повідомлень? No comment provided by engineer. + + Enable camera access + No comment provided by engineer. + Enable for all Увімкнути для всіх @@ -2318,6 +2312,10 @@ This cannot be undone! Помилка збереження пароля користувача No comment provided by engineer. + + Error scanning code: %@ + No comment provided by engineer. + Error sending email Помилка надсилання електронного листа @@ -2786,11 +2784,6 @@ This cannot be undone! Якщо ви не можете зустрітися особисто, покажіть QR-код у відеодзвінку або поділіться посиланням. No comment provided by engineer. - - If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link. - Якщо ви не можете зустрітися особисто, ви можете **сканувати QR-код у відеодзвінку**, або ваш контакт може поділитися посиланням на запрошення. - No comment provided by engineer. - If you enter this passcode when opening the app, all app data will be irreversibly removed! Якщо ви введете цей пароль при відкритті програми, всі дані програми будуть безповоротно видалені! @@ -2946,11 +2939,19 @@ This cannot be undone! Інтерфейс No comment provided by engineer. + + Invalid QR code + No comment provided by engineer. + Invalid connection link Неправильне посилання для підключення No comment provided by engineer. + + Invalid link + No comment provided by engineer. + Invalid name! No comment provided by engineer. @@ -3073,10 +3074,18 @@ This is your link for group %@! Приєднання до групи No comment provided by engineer. + + Keep + No comment provided by engineer. + Keep the app open to use it from desktop No comment provided by engineer. + + Keep unused invitation? + No comment provided by engineer. + Keep your connections Зберігайте свої зв'язки @@ -3403,6 +3412,10 @@ This is your link for group %@! Новий пароль No comment provided by engineer. + + New chat + No comment provided by engineer. + New contact request Новий запит на контакт @@ -3525,6 +3538,10 @@ This is your link for group %@! - відключати користувачів (роль "спостерігач") No comment provided by engineer. + + OK + No comment provided by engineer. + Off Вимкнено @@ -3672,6 +3689,14 @@ This is your link for group %@! Opening app… No comment provided by engineer. + + Or scan QR code + No comment provided by engineer. + + + Or show this code + No comment provided by engineer. + PING count Кількість PING @@ -3712,11 +3737,6 @@ This is your link for group %@! Показати пароль No comment provided by engineer. - - Paste - Вставити - No comment provided by engineer. - Paste desktop address No comment provided by engineer. @@ -3726,16 +3746,10 @@ This is your link for group %@! Вставити зображення No comment provided by engineer. - - Paste received link - Вставте отримане посилання + + Paste the link you received No comment provided by engineer. - - Paste the link you received to connect with your contact. - Вставте отримане посилання для зв'язку з вашим контактом. - placeholder - People can connect to you only via the links you share. Люди можуть зв'язатися з вами лише за посиланнями, якими ви ділитеся. @@ -3979,6 +3993,10 @@ Error: %@ Читайте більше в [Посібнику користувача](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). Читайте більше в [Посібнику користувача](https://simplex.chat/docs/guide/readme.html#connect-to-friends). @@ -4187,6 +4205,10 @@ Error: %@ Відновлення помилки бази даних No comment provided by engineer. + + Retry + No comment provided by engineer. + Reveal Показувати @@ -4341,6 +4363,10 @@ Error: %@ Пошук No comment provided by engineer. + + Search or paste SimpleX link + No comment provided by engineer. + Secure queue Безпечна черга @@ -4614,9 +4640,8 @@ Error: %@ Поділіться посиланням No comment provided by engineer. - - Share one-time invitation link - Поділіться посиланням на одноразове запрошення + + Share this 1-time invite link No comment provided by engineer. @@ -4738,11 +4763,6 @@ Error: %@ Хтось notification title - - Start a new chat - Почніть новий чат - No comment provided by engineer. - Start chat Почати чат @@ -4876,6 +4896,14 @@ Error: %@ Натисніть, щоб приєднатися інкогніто No comment provided by engineer. + + Tap to paste link + No comment provided by engineer. + + + Tap to scan + No comment provided by engineer. + Tap to start a new chat Натисніть, щоб почати новий чат @@ -4938,6 +4966,10 @@ It can happen because of some bug or when the connection is compromised.Спроба змінити пароль до бази даних не була завершена. No comment provided by engineer. + + The code you scanned is not a SimpleX link QR code. + No comment provided by engineer. + The connection you accepted will be cancelled! Прийняте вами з'єднання буде скасовано! @@ -5003,6 +5035,10 @@ It can happen because of some bug or when the connection is compromised.Сервери для нових підключень вашого поточного профілю чату **%@**. No comment provided by engineer. + + The text you pasted is not a SimpleX link. + No comment provided by engineer. + Theme Тема @@ -5599,11 +5635,6 @@ Repeat join request? Ви можете приймати дзвінки з екрана блокування без автентифікації пристрою та програми. No comment provided by engineer. - - You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button. - Ви також можете підключитися за посиланням. Якщо воно відкриється в браузері, натисніть кнопку **Відкрити в мобільному додатку**. - No comment provided by engineer. - You can create it later Ви можете створити його пізніше @@ -5668,6 +5699,10 @@ Repeat join request? Ви можете використовувати розмітку для форматування повідомлень: No comment provided by engineer. + + You can view invitation link again in connection details. + No comment provided by engineer. + You can't send messages! Ви не можете надсилати повідомлення! diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index 6a07b28157..60434b1661 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -303,14 +303,17 @@ ) No comment provided by engineer. + + **Add contact**: to create a new invitation link, or connect via a link you received. + No comment provided by engineer. + **Add new contact**: to create your one-time QR Code or link for your contact. **添加新联系人**:为您的联系人创建一次性二维码或者链接。 No comment provided by engineer. - - **Create link / QR code** for your contact to use. - **创建链接 / 二维码** 给您的联系人使用。 + + **Create group**: to create a new group. No comment provided by engineer. @@ -323,11 +326,6 @@ **最私密**:不使用 SimpleX Chat 通知服务器,在后台定期检查消息(取决于您多经常使用应用程序)。 No comment provided by engineer. - - **Paste received link** or open it in the browser and tap **Open in mobile app**. - **粘贴收到的链接**或者在浏览器里打开并且点击**在移动应用程序里打开**。 - No comment provided by engineer. - **Please note**: you will NOT be able to recover or change passphrase if you lose it. **请注意**:如果您丢失密码,您将无法恢复或者更改密码。 @@ -338,11 +336,6 @@ **推荐**:设备令牌和通知会发送至 SimpleX Chat 通知服务器,但是消息内容、大小或者发送人不会。 No comment provided by engineer. - - **Scan QR code**: to connect to your contact in person or via video call. - **扫描二维码**:见面或者通过视频通话来连接您的联系人。 - No comment provided by engineer. - **Warning**: Instant push notifications require passphrase saved in Keychain. **警告**:及时推送通知需要保存在钥匙串的密码。 @@ -440,11 +433,6 @@ 1周 time interval - - 1-time link - 一次性链接 - No comment provided by engineer. - 5 minutes 5分钟 @@ -560,6 +548,10 @@ 将地址添加到您的个人资料,以便您的联系人可以与其他人共享。个人资料更新将发送给您的联系人。 No comment provided by engineer. + + Add contact + No comment provided by engineer. + Add preset servers 添加预设服务器 @@ -956,6 +948,10 @@ 通话 No comment provided by engineer. + + Camera not available + No comment provided by engineer. + Can't delete user profile! 无法删除用户个人资料! @@ -1212,11 +1208,6 @@ This is your own one-time link! 通过链接连接 No comment provided by engineer. - - Connect via link / QR code - 通过群组链接/二维码连接 - No comment provided by engineer. - Connect via one-time link 通过一次性链接连接 @@ -1384,11 +1375,6 @@ This is your own one-time link! 在[桌面应用程序](https://simplex.chat/downloads/)中创建新的个人资料。 💻 No comment provided by engineer. - - Create one-time invitation link - 创建一次性邀请链接 - No comment provided by engineer. - Create profile No comment provided by engineer. @@ -1413,6 +1399,10 @@ This is your own one-time link! 创建于 %@ No comment provided by engineer. + + Creating link… + No comment provided by engineer. + Current Passcode 当前密码 @@ -1954,6 +1944,10 @@ This cannot be undone! 启用自动删除消息? No comment provided by engineer. + + Enable camera access + No comment provided by engineer. + Enable for all 全部启用 @@ -2292,6 +2286,10 @@ This cannot be undone! 保存用户密码时出错 No comment provided by engineer. + + Error scanning code: %@ + No comment provided by engineer. + Error sending email 发送电邮错误 @@ -2761,11 +2759,6 @@ This cannot be undone! 如果您不能亲自见面,可以在视频通话中展示二维码,或分享链接。 No comment provided by engineer. - - If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link. - 如果您不能亲自见面,您可以**扫描视频通话中的二维码**,或者您的联系人可以分享邀请链接。 - No comment provided by engineer. - If you enter this passcode when opening the app, all app data will be irreversibly removed! 如果您在打开应用时输入该密码,所有应用程序数据将被不可撤回地删除! @@ -2921,11 +2914,19 @@ This cannot be undone! 界面 No comment provided by engineer. + + Invalid QR code + No comment provided by engineer. + Invalid connection link 无效的连接链接 No comment provided by engineer. + + Invalid link + No comment provided by engineer. + Invalid name! No comment provided by engineer. @@ -3048,10 +3049,18 @@ This is your link for group %@! 加入群组中 No comment provided by engineer. + + Keep + No comment provided by engineer. + Keep the app open to use it from desktop No comment provided by engineer. + + Keep unused invitation? + No comment provided by engineer. + Keep your connections 保持连接 @@ -3378,6 +3387,10 @@ This is your link for group %@! 新密码 No comment provided by engineer. + + New chat + No comment provided by engineer. + New contact request 新联系人请求 @@ -3501,6 +3514,10 @@ This is your link for group %@! - 禁用成员(“观察员”角色) No comment provided by engineer. + + OK + No comment provided by engineer. + Off 关闭 @@ -3649,6 +3666,14 @@ This is your link for group %@! Opening app… No comment provided by engineer. + + Or scan QR code + No comment provided by engineer. + + + Or show this code + No comment provided by engineer. + PING count PING 次数 @@ -3689,11 +3714,6 @@ This is your link for group %@! 显示密码 No comment provided by engineer. - - Paste - 粘贴 - No comment provided by engineer. - Paste desktop address No comment provided by engineer. @@ -3703,16 +3723,10 @@ This is your link for group %@! 粘贴图片 No comment provided by engineer. - - Paste received link - 粘贴收到的链接 + + Paste the link you received No comment provided by engineer. - - Paste the link you received to connect with your contact. - 将您收到的链接粘贴到下面的框中以与您的联系人联系。 - placeholder - People can connect to you only via the links you share. 人们只能通过您共享的链接与您建立联系。 @@ -3956,6 +3970,10 @@ Error: %@ 在 [用户指南](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address) 中阅读更多内容。 No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). 在 [用户指南](https://simplex.chat/docs/guide/readme.html#connect-to-friends) 中阅读更多内容。 @@ -4164,6 +4182,10 @@ Error: %@ 恢复数据库错误 No comment provided by engineer. + + Retry + No comment provided by engineer. + Reveal 揭示 @@ -4318,6 +4340,10 @@ Error: %@ 搜索 No comment provided by engineer. + + Search or paste SimpleX link + No comment provided by engineer. + Secure queue 保护队列 @@ -4592,9 +4618,8 @@ Error: %@ 分享链接 No comment provided by engineer. - - Share one-time invitation link - 分享一次性邀请链接 + + Share this 1-time invite link No comment provided by engineer. @@ -4717,11 +4742,6 @@ Error: %@ 某人 notification title - - Start a new chat - 开始新聊天 - No comment provided by engineer. - Start chat 开始聊天 @@ -4855,6 +4875,14 @@ Error: %@ 点击以加入隐身聊天 No comment provided by engineer. + + Tap to paste link + No comment provided by engineer. + + + Tap to scan + No comment provided by engineer. + Tap to start a new chat 点击开始一个新聊天 @@ -4917,6 +4945,10 @@ It can happen because of some bug or when the connection is compromised.更改数据库密码的尝试未完成。 No comment provided by engineer. + + The code you scanned is not a SimpleX link QR code. + No comment provided by engineer. + The connection you accepted will be cancelled! 您接受的连接将被取消! @@ -4982,6 +5014,10 @@ It can happen because of some bug or when the connection is compromised.您当前聊天资料 **%@** 的新连接服务器。 No comment provided by engineer. + + The text you pasted is not a SimpleX link. + No comment provided by engineer. + Theme 主题 @@ -5579,11 +5615,6 @@ Repeat join request? 您可以从锁屏上接听电话,无需设备和应用程序的认证。 No comment provided by engineer. - - You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button. - 您也可以通过点击链接进行连接。如果在浏览器中打开,请点击“在移动应用程序中打开”按钮。 - No comment provided by engineer. - You can create it later 您可以以后创建它 @@ -5648,6 +5679,10 @@ Repeat join request? 您可以使用 markdown 来编排消息格式: No comment provided by engineer. + + You can view invitation link again in connection details. + No comment provided by engineer. + You can't send messages! 您无法发送消息! diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 801116bf80..a6b5eb49e9 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -16,7 +16,6 @@ 18415C6C56DBCEC2CBBD2F11 /* WebRTCClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18415323A4082FC92887F906 /* WebRTCClient.swift */; }; 18415F9A2D551F9757DA4654 /* CIVideoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18415FD2E36F13F596A45BB4 /* CIVideoView.swift */; }; 18415FEFE153C5920BFB7828 /* GroupWelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1841516F0CE5992B0EDFB377 /* GroupWelcomeView.swift */; }; - 3C8C548928133C84000A3EC7 /* PasteToConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C8C548828133C84000A3EC7 /* PasteToConnectView.swift */; }; 3CDBCF4227FAE51000354CDD /* ComposeLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CDBCF4127FAE51000354CDD /* ComposeLinkView.swift */; }; 3CDBCF4827FF621E00354CDD /* CILinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CDBCF4727FF621E00354CDD /* CILinkView.swift */; }; 5C00164428A26FBC0094D739 /* ContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00164328A26FBC0094D739 /* ContextMenu.swift */; }; @@ -61,7 +60,6 @@ 5C5F2B7027EBC704006A9D5F /* ProfileImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5F2B6F27EBC704006A9D5F /* ProfileImage.swift */; }; 5C65DAF929D0CC20003CEE45 /* DeveloperView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C65DAF829D0CC20003CEE45 /* DeveloperView.swift */; }; 5C65F343297D45E100B67AF3 /* VersionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C65F341297D3F3600B67AF3 /* VersionView.swift */; }; - 5C6AD81327A834E300348BD7 /* NewChatButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6AD81227A834E300348BD7 /* NewChatButton.swift */; }; 5C6BA667289BD954009B8ECC /* DismissSheets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6BA666289BD954009B8ECC /* DismissSheets.swift */; }; 5C7031162953C97F00150A12 /* CIFeaturePreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7031152953C97F00150A12 /* CIFeaturePreferenceView.swift */; }; 5C7505A227B65FDB00BE3227 /* CIMetaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7505A127B65FDB00BE3227 /* CIMetaView.swift */; }; @@ -98,8 +96,6 @@ 5CB0BA92282713FD00B3292C /* CreateProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB0BA91282713FD00B3292C /* CreateProfile.swift */; }; 5CB0BA9A2827FD8800B3292C /* HowItWorks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB0BA992827FD8800B3292C /* HowItWorks.swift */; }; 5CB2084F28DA4B4800D024EC /* RTCServers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB2084E28DA4B4800D024EC /* RTCServers.swift */; }; - 5CB2085128DB64CA00D024EC /* CreateLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB2085028DB64CA00D024EC /* CreateLinkView.swift */; }; - 5CB2085328DB7CAF00D024EC /* ConnectViaLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB2085228DB7CAF00D024EC /* ConnectViaLinkView.swift */; }; 5CB346E52868AA7F001FD2EF /* SuspendChat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB346E42868AA7F001FD2EF /* SuspendChat.swift */; }; 5CB346E72868D76D001FD2EF /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB346E62868D76D001FD2EF /* NotificationsView.swift */; }; 5CB346E92869E8BA001FD2EF /* PushEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB346E82869E8BA001FD2EF /* PushEnvironment.swift */; }; @@ -121,8 +117,6 @@ 5CC2C0FF2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CC2C0FD2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings */; }; 5CC868F329EB540C0017BBFD /* CIRcvDecryptionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC868F229EB540C0017BBFD /* CIRcvDecryptionError.swift */; }; 5CCB939C297EFCB100399E78 /* NavStackCompat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCB939B297EFCB100399E78 /* NavStackCompat.swift */; }; - 5CCD403427A5F6DF00368C90 /* AddContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403327A5F6DF00368C90 /* AddContactView.swift */; }; - 5CCD403727A5F9A200368C90 /* ScanToConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */; }; 5CD67B8F2B0E858A00C510B1 /* hs_init.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CD67B8D2B0E858A00C510B1 /* hs_init.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5CD67B902B0E858A00C510B1 /* hs_init.c in Sources */ = {isa = PBXBuildFile; fileRef = 5CD67B8E2B0E858A00C510B1 /* hs_init.c */; }; 5CDCAD482818589900503DA2 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CDCAD472818589900503DA2 /* NotificationService.swift */; }; @@ -157,6 +151,8 @@ 5CFA59D12864782E00863A68 /* ChatArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFA59CF286477B400863A68 /* ChatArchiveView.swift */; }; 5CFE0921282EEAF60002594B /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */; }; 5CFE0922282EEAF60002594B /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */; }; + 640417CD2B29B8C200CCB412 /* NewChatMenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640417CB2B29B8C200CCB412 /* NewChatMenuButton.swift */; }; + 640417CE2B29B8C200CCB412 /* NewChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640417CC2B29B8C200CCB412 /* NewChatView.swift */; }; 6407BA83295DA85D0082BA18 /* CIInvalidJSONView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */; }; 6419EC562AB8BC8B004A607A /* ContextInvitingContactMemberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */; }; 6419EC582AB97507004A607A /* CIMemberCreatedContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */; }; @@ -177,6 +173,11 @@ 646BB38E283FDB6D001CE359 /* LocalAuthenticationUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 646BB38D283FDB6D001CE359 /* LocalAuthenticationUtils.swift */; }; 647F090E288EA27B00644C40 /* GroupMemberInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647F090D288EA27B00644C40 /* GroupMemberInfoView.swift */; }; 648010AB281ADD15009009B9 /* CIFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648010AA281ADD15009009B9 /* CIFileView.swift */; }; + 64863B9B2B3C536500714A11 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64863B962B3C536500714A11 /* libgmpxx.a */; }; + 64863B9C2B3C536500714A11 /* libHSsimplex-chat-5.4.2.0-1dXNnkvLJVS8FSAgswHDGD-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64863B972B3C536500714A11 /* libHSsimplex-chat-5.4.2.0-1dXNnkvLJVS8FSAgswHDGD-ghc9.6.3.a */; }; + 64863B9D2B3C536500714A11 /* libHSsimplex-chat-5.4.2.0-1dXNnkvLJVS8FSAgswHDGD.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64863B982B3C536500714A11 /* libHSsimplex-chat-5.4.2.0-1dXNnkvLJVS8FSAgswHDGD.a */; }; + 64863B9E2B3C536500714A11 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64863B992B3C536500714A11 /* libgmp.a */; }; + 64863B9F2B3C536500714A11 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64863B9A2B3C536500714A11 /* libffi.a */; }; 649BCDA0280460FD00C3A862 /* ComposeImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */; }; 649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCDA12805D6EF00C3A862 /* CIImageView.swift */; }; 64AA1C6927EE10C800AC7277 /* ContextItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6827EE10C800AC7277 /* ContextItemView.swift */; }; @@ -263,7 +264,6 @@ 18415B08031E8FB0F7FC27F9 /* CallViewRenderers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallViewRenderers.swift; sourceTree = ""; }; 18415DAAAD1ADBEDB0EDA852 /* VideoPlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoPlayerView.swift; sourceTree = ""; }; 18415FD2E36F13F596A45BB4 /* CIVideoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CIVideoView.swift; sourceTree = ""; }; - 3C8C548828133C84000A3EC7 /* PasteToConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasteToConnectView.swift; sourceTree = ""; }; 3CDBCF4127FAE51000354CDD /* ComposeLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeLinkView.swift; sourceTree = ""; }; 3CDBCF4727FF621E00354CDD /* CILinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CILinkView.swift; sourceTree = ""; }; 5C00164328A26FBC0094D739 /* ContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenu.swift; sourceTree = ""; }; @@ -324,7 +324,6 @@ 5C65DAED29CB8908003CEE45 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; 5C65DAF829D0CC20003CEE45 /* DeveloperView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperView.swift; sourceTree = ""; }; 5C65F341297D3F3600B67AF3 /* VersionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionView.swift; sourceTree = ""; }; - 5C6AD81227A834E300348BD7 /* NewChatButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewChatButton.swift; sourceTree = ""; }; 5C6BA666289BD954009B8ECC /* DismissSheets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DismissSheets.swift; sourceTree = ""; }; 5C6D183229E93FBA00D430B3 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = "pl.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = ""; }; 5C6D183329E93FBA00D430B3 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -381,8 +380,6 @@ 5CB0BA91282713FD00B3292C /* CreateProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateProfile.swift; sourceTree = ""; }; 5CB0BA992827FD8800B3292C /* HowItWorks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HowItWorks.swift; sourceTree = ""; }; 5CB2084E28DA4B4800D024EC /* RTCServers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RTCServers.swift; sourceTree = ""; }; - 5CB2085028DB64CA00D024EC /* CreateLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateLinkView.swift; sourceTree = ""; }; - 5CB2085228DB7CAF00D024EC /* ConnectViaLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectViaLinkView.swift; sourceTree = ""; }; 5CB2085428DE647400D024EC /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; 5CB346E42868AA7F001FD2EF /* SuspendChat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuspendChat.swift; sourceTree = ""; }; 5CB346E62868D76D001FD2EF /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = ""; }; @@ -408,8 +405,6 @@ 5CC2C0FE2809BF11000C35E3 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = "ru.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = ""; }; 5CC868F229EB540C0017BBFD /* CIRcvDecryptionError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIRcvDecryptionError.swift; sourceTree = ""; }; 5CCB939B297EFCB100399E78 /* NavStackCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavStackCompat.swift; sourceTree = ""; }; - 5CCD403327A5F6DF00368C90 /* AddContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactView.swift; sourceTree = ""; }; - 5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanToConnectView.swift; sourceTree = ""; }; 5CD67B8D2B0E858A00C510B1 /* hs_init.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = hs_init.h; sourceTree = ""; }; 5CD67B8E2B0E858A00C510B1 /* hs_init.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = hs_init.c; sourceTree = ""; }; 5CDCAD452818589900503DA2 /* SimpleX NSE.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "SimpleX NSE.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -444,6 +439,8 @@ 5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateToAppGroupView.swift; sourceTree = ""; }; 5CFA59CF286477B400863A68 /* ChatArchiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatArchiveView.swift; sourceTree = ""; }; 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ZoomableScrollView.swift; path = Shared/Views/ZoomableScrollView.swift; sourceTree = SOURCE_ROOT; }; + 640417CB2B29B8C200CCB412 /* NewChatMenuButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewChatMenuButton.swift; sourceTree = ""; }; + 640417CC2B29B8C200CCB412 /* NewChatView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewChatView.swift; sourceTree = ""; }; 6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIInvalidJSONView.swift; sourceTree = ""; }; 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextInvitingContactMemberView.swift; sourceTree = ""; }; 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIMemberCreatedContactView.swift; sourceTree = ""; }; @@ -464,6 +461,11 @@ 646BB38D283FDB6D001CE359 /* LocalAuthenticationUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAuthenticationUtils.swift; sourceTree = ""; }; 647F090D288EA27B00644C40 /* GroupMemberInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMemberInfoView.swift; sourceTree = ""; }; 648010AA281ADD15009009B9 /* CIFileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIFileView.swift; sourceTree = ""; }; + 64863B962B3C536500714A11 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + 64863B972B3C536500714A11 /* libHSsimplex-chat-5.4.2.0-1dXNnkvLJVS8FSAgswHDGD-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.2.0-1dXNnkvLJVS8FSAgswHDGD-ghc9.6.3.a"; sourceTree = ""; }; + 64863B982B3C536500714A11 /* libHSsimplex-chat-5.4.2.0-1dXNnkvLJVS8FSAgswHDGD.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.2.0-1dXNnkvLJVS8FSAgswHDGD.a"; sourceTree = ""; }; + 64863B992B3C536500714A11 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + 64863B9A2B3C536500714A11 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; 6493D667280ED77F007A76FB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeImageView.swift; sourceTree = ""; }; 649BCDA12805D6EF00C3A862 /* CIImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIImageView.swift; sourceTree = ""; }; @@ -737,14 +739,10 @@ 5CB924DD27A8622200ACCCDD /* NewChat */ = { isa = PBXGroup; children = ( - 5C6AD81227A834E300348BD7 /* NewChatButton.swift */, - 5CCD403327A5F6DF00368C90 /* AddContactView.swift */, - 5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */, - 3C8C548828133C84000A3EC7 /* PasteToConnectView.swift */, + 640417CB2B29B8C200CCB412 /* NewChatMenuButton.swift */, + 640417CC2B29B8C200CCB412 /* NewChatView.swift */, 5CC1C99127A6C7F5000D9FF6 /* QRCode.swift */, 6442E0B9287F169300CEC0F9 /* AddGroupView.swift */, - 5CB2085028DB64CA00D024EC /* CreateLinkView.swift */, - 5CB2085228DB7CAF00D024EC /* ConnectViaLinkView.swift */, 64D0C2C529FAC1EC00B38D5F /* AddContactLearnMore.swift */, ); path = NewChat; @@ -1113,8 +1111,8 @@ buildActionMask = 2147483647; files = ( 64C06EB52A0A4A7C00792D4D /* ChatItemInfoView.swift in Sources */, + 640417CE2B29B8C200CCB412 /* NewChatView.swift in Sources */, 6440CA03288AECA70062C672 /* AddGroupMembersView.swift in Sources */, - 5C6AD81327A834E300348BD7 /* NewChatButton.swift in Sources */, 5C3F1D58284363C400EC8A82 /* PrivacySettings.swift in Sources */, 5C55A923283CEDE600C4E99E /* SoundPlayer.swift in Sources */, 5C93292F29239A170090FFF9 /* ProtocolServersView.swift in Sources */, @@ -1161,13 +1159,11 @@ 5C063D2727A4564100AEC577 /* ChatPreviewView.swift in Sources */, 5CC868F329EB540C0017BBFD /* CIRcvDecryptionError.swift in Sources */, 5C35CFCB27B2E91D00FB6C6D /* NtfManager.swift in Sources */, - 3C8C548928133C84000A3EC7 /* PasteToConnectView.swift in Sources */, 5C9D13A3282187BB00AB8B43 /* WebRTC.swift in Sources */, 5C9A5BDB2871E05400A5B906 /* SetNotificationsMode.swift in Sources */, 5CB0BA8E2827126500B3292C /* OnboardingView.swift in Sources */, 6442E0BE2880182D00CEC0F9 /* GroupChatInfoView.swift in Sources */, 5C2E261227A30FEA00F70299 /* TerminalView.swift in Sources */, - 5CB2085128DB64CA00D024EC /* CreateLinkView.swift in Sources */, 5C9FD96E27A5D6ED0075386C /* SendMessageView.swift in Sources */, 5CA7DFC329302AF000F7FDDE /* AppSheet.swift in Sources */, 64E972072881BB22008DBC02 /* CIGroupInvitationView.swift in Sources */, @@ -1176,8 +1172,8 @@ 5CB9250D27A9432000ACCCDD /* ChatListNavLink.swift in Sources */, 649BCDA0280460FD00C3A862 /* ComposeImageView.swift in Sources */, 5CA059ED279559F40002BEB4 /* ContentView.swift in Sources */, - 5CCD403427A5F6DF00368C90 /* AddContactView.swift in Sources */, 5C05DF532840AA1D00C683F9 /* CallSettings.swift in Sources */, + 640417CD2B29B8C200CCB412 /* NewChatMenuButton.swift in Sources */, 5CFE0921282EEAF60002594B /* ZoomableScrollView.swift in Sources */, 5C3A88CE27DF50170060F1C2 /* DetermineWidth.swift in Sources */, 5C7505A527B679EE00BE3227 /* NavLinkPlain.swift in Sources */, @@ -1212,7 +1208,6 @@ 6448BBB628FA9D56000D2AB9 /* GroupLinkView.swift in Sources */, 5CB346E92869E8BA001FD2EF /* PushEnvironment.swift in Sources */, 5C55A91F283AD0E400C4E99E /* CallManager.swift in Sources */, - 5CCD403727A5F9A200368C90 /* ScanToConnectView.swift in Sources */, 5CFA59D12864782E00863A68 /* ChatArchiveView.swift in Sources */, 649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */, 8C05382E2B39887E006436DC /* VideoUtils.swift in Sources */, @@ -1235,7 +1230,6 @@ 5C93293F2928E0FD0090FFF9 /* AudioRecPlay.swift in Sources */, 5C029EA82837DBB3004A9677 /* CICallItemView.swift in Sources */, 5CE4407227ADB1D0007B033A /* Emoji.swift in Sources */, - 5CB2085328DB7CAF00D024EC /* ConnectViaLinkView.swift in Sources */, 5C9CC7A928C532AB00BEF955 /* DatabaseErrorView.swift in Sources */, 5C1A4C1E27A715B700EAD5AD /* ChatItemView.swift in Sources */, 64AA1C6927EE10C800AC7277 /* ContextItemView.swift in Sources */, diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index a545d3508c..02c693cd28 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -3121,6 +3121,15 @@ public enum Format: Decodable, Equatable { case simplexLink(linkType: SimplexLinkType, simplexUri: String, smpHosts: [String]) case email case phone + + public var isSimplexLink: Bool { + get { + switch (self) { + case .simplexLink: return true + default: return false + } + } + } } public enum SimplexLinkType: String, Decodable { From 9c061508a41f5f88f1fab25422fe10b12d52d8eb Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 29 Dec 2023 22:46:45 +0700 Subject: [PATCH 05/16] android, desktop: rework UX of creating new connection (#3529) * android, desktop: rework UX of creating new connection * different place for clipboard listener * changes * changes * changes * button, strings * focus * code optimization and search icon * incognito link * comment * paddings * optimization * icon size and space in search * appbar background color * secondary color for icons in search bar * lighter tool bar and avatars * darker avatars in toolbar * background for selected item and divider * replacing connection view with actual chat view * clear * close unneeded view * filter icon background * filter doesn't hide current chat with empty search field * fixes for review * clearing focus on hiding keyboard * fix invalid qr code message * rename * color * buttons and text visibility when chat is not running yet * loading chats label --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> --- .../main/java/chat/simplex/app/SimplexApp.kt | 10 + .../common/platform/Modifier.android.kt | 2 + .../chatlist/ChatListNavLinkView.android.kt | 3 +- .../common/views/helpers/Utils.android.kt | 17 +- .../newchat/ConnectViaLinkView.android.kt | 68 -- .../views/newchat/QRCodeScanner.android.kt | 162 +++-- .../newchat/ScanToConnectView.android.kt | 22 - .../kotlin/chat/simplex/common/App.kt | 19 +- .../chat/simplex/common/model/ChatModel.kt | 41 +- .../chat/simplex/common/model/SimpleXAPI.kt | 28 +- .../chat/simplex/common/platform/Core.kt | 7 +- .../chat/simplex/common/platform/Modifier.kt | 2 + .../simplex/common/views/chat/ChatInfoView.kt | 2 +- .../simplex/common/views/chat/ChatView.kt | 681 +++++++++--------- .../simplex/common/views/chat/ScanCodeView.kt | 31 +- .../common/views/chat/VerifyCodeView.kt | 4 +- .../common/views/chat/group/GroupLinkView.kt | 2 +- .../views/chat/group/GroupMemberInfoView.kt | 4 +- .../common/views/chatlist/ChatHelpView.kt | 4 +- .../views/chatlist/ChatListNavLinkView.kt | 47 +- .../common/views/chatlist/ChatListView.kt | 263 +++++-- .../common/views/helpers/AlertManager.kt | 23 +- .../views/helpers/DefaultProgressBar.kt | 27 + .../common/views/helpers/DefaultTopAppBar.kt | 2 +- .../simplex/common/views/helpers/ModalView.kt | 16 +- .../common/views/helpers/SearchTextField.kt | 7 +- .../simplex/common/views/helpers/Utils.kt | 3 + .../views/newchat/AddContactLearnMore.kt | 7 +- .../common/views/newchat/AddContactView.kt | 186 ----- .../{ScanToConnectView.kt => ConnectPlan.kt} | 299 ++++---- .../views/newchat/ConnectViaLinkView.kt | 12 - .../newchat/ContactConnectionInfoView.kt | 49 +- .../common/views/newchat/CreateLinkView.kt | 118 --- .../common/views/newchat/NewChatSheet.kt | 18 +- .../common/views/newchat/NewChatView.kt | 436 +++++++++++ .../common/views/newchat/PasteToConnect.kt | 135 ---- .../simplex/common/views/newchat/QRCode.kt | 35 +- .../common/views/newchat/QRCodeScanner.kt | 10 +- .../views/onboarding/CreateSimpleXAddress.kt | 2 +- .../common/views/remote/ConnectDesktopView.kt | 13 +- .../common/views/remote/ConnectMobileView.kt | 6 +- .../views/usersettings/IncognitoView.kt | 2 + .../views/usersettings/ProtocolServerView.kt | 2 +- .../views/usersettings/ScanProtocolServer.kt | 28 +- .../views/usersettings/UserAddressView.kt | 2 +- .../commonMain/resources/MR/ar/strings.xml | 5 +- .../commonMain/resources/MR/base/strings.xml | 38 +- .../commonMain/resources/MR/bg/strings.xml | 9 +- .../commonMain/resources/MR/cs/strings.xml | 3 - .../commonMain/resources/MR/de/strings.xml | 13 +- .../commonMain/resources/MR/es/strings.xml | 3 - .../commonMain/resources/MR/fi/strings.xml | 3 - .../commonMain/resources/MR/fr/strings.xml | 11 +- .../commonMain/resources/MR/hu/strings.xml | 9 +- .../commonMain/resources/MR/it/strings.xml | 11 +- .../commonMain/resources/MR/iw/strings.xml | 3 - .../commonMain/resources/MR/ja/strings.xml | 3 - .../commonMain/resources/MR/ko/strings.xml | 3 - .../commonMain/resources/MR/lt/strings.xml | 3 - .../commonMain/resources/MR/nl/strings.xml | 11 +- .../commonMain/resources/MR/pl/strings.xml | 11 +- .../resources/MR/pt-rBR/strings.xml | 3 - .../commonMain/resources/MR/pt/strings.xml | 3 - .../commonMain/resources/MR/ru/strings.xml | 13 +- .../commonMain/resources/MR/th/strings.xml | 3 - .../commonMain/resources/MR/tr/strings.xml | 9 +- .../commonMain/resources/MR/uk/strings.xml | 9 +- .../resources/MR/zh-rCN/strings.xml | 11 +- .../resources/MR/zh-rTW/strings.xml | 3 - .../common/platform/Modifier.desktop.kt | 4 + .../chatlist/ChatListNavLinkView.desktop.kt | 14 +- .../common/views/helpers/Utils.desktop.kt | 35 + .../newchat/ConnectViaLinkView.desktop.kt | 11 - .../views/newchat/QRCodeScanner.desktop.kt | 7 +- .../newchat/ScanToConnectView.desktop.kt | 15 - 75 files changed, 1670 insertions(+), 1466 deletions(-) delete mode 100644 apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.android.kt delete mode 100644 apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.android.kt create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultProgressBar.kt delete mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactView.kt rename apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/{ScanToConnectView.kt => ConnectPlan.kt} (71%) delete mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.kt delete mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/CreateLinkView.kt create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt delete mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/PasteToConnect.kt delete mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.desktop.kt delete mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.desktop.kt diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt index ee43da5d44..a8c8b5c1b2 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt @@ -1,6 +1,9 @@ package chat.simplex.app import android.app.Application +import android.content.Context +import androidx.compose.ui.platform.ClipboardManager +import chat.simplex.common.platform.Log import android.app.UiModeManager import android.os.* import androidx.lifecycle.* @@ -95,6 +98,13 @@ class SimplexApp: Application(), LifecycleEventObserver { } Lifecycle.Event.ON_RESUME -> { isAppOnForeground = true + /** + * When the app calls [ClipboardManager.shareText] and a user copies text in clipboard, Android denies + * access to clipboard because the app considered in background. + * This will ensure that the app will get the event on resume + * */ + val service = androidAppContext.getSystemService(Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager + chatModel.clipboardHasText.value = service.hasPrimaryClip() if (chatModel.controller.appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete && chatModel.currentUser.value != null) { SimplexService.showBackgroundServiceNoticeIfNeeded() } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Modifier.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Modifier.android.kt index 26ada2b7e6..b103367fe8 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Modifier.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Modifier.android.kt @@ -26,3 +26,5 @@ actual fun Modifier.desktopOnExternalDrag( ): Modifier = this actual fun Modifier.onRightClick(action: () -> Unit): Modifier = this + +actual fun Modifier.desktopPointerHoverIconHand(): Modifier = this diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.android.kt index 30f5b81387..f8914c6653 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.android.kt @@ -17,7 +17,8 @@ actual fun ChatListNavLinkLayout( dropdownMenuItems: (@Composable () -> Unit)?, showMenu: MutableState, stopped: Boolean, - selectedChat: State + selectedChat: State, + nextChatSelected: State, ) { var modifier = Modifier.fillMaxWidth() if (!stopped) modifier = modifier diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/Utils.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/Utils.android.kt index d244294763..904f9a555a 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/Utils.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/Utils.android.kt @@ -1,5 +1,7 @@ package chat.simplex.common.views.helpers +import android.content.ClipboardManager +import android.content.Context import android.content.res.Resources import android.graphics.* import android.graphics.Typeface @@ -12,6 +14,7 @@ import android.text.SpannedString import android.text.style.* import android.util.Base64 import android.view.WindowManager +import androidx.compose.runtime.* import androidx.compose.ui.graphics.* import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.* @@ -24,7 +27,6 @@ import androidx.core.text.HtmlCompat import chat.simplex.common.helpers.* import chat.simplex.common.model.* import chat.simplex.common.platform.* -import chat.simplex.res.MR import dev.icerock.moko.resources.StringResource import java.io.* import java.net.URI @@ -55,6 +57,19 @@ fun keepScreenOn(on: Boolean) { } } +@Composable +actual fun SetupClipboardListener() { + DisposableEffect(Unit) { + val service = androidAppContext.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val listener = { chatModel.clipboardHasText.value = service.hasPrimaryClip() } + chatModel.clipboardHasText.value = service.hasPrimaryClip() + service.addPrimaryClipChangedListener(listener) + onDispose { + service.removePrimaryClipChangedListener(listener) + } + } +} + actual fun escapedHtmlToAnnotatedString(text: String, density: Density): AnnotatedString { return spannableStringToAnnotatedString(HtmlCompat.fromHtml(text, HtmlCompat.FROM_HTML_MODE_LEGACY), density) } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.android.kt deleted file mode 100644 index e5a7ae40a5..0000000000 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.android.kt +++ /dev/null @@ -1,68 +0,0 @@ -package chat.simplex.common.views.newchat - -import androidx.compose.foundation.layout.* -import androidx.compose.material.* -import androidx.compose.runtime.* -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.unit.sp -import chat.simplex.common.model.ChatModel -import chat.simplex.common.model.RemoteHostInfo -import chat.simplex.res.MR - -@Composable -actual fun ConnectViaLinkView(m: ChatModel, rh: RemoteHostInfo?, close: () -> Unit) { - // TODO this should close if remote host changes in model - val selection = remember { - mutableStateOf( - runCatching { ConnectViaLinkTab.valueOf(m.controller.appPrefs.connectViaLinkTab.get()!!) }.getOrDefault(ConnectViaLinkTab.SCAN) - ) - } - val tabTitles = ConnectViaLinkTab.values().map { - when (it) { - ConnectViaLinkTab.SCAN -> stringResource(MR.strings.scan_QR_code) - ConnectViaLinkTab.PASTE -> stringResource(MR.strings.paste_the_link_you_received) - } - } - Column( - Modifier.fillMaxHeight(), - verticalArrangement = Arrangement.SpaceBetween - ) { - Column(Modifier.weight(1f)) { - when (selection.value) { - ConnectViaLinkTab.SCAN -> { - ScanToConnectView(m, rh, close) - } - ConnectViaLinkTab.PASTE -> { - PasteToConnectView(m, rh, close) - } - } - } - TabRow( - selectedTabIndex = selection.value.ordinal, - backgroundColor = Color.Transparent, - contentColor = MaterialTheme.colors.primary, - ) { - tabTitles.forEachIndexed { index, it -> - Tab( - selected = selection.value.ordinal == index, - onClick = { - selection.value = ConnectViaLinkTab.values()[index] - m.controller.appPrefs.connectViaLinkTab.set(selection.value .name) - }, - text = { Text(it, fontSize = 13.sp) }, - icon = { - Icon( - if (ConnectViaLinkTab.SCAN.ordinal == index) painterResource(MR.images.ic_qr_code) else painterResource(MR.images.ic_article), - it - ) - }, - selectedContentColor = MaterialTheme.colors.primary, - unselectedContentColor = MaterialTheme.colors.secondary, - ) - } - } - } -} diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.android.kt index e7453ce20a..362d793e83 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.android.kt @@ -2,16 +2,20 @@ package chat.simplex.common.views.newchat import android.Manifest import android.annotation.SuppressLint +import android.content.pm.PackageManager import android.util.Log import android.view.ViewGroup import androidx.camera.core.* import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.view.PreviewView +import androidx.compose.foundation.layout.* +import androidx.compose.material.* import androidx.compose.runtime.* +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.platform.* +import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat import boofcv.abst.fiducial.QrCodeDetector @@ -20,18 +24,23 @@ import boofcv.android.ConvertCameraImage import boofcv.factory.fiducial.FactoryFiducial import boofcv.struct.image.GrayU8 import chat.simplex.common.platform.TAG +import chat.simplex.common.ui.theme.DEFAULT_PADDING_HALF +import chat.simplex.common.views.helpers.* +import chat.simplex.res.MR import com.google.accompanist.permissions.rememberPermissionState import com.google.common.util.concurrent.ListenableFuture +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource import java.util.concurrent.* // Adapted from learntodroid - https://gist.github.com/learntodroid/8f839be0b29d0378f843af70607bd7f5 @Composable -actual fun QRCodeScanner(onBarcode: (String) -> Unit) { - val cameraPermissionState = rememberPermissionState(permission = Manifest.permission.CAMERA) - LaunchedEffect(Unit) { - cameraPermissionState.launchPermissionRequest() - } +actual fun QRCodeScanner( + showQRCodeScanner: MutableState, + padding: PaddingValues, + onBarcode: (String) -> Unit +) { val context = LocalContext.current val lifecycleOwner = LocalLifecycleOwner.current var preview by remember { mutableStateOf(null) } @@ -48,57 +57,102 @@ actual fun QRCodeScanner(onBarcode: (String) -> Unit) { } } - AndroidView( - factory = { AndroidViewContext -> - PreviewView(AndroidViewContext).apply { - this.scaleType = PreviewView.ScaleType.FILL_CENTER - layoutParams = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT, - ) - implementationMode = PreviewView.ImplementationMode.COMPATIBLE - } - }, - modifier = Modifier.clipToBounds() - ) { previewView -> - val cameraSelector: CameraSelector = CameraSelector.Builder() - .requireLensFacing(CameraSelector.LENS_FACING_BACK) - .build() - val cameraExecutor: ExecutorService = Executors.newSingleThreadExecutor() - cameraProviderFuture?.addListener({ - preview = Preview.Builder().build().also { - it.setSurfaceProvider(previewView.surfaceProvider) - } - val detector: QrCodeDetector = FactoryFiducial.qrcode(null, GrayU8::class.java) - fun getQR(imageProxy: ImageProxy) { - val currentTimeStamp = System.currentTimeMillis() - if (currentTimeStamp - lastAnalyzedTimeStamp >= TimeUnit.SECONDS.toMillis(1)) { - detector.process(imageProxyToGrayU8(imageProxy)) - val found = detector.detections - val qr = found.firstOrNull() - if (qr != null) { - if (qr.message != contactLink) { - // Make sure link is new and not a repeat - contactLink = qr.message - onBarcode(contactLink) + Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { + val cameraPermissionState = rememberPermissionState(permission = Manifest.permission.CAMERA) + val modifier = Modifier + .padding(padding) + .clipToBounds() + .widthIn(max = 400.dp) + .aspectRatio(1f) + val showScanner = remember { showQRCodeScanner } + if (showScanner.value && cameraPermissionState.hasPermission) { + AndroidView( + factory = { AndroidViewContext -> + PreviewView(AndroidViewContext).apply { + this.scaleType = PreviewView.ScaleType.FILL_CENTER + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + ) + implementationMode = PreviewView.ImplementationMode.COMPATIBLE + } + }, + modifier = modifier + ) { previewView -> + val cameraSelector: CameraSelector = CameraSelector.Builder() + .requireLensFacing(CameraSelector.LENS_FACING_BACK) + .build() + val cameraExecutor: ExecutorService = Executors.newSingleThreadExecutor() + cameraProviderFuture?.addListener({ + preview = Preview.Builder().build().also { + it.setSurfaceProvider(previewView.surfaceProvider) + } + val detector: QrCodeDetector = FactoryFiducial.qrcode(null, GrayU8::class.java) + fun getQR(imageProxy: ImageProxy) { + val currentTimeStamp = System.currentTimeMillis() + if (currentTimeStamp - lastAnalyzedTimeStamp >= TimeUnit.SECONDS.toMillis(1)) { + detector.process(imageProxyToGrayU8(imageProxy)) + val found = detector.detections + val qr = found.firstOrNull() + if (qr != null) { + if (qr.message != contactLink) { + // Make sure link is new and not a repeat + contactLink = qr.message + onBarcode(contactLink) + } + } } + imageProxy.close() + } + + val imageAnalyzer = ImageAnalysis.Analyzer { proxy -> getQR(proxy) } + val imageAnalysis: ImageAnalysis = ImageAnalysis.Builder() + .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) + .setImageQueueDepth(1) + .build() + .also { it.setAnalyzer(cameraExecutor, imageAnalyzer) } + try { + cameraProviderFuture?.get()?.unbindAll() + cameraProviderFuture?.get()?.bindToLifecycle(lifecycleOwner, cameraSelector, preview, imageAnalysis) + } catch (e: Exception) { + Log.d(TAG, "CameraPreview: ${e.localizedMessage}") + } + }, ContextCompat.getMainExecutor(context)) + } + } else { + val buttonColors = ButtonDefaults.buttonColors( + backgroundColor = MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.9f), + contentColor = MaterialTheme.colors.primary, + disabledBackgroundColor = MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.9f), + disabledContentColor = MaterialTheme.colors.primary, + ) + when { + !cameraPermissionState.hasPermission && !cameraPermissionState.permissionRequested && showScanner.value -> { + LaunchedEffect(Unit) { + cameraPermissionState.launchPermissionRequest() + } + } + !cameraPermissionState.hasPermission -> { + Button({ withBGApi { cameraPermissionState.launchPermissionRequest() } }, modifier = modifier, colors = buttonColors) { + Icon(painterResource(MR.images.ic_camera_enhance), null) + Spacer(Modifier.width(DEFAULT_PADDING_HALF)) + Text(stringResource(MR.strings.enable_camera_access)) + } + } + cameraPermissionState.hasPermission -> { + Button({ showQRCodeScanner.value = true }, modifier = modifier, colors = buttonColors) { + Icon(painterResource(MR.images.ic_qr_code), null) + Spacer(Modifier.width(DEFAULT_PADDING_HALF)) + Text(stringResource(MR.strings.tap_to_scan)) + } + } + !LocalContext.current.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) -> { + Button({ }, enabled = false, modifier = modifier, colors = buttonColors) { + Text(stringResource(MR.strings.camera_not_available)) } } - imageProxy.close() } - val imageAnalyzer = ImageAnalysis.Analyzer { proxy -> getQR(proxy) } - val imageAnalysis: ImageAnalysis = ImageAnalysis.Builder() - .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) - .setImageQueueDepth(1) - .build() - .also { it.setAnalyzer(cameraExecutor, imageAnalyzer) } - try { - cameraProviderFuture?.get()?.unbindAll() - cameraProviderFuture?.get()?.bindToLifecycle(lifecycleOwner, cameraSelector, preview, imageAnalysis) - } catch (e: Exception) { - Log.d(TAG, "CameraPreview: ${e.localizedMessage}") - } - }, ContextCompat.getMainExecutor(context)) + } } } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.android.kt deleted file mode 100644 index f046f44bee..0000000000 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.android.kt +++ /dev/null @@ -1,22 +0,0 @@ -package chat.simplex.common.views.newchat - -import android.Manifest -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import chat.simplex.common.model.ChatModel -import chat.simplex.common.model.RemoteHostInfo -import com.google.accompanist.permissions.rememberPermissionState - -@Composable -actual fun ScanToConnectView(chatModel: ChatModel, rh: RemoteHostInfo?, close: () -> Unit) { - val cameraPermissionState = rememberPermissionState(permission = Manifest.permission.CAMERA) - LaunchedEffect(Unit) { - cameraPermissionState.launchPermissionRequest() - } - ConnectContactLayout( - chatModel = chatModel, - rh = rh, - incognitoPref = chatModel.controller.appPrefs.incognito, - close = close - ) -} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt index d457eb57a1..950515f055 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt @@ -107,7 +107,7 @@ fun MainScreen() { val localUserCreated = chatModel.localUserCreated.value var showInitializationView by remember { mutableStateOf(false) } when { - chatModel.chatDbStatus.value == null && showInitializationView -> InitializationView() + chatModel.chatDbStatus.value == null && showInitializationView -> DefaultProgressView(stringResource(MR.strings.opening_database)) showChatDatabaseError -> { chatModel.chatDbStatus.value?.let { DatabaseErrorView(chatModel.chatDbStatus, chatModel.controller.appPrefs) @@ -125,6 +125,7 @@ fun MainScreen() { } val scaffoldState = rememberScaffoldState() val settingsState = remember { SettingsViewState(userPickerState, scaffoldState) } + SetupClipboardListener() if (appPlatform.isAndroid) { AndroidScreen(settingsState) } else { @@ -342,22 +343,6 @@ fun DesktopScreen(settingsState: SettingsViewState) { } } -@Composable -fun InitializationView() { - Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - CircularProgressIndicator( - Modifier - .padding(bottom = DEFAULT_PADDING) - .size(30.dp), - color = MaterialTheme.colors.secondary, - strokeWidth = 2.5.dp - ) - Text(stringResource(MR.strings.opening_database)) - } - } -} - @Composable private fun SwitchingUsersView() { if (remember { chatModel.switchingUsersAndHosts }.value) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 708bbb9073..f51f6986f8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -97,8 +97,8 @@ object ChatModel { val showCallView = mutableStateOf(false) val switchingCall = mutableStateOf(false) - // currently showing QR code - val connReqInv = mutableStateOf(null as String?) + // currently showing invitation + val showingInvitation = mutableStateOf(null as ShowingInvitation?) var draft = mutableStateOf(null as ComposeState?) var draftChatId = mutableStateOf(null as String?) @@ -109,6 +109,8 @@ object ChatModel { val filesToDelete = mutableSetOf() val simplexLinkMode by lazy { mutableStateOf(ChatController.appPrefs.simplexLinkMode.get()) } + val clipboardHasText = mutableStateOf(false) + var updatingChatsMutex: Mutex = Mutex() val desktopNoUserNoRemote: Boolean @Composable get() = appPlatform.isDesktop && currentUser.value == null && currentRemoteHost.value == null @@ -561,15 +563,30 @@ object ChatModel { chats.add(index = 0, chat) } - fun dismissConnReqView(id: String) { - if (connReqInv.value == null) return - val info = getChat(id)?.chatInfo as? ChatInfo.ContactConnection ?: return - if (info.contactConnection.connReqInv == connReqInv.value) { - connReqInv.value = null - ModalManager.center.closeModals() + fun replaceConnReqView(id: String, withId: String) { + if (id == showingInvitation.value?.connId) { + showingInvitation.value = null + chatModel.chatItems.clear() + chatModel.chatId.value = withId + ModalManager.end.closeModals() } } + fun dismissConnReqView(id: String) { + if (id == showingInvitation.value?.connId) { + showingInvitation.value = null + chatModel.chatItems.clear() + chatModel.chatId.value = null + // Close NewChatView + ModalManager.center.closeModals() + ModalManager.end.closeModals() + } + } + + fun markShowingInvitationUsed() { + showingInvitation.value = showingInvitation.value?.copy(connChatUsed = true) + } + fun removeChat(rhId: Long?, id: String) { chats.removeAll { it.id == id && it.remoteHostId == rhId } } @@ -630,6 +647,12 @@ object ChatModel { fun connectedToRemote(): Boolean = currentRemoteHost.value != null || remoteCtrlSession.value?.active == true } +data class ShowingInvitation( + val connId: String, + val connReq: String, + val connChatUsed: Boolean +) + enum class ChatType(val type: String) { Direct("@"), Group("#"), @@ -2664,6 +2687,8 @@ sealed class Format { is Phone -> linkStyle } + val isSimplexLink = this is SimplexLink + companion object { val linkStyle @Composable get() = SpanStyle(color = MaterialTheme.colors.primary, textDecoration = TextDecoration.Underline) } 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 e3f565b77c..619238f6d3 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 @@ -9,7 +9,6 @@ import dev.icerock.moko.resources.compose.painterResource import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.call.* -import chat.simplex.common.views.newchat.ConnectViaLinkTab import chat.simplex.common.views.onboarding.OnboardingStage import chat.simplex.common.views.usersettings.* import com.charleskorn.kaml.Yaml @@ -135,7 +134,6 @@ class AppPreferences { val networkTCPKeepIntvl = mkIntPreference(SHARED_PREFS_NETWORK_TCP_KEEP_INTVL, KeepAliveOpts.defaults.keepIntvl) val networkTCPKeepCnt = mkIntPreference(SHARED_PREFS_NETWORK_TCP_KEEP_CNT, KeepAliveOpts.defaults.keepCnt) val incognito = mkBoolPreference(SHARED_PREFS_INCOGNITO, false) - val connectViaLinkTab = mkStrPreference(SHARED_PREFS_CONNECT_VIA_LINK_TAB, ConnectViaLinkTab.SCAN.name) val liveMessageAlertShown = mkBoolPreference(SHARED_PREFS_LIVE_MESSAGE_ALERT_SHOWN, false) val showHiddenProfilesNotice = mkBoolPreference(SHARED_PREFS_SHOW_HIDDEN_PROFILES_NOTICE, true) val showMuteProfileAlert = mkBoolPreference(SHARED_PREFS_SHOW_MUTE_PROFILE_ALERT, true) @@ -292,7 +290,6 @@ class AppPreferences { private const val SHARED_PREFS_NETWORK_TCP_KEEP_INTVL = "NetworkTCPKeepIntvl" private const val SHARED_PREFS_NETWORK_TCP_KEEP_CNT = "NetworkTCPKeepCnt" private const val SHARED_PREFS_INCOGNITO = "Incognito" - private const val SHARED_PREFS_CONNECT_VIA_LINK_TAB = "ConnectViaLinkTab" private const val SHARED_PREFS_LIVE_MESSAGE_ALERT_SHOWN = "LiveMessageAlertShown" private const val SHARED_PREFS_SHOW_HIDDEN_PROFILES_NOTICE = "ShowHiddenProfilesNotice" private const val SHARED_PREFS_SHOW_MUTE_PROFILE_ALERT = "ShowMuteProfileAlert" @@ -890,19 +887,19 @@ object ChatController { - suspend fun apiAddContact(rh: Long?, incognito: Boolean): Pair? { + suspend fun apiAddContact(rh: Long?, incognito: Boolean): Pair?, (() -> Unit)?> { val userId = chatModel.currentUser.value?.userId ?: run { Log.e(TAG, "apiAddContact: no current user") - return null + return null to null } val r = sendCmd(rh, CC.APIAddContact(userId, incognito)) return when (r) { - is CR.Invitation -> r.connReqInvitation to r.connection + is CR.Invitation -> (r.connReqInvitation to r.connection) to null else -> { if (!(networkErrorAlert(r))) { - apiErrorAlert("apiAddContact", generalGetString(MR.strings.connection_error), r) + return null to { apiErrorAlert("apiAddContact", generalGetString(MR.strings.connection_error), r) } } - null + null to null } } } @@ -981,6 +978,13 @@ object ChatController { } } + suspend fun deleteChat(chat: Chat, notify: Boolean? = null) { + val cInfo = chat.chatInfo + if (apiDeleteChat(rh = chat.remoteHostId, type = cInfo.chatType, id = cInfo.apiId, notify = notify)) { + chatModel.removeChat(chat.remoteHostId, cInfo.id) + } + } + suspend fun apiDeleteChat(rh: Long?, type: ChatType, id: Long, notify: Boolean? = null): Boolean { val r = sendCmd(rh, CC.ApiDeleteChat(type, id, notify)) when { @@ -1568,7 +1572,7 @@ object ChatController { chatModel.updateContact(rhId, r.contact) val conn = r.contact.activeConn if (conn != null) { - chatModel.dismissConnReqView(conn.id) + chatModel.replaceConnReqView(conn.id, "@${r.contact.contactId}") chatModel.removeChat(rhId, conn.id) } } @@ -1582,7 +1586,7 @@ object ChatController { chatModel.updateContact(rhId, r.contact) val conn = r.contact.activeConn if (conn != null) { - chatModel.dismissConnReqView(conn.id) + chatModel.replaceConnReqView(conn.id, "@${r.contact.contactId}") chatModel.removeChat(rhId, conn.id) } } @@ -1717,7 +1721,7 @@ object ChatController { chatModel.updateGroup(rhId, r.groupInfo) val conn = r.hostContact?.activeConn if (conn != null) { - chatModel.dismissConnReqView(conn.id) + chatModel.replaceConnReqView(conn.id, "#${r.groupInfo.groupId}") chatModel.removeChat(rhId, conn.id) } } @@ -1727,7 +1731,7 @@ object ChatController { chatModel.updateGroup(rhId, r.groupInfo) val hostConn = r.hostMember.activeConn if (hostConn != null) { - chatModel.dismissConnReqView(hostConn.id) + chatModel.replaceConnReqView(hostConn.id, "#${r.groupInfo.groupId}") chatModel.removeChat(rhId, hostConn.id) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt index 7d097efb7a..52ff269f54 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt @@ -73,11 +73,14 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat } } else { val savedOnboardingStage = appPreferences.onboardingStage.get() - appPreferences.onboardingStage.set(if (listOf(OnboardingStage.Step1_SimpleXInfo, OnboardingStage.Step2_CreateProfile).contains(savedOnboardingStage) && chatModel.users.size == 1) { + val newStage = if (listOf(OnboardingStage.Step1_SimpleXInfo, OnboardingStage.Step2_CreateProfile).contains(savedOnboardingStage) && chatModel.users.size == 1) { OnboardingStage.Step3_CreateSimpleXAddress } else { savedOnboardingStage - }) + } + if (appPreferences.onboardingStage.get() != newStage) { + appPreferences.onboardingStage.set(newStage) + } if (appPreferences.onboardingStage.get() == OnboardingStage.OnboardingComplete && !chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.get()) { chatModel.setDeliveryReceipts.value = true } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Modifier.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Modifier.kt index 1141ab21ab..4a10027746 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Modifier.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Modifier.kt @@ -23,3 +23,5 @@ expect fun Modifier.desktopOnExternalDrag( ): Modifier expect fun Modifier.onRightClick(action: () -> Unit): Modifier + +expect fun Modifier.desktopPointerHoverIconHand(): Modifier diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt index 694ec2ba18..d87fce5fba 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt @@ -355,7 +355,7 @@ fun ChatInfoLayout( if (contact.contactLink != null) { SectionView(stringResource(MR.strings.address_section_title).uppercase()) { - SimpleXLinkQRCode(contact.contactLink, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f)) + SimpleXLinkQRCode(contact.contactLink) val clipboard = LocalClipboardManager.current ShareAddressButton { clipboard.shareText(simplexChatLink(contact.contactLink)) } SectionTextFooter(stringResource(MR.strings.you_can_share_this_address_with_your_contacts).format(contact.displayName)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 0e2a7c1680..69a1b50e28 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -33,6 +33,7 @@ import chat.simplex.common.views.helpers.* import chat.simplex.common.model.GroupInfo import chat.simplex.common.platform.* import chat.simplex.common.platform.AudioPlayer +import chat.simplex.common.views.newchat.ContactConnectionInfoView import chat.simplex.res.MR import kotlinx.coroutines.* import kotlinx.coroutines.flow.* @@ -114,343 +115,371 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: } } val clipboard = LocalClipboardManager.current - - ChatLayout( - chat, - unreadCount, - composeState, - composeView = { - Column( - Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - if ( - chat.chatInfo is ChatInfo.Direct - && !chat.chatInfo.contact.ready - && chat.chatInfo.contact.active - && !chat.chatInfo.contact.nextSendGrpInv - ) { - Text( - generalGetString(MR.strings.contact_connection_pending), - Modifier.padding(top = 4.dp), - fontSize = 14.sp, - color = MaterialTheme.colors.secondary - ) - } - ComposeView( - chatModel, chat, composeState, attachmentOption, - showChooseAttachment = { scope.launch { attachmentBottomSheetState.show() } } - ) - } - }, - attachmentOption, - attachmentBottomSheetState, - chatModel.chatItems, - searchText, - useLinkPreviews = useLinkPreviews, - linkMode = chatModel.simplexLinkMode.value, - back = { - hideKeyboard(view) - AudioPlayer.stop() - chatModel.chatId.value = null - chatModel.groupMembers.clear() - }, - info = { - if (ModalManager.end.hasModalsOpen()) { - ModalManager.end.closeModals() - return@ChatLayout - } - hideKeyboard(view) - withApi { - // The idea is to preload information before showing a modal because large groups can take time to load all members - var preloadedContactInfo: Pair? = null - var preloadedCode: String? = null - var preloadedLink: Pair? = null - if (chat.chatInfo is ChatInfo.Direct) { - preloadedContactInfo = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId) - preloadedCode = chatModel.controller.apiGetContactCode(chatRh, chat.chatInfo.apiId)?.second - } else if (chat.chatInfo is ChatInfo.Group) { - setGroupMembers(chatRh, chat.chatInfo.groupInfo, chatModel) - preloadedLink = chatModel.controller.apiGetGroupLink(chatRh, chat.chatInfo.groupInfo.groupId) - } - ModalManager.end.showModalCloseable(true) { close -> - val chat = remember { activeChat }.value - if (chat?.chatInfo is ChatInfo.Direct) { - var contactInfo: Pair? by remember { mutableStateOf(preloadedContactInfo) } - var code: String? by remember { mutableStateOf(preloadedCode) } - KeyChangeEffect(chat.id, ChatModel.networkStatuses.toMap()) { - contactInfo = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId) - preloadedContactInfo = contactInfo - code = chatModel.controller.apiGetContactCode(chatRh, chat.chatInfo.apiId)?.second - preloadedCode = code + when (chat.chatInfo) { + is ChatInfo.Direct, is ChatInfo.Group -> { + ChatLayout( + chat, + unreadCount, + composeState, + composeView = { + Column( + Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + if ( + chat.chatInfo is ChatInfo.Direct + && !chat.chatInfo.contact.ready + && chat.chatInfo.contact.active + && !chat.chatInfo.contact.nextSendGrpInv + ) { + Text( + generalGetString(MR.strings.contact_connection_pending), + Modifier.padding(top = 4.dp), + fontSize = 14.sp, + color = MaterialTheme.colors.secondary + ) } - ChatInfoView(chatModel, (chat.chatInfo as ChatInfo.Direct).contact, contactInfo?.first, contactInfo?.second, chat.chatInfo.localAlias, code, close) - } else if (chat?.chatInfo is ChatInfo.Group) { - var link: Pair? by remember(chat.id) { mutableStateOf(preloadedLink) } - KeyChangeEffect(chat.id) { - setGroupMembers(chatRh, (chat.chatInfo as ChatInfo.Group).groupInfo, chatModel) - link = chatModel.controller.apiGetGroupLink(chatRh, chat.chatInfo.groupInfo.groupId) - preloadedLink = link + ComposeView( + chatModel, chat, composeState, attachmentOption, + showChooseAttachment = { scope.launch { attachmentBottomSheetState.show() } } + ) + } + }, + attachmentOption, + attachmentBottomSheetState, + chatModel.chatItems, + searchText, + useLinkPreviews = useLinkPreviews, + linkMode = chatModel.simplexLinkMode.value, + back = { + hideKeyboard(view) + AudioPlayer.stop() + chatModel.chatId.value = null + chatModel.groupMembers.clear() + }, + info = { + if (ModalManager.end.hasModalsOpen()) { + ModalManager.end.closeModals() + return@ChatLayout + } + hideKeyboard(view) + withApi { + // The idea is to preload information before showing a modal because large groups can take time to load all members + var preloadedContactInfo: Pair? = null + var preloadedCode: String? = null + var preloadedLink: Pair? = null + if (chat.chatInfo is ChatInfo.Direct) { + preloadedContactInfo = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId) + preloadedCode = chatModel.controller.apiGetContactCode(chatRh, chat.chatInfo.apiId)?.second + } else if (chat.chatInfo is ChatInfo.Group) { + setGroupMembers(chatRh, chat.chatInfo.groupInfo, chatModel) + preloadedLink = chatModel.controller.apiGetGroupLink(chatRh, chat.chatInfo.groupInfo.groupId) } - GroupChatInfoView(chatModel, chatRh, chat.id, link?.first, link?.second, { - link = it - preloadedLink = it - }, close) - } - } - } - }, - showMemberInfo = { groupInfo: GroupInfo, member: GroupMember -> - hideKeyboard(view) - withApi { - val r = chatModel.controller.apiGroupMemberInfo(chatRh, groupInfo.groupId, member.groupMemberId) - val stats = r?.second - val (_, code) = if (member.memberActive) { - val memCode = chatModel.controller.apiGetGroupMemberCode(chatRh, groupInfo.apiId, member.groupMemberId) - member to memCode?.second - } else { - member to null - } - setGroupMembers(chatRh, groupInfo, chatModel) - ModalManager.end.closeModals() - ModalManager.end.showModalCloseable(true) { close -> - remember { derivedStateOf { chatModel.getGroupMember(member.groupMemberId) } }.value?.let { mem -> - GroupMemberInfoView(chatRh, groupInfo, mem, stats, code, chatModel, close, close) - } - } - } - }, - loadPrevMessages = { - if (chatModel.chatId.value != activeChat.value?.id) return@ChatLayout - val c = chatModel.getChat(chatModel.chatId.value ?: return@ChatLayout) - val firstId = chatModel.chatItems.firstOrNull()?.id - if (c != null && firstId != null) { - withApi { - Log.d(TAG, "TODOCHAT: loadPrevMessages: loading for ${c.id}, current chatId ${ChatModel.chatId.value}, size was ${ChatModel.chatItems.size}") - apiLoadPrevMessages(c, chatModel, firstId, searchText.value) - Log.d(TAG, "TODOCHAT: loadPrevMessages: loaded for ${c.id}, current chatId ${ChatModel.chatId.value}, size now ${ChatModel.chatItems.size}") - } - } - }, - deleteMessage = { itemId, mode -> - withApi { - val cInfo = chat.chatInfo - val toDeleteItem = chatModel.chatItems.firstOrNull { it.id == itemId } - val toModerate = toDeleteItem?.memberToModerate(chat.chatInfo) - val groupInfo = toModerate?.first - val groupMember = toModerate?.second - val deletedChatItem: ChatItem? - val toChatItem: ChatItem? - if (mode == CIDeleteMode.cidmBroadcast && groupInfo != null && groupMember != null) { - val r = chatModel.controller.apiDeleteMemberChatItem( - chatRh, - groupId = groupInfo.groupId, - groupMemberId = groupMember.groupMemberId, - itemId = itemId - ) - deletedChatItem = r?.first - toChatItem = r?.second - } else { - val r = chatModel.controller.apiDeleteChatItem( - chatRh, - type = cInfo.chatType, - id = cInfo.apiId, - itemId = itemId, - mode = mode - ) - deletedChatItem = r?.deletedChatItem?.chatItem - toChatItem = r?.toChatItem?.chatItem - } - if (toChatItem == null && deletedChatItem != null) { - chatModel.removeChatItem(chatRh, cInfo, deletedChatItem) - } else if (toChatItem != null) { - chatModel.upsertChatItem(chatRh, cInfo, toChatItem) - } - } - }, - deleteMessages = { itemIds -> - if (itemIds.isNotEmpty()) { - val chatInfo = chat.chatInfo - withBGApi { - val deletedItems: ArrayList = arrayListOf() - for (itemId in itemIds) { - val di = chatModel.controller.apiDeleteChatItem( - chatRh, chatInfo.chatType, chatInfo.apiId, itemId, CIDeleteMode.cidmInternal - )?.deletedChatItem?.chatItem - if (di != null) { - deletedItems.add(di) + ModalManager.end.showModalCloseable(true) { close -> + val chat = remember { activeChat }.value + if (chat?.chatInfo is ChatInfo.Direct) { + var contactInfo: Pair? by remember { mutableStateOf(preloadedContactInfo) } + var code: String? by remember { mutableStateOf(preloadedCode) } + KeyChangeEffect(chat.id, ChatModel.networkStatuses.toMap()) { + contactInfo = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId) + preloadedContactInfo = contactInfo + code = chatModel.controller.apiGetContactCode(chatRh, chat.chatInfo.apiId)?.second + preloadedCode = code + } + ChatInfoView(chatModel, (chat.chatInfo as ChatInfo.Direct).contact, contactInfo?.first, contactInfo?.second, chat.chatInfo.localAlias, code, close) + } else if (chat?.chatInfo is ChatInfo.Group) { + var link: Pair? by remember(chat.id) { mutableStateOf(preloadedLink) } + KeyChangeEffect(chat.id) { + setGroupMembers(chatRh, (chat.chatInfo as ChatInfo.Group).groupInfo, chatModel) + link = chatModel.controller.apiGetGroupLink(chatRh, chat.chatInfo.groupInfo.groupId) + preloadedLink = link + } + GroupChatInfoView(chatModel, chatRh, chat.id, link?.first, link?.second, { + link = it + preloadedLink = it + }, close) + } } } - for (di in deletedItems) { - chatModel.removeChatItem(chatRh, chatInfo, di) + }, + showMemberInfo = { groupInfo: GroupInfo, member: GroupMember -> + hideKeyboard(view) + withApi { + val r = chatModel.controller.apiGroupMemberInfo(chatRh, groupInfo.groupId, member.groupMemberId) + val stats = r?.second + val (_, code) = if (member.memberActive) { + val memCode = chatModel.controller.apiGetGroupMemberCode(chatRh, groupInfo.apiId, member.groupMemberId) + member to memCode?.second + } else { + member to null + } + setGroupMembers(chatRh, groupInfo, chatModel) + ModalManager.end.closeModals() + ModalManager.end.showModalCloseable(true) { close -> + remember { derivedStateOf { chatModel.getGroupMember(member.groupMemberId) } }.value?.let { mem -> + GroupMemberInfoView(chatRh, groupInfo, mem, stats, code, chatModel, close, close) + } + } } - } - } - }, - receiveFile = { fileId, encrypted -> - withApi { chatModel.controller.receiveFile(chatRh, user, fileId, encrypted) } - }, - cancelFile = { fileId -> - withApi { chatModel.controller.cancelFile(chatRh, user, fileId) } - }, - joinGroup = { groupId, onComplete -> - withApi { - chatModel.controller.apiJoinGroup(chatRh, groupId) - onComplete.invoke() - } - }, - startCall = out@ { media -> - withBGApi { - val cInfo = chat.chatInfo - if (cInfo is ChatInfo.Direct) { - chatModel.activeCall.value = Call(remoteHostId = chatRh, contact = cInfo.contact, callState = CallState.WaitCapabilities, localMedia = media) - chatModel.showCallView.value = true - chatModel.callCommand.add(WCallCommand.Capabilities(media)) - } - } - }, - endCall = { - val call = chatModel.activeCall.value - if (call != null) withApi { chatModel.callManager.endCall(call) } - }, - acceptCall = { contact -> - hideKeyboard(view) - val invitation = chatModel.callInvitations.remove(contact.id) - if (invitation == null) { - AlertManager.shared.showAlertMsg(generalGetString(MR.strings.call_already_ended)) - } else { - chatModel.callManager.acceptIncomingCall(invitation = invitation) - } - }, - acceptFeature = { contact, feature, param -> - withApi { - chatModel.controller.allowFeatureToContact(chatRh, contact, feature, param) - } - }, - openDirectChat = { contactId -> - withApi { - openDirectChat(chatRh, contactId, chatModel) - } - }, - updateContactStats = { contact -> - withApi { - val r = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId) - if (r != null) { - val contactStats = r.first - if (contactStats != null) - chatModel.updateContactConnectionStats(chatRh, contact, contactStats) - } - } - }, - updateMemberStats = { groupInfo, member -> - withApi { - val r = chatModel.controller.apiGroupMemberInfo(chatRh, groupInfo.groupId, member.groupMemberId) - if (r != null) { - val memStats = r.second - if (memStats != null) { - chatModel.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, memStats) + }, + loadPrevMessages = { + if (chatModel.chatId.value != activeChat.value?.id) return@ChatLayout + val c = chatModel.getChat(chatModel.chatId.value ?: return@ChatLayout) + val firstId = chatModel.chatItems.firstOrNull()?.id + if (c != null && firstId != null) { + withApi { + Log.d(TAG, "TODOCHAT: loadPrevMessages: loading for ${c.id}, current chatId ${ChatModel.chatId.value}, size was ${ChatModel.chatItems.size}") + apiLoadPrevMessages(c, chatModel, firstId, searchText.value) + Log.d(TAG, "TODOCHAT: loadPrevMessages: loaded for ${c.id}, current chatId ${ChatModel.chatId.value}, size now ${ChatModel.chatItems.size}") + } } - } - } - }, - syncContactConnection = { contact -> - withApi { - val cStats = chatModel.controller.apiSyncContactRatchet(chatRh, contact.contactId, force = false) - if (cStats != null) { - chatModel.updateContactConnectionStats(chatRh, contact, cStats) - } - } - }, - syncMemberConnection = { groupInfo, member -> - withApi { - val r = chatModel.controller.apiSyncGroupMemberRatchet(chatRh, groupInfo.apiId, member.groupMemberId, force = false) - if (r != null) { - chatModel.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, r.second) - } - } - }, - findModelChat = { chatId -> - chatModel.getChat(chatId) - }, - findModelMember = { memberId -> - chatModel.groupMembers.find { it.id == memberId } - }, - setReaction = { cInfo, cItem, add, reaction -> - withApi { - val updatedCI = chatModel.controller.apiChatItemReaction( - rh = chatRh, - type = cInfo.chatType, - id = cInfo.apiId, - itemId = cItem.id, - add = add, - reaction = reaction - ) - if (updatedCI != null) { - chatModel.updateChatItem(cInfo, updatedCI) - } - } - }, - showItemDetails = { cInfo, cItem -> - withApi { - val ciInfo = chatModel.controller.apiGetChatItemInfo(chatRh, cInfo.chatType, cInfo.apiId, cItem.id) - if (ciInfo != null) { - if (chat.chatInfo is ChatInfo.Group) { - setGroupMembers(chatRh, chat.chatInfo.groupInfo, chatModel) + }, + deleteMessage = { itemId, mode -> + withApi { + val cInfo = chat.chatInfo + val toDeleteItem = chatModel.chatItems.firstOrNull { it.id == itemId } + val toModerate = toDeleteItem?.memberToModerate(chat.chatInfo) + val groupInfo = toModerate?.first + val groupMember = toModerate?.second + val deletedChatItem: ChatItem? + val toChatItem: ChatItem? + if (mode == CIDeleteMode.cidmBroadcast && groupInfo != null && groupMember != null) { + val r = chatModel.controller.apiDeleteMemberChatItem( + chatRh, + groupId = groupInfo.groupId, + groupMemberId = groupMember.groupMemberId, + itemId = itemId + ) + deletedChatItem = r?.first + toChatItem = r?.second + } else { + val r = chatModel.controller.apiDeleteChatItem( + chatRh, + type = cInfo.chatType, + id = cInfo.apiId, + itemId = itemId, + mode = mode + ) + deletedChatItem = r?.deletedChatItem?.chatItem + toChatItem = r?.toChatItem?.chatItem + } + if (toChatItem == null && deletedChatItem != null) { + chatModel.removeChatItem(chatRh, cInfo, deletedChatItem) + } else if (toChatItem != null) { + chatModel.upsertChatItem(chatRh, cInfo, toChatItem) + } } - ModalManager.end.closeModals() - ModalManager.end.showModal(endButtons = { ShareButton { - clipboard.shareText(itemInfoShareText(chatModel, cItem, ciInfo, chatModel.controller.appPrefs.developerTools.get())) - } }) { - ChatItemInfoView(chatModel, cItem, ciInfo, devTools = chatModel.controller.appPrefs.developerTools.get()) + }, + deleteMessages = { itemIds -> + if (itemIds.isNotEmpty()) { + val chatInfo = chat.chatInfo + withBGApi { + val deletedItems: ArrayList = arrayListOf() + for (itemId in itemIds) { + val di = chatModel.controller.apiDeleteChatItem( + chatRh, chatInfo.chatType, chatInfo.apiId, itemId, CIDeleteMode.cidmInternal + )?.deletedChatItem?.chatItem + if (di != null) { + deletedItems.add(di) + } + } + for (di in deletedItems) { + chatModel.removeChatItem(chatRh, chatInfo, di) + } + } } - } - } - }, - addMembers = { groupInfo -> - hideKeyboard(view) - withApi { - setGroupMembers(chatRh, groupInfo, chatModel) + }, + receiveFile = { fileId, encrypted -> + withApi { chatModel.controller.receiveFile(chatRh, user, fileId, encrypted) } + }, + cancelFile = { fileId -> + withApi { chatModel.controller.cancelFile(chatRh, user, fileId) } + }, + joinGroup = { groupId, onComplete -> + withApi { + chatModel.controller.apiJoinGroup(chatRh, groupId) + onComplete.invoke() + } + }, + startCall = out@{ media -> + withBGApi { + val cInfo = chat.chatInfo + if (cInfo is ChatInfo.Direct) { + chatModel.activeCall.value = Call(remoteHostId = chatRh, contact = cInfo.contact, callState = CallState.WaitCapabilities, localMedia = media) + chatModel.showCallView.value = true + chatModel.callCommand.add(WCallCommand.Capabilities(media)) + } + } + }, + endCall = { + val call = chatModel.activeCall.value + if (call != null) withApi { chatModel.callManager.endCall(call) } + }, + acceptCall = { contact -> + hideKeyboard(view) + val invitation = chatModel.callInvitations.remove(contact.id) + if (invitation == null) { + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.call_already_ended)) + } else { + chatModel.callManager.acceptIncomingCall(invitation = invitation) + } + }, + acceptFeature = { contact, feature, param -> + withApi { + chatModel.controller.allowFeatureToContact(chatRh, contact, feature, param) + } + }, + openDirectChat = { contactId -> + withApi { + openDirectChat(chatRh, contactId, chatModel) + } + }, + updateContactStats = { contact -> + withApi { + val r = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId) + if (r != null) { + val contactStats = r.first + if (contactStats != null) + chatModel.updateContactConnectionStats(chatRh, contact, contactStats) + } + } + }, + updateMemberStats = { groupInfo, member -> + withApi { + val r = chatModel.controller.apiGroupMemberInfo(chatRh, groupInfo.groupId, member.groupMemberId) + if (r != null) { + val memStats = r.second + if (memStats != null) { + chatModel.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, memStats) + } + } + } + }, + syncContactConnection = { contact -> + withApi { + val cStats = chatModel.controller.apiSyncContactRatchet(chatRh, contact.contactId, force = false) + if (cStats != null) { + chatModel.updateContactConnectionStats(chatRh, contact, cStats) + } + } + }, + syncMemberConnection = { groupInfo, member -> + withApi { + val r = chatModel.controller.apiSyncGroupMemberRatchet(chatRh, groupInfo.apiId, member.groupMemberId, force = false) + if (r != null) { + chatModel.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, r.second) + } + } + }, + findModelChat = { chatId -> + chatModel.getChat(chatId) + }, + findModelMember = { memberId -> + chatModel.groupMembers.find { it.id == memberId } + }, + setReaction = { cInfo, cItem, add, reaction -> + withApi { + val updatedCI = chatModel.controller.apiChatItemReaction( + rh = chatRh, + type = cInfo.chatType, + id = cInfo.apiId, + itemId = cItem.id, + add = add, + reaction = reaction + ) + if (updatedCI != null) { + chatModel.updateChatItem(cInfo, updatedCI) + } + } + }, + showItemDetails = { cInfo, cItem -> + withApi { + val ciInfo = chatModel.controller.apiGetChatItemInfo(chatRh, cInfo.chatType, cInfo.apiId, cItem.id) + if (ciInfo != null) { + if (chat.chatInfo is ChatInfo.Group) { + setGroupMembers(chatRh, chat.chatInfo.groupInfo, chatModel) + } + ModalManager.end.closeModals() + ModalManager.end.showModal(endButtons = { + ShareButton { + clipboard.shareText(itemInfoShareText(chatModel, cItem, ciInfo, chatModel.controller.appPrefs.developerTools.get())) + } + }) { + ChatItemInfoView(chatModel, cItem, ciInfo, devTools = chatModel.controller.appPrefs.developerTools.get()) + } + } + } + }, + addMembers = { groupInfo -> + hideKeyboard(view) + withApi { + setGroupMembers(chatRh, groupInfo, chatModel) + ModalManager.end.closeModals() + ModalManager.end.showModalCloseable(true) { close -> + AddGroupMembersView(chatRh, groupInfo, false, chatModel, close) + } + } + }, + openGroupLink = { groupInfo -> + hideKeyboard(view) + withApi { + val link = chatModel.controller.apiGetGroupLink(chatRh, groupInfo.groupId) + ModalManager.end.closeModals() + ModalManager.end.showModalCloseable(true) { + GroupLinkView(chatModel, chatRh, groupInfo, link?.first, link?.second, onGroupLinkUpdated = null) + } + } + }, + markRead = { range, unreadCountAfter -> + chatModel.markChatItemsRead(chat, range, unreadCountAfter) + ntfManager.cancelNotificationsForChat(chat.id) + withBGApi { + chatModel.controller.apiChatRead( + chatRh, + chat.chatInfo.chatType, + chat.chatInfo.apiId, + range + ) + } + }, + changeNtfsState = { enabled, currentValue -> toggleNotifications(chat, enabled, chatModel, currentValue) }, + onSearchValueChanged = { value -> + if (searchText.value == value) return@ChatLayout + if (chatModel.chatId.value != activeChat.value?.id) return@ChatLayout + val c = chatModel.getChat(chatModel.chatId.value ?: return@ChatLayout) ?: return@ChatLayout + withApi { + apiFindMessages(c, chatModel, value) + searchText.value = value + } + }, + onComposed, + developerTools = chatModel.controller.appPrefs.developerTools.get(), + ) + } + is ChatInfo.ContactConnection -> { + val close = { chatModel.chatId.value = null } + ModalView(close, showClose = appPlatform.isAndroid, content = { + ContactConnectionInfoView(chatModel, chat.remoteHostId, chat.chatInfo.contactConnection.connReqInv, chat.chatInfo.contactConnection, false, close) + }) + LaunchedEffect(chat.id) { + onComposed(chat.id) ModalManager.end.closeModals() - ModalManager.end.showModalCloseable(true) { close -> - AddGroupMembersView(chatRh, groupInfo, false, chatModel, close) - } + chatModel.chatItems.clear() } - }, - openGroupLink = { groupInfo -> - hideKeyboard(view) - withApi { - val link = chatModel.controller.apiGetGroupLink(chatRh, groupInfo.groupId) + } + is ChatInfo.InvalidJSON -> { + val close = { chatModel.chatId.value = null } + ModalView(close, showClose = appPlatform.isAndroid, content = { + InvalidJSONView(chat.chatInfo.json) + }) + LaunchedEffect(chat.id) { + onComposed(chat.id) ModalManager.end.closeModals() - ModalManager.end.showModalCloseable(true) { - GroupLinkView(chatModel, chatRh, groupInfo, link?.first, link?.second, onGroupLinkUpdated = null) - } + chatModel.chatItems.clear() } - }, - markRead = { range, unreadCountAfter -> - chatModel.markChatItemsRead(chat, range, unreadCountAfter) - ntfManager.cancelNotificationsForChat(chat.id) - withBGApi { - chatModel.controller.apiChatRead( - chatRh, - chat.chatInfo.chatType, - chat.chatInfo.apiId, - range - ) - } - }, - changeNtfsState = { enabled, currentValue -> toggleNotifications(chat, enabled, chatModel, currentValue) }, - onSearchValueChanged = { value -> - if (searchText.value == value) return@ChatLayout - if (chatModel.chatId.value != activeChat.value?.id) return@ChatLayout - val c = chatModel.getChat(chatModel.chatId.value ?: return@ChatLayout) ?: return@ChatLayout - withApi { - apiFindMessages(c, chatModel, value) - searchText.value = value - } - }, - onComposed, - developerTools = chatModel.controller.appPrefs.developerTools.get(), - ) + } + else -> {} + } } } @@ -733,7 +762,7 @@ fun ChatInfoToolbar( } @Composable -fun ChatInfoToolbarTitle(cInfo: ChatInfo, imageSize: Dp = 40.dp, iconColor: Color = MaterialTheme.colors.secondaryVariant) { +fun ChatInfoToolbarTitle(cInfo: ChatInfo, imageSize: Dp = 40.dp, iconColor: Color = MaterialTheme.colors.secondaryVariant.mixWith(MaterialTheme.colors.onBackground, 0.97f)) { Row( horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ScanCodeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ScanCodeView.kt index bb479d8eb3..73017c3d42 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ScanCodeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ScanCodeView.kt @@ -13,29 +13,20 @@ import dev.icerock.moko.resources.compose.stringResource @Composable fun ScanCodeView(verifyCode: (String?, cb: (Boolean) -> Unit) -> Unit, close: () -> Unit) { Column( - Modifier - .fillMaxSize() - .padding(horizontal = DEFAULT_PADDING) + Modifier.fillMaxSize() ) { - AppBarTitle(stringResource(MR.strings.scan_code), withPadding = false) - Box( - Modifier - .fillMaxWidth() - .aspectRatio(ratio = 1F) - .padding(bottom = DEFAULT_PADDING) - ) { - QRCodeScanner { text -> - verifyCode(text) { - if (it) { - close() - } else { - AlertManager.shared.showAlertMsg( - title = generalGetString(MR.strings.incorrect_code) - ) - } + AppBarTitle(stringResource(MR.strings.scan_code)) + QRCodeScanner { text -> + verifyCode(text) { + if (it) { + close() + } else { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.incorrect_code) + ) } } } - Text(stringResource(MR.strings.scan_code_from_contacts_app)) + Text(stringResource(MR.strings.scan_code_from_contacts_app), Modifier.padding(horizontal = DEFAULT_PADDING)) } } 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 e1840dd885..57c469adf2 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 @@ -74,9 +74,7 @@ private fun VerifyCodeLayout( } } - SectionView { - QRCode(connectionCode, Modifier.aspectRatio(1f)) - } + QRCode(connectionCode, 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/chat/group/GroupLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt index 02ce90243c..dfb679c081 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 @@ -153,7 +153,7 @@ fun GroupLinkLayout( } initialLaunch = false } - SimpleXLinkQRCode(groupLink, Modifier.aspectRatio(1f).padding(horizontal = DEFAULT_PADDING)) + SimpleXLinkQRCode(groupLink) Row( horizontalArrangement = Arrangement.spacedBy(10.dp), verticalAlignment = Alignment.CenterVertically, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt index 00b236c7dd..c6654dc818 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt @@ -289,7 +289,7 @@ fun GroupMemberInfoLayout( if (member.contactLink != null) { SectionView(stringResource(MR.strings.address_section_title).uppercase()) { - SimpleXLinkQRCode(member.contactLink, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f)) + SimpleXLinkQRCode(member.contactLink) val clipboard = LocalClipboardManager.current ShareAddressButton { clipboard.shareText(simplexChatLink(member.contactLink)) } if (contactId != null) { @@ -506,7 +506,7 @@ fun connectViaMemberAddressAlert(rhId: Long?, connReqUri: String) { try { val uri = URI(connReqUri) withApi { - planAndConnect(chatModel, rhId, uri, incognito = null, close = { ModalManager.closeAllModalsEverywhere() }) + planAndConnect(rhId, uri, incognito = null, close = { ModalManager.closeAllModalsEverywhere() }) } } catch (e: RuntimeException) { AlertManager.shared.showAlertMsg( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatHelpView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatHelpView.kt index 866ad04b02..f36978cd3c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatHelpView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatHelpView.kt @@ -50,8 +50,8 @@ fun ChatHelpView(addContact: (() -> Unit)? = null) { ) Text(stringResource(MR.strings.above_then_preposition_continuation)) } - Text(annotatedStringResource(MR.strings.add_new_contact_to_create_one_time_QR_code), lineHeight = 22.sp) - Text(annotatedStringResource(MR.strings.scan_QR_code_to_connect_to_contact_who_shows_QR_code), lineHeight = 22.sp) + Text(annotatedStringResource(MR.strings.add_contact_button_to_create_link_or_connect_via_link), lineHeight = 22.sp) + Text(annotatedStringResource(MR.strings.create_group_button_to_create_new_group), lineHeight = 22.sp) } Column( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt index 8d5446aa53..84ad14ed77 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt @@ -24,17 +24,15 @@ import chat.simplex.common.ui.theme.* import chat.simplex.common.views.chat.* import chat.simplex.common.views.chat.group.deleteGroupDialog import chat.simplex.common.views.chat.group.leaveGroupDialog -import chat.simplex.common.views.chat.item.InvalidJSONView import chat.simplex.common.views.chat.item.ItemAction import chat.simplex.common.views.helpers.* import chat.simplex.common.views.newchat.* import chat.simplex.res.MR import kotlinx.coroutines.delay import kotlinx.datetime.Clock -import java.net.URI @Composable -fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) { +fun ChatListNavLinkView(chat: Chat, nextChatSelected: State) { val showMenu = remember { mutableStateOf(false) } val showMarkRead = remember(chat.chatStats.unreadCount, chat.chatStats.unreadChat) { chat.chatStats.unreadCount > 0 || chat.chatStats.unreadChat @@ -45,7 +43,7 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) { showMenu.value = false delay(500L) } - val selectedChat = remember(chat.id) { derivedStateOf { chat.id == ChatModel.chatId.value } } + val selectedChat = remember(chat.id) { derivedStateOf { chat.id == chatModel.chatId.value } } val showChatPreviews = chatModel.showChatPreviews.value val inProgress = remember { mutableStateOf(false) } var progressByTimeout by rememberSaveable { mutableStateOf(false) } @@ -75,7 +73,8 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) { }, showMenu, stopped, - selectedChat + selectedChat, + nextChatSelected, ) } is ChatInfo.Group -> @@ -93,7 +92,8 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) { }, showMenu, stopped, - selectedChat + selectedChat, + nextChatSelected, ) is ChatInfo.ContactRequest -> ChatListNavLinkLayout( @@ -110,7 +110,8 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) { }, showMenu, stopped, - selectedChat + selectedChat, + nextChatSelected, ) is ChatInfo.ContactConnection -> ChatListNavLinkLayout( @@ -120,11 +121,7 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) { } }, click = { - ModalManager.center.closeModals() - ModalManager.end.closeModals() - ModalManager.center.showModalCloseable(true, showClose = appPlatform.isAndroid) { close -> - ContactConnectionInfoView(chatModel, chat.remoteHostId, chat.chatInfo.contactConnection.connReqInv, chat.chatInfo.contactConnection, false, close) - } + chatModel.chatId.value = chat.id }, dropdownMenuItems = { tryOrShowError("${chat.id}ChatListNavLinkDropdown", error = {}) { @@ -133,7 +130,8 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) { }, showMenu, stopped, - selectedChat + selectedChat, + nextChatSelected, ) is ChatInfo.InvalidJSON -> ChatListNavLinkLayout( @@ -143,13 +141,13 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) { } }, click = { - ModalManager.end.closeModals() - ModalManager.center.showModal(true) { InvalidJSONView(chat.chatInfo.json) } + chatModel.chatId.value = chat.id }, dropdownMenuItems = null, showMenu, stopped, - selectedChat + selectedChat, + nextChatSelected, ) } } @@ -476,10 +474,7 @@ fun ContactConnectionMenuItems(rhId: Long?, chatInfo: ChatInfo.ContactConnection painterResource(MR.images.ic_delete), onClick = { deleteContactConnectionAlert(rhId, chatInfo.contactConnection, chatModel) { - if (chatModel.chatId.value == null) { - ModalManager.center.closeModals() - ModalManager.end.closeModals() - } + chatModel.dismissConnReqView(chatInfo.contactConnection.id) } showMenu.value = false }, @@ -804,7 +799,8 @@ expect fun ChatListNavLinkLayout( dropdownMenuItems: (@Composable () -> Unit)?, showMenu: MutableState, stopped: Boolean, - selectedChat: State + selectedChat: State, + nextChatSelected: State, ) @Preview/*( @@ -846,7 +842,8 @@ fun PreviewChatListNavLinkDirect() { dropdownMenuItems = null, showMenu = remember { mutableStateOf(false) }, stopped = false, - selectedChat = remember { mutableStateOf(false) } + selectedChat = remember { mutableStateOf(false) }, + nextChatSelected = remember { mutableStateOf(false) } ) } } @@ -890,7 +887,8 @@ fun PreviewChatListNavLinkGroup() { dropdownMenuItems = null, showMenu = remember { mutableStateOf(false) }, stopped = false, - selectedChat = remember { mutableStateOf(false) } + selectedChat = remember { mutableStateOf(false) }, + nextChatSelected = remember { mutableStateOf(false) } ) } } @@ -911,7 +909,8 @@ fun PreviewChatListNavLinkContactRequest() { dropdownMenuItems = null, showMenu = remember { mutableStateOf(false) }, stopped = false, - selectedChat = remember { mutableStateOf(false) } + selectedChat = remember { mutableStateOf(false) }, + nextChatSelected = remember { mutableStateOf(false) } ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index cf12727d74..4280f51368 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -10,11 +10,15 @@ import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.* import androidx.compose.ui.graphics.* import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.platform.* +import androidx.compose.ui.text.TextRange import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.* import chat.simplex.common.SettingsViewState import chat.simplex.common.model.* @@ -29,6 +33,7 @@ import chat.simplex.common.views.newchat.* import chat.simplex.res.MR import kotlinx.coroutines.* import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.distinctUntilChanged import java.net.URI @Composable @@ -60,10 +65,10 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf } } val endPadding = if (appPlatform.isDesktop) 56.dp else 0.dp - var searchInList by rememberSaveable { mutableStateOf("") } + val searchText = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) } val scope = rememberCoroutineScope() val (userPickerState, scaffoldState ) = settingsState - Scaffold(topBar = { Box(Modifier.padding(end = endPadding)) { ChatListToolbar(chatModel, scaffoldState.drawerState, userPickerState, stopped) { searchInList = it.trim() } } }, + Scaffold(topBar = { Box(Modifier.padding(end = endPadding)) { ChatListToolbar(searchText, scaffoldState.drawerState, userPickerState, stopped)} }, scaffoldState = scaffoldState, drawerContent = { tryOrShowError("Settings", error = { ErrorSettingsView() }) { @@ -73,7 +78,7 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf drawerScrimColor = MaterialTheme.colors.onSurface.copy(alpha = if (isInDarkTheme()) 0.16f else 0.32f), drawerGesturesEnabled = appPlatform.isAndroid, floatingActionButton = { - if (searchInList.isEmpty() && !chatModel.desktopNoUserNoRemote) { + if (searchText.value.text.isEmpty() && !chatModel.desktopNoUserNoRemote && chatModel.chatRunning.value == true) { FloatingActionButton( onClick = { if (!stopped) { @@ -101,19 +106,20 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf .fillMaxSize() ) { if (chatModel.chats.isNotEmpty()) { - ChatList(chatModel, search = searchInList) + ChatList(chatModel, searchText = searchText) } else if (!chatModel.switchingUsersAndHosts.value && !chatModel.desktopNoUserNoRemote) { Box(Modifier.fillMaxSize()) { - if (!stopped && !newChatSheetState.collectAsState().value.isVisible()) { + if (!stopped && !newChatSheetState.collectAsState().value.isVisible() && chatModel.chatRunning.value == true) { OnboardingButtons(showNewChatSheet) } - Text(stringResource(MR.strings.you_have_no_chats), Modifier.align(Alignment.Center), color = MaterialTheme.colors.secondary) + Text(stringResource( + if (chatModel.chatRunning.value == null) MR.strings.loading_chats else MR.strings.you_have_no_chats), Modifier.align(Alignment.Center), color = MaterialTheme.colors.secondary) } } } } } - if (searchInList.isEmpty()) { + if (searchText.value.text.isEmpty()) { DesktopActiveCallOverlayLayout(newChatSheetState) // TODO disable this button and sheet for the duration of the switch tryOrShowError("NewChatSheet", error = {}) { @@ -168,20 +174,8 @@ private fun ConnectButton(text: String, onClick: () -> Unit) { } @Composable -private fun ChatListToolbar(chatModel: ChatModel, drawerState: DrawerState, userPickerState: MutableStateFlow, stopped: Boolean, onSearchValueChanged: (String) -> Unit) { - var showSearch by rememberSaveable { mutableStateOf(false) } - val hideSearchOnBack = { onSearchValueChanged(""); showSearch = false } - if (showSearch) { - BackHandler(onBack = hideSearchOnBack) - } +private fun ChatListToolbar(searchInList: State, drawerState: DrawerState, userPickerState: MutableStateFlow, stopped: Boolean) { val barButtons = arrayListOf<@Composable RowScope.() -> Unit>() - if (chatModel.chats.size > 0) { - barButtons.add { - IconButton({ showSearch = true }) { - Icon(painterResource(MR.images.ic_search_500), stringResource(MR.strings.search_verb), tint = MaterialTheme.colors.primary) - } - } - } if (stopped) { barButtons.add { IconButton(onClick = { @@ -201,9 +195,7 @@ private fun ChatListToolbar(chatModel: ChatModel, drawerState: DrawerState, user val scope = rememberCoroutineScope() DefaultTopAppBar( navigationButton = { - if (showSearch) { - NavigationButtonBack(hideSearchOnBack) - } else if (chatModel.users.isEmpty() && !chatModel.desktopNoUserNoRemote) { + if (chatModel.users.isEmpty() && !chatModel.desktopNoUserNoRemote) { NavigationButtonMenu { scope.launch { if (drawerState.isOpen) drawerState.close() else drawerState.open() } } } else { val users by remember { derivedStateOf { chatModel.users.filter { u -> u.user.activeUser || !u.user.hidden } } } @@ -227,13 +219,18 @@ private fun ChatListToolbar(chatModel: ChatModel, drawerState: DrawerState, user fontWeight = FontWeight.SemiBold, ) if (chatModel.chats.size > 0) { - ToggleFilterButton() + val enabled = remember { derivedStateOf { searchInList.value.text.isEmpty() } } + if (enabled.value) { + ToggleFilterEnabledButton() + } else { + ToggleFilterDisabledButton() + } } } }, onTitleClick = null, - showSearch = showSearch, - onSearchValueChanged = onSearchValueChanged, + showSearch = false, + onSearchValueChanged = {}, buttons = barButtons ) Divider(Modifier.padding(top = AppBarHeight)) @@ -246,7 +243,8 @@ fun UserProfileButton(image: String?, allRead: Boolean, onButtonClicked: () -> U Box { ProfileImage( image = image, - size = 37.dp + size = 37.dp, + color = MaterialTheme.colors.secondaryVariant.mixWith(MaterialTheme.colors.onBackground, 0.97f) ) if (!allRead) { unreadBadge() @@ -281,7 +279,7 @@ private fun BoxScope.unreadBadge(text: String? = "") { } @Composable -private fun ToggleFilterButton() { +private fun ToggleFilterEnabledButton() { val pref = remember { ChatController.appPrefs.showUnreadAndFavorites } IconButton(onClick = { pref.set(!pref.get()) }) { Icon( @@ -290,7 +288,7 @@ private fun ToggleFilterButton() { tint = if (pref.state.value) MaterialTheme.colors.background else MaterialTheme.colors.primary, modifier = Modifier .padding(3.dp) - .background(color = if (pref.state.value) MaterialTheme.colors.primary else MaterialTheme.colors.background, shape = RoundedCornerShape(50)) + .background(color = if (pref.state.value) MaterialTheme.colors.primary else Color.Unspecified, shape = RoundedCornerShape(50)) .border(width = 1.dp, color = MaterialTheme.colors.primary, shape = RoundedCornerShape(50)) .padding(3.dp) .size(16.dp) @@ -298,6 +296,22 @@ private fun ToggleFilterButton() { } } +@Composable +private fun ToggleFilterDisabledButton() { + IconButton({}, enabled = false) { + Icon( + painterResource(MR.images.ic_filter_list), + null, + tint = MaterialTheme.colors.secondary, + modifier = Modifier + .padding(3.dp) + .border(width = 1.dp, color = MaterialTheme.colors.secondary, shape = RoundedCornerShape(50)) + .padding(3.dp) + .size(16.dp) + ) + } +} + @Composable expect fun DesktopActiveCallOverlayLayout(newChatSheetState: MutableStateFlow) @@ -307,11 +321,115 @@ fun connectIfOpenedViaUri(rhId: Long?, uri: URI, chatModel: ChatModel) { chatModel.appOpenUrl.value = rhId to uri } else { withApi { - planAndConnect(chatModel, rhId, uri, incognito = null, close = null) + planAndConnect(rhId, uri, incognito = null, close = null) } } } +@Composable +private fun ChatListSearchBar(listState: LazyListState, searchText: MutableState, searchShowingSimplexLink: MutableState, searchChatFilteredBySimplexLink: MutableState) { + Row(verticalAlignment = Alignment.CenterVertically) { + val focusRequester = remember { FocusRequester() } + var focused by remember { mutableStateOf(false) } + Icon(painterResource(MR.images.ic_search), null, Modifier.padding(horizontal = DEFAULT_PADDING_HALF), tint = MaterialTheme.colors.secondary) + SearchTextField( + Modifier.weight(1f).onFocusChanged { focused = it.hasFocus }.focusRequester(focusRequester), + placeholder = stringResource(MR.strings.search_or_paste_simplex_link), + alwaysVisible = true, + searchText = searchText, + enabled = !remember { searchShowingSimplexLink }.value, + trailingContent = null, + ) { + searchText.value = searchText.value.copy(it) + } + val hasText = remember { derivedStateOf { searchText.value.text.isNotEmpty() } } + if (hasText.value) { + val hideSearchOnBack: () -> Unit = { searchText.value = TextFieldValue() } + BackHandler(onBack = hideSearchOnBack) + KeyChangeEffect(chatModel.currentRemoteHost.value) { + hideSearchOnBack() + } + } else { + Row { + val padding = if (appPlatform.isDesktop) 0.dp else 7.dp + val clipboard = LocalClipboardManager.current + val clipboardHasText = remember(focused) { chatModel.clipboardHasText }.value + if (clipboardHasText) { + IconButton( + onClick = { searchText.value = searchText.value.copy(clipboard.getText()?.text ?: return@IconButton) }, + Modifier.size(30.dp).desktopPointerHoverIconHand() + ) { + Icon(painterResource(MR.images.ic_article), null, tint = MaterialTheme.colors.secondary) + } + } + Spacer(Modifier.width(padding)) + IconButton( + onClick = { + val fixedRhId = chatModel.currentRemoteHost.value + ModalManager.center.closeModals() + ModalManager.center.showModalCloseable { close -> + NewChatView(fixedRhId, selection = NewChatOption.CONNECT, showQRCodeScanner = true, close = close) + } + }, + Modifier.size(30.dp).desktopPointerHoverIconHand() + ) { + Icon(painterResource(MR.images.ic_qr_code), null, tint = MaterialTheme.colors.secondary) + } + Spacer(Modifier.width(padding)) + } + } + val focusManager = LocalFocusManager.current + val keyboardState = getKeyboardState() + LaunchedEffect(keyboardState.value) { + if (keyboardState.value == KeyboardState.Closed && focused) { + focusManager.clearFocus() + } + } + val view = LocalMultiplatformView() + LaunchedEffect(Unit) { + snapshotFlow { searchText.value.text } + .distinctUntilChanged() + .collect { + val link = strHasSingleSimplexLink(it.trim()) + if (link != null) { + // if SimpleX link is pasted, show connection dialogue + hideKeyboard(view) + if (link.format is Format.SimplexLink) { + val linkText = link.simplexLinkText(link.format.linkType, link.format.smpHosts) + searchText.value = searchText.value.copy(linkText, selection = TextRange.Zero) + } + searchShowingSimplexLink.value = true + searchChatFilteredBySimplexLink.value = null + connect(link.text, searchChatFilteredBySimplexLink) { searchText.value = TextFieldValue() } + } else if (!searchShowingSimplexLink.value || it.isEmpty()) { + if (it.isNotEmpty()) { + // if some other text is pasted, enter search mode + focusRequester.requestFocus() + } else if (listState.layoutInfo.totalItemsCount > 0) { + listState.scrollToItem(0) + } + searchShowingSimplexLink.value = false + searchChatFilteredBySimplexLink.value = null + } + } + } + } +} + +private fun connect(link: String, searchChatFilteredBySimplexLink: MutableState, cleanup: (() -> Unit)?) { + withBGApi { + planAndConnect( + chatModel.remoteHostId(), + URI.create(link), + incognito = null, + filterKnownContact = { searchChatFilteredBySimplexLink.value = it.id }, + filterKnownGroup = { searchChatFilteredBySimplexLink.value = it.id }, + close = null, + cleanup = cleanup, + ) + } +} + @Composable private fun ErrorSettingsView() { Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { @@ -322,7 +440,7 @@ private fun ErrorSettingsView() { private var lazyListState = 0 to 0 @Composable -private fun ChatList(chatModel: ChatModel, search: String) { +private fun ChatList(chatModel: ChatModel, searchText: MutableState) { val listState = rememberLazyListState(lazyListState.first, lazyListState.second) DisposableEffect(Unit) { onDispose { lazyListState = listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset } @@ -332,13 +450,35 @@ private fun ChatList(chatModel: ChatModel, search: String) { // In some not always reproducible situations this code produce IndexOutOfBoundsException on Compose's side // which is related to [derivedStateOf]. Using safe alternative instead // val chats by remember(search, showUnreadAndFavorites) { derivedStateOf { filteredChats(showUnreadAndFavorites, search, allChats.toList()) } } - val chats = filteredChats(showUnreadAndFavorites, search, allChats.toList()) + val searchShowingSimplexLink = remember { mutableStateOf(false) } + val searchChatFilteredBySimplexLink = remember { mutableStateOf(null) } + val chats = filteredChats(showUnreadAndFavorites, searchShowingSimplexLink, searchChatFilteredBySimplexLink, searchText.value.text, allChats.toList()) LazyColumn( - modifier = Modifier.fillMaxWidth(), + Modifier.fillMaxWidth(), listState ) { - items(chats) { chat -> - ChatListNavLinkView(chat, chatModel) + stickyHeader { + Column( + Modifier + .offset { + val y = if (searchText.value.text.isEmpty()) { + if (listState.firstVisibleItemIndex == 0) -listState.firstVisibleItemScrollOffset else -1000 + } else { + 0 + } + IntOffset(0, y) + } + .background(MaterialTheme.colors.background) + ) { + ChatListSearchBar(listState, searchText, searchShowingSimplexLink, searchChatFilteredBySimplexLink) + Divider() + } + } + itemsIndexed(chats) { index, chat -> + val nextChatSelected = remember(chat.id, chats) { derivedStateOf { + chatModel.chatId.value != null && chats.getOrNull(index + 1)?.id == chatModel.chatId.value + } } + ChatListNavLinkView(chat, nextChatSelected) } } if (chats.isEmpty() && !chatModel.chats.isEmpty()) { @@ -348,28 +488,39 @@ private fun ChatList(chatModel: ChatModel, search: String) { } } -private fun filteredChats(showUnreadAndFavorites: Boolean, searchText: String, chats: List): List { - val s = searchText.trim().lowercase() - return if (s.isEmpty() && !showUnreadAndFavorites) - chats - else { - chats.filter { chat -> - when (val cInfo = chat.chatInfo) { - is ChatInfo.Direct -> if (s.isEmpty()) { - filtered(chat) - } else { - (viewNameContains(cInfo, s) || - cInfo.contact.profile.displayName.lowercase().contains(s) || - cInfo.contact.fullName.lowercase().contains(s)) +private fun filteredChats( + showUnreadAndFavorites: Boolean, + searchShowingSimplexLink: State, + searchChatFilteredBySimplexLink: State, + searchText: String, + chats: List +): List { + val linkChatId = searchChatFilteredBySimplexLink.value + return if (linkChatId != null) { + chats.filter { it.id == linkChatId } + } else { + val s = if (searchShowingSimplexLink.value) "" else searchText.trim().lowercase() + if (s.isEmpty() && !showUnreadAndFavorites) + chats + else { + chats.filter { chat -> + when (val cInfo = chat.chatInfo) { + is ChatInfo.Direct -> if (s.isEmpty()) { + chat.id == chatModel.chatId.value || filtered(chat) + } else { + (viewNameContains(cInfo, s) || + cInfo.contact.profile.displayName.lowercase().contains(s) || + cInfo.contact.fullName.lowercase().contains(s)) + } + is ChatInfo.Group -> if (s.isEmpty()) { + chat.id == chatModel.chatId.value || filtered(chat) || cInfo.groupInfo.membership.memberStatus == GroupMemberStatus.MemInvited + } else { + viewNameContains(cInfo, s) + } + is ChatInfo.ContactRequest -> s.isEmpty() || viewNameContains(cInfo, s) + is ChatInfo.ContactConnection -> (s.isNotEmpty() && cInfo.contactConnection.localAlias.lowercase().contains(s)) || (s.isEmpty() && chat.id == chatModel.chatId.value) + is ChatInfo.InvalidJSON -> chat.id == chatModel.chatId.value } - is ChatInfo.Group -> if (s.isEmpty()) { - (filtered(chat) || cInfo.groupInfo.membership.memberStatus == GroupMemberStatus.MemInvited) - } else { - viewNameContains(cInfo, s) - } - is ChatInfo.ContactRequest -> s.isEmpty() || viewNameContains(cInfo, s) - is ChatInfo.ContactConnection -> s.isNotEmpty() && cInfo.contactConnection.localAlias.lowercase().contains(s) - is ChatInfo.InvalidJSON -> false } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AlertManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AlertManager.kt index 177efbfddc..f518f1c964 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AlertManager.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AlertManager.kt @@ -62,7 +62,28 @@ class AlertManager { fun showAlertDialogButtonsColumn( title: String, - text: AnnotatedString? = null, + text: String? = null, + onDismissRequest: (() -> Unit)? = null, + hostDevice: Pair? = null, + buttons: @Composable () -> Unit, + ) { + showAlert { + AlertDialog( + onDismissRequest = { onDismissRequest?.invoke(); hideAlert() }, + title = alertTitle(title), + buttons = { + AlertContent(text, hostDevice, extraPadding = true) { + buttons() + } + }, + shape = RoundedCornerShape(corner = CornerSize(25.dp)) + ) + } + } + + fun showAlertDialogButtonsColumn( + title: String, + text: AnnotatedString, onDismissRequest: (() -> Unit)? = null, hostDevice: Pair? = null, buttons: @Composable () -> Unit, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultProgressBar.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultProgressBar.kt new file mode 100644 index 0000000000..ec2500ab2e --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultProgressBar.kt @@ -0,0 +1,27 @@ +package chat.simplex.common.views.helpers + +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import chat.simplex.common.ui.theme.DEFAULT_PADDING + +@Composable +fun DefaultProgressView(description: String?) { + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + CircularProgressIndicator( + Modifier + .padding(bottom = DEFAULT_PADDING) + .size(30.dp), + color = MaterialTheme.colors.secondary, + strokeWidth = 2.5.dp + ) + if (description != null) { + Text(description) + } + } + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt index 0162ac7e78..93be24d921 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt @@ -36,7 +36,7 @@ fun DefaultTopAppBar( SearchTextField(Modifier.fillMaxWidth(), alwaysVisible = false, onValueChange = onSearchValueChanged) } }, - backgroundColor = if (isInDarkTheme()) ToolbarDark else ToolbarLight, + backgroundColor = MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.97f), navigationIcon = navigationButton, buttons = if (!showSearch) buttons else emptyList(), centered = !showSearch, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt index 703b6f905f..7e9cefa31c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt @@ -38,6 +38,12 @@ enum class ModalPlacement { START, CENTER, END, FULLSCREEN } +class ModalData { + private val state = mutableMapOf>() + fun stateGetOrPut (key: String, default: () -> T): MutableState = + state.getOrPut(key) { mutableStateOf(default() as Any) } as MutableState +} + class ModalManager(private val placement: ModalPlacement? = null) { private val modalViews = arrayListOf Unit) -> Unit)>>() private val modalCount = mutableStateOf(0) @@ -45,15 +51,17 @@ class ModalManager(private val placement: ModalPlacement? = null) { private var oldViewChanging = AtomicBoolean(false) private var passcodeView: MutableState<(@Composable (close: () -> Unit) -> Unit)?> = mutableStateOf(null) - fun showModal(settings: Boolean = false, showClose: Boolean = true, endButtons: @Composable RowScope.() -> Unit = {}, content: @Composable () -> Unit) { + fun showModal(settings: Boolean = false, showClose: Boolean = true, endButtons: @Composable RowScope.() -> Unit = {}, content: @Composable ModalData.() -> Unit) { + val data = ModalData() showCustomModal { close -> - ModalView(close, showClose = showClose, endButtons = endButtons, content = content) + ModalView(close, showClose = showClose, endButtons = endButtons, content = { data.content() }) } } - fun showModalCloseable(settings: Boolean = false, showClose: Boolean = true, content: @Composable (close: () -> Unit) -> Unit) { + fun showModalCloseable(settings: Boolean = false, showClose: Boolean = true, content: @Composable ModalData.(close: () -> Unit) -> Unit) { + val data = ModalData() showCustomModal { close -> - ModalView(close, showClose = showClose, content = { content(close) }) + ModalView(close, showClose = showClose, content = { data.content(close) }) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SearchTextField.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SearchTextField.kt index 4b6c70df44..23fac21e79 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SearchTextField.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SearchTextField.kt @@ -34,6 +34,8 @@ fun SearchTextField( alwaysVisible: Boolean, searchText: MutableState = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) }, placeholder: String = stringResource(MR.strings.search_verb), + enabled: Boolean = true, + trailingContent: @Composable (() -> Unit)? = null, onValueChange: (String) -> Unit ) { val focusRequester = remember { FocusRequester() } @@ -54,12 +56,12 @@ fun SearchTextField( } } - val enabled = true val colors = TextFieldDefaults.textFieldColors( backgroundColor = Color.Unspecified, textColor = MaterialTheme.colors.onBackground, focusedIndicatorColor = Color.Unspecified, unfocusedIndicatorColor = Color.Unspecified, + disabledIndicatorColor = Color.Unspecified, ) val shape = MaterialTheme.shapes.small.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize) val interactionSource = remember { MutableInteractionSource() } @@ -77,6 +79,7 @@ fun SearchTextField( searchText.value = it onValueChange(it.text) }, + enabled = rememberUpdatedState(enabled).value, cursorBrush = SolidColor(colors.cursorColor(false).value), visualTransformation = VisualTransformation.None, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), @@ -105,7 +108,7 @@ fun SearchTextField( }) { Icon(painterResource(MR.images.ic_close), stringResource(MR.strings.icon_descr_close_button), tint = MaterialTheme.colors.primary,) } - }} else null, + }} else trailingContent, singleLine = true, enabled = enabled, interactionSource = interactionSource, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt index 9a81b9f9d7..cae9523e1b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt @@ -60,6 +60,9 @@ fun annotatedStringResource(id: StringResource, vararg args: Any?): AnnotatedStr } } +@Composable +expect fun SetupClipboardListener() + // maximum image file size to be auto-accepted const val MAX_IMAGE_SIZE: Long = 261_120 // 255KB const val MAX_IMAGE_SIZE_AUTO_RCV: Long = MAX_IMAGE_SIZE * 2 diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactLearnMore.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactLearnMore.kt index 2913f6ac79..c2523c9b2e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactLearnMore.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactLearnMore.kt @@ -4,14 +4,16 @@ import androidx.compose.foundation.* import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import chat.simplex.common.platform.chatModel import dev.icerock.moko.resources.compose.stringResource import chat.simplex.common.views.helpers.AppBarTitle +import chat.simplex.common.views.helpers.KeyChangeEffect import chat.simplex.common.views.onboarding.ReadableText import chat.simplex.common.views.onboarding.ReadableTextWithLink import chat.simplex.res.MR @Composable -fun AddContactLearnMore() { +fun AddContactLearnMore(close: () -> Unit) { Column( Modifier.verticalScroll(rememberScrollState()), ) { @@ -20,4 +22,7 @@ fun AddContactLearnMore() { ReadableText(MR.strings.if_you_cant_meet_in_person) ReadableTextWithLink(MR.strings.read_more_in_user_guide_with_link, "https://simplex.chat/docs/guide/readme.html#connect-to-friends") } + KeyChangeEffect(chatModel.chatId.value) { + close() + } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactView.kt deleted file mode 100644 index 84080d5b97..0000000000 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactView.kt +++ /dev/null @@ -1,186 +0,0 @@ -package chat.simplex.common.views.newchat - -import SectionBottomSpacer -import SectionTextFooter -import SectionView -import androidx.compose.desktop.ui.tooling.preview.Preview -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.* -import dev.icerock.moko.resources.compose.painterResource -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalClipboardManager -import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.unit.dp -import chat.simplex.common.model.* -import chat.simplex.common.platform.shareText -import chat.simplex.common.ui.theme.* -import chat.simplex.common.views.helpers.* -import chat.simplex.common.views.usersettings.* -import chat.simplex.res.MR - -@Composable -fun AddContactView( - chatModel: ChatModel, - rh: RemoteHostInfo?, - connReqInvitation: String, - contactConnection: MutableState -) { - val clipboard = LocalClipboardManager.current - AddContactLayout( - rh = rh, - chatModel = chatModel, - incognitoPref = chatModel.controller.appPrefs.incognito, - connReq = connReqInvitation, - contactConnection = contactConnection, - learnMore = { - ModalManager.center.showModal { - Column( - Modifier - .fillMaxHeight() - .padding(horizontal = DEFAULT_PADDING), - verticalArrangement = Arrangement.SpaceBetween - ) { - AddContactLearnMore() - } - } - } - ) -} - -@Composable -fun AddContactLayout( - chatModel: ChatModel, - rh: RemoteHostInfo?, - incognitoPref: SharedPreference, - connReq: String, - contactConnection: MutableState, - learnMore: () -> Unit -) { - val incognito = remember { mutableStateOf(incognitoPref.get()) } - - LaunchedEffect(incognito.value) { - withApi { - val contactConnVal = contactConnection.value - if (contactConnVal != null) { - chatModel.controller.apiSetConnectionIncognito(rh?.remoteHostId, contactConnVal.pccConnId, incognito.value)?.let { - contactConnection.value = it - chatModel.updateContactConnection(rh?.remoteHostId, it) - } - } - } - } - - Column( - Modifier - .verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.SpaceBetween, - ) { - AppBarTitle(stringResource(MR.strings.add_contact), hostDevice(rh?.remoteHostId)) - - SectionView(stringResource(MR.strings.one_time_link_short).uppercase()) { - if (connReq.isNotEmpty()) { - SimpleXLinkQRCode( - connReq, Modifier - .padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF) - .aspectRatio(1f) - ) - } else { - CircularProgressIndicator( - Modifier - .size(36.dp) - .padding(4.dp) - .align(Alignment.CenterHorizontally), - color = MaterialTheme.colors.secondary, - strokeWidth = 3.dp - ) - } - - IncognitoToggle(incognitoPref, incognito) { ModalManager.start.showModal { IncognitoView() } } - ShareLinkButton(connReq) - OneTimeLinkLearnMoreButton(learnMore) - } - SectionTextFooter(sharedProfileInfo(chatModel, incognito.value)) - - SectionBottomSpacer() - } -} - -@Composable -fun ShareLinkButton(connReqInvitation: String) { - val clipboard = LocalClipboardManager.current - SettingsActionItem( - painterResource(MR.images.ic_share), - stringResource(MR.strings.share_invitation_link), - click = { clipboard.shareText(simplexChatLink(connReqInvitation)) }, - iconColor = MaterialTheme.colors.primary, - textColor = MaterialTheme.colors.primary, - ) -} - -@Composable -fun OneTimeLinkLearnMoreButton(onClick: () -> Unit) { - SettingsActionItem( - painterResource(MR.images.ic_info), - stringResource(MR.strings.learn_more), - onClick, - ) -} - -@Composable -fun IncognitoToggle( - incognitoPref: SharedPreference, - incognito: MutableState, - onClickInfo: () -> Unit -) { - SettingsActionItemWithContent( - icon = if (incognito.value) painterResource(MR.images.ic_theater_comedy_filled) else painterResource(MR.images.ic_theater_comedy), - text = null, - click = onClickInfo, - iconColor = if (incognito.value) Indigo else MaterialTheme.colors.secondary, - extraPadding = false - ) { - SharedPreferenceToggleWithIcon( - stringResource(MR.strings.incognito), - painterResource(MR.images.ic_info), - stopped = false, - onClickInfo = onClickInfo, - preference = incognitoPref, - preferenceState = incognito - ) - } -} - -fun sharedProfileInfo( - chatModel: ChatModel, - incognito: Boolean -): String { - val name = chatModel.currentUser.value?.displayName ?: "" - return if (incognito) { - generalGetString(MR.strings.connect__a_new_random_profile_will_be_shared) - } else { - String.format(generalGetString(MR.strings.connect__your_profile_will_be_shared), name) - } -} - -@Preview/*( - uiMode = Configuration.UI_MODE_NIGHT_YES, - showBackground = true, - name = "Dark Mode" -)*/ -@Composable -fun PreviewAddContactView() { - SimpleXTheme { - AddContactLayout( - rh = null, - chatModel = ChatModel, - incognitoPref = SharedPreference({ false }, {}), - connReq = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", - contactConnection = mutableStateOf(PendingContactConnection.getSampleData()), - learnMore = {}, - ) - } -} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt similarity index 71% rename from apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt index 9f28074aef..adbacca6bc 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt @@ -1,57 +1,51 @@ package chat.simplex.common.views.newchat -import SectionBottomSpacer import SectionItemView -import SectionTextFooter -import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.material.* -import androidx.compose.runtime.* import androidx.compose.ui.Modifier -import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextAlign import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.unit.dp import chat.simplex.common.model.* import chat.simplex.common.platform.* -import chat.simplex.common.ui.theme.* import chat.simplex.common.views.chatlist.* import chat.simplex.common.views.helpers.* -import chat.simplex.common.views.usersettings.IncognitoView import chat.simplex.res.MR import java.net.URI -@Composable -expect fun ScanToConnectView(chatModel: ChatModel, rh: RemoteHostInfo?, close: () -> Unit) - enum class ConnectionLinkType { INVITATION, CONTACT, GROUP } suspend fun planAndConnect( - chatModel: ChatModel, rhId: Long?, uri: URI, incognito: Boolean?, - close: (() -> Unit)? + close: (() -> Unit)?, + cleanup: (() -> Unit)? = null, + filterKnownContact: ((Contact) -> Unit)? = null, + filterKnownGroup: ((GroupInfo) -> Unit)? = null, ) { val connectionPlan = chatModel.controller.apiConnectPlan(rhId, uri.toString()) if (connectionPlan != null) { + val link = strHasSingleSimplexLink(uri.toString().trim()) + val linkText = if (link?.format is Format.SimplexLink) + "

${link.simplexLinkText(link.format.linkType, link.format.smpHosts)}" + else + "" when (connectionPlan) { is ConnectionPlan.InvitationLink -> when (connectionPlan.invitationLinkPlan) { InvitationLinkPlan.Ok -> { Log.d(TAG, "planAndConnect, .InvitationLink, .Ok, incognito=$incognito") if (incognito != null) { - connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close) + connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } else { askCurrentOrIncognitoProfileAlert( chatModel, rhId, uri, connectionPlan, close, title = generalGetString(MR.strings.connect_via_invitation_link), - text = AnnotatedString(generalGetString(MR.strings.profile_will_be_sent_to_contact_sending_link)), - connectDestructive = false + text = generalGetString(MR.strings.profile_will_be_sent_to_contact_sending_link) + linkText, + connectDestructive = false, + cleanup = cleanup, ) } } @@ -60,9 +54,11 @@ suspend fun planAndConnect( if (incognito != null) { AlertManager.privacySensitive.showAlertDialog( title = generalGetString(MR.strings.connect_plan_connect_to_yourself), - text = generalGetString(MR.strings.connect_plan_this_is_your_own_one_time_link), + text = generalGetString(MR.strings.connect_plan_this_is_your_own_one_time_link) + linkText, confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb), - onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close) } }, + onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } }, + onDismiss = cleanup, + onDismissRequest = cleanup, destructive = true, hostDevice = hostDevice(rhId), ) @@ -70,8 +66,9 @@ suspend fun planAndConnect( askCurrentOrIncognitoProfileAlert( chatModel, rhId, uri, connectionPlan, close, title = generalGetString(MR.strings.connect_plan_connect_to_yourself), - text = AnnotatedString(generalGetString(MR.strings.connect_plan_this_is_your_own_one_time_link)), - connectDestructive = true + text = generalGetString(MR.strings.connect_plan_this_is_your_own_one_time_link) + linkText, + connectDestructive = true, + cleanup = cleanup, ) } } @@ -79,42 +76,54 @@ suspend fun planAndConnect( Log.d(TAG, "planAndConnect, .InvitationLink, .Connecting, incognito=$incognito") val contact = connectionPlan.invitationLinkPlan.contact_ if (contact != null) { - openKnownContact(chatModel, rhId, close, contact) - AlertManager.privacySensitive.showAlertMsg( - generalGetString(MR.strings.contact_already_exists), - String.format(generalGetString(MR.strings.connect_plan_you_are_already_connecting_to_vName), contact.displayName), - hostDevice = hostDevice(rhId), - ) + if (filterKnownContact != null) { + filterKnownContact(contact) + } else { + openKnownContact(chatModel, rhId, close, contact) + AlertManager.privacySensitive.showAlertMsg( + generalGetString(MR.strings.contact_already_exists), + String.format(generalGetString(MR.strings.connect_plan_you_are_already_connecting_to_vName), contact.displayName) + linkText, + hostDevice = hostDevice(rhId), + ) + cleanup?.invoke() + } } else { AlertManager.privacySensitive.showAlertMsg( generalGetString(MR.strings.connect_plan_already_connecting), - generalGetString(MR.strings.connect_plan_you_are_already_connecting_via_this_one_time_link), + generalGetString(MR.strings.connect_plan_you_are_already_connecting_via_this_one_time_link) + linkText, hostDevice = hostDevice(rhId), ) + cleanup?.invoke() } } is InvitationLinkPlan.Known -> { Log.d(TAG, "planAndConnect, .InvitationLink, .Known, incognito=$incognito") val contact = connectionPlan.invitationLinkPlan.contact - openKnownContact(chatModel, rhId, close, contact) - AlertManager.privacySensitive.showAlertMsg( - generalGetString(MR.strings.contact_already_exists), - String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), contact.displayName), - hostDevice = hostDevice(rhId), - ) + if (filterKnownContact != null) { + filterKnownContact(contact) + } else { + openKnownContact(chatModel, rhId, close, contact) + AlertManager.privacySensitive.showAlertMsg( + generalGetString(MR.strings.contact_already_exists), + String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), contact.displayName) + linkText, + hostDevice = hostDevice(rhId), + ) + cleanup?.invoke() + } } } is ConnectionPlan.ContactAddress -> when (connectionPlan.contactAddressPlan) { ContactAddressPlan.Ok -> { Log.d(TAG, "planAndConnect, .ContactAddress, .Ok, incognito=$incognito") if (incognito != null) { - connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close) + connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } else { askCurrentOrIncognitoProfileAlert( chatModel, rhId, uri, connectionPlan, close, title = generalGetString(MR.strings.connect_via_contact_link), - text = AnnotatedString(generalGetString(MR.strings.profile_will_be_sent_to_contact_sending_link)), - connectDestructive = false + text = generalGetString(MR.strings.profile_will_be_sent_to_contact_sending_link) + linkText, + connectDestructive = false, + cleanup, ) } } @@ -123,18 +132,21 @@ suspend fun planAndConnect( if (incognito != null) { AlertManager.privacySensitive.showAlertDialog( title = generalGetString(MR.strings.connect_plan_connect_to_yourself), - text = generalGetString(MR.strings.connect_plan_this_is_your_own_simplex_address), + text = generalGetString(MR.strings.connect_plan_this_is_your_own_simplex_address) + linkText, confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb), - onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close) } }, + onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } }, destructive = true, + onDismiss = cleanup, + onDismissRequest = cleanup, hostDevice = hostDevice(rhId), ) } else { askCurrentOrIncognitoProfileAlert( chatModel, rhId, uri, connectionPlan, close, title = generalGetString(MR.strings.connect_plan_connect_to_yourself), - text = AnnotatedString(generalGetString(MR.strings.connect_plan_this_is_your_own_simplex_address)), - connectDestructive = true + text = generalGetString(MR.strings.connect_plan_this_is_your_own_simplex_address) + linkText, + connectDestructive = true, + cleanup = cleanup, ) } } @@ -143,9 +155,11 @@ suspend fun planAndConnect( if (incognito != null) { AlertManager.privacySensitive.showAlertDialog( title = generalGetString(MR.strings.connect_plan_repeat_connection_request), - text = generalGetString(MR.strings.connect_plan_you_have_already_requested_connection_via_this_address), + text = generalGetString(MR.strings.connect_plan_you_have_already_requested_connection_via_this_address) + linkText, confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb), - onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close) } }, + onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } }, + onDismiss = cleanup, + onDismissRequest = cleanup, destructive = true, hostDevice = hostDevice(rhId), ) @@ -153,30 +167,41 @@ suspend fun planAndConnect( askCurrentOrIncognitoProfileAlert( chatModel, rhId, uri, connectionPlan, close, title = generalGetString(MR.strings.connect_plan_repeat_connection_request), - text = AnnotatedString(generalGetString(MR.strings.connect_plan_you_have_already_requested_connection_via_this_address)), - connectDestructive = true + text = generalGetString(MR.strings.connect_plan_you_have_already_requested_connection_via_this_address) + linkText, + connectDestructive = true, + cleanup = cleanup, ) } } is ContactAddressPlan.ConnectingProhibit -> { Log.d(TAG, "planAndConnect, .ContactAddress, .ConnectingProhibit, incognito=$incognito") val contact = connectionPlan.contactAddressPlan.contact - openKnownContact(chatModel, rhId, close, contact) - AlertManager.privacySensitive.showAlertMsg( - generalGetString(MR.strings.contact_already_exists), - String.format(generalGetString(MR.strings.connect_plan_you_are_already_connecting_to_vName), contact.displayName), - hostDevice = hostDevice(rhId), - ) + if (filterKnownContact != null) { + filterKnownContact(contact) + } else { + openKnownContact(chatModel, rhId, close, contact) + AlertManager.privacySensitive.showAlertMsg( + generalGetString(MR.strings.contact_already_exists), + String.format(generalGetString(MR.strings.connect_plan_you_are_already_connecting_to_vName), contact.displayName) + linkText, + hostDevice = hostDevice(rhId), + ) + cleanup?.invoke() + } } is ContactAddressPlan.Known -> { Log.d(TAG, "planAndConnect, .ContactAddress, .Known, incognito=$incognito") val contact = connectionPlan.contactAddressPlan.contact - openKnownContact(chatModel, rhId, close, contact) - AlertManager.privacySensitive.showAlertMsg( - generalGetString(MR.strings.contact_already_exists), - String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), contact.displayName), - hostDevice = hostDevice(rhId), - ) + if (filterKnownContact != null) { + filterKnownContact(contact) + } else { + openKnownContact(chatModel, rhId, close, contact) + AlertManager.privacySensitive.showAlertMsg( + generalGetString(MR.strings.contact_already_exists), + String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), contact.displayName) + linkText, + hostDevice = hostDevice(rhId), + ) + cleanup?.invoke() + } } is ContactAddressPlan.ContactViaAddress -> { Log.d(TAG, "planAndConnect, .ContactAddress, .ContactViaAddress, incognito=$incognito") @@ -187,6 +212,7 @@ suspend fun planAndConnect( } else { askCurrentOrIncognitoProfileConnectContactViaAddress(chatModel, rhId, contact, close, openChat = false) } + cleanup?.invoke() } } is ConnectionPlan.GroupLink -> when (connectionPlan.groupLinkPlan) { @@ -195,33 +221,42 @@ suspend fun planAndConnect( if (incognito != null) { AlertManager.privacySensitive.showAlertDialog( title = generalGetString(MR.strings.connect_via_group_link), - text = generalGetString(MR.strings.you_will_join_group), + text = generalGetString(MR.strings.you_will_join_group) + linkText, confirmText = if (incognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button), - onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close) } }, + onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } }, + onDismiss = cleanup, + onDismissRequest = cleanup, hostDevice = hostDevice(rhId), ) } else { askCurrentOrIncognitoProfileAlert( chatModel, rhId, uri, connectionPlan, close, title = generalGetString(MR.strings.connect_via_group_link), - text = AnnotatedString(generalGetString(MR.strings.you_will_join_group)), - connectDestructive = false + text = generalGetString(MR.strings.you_will_join_group) + linkText, + connectDestructive = false, + cleanup = cleanup, ) } } is GroupLinkPlan.OwnLink -> { Log.d(TAG, "planAndConnect, .GroupLink, .OwnLink, incognito=$incognito") val groupInfo = connectionPlan.groupLinkPlan.groupInfo - ownGroupLinkConfirmConnect(chatModel, rhId, uri, incognito, connectionPlan, groupInfo, close) + if (filterKnownGroup != null) { + filterKnownGroup(groupInfo) + } else { + ownGroupLinkConfirmConnect(chatModel, rhId, uri, linkText, incognito, connectionPlan, groupInfo, close, cleanup) + } } GroupLinkPlan.ConnectingConfirmReconnect -> { Log.d(TAG, "planAndConnect, .GroupLink, .ConnectingConfirmReconnect, incognito=$incognito") if (incognito != null) { AlertManager.privacySensitive.showAlertDialog( title = generalGetString(MR.strings.connect_plan_repeat_join_request), - text = generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link), + text = generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link) + linkText, confirmText = if (incognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button), - onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close) } }, + onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } }, + onDismiss = cleanup, + onDismissRequest = cleanup, destructive = true, hostDevice = hostDevice(rhId), ) @@ -229,8 +264,9 @@ suspend fun planAndConnect( askCurrentOrIncognitoProfileAlert( chatModel, rhId, uri, connectionPlan, close, title = generalGetString(MR.strings.connect_plan_repeat_join_request), - text = AnnotatedString(generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link)), - connectDestructive = true + text = generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link) + linkText, + connectDestructive = true, + cleanup = cleanup, ) } } @@ -240,37 +276,44 @@ suspend fun planAndConnect( if (groupInfo != null) { AlertManager.privacySensitive.showAlertMsg( generalGetString(MR.strings.connect_plan_group_already_exists), - String.format(generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_vName), groupInfo.displayName) + String.format(generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_vName), groupInfo.displayName) + linkText ) } else { AlertManager.privacySensitive.showAlertMsg( generalGetString(MR.strings.connect_plan_already_joining_the_group), - generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link), + generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link) + linkText, hostDevice = hostDevice(rhId), ) } + cleanup?.invoke() } is GroupLinkPlan.Known -> { Log.d(TAG, "planAndConnect, .GroupLink, .Known, incognito=$incognito") val groupInfo = connectionPlan.groupLinkPlan.groupInfo - openKnownGroup(chatModel, rhId, close, groupInfo) - AlertManager.privacySensitive.showAlertMsg( - generalGetString(MR.strings.connect_plan_group_already_exists), - String.format(generalGetString(MR.strings.connect_plan_you_are_already_in_group_vName), groupInfo.displayName), - hostDevice = hostDevice(rhId), - ) + if (filterKnownGroup != null) { + filterKnownGroup(groupInfo) + } else { + openKnownGroup(chatModel, rhId, close, groupInfo) + AlertManager.privacySensitive.showAlertMsg( + generalGetString(MR.strings.connect_plan_group_already_exists), + String.format(generalGetString(MR.strings.connect_plan_you_are_already_in_group_vName), groupInfo.displayName) + linkText, + hostDevice = hostDevice(rhId), + ) + cleanup?.invoke() + } } } } } else { Log.d(TAG, "planAndConnect, plan error") if (incognito != null) { - connectViaUri(chatModel, rhId, uri, incognito, connectionPlan = null, close) + connectViaUri(chatModel, rhId, uri, incognito, connectionPlan = null, close, cleanup) } else { askCurrentOrIncognitoProfileAlert( chatModel, rhId, uri, connectionPlan = null, close, title = generalGetString(MR.strings.connect_plan_connect_via_link), - connectDestructive = false + connectDestructive = false, + cleanup = cleanup, ) } } @@ -282,7 +325,8 @@ suspend fun connectViaUri( uri: URI, incognito: Boolean, connectionPlan: ConnectionPlan?, - close: (() -> Unit)? + close: (() -> Unit)?, + cleanup: (() -> Unit)?, ) { val pcc = chatModel.controller.apiConnect(rhId, incognito, uri.toString()) val connLinkType = if (connectionPlan != null) planToConnectionLinkType(connectionPlan) else ConnectionLinkType.INVITATION @@ -300,6 +344,7 @@ suspend fun connectViaUri( hostDevice = hostDevice(rhId), ) } + cleanup?.invoke() } fun planToConnectionLinkType(connectionPlan: ConnectionPlan): ConnectionLinkType { @@ -317,8 +362,9 @@ fun askCurrentOrIncognitoProfileAlert( connectionPlan: ConnectionPlan?, close: (() -> Unit)?, title: String, - text: AnnotatedString? = null, + text: String? = null, connectDestructive: Boolean, + cleanup: (() -> Unit)?, ) { AlertManager.privacySensitive.showAlertDialogButtonsColumn( title = title, @@ -329,7 +375,7 @@ fun askCurrentOrIncognitoProfileAlert( SectionItemView({ AlertManager.privacySensitive.hideAlert() withApi { - connectViaUri(chatModel, rhId, uri, incognito = false, connectionPlan, close) + connectViaUri(chatModel, rhId, uri, incognito = false, connectionPlan, close, cleanup) } }) { Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = connectColor) @@ -337,18 +383,20 @@ fun askCurrentOrIncognitoProfileAlert( SectionItemView({ AlertManager.privacySensitive.hideAlert() withApi { - connectViaUri(chatModel, rhId, uri, incognito = true, connectionPlan, close) + connectViaUri(chatModel, rhId, uri, incognito = true, connectionPlan, close, cleanup) } }) { Text(generalGetString(MR.strings.connect_use_new_incognito_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = connectColor) } SectionItemView({ AlertManager.privacySensitive.hideAlert() + cleanup?.invoke() }) { Text(stringResource(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) } } }, + onDismissRequest = cleanup, hostDevice = hostDevice(rhId), ) } @@ -367,20 +415,23 @@ fun ownGroupLinkConfirmConnect( chatModel: ChatModel, rhId: Long?, uri: URI, + linkText: String, incognito: Boolean?, connectionPlan: ConnectionPlan?, groupInfo: GroupInfo, close: (() -> Unit)?, + cleanup: (() -> Unit)?, ) { AlertManager.privacySensitive.showAlertDialogButtonsColumn( title = generalGetString(MR.strings.connect_plan_join_your_group), - text = AnnotatedString(String.format(generalGetString(MR.strings.connect_plan_this_is_your_link_for_group_vName), groupInfo.displayName)), + text = String.format(generalGetString(MR.strings.connect_plan_this_is_your_link_for_group_vName), groupInfo.displayName) + linkText, buttons = { Column { // Open group SectionItemView({ AlertManager.privacySensitive.hideAlert() openKnownGroup(chatModel, rhId, close, groupInfo) + cleanup?.invoke() }) { Text(generalGetString(MR.strings.connect_plan_open_group), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) } @@ -389,7 +440,7 @@ fun ownGroupLinkConfirmConnect( SectionItemView({ AlertManager.privacySensitive.hideAlert() withApi { - connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close) + connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } }) { Text( @@ -402,7 +453,7 @@ fun ownGroupLinkConfirmConnect( SectionItemView({ AlertManager.privacySensitive.hideAlert() withApi { - connectViaUri(chatModel, rhId, uri, incognito = false, connectionPlan, close) + connectViaUri(chatModel, rhId, uri, incognito = false, connectionPlan, close, cleanup) } }) { Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error) @@ -411,7 +462,7 @@ fun ownGroupLinkConfirmConnect( SectionItemView({ AlertManager.privacySensitive.hideAlert() withApi { - connectViaUri(chatModel, rhId, uri, incognito = true, connectionPlan, close) + connectViaUri(chatModel, rhId, uri, incognito = true, connectionPlan, close, cleanup) } }) { Text(generalGetString(MR.strings.connect_use_new_incognito_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error) @@ -420,11 +471,13 @@ fun ownGroupLinkConfirmConnect( // Cancel SectionItemView({ AlertManager.privacySensitive.hideAlert() + cleanup?.invoke() }) { Text(stringResource(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) } } }, + onDismissRequest = cleanup, hostDevice = hostDevice(rhId), ) } @@ -438,77 +491,3 @@ fun openKnownGroup(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, grou } } } - -@Composable -fun ConnectContactLayout( - chatModel: ChatModel, - rh: RemoteHostInfo?, - incognitoPref: SharedPreference, - close: () -> Unit -) { - val incognito = remember { mutableStateOf(incognitoPref.get()) } - - @Composable - fun QRCodeScanner(close: () -> Unit) { - QRCodeScanner { connReqUri -> - try { - val uri = URI(connReqUri) - withApi { - planAndConnect(chatModel, rh?.remoteHostId, uri, incognito = incognito.value, close) - } - } catch (e: RuntimeException) { - AlertManager.shared.showAlertMsg( - title = generalGetString(MR.strings.invalid_QR_code), - text = generalGetString(MR.strings.this_QR_code_is_not_a_link) - ) - } - } - } - - Column( - Modifier.verticalScroll(rememberScrollState()).padding(horizontal = DEFAULT_PADDING), - verticalArrangement = Arrangement.SpaceBetween - ) { - AppBarTitle(stringResource(MR.strings.scan_QR_code), hostDevice(rh?.remoteHostId), withPadding = false) - Box( - Modifier - .fillMaxWidth() - .aspectRatio(ratio = 1F) - .padding(bottom = 12.dp) - ) { QRCodeScanner(close) } - - IncognitoToggle(incognitoPref, incognito) { ModalManager.start.showModal { IncognitoView() } } - - SectionTextFooter( - buildAnnotatedString { - append(sharedProfileInfo(chatModel, incognito.value)) - append("\n\n") - append(annotatedStringResource(MR.strings.if_you_cannot_meet_in_person_scan_QR_in_video_call_or_ask_for_invitation_link)) - } - ) - - SectionBottomSpacer() - } -} - -fun URI.getQueryParameter(param: String): String? { - if (!query.contains("$param=")) return null - return query.substringAfter("$param=").substringBefore("&") -} - -@Preview/*( - uiMode = Configuration.UI_MODE_NIGHT_YES, - showBackground = true, - name = "Dark Mode" -)*/ -@Composable -fun PreviewConnectContactLayout() { - SimpleXTheme { - ConnectContactLayout( - chatModel = ChatModel, - rh = null, - incognitoPref = SharedPreference({ false }, {}), - close = {}, - ) - } -} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.kt deleted file mode 100644 index 0077e2849c..0000000000 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.kt +++ /dev/null @@ -1,12 +0,0 @@ -package chat.simplex.common.views.newchat - -import androidx.compose.runtime.* -import chat.simplex.common.model.ChatModel -import chat.simplex.common.model.RemoteHostInfo - -enum class ConnectViaLinkTab { - SCAN, PASTE -} - -@Composable -expect fun ConnectViaLinkView(m: ChatModel, rh: RemoteHostInfo?, close: () -> Unit) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt index 5e9495e866..7fa0e6a704 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt @@ -23,7 +23,7 @@ import chat.simplex.common.views.chatlist.deleteContactConnectionAlert import chat.simplex.common.views.helpers.* import chat.simplex.common.model.ChatModel import chat.simplex.common.model.PendingContactConnection -import chat.simplex.common.platform.shareText +import chat.simplex.common.platform.* import chat.simplex.common.views.usersettings.* import chat.simplex.res.MR @@ -37,16 +37,19 @@ fun ContactConnectionInfoView( close: () -> Unit ) { LaunchedEffect(connReqInvitation) { - chatModel.connReqInv.value = connReqInvitation + if (connReqInvitation != null) { + chatModel.showingInvitation.value = ShowingInvitation(contactConnection.id, connReqInvitation, false) + } } - /** When [AddContactView] is open, we don't need to drop [chatModel.connReqInv]. - * Otherwise, it will be called here AFTER [AddContactView] is launched and will clear the value too soon. + /** When [AddContactLearnMore] is open, we don't need to drop [ChatModel.showingInvitation]. + * Otherwise, it will be called here AFTER [AddContactLearnMore] is launched and will clear the value too soon. * It will be dropped automatically when connection established or when user goes away from this screen. + * It applies only to Android because on Desktop center space will not be overlapped by [AddContactLearnMore] **/ DisposableEffect(Unit) { onDispose { - if (!ModalManager.center.hasModalsOpen()) { - chatModel.connReqInv.value = null + if (!ModalManager.center.hasModalsOpen() || appPlatform.isDesktop) { + chatModel.showingInvitation.value = null } } } @@ -61,14 +64,14 @@ fun ContactConnectionInfoView( onLocalAliasChanged = { setContactAlias(rhId, contactConnection, it, chatModel) }, share = { if (connReqInvitation != null) clipboard.shareText(connReqInvitation) }, learnMore = { - ModalManager.center.showModal { + ModalManager.end.showModalCloseable { close -> Column( Modifier .fillMaxHeight() .padding(horizontal = DEFAULT_PADDING), verticalArrangement = Arrangement.SpaceBetween ) { - AddContactLearnMore() + AddContactLearnMore(close) } } } @@ -135,11 +138,7 @@ private fun ContactConnectionInfoLayout( SectionView { if (!connReq.isNullOrEmpty() && contactConnection.initiated) { - SimpleXLinkQRCode( - connReq, Modifier - .padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF) - .aspectRatio(1f) - ) + SimpleXLinkQRCode(connReq) incognitoEnabled() ShareLinkButton(connReq) OneTimeLinkLearnMoreButton(learnMore) @@ -158,6 +157,30 @@ private fun ContactConnectionInfoLayout( } } +@Composable +fun ShareLinkButton(connReqInvitation: String) { + val clipboard = LocalClipboardManager.current + SettingsActionItem( + painterResource(MR.images.ic_share), + stringResource(MR.strings.share_invitation_link), + click = { + chatModel.showingInvitation.value = chatModel.showingInvitation.value?.copy(connChatUsed = true) + clipboard.shareText(simplexChatLink(connReqInvitation)) + }, + iconColor = MaterialTheme.colors.primary, + textColor = MaterialTheme.colors.primary, + ) +} + +@Composable +fun OneTimeLinkLearnMoreButton(onClick: () -> Unit) { + SettingsActionItem( + painterResource(MR.images.ic_info), + stringResource(MR.strings.learn_more), + onClick, + ) +} + @Composable fun DeleteButton(onClick: () -> Unit) { SettingsActionItem( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/CreateLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/CreateLinkView.kt deleted file mode 100644 index 6f3caf4674..0000000000 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/CreateLinkView.kt +++ /dev/null @@ -1,118 +0,0 @@ -package chat.simplex.common.views.newchat - -import androidx.compose.foundation.layout.* -import androidx.compose.material.* -import androidx.compose.runtime.* -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.unit.sp -import chat.simplex.common.model.* -import chat.simplex.common.views.helpers.* -import chat.simplex.common.views.usersettings.UserAddressView -import chat.simplex.res.MR - -enum class CreateLinkTab { - ONE_TIME, LONG_TERM -} - -@Composable -fun CreateLinkView(m: ChatModel, rh: RemoteHostInfo?, initialSelection: CreateLinkTab) { - val selection = remember { mutableStateOf(initialSelection) } - val connReqInvitation = rememberSaveable { m.connReqInv } - val contactConnection: MutableState = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf(null) } - val creatingConnReq = rememberSaveable { mutableStateOf(false) } - LaunchedEffect(selection.value) { - if ( - selection.value == CreateLinkTab.ONE_TIME - && connReqInvitation.value.isNullOrEmpty() - && contactConnection.value == null - && !creatingConnReq.value - ) { - createInvitation(m, rh?.remoteHostId, creatingConnReq, connReqInvitation, contactConnection) - } - } - /** When [AddContactView] is open, we don't need to drop [chatModel.connReqInv]. - * Otherwise, it will be called here AFTER [AddContactView] is launched and will clear the value too soon. - * It will be dropped automatically when connection established or when user goes away from this screen. - **/ - DisposableEffect(Unit) { - onDispose { - if (!ModalManager.center.hasModalsOpen()) { - m.connReqInv.value = null - } - } - } - val tabTitles = CreateLinkTab.values().map { - when { - it == CreateLinkTab.ONE_TIME && connReqInvitation.value.isNullOrEmpty() && contactConnection.value == null -> - stringResource(MR.strings.create_one_time_link) - it == CreateLinkTab.ONE_TIME -> - stringResource(MR.strings.one_time_link) - it == CreateLinkTab.LONG_TERM -> - stringResource(MR.strings.your_simplex_contact_address) - else -> "" - } - } - Column( - Modifier - .fillMaxHeight(), - verticalArrangement = Arrangement.SpaceBetween - ) { - Column(Modifier.weight(1f)) { - when (selection.value) { - CreateLinkTab.ONE_TIME -> { - AddContactView(m, rh,connReqInvitation.value ?: "", contactConnection) - } - CreateLinkTab.LONG_TERM -> { - UserAddressView(m, viaCreateLinkView = true, close = {}) - } - } - } - TabRow( - selectedTabIndex = selection.value.ordinal, - backgroundColor = Color.Transparent, - contentColor = MaterialTheme.colors.primary, - ) { - tabTitles.forEachIndexed { index, it -> - Tab( - selected = selection.value.ordinal == index, - onClick = { - selection.value = CreateLinkTab.values()[index] - }, - text = { Text(it, fontSize = 13.sp) }, - icon = { - Icon( - if (CreateLinkTab.ONE_TIME.ordinal == index) painterResource(MR.images.ic_repeat_one) else painterResource(MR.images.ic_all_inclusive), - it - ) - }, - selectedContentColor = MaterialTheme.colors.primary, - unselectedContentColor = MaterialTheme.colors.secondary, - ) - } - } - } -} - -private fun createInvitation( - m: ChatModel, - rhId: Long?, - creatingConnReq: MutableState, - connReqInvitation: MutableState, - contactConnection: MutableState -) { - creatingConnReq.value = true - withApi { - val r = m.controller.apiAddContact(rhId, incognito = m.controller.appPrefs.incognito.get()) - if (r != null) { - m.updateContactConnection(rhId, r.second) - connReqInvitation.value = r.first - contactConnection.value = r.second - } else { - creatingConnReq.value = false - } - } -} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt index 86929584c1..f2f2e9ec5f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt @@ -42,12 +42,7 @@ fun NewChatSheet(chatModel: ChatModel, newChatSheetState: StateFlow ConnectViaLinkView(chatModel, chatModel.currentRemoteHost.value, close) } + ModalManager.center.showModalCloseable { close -> NewChatView(chatModel.currentRemoteHost.value, NewChatOption.INVITE, close = close) } }, createGroup = { closeNewChatSheet(false) @@ -59,18 +54,16 @@ fun NewChatSheet(chatModel: ChatModel, newChatSheetState: StateFlow, stopped: Boolean, addContact: () -> Unit, - connectViaLink: () -> Unit, createGroup: () -> Unit, closeNewChatSheet: (animated: Boolean) -> Unit, ) { @@ -109,7 +102,7 @@ private fun NewChatSheetLayout( verticalArrangement = Arrangement.Bottom, horizontalAlignment = Alignment.End ) { - val actions = remember { listOf(addContact, connectViaLink, createGroup) } + val actions = remember { listOf(addContact, createGroup) } val backgroundColor = if (isInDarkTheme()) blendARGB(MaterialTheme.colors.primary, Color.Black, 0.7F) else @@ -271,7 +264,6 @@ private fun PreviewNewChatSheet() { MutableStateFlow(AnimatedViewState.VISIBLE), stopped = false, addContact = {}, - connectViaLink = {}, createGroup = {}, closeNewChatSheet = {}, ) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt new file mode 100644 index 0000000000..0686f3c861 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -0,0 +1,436 @@ +package chat.simplex.common.views.newchat + +import SectionBottomSpacer +import SectionItemView +import SectionTextFooter +import SectionView +import androidx.compose.foundation.* +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.unit.dp +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource +import androidx.compose.ui.unit.sp +import chat.simplex.common.model.* +import chat.simplex.common.model.ChatModel.controller +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.usersettings.* +import chat.simplex.res.MR +import kotlinx.coroutines.launch +import java.net.URI + +enum class NewChatOption { + INVITE, CONNECT +} + +@Composable +fun ModalData.NewChatView(rh: RemoteHostInfo?, selection: NewChatOption, showQRCodeScanner: Boolean = false, close: () -> Unit) { + val selection = remember { stateGetOrPut("selection") { selection } } + val showQRCodeScanner = remember { stateGetOrPut("showQRCodeScanner") { showQRCodeScanner } } + val contactConnection: MutableState = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf(null) } + val connReqInvitation by remember { derivedStateOf { chatModel.showingInvitation.value?.connReq ?: "" } } + val creatingConnReq = rememberSaveable { mutableStateOf(false) } + val pastedLink = rememberSaveable { mutableStateOf("") } + LaunchedEffect(selection.value) { + if ( + selection.value == NewChatOption.INVITE + && connReqInvitation.isEmpty() + && contactConnection.value == null + && !creatingConnReq.value + ) { + createInvitation(rh?.remoteHostId, creatingConnReq, connReqInvitation, contactConnection) + } + } + DisposableEffect(Unit) { + onDispose { + /** When [AddContactLearnMore] is open, we don't need to drop [ChatModel.showingInvitation]. + * Otherwise, it will be called here AFTER [AddContactLearnMore] is launched and will clear the value too soon. + * It will be dropped automatically when connection established or when user goes away from this screen. + * It applies only to Android because on Desktop center space will not be overlapped by [AddContactLearnMore] + **/ + if (chatModel.showingInvitation.value != null && (!ModalManager.center.hasModalsOpen() || appPlatform.isDesktop)) { + val conn = contactConnection.value + if (chatModel.showingInvitation.value?.connChatUsed == false && conn != null) { + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.keep_unused_invitation_question), + text = generalGetString(MR.strings.you_can_view_invitation_link_again), + confirmText = generalGetString(MR.strings.delete_verb), + dismissText = generalGetString(MR.strings.keep_invitation_link), + destructive = true, + onConfirm = { + withBGApi { + val chatInfo = ChatInfo.ContactConnection(conn) + controller.deleteChat(Chat(remoteHostId = rh?.remoteHostId, chatInfo = chatInfo, chatItems = listOf())) + if (chatModel.chatId.value == chatInfo.id) { + chatModel.chatId.value = null + ModalManager.end.closeModals() + } + } + } + ) + } + chatModel.showingInvitation.value = null + } + } + } + val tabTitles = NewChatOption.values().map { + when(it) { + NewChatOption.INVITE -> + stringResource(MR.strings.add_contact_tab) + NewChatOption.CONNECT -> + stringResource(MR.strings.connect_via_link) + } + } + + Column( + Modifier.fillMaxSize(), + ) { + Box(contentAlignment = Alignment.Center) { + val bottomPadding = DEFAULT_PADDING + AppBarTitle(stringResource(MR.strings.new_chat), hostDevice(rh?.remoteHostId), bottomPadding = bottomPadding) + Column(Modifier.align(Alignment.CenterEnd).padding(bottom = bottomPadding, end = DEFAULT_PADDING)) { + AddContactLearnMoreButton() + } + } + val scope = rememberCoroutineScope() + val pagerState = rememberPagerState( + initialPage = selection.value.ordinal, + initialPageOffsetFraction = 0f + ) { NewChatOption.values().size } + KeyChangeEffect(pagerState.currentPage) { + selection.value = NewChatOption.values()[pagerState.currentPage] + } + TabRow( + selectedTabIndex = pagerState.currentPage, + backgroundColor = Color.Transparent, + contentColor = MaterialTheme.colors.primary, + ) { + tabTitles.forEachIndexed { index, it -> + LeadingIconTab( + selected = pagerState.currentPage == index, + onClick = { + scope.launch { + pagerState.animateScrollToPage(index) + } + }, + text = { Text(it, fontSize = 13.sp) }, + icon = { + Icon( + if (NewChatOption.INVITE.ordinal == index) painterResource(MR.images.ic_repeat_one) else painterResource(MR.images.ic_qr_code), + it + ) + }, + selectedContentColor = MaterialTheme.colors.primary, + unselectedContentColor = MaterialTheme.colors.secondary, + ) + } + } + + HorizontalPager(state = pagerState, Modifier.fillMaxSize(), verticalAlignment = Alignment.Top) { index -> + Column( + Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()), + verticalArrangement = if (index == NewChatOption.INVITE.ordinal && connReqInvitation.isEmpty()) Arrangement.Center else Arrangement.Top) { + Spacer(Modifier.height(DEFAULT_PADDING)) + when (index) { + NewChatOption.INVITE.ordinal -> { + PrepareAndInviteView(rh?.remoteHostId, contactConnection, connReqInvitation, creatingConnReq) + } + NewChatOption.CONNECT.ordinal -> { + ConnectView(rh?.remoteHostId, showQRCodeScanner, pastedLink, close) + } + } + SectionBottomSpacer() + } + } + } +} + +@Composable +private fun PrepareAndInviteView(rhId: Long?, contactConnection: MutableState, connReqInvitation: String, creatingConnReq: MutableState) { + if (connReqInvitation.isNotEmpty()) { + InviteView( + rhId, + connReqInvitation = connReqInvitation, + contactConnection = contactConnection, + ) + } else if (creatingConnReq.value) { + CreatingLinkProgressView() + } else { + RetryButton { createInvitation(rhId, creatingConnReq, connReqInvitation, contactConnection) } + } +} + +@Composable +private fun CreatingLinkProgressView() { + DefaultProgressView(stringResource(MR.strings.creating_link)) +} + +@Composable +private fun RetryButton(onClick: () -> Unit) { + Column( + Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + IconButton(onClick, Modifier.size(30.dp)) { + Icon(painterResource(MR.images.ic_refresh), null) + } + Spacer(Modifier.height(DEFAULT_PADDING)) + Text(stringResource(MR.strings.retry_verb)) + } +} + +@Composable +private fun InviteView(rhId: Long?, connReqInvitation: String, contactConnection: MutableState) { + SectionView(stringResource(MR.strings.share_this_1_time_link).uppercase()) { + LinkTextView(connReqInvitation, true) + } + + Spacer(Modifier.height(10.dp)) + + SectionView(stringResource(MR.strings.or_show_this_qr_code).uppercase()) { + SimpleXLinkQRCode(connReqInvitation, onShare = { chatModel.markShowingInvitationUsed() }) + } + + Spacer(Modifier.height(10.dp)) + val incognito = remember { mutableStateOf(controller.appPrefs.incognito.get()) } + IncognitoToggle(controller.appPrefs.incognito, incognito) { + if (appPlatform.isDesktop) ModalManager.end.closeModals() + ModalManager.end.showModal { IncognitoView() } + } + KeyChangeEffect(incognito.value) { + withBGApi { + val contactConn = contactConnection.value ?: return@withBGApi + val conn = controller.apiSetConnectionIncognito(rhId, contactConn.pccConnId, incognito.value) ?: return@withBGApi + contactConnection.value = conn + chatModel.updateContactConnection(rhId, conn) + } + chatModel.markShowingInvitationUsed() + } + SectionTextFooter(sharedProfileInfo(chatModel, incognito.value)) +} + +@Composable +private fun AddContactLearnMoreButton() { + IconButton( + { + if (appPlatform.isDesktop) ModalManager.end.closeModals() + ModalManager.end.showModalCloseable { close -> + Column( + Modifier + .fillMaxHeight() + .padding(horizontal = DEFAULT_PADDING), + verticalArrangement = Arrangement.SpaceBetween + ) { + AddContactLearnMore(close) + } + } + } + ) { + Icon( + painterResource(MR.images.ic_info), + stringResource(MR.strings.learn_more), + ) + } +} + +@Composable +private fun ConnectView(rhId: Long?, showQRCodeScanner: MutableState, pastedLink: MutableState, close: () -> Unit) { + SectionView(stringResource(MR.strings.paste_the_link_you_received).uppercase()) { + PasteLinkView(rhId, pastedLink, showQRCodeScanner, close) + } + + if (appPlatform.isAndroid) { + Spacer(Modifier.height(10.dp)) + + SectionView(stringResource(MR.strings.or_scan_qr_code).uppercase()) { + QRCodeScanner(showQRCodeScanner) { text -> + withBGApi { + val res = verify(rhId, text, close) + if (!res) { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.invalid_qr_code), + text = generalGetString(MR.strings.code_you_scanned_is_not_simplex_link_qr_code) + ) + } + } + } + } + } +} + +@Composable +private fun PasteLinkView(rhId: Long?, pastedLink: MutableState, showQRCodeScanner: MutableState, close: () -> Unit) { + if (pastedLink.value.isEmpty()) { + val clipboard = LocalClipboardManager.current + SectionItemView({ + val str = clipboard.getText()?.text ?: return@SectionItemView + val link = strHasSingleSimplexLink(str.trim()) + if (link != null) { + pastedLink.value = link.text + showQRCodeScanner.value = false + withBGApi { connect(rhId, link.text, close) { pastedLink.value = "" } } + } else { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.invalid_contact_link), + text = generalGetString(MR.strings.the_text_you_pasted_is_not_a_link) + ) + } + }) { + Text(stringResource(MR.strings.tap_to_paste_link)) + } + } else { + LinkTextView(pastedLink.value, false) + } +} + +@Composable +private fun LinkTextView(link: String, share: Boolean) { + val clipboard = LocalClipboardManager.current + Row(Modifier.fillMaxWidth().heightIn(min = 46.dp).padding(horizontal = DEFAULT_PADDING), verticalAlignment = Alignment.CenterVertically) { + Box(Modifier.weight(1f).clickable { + chatModel.markShowingInvitationUsed() + clipboard.shareText(link) + }) { + BasicTextField( + value = link, + onValueChange = { }, + enabled = false, + textStyle = TextStyle(fontSize = 16.sp, color = MaterialTheme.colors.onBackground), + singleLine = true, + decorationBox = @Composable { innerTextField -> + TextFieldDefaults.TextFieldDecorationBox( + value = link, + innerTextField = innerTextField, + contentPadding = PaddingValues(), + label = null, + visualTransformation = VisualTransformation.None, + leadingIcon = null, + trailingIcon = null, + singleLine = true, + enabled = false, + isError = false, + interactionSource = remember { MutableInteractionSource() }, + ) + }) + } + // Element Text() can add ellipsis (...) in random place of the string, sometimes even after half of width of a screen. + // So using BasicTextField + manual ... + Text("…", fontSize = 16.sp) + if (share) { + Spacer(Modifier.width(DEFAULT_PADDING)) + IconButton({ + chatModel.markShowingInvitationUsed() + clipboard.shareText(link) + }, Modifier.size(20.dp)) { + Icon(painterResource(MR.images.ic_share_filled), null, tint = MaterialTheme.colors.primary) + } + } + } +} + +private suspend fun verify(rhId: Long?, text: String?, close: () -> Unit): Boolean { + if (text != null && strIsSimplexLink(text)) { + connect(rhId, text, close) + return true + } + return false +} + +private suspend fun connect(rhId: Long?, link: String, close: () -> Unit, cleanup: (() -> Unit)? = null) { + planAndConnect( + rhId, + URI.create(link), + close = close, + cleanup = cleanup, + incognito = null + ) +} + +private fun createInvitation( + rhId: Long?, + creatingConnReq: MutableState, + connReqInvitation: String, + contactConnection: MutableState +) { + if (connReqInvitation.isNotEmpty() || contactConnection.value != null || creatingConnReq.value) return + creatingConnReq.value = true + withApi { + val (r, alert) = controller.apiAddContact(rhId, incognito = controller.appPrefs.incognito.get()) + if (r != null) { + chatModel.updateContactConnection(rhId, r.second) + chatModel.showingInvitation.value = ShowingInvitation(connId = r.second.id, connReq = simplexChatLink(r.first), connChatUsed = false) + contactConnection.value = r.second + } else { + creatingConnReq.value = false + if (alert != null) { + alert() + } + } + } +} + +fun strIsSimplexLink(str: String): Boolean { + val parsedMd = parseToMarkdown(str) + return parsedMd != null && parsedMd.size == 1 && parsedMd[0].format is Format.SimplexLink +} + +fun strHasSingleSimplexLink(str: String): FormattedText? { + val parsedMd = parseToMarkdown(str) ?: return null + val parsedLinks = parsedMd.filter { it.format?.isSimplexLink ?: false } + if (parsedLinks.size != 1) return null + + return parsedLinks[0] +} + +@Composable +fun IncognitoToggle( + incognitoPref: SharedPreference, + incognito: MutableState, + onClickInfo: () -> Unit +) { + SettingsActionItemWithContent( + icon = if (incognito.value) painterResource(MR.images.ic_theater_comedy_filled) else painterResource(MR.images.ic_theater_comedy), + text = null, + click = onClickInfo, + iconColor = if (incognito.value) Indigo else MaterialTheme.colors.secondary, + extraPadding = false + ) { + SharedPreferenceToggleWithIcon( + stringResource(MR.strings.incognito), + painterResource(MR.images.ic_info), + stopped = false, + onClickInfo = onClickInfo, + preference = incognitoPref, + preferenceState = incognito + ) + } +} + +fun sharedProfileInfo( + chatModel: ChatModel, + incognito: Boolean +): String { + val name = chatModel.currentUser.value?.displayName ?: "" + return if (incognito) { + generalGetString(MR.strings.connect__a_new_random_profile_will_be_shared) + } else { + String.format(generalGetString(MR.strings.connect__your_profile_will_be_shared), name) + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/PasteToConnect.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/PasteToConnect.kt deleted file mode 100644 index dacf937575..0000000000 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/PasteToConnect.kt +++ /dev/null @@ -1,135 +0,0 @@ -package chat.simplex.common.views.newchat - -import SectionBottomSpacer -import SectionTextFooter -import androidx.compose.desktop.ui.tooling.preview.Preview -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.MaterialTheme -import androidx.compose.runtime.* -import androidx.compose.ui.Modifier -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.platform.LocalClipboardManager -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.unit.dp -import chat.simplex.common.model.* -import chat.simplex.common.ui.theme.* -import chat.simplex.common.views.helpers.* -import chat.simplex.common.views.usersettings.IncognitoView -import chat.simplex.common.views.usersettings.SettingsActionItem -import chat.simplex.res.MR -import java.net.URI - -@Composable -fun PasteToConnectView(chatModel: ChatModel, rh: RemoteHostInfo?, close: () -> Unit) { - val connectionLink = remember { mutableStateOf("") } - val clipboard = LocalClipboardManager.current - PasteToConnectLayout( - chatModel = chatModel, - rh = rh, - incognitoPref = chatModel.controller.appPrefs.incognito, - connectionLink = connectionLink, - pasteFromClipboard = { - connectionLink.value = clipboard.getText()?.text ?: return@PasteToConnectLayout - }, - close = close - ) -} - -@Composable -fun PasteToConnectLayout( - chatModel: ChatModel, - rh: RemoteHostInfo?, - incognitoPref: SharedPreference, - connectionLink: MutableState, - pasteFromClipboard: () -> Unit, - close: () -> Unit -) { - val incognito = remember { mutableStateOf(incognitoPref.get()) } - val rhId = rh?.remoteHostId - fun connectViaLink(connReqUri: String) { - try { - val uri = URI(connReqUri) - withApi { - planAndConnect(chatModel, rhId, uri, incognito = incognito.value, close) - } - } catch (e: RuntimeException) { - AlertManager.shared.showAlertMsg( - title = generalGetString(MR.strings.invalid_connection_link), - text = generalGetString(MR.strings.this_string_is_not_a_connection_link) - ) - } - } - - Column( - Modifier.verticalScroll(rememberScrollState()).padding(horizontal = DEFAULT_PADDING), - verticalArrangement = Arrangement.SpaceBetween, - ) { - AppBarTitle(stringResource(MR.strings.connect_via_link), hostDevice(rhId), withPadding = false) - - Box(Modifier.padding(top = DEFAULT_PADDING, bottom = 6.dp)) { - TextEditor( - connectionLink, - Modifier.height(180.dp), - contentPadding = PaddingValues(), - placeholder = stringResource(MR.strings.paste_the_link_you_received_to_connect_with_your_contact) - ) - } - - if (connectionLink.value == "") { - SettingsActionItem( - painterResource(MR.images.ic_content_paste), - stringResource(MR.strings.paste_button), - click = pasteFromClipboard, - ) - } else { - SettingsActionItem( - painterResource(MR.images.ic_close), - stringResource(MR.strings.clear_verb), - click = { connectionLink.value = "" }, - ) - } - - SettingsActionItem( - painterResource(MR.images.ic_link), - stringResource(MR.strings.connect_button), - click = { connectViaLink(connectionLink.value) }, - textColor = MaterialTheme.colors.primary, - iconColor = MaterialTheme.colors.primary, - disabled = connectionLink.value.isEmpty() || connectionLink.value.trim().contains(" ") - ) - - IncognitoToggle(incognitoPref, incognito) { ModalManager.start.showModal { IncognitoView() } } - - SectionTextFooter( - buildAnnotatedString { - append(sharedProfileInfo(chatModel, incognito.value)) - append("\n\n") - append(annotatedStringResource(MR.strings.you_can_also_connect_by_clicking_the_link)) - } - ) - - SectionBottomSpacer() - } -} - - -@Preview/*( - uiMode = Configuration.UI_MODE_NIGHT_YES, - name = "Dark Mode" -)*/ -@Composable -fun PreviewPasteToConnectTextbox() { - SimpleXTheme { - PasteToConnectLayout( - chatModel = ChatModel, - rh = null, - incognitoPref = SharedPreference({ false }, {}), - connectionLink = remember { mutableStateOf("") }, - pasteFromClipboard = {}, - close = {} - ) - } -} 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 7f9fae60a3..e38c983487 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 @@ -14,7 +14,7 @@ import boofcv.alg.drawing.FiducialImageEngine import boofcv.alg.fiducial.qrcode.* import chat.simplex.common.model.CryptoFile import chat.simplex.common.platform.* -import chat.simplex.common.ui.theme.SimpleXTheme +import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import kotlinx.coroutines.launch @@ -23,14 +23,18 @@ import kotlinx.coroutines.launch fun SimpleXLinkQRCode( connReq: String, modifier: Modifier = Modifier, + padding: PaddingValues = PaddingValues(horizontal = DEFAULT_PADDING * 2f, vertical = DEFAULT_PADDING_HALF), tintColor: Color = Color(0xff062d56), - withLogo: Boolean = true + withLogo: Boolean = true, + onShare: (() -> Unit)? = null, ) { QRCode( simplexChatLink(connReq), modifier, + padding, tintColor, - withLogo + withLogo, + onShare, ) } @@ -46,22 +50,24 @@ fun simplexChatLink(uri: String): String { fun QRCode( connReq: String, modifier: Modifier = Modifier, + padding: PaddingValues = PaddingValues(horizontal = DEFAULT_PADDING * 2f, vertical = DEFAULT_PADDING_HALF), tintColor: Color = Color(0xff062d56), - withLogo: Boolean = true + withLogo: Boolean = true, + onShare: (() -> Unit)? = null, ) { val scope = rememberCoroutineScope() - - BoxWithConstraints(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { - val maxWidthInPx = with(LocalDensity.current) { maxWidth.roundToPx() } - val qr = remember(maxWidthInPx, connReq, tintColor, withLogo) { - qrCodeBitmap(connReq, maxWidthInPx).replaceColor(Color.Black.toArgb(), tintColor.toArgb()) - .let { if (withLogo) it.addLogo() else it } - } + val qr = remember(connReq, tintColor, withLogo) { + qrCodeBitmap(connReq, 1024).replaceColor(Color.Black.toArgb(), tintColor.toArgb()) + .let { if (withLogo) it.addLogo() else it } + } + Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { Image( bitmap = qr, contentDescription = stringResource(MR.strings.image_descr_qr_code), Modifier - .widthIn(max = 360.dp) + .padding(padding) + .widthIn(max = 400.dp) + .aspectRatio(1f) .then(modifier) .clickable { scope.launch { @@ -70,6 +76,7 @@ fun QRCode( val file = saveTempImageUncompressed(image, true) if (file != null) { shareFile("", CryptoFile.plain(file.absolutePath)) + onShare?.invoke() } } } @@ -81,7 +88,9 @@ fun qrCodeBitmap(content: String, size: Int = 1024): ImageBitmap { val qrCode = QrCodeEncoder().addAutomatic(content).setError(QrCode.ErrorLevel.L).fixate() /** See [QrCodeGeneratorImage.initialize] and [FiducialImageEngine.configure] for size calculation */ val numModules = QrCode.totalModules(qrCode.version) - val borderModule = 1 + // Hide border on light themes to make it fit to the same place as camera in QRCodeScanner. + // On dark themes better to show the border + val borderModule = if (CurrentColors.value.colors.isLight) 0 else 1 // val calculatedFinalWidth = (pixelsPerModule * numModules) + 2 * (borderModule * pixelsPerModule) // size = (x * numModules) + 2 * (borderModule * x) // size / x = numModules + 2 * borderModule diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.kt index 66ba595e17..1e497e0581 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.kt @@ -1,6 +1,14 @@ package chat.simplex.common.views.newchat +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.* +import androidx.compose.ui.unit.dp +import chat.simplex.common.ui.theme.DEFAULT_PADDING +import chat.simplex.common.ui.theme.DEFAULT_PADDING_HALF @Composable -expect fun QRCodeScanner(onBarcode: (String) -> Unit) +expect fun QRCodeScanner( + showQRCodeScanner: MutableState = remember { mutableStateOf(true) }, + padding: PaddingValues = PaddingValues(horizontal = DEFAULT_PADDING * 2f, vertical = DEFAULT_PADDING_HALF), + onBarcode: (String) -> Unit +) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt index 8534198028..a5442a5bca 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt @@ -84,7 +84,7 @@ private fun CreateSimpleXAddressLayout( Spacer(Modifier.weight(1f)) if (userAddress != null) { - SimpleXLinkQRCode(userAddress.connReqContact, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f)) + SimpleXLinkQRCode(userAddress.connReqContact) ShareAddressButton { share(simplexChatLink(userAddress.connReqContact)) } Spacer(Modifier.weight(1f)) ShareViaEmailButton { sendEmail(userAddress) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt index 44e6969b8d..5acb240cb3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt @@ -339,16 +339,9 @@ private fun DevicesView(deviceName: String, remoteCtrls: SnapshotStateList) { SectionView(stringResource(MR.strings.scan_qr_code_from_desktop).uppercase()) { - Box( - Modifier - .fillMaxWidth() - .aspectRatio(ratio = 1F) - .padding(DEFAULT_PADDING) - ) { - QRCodeScanner { text -> - sessionAddress.value = text - processDesktopQRCode(sessionAddress, text) - } + QRCodeScanner { text -> + sessionAddress.value = text + processDesktopQRCode(sessionAddress, text) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt index a3218c961b..c06265e70c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt @@ -187,11 +187,7 @@ private fun ConnectMobileViewLayout( SectionView { if (invitation != null && sessionCode == null && port != null) { Box { - QRCode( - invitation, Modifier - .padding(start = DEFAULT_PADDING, top = DEFAULT_PADDING_HALF, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING_HALF) - .aspectRatio(1f) - ) + QRCode(invitation) if (staleQrCode) { Box(Modifier.matchParentSize().background(MaterialTheme.colors.background.copy(alpha = 0.9f)), contentAlignment = Alignment.Center) { SimpleButtonDecorated(stringResource(MR.strings.refresh_qr_code), painterResource(MR.images.ic_refresh), click = refreshQrCode) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/IncognitoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/IncognitoView.kt index e264172f9c..6da0d34bd3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/IncognitoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/IncognitoView.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.unit.dp import chat.simplex.common.ui.theme.DEFAULT_PADDING import chat.simplex.common.views.helpers.AppBarTitle import chat.simplex.common.views.helpers.generalGetString +import chat.simplex.common.views.onboarding.ReadableTextWithLink import chat.simplex.res.MR @Composable @@ -31,6 +32,7 @@ fun IncognitoLayout() { Text(generalGetString(MR.strings.incognito_info_protects)) Text(generalGetString(MR.strings.incognito_info_allows)) Text(generalGetString(MR.strings.incognito_info_share)) + ReadableTextWithLink(MR.strings.read_more_in_user_guide_with_link, "https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode") SectionBottomSpacer() } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt index 4e8da36a7e..08ebc4ef15 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt @@ -160,7 +160,7 @@ private fun CustomServer( if (valid.value) { SectionDividerSpaced() SectionView(stringResource(MR.strings.smp_servers_add_to_another_device).uppercase()) { - QRCode(serverAddress.value, Modifier.aspectRatio(1f).padding(horizontal = DEFAULT_PADDING)) + QRCode(serverAddress.value) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt index 77cb0ead13..502b579d64 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt @@ -20,25 +20,17 @@ fun ScanProtocolServerLayout(rhId: Long?, onNext: (ServerCfg) -> Unit) { Column( Modifier .fillMaxSize() - .padding(horizontal = DEFAULT_PADDING) ) { - AppBarTitle(stringResource(MR.strings.smp_servers_scan_qr), withPadding = false) - Box( - Modifier - .fillMaxWidth() - .aspectRatio(ratio = 1F) - .padding(bottom = 12.dp) - ) { - QRCodeScanner { text -> - val res = parseServerAddress(text) - if (res != null) { - onNext(ServerCfg(remoteHostId = rhId, text, false, null, true)) - } else { - AlertManager.shared.showAlertMsg( - title = generalGetString(MR.strings.smp_servers_invalid_address), - text = generalGetString(MR.strings.smp_servers_check_address) - ) - } + AppBarTitle(stringResource(MR.strings.smp_servers_scan_qr)) + QRCodeScanner { text -> + val res = parseServerAddress(text) + if (res != null) { + onNext(ServerCfg(remoteHostId = rhId, text, false, null, true)) + } else { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.smp_servers_invalid_address), + text = generalGetString(MR.strings.smp_servers_check_address) + ) } } } 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 915120d81d..299be43223 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 @@ -207,7 +207,7 @@ private fun UserAddressLayout( val autoAcceptState = remember { mutableStateOf(AutoAcceptState(userAddress)) } val autoAcceptStateSaved = remember { mutableStateOf(autoAcceptState.value) } SectionView(stringResource(MR.strings.address_section_title).uppercase()) { - SimpleXLinkQRCode(userAddress.connReqContact, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f)) + SimpleXLinkQRCode(userAddress.connReqContact) ShareAddressButton { share(simplexChatLink(userAddress.connReqContact)) } ShareViaEmailButton { sendEmail(userAddress) } ShareWithContactsButton(shareViaProfile, setProfileAddress) diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml index 1e6f1b0643..883e5e5f46 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -147,8 +147,6 @@ تم تغيير العنوان من أجلك لا يمكن حذف ملف تعريف المستخدم! طلب لاستلام الفيديو - إضافة جهة اتصال جديدة : لإنشاء رمز الاستجابة السريعة الخاص بك لمرة واحدة لجهة اتصالك.]]> - امسح رمز الاستجابة السريعة : للاتصال بجهة الاتصال التي تعرض لك رمز الاستجابة السريعة.]]> مكالمتك تحت الإجراء تغيير عبارة مرور قاعدة البيانات؟ لا يمكن الوصول إلى Keystore لحفظ كلمة مرور قاعدة البيانات @@ -198,7 +196,6 @@ قارن الملف خطأ إنشاء مجموعة سرية - إنشاء رابط دعوة لمرة واحدة خطأ في إحباط تغيير العنوان تفعيل قفل SimpleX تأكد من بيانات الاعتماد الخاصة بك @@ -1324,7 +1321,7 @@ لا يمكنك إرسال رسائل! تحتاج إلى السماح لجهة الاتصال الخاصة بك بإرسال رسائل صوتية لتتمكن من إرسالها. أرسلت جهة اتصالك ملفًا أكبر من الحجم الأقصى المعتمد حاليًا (%1$s). - الاتصال بمطوري SimpleX Chat لطرح أي أسئلة وتلقي التحديثات.]]> + الاتصال بمطوري SimpleX Chat لطرح أي أسئلة وتلقي التحديثات.]]> خادمك يُخزن ملف تعريفك على جهازك ومشاركته فقط مع جهات اتصالك. لا تستطيع خوادم SimpleX رؤية ملف تعريفك. الفيديو مقفل 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 4ad40d7a62..e30b4eb56d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -293,9 +293,11 @@ Tap to start a new chat Chat with the developers You have no chats + Loading chats… No filtered chats Tap to Connect Connect with %1$s? + Search or paste SimpleX link No selected chat @@ -427,6 +429,11 @@ (scan or paste from clipboard) (only stored by group members) + + Enable camera access + Tap to scan + Camera not available + Permission Denied! Camera @@ -442,8 +449,8 @@ To start a new chat Tap button above, then: - Add new contact: to create your one-time QR Code for your contact.]]> - Scan QR code: to connect to your contact who shows QR code to you.]]> + Add contact: to create a new invitation link, or connect via a link you received.]]> + Create group: to create a new group.]]> To connect via link If you received SimpleX Chat invitation link, you can open it in your browser: Scan QR code.]]> @@ -546,11 +553,26 @@ This string is not a connection link! Open in mobile app button.]]> - - Create one-time invitation link + + New chat + Add contact One-time invitation link 1-time link SimpleX address + Or show this code + Or scan QR code + Keep unused invitation? + You can view invitation link again in connection details. + Keep + Creating link… + Retry + Share this 1-time invite link + Paste the link you received + The text you pasted is not a SimpleX link. + Tap to paste link + + Invalid QR code + The code you scanned is not a SimpleX link QR code. Scan code @@ -1708,20 +1730,20 @@ Connect to yourself? This is your own one-time link! - You are already connecting to %1$s. + %1$s.]]> Already connecting! You are already connecting via this one-time link! This is your own SimpleX address! Repeat connection request? You have already requested connection via this address! Join your group? - This is your link for group %1$s! + %1$s!]]> Open group Repeat join request? Group already exists! - You are already joining the group %1$s. + %1$s.]]> Already joining the group! You are already joining the group via this link. - You are already in group %1$s. + %1$s.]]> Connect via link? \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml index bdb9b39beb..4101b1c932 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml @@ -119,8 +119,6 @@ Назад Отказ Спри живото съобщение - Добави нов контакт: за да създадете своя еднократен QR код за вашия контакт.]]> - Сканирай QR код: за да се свържете с вашия контакт, който ви показва QR код.]]> Камера Ако не можете да се срещнете лично, покажете QR код във видеоразговора или споделете линка. спри визуализацията на линка @@ -404,7 +402,6 @@ Изтрий Изтрий Свърване чрез линк - Създай линк за еднократна покана Парола за базата данни и експортиране Допринеси Продължи @@ -794,7 +791,7 @@ Без звук QR код Повече - Ръководство за потребителя.]]> + Ръководство за потребителя.]]> Маркирай като проверено %s не е потвърдено %s е потвърдено @@ -814,7 +811,7 @@ Няма се използват Onion хостове. Нека да поговорим в SimpleX Chat Парола за показване - GitHub хранилище.]]> + GitHub хранилище.]]> Когато приложението работи Периодично Постави получения линк @@ -1328,7 +1325,7 @@ Ще трябва да се идентифицирате, когато стартирате или възобновите приложението след 30 секунди във фонов режим. вие сте наблюдател Видео - се свържете с разработчиците на SimpleX Chat, за да задавате въпроси и да получавате актуализации;.]]> + се свържете с разработчиците на SimpleX Chat, за да задавате въпроси и да получавате актуализации;.]]> иска да се свърже с вас! Отваряне в мобилно приложение.]]> XFTP сървъри diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml index 83fc06e87e..ae19884220 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml @@ -191,8 +191,6 @@ Pro zahájení nové konverzace Klepněte na tlačítko potom: - Přidejte nový kontakt: vytvořte jednorázý QR kód pro váš kontakt.]]> - Naskenujte QR kód: připojíte se ke kontaktu, který vám QR kód ukázal.]]> Skenovat QR kód.]]> Vyčistit chat\? Vyčistit @@ -209,7 +207,6 @@ Ke skupině budete připojeni, až bude zařízení hostitele skupiny online, vyčkejte prosím nebo se podívejte později! Budete připojeni, jakmile bude vaše žádost o připojení přijata, vyčkejte prosím nebo se podívejte později! Požadavek na připojení byl odeslán! - Vytvořit jednorázovou pozvánku Jednorázová pozvánka Bezpečnostní kód %s je ověřeno 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 7401c0d4d9..20fa5c88aa 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -262,12 +262,10 @@ Video Danke, dass Sie SimpleX Chat installiert haben! - mit SimpleX-Chat-Entwicklern verbinden, um Fragen zu stellen und Updates zu erhalten.]]> + mit SimpleX-Chat-Entwicklern verbinden, um Fragen zu stellen und Updates zu erhalten.]]> Um einen neuen Chat zu starten Schaltfläche antippen Danach die gewünschte Aktion auswählen: - Neuen Kontakt hinzufügen: Um Ihren Einmal-QR-Code für Ihren Kontakt zu erstellen.]]> - QR-Code scannen: Um sich mit Ihrem Kontakt zu verbinden, der Ihnen seinen QR-Code zeigt.]]> Über Link verbinden Wenn Sie einen SimpleX-Chat-Einladungslink erhalten haben, können Sie ihn in Ihrem Browser öffnen: QR-Code scannen.]]> @@ -341,7 +339,6 @@ Diese Zeichenfolge entspricht keinem gültigen Verbindungslink! In mobiler App öffnen“.]]> - Einmal-Einladungslink erstellen Einmal-Einladungslink Ihre Einstellungen @@ -1488,14 +1485,14 @@ Erweitern Verbindungsanfrage wiederholen? Gelöschter Kontakt - Sie sind bereits mit %1$s verbunden. + %1$s verbunden.]]> Fehler Sie sind über diesen Link bereits Mitglied der Gruppe. Gruppe erstellen Profil erstellen %s und %s Ihrer Gruppe beitreten? - Sie sind bereits Mitglied in der Gruppe %1$s. + %1$s.]]> Das ist Ihr eigener Einmal-Link! %d Nachrichten als gelöscht markiert Gruppe besteht bereits! @@ -1510,7 +1507,7 @@ Mitglied freigeben Mit Ihnen selbst verbinden? Zum Verbinden antippen - Sie sind bereits Mitglied in der Gruppe %1$s. + %1$s.]]> Das ist Ihre eigene SimpleX-Adresse! Richtiger Name für %s? %d Nachrichten löschen? @@ -1531,7 +1528,7 @@ Mitglied blockieren? %d Gruppenereignisse Ungültiger Name! - Das ist Ihr Link für die Gruppe %1$s! + %1$s!]]> Freigeben Ungültiger Datei-Pfad Sie haben über diese Adresse bereits eine Verbindung beantragt! diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index a656b932de..928f007635 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -27,7 +27,6 @@ Siempre activo Permitir y después: - Añadir nuevo contacto: para crear tu código QR de un solo uso para tu contacto.]]> ¿Aceptar solicitud de conexión\? Aceptar incógnito Se eliminarán todos los mensajes SOLO para tí. ¡No podrá deshacerse! @@ -79,9 +78,7 @@ ¡Consume más batería! El servicio en segundo plano se ejecuta continuamente y las notificaciones se mostrarán de inmediato.]]> Tanto tú como tu contacto podéis eliminar de forma irreversible los mensajes enviados. Tanto tú como tu contacto podéis enviar mensajes temporales. - Escanear código QR: para conectar con tu contacto mediante su código QR.]]> Crear - Crea enlace de invitación de un uso Crea grupo secreto La contraseña de cifrado de la base de datos será actualizada. ID base de datos diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml index be4072c4bf..78edeaecda 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml @@ -188,7 +188,6 @@ Salli kontaktiesi lähettää katoavia viestejä. Katoavat viestit Kontekstikuvake - Skannaa QR-koodi: muodostaaksesi yhteyden kontaktiisi, joka näyttää QR-koodin sinulle.]]> Peruuta live-viesti Määritä ICE-palvelimet Lisää osoite profiiliisi, jotta kontaktisi voivat jakaa sen muiden kanssa. Profiilipäivitys lähetetään kontakteillesi. @@ -231,7 +230,6 @@ Tyhjennä Tyhjennä keskustelu Tyhjennä keskustelu\? - Luo kertaluonteinen kutsulinkki Sovellusversio: v%s soittaa… Poista keskusteluprofiili @@ -271,7 +269,6 @@ Katkaistu Takaisin Yhdistä linkillä / QR-koodilla - Lisää uusi kontakti: luo kertakäyttöinen QR-koodi kontaktille.]]> Tyhjennä Skannaa QR-koodi.]]> Poistetaanko odottava yhteys\? diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml index 5ad0b3cbfc..4532e0aff5 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml @@ -206,7 +206,6 @@ Vidéo Pour démarrer une nouvelle discussion Appuyez sur le bouton - Scanner un code QR : pour vous connecter à votre contact qui vous montre un code QR.]]> Pour se connecter via un lien Si vous avez reçu un lien d\'invitation SimpleX Chat, vous pouvez l\'ouvrir dans votre navigateur : Scanner le code QR.]]> @@ -246,7 +245,6 @@ Coller Cette chaîne n\'est pas un lien de connexion ! Ouvrir dans l\'app mobile.]]> - Créer un lien d\'invitation unique Définir le nom du contact… Déconnecté Erreur @@ -296,7 +294,6 @@ Merci d\'avoir installé SimpleX Chat ! vous connecter aux développeurs de SimpleX Chat pour leur poser des questions et recevoir des réponses :.]]> ci-dessus, puis : - Ajouter un nouveau contact : afin de créer un code QR à usage unique pour votre contact.]]> Si vous choisissez de la rejeter, l\'expéditeur·rice NE sera PAS notifié·e. Accepter Muet @@ -1407,14 +1404,14 @@ Développer Répéter la demande de connexion ? contact supprimé - Vous êtes déjà connecté(e) à %1$s. + %1$s.]]> Erreur Vous êtes déjà en train de rejoindre le groupe via ce lien. Créer un groupe Créer le profil %s et %s Rejoindre votre groupe ? - Vous êtes déjà en train de rejoindre le groupe %1$s. + %1$s.]]> Voici votre propre lien unique ! %d messages marqués comme supprimés Ce groupe existe déjà ! @@ -1429,7 +1426,7 @@ Débloquer ce membre Se connecter à soi-même ? Tapez pour vous connecter - Vous êtes déjà dans le groupe %1$s. + %1$s.]]> Voici votre propre adresse SimpleX ! Corriger le nom pour %s ? Supprimer %d messages ? @@ -1450,7 +1447,7 @@ Bloquer ce membre ? %d événements de groupe Nom invalide ! - Voici votre lien pour le groupe %1$s ! + %1$s !]]> Débloquer Chemin du fichier invalide Vous avez déjà demandé une connexion via cette adresse ! diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index fac97864f7..6e2ba94ea9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -44,7 +44,6 @@ minden chat profilodra az appban.]]> Mindketten, te és az ismerősöd is küldhettek eltűnő üzeneteket. Az Android Keystore-t jelmondat biztonságos tárolására használják - lehetővé teszi az értesítési szolgáltatás működését. - QR-kód beolvasása: kapcsolódás ismerőshöz a megmutatott QR-kódja alapján]]> Téves üzenet hash Felhasználói profil törlése nem lehetséges! Háttér @@ -117,7 +116,6 @@ vastagított Az app számkód helyettesítésre kerül egy önmegsemmisítő számkóddal. Arab, bulgár, finn, héber, thai és ukrán - köszönet a felhasználóknak és a Weblate-nek! - Új ismerős hozzáadása: egyszer használatos QR-kód készítése az ismerős számára.]]> Hangüzenetek engedélyezése? Mindig használt relay szervert mindig @@ -338,7 +336,6 @@ Csatlakoztatva a mobilhoz Jelenlegi jelmondat… Fájl választása - Egyszer használatos meghívó link létrehozása Kép törlése Fájl létrehozása Tikos csoport létrehozása @@ -1432,7 +1429,7 @@ A chat szolgáltatást elindíthatod a beállítások / adatbázis pontban vagy az app újraindításával. Ellenőrizd a kódot a mobilon! Csatlakoztál ehhez a csoporthoz. Kapcsolódás a meghívó csoporttaghoz. - a SimpleX Chat fejlesztőivel és kérdezhetsz bármit és értesülhetsz az újdonságokról.]]> + a SimpleX Chat fejlesztőivel és kérdezhetsz bármit és értesülhetsz az újdonságokról.]]> Opcionális üdvözlő szöveggel. Ismeretlen adatbázis hiba: %s Elrejtheted vagy némíthatod egy felhasználó profilját - tartsd lenyomva a menühöz! @@ -1518,13 +1515,13 @@ A titkosítás működik, és új titkosítási egyezményre nincs szükség. Ez kapcsolati hibákat eredményezhet! Ez a művelet nem vonható vissza - profilod, ismerőseid, üzeneteid és fájljaid visszafordíthatatlanul törlésre kerülnek. A bejegyzés frissítve - Felhasználói útmutatóban olvasható.]]> + Felhasználói útmutatóban olvasható.]]> A jelmondat a beállításokban egyszerű szövegként van tárolva. Konzol megjelenítése új ablakban Az előző üzenet hash-e más. Ezek a beállítások a jelenlegi profilodra vonatkoznak Kérjük, várj, amíg a fájl betöltődik az összekapcsolt mobilról. - GitHub tárolónkban.]]> + GitHub tárolónkban.]]> hiba a tartalom megjelenítése közben hiba az üzenet megjelenítésekor \ No newline at end of file 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 f01568c03b..21713ba93f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -292,8 +292,6 @@ Elimina I messaggi diretti tra i membri sono vietati in questo gruppo. Inserisci il tuo nome: - Aggiungi un contatto: per creare il tuo codice QR una tantum per il tuo contatto.]]> - Scansiona codice QR: per connetterti al contatto che ti mostra il codice QR.]]> File Svuota chat Svuotare la chat\? @@ -319,7 +317,6 @@ Annulla la verifica Connetti Connetti via link - Crea link di invito una tantum Password del database ed esportazione Inserisci il server manualmente Come si usa @@ -1407,14 +1404,14 @@ Espandi Ripetere la richiesta di connessione? contatto eliminato - Ti stai già connettendo a %1$s. + %1$s.]]> Errore Stai già entrando nel gruppo tramite questo link. Crea gruppo Crea profilo %s e %s Entrare nel tuo gruppo? - Stai già entrando nel gruppo %1$s. + %1$s.]]> Questo è il tuo link una tantum! %d messaggi contrassegnati eliminati Il gruppo esiste già! @@ -1429,7 +1426,7 @@ Sblocca membro Connettersi a te stesso? Tocca per connettere - Sei già nel gruppo %1$s. + %1$s.]]> Questo è il tuo indirizzo SimpleX! Correggere il nome a %s? Eliminare %d messaggi? @@ -1450,7 +1447,7 @@ Bloccare il membro? %d eventi del gruppo Nome non valido! - Questo è il tuo link per il gruppo %1$s! + %1$s!]]> Sblocca Percorso file non valido Hai già richiesto la connessione tramite questo indirizzo! diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml index 58bb6b0a0a..e1657ed6db 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml @@ -93,7 +93,6 @@ אשר אוטומטית בקשות ליצירת קשר. אימות בוטל אימות לא זמין - הוסיפו איש קשר חדש: ליצירת קוד QR חד־פעמי עבור איש הקשר שלכם.]]> הטוב ביותר לסוללה. התראות יוצגו רק כאשר האפליקציה מופעלת (ללא שירות רקע).]]> טוב לסוללה. שירות הרקע ייבדוק הודעות כל 10 דקות. שיחות או הודעות דחופות עלולות להתפספס.]]> גם אתם וגם איש הקשר יכולים למחוק באופן בלתי הפיך הודעות שנשלחו. @@ -103,7 +102,6 @@ ביטול בטל הודעה חיה מצלמה - סירקו קוד QR: כדי להתחבר לאיש קשר המציג לכם קוד QR.]]> בטל תצוגה מקדימה של קישורים שגיאת שיחה שיחה מתמשכת @@ -219,7 +217,6 @@ הועתק ללוח צור קישור הזמנה חד־פעמי צור קבוצה סודית - צור קישור הזמנה חד־פעמי תרומה גרסת ליבה: v%s צור כתובת diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml index bebf716e0d..419e0c3a6e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml @@ -73,12 +73,10 @@ 認証不可能 画像を自動的に受信 バックグラウンド機能が常にオンで、メッセージが到着次第に通知が出ます。 - 新しい連絡先を追加:使い捨てのQRコードを発行]]> 電池省エネをオンに、バックグラウンド機能と定期的な受信依頼をオフにします。設定メニューにて変更できます。 電池消費が最少:アプリがアクティブ時のみに通知が出ます(バックグラウンドサービス無し)。]]> 設定メニューにてオフにできます。 アプリがアクティブ時に通知が出ます。]]> あなたと連絡相手が送信済みメッセージを永久削除できます。 - QRコードを読み込み:連絡相手のQRコードをスキャンすると繋がります。]]> チャットのアーカイブ チャットのアーカイブを削除しますか? シークレットモードで参加 @@ -524,7 +522,6 @@ ミュート 接続待ちの繋がりを削除しますか? 接続 - 使い捨てリンクを発行する 使い捨ての招待リンク データベース暗証フレーズとエキスポート 使い方 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml index 3c7554c972..b059cc5ccd 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml @@ -27,7 +27,6 @@ 컨텍스트 아이콘 연결됨 뒤로 - 새 대화 상대 추가 : 대화를 위한 일회용 QR 코드 만들기]]> 취소 라이브 메시지 취소 파일 선택 @@ -171,7 +170,6 @@ 배터리에 가장 좋음. 앱이 실행 중일 때만 알림을 받게 됩니다 (백그라운드에서 실행되지 않음).]]> 설정을 통해 비활성화할 수 있습니다. – 앱이 실행되는 동안 알림이 표시됩니다.]]> 당신과 대화 상대 모두 사라지는 메시지를 보낼 수 있습니다. - QR 코드 스캔: QR 코드를 보여주는 사람과 대화할 수 있습니다.]]> 데이터베이스 암호를 저장하고 있는 Keystore에 접근할 수 없습니다. 배터리 더욱 사용! 백그라운드 서비스가 항상 실행됩니다. - 메시지를 수신되는 즉시 알림이 표시됩니다.]]> 통화 종료됨 %1$s @@ -202,7 +200,6 @@ 대화 상대와 종단간 암호화됨 대화 상대와 아직 연결되지 않았습니다! %1$s에 생성 완료 - 일회용 초대 링크 생성 비밀 그룹 생성 익명 수락 1개월 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml index 163645c7d5..5d74cac33a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml @@ -117,7 +117,6 @@ QR kodas pagalba El. paštas - Sukurti vienkartinio pakvietimo nuorodą Skenuoti kodą Duomenų bazės slaptafrazė ir eksportavimas Ištrinti serverį @@ -387,8 +386,6 @@ Gali būti, kad liudijimo kontrolinis kodas serverio adrese yra neteisingas Ištrinti failą Dekodavimo klaida - Pridėti naują adresatą: norėdami sukurti adresatui vienkartinį QR kodą.]]> - Skenuoti QR kodą: norėdami prisijungti prie adresato, kuris jums rodo QR kodą.]]> Išvalyti pokalbį Įjungti pranešimus Neteisingas QR kodas diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index bb5226e3d4..b9dfb6866e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -66,7 +66,6 @@ Spraak berichten toestaan\? Goed voor de batterij. Achtergrondservice controleert berichten elke 10 minuten. Mogelijk mist u oproepen of dringende berichten.]]> Onjuiste bericht hash - Scan QR-code: om verbinding te maken met uw contact die u de QR-code laat zien.]]> Onjuiste bericht-ID Oproep al beëindigd! 1 maand @@ -94,7 +93,6 @@ voor elk chat profiel dat je in de app hebt .]]> audio oproep (niet e2e versleuteld) Achtergrondservice is altijd actief, meldingen worden weergegeven zodra de berichten beschikbaar zijn. - Nieuw contact toevoegen: om een eenmalige QR-code voor uw contact te maken.]]> Oproep beëindigd Batterijoptimalisatie is actief, waardoor achtergrondservice en periodieke verzoeken om nieuwe berichten worden uitgeschakeld. Je kunt ze weer inschakelen via instellingen. Het beste voor de batterij. U ontvangt alleen meldingen wanneer de app wordt uitgevoerd (GEEN achtergrondservice).]]> @@ -203,7 +201,6 @@ Verwijderd verificatie Verbind Maak verbinding via link - Maak een eenmalige uitnodiging link gekleurd Oproep verbinden… Maak @@ -1442,17 +1439,17 @@ Console in nieuw venster weergeven Alle nieuwe berichten van %s worden verborgen! geblokkeerd - Je bent al verbonden met %1$s. + %1$s.]]> Je wordt al lid van de groep via deze link. - Je bent al lid van de groep %1$s. + %1$s.]]> Dit is uw eigen eenmalige link! Lid deblokkeren - Je zit al in groep %1$s. + %1$s.]]> Dit is uw eigen SimpleX adres! Lid deblokkeren? Je maakt al verbinding via deze eenmalige link! Je hebt een ongeldig bestandslocatie gedeeld. Rapporteer het probleem aan de app-ontwikkelaars. - Dit is jouw link voor groep %1$s! + %1$s!]]> Deblokkeren U heeft al een verbinding aangevraagd via dit adres! Fout bij heronderhandeling van codering diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml index e811e47ea1..6e1fdfbbba 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml @@ -229,7 +229,6 @@ Akceptuj Akceptuj incognito Wszystkie wiadomości zostaną usunięte - nie można tego cofnąć! Wiadomości zostaną usunięte TYLKO dla Ciebie. - Zeskanuj kod QR: aby połączyć się z kontaktem, który pokaże Ci kod QR.]]> anuluj podgląd linku Wyczyść Wyczyść @@ -917,7 +916,6 @@ Uwierzytelnianie niedostępne Dołącz Wstecz - Dodaj nowy kontakt: aby stworzyć swój jednorazowy kod QR dla kontaktu.]]> Optymalizacja baterii jest aktywna, wyłącza usługi w tle i okresowe żądania nowych wiadomości. Możesz je ponownie włączyć za pośrednictwem ustawień. Można je wyłączyć poprzez ustawienia - powiadomienia nadal będą pokazywane podczas działania aplikacji.]]> Najlepsze dla baterii. Będziesz otrzymywać powiadomienia tylko wtedy, gdy aplikacja jest uruchomiona (NIE w tle).]]> @@ -933,7 +931,6 @@ Błąd połączenia (UWIERZYTELNIANIE) Połącz się przez link / kod QR Utworzony na %1$s - Utwórz jednorazowy link do zaproszenia Utwórz tajną grupę Utwórz tajną grupę Baza danych jest zaszyfrowana przy użyciu losowego hasła. Proszę zmienić je przed eksportem. @@ -1427,12 +1424,12 @@ zablokowany Rozszerz Powtórzyć prośbę połączenia? - Już jesteś połączony z %1$s. + %1$s.]]> Błąd Już dołączasz do grupy przez ten link. %s i %s Dołączyć do twojej grupy? - Już dołączasz do grupy %1$s. + %1$s.]]> To jest twój jednorazowy link! Grupa już istnieje! Wideo nie może zostać zdekodowane, spróbuj inne wideo lub skontaktuj się z deweloperami. @@ -1440,7 +1437,7 @@ %s, %s i %d członków Odblokuj członka Dotknij aby połączyć - Już jesteś w grupie %1$s. + %1$s.]]> To jest twój własny adres SimpleX! Usuń członka Odblokować członka? @@ -1452,7 +1449,7 @@ Błąd wysyłania zaproszenia Udostępniłeś nieprawidłową ścieżkę pliku. Zgłoś problem do deweloperów aplikacji. Nieprawidłowa nazwa! - To jest twój link zaproszenia do grupy %1$s! + %1$s!]]> Odblokuj Nieprawidłowa ścieżka pliku Już prosiłeś o połączenie na ten adres! diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml index 622ad8b2d6..769375604d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml @@ -24,8 +24,6 @@ Cancelar mensagem ao vivo Voltar Arquivo - Adicionar novo contato: para criar seu QR code de uso único para seu contato.]]> - Escanear código QR: para se conectar ao seu contato que mostra o código QR para você.]]> Aceitar Limpar chat\? Limpar @@ -374,7 +372,6 @@ A autenticação do dispositivo está desativada. Desativando o bloqueio SimpleX. Para todos Oculto - Gerar um link de convite de uso único. Como usar seus servidores Importar Importar banco de dados de chat\? diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml index 072eb97ebf..e755d9aab9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml @@ -211,7 +211,6 @@ Grupo Áudio ligado Autenticação cancelada - Adicionar novo contato: para criar o seu código QR de utilização única para o seu contato.]]> Bom para a bateria . O serviço em segundo plano verifica se há mensagens a cada 10 minutos. Você pode perder chamadas ou mensagens urgentes.]]> A otimização da bateria está ativa, desativando o serviço em segundo plano e os pedidos periódicos de novas mensagens. Você pode reativá-los através das definições. Melhor para a bateria. Apenas receberá notificações enquanto a app estiver em execução (SEM serviço em segundo plano)]]> @@ -225,7 +224,6 @@ Chamadas de áudio/vídeo são proibidas. O serviço em segundo plano está sempre em execução - as notificações serão exibidas assim que as mensagens estiverem disponíveis. Autenticar - Leia o código QR : para se conectar ao seu contato que lhe mostra o código QR.]]> chamada finalizada %1$s a chamar… erro de chamada @@ -366,7 +364,6 @@ Erro de conexão conexão %1$d O contato já existe - Criar convite de ligação de utilização única Convite de ligação de utilização única Salvar Modo anónimo 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 76a49f678d..aa0e3282f2 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml @@ -267,12 +267,10 @@ Чтобы начать новый чат Нажмите кнопку сверху, затем: - Добавить новый контакт: чтобы создать одноразовый QR код/ссылку для Вашего контакта.]]> - Сканировать QR код: чтобы соединиться с контактом, который показывает Вам QR код.]]> Чтобы соединиться через ссылку Если Вы получили ссылку с приглашением из SimpleX Chat, Вы можете открыть ее в браузере: Сканировать QR код.]]> - Open in mobile app на веб странице, затем нажмите Соединиться в приложении.]]> + Open in mobile app на веб странице, затем нажмите Соединиться в приложении.]]> Принять запрос на соединение? Отправителю НЕ будет послано уведомление, если Вы отклоните запрос на соединение. @@ -339,7 +337,6 @@ Соединиться Вставить - Создать одноразовую ссылку Одноразовая ссылка Настройки @@ -1566,9 +1563,9 @@ Все новые сообщения от %s будут скрыты! Версия настольного приложения %s несовместима с этим приложением. заблокировано - Вы уже соединяетесь с %1$s. + %1$s.]]> Вы уже вступаете в группу по этой ссылке. - Вы уже вступаете в группу %1$s. + %1$s.]]> Это ваша собственная одноразовая ссылка! Через безопасный квантово-устойчивый протокол. Чтобы скрыть нежелательные сообщения. @@ -1580,7 +1577,7 @@ Разблокировать члена группы Нажмите чтобы соединиться Имя этого устройства - Вы уже состоите в группе %1$s. + %1$s.]]> Это ваш собственный адрес SimpleX! Разблокировать члена группы? Использовать с компьютера @@ -1590,7 +1587,7 @@ Имя устройства будет доступно подключенному мобильному клиенту. Сверьте код на мобильном Указан неверный путь к файлу. Сообщите о проблеме разработчикам приложения. - Это ваша ссылка на группу %1$s! + %1$s!]]> Сверьте код с компьютером Сканировать QR код с компьютера Разблокировать diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml index 91330717c4..3eec7777f6 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml @@ -112,8 +112,6 @@ ทั้งคุณและผู้ติดต่อของคุณสามารถเพิ่มปฏิกิริยาต่อข้อความได้ ทั้งคุณและผู้ติดต่อของคุณสามารถลบข้อความที่ส่งแล้วอย่างถาวรได้ กล้อง - เพิ่มผู้ติดต่อใหม่ : เพื่อสร้างรหัส QR แบบใช้ครั้งเดียวสําหรับผู้ติดต่อของคุณ]]> - สแกนรหัส QR: เพื่อเชื่อมต่อกับผู้ติดต่อที่แสดงรหัส QR ให้คุณ]]> เกี่ยวกับที่อยู่ SimpleX ตัวหนา กำลังโทร… @@ -205,7 +203,6 @@ ปุ่มปิด เชื่อมต่อ เชื่อมต่อผ่านลิงก์ - สร้างลิงก์เชิญแบบใช้ครั้งเดียว ล้างการยืนยัน คอนโซลแชท ตรวจสอบที่อยู่เซิร์ฟเวอร์แล้วลองอีกครั้ง diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml index 8a3eeef61d..1cffad1df1 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml @@ -3,7 +3,6 @@ Bildirimler Bağlantı ya da karekod ile bağlan Gizli grup oluştur - Tek seferlik davet bağlantısı oluştur SimpleX addresin cevapsız çağrı Gelen görüntülü arama @@ -736,7 +735,6 @@ Geri alınamaz mesaj silme İtalyanca arayüz Dosya seç - QR kodunu tara: size QR kodunu gösteren kişiyle bağlantı kurmak için.]]> Arkadaşlarınızı davet edin kalın İtalik @@ -790,7 +788,6 @@ Mesajlar silinecek - bu geri alınamaz! Alıcı adresini değiştir\? Geri - Yeni kişi ekle: Kişiniz için tek seferlik QR Kodunuzu oluşturmak için.]]> Bağlantı isteğiniz kabul edildiğinde bağlanacaksınız, lütfen bekleyin veya daha sonra kontrol edin! Kişinizin cihazı çevrimiçi olduğunda bağlanacaksınız, lütfen bekleyin veya daha sonra kontrol edin! Daha fazla bilgi edinin @@ -902,7 +899,7 @@ Kişiniz desteklenen maksimum boyuttan (%1$s) daha büyük bir dosya gönderdi. Kişiniz yüklemeyi tamamladığında video alınacaktır. mobil uygulamada aç seçeneğine tıklayın.]]> - SimpleX Chat geliştiricilerine bağlanabilirsiniz.]]> + SimpleX Chat geliştiricilerine bağlanabilirsiniz.]]> Bir kullanıcının profilini gizleyebilir veya sessize alabilirsiniz - menü için basılı tutun. Sohbet veritabanınızın en son sürümünü SADECE bir cihazda kullanmalısınız, aksi takdirde bazı kişilerden daha fazla mesaj alamayabilirsiniz. Yanlış veritabanı parolası @@ -976,7 +973,7 @@ Link ile bağlanmak için Bu geçerli bir bağlantı linki değil Bu QR kodu bir bağlantı değil! - Kullanıcı Kılavuzu.]]> + Kullanıcı Kılavuzu.]]> Yapıştır Bu dize bir bağlantı linki değil! Uygulamaya puan verin @@ -1020,7 +1017,7 @@ Favorilerden çıkar Sohbeti gizli yap! Profil güncellemesi kişilerinize gönderilecektir. - GitHub repomuzda daha fazlasını okuyun.]]> + GitHub repomuzda daha fazlasını okuyun.]]> Lütfen geliştiricilere bildirin. Profil ve sunucu bağlantıları gizlemeyi kaldır diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml index faf2895827..beb5584b9a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml @@ -104,7 +104,6 @@ поганий хеш повідомлення поганий ідентифікатор повідомлення Фон - Додайте новий контакт: щоб створити одноразовий QR-код для вашого контакту.]]> Це можна вимкнути у налаштуваннях – сповіщення все одно будуть відображатися, коли програма працює. Служба фонового режиму завжди активна – сповіщення відображатимуться, як тільки повідомлення будуть доступні. Запит на отримання зображення @@ -356,7 +355,6 @@ Це посилання не є дійсним з\'єднувальним посиланням! Запит на з\'єднання відправлено! Відкрити у мобільному додатку.]]> - Створити одноразове запрошення Сканувати код Скануйте код безпеки з додатка вашого контакту. Невірна адреса сервера! @@ -529,7 +527,7 @@ зображення профілю Більше Створити профіль - GitHub.]]> + GitHub.]]> Відео увімкнено Це може трапитися, якщо ви або ваше з\'єднання використовували застарілу резервну копію бази даних. Відновити резервну копію бази даних @@ -606,7 +604,6 @@ Доступ відхилено! Камера Дякуємо за установку SimpleX Chat! - Сканувати QR-код: щоб підключитися до вашого контакту, який вам показує QR-код.]]> Якщо ви отримали запрошення від SimpleX Chat, ви можете відкрити його у вашому браузері: Очистити Видалити @@ -1028,7 +1025,7 @@ Вас підключать, коли пристрій вашого контакту буде в мережі, зачекайте або перевірте пізніше! Ви не втратите свої контакти, якщо ви пізніше видалите свою адресу. Коли люди просять про з\'єднання, ви можете його прийняти чи відхилити. - Посібнику користувача.]]> + Посібнику користувача.]]> SimpleX-адреса Очистити перевірку %s перевірено @@ -1200,7 +1197,7 @@ Створити секретну групу (щоб поділитися з вашим контактом) (сканувати або вставити з буферу обміну) - підключитися до розробників SimpleX Chat, щоб задати будь-які питання і отримувати оновлення.]]> + підключитися до розробників SimpleX Chat, щоб задати будь-які питання і отримувати оновлення.]]> Сканувати QR-код.]]> Адреса SimpleX Показати QR-код 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 7b8d8ec7eb..449f983606 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 @@ -118,7 +118,6 @@ 每个联系人和群组成员 将使用单独的 TCP 连接(和 SOCKS 凭证)。 \n请注意:如果您有很多连接,您的电池和流量消耗可能会大大增加,并且某些连接可能会失败。 返回 - 添加新联系人:为您的联系人创建一次性二维码。]]> 最长续航 。您只会在应用程序运行时收到通知(无后台服务)。]]> 较长续航 。后台服务每 10 分钟检查一次消息。您可能会错过来电或者紧急信息。]]> 加粗 @@ -129,7 +128,6 @@ 使用更多电量 !后台服务始终运行——一旦收到消息,就会显示通知。]]> 请注意:如果您丢失密码,您将无法恢复或者更改密码。]]> 通话已结束! - 扫描二维码 :与向您展示二维码的联系人联系。]]> 无法邀请联系人! 无法邀请联系人! 取消 @@ -338,7 +336,6 @@ 创建私密群组 不同的名字、头像和传输隔离。 法语界面 - 创建一次性邀请链接 如何使用它 错误 连接中…… @@ -1407,14 +1404,14 @@ 展开 重复连接请求吗? 已删除联系人 - 你已经在连接到 %1$s。 + %1$s。]]> 错误 你已经在通过此链接加入该群。 建群 创建个人资料 %s 和 %s 加入你的群吗? - 你已经在加入 %1$s 群。 + %1$s 群。]]> 这是你自己的一次性链接! %d 条消息被标记为删除 群已存在! @@ -1428,7 +1425,7 @@ 解封成员 连接到你自己? 轻按连接 - 你已经在%1$s 群内。 + %1$s 群内。]]> 这是你自己的 SimpleX 地址! 更正名称为 %s? 删除 %d 条消息吗? @@ -1449,7 +1446,7 @@ 封禁成员吗? %d 个群事件 无效名称! - 这是给你的 %1$s 群链接! + %1$s 群链接!]]> 解封 无效的文件路径 你已经请求通过此地址进行连接! diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml index 7ab98ca323..f5b6e69871 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml @@ -47,8 +47,6 @@ 允許使用語音訊息? 取消 取消實況訊息 - 新增新的聯絡人:建立你的一次性二維碼給你的聯絡人。]]> - 掃描二維碼:連接到向你出示二維碼的聯絡人。]]> 選擇檔案 相機 從圖片庫選擇圖片 @@ -424,7 +422,6 @@ 掃描二維碼。]]> 設定 這個二維碼不是一個連結! - 建立一次性邀請連結 當可行的時候 核心版本:v%s simplexmq: v%s (%2s) diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Modifier.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Modifier.desktop.kt index 6f317acb92..9245f2b950 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Modifier.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Modifier.desktop.kt @@ -4,6 +4,8 @@ import androidx.compose.foundation.contextMenuOpenDetector import androidx.compose.runtime.Composable import androidx.compose.ui.* import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.input.pointer.PointerIcon +import androidx.compose.ui.input.pointer.pointerHoverIcon import java.io.File import java.net.URI @@ -36,3 +38,5 @@ onExternalDrag(enabled) { } actual fun Modifier.onRightClick(action: () -> Unit): Modifier = contextMenuOpenDetector { action() } + +actual fun Modifier.desktopPointerHoverIconHand(): Modifier = Modifier.pointerHoverIcon(PointerIcon.Hand) diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.desktop.kt index 6c37c93ccc..b2fc451969 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.desktop.kt @@ -1,5 +1,6 @@ package chat.simplex.common.views.chatlist +import SectionDivider import androidx.compose.foundation.* import androidx.compose.foundation.interaction.InteractionSource import androidx.compose.foundation.layout.* @@ -33,11 +34,11 @@ actual fun ChatListNavLinkLayout( dropdownMenuItems: (@Composable () -> Unit)?, showMenu: MutableState, stopped: Boolean, - selectedChat: State + selectedChat: State, + nextChatSelected: State, ) { var modifier = Modifier.fillMaxWidth() if (!stopped) modifier = modifier - .background(color = if (selectedChat.value) MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.95f) else Color.Unspecified) .combinedClickable(onClick = click, onLongClick = { showMenu.value = true }) .onRightClick { showMenu.value = true } CompositionLocalProvider( @@ -52,10 +53,17 @@ actual fun ChatListNavLinkLayout( ) { chatLinkPreview() } + if (selectedChat.value) { + Box(Modifier.matchParentSize().background(MaterialTheme.colors.onBackground.copy(0.05f))) + } if (dropdownMenuItems != null) { DefaultDropdownMenu(showMenu, dropdownMenuItems = dropdownMenuItems) } } } - Divider() + if (selectedChat.value || nextChatSelected.value) { + Divider() + } else { + SectionDivider() + } } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/Utils.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/Utils.desktop.kt index 19c9fc0fd7..9fa93cdfdf 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/Utils.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/Utils.desktop.kt @@ -1,14 +1,20 @@ package chat.simplex.common.views.helpers +import androidx.compose.runtime.* import androidx.compose.ui.graphics.* +import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.Density import chat.simplex.common.model.CIFile import chat.simplex.common.model.readCryptoFile import chat.simplex.common.platform.* import chat.simplex.common.simplexWindowState +import kotlinx.coroutines.delay +import java.io.ByteArrayInputStream +import java.io.File import java.io.* import java.net.URI import javax.imageio.ImageIO @@ -17,6 +23,7 @@ import kotlin.io.encoding.ExperimentalEncodingApi private val bStyle = SpanStyle(fontWeight = FontWeight.Bold) private val iStyle = SpanStyle(fontStyle = FontStyle.Italic) +private val uStyle = SpanStyle(textDecoration = TextDecoration.Underline) private fun fontStyle(color: String) = SpanStyle(color = Color(color.replace("#", "ff").toLongOrNull(16) ?: Color.White.toArgb().toLong())) @@ -54,6 +61,22 @@ actual fun escapedHtmlToAnnotatedString(text: String, density: Density): Annotat } break } + text.substringSafe(innerI, 2) == "u>" -> { + val textStart = innerI + 2 + for (insideTagI in textStart until text.length) { + if (text[insideTagI] == '<') { + withStyle(uStyle) { append(text.substring(textStart, insideTagI)) } + skipTil = insideTagI + 4 + break + } + } + break + } + text.substringSafe(innerI, 3) == "br>" -> { + val textStart = innerI + 3 + append("\n") + skipTil = textStart + } text.substringSafe(innerI, 4) == "font" -> { var textStart = innerI + 5 var color = "#000000" @@ -85,6 +108,18 @@ actual fun escapedHtmlToAnnotatedString(text: String, density: Density): Annotat AnnotatedString(text) } +@Composable +actual fun SetupClipboardListener() { + val clipboard = LocalClipboardManager.current + chatModel.clipboardHasText.value = clipboard.hasText() + LaunchedEffect(Unit) { + while (true) { + delay(1000) + chatModel.clipboardHasText.value = clipboard.hasText() + } + } +} + actual fun getAppFileUri(fileName: String): URI { val rh = chatModel.currentRemoteHost.value return if (rh == null) { diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.desktop.kt deleted file mode 100644 index 72d9678154..0000000000 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.desktop.kt +++ /dev/null @@ -1,11 +0,0 @@ -package chat.simplex.common.views.newchat - -import androidx.compose.runtime.* -import chat.simplex.common.model.ChatModel -import chat.simplex.common.model.RemoteHostInfo - -@Composable -actual fun ConnectViaLinkView(m: ChatModel, rh: RemoteHostInfo?, close: () -> Unit) { - // TODO this should close if remote host changes in model - PasteToConnectView(m, rh, close) -} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.desktop.kt index 16d35b5b8d..0142afb4ac 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.desktop.kt @@ -1,8 +1,13 @@ package chat.simplex.common.views.newchat +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.* @Composable -actual fun QRCodeScanner(onBarcode: (String) -> Unit) { +actual fun QRCodeScanner( + showQRCodeScanner: MutableState, + padding: PaddingValues, + onBarcode: (String) -> Unit +) { //LALAL } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.desktop.kt deleted file mode 100644 index 7579f09fa5..0000000000 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.desktop.kt +++ /dev/null @@ -1,15 +0,0 @@ -package chat.simplex.common.views.newchat - -import androidx.compose.runtime.Composable -import chat.simplex.common.model.ChatModel -import chat.simplex.common.model.RemoteHostInfo - -@Composable -actual fun ScanToConnectView(chatModel: ChatModel, rh: RemoteHostInfo?, close: () -> Unit) { - ConnectContactLayout( - chatModel = chatModel, - rh = rh, - incognitoPref = chatModel.controller.appPrefs.incognito, - close = close - ) -} From 05b55d3fb5fb570d576d844d11d0c39f73c7d661 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 29 Dec 2023 21:46:28 +0400 Subject: [PATCH 06/16] ui: group history preference, enable in new groups by default; core: create group history feature items (#3596) * Revert "core: do not create group history item (#3586)" This reverts commit 2834b192ce8e10ef17bb240623df4211bbea378c. * ios: group history preference * fix tests * android * texts * enable in new groups ios * enable in new groups android * android texts * ios texts * remove ellipsis --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- .../Views/Chat/ContactPreferencesView.swift | 1 - .../Chat/Group/GroupPreferencesView.swift | 2 +- .../Shared/Views/NewChat/AddGroupView.swift | 1 + .../Views/UserSettings/PreferencesView.swift | 1 - apps/ios/SimpleXChat/ChatTypes.swift | 57 +++++++++++++------ .../chat/simplex/common/model/SimpleXAPI.kt | 31 +++++++--- .../views/chat/group/GroupPreferences.kt | 5 ++ .../common/views/newchat/AddGroupView.kt | 3 +- .../commonMain/resources/MR/base/strings.xml | 19 ++++--- .../resources/MR/images/ic_schedule.svg | 1 + .../MR/images/ic_schedule_filled.svg | 1 + src/Simplex/Chat.hs | 4 +- src/Simplex/Chat/Types/Preferences.hs | 10 ++-- tests/ChatTests/Groups.hs | 6 +- tests/ChatTests/Utils.hs | 4 +- 15 files changed, 98 insertions(+), 48 deletions(-) create mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/images/ic_schedule.svg create mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/images/ic_schedule_filled.svg diff --git a/apps/ios/Shared/Views/Chat/ContactPreferencesView.swift b/apps/ios/Shared/Views/Chat/ContactPreferencesView.swift index ff1892d996..57007fff3f 100644 --- a/apps/ios/Shared/Views/Chat/ContactPreferencesView.swift +++ b/apps/ios/Shared/Views/Chat/ContactPreferencesView.swift @@ -116,7 +116,6 @@ struct ContactPreferencesView: View { private func featureFooter(_ feature: ChatFeature, _ enabled: FeatureEnabled) -> some View { Text(feature.enabledDescription(enabled)) - .frame(height: 36, alignment: .topLeading) } private func savePreferences() { diff --git a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift index 860a6febb0..d88bdfa4a4 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift @@ -28,6 +28,7 @@ struct GroupPreferencesView: View { featureSection(.reactions, $preferences.reactions.enable) featureSection(.voice, $preferences.voice.enable) featureSection(.files, $preferences.files.enable) + featureSection(.history, $preferences.history.enable) if groupInfo.canEdit { Section { @@ -96,7 +97,6 @@ struct GroupPreferencesView: View { } } footer: { Text(feature.enableDescription(enableFeature.wrappedValue, groupInfo.canEdit)) - .frame(height: 36, alignment: .topLeading) } } diff --git a/apps/ios/Shared/Views/NewChat/AddGroupView.swift b/apps/ios/Shared/Views/NewChat/AddGroupView.swift index 6c7919669b..3f3623033e 100644 --- a/apps/ios/Shared/Views/NewChat/AddGroupView.swift +++ b/apps/ios/Shared/Views/NewChat/AddGroupView.swift @@ -187,6 +187,7 @@ struct AddGroupView: View { hideKeyboard() do { profile.displayName = profile.displayName.trimmingCharacters(in: .whitespaces) + profile.groupPreferences = GroupPreferences(history: GroupPreference(enable: .on)) let gInfo = try apiNewGroup(incognito: incognitoDefault, groupProfile: profile) Task { let groupMembers = await apiListMembers(gInfo.groupId) diff --git a/apps/ios/Shared/Views/UserSettings/PreferencesView.swift b/apps/ios/Shared/Views/UserSettings/PreferencesView.swift index 960afb6d38..2e560f8578 100644 --- a/apps/ios/Shared/Views/UserSettings/PreferencesView.swift +++ b/apps/ios/Shared/Views/UserSettings/PreferencesView.swift @@ -63,7 +63,6 @@ struct PreferencesView: View { private func featureFooter(_ feature: ChatFeature, _ allowFeature: Binding) -> some View { Text(feature.allowDescription(allowFeature.wrappedValue)) - .frame(height: 36, alignment: .topLeading) } private func savePreferences() { diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 02c693cd28..74e5e4a3cb 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -616,8 +616,8 @@ public enum ChatFeature: String, Decodable, Feature { } case .fullDelete: switch allowed { - case .always: return "Allow your contacts to irreversibly delete sent messages." - case .yes: return "Allow irreversible message deletion only if your contact allows it to you." + case .always: return "Allow your contacts to irreversibly delete sent messages. (24 hours)" + case .yes: return "Allow irreversible message deletion only if your contact allows it to you. (24 hours)" case .no: return "Contacts can mark messages for deletion; you will be able to view them." } case .reactions: @@ -653,11 +653,11 @@ public enum ChatFeature: String, Decodable, Feature { : "Disappearing messages are prohibited in this chat." case .fullDelete: return enabled.forUser && enabled.forContact - ? "Both you and your contact can irreversibly delete sent messages." + ? "Both you and your contact can irreversibly delete sent messages. (24 hours)" : enabled.forUser - ? "Only you can irreversibly delete messages (your contact can mark them for deletion)." + ? "Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours)" : enabled.forContact - ? "Only your contact can irreversibly delete messages (you can mark them for deletion)." + ? "Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours)" : "Irreversible message deletion is prohibited in this chat." case .reactions: return enabled.forUser && enabled.forContact @@ -694,6 +694,7 @@ public enum GroupFeature: String, Decodable, Feature { case reactions case voice case files + case history public var id: Self { self } @@ -712,6 +713,7 @@ public enum GroupFeature: String, Decodable, Feature { case .reactions: return NSLocalizedString("Message reactions", comment: "chat feature") case .voice: return NSLocalizedString("Voice messages", comment: "chat feature") case .files: return NSLocalizedString("Files and media", comment: "chat feature") + case .history: return NSLocalizedString("Visible history", comment: "chat feature") } } @@ -723,6 +725,7 @@ public enum GroupFeature: String, Decodable, Feature { case .reactions: return "face.smiling" case .voice: return "mic" case .files: return "doc" + case .history: return "clock" } } @@ -734,6 +737,7 @@ public enum GroupFeature: String, Decodable, Feature { case .reactions: return "face.smiling.fill" case .voice: return "mic.fill" case .files: return "doc.fill" + case .history: return "clock.fill" } } @@ -759,7 +763,7 @@ public enum GroupFeature: String, Decodable, Feature { } case .fullDelete: switch enabled { - case .on: return "Allow to irreversibly delete sent messages." + case .on: return "Allow to irreversibly delete sent messages. (24 hours)" case .off: return "Prohibit irreversible message deletion." } case .reactions: @@ -777,6 +781,11 @@ public enum GroupFeature: String, Decodable, Feature { case .on: return "Allow to send files and media." case .off: return "Prohibit sending files and media." } + case .history: + switch enabled { + case .on: return "Send up to 100 last messages to new members." + case .off: return "Do not send history to new members." + } } } else { switch self { @@ -792,7 +801,7 @@ public enum GroupFeature: String, Decodable, Feature { } case .fullDelete: switch enabled { - case .on: return "Group members can irreversibly delete sent messages." + case .on: return "Group members can irreversibly delete sent messages. (24 hours)" case .off: return "Irreversible message deletion is prohibited in this group." } case .reactions: @@ -810,6 +819,11 @@ public enum GroupFeature: String, Decodable, Feature { case .on: return "Group members can send files and media." case .off: return "Files and media are prohibited in this group." } + case .history: + switch enabled { + case .on: return "Up to 100 last messages are sent to new members." + case .off: return "History is not sent to new members." + } } } } @@ -949,6 +963,7 @@ public struct FullGroupPreferences: Decodable, Equatable { public var reactions: GroupPreference public var voice: GroupPreference public var files: GroupPreference + public var history: GroupPreference public init( timedMessages: TimedMessagesGroupPreference, @@ -956,7 +971,8 @@ public struct FullGroupPreferences: Decodable, Equatable { fullDelete: GroupPreference, reactions: GroupPreference, voice: GroupPreference, - files: GroupPreference + files: GroupPreference, + history: GroupPreference ) { self.timedMessages = timedMessages self.directMessages = directMessages @@ -964,6 +980,7 @@ public struct FullGroupPreferences: Decodable, Equatable { self.reactions = reactions self.voice = voice self.files = files + self.history = history } public static let sampleData = FullGroupPreferences( @@ -972,7 +989,8 @@ public struct FullGroupPreferences: Decodable, Equatable { fullDelete: GroupPreference(enable: .off), reactions: GroupPreference(enable: .on), voice: GroupPreference(enable: .on), - files: GroupPreference(enable: .on) + files: GroupPreference(enable: .on), + history: GroupPreference(enable: .on) ) } @@ -983,14 +1001,16 @@ public struct GroupPreferences: Codable { public var reactions: GroupPreference? public var voice: GroupPreference? public var files: GroupPreference? + public var history: GroupPreference? public init( - timedMessages: TimedMessagesGroupPreference?, - directMessages: GroupPreference?, - fullDelete: GroupPreference?, - reactions: GroupPreference?, - voice: GroupPreference?, - files: GroupPreference? + timedMessages: TimedMessagesGroupPreference? = nil, + directMessages: GroupPreference? = nil, + fullDelete: GroupPreference? = nil, + reactions: GroupPreference? = nil, + voice: GroupPreference? = nil, + files: GroupPreference? = nil, + history: GroupPreference? = nil ) { self.timedMessages = timedMessages self.directMessages = directMessages @@ -998,6 +1018,7 @@ public struct GroupPreferences: Codable { self.reactions = reactions self.voice = voice self.files = files + self.history = history } public static let sampleData = GroupPreferences( @@ -1006,7 +1027,8 @@ public struct GroupPreferences: Codable { fullDelete: GroupPreference(enable: .off), reactions: GroupPreference(enable: .on), voice: GroupPreference(enable: .on), - files: GroupPreference(enable: .on) + files: GroupPreference(enable: .on), + history: GroupPreference(enable: .on) ) } @@ -1017,7 +1039,8 @@ public func toGroupPreferences(_ fullPreferences: FullGroupPreferences) -> Group fullDelete: fullPreferences.fullDelete, reactions: fullPreferences.reactions, voice: fullPreferences.voice, - files: fullPreferences.files + files: fullPreferences.files, + history: fullPreferences.history ) } 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 619238f6d3..e2ab3a4d99 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 @@ -3308,7 +3308,8 @@ enum class GroupFeature: Feature { @SerialName("fullDelete") FullDelete, @SerialName("reactions") Reactions, @SerialName("voice") Voice, - @SerialName("files") Files; + @SerialName("files") Files, + @SerialName("history") History; override val hasParam: Boolean get() = when(this) { TimedMessages -> true @@ -3323,6 +3324,7 @@ enum class GroupFeature: Feature { Reactions -> generalGetString(MR.strings.message_reactions) Voice -> generalGetString(MR.strings.voice_messages) Files -> generalGetString(MR.strings.files_and_media) + History -> generalGetString(MR.strings.recent_history) } val icon: Painter @@ -3333,6 +3335,7 @@ enum class GroupFeature: Feature { Reactions -> painterResource(MR.images.ic_add_reaction) Voice -> painterResource(MR.images.ic_keyboard_voice) Files -> painterResource(MR.images.ic_draft) + History -> painterResource(MR.images.ic_schedule) } @Composable @@ -3343,6 +3346,7 @@ enum class GroupFeature: Feature { Reactions -> painterResource(MR.images.ic_add_reaction_filled) Voice -> painterResource(MR.images.ic_keyboard_voice_filled) Files -> painterResource(MR.images.ic_draft_filled) + History -> painterResource(MR.images.ic_schedule_filled) } fun enableDescription(enabled: GroupFeatureEnabled, canEdit: Boolean): String = @@ -3372,6 +3376,10 @@ enum class GroupFeature: Feature { GroupFeatureEnabled.ON -> generalGetString(MR.strings.allow_to_send_files) GroupFeatureEnabled.OFF -> generalGetString(MR.strings.prohibit_sending_files) } + History -> when(enabled) { + GroupFeatureEnabled.ON -> generalGetString(MR.strings.enable_sending_recent_history) + GroupFeatureEnabled.OFF -> generalGetString(MR.strings.disable_sending_recent_history) + } } } else { when(this) { @@ -3399,6 +3407,10 @@ enum class GroupFeature: Feature { GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_send_files) GroupFeatureEnabled.OFF -> generalGetString(MR.strings.files_are_prohibited_in_group) } + History -> when(enabled) { + GroupFeatureEnabled.ON -> generalGetString(MR.strings.recent_history_is_sent_to_new_members) + GroupFeatureEnabled.OFF -> generalGetString(MR.strings.recent_history_is_not_sent_to_new_members) + } } } } @@ -3513,6 +3525,7 @@ data class FullGroupPreferences( val reactions: GroupPreference, val voice: GroupPreference, val files: GroupPreference, + val history: GroupPreference, ) { fun toGroupPreferences(): GroupPreferences = GroupPreferences( @@ -3522,6 +3535,7 @@ data class FullGroupPreferences( reactions = reactions, voice = voice, files = files, + history = history ) companion object { @@ -3532,18 +3546,20 @@ data class FullGroupPreferences( reactions = GroupPreference(GroupFeatureEnabled.ON), voice = GroupPreference(GroupFeatureEnabled.ON), files = GroupPreference(GroupFeatureEnabled.ON), + history = GroupPreference(GroupFeatureEnabled.ON), ) } } @Serializable data class GroupPreferences( - val timedMessages: TimedMessagesGroupPreference?, - val directMessages: GroupPreference?, - val fullDelete: GroupPreference?, - val reactions: GroupPreference?, - val voice: GroupPreference?, - val files: GroupPreference?, + val timedMessages: TimedMessagesGroupPreference? = null, + val directMessages: GroupPreference? = null, + val fullDelete: GroupPreference? = null, + val reactions: GroupPreference? = null, + val voice: GroupPreference? = null, + val files: GroupPreference? = null, + val history: GroupPreference? = null, ) { companion object { val sampleData = GroupPreferences( @@ -3553,6 +3569,7 @@ data class GroupPreferences( reactions = GroupPreference(GroupFeatureEnabled.ON), voice = GroupPreference(GroupFeatureEnabled.ON), files = GroupPreference(GroupFeatureEnabled.ON), + history = GroupPreference(GroupFeatureEnabled.ON), ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt index 3cdfaad2d9..bd584f0c8b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt @@ -112,6 +112,11 @@ private fun GroupPreferencesLayout( FeatureSection(GroupFeature.Files, allowFiles, groupInfo, preferences, onTTLUpdated) { applyPrefs(preferences.copy(files = GroupPreference(enable = it))) } + SectionDividerSpaced(true, maxBottomPadding = false) + val enableHistory = remember(preferences) { mutableStateOf(preferences.history.enable) } + FeatureSection(GroupFeature.History, enableHistory, groupInfo, preferences, onTTLUpdated) { + applyPrefs(preferences.copy(history = GroupPreference(enable = it))) + } if (groupInfo.canEdit) { SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false) ResetSaveButtons( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt index 4f71e81b0d..eca579bcc8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt @@ -139,7 +139,8 @@ fun AddGroupLayout( createGroup(incognito.value, GroupProfile( displayName = displayName.value.trim(), fullName = "", - image = profileImage.value + image = profileImage.value, + groupPreferences = GroupPreferences(history = GroupPreference(GroupFeatureEnabled.ON)) )) }, textColor = MaterialTheme.colors.primary, 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 e30b4eb56d..09ccf1e409 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1461,6 +1461,7 @@ Message reactions Voice messages Files and media + Visible history Audio/video calls \nAvailable in v5.1 enabled @@ -1473,8 +1474,8 @@ Allow your contacts to send disappearing messages. Allow disappearing messages only if your contact allows them. Prohibit sending disappearing messages. - Allow your contacts to irreversibly delete sent messages. - Allow irreversible message deletion only if your contact allows it to you. + Allow your contacts to irreversibly delete sent messages. (24 hours) + Allow irreversible message deletion only if your contact allows it to you. (24 hours) Contacts can mark messages for deletion; you will be able to view them. Allow your contacts to send voice messages. Allow voice messages only if your contact allows them. @@ -1489,9 +1490,9 @@ Only you can send disappearing messages. Only your contact can send disappearing messages. Disappearing messages are prohibited in this chat. - Both you and your contact can irreversibly delete sent messages. - Only you can irreversibly delete messages (your contact can mark them for deletion). - Only your contact can irreversibly delete messages (you can mark them for deletion). + Both you and your contact can irreversibly delete sent messages. (24 hours) + Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours) + Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours) Irreversible message deletion is prohibited in this chat. Both you and your contact can send voice messages. Only you can send voice messages. @@ -1509,7 +1510,7 @@ Prohibit sending disappearing messages. Allow sending direct messages to members. Prohibit sending direct messages to members. - Allow to irreversibly delete sent messages. + Allow to irreversibly delete sent messages. (24 hours) Prohibit irreversible message deletion. Allow to send voice messages. Prohibit sending voice messages. @@ -1517,11 +1518,13 @@ Prohibit messages reactions. Allow to send files and media. Prohibit sending files and media. + Send up to 100 last messages to new members. + Do not send history to new members. Group members can send disappearing messages. Disappearing messages are prohibited in this group. Group members can send direct messages. Direct messages between members are prohibited in this group. - Group members can irreversibly delete sent messages. + Group members can irreversibly delete sent messages. (24 hours) Irreversible message deletion is prohibited in this group. Group members can send voice messages. Voice messages are prohibited in this group. @@ -1529,6 +1532,8 @@ Message reactions are prohibited in this group. Group members can send files and media. Files and media are prohibited in this group. + Up to 100 last messages are sent to new members. + History is not sent to new members. Delete after %d sec %ds diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_schedule.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_schedule.svg new file mode 100644 index 0000000000..4bd8e90cb6 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_schedule.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_schedule_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_schedule_filled.svg new file mode 100644 index 0000000000..368e2fcc1f --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_schedule_filled.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 1b61fb7487..fb2abf23d7 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -4798,7 +4798,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = createGroupFeatureItems :: GroupInfo -> GroupMember -> m () createGroupFeatureItems g@GroupInfo {fullGroupPreferences} m = - forM_ allGroupFeatureItems $ \(AGF f) -> do + forM_ allGroupFeatures $ \(AGF f) -> do let p = getGroupPreference f fullGroupPreferences (_, param) = groupFeatureState p createInternalChatItem user (CDGroupRcv g m) (CIRcvGroupFeature (toGroupFeature f) (toGroupPreference p) param) Nothing @@ -6002,7 +6002,7 @@ createFeatureItems user Contact {mergedPreferences = cups} ct'@Contact {mergedPr createGroupFeatureChangedItems :: (MsgDirectionI d, ChatMonad m) => User -> ChatDirection 'CTGroup d -> (GroupFeature -> GroupPreference -> Maybe Int -> CIContent d) -> GroupInfo -> GroupInfo -> m () createGroupFeatureChangedItems user cd ciContent GroupInfo {fullGroupPreferences = gps} GroupInfo {fullGroupPreferences = gps'} = - forM_ allGroupFeatureItems $ \(AGF f) -> do + forM_ allGroupFeatures $ \(AGF f) -> do let state = groupFeatureState $ getGroupPreference f gps pref' = getGroupPreference f gps' state'@(_, int') = groupFeatureState pref' diff --git a/src/Simplex/Chat/Types/Preferences.hs b/src/Simplex/Chat/Types/Preferences.hs index 18a10a83f4..5c79680c50 100644 --- a/src/Simplex/Chat/Types/Preferences.hs +++ b/src/Simplex/Chat/Types/Preferences.hs @@ -184,19 +184,17 @@ groupFeatureAllowed' :: GroupFeatureI f => SGroupFeature f -> FullGroupPreferenc groupFeatureAllowed' feature prefs = getField @"enable" (getGroupPreference feature prefs) == FEOn -allGroupFeatureItems :: [AGroupFeature] -allGroupFeatureItems = +allGroupFeatures :: [AGroupFeature] +allGroupFeatures = [ AGF SGFTimedMessages, AGF SGFDirectMessages, AGF SGFFullDelete, AGF SGFReactions, AGF SGFVoice, - AGF SGFFiles + AGF SGFFiles, + AGF SGFHistory ] -allGroupFeatures :: [AGroupFeature] -allGroupFeatures = allGroupFeatureItems <> [AGF SGFHistory] - groupPrefSel :: SGroupFeature f -> GroupPreferences -> Maybe (GroupFeaturePreference f) groupPrefSel f ps = case f of SGFTimedMessages -> ps.timedMessages diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index 76bc520aa4..f90e7f14d8 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -2681,7 +2681,7 @@ testGroupLinkNoContact = ] threadDelay 100000 - alice #$> ("/_get chat #1 count=100", chat, [(0, "invited via your group link"), (0, "connected")]) + alice #$> ("/_get chat #1 count=100", chat, [(1, "Recent history: off"), (0, "invited via your group link"), (0, "connected")]) alice @@@ [("#team", "connected")] bob @@@ [("#team", "connected")] @@ -2744,7 +2744,7 @@ testGroupLinkNoContactInviteesWereConnected = ] threadDelay 100000 - alice #$> ("/_get chat #1 count=100", chat, [(0, "invited via your group link"), (0, "connected")]) + alice #$> ("/_get chat #1 count=100", chat, [(1, "Recent history: off"), (0, "invited via your group link"), (0, "connected")]) alice @@@ [("#team", "connected")] bob @@@ [("#team", "connected"), ("@cath", "hey")] @@ -2825,7 +2825,7 @@ testGroupLinkNoContactAllMembersWereConnected = ] threadDelay 100000 - alice #$> ("/_get chat #1 count=100", chat, [(0, "invited via your group link"), (0, "connected")]) + alice #$> ("/_get chat #1 count=100", chat, [(1, "Recent history: off"), (0, "invited via your group link"), (0, "connected")]) alice @@@ [("#team", "connected"), ("@bob", "hey"), ("@cath", "hey")] bob @@@ [("#team", "connected"), ("@alice", "hey"), ("@cath", "hey")] diff --git a/tests/ChatTests/Utils.hs b/tests/ChatTests/Utils.hs index 65fa3bc4c1..ac87311c9e 100644 --- a/tests/ChatTests/Utils.hs +++ b/tests/ChatTests/Utils.hs @@ -223,8 +223,8 @@ groupFeatures'' = ((0, "Full deletion: off"), Nothing, Nothing), ((0, "Message reactions: on"), Nothing, Nothing), ((0, "Voice messages: on"), Nothing, Nothing), - ((0, "Files and media: on"), Nothing, Nothing) - -- ((0, "Recent history: on"), Nothing, Nothing) + ((0, "Files and media: on"), Nothing, Nothing), + ((0, "Recent history: on"), Nothing, Nothing) ] itemId :: Int -> String From 1438fd00e2164608e99cdd42917978dc304de16e Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Sat, 30 Dec 2023 00:47:25 +0700 Subject: [PATCH 07/16] android, desktop: self destruct becomes better (#3598) * android, desktop: self destruct becomes better * better way of doing it * fix script * firstOrNull * changes for review * comment --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- .../main/java/chat/simplex/app/SimplexApp.kt | 5 +- .../src/commonMain/cpp/android/simplex-api.c | 7 ++ .../src/commonMain/cpp/desktop/simplex-api.c | 7 ++ .../kotlin/chat/simplex/common/App.kt | 5 +- .../chat/simplex/common/model/ChatModel.kt | 1 + .../chat/simplex/common/model/SimpleXAPI.kt | 10 +- .../chat/simplex/common/platform/Core.kt | 117 ++++++++++-------- .../common/views/chatlist/UserPicker.kt | 5 +- .../common/views/database/DatabaseView.kt | 20 ++- .../common/views/helpers/DatabaseUtils.kt | 10 +- .../common/views/localauth/LocalAuthView.kt | 57 +++++++-- .../common/views/localauth/PasscodeView.kt | 13 +- .../views/localauth/SetAppPasscodeView.kt | 5 +- .../views/usersettings/PrivacySettings.kt | 4 +- .../commonMain/resources/MR/base/strings.xml | 1 + .../common/platform/AppCommon.desktop.kt | 8 +- libsimplex.dll.def | 1 + 17 files changed, 188 insertions(+), 88 deletions(-) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt index a8c8b5c1b2..84b39e983f 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt @@ -63,10 +63,7 @@ class SimplexApp: Application(), LifecycleEventObserver { tmpDir.deleteRecursively() tmpDir.mkdir() - withBGApi { - initChatController() - runMigrations() - } + initChatControllerAndRunMigrations(false) ProcessLifecycleOwner.get().lifecycle.addObserver(this@SimplexApp) } diff --git a/apps/multiplatform/common/src/commonMain/cpp/android/simplex-api.c b/apps/multiplatform/common/src/commonMain/cpp/android/simplex-api.c index 676c58fb49..5936bd5ff2 100644 --- a/apps/multiplatform/common/src/commonMain/cpp/android/simplex-api.c +++ b/apps/multiplatform/common/src/commonMain/cpp/android/simplex-api.c @@ -57,6 +57,7 @@ typedef long* chat_ctrl; */ extern char *chat_migrate_init(const char *path, const char *key, const char *confirm, chat_ctrl *ctrl); +extern char *chat_close_store(chat_ctrl ctrl); extern char *chat_send_cmd(chat_ctrl ctrl, const char *cmd); extern char *chat_send_remote_cmd(chat_ctrl ctrl, const int rhId, const char *cmd); extern char *chat_recv_msg(chat_ctrl ctrl); // deprecated @@ -93,6 +94,12 @@ Java_chat_simplex_common_platform_CoreKt_chatMigrateInit(JNIEnv *env, __unused j return ret; } +JNIEXPORT jstring JNICALL +Java_chat_simplex_common_platform_CoreKt_chatCloseStore(JNIEnv *env, __unused jclass clazz, jlong controller) { + jstring res = (*env)->NewStringUTF(env, chat_close_store((void*)controller)); + return res; +} + JNIEXPORT jstring JNICALL Java_chat_simplex_common_platform_CoreKt_chatSendCmd(JNIEnv *env, __unused jclass clazz, jlong controller, jstring msg) { const char *_msg = (*env)->GetStringUTFChars(env, msg, JNI_FALSE); diff --git a/apps/multiplatform/common/src/commonMain/cpp/desktop/simplex-api.c b/apps/multiplatform/common/src/commonMain/cpp/desktop/simplex-api.c index 292715bdc5..f15689285a 100644 --- a/apps/multiplatform/common/src/commonMain/cpp/desktop/simplex-api.c +++ b/apps/multiplatform/common/src/commonMain/cpp/desktop/simplex-api.c @@ -30,6 +30,7 @@ typedef long* chat_ctrl; */ extern char *chat_migrate_init(const char *path, const char *key, const char *confirm, chat_ctrl *ctrl); +extern char *chat_close_store(chat_ctrl ctrl); extern char *chat_send_cmd(chat_ctrl ctrl, const char *cmd); extern char *chat_send_remote_cmd(chat_ctrl ctrl, const int rhId, const char *cmd); extern char *chat_recv_msg(chat_ctrl ctrl); // deprecated @@ -106,6 +107,12 @@ Java_chat_simplex_common_platform_CoreKt_chatMigrateInit(JNIEnv *env, jclass cla return ret; } +JNIEXPORT jstring JNICALL +Java_chat_simplex_common_platform_CoreKt_chatCloseStore(JNIEnv *env, jclass clazz, jlong controller) { + jstring res = decode_to_utf8_string(env, chat_close_store((void*)controller)); + return res; +} + JNIEXPORT jstring JNICALL Java_chat_simplex_common_platform_CoreKt_chatSendCmd(JNIEnv *env, jclass clazz, jlong controller, jstring msg) { const char *_msg = encode_to_utf8_chars(env, msg); diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt index 950515f055..f61f3b4b80 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt @@ -103,13 +103,15 @@ fun MainScreen() { } Box { + val unauthorized = remember { derivedStateOf { AppLock.userAuthorized.value != true } } val onboarding by remember { chatModel.controller.appPrefs.onboardingStage.state } val localUserCreated = chatModel.localUserCreated.value var showInitializationView by remember { mutableStateOf(false) } when { chatModel.chatDbStatus.value == null && showInitializationView -> DefaultProgressView(stringResource(MR.strings.opening_database)) showChatDatabaseError -> { - chatModel.chatDbStatus.value?.let { + // Prevent showing keyboard on Android when: passcode enabled and database password not saved + if (!unauthorized.value && chatModel.chatDbStatus.value != null) { DatabaseErrorView(chatModel.chatDbStatus, chatModel.controller.appPrefs) } } @@ -150,7 +152,6 @@ fun MainScreen() { SwitchingUsersView() } - val unauthorized = remember { derivedStateOf { AppLock.userAuthorized.value != true } } if (unauthorized.value && !(chatModel.activeCallViewIsVisible.value && chatModel.showCallView.value)) { LaunchedEffect(Unit) { // With these constrains when user presses back button while on ChatList, activity destroys and shows auth request diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index f51f6986f8..b8d421b07b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -47,6 +47,7 @@ object ChatModel { val chatDbChanged = mutableStateOf(false) val chatDbEncrypted = mutableStateOf(false) val chatDbStatus = mutableStateOf(null) + val ctrlInitInProgress = mutableStateOf(false) val chats = mutableStateListOf() // map of connections network statuses, key is agent connection id val networkStatuses = mutableStateMapOf() 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 e2ab3a4d99..07e091b484 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 @@ -2145,7 +2145,15 @@ class SharedPreference(val get: () -> T, set: (T) -> Unit) { init { this.set = { value -> set(value) - _state.value = value + try { + _state.value = value + } catch (e: IllegalStateException) { + // Can be `Reading a state that was created after the snapshot was taken or in a snapshot that has not yet been applied` + Log.i(TAG, e.stackTraceToString()) + withApi { + _state.value = value + } + } } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt index 52ff269f54..07e59a55e0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt @@ -14,6 +14,7 @@ external fun pipeStdOutToSocket(socketName: String) : Int // SimpleX API typealias ChatCtrl = Long external fun chatMigrateInit(dbPath: String, dbKey: String, confirm: String): Array +external fun chatCloseStore(ctrl: ChatCtrl): String external fun chatSendCmd(ctrl: ChatCtrl, msg: String): String external fun chatSendRemoteCmd(ctrl: ChatCtrl, rhId: Int, msg: String): String external fun chatRecvMsg(ctrl: ChatCtrl): String @@ -35,57 +36,71 @@ val appPreferences: AppPreferences val chatController: ChatController = ChatController -suspend fun initChatController(useKey: String? = null, confirmMigrations: MigrationConfirmation? = null, startChat: Boolean = true) { - val dbKey = useKey ?: DatabaseUtils.useDatabaseKey() - val confirm = confirmMigrations ?: if (appPreferences.confirmDBUpgrades.get()) MigrationConfirmation.Error else MigrationConfirmation.YesUp - val migrated: Array = chatMigrateInit(dbAbsolutePrefixPath, dbKey, confirm.value) - val res: DBMigrationResult = kotlin.runCatching { - json.decodeFromString(migrated[0] as String) - }.getOrElse { DBMigrationResult.Unknown(migrated[0] as String) } - val ctrl = if (res is DBMigrationResult.OK) { - migrated[1] as Long - } else null - chatController.ctrl = ctrl - chatModel.chatDbEncrypted.value = dbKey != "" - chatModel.chatDbStatus.value = res - if (res != DBMigrationResult.OK) { - Log.d(TAG, "Unable to migrate successfully: $res") - } else if (startChat) { - // If we migrated successfully means previous re-encryption process on database level finished successfully too - if (appPreferences.encryptionStartedAt.get() != null) appPreferences.encryptionStartedAt.set(null) - val user = chatController.apiGetActiveUser(null) - if (user == null) { - chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true) - chatModel.currentUser.value = null - chatModel.users.clear() - if (appPlatform.isDesktop) { - /** - * Setting it here to null because otherwise the screen will flash in [MainScreen] after the first start - * because of default value of [OnboardingStage.OnboardingComplete] - * */ - chatModel.localUserCreated.value = null - if (chatController.listRemoteHosts()?.isEmpty() == true) { - chatController.appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) - } - chatController.startChatWithoutUser() - } else { - chatController.appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) - } - } else { - val savedOnboardingStage = appPreferences.onboardingStage.get() - val newStage = if (listOf(OnboardingStage.Step1_SimpleXInfo, OnboardingStage.Step2_CreateProfile).contains(savedOnboardingStage) && chatModel.users.size == 1) { - OnboardingStage.Step3_CreateSimpleXAddress - } else { - savedOnboardingStage - } - if (appPreferences.onboardingStage.get() != newStage) { - appPreferences.onboardingStage.set(newStage) - } - if (appPreferences.onboardingStage.get() == OnboardingStage.OnboardingComplete && !chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.get()) { - chatModel.setDeliveryReceipts.value = true - } - chatController.startChat(user) - platform.androidChatInitializedAndStarted() +fun initChatControllerAndRunMigrations(ignoreSelfDestruct: Boolean) { + if (ignoreSelfDestruct || DatabaseUtils.ksSelfDestructPassword.get() == null) { + withBGApi { + initChatController() + runMigrations() } } } + +suspend fun initChatController(useKey: String? = null, confirmMigrations: MigrationConfirmation? = null, startChat: Boolean = true) { + try { + chatModel.ctrlInitInProgress.value = true + val dbKey = useKey ?: DatabaseUtils.useDatabaseKey() + val confirm = confirmMigrations ?: if (appPreferences.confirmDBUpgrades.get()) MigrationConfirmation.Error else MigrationConfirmation.YesUp + val migrated: Array = chatMigrateInit(dbAbsolutePrefixPath, dbKey, confirm.value) + val res: DBMigrationResult = kotlin.runCatching { + json.decodeFromString(migrated[0] as String) + }.getOrElse { DBMigrationResult.Unknown(migrated[0] as String) } + val ctrl = if (res is DBMigrationResult.OK) { + migrated[1] as Long + } else null + chatController.ctrl = ctrl + chatModel.chatDbEncrypted.value = dbKey != "" + chatModel.chatDbStatus.value = res + if (res != DBMigrationResult.OK) { + Log.d(TAG, "Unable to migrate successfully: $res") + } else if (startChat) { + // If we migrated successfully means previous re-encryption process on database level finished successfully too + if (appPreferences.encryptionStartedAt.get() != null) appPreferences.encryptionStartedAt.set(null) + val user = chatController.apiGetActiveUser(null) + if (user == null) { + chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true) + chatModel.currentUser.value = null + chatModel.users.clear() + if (appPlatform.isDesktop) { + /** + * Setting it here to null because otherwise the screen will flash in [MainScreen] after the first start + * because of default value of [OnboardingStage.OnboardingComplete] + * */ + chatModel.localUserCreated.value = null + if (chatController.listRemoteHosts()?.isEmpty() == true) { + chatController.appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) + } + chatController.startChatWithoutUser() + } else { + chatController.appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) + } + } else { + val savedOnboardingStage = appPreferences.onboardingStage.get() + val newStage = if (listOf(OnboardingStage.Step1_SimpleXInfo, OnboardingStage.Step2_CreateProfile).contains(savedOnboardingStage) && chatModel.users.size == 1) { + OnboardingStage.Step3_CreateSimpleXAddress + } else { + savedOnboardingStage + } + if (appPreferences.onboardingStage.get() != newStage) { + appPreferences.onboardingStage.set(newStage) + } + if (appPreferences.onboardingStage.get() == OnboardingStage.OnboardingComplete && !chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.get()) { + chatModel.setDeliveryReceipts.value = true + } + chatController.startChat(user) + platform.androidChatInitializedAndStarted() + } + } + } finally { + chatModel.ctrlInitInProgress.value = false + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt index d87c05a913..a0db4188ad 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt @@ -116,7 +116,10 @@ fun UserPicker( } } LaunchedEffect(Unit) { - controller.reloadRemoteHosts() + // Controller.ctrl can be null when self-destructing activates + if (controller.ctrl != null && controller.ctrl != -1L) { + controller.reloadRemoteHosts() + } } val UsersView: @Composable ColumnScope.() -> Unit = { users.forEach { u -> diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index 224317f949..5e1abb6846 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -4,7 +4,6 @@ import SectionBottomSpacer import SectionDividerSpaced import SectionTextFooter import SectionItemView -import SectionSpacer import SectionView import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.layout.* @@ -426,6 +425,7 @@ private fun authStopChat(m: ChatModel) { } is LAResult.Error -> { m.chatRunning.value = true + laFailedAlert() } is LAResult.Failed -> { m.chatRunning.value = true @@ -459,6 +459,24 @@ suspend fun deleteChatAsync(m: ChatModel) { m.controller.apiDeleteStorage() DatabaseUtils.ksDatabasePassword.remove() m.controller.appPrefs.storeDBPassphrase.set(true) + deleteChatDatabaseFiles() +} + +fun deleteChatDatabaseFiles() { + val chat = File(dataDir, chatDatabaseFileName) + val chatBak = File(dataDir, "$chatDatabaseFileName.bak") + val agent = File(dataDir, agentDatabaseFileName) + val agentBak = File(dataDir, "$agentDatabaseFileName.bak") + chat.delete() + chatBak.delete() + agent.delete() + agentBak.delete() + filesDir.deleteRecursively() + remoteHostsDir.deleteRecursively() + tmpDir.deleteRecursively() + tmpDir.mkdir() + DatabaseUtils.ksDatabasePassword.remove() + controller.appPrefs.storeDBPassphrase.set(true) } private fun exportArchive( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt index e7da47f8f0..c984e16452 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt @@ -17,7 +17,7 @@ object DatabaseUtils { val ksAppPassword = KeyStoreItem(APP_PASSWORD_ALIAS, appPreferences.encryptedAppPassphrase, appPreferences.initializationVectorAppPassphrase) val ksSelfDestructPassword = KeyStoreItem(SELF_DESTRUCT_PASSWORD_ALIAS, appPreferences.encryptedSelfDestructPassphrase, appPreferences.initializationVectorSelfDestructPassphrase) - class KeyStoreItem(private val alias: String, val passphrase: SharedPreference, val initVector: SharedPreference) { + class KeyStoreItem(val alias: String, val passphrase: SharedPreference, val initVector: SharedPreference) { fun get(): String? { return cryptor.decryptData( passphrase.get()?.toByteArrayFromBase64ForPassphrase() ?: return null, @@ -75,11 +75,11 @@ object DatabaseUtils { sealed class DBMigrationResult { @Serializable @SerialName("ok") object OK: DBMigrationResult() @Serializable @SerialName("invalidConfirmation") object InvalidConfirmation: DBMigrationResult() - @Serializable @SerialName("errorNotADatabase") class ErrorNotADatabase(val dbFile: String): DBMigrationResult() - @Serializable @SerialName("errorMigration") class ErrorMigration(val dbFile: String, val migrationError: MigrationError): DBMigrationResult() - @Serializable @SerialName("errorSQL") class ErrorSQL(val dbFile: String, val migrationSQLError: String): DBMigrationResult() + @Serializable @SerialName("errorNotADatabase") data class ErrorNotADatabase(val dbFile: String): DBMigrationResult() + @Serializable @SerialName("errorMigration") data class ErrorMigration(val dbFile: String, val migrationError: MigrationError): DBMigrationResult() + @Serializable @SerialName("errorSQL") data class ErrorSQL(val dbFile: String, val migrationSQLError: String): DBMigrationResult() @Serializable @SerialName("errorKeychain") object ErrorKeychain: DBMigrationResult() - @Serializable @SerialName("unknown") class Unknown(val json: String): DBMigrationResult() + @Serializable @SerialName("unknown") data class Unknown(val json: String): DBMigrationResult() } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt index 468dd8580e..0401906527 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt @@ -1,32 +1,45 @@ package chat.simplex.common.views.localauth -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable +import chat.simplex.common.model.* +import chat.simplex.common.model.ChatModel.controller import dev.icerock.moko.resources.compose.stringResource -import chat.simplex.common.views.database.deleteChatAsync -import chat.simplex.common.views.database.stopChatAsync import chat.simplex.common.views.helpers.* import chat.simplex.common.views.helpers.DatabaseUtils.ksSelfDestructPassword import chat.simplex.common.views.helpers.DatabaseUtils.ksAppPassword import chat.simplex.common.views.onboarding.OnboardingStage -import chat.simplex.common.model.ChatModel -import chat.simplex.common.model.Profile import chat.simplex.common.platform.* +import chat.simplex.common.views.database.* import chat.simplex.res.MR +import kotlinx.coroutines.delay @Composable fun LocalAuthView(m: ChatModel, authRequest: LocalAuthRequest) { val passcode = rememberSaveable { mutableStateOf("") } - PasscodeView(passcode, authRequest.title ?: stringResource(MR.strings.la_enter_app_passcode), authRequest.reason, stringResource(MR.strings.submit_passcode), + val allowToReact = rememberSaveable { mutableStateOf(true) } + if (!allowToReact.value) { + BackHandler { + // do nothing until submit action finishes to prevent concurrent removing of storage + } + } + PasscodeView(passcode, authRequest.title ?: stringResource(MR.strings.la_enter_app_passcode), authRequest.reason, stringResource(MR.strings.submit_passcode), buttonsEnabled = allowToReact, submit = { val sdPassword = ksSelfDestructPassword.get() if (sdPassword == passcode.value && authRequest.selfDestruct) { + allowToReact.value = false deleteStorageAndRestart(m, sdPassword) { r -> authRequest.completed(r) } } else { - val r: LAResult = if (passcode.value == authRequest.password) LAResult.Success else LAResult.Error(generalGetString(MR.strings.incorrect_passcode)) + val r: LAResult = if (passcode.value == authRequest.password) { + if (authRequest.selfDestruct && sdPassword != null && controller.ctrl == -1L) { + initChatControllerAndRunMigrations(true) + } + LAResult.Success + } else { + LAResult.Error(generalGetString(MR.strings.incorrect_passcode)) + } authRequest.completed(r) } }, @@ -38,8 +51,28 @@ fun LocalAuthView(m: ChatModel, authRequest: LocalAuthRequest) { private fun deleteStorageAndRestart(m: ChatModel, password: String, completed: (LAResult) -> Unit) { withBGApi { try { - stopChatAsync(m) - deleteChatAsync(m) + /** Waiting until [initChatController] finishes */ + while (m.ctrlInitInProgress.value) { + delay(50) + } + if (m.chatRunning.value == true) { + stopChatAsync(m) + } + val ctrl = m.controller.ctrl + if (ctrl != null && ctrl != -1L) { + /** + * The following sequence can bring a user here: + * the user opened the app, entered app passcode, went to background, returned back, entered self-destruct code. + * In this case database should be closed to prevent possible situation when OS can deny database removal command + * */ + chatCloseStore(ctrl) + } + deleteChatDatabaseFiles() + // Clear sensitive data on screen just in case ModalManager will fail to prevent hiding its modals while database encrypts itself + m.chatId.value = null + m.chatItems.clear() + m.chats.clear() + m.users.clear() ksAppPassword.set(password) ksSelfDestructPassword.remove() ntfManager.cancelAllNotifications() @@ -67,13 +100,15 @@ private fun deleteStorageAndRestart(m: ChatModel, password: String, completed: ( m.currentUser.value = createdUser m.controller.appPrefs.onboardingStage.set(OnboardingStage.OnboardingComplete) if (createdUser != null) { + controller.chatModel.chatRunning.value = false m.controller.startChat(createdUser) } - ModalManager.fullscreen.closeModals() + ModalManager.closeAllModalsEverywhere() AlertManager.shared.hideAllAlerts() AlertManager.privacySensitive.hideAllAlerts() completed(LAResult.Success) } catch (e: Exception) { + Log.e(TAG, "Unable to delete storage: ${e.stackTraceToString()}") completed(LAResult.Error(generalGetString(MR.strings.incorrect_passcode))) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/PasscodeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/PasscodeView.kt index 4784951ad0..2b2f006d25 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/PasscodeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/PasscodeView.kt @@ -12,6 +12,7 @@ import dev.icerock.moko.resources.compose.painterResource import androidx.compose.ui.unit.dp import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.DEFAULT_PADDING +import chat.simplex.common.views.chat.group.ProgressIndicator import chat.simplex.common.views.helpers.SimpleButton import chat.simplex.common.views.helpers.* import chat.simplex.res.MR @@ -23,6 +24,7 @@ fun PasscodeView( reason: String? = null, submitLabel: String, submitEnabled: ((String) -> Boolean)? = null, + buttonsEnabled: State = remember { mutableStateOf(true) }, submit: () -> Unit, cancel: () -> Unit, ) { @@ -74,9 +76,9 @@ fun PasscodeView( } PasscodeEntry(passcode, true) Row(Modifier.heightIn(min = 70.dp), verticalAlignment = Alignment.CenterVertically) { - SimpleButton(generalGetString(MR.strings.cancel_verb), icon = painterResource(MR.images.ic_close), click = cancel) + SimpleButton(generalGetString(MR.strings.cancel_verb), icon = painterResource(MR.images.ic_close), disabled = !buttonsEnabled.value, click = cancel) Spacer(Modifier.size(20.dp)) - SimpleButton(submitLabel, icon = painterResource(MR.images.ic_done_filled), disabled = submitEnabled?.invoke(passcode.value) == false || passcode.value.length < 4, click = submit) + SimpleButton(submitLabel, icon = painterResource(MR.images.ic_done_filled), disabled = submitEnabled?.invoke(passcode.value) == false || passcode.value.length < 4 || !buttonsEnabled.value, click = submit) } } } @@ -117,8 +119,8 @@ fun PasscodeView( Modifier.padding(start = 30.dp).height(s * 3), verticalArrangement = Arrangement.SpaceEvenly ) { - SimpleButton(generalGetString(MR.strings.cancel_verb), icon = painterResource(MR.images.ic_close), click = cancel) - SimpleButton(submitLabel, icon = painterResource(MR.images.ic_done_filled), disabled = submitEnabled?.invoke(passcode.value) == false || passcode.value.length < 4, click = submit) + SimpleButton(generalGetString(MR.strings.cancel_verb), icon = painterResource(MR.images.ic_close), disabled = !buttonsEnabled.value, click = cancel) + SimpleButton(submitLabel, icon = painterResource(MR.images.ic_done_filled), disabled = submitEnabled?.invoke(passcode.value) == false || passcode.value.length < 4 || !buttonsEnabled.value, click = submit) } } } @@ -130,6 +132,9 @@ fun PasscodeView( } else { HorizontalLayout() } + if (!buttonsEnabled.value) { + ProgressIndicator() + } LaunchedEffect(Unit) { focusRequester.requestFocus() // Disallow to steal a focus by clicking on buttons or using Tab diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/SetAppPasscodeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/SetAppPasscodeView.kt index 18437dbf98..eadd399428 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/SetAppPasscodeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/SetAppPasscodeView.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.saveable.rememberSaveable import chat.simplex.common.platform.BackHandler import chat.simplex.common.views.helpers.DatabaseUtils import chat.simplex.common.views.helpers.DatabaseUtils.ksAppPassword +import chat.simplex.common.views.helpers.DatabaseUtils.ksSelfDestructPassword import chat.simplex.common.views.helpers.generalGetString import chat.simplex.res.MR @@ -48,7 +49,9 @@ fun SetAppPasscodeView( } } } else { - SetPasswordView(title, generalGetString(MR.strings.save_verb)) { + SetPasswordView(title, generalGetString(MR.strings.save_verb), + // Do not allow to set app passcode == selfDestruct passcode + submitEnabled = { pwd -> pwd != (if (passcodeKeychain.alias == ksSelfDestructPassword.alias) ksAppPassword else ksSelfDestructPassword).get() }) { enteredPassword = passcode.value passcode.value = "" confirming = true diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt index 3d2b7b7fa5..9a4f083746 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt @@ -429,6 +429,7 @@ fun SimplexLockView( ModalManager.fullscreen.showCustomModal { close -> Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { SetAppPasscodeView( + reason = generalGetString(MR.strings.la_app_passcode), submit = { passcodeAlert(generalGetString(MR.strings.passcode_changed)) }, cancel = { @@ -453,6 +454,7 @@ fun SimplexLockView( Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { SetAppPasscodeView( passcodeKeychain = ksSelfDestructPassword, + reason = generalGetString(MR.strings.self_destruct), submit = { selfDestructPasscodeAlert(generalGetString(MR.strings.self_destruct_passcode_changed)) }, cancel = { @@ -553,7 +555,7 @@ fun SimplexLockView( fontSize = 16.sp, modifier = Modifier.padding(bottom = DEFAULT_PADDING_HALF) ) - ProfileNameField(selfDestructDisplayName, "", ::isValidDisplayName) + ProfileNameField(selfDestructDisplayName, "", { isValidDisplayName(it.trim()) }) LaunchedEffect(selfDestructDisplayName.value) { val new = selfDestructDisplayName.value if (isValidDisplayName(new) && selfDestructDisplayNamePref.get() != new) { 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 09ccf1e409..8f5f6338fa 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -919,6 +919,7 @@ Authentication cancelled System Passcode + App passcode Off Passcode set! Passcode changed! diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt index 92111f162a..4e70956be7 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt @@ -2,8 +2,7 @@ package chat.simplex.common.platform import chat.simplex.common.model.* import chat.simplex.common.views.call.RcvCallInvitation -import chat.simplex.common.views.helpers.generalGetString -import chat.simplex.common.views.helpers.withBGApi +import chat.simplex.common.views.helpers.* import java.util.* import chat.simplex.res.MR @@ -25,10 +24,7 @@ fun initApp() { override fun cancelAllNotifications() = chat.simplex.common.model.NtfManager.cancelAllNotifications() } applyAppLocale() - withBGApi { - initChatController() - runMigrations() - } + initChatControllerAndRunMigrations(false) // LALAL //testCrypto() } diff --git a/libsimplex.dll.def b/libsimplex.dll.def index 4255f4409c..f927e3ee24 100644 --- a/libsimplex.dll.def +++ b/libsimplex.dll.def @@ -3,6 +3,7 @@ EXPORTS hs_init hs_init_with_rtsopts chat_migrate_init + chat_close_store chat_send_cmd chat_send_remote_cmd chat_recv_msg From 478bb32cdb1f631b77ed22c9d3438c97936cc60c Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 29 Dec 2023 22:42:55 +0400 Subject: [PATCH 08/16] core: send group description to new members as welcome message after sending history (fixes welcome message being created before history) (#3623) --- src/Simplex/Chat.hs | 17 ++++++++++-- src/Simplex/Chat/Protocol.hs | 6 +++- tests/ChatTests/Groups.hs | 54 ++++++++++++++++++++++++++++++++++++ tests/ProtocolTests.hs | 8 +++--- 4 files changed, 77 insertions(+), 8 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index fb2abf23d7..9d7f5bccc3 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -3593,7 +3593,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = createGroupFeatureItems gInfo m let GroupInfo {groupProfile = GroupProfile {description}} = gInfo memberConnectedChatItem gInfo m - forM_ description $ groupDescriptionChatItem gInfo m + unless expectHistory $ forM_ description $ groupDescriptionChatItem gInfo m + where + expectHistory = + groupFeatureAllowed SGFHistory gInfo + && isCompatibleRange (memberChatVRange' m) groupHistoryIncludeWelcomeVRange GCInviteeMember -> do memberConnectedChatItem gInfo m toView $ CRJoinedGroupMember user gInfo m {memberStatus = GSMemConnected} @@ -3636,8 +3640,15 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = (errs', events) <- partitionEithers <$> mapM (tryChatError . itemForwardEvents) items let errors = map ChatErrorStore errs <> errs' unless (null errors) $ toView $ CRChatErrors (Just user) errors - forM_ (L.nonEmpty $ concat events) $ \events' -> - sendGroupMemberMessages user conn events' groupId + let events' = maybe (concat events) (\x -> concat events <> [x]) descrEvent_ + forM_ (L.nonEmpty events') $ \events'' -> + sendGroupMemberMessages user conn events'' groupId + descrEvent_ :: Maybe (ChatMsgEvent 'Json) + descrEvent_ + | isCompatibleRange (memberChatVRange' m) groupHistoryIncludeWelcomeVRange = do + let GroupInfo {groupProfile = GroupProfile {description}} = gInfo + fmap (\descr -> XMsgNew $ MCSimple $ extMsgContent (MCText descr) Nothing) description + | otherwise = Nothing itemForwardEvents :: CChatItem 'CTGroup -> m [ChatMsgEvent 'Json] itemForwardEvents cci = case cci of (CChatItem SMDRcv ci@ChatItem {chatDir = CIGroupRcv sender, content = CIRcvMsgContent mc, file}) -> do diff --git a/src/Simplex/Chat/Protocol.hs b/src/Simplex/Chat/Protocol.hs index a0df9f9865..220d521322 100644 --- a/src/Simplex/Chat/Protocol.hs +++ b/src/Simplex/Chat/Protocol.hs @@ -56,7 +56,7 @@ import Simplex.Messaging.Version hiding (version) -- This indirection is needed for backward/forward compatibility testing. -- Testing with real app versions is still needed, as tests use the current code with different version ranges, not the old code. currentChatVersion :: Version -currentChatVersion = 5 +currentChatVersion = 6 -- This should not be used directly in code, instead use `chatVRange` from ChatConfig (see comment above) supportedChatVRange :: VersionRange @@ -82,6 +82,10 @@ groupForwardVRange = mkVersionRange 4 currentChatVersion batchSendVRange :: VersionRange batchSendVRange = mkVersionRange 5 currentChatVersion +-- version range that supports sending group welcome message in group history +groupHistoryIncludeWelcomeVRange :: VersionRange +groupHistoryIncludeWelcomeVRange = mkVersionRange 6 currentChatVersion + data ConnectionEntity = RcvDirectMsgConnection {entityConnection :: Connection, contact :: Maybe Contact} | RcvGroupMsgConnection {entityConnection :: Connection, groupInfo :: GroupInfo, groupMember :: GroupMember} diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index f90e7f14d8..28ca35cbab 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -131,6 +131,7 @@ chatGroupTests = do it "quoted messages" testGroupHistoryQuotes it "deleted message is not included" testGroupHistoryDeletedMessage it "disappearing message is sent as disappearing" testGroupHistoryDisappearingMessage + it "welcome message (group description) is sent after history" testGroupHistoryWelcomeMessage where _0 = supportedChatVRange -- don't create direct connections _1 = groupCreateDirectVRange @@ -5125,3 +5126,56 @@ testGroupHistoryDisappearingMessage = r2 `shouldContain` [(0, "1"), (0, "4")] r2 `shouldNotContain` [(0, "2")] r2 `shouldNotContain` [(0, "3")] + +testGroupHistoryWelcomeMessage :: HasCallStack => FilePath -> IO () +testGroupHistoryWelcomeMessage = + testChat3 aliceProfile bobProfile cathProfile $ + \alice bob cath -> do + createGroup2 "team" alice bob + + alice ##> "/set welcome #team welcome to team" + alice <## "description changed to:" + alice <## "welcome to team" + + bob <## "alice updated group #team:" + bob <## "description changed to:" + bob <## "welcome to team" + + threadDelay 1000000 + + alice #> "#team hello" + bob <# "#team alice> hello" + + threadDelay 1000000 + + bob #> "#team hey!" + alice <# "#team bob> hey!" + + connectUsers alice cath + addMember "team" alice cath GRAdmin + cath ##> "/j team" + concurrentlyN_ + [ alice <## "#team: cath joined the group", + cath + <### [ "#team: you joined the group", + WithTime "#team alice> hello [>>]", + WithTime "#team bob> hey! [>>]", + WithTime "#team alice> welcome to team", + "#team: member bob (Bob) is connected" + ], + do + bob <## "#team: alice added cath (Catherine) to the group (connecting...)" + bob <## "#team: new member cath is connected" + ] + + cath ##> "/_get chat #1 count=100" + r <- chat <$> getTermLine cath + r `shouldContain` [(0, "hello"), (0, "hey!"), (0, "welcome to team")] + + -- message delivery works after sending history + alice #> "#team 1" + [bob, cath] *<# "#team alice> 1" + bob #> "#team 2" + [alice, cath] *<# "#team bob> 2" + cath #> "#team 3" + [alice, bob] *<# "#team cath> 3" diff --git a/tests/ProtocolTests.hs b/tests/ProtocolTests.hs index 23fbce249e..782cf3a3e0 100644 --- a/tests/ProtocolTests.hs +++ b/tests/ProtocolTests.hs @@ -130,7 +130,7 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do "{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}" ##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew (MCSimple (extMsgContent (MCText "hello") Nothing))) it "x.msg.new chat message with chat version range" $ - "{\"v\":\"1-5\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}" + "{\"v\":\"1-6\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}" ##==## ChatMessage supportedChatVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew (MCSimple (extMsgContent (MCText "hello") Nothing))) it "x.msg.new quote" $ "{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello to you too\",\"type\":\"text\"},\"quote\":{\"content\":{\"text\":\"hello there!\",\"type\":\"text\"},\"msgRef\":{\"msgId\":\"BQYHCA==\",\"sent\":true,\"sentAt\":\"1970-01-01T00:00:01.000000001Z\"}}}}" @@ -240,13 +240,13 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do "{\"v\":\"1\",\"event\":\"x.grp.mem.new\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemNew MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile} it "x.grp.mem.new with member chat version range" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.new\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-5\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" + "{\"v\":\"1\",\"event\":\"x.grp.mem.new\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-6\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemNew MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile} it "x.grp.mem.intro" $ "{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemIntro MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile} it "x.grp.mem.intro with member chat version range" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-5\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" + "{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-6\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemIntro MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile} it "x.grp.mem.inv" $ "{\"v\":\"1\",\"event\":\"x.grp.mem.inv\",\"params\":{\"memberId\":\"AQIDBA==\",\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}}" @@ -258,7 +258,7 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do "{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemFwd MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile} IntroInvitation {groupConnReq = testConnReq, directConnReq = Just testConnReq} it "x.grp.mem.fwd with member chat version range and w/t directConnReq" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-5\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" + "{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-6\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemFwd MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile} IntroInvitation {groupConnReq = testConnReq, directConnReq = Nothing} it "x.grp.mem.info" $ "{\"v\":\"1\",\"event\":\"x.grp.mem.info\",\"params\":{\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}" From e253c55ba41f9158b695413f2151d091b0f5cad4 Mon Sep 17 00:00:00 2001 From: Alexander Bondarenko <486682+dpwiz@users.noreply.github.com> Date: Fri, 29 Dec 2023 23:15:14 +0200 Subject: [PATCH 09/16] core: compatibility with GHC 8.10.7 (#3608) * GHC-8.10 compatibility * tweak setters * restore membership * remove Show Batch * fix bytestring-10 compat * preserve membership qualifier in names * a few more memberships * rename * remove with-compiler * ci: add 8.10 builds, limit releases to 9.6 * use matrix.asset_name as release guard * fix windows_build * actually use ghc version from matrix * fix typo * revert build/hash split * add ghc to cache key * Force cache between build and tests * use explicit caching steps * skip unneeded tasks --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Co-authored-by: Avently <7953703+avently@users.noreply.github.com> --- .github/workflows/build.yml | 60 ++++++++---- cabal.project | 2 - src/Simplex/Chat.hs | 127 ++++++++++++++------------ src/Simplex/Chat/Messages.hs | 5 +- src/Simplex/Chat/Messages/Batch.hs | 1 - src/Simplex/Chat/Mobile/Shared.hs | 6 +- src/Simplex/Chat/Store/Direct.hs | 3 +- src/Simplex/Chat/Store/Files.hs | 3 +- src/Simplex/Chat/Store/Groups.hs | 9 +- src/Simplex/Chat/Types.hs | 15 ++- src/Simplex/Chat/Types/Preferences.hs | 95 ++++++++++--------- src/Simplex/Chat/View.hs | 13 +-- tests/Bots/BroadcastTests.hs | 5 +- tests/Bots/DirectoryTests.hs | 5 +- tests/ChatClient.hs | 36 ++++---- 15 files changed, 207 insertions(+), 178 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index afdb9bea1a..2727fc4add 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -42,7 +42,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} build: - name: build-${{ matrix.os }} + name: build-${{ matrix.os }}-${{ matrix.ghc }} if: always() needs: prepare-release runs-on: ${{ matrix.os }} @@ -51,18 +51,25 @@ jobs: matrix: include: - os: ubuntu-20.04 + ghc: "8.10.7" + cache_path: ~/.cabal/store + - os: ubuntu-20.04 + ghc: "9.6.3" cache_path: ~/.cabal/store asset_name: simplex-chat-ubuntu-20_04-x86-64 desktop_asset_name: simplex-desktop-ubuntu-20_04-x86_64.deb - os: ubuntu-22.04 + ghc: "9.6.3" cache_path: ~/.cabal/store asset_name: simplex-chat-ubuntu-22_04-x86-64 desktop_asset_name: simplex-desktop-ubuntu-22_04-x86_64.deb - os: macos-latest + ghc: "9.6.3" cache_path: ~/.cabal/store asset_name: simplex-chat-macos-x86-64 desktop_asset_name: simplex-desktop-macos-x86_64.dmg - os: windows-latest + ghc: "9.6.3" cache_path: C:/cabal asset_name: simplex-chat-windows-x86-64 desktop_asset_name: simplex-desktop-windows-x86_64.msi @@ -81,16 +88,17 @@ jobs: - name: Setup Haskell uses: haskell-actions/setup@v2 with: - ghc-version: "9.6.3" + ghc-version: ${{ matrix.ghc }} cabal-version: "3.10.1.0" - - name: Cache dependencies - uses: actions/cache@v3 + - name: Restore cached build + id: restore_cache + uses: actions/cache/restore@v3 with: path: | ${{ matrix.cache_path }} dist-newstyle - key: ${{ matrix.os }}-${{ hashFiles('cabal.project', 'simplex-chat.cabal') }} + key: ${{ matrix.os }}-ghc${{ matrix.ghc }}-${{ hashFiles('cabal.project', 'simplex-chat.cabal') }} # / Unix @@ -105,7 +113,7 @@ jobs: echo " flags: +openssl" >> cabal.project.local - name: Install AppImage dependencies - if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'ubuntu-20.04' + if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os == 'ubuntu-20.04' run: sudo apt install -y desktop-file-utils - name: Install pkg-config for Mac @@ -131,7 +139,7 @@ jobs: echo "bin_hash=$(echo SHA2-512\(${{ matrix.asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - name: Unix upload CLI binary to release - if: startsWith(github.ref, 'refs/tags/v') && matrix.os != 'windows-latest' + if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os != 'windows-latest' uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} @@ -140,7 +148,7 @@ jobs: tag: ${{ github.ref }} - name: Unix update CLI binary hash - if: startsWith(github.ref, 'refs/tags/v') && matrix.os != 'windows-latest' + if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os != 'windows-latest' uses: softprops/action-gh-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -150,7 +158,7 @@ jobs: ${{ steps.unix_cli_build.outputs.bin_hash }} - name: Setup Java - if: startsWith(github.ref, 'refs/tags/v') + if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name uses: actions/setup-java@v3 with: distribution: 'corretto' @@ -159,7 +167,7 @@ jobs: - name: Linux build desktop id: linux_desktop_build - if: startsWith(github.ref, 'refs/tags/v') && (matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04') + if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && (matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04') shell: bash run: | scripts/desktop/build-lib-linux.sh @@ -168,10 +176,10 @@ jobs: path=$(echo $PWD/release/main/deb/simplex_*_amd64.deb) echo "package_path=$path" >> $GITHUB_OUTPUT echo "package_hash=$(echo SHA2-512\(${{ matrix.desktop_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - + - name: Linux make AppImage id: linux_appimage_build - if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'ubuntu-20.04' + if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os == 'ubuntu-20.04' shell: bash run: | scripts/desktop/make-appimage-linux.sh @@ -194,7 +202,7 @@ jobs: echo "package_hash=$(echo SHA2-512\(${{ matrix.desktop_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - name: Linux upload desktop package to release - if: startsWith(github.ref, 'refs/tags/v') && (matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04') + if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && (matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04') uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} @@ -203,7 +211,7 @@ jobs: tag: ${{ github.ref }} - name: Linux update desktop package hash - if: startsWith(github.ref, 'refs/tags/v') && (matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04') + if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && (matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04') uses: softprops/action-gh-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -213,7 +221,7 @@ jobs: ${{ steps.linux_desktop_build.outputs.package_hash }} - name: Linux upload AppImage to release - if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'ubuntu-20.04' + if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os == 'ubuntu-20.04' uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} @@ -222,7 +230,7 @@ jobs: tag: ${{ github.ref }} - name: Linux update AppImage hash - if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'ubuntu-20.04' + if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os == 'ubuntu-20.04' uses: softprops/action-gh-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -250,6 +258,15 @@ jobs: body: | ${{ steps.mac_desktop_build.outputs.package_hash }} + - name: Cache unix build + uses: actions/cache/save@v3 + if: matrix.os != 'windows-latest' + with: + path: | + ${{ matrix.cache_path }} + dist-newstyle + key: ${{ steps.restore_cache.outputs.cache-primary-key }} + - name: Unix test if: matrix.os != 'windows-latest' timeout-minutes: 30 @@ -330,7 +347,7 @@ jobs: path=$(echo $PWD/release/main/msi/*imple*.msi | sed 's#/\([a-z]\)#\1:#' | sed 's#/#\\#g') echo "package_path=$path" >> $GITHUB_OUTPUT echo "package_hash=$(echo SHA2-512\(${{ matrix.desktop_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - + - name: Windows upload desktop package to release if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest' uses: svenstaro/upload-release-action@v2 @@ -350,4 +367,13 @@ jobs: body: | ${{ steps.windows_desktop_build.outputs.package_hash }} + - name: Cache windows build + uses: actions/cache/save@v3 + if: matrix.os == 'windows-latest' + with: + path: | + ${{ matrix.cache_path }} + dist-newstyle + key: ${{ steps.restore_cache.outputs.cache-primary-key }} + # Windows / diff --git a/cabal.project b/cabal.project index baf7d5c4c1..dbd050bcf1 100644 --- a/cabal.project +++ b/cabal.project @@ -2,8 +2,6 @@ packages: . -- packages: . ../simplexmq -- packages: . ../simplexmq ../direct-sqlcipher ../sqlcipher-simple -with-compiler: ghc-9.6.3 - index-state: 2023-12-12T00:00:00Z package cryptostore diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 9d7f5bccc3..d61fc5f5a5 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -5,7 +5,6 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiWayIf #-} {-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} @@ -279,8 +278,9 @@ newChatController where configServers :: DefaultAgentServers configServers = - let smp' = fromMaybe (defaultServers.smp) (nonEmpty smpServers) - xftp' = fromMaybe (defaultServers.xftp) (nonEmpty xftpServers) + let DefaultAgentServers {smp = defSmp, xftp = defXftp} = defaultServers + smp' = fromMaybe defSmp (nonEmpty smpServers) + xftp' = fromMaybe defXftp (nonEmpty xftpServers) in defaultServers {smp = smp', xftp = xftp', netCfg = networkConfig} agentServers :: ChatConfig -> IO InitialAgentServers agentServers config@ChatConfig {defaultServers = defServers@DefaultAgentServers {ntf, netCfg}} = do @@ -307,9 +307,9 @@ activeAgentServers ChatConfig {defaultServers} p = . filter (\ServerCfg {enabled} -> enabled) cfgServers :: UserProtocol p => SProtocolType p -> (DefaultAgentServers -> NonEmpty (ProtoServerWithAuth p)) -cfgServers p s = case p of - SPSMP -> s.smp - SPXFTP -> s.xftp +cfgServers p DefaultAgentServers {smp, xftp} = case p of + SPSMP -> smp + SPXFTP -> xftp startChatController :: forall m. ChatMonad' m => Bool -> Bool -> Bool -> m (Async ()) startChatController subConns enableExpireCIs startXFTPWorkers = do @@ -971,7 +971,8 @@ processChatCommand' vr = \case pure $ CRContactConnectionDeleted user conn CTGroup -> do Group gInfo@GroupInfo {membership} members <- withStore $ \db -> getGroup db vr user chatId - let isOwner = membership.memberRole == GROwner + let GroupMember {memberRole = membershipMemRole} = membership + let isOwner = membershipMemRole == GROwner canDelete = isOwner || not (memberCurrent membership) unless canDelete $ throwChatError $ CEGroupUserRole gInfo GROwner filesInfo <- withStore' $ \db -> getGroupFileInfo db user gInfo @@ -1611,11 +1612,12 @@ processChatCommand' vr = \case inv@ReceivedGroupInvitation {fromMember} <- getGroupInvitation db vr user groupId (inv,) <$> getContactViaMember db user fromMember let ReceivedGroupInvitation {fromMember, connRequest, groupInfo = g@GroupInfo {membership}} = invitation + GroupMember {memberId = membershipMemId} = membership Contact {activeConn} = ct case activeConn of Just Connection {peerChatVRange} -> do subMode <- chatReadVar subscriptionMode - dm <- directMessage $ XGrpAcpt membership.memberId + dm <- directMessage $ XGrpAcpt membershipMemId agentConnId <- withAgent $ \a -> joinConnection a (aUserId user) True connRequest dm subMode withStore' $ \db -> do createMemberConnection db userId fromMember agentConnId (fromJVersionRange peerChatVRange) subMode @@ -1767,12 +1769,12 @@ processChatCommand' vr = \case pure $ CRNewMemberContact user ct g m _ -> throwChatError CEGroupMemberNotActive APISendMemberContactInvitation contactId msgContent_ -> withUser $ \user -> do - (g, m, ct, cReq) <- withStore $ \db -> getMemberContact db vr user contactId + (g@GroupInfo {groupId}, m, ct, cReq) <- withStore $ \db -> getMemberContact db vr user contactId when (contactGrpInvSent ct) $ throwChatError $ CECommandError "x.grp.direct.inv already sent" case memberConn m of Just mConn -> do let msg = XGrpDirectInv cReq msgContent_ - (sndMsg, _) <- sendDirectMessage mConn msg (GroupId $ g.groupId) + (sndMsg, _) <- sendDirectMessage mConn msg $ GroupId groupId withStore' $ \db -> setContactGrpInvSent db ct True let ct' = ct {contactGrpInvSent = True} forM_ msgContent_ $ \mc -> do @@ -2191,7 +2193,8 @@ processChatCommand' vr = \case when (displayName /= validName) $ throwChatError CEInvalidDisplayName {displayName, validName} assertUserGroupRole :: GroupInfo -> GroupMemberRole -> m () assertUserGroupRole g@GroupInfo {membership} requiredRole = do - when (membership.memberRole < requiredRole) $ throwChatError $ CEGroupUserRole g requiredRole + let GroupMember {memberRole = membershipMemRole} = membership + when (membershipMemRole < requiredRole) $ throwChatError $ CEGroupUserRole g requiredRole when (memberStatus membership == GSMemInvited) $ throwChatError (CEGroupNotJoined g) when (memberRemoved membership) $ throwChatError CEGroupMemberUserRemoved unless (memberActive membership) $ throwChatError CEGroupMemberNotActive @@ -2235,7 +2238,7 @@ processChatCommand' vr = \case forwardFile chatName fileId sendCommand = withUser $ \user -> do withStore (\db -> getFileTransfer db user fileId) >>= \case FTRcv RcvFileTransfer {fileStatus = RFSComplete RcvFileInfo {filePath}, cryptoArgs} -> forward filePath cryptoArgs - FTSnd {fileTransferMeta = FileTransferMeta {filePath, xftpSndFile}} -> forward filePath $ xftpSndFile >>= \f -> f.cryptoArgs + FTSnd {fileTransferMeta = FileTransferMeta {filePath, xftpSndFile}} -> forward filePath $ xftpSndFile >>= \XFTPSndFile {cryptoArgs} -> cryptoArgs _ -> throwChatError CEFileNotReceived {fileId} where forward path cfArgs = processChatCommand . sendCommand chatName $ CryptoFile path cfArgs @@ -2327,7 +2330,7 @@ processChatCommand' vr = \case _ -> throwChatError $ CECommandError "not supported" processChatCommand $ APISetChatSettings (ChatRef cType chatId) $ updateSettings chatSettings connectPlan :: User -> AConnectionRequestUri -> m ConnectionPlan - connectPlan user (ACR SCMInvitation cReq) = do + connectPlan user (ACR SCMInvitation (CRInvitationUri crData e2e)) = do withStore' (\db -> getConnectionEntityByConnReq db vr user cReqSchemas) >>= \case Nothing -> pure $ CPInvitationLink ILPOk Just (RcvDirectMsgConnection conn ct_) -> do @@ -2343,13 +2346,12 @@ processChatCommand' vr = \case Just _ -> throwChatError $ CECommandError "found connection entity is not RcvDirectMsgConnection" where cReqSchemas :: (ConnReqInvitation, ConnReqInvitation) - cReqSchemas = case cReq of - (CRInvitationUri crData e2e) -> - ( CRInvitationUri crData {crScheme = CRSSimplex} e2e, - CRInvitationUri crData {crScheme = simplexChat} e2e - ) - connectPlan user (ACR SCMContact cReq) = do - let CRContactUri ConnReqUriData {crClientData} = cReq + cReqSchemas = + ( CRInvitationUri crData {crScheme = CRSSimplex} e2e, + CRInvitationUri crData {crScheme = simplexChat} e2e + ) + connectPlan user (ACR SCMContact (CRContactUri crData)) = do + let ConnReqUriData {crClientData} = crData groupLinkId = crClientData >>= decodeJSON >>= \(CRDataGroup gli) -> Just gli case groupLinkId of -- contact address @@ -2389,11 +2391,10 @@ processChatCommand' vr = \case | otherwise -> pure $ CPGroupLink GLPOk where cReqSchemas :: (ConnReqContact, ConnReqContact) - cReqSchemas = case cReq of - (CRContactUri crData) -> - ( CRContactUri crData {crScheme = CRSSimplex}, - CRContactUri crData {crScheme = simplexChat} - ) + cReqSchemas = + ( CRContactUri crData {crScheme = CRSSimplex}, + CRContactUri crData {crScheme = simplexChat} + ) cReqHashes :: (ConnReqUriHash, ConnReqUriHash) cReqHashes = bimap hash hash cReqSchemas hash = ConnReqUriHash . C.sha256Hash . strEncode @@ -3561,9 +3562,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = case chatMsgEvent of XGrpMemInfo memId _memProfile | sameMemberId memId m -> do + let GroupMember {memberId = membershipMemId} = membership -- TODO update member profile -- [async agent commands] no continuation needed, but command should be asynchronous for stability - allowAgentConnectionAsync user conn' confId $ XGrpMemInfo membership.memberId (fromLocalProfile $ memberProfile membership) + allowAgentConnectionAsync user conn' confId $ XGrpMemInfo membershipMemId (fromLocalProfile $ memberProfile membership) | otherwise -> messageError "x.grp.mem.info: memberId is different from expected" _ -> messageError "CONF from member must have x.grp.mem.info" INFO connInfo -> do @@ -3689,7 +3691,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = fInv = xftpFileInvitation fileName fileSize fInvDescr in Just (fInv, fileDescrText) | otherwise = Nothing - processContentItem :: GroupMember -> ChatItem 'CTGroup d -> MsgContent -> Maybe (FileInvitation, RcvFileDescrText) -> m [ChatMsgEvent Json] + processContentItem :: GroupMember -> ChatItem 'CTGroup d -> MsgContent -> Maybe (FileInvitation, RcvFileDescrText) -> m [ChatMsgEvent 'Json] processContentItem sender ChatItem {meta, quotedItem} mc fInvDescr_ = if isNothing fInvDescr_ && not (msgContentHasText mc) then pure [] @@ -3724,17 +3726,18 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = when (memCategory == GCPreMember) $ probeMatchingContactsAndMembers ct connectedIncognito True sendXGrpMemCon memCategory where + GroupMember {memberId} = m sendXGrpMemCon = \case GCPreMember -> forM_ (invitedByGroupMemberId membership) $ \hostId -> do host <- withStore $ \db -> getGroupMember db user groupId hostId forM_ (memberConn host) $ \hostConn -> - void $ sendDirectMessage hostConn (XGrpMemCon m.memberId) (GroupId groupId) + void $ sendDirectMessage hostConn (XGrpMemCon memberId) (GroupId groupId) GCPostMember -> forM_ (invitedByGroupMemberId m) $ \invitingMemberId -> do im <- withStore $ \db -> getGroupMember db user groupId invitingMemberId forM_ (memberConn im) $ \imConn -> - void $ sendDirectMessage imConn (XGrpMemCon m.memberId) (GroupId groupId) + void $ sendDirectMessage imConn (XGrpMemCon memberId) (GroupId groupId) _ -> messageWarning "sendXGrpMemCon: member category GCPreMember or GCPostMember is expected" MSG msgMeta _msgFlags msgBody -> do checkIntegrityCreateItem (CDGroupRcv gInfo m) msgMeta `catchChatError` \_ -> pure () @@ -3747,7 +3750,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = Left e -> toView $ CRChatError (Just user) (ChatError . CEException $ "error parsing chat message: " <> e) checkSendRcpt $ rights aChatMsgs -- currently only a single message is forwarded - when (membership.memberRole >= GRAdmin) $ case aChatMsgs of + let GroupMember {memberRole = membershipMemRole} = membership + when (membershipMemRole >= GRAdmin) $ case aChatMsgs of [Right (ACMsg _ chatMsg)] -> forwardMsg_ chatMsg _ -> pure () where @@ -3807,8 +3811,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = else pure [] -- invited members to which this member was introduced invitedMembers <- withStore' $ \db -> getForwardInvitedMembers db user m highlyAvailable - let ms = introducedMembers <> invitedMembers - msg = XGrpMsgForward m.memberId chatMsg' brokerTs + let GroupMember {memberId} = m + ms = introducedMembers <> invitedMembers + msg = XGrpMsgForward memberId chatMsg' brokerTs unless (null ms) . void $ sendGroupMessage user gInfo ms msg RCVD msgMeta msgRcpt -> @@ -4069,8 +4074,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = _ -> toView $ CRReceivedContactRequest user cReq memberCanSend :: GroupMember -> m () -> m () - memberCanSend mem a - | mem.memberRole <= GRObserver = messageError "member is not allowed to send messages" + memberCanSend GroupMember {memberRole} a + | memberRole <= GRObserver = messageError "member is not allowed to send messages" | otherwise = a incAuthErrCounter :: ConnectionEntity -> Connection -> AgentErrorType -> m () @@ -4692,12 +4697,12 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = when (fromRole < GRAdmin || fromRole < memRole) $ throwChatError (CEGroupContactRole c) when (fromMemId == memId) $ throwChatError CEGroupDuplicateMemberId -- [incognito] if direct connection with host is incognito, create membership using the same incognito profile - (gInfo@GroupInfo {groupId, localDisplayName, groupProfile, membership = membership@GroupMember {groupMemberId, memberId}}, hostId) <- - withStore $ \db -> createGroupInvitation db vr user ct inv customUserProfileId + (gInfo@GroupInfo {groupId, localDisplayName, groupProfile, membership}, hostId) <- withStore $ \db -> createGroupInvitation db vr user ct inv customUserProfileId + let GroupMember {groupMemberId, memberId = membershipMemId} = membership if sameGroupLinkId groupLinkId groupLinkId' then do subMode <- chatReadVar subscriptionMode - dm <- directMessage $ XGrpAcpt memberId + dm <- directMessage $ XGrpAcpt membershipMemId connIds <- joinAgentConnectionAsync user True connRequest dm subMode withStore' $ \db -> do setViaGroupLinkHash db groupId connId @@ -5128,6 +5133,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = xGrpMemFwd :: GroupInfo -> GroupMember -> MemberInfo -> IntroInvitation -> m () xGrpMemFwd gInfo@GroupInfo {membership, chatSettings} m memInfo@(MemberInfo memId memRole memChatVRange _) introInv@IntroInvitation {groupConnReq, directConnReq} = do + let GroupMember {memberId = membershipMemId} = membership checkHostRole m memRole toMember <- withStore' (\db -> runExceptT $ getGroupMemberByMemberId db user gInfo memId) >>= \case @@ -5140,7 +5146,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = withStore' $ \db -> saveMemberInvitation db toMember introInv subMode <- chatReadVar subscriptionMode -- [incognito] send membership incognito profile, create direct connection as incognito - dm <- directMessage $ XGrpMemInfo membership.memberId (fromLocalProfile $ memberProfile membership) + dm <- directMessage $ XGrpMemInfo membershipMemId (fromLocalProfile $ memberProfile membership) -- [async agent commands] no continuation needed, but commands should be asynchronous for stability groupConnIds <- joinAgentConnectionAsync user (chatHasNtfs chatSettings) groupConnReq dm subMode directConnIds <- forM directConnReq $ \dcr -> joinAgentConnectionAsync user True dcr dm subMode @@ -5150,7 +5156,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = xGrpMemRole :: GroupInfo -> GroupMember -> MemberId -> GroupMemberRole -> RcvMessage -> UTCTime -> m () xGrpMemRole gInfo@GroupInfo {membership} m@GroupMember {memberRole = senderRole} memId memRole msg brokerTs - | membership.memberId == memId = + | membershipMemId == memId = let gInfo' = gInfo {membership = membership {memberRole = memRole}} in changeMemberRole gInfo' membership $ RGEUserRole memRole | otherwise = @@ -5158,6 +5164,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = Right member -> changeMemberRole gInfo member $ RGEMemberRole (groupMemberId' member) (fromLocalProfile $ memberProfile member) memRole Left _ -> messageError "x.grp.mem.role with unknown member ID" where + GroupMember {memberId = membershipMemId} = membership changeMemberRole gInfo' member@GroupMember {memberRole = fromRole} gEvent | senderRole < GRAdmin || senderRole < fromRole = messageError "x.grp.mem.role with insufficient member permissions" | otherwise = do @@ -5211,7 +5218,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = xGrpMemDel :: GroupInfo -> GroupMember -> MemberId -> RcvMessage -> UTCTime -> m () xGrpMemDel gInfo@GroupInfo {membership} m@GroupMember {memberRole = senderRole} memId msg brokerTs = do - if membership.memberId == memId + let GroupMember {memberId = membershipMemId} = membership + if membershipMemId == memId then checkRole membership $ do deleteGroupLinkIfExists user gInfo -- member records are not deleted to keep history @@ -5323,8 +5331,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = createInternalChatItem user (CDDirectRcv ct) (CIRcvConnEvent RCEVerificationCodeReset) Nothing xGrpMsgForward :: GroupInfo -> GroupMember -> MemberId -> ChatMessage 'Json -> UTCTime -> m () - xGrpMsgForward gInfo@GroupInfo {groupId} m memberId msg msgTs = do - when (m.memberRole < GRAdmin) $ throwChatError (CEGroupContactRole m.localDisplayName) + xGrpMsgForward gInfo@GroupInfo {groupId} m@GroupMember {memberRole, localDisplayName} memberId msg msgTs = do + when (memberRole < GRAdmin) $ throwChatError (CEGroupContactRole localDisplayName) author <- withStore $ \db -> getGroupMemberByMemberId db user gInfo memberId processForwardedMsg author msg where @@ -5501,7 +5509,7 @@ parseFileChunk :: ChatMonad m => ByteString -> m FileChunk parseFileChunk = liftEither . first (ChatError . CEFileRcvChunk) . smpDecode appendFileChunk :: forall m. ChatMonad m => RcvFileTransfer -> Integer -> ByteString -> Bool -> m () -appendFileChunk ft@RcvFileTransfer {fileId, fileStatus, cryptoArgs} chunkNo chunk final = +appendFileChunk ft@RcvFileTransfer {fileId, fileStatus, cryptoArgs, fileInvitation = FileInvitation {fileName}} chunkNo chunk final = case fileStatus of RFSConnected RcvFileInfo {filePath} -> append_ filePath -- sometimes update of file transfer status to FSConnected @@ -5519,7 +5527,7 @@ appendFileChunk ft@RcvFileTransfer {fileId, fileStatus, cryptoArgs} chunkNo chun when final $ do closeFileHandle fileId rcvFiles forM_ cryptoArgs $ \cfArgs -> do - tmpFile <- getChatTempDirectory >>= (`uniqueCombine` ft.fileInvitation.fileName) + tmpFile <- getChatTempDirectory >>= (`uniqueCombine` fileName) tryChatError (liftError encryptErr $ encryptFile fsFilePath tmpFile cfArgs) >>= \case Right () -> do removeFile fsFilePath `catchChatError` \_ -> pure () @@ -5734,7 +5742,7 @@ sendGroupMessage user GroupInfo {groupId} members chatMsgEvent = do data MemberSendAction = MSASend Connection | MSAPending memberSendAction :: ChatMsgEvent e -> [GroupMember] -> GroupMember -> Maybe MemberSendAction -memberSendAction chatMsgEvent members m = case memberConn m of +memberSendAction chatMsgEvent members m@GroupMember {invitedByGroupMemberId} = case memberConn m of Nothing -> pendingOrForwarded Just conn@Connection {connStatus} | connDisabled conn || connStatus == ConnDeleted -> Nothing @@ -5749,7 +5757,7 @@ memberSendAction chatMsgEvent members m = case memberConn m of forwardSupported = let mcvr = memberChatVRange' m in isCompatibleRange mcvr groupForwardVRange && invitingMemberSupportsForward - invitingMemberSupportsForward = case m.invitedByGroupMemberId of + invitingMemberSupportsForward = case invitedByGroupMemberId of Just invMemberId -> -- can be optimized for large groups by replacing [GroupMember] with Map GroupMemberId GroupMember case find (\m' -> groupMemberId' m' == invMemberId) members of @@ -5804,34 +5812,33 @@ saveDirectRcvMSG conn@Connection {connId} agentMsgMeta agentAckCmdId msgBody = saveGroupRcvMsg :: (MsgEncodingI e, ChatMonad m) => User -> GroupId -> GroupMember -> Connection -> MsgMeta -> CommandId -> MsgBody -> ChatMessage e -> m (GroupMember, Connection, RcvMessage) saveGroupRcvMsg user groupId authorMember conn@Connection {connId} agentMsgMeta agentAckCmdId msgBody ChatMessage {chatVRange, msgId = sharedMsgId_, chatMsgEvent} = do - (am', conn') <- updateMemberChatVRange authorMember conn chatVRange + (am'@GroupMember {memberId = amMemId, groupMemberId = amGroupMemId}, conn') <- updateMemberChatVRange authorMember conn chatVRange let agentMsgId = fst $ recipient agentMsgMeta newMsg = NewRcvMessage {chatMsgEvent, msgBody} rcvMsgDelivery = RcvMsgDelivery {connId, agentMsgId, agentMsgMeta, agentAckCmdId} - amId = Just am'.groupMemberId msg <- - withStore (\db -> createNewMessageAndRcvMsgDelivery db (GroupId groupId) newMsg sharedMsgId_ rcvMsgDelivery amId) + withStore (\db -> createNewMessageAndRcvMsgDelivery db (GroupId groupId) newMsg sharedMsgId_ rcvMsgDelivery $ Just amGroupMemId) `catchChatError` \e -> case e of ChatErrorStore (SEDuplicateGroupMessage _ _ _ (Just forwardedByGroupMemberId)) -> do fm <- withStore $ \db -> getGroupMember db user groupId forwardedByGroupMemberId forM_ (memberConn fm) $ \fmConn -> - void $ sendDirectMessage fmConn (XGrpMemCon am'.memberId) (GroupId groupId) + void $ sendDirectMessage fmConn (XGrpMemCon amMemId) (GroupId groupId) throwError e _ -> throwError e pure (am', conn', msg) saveGroupFwdRcvMsg :: (MsgEncodingI e, ChatMonad m) => User -> GroupId -> GroupMember -> GroupMember -> MsgBody -> ChatMessage e -> m RcvMessage -saveGroupFwdRcvMsg user groupId forwardingMember refAuthorMember msgBody ChatMessage {msgId = sharedMsgId_, chatMsgEvent} = do +saveGroupFwdRcvMsg user groupId forwardingMember refAuthorMember@GroupMember {memberId = refMemberId} msgBody ChatMessage {msgId = sharedMsgId_, chatMsgEvent} = do let newMsg = NewRcvMessage {chatMsgEvent, msgBody} fwdMemberId = Just $ groupMemberId' forwardingMember refAuthorId = Just $ groupMemberId' refAuthorMember withStore (\db -> createNewRcvMessage db (GroupId groupId) newMsg sharedMsgId_ refAuthorId fwdMemberId) `catchChatError` \e -> case e of ChatErrorStore (SEDuplicateGroupMessage _ _ (Just authorGroupMemberId) Nothing) -> do - am <- withStore $ \db -> getGroupMember db user groupId authorGroupMemberId - if sameMemberId refAuthorMember.memberId am + am@GroupMember {memberId = amMemberId} <- withStore $ \db -> getGroupMember db user groupId authorGroupMemberId + if sameMemberId refMemberId am then forM_ (memberConn forwardingMember) $ \fmConn -> - void $ sendDirectMessage fmConn (XGrpMemCon am.memberId) (GroupId groupId) + void $ sendDirectMessage fmConn (XGrpMemCon amMemberId) (GroupId groupId) else toView $ CRMessageError user "error" "saveGroupFwdRcvMsg: referenced author member id doesn't match message member id" throwError e _ -> throwError e @@ -5977,7 +5984,9 @@ createSndFeatureItems :: forall m. ChatMonad m => User -> Contact -> Contact -> createSndFeatureItems user ct ct' = createFeatureItems user ct ct' CDDirectSnd CISndChatFeature CISndChatPreference getPref where - getPref u = (userPreference u).preference + getPref ContactUserPreference {userPreference} = case userPreference of + CUPContact {preference} -> preference + CUPUser {preference} -> preference type FeatureContent a d = ChatFeature -> a -> Maybe Int -> CIContent d @@ -6060,8 +6069,8 @@ getCreateActiveUser st testView = do Left e -> putStrLn ("database error " <> show e) >> exitFailure Right user -> pure user selectUser :: [User] -> IO User - selectUser [user] = do - withTransaction st (`setActiveUser` user.userId) + selectUser [user@User {userId}] = do + withTransaction st (`setActiveUser` userId) pure user selectUser users = do putStrLn "Select user profile:" @@ -6075,8 +6084,8 @@ getCreateActiveUser st testView = do Just n | n <= 0 || n > length users -> putStrLn "invalid user number" >> loop | otherwise -> do - let user = users !! (n - 1) - withTransaction st (`setActiveUser` user.userId) + let user@User {userId} = users !! (n - 1) + withTransaction st (`setActiveUser` userId) pure user userStr :: User -> String userStr User {localDisplayName, profile = LocalProfile {fullName}} = diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index 74b41dc9f2..4ee2e9cc15 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -5,7 +5,6 @@ {-# LANGUAGE KindSignatures #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} @@ -345,7 +344,9 @@ contactTimedTTL Contact {mergedPreferences = ContactUserPreferences {timedMessag | forUser enabled && forContact enabled = Just ttl | otherwise = Nothing where - TimedMessagesPreference {ttl} = userPreference.preference + TimedMessagesPreference {ttl} = case userPreference of + CUPContact {preference} -> preference + CUPUser {preference} -> preference groupTimedTTL :: GroupInfo -> Maybe (Maybe Int) groupTimedTTL GroupInfo {fullGroupPreferences = FullGroupPreferences {timedMessages = TimedMessagesGroupPreference {enable, ttl}}} diff --git a/src/Simplex/Chat/Messages/Batch.hs b/src/Simplex/Chat/Messages/Batch.hs index 8b06873a33..3e3a1fd0b6 100644 --- a/src/Simplex/Chat/Messages/Batch.hs +++ b/src/Simplex/Chat/Messages/Batch.hs @@ -16,7 +16,6 @@ import Simplex.Chat.Controller (ChatError (..), ChatErrorType (..)) import Simplex.Chat.Messages data MsgBatch = MsgBatch Builder [SndMessage] - deriving (Show) -- | Batches [SndMessage] into batches of ByteString builders in form of JSON arrays. -- Does not check if the resulting batch is a valid JSON. diff --git a/src/Simplex/Chat/Mobile/Shared.hs b/src/Simplex/Chat/Mobile/Shared.hs index a4961c15f3..5f13f58c72 100644 --- a/src/Simplex/Chat/Mobile/Shared.hs +++ b/src/Simplex/Chat/Mobile/Shared.hs @@ -16,12 +16,12 @@ type JSONByteString = LB.ByteString getByteString :: Ptr Word8 -> CInt -> IO ByteString getByteString ptr len = do fp <- newForeignPtr_ ptr - pure $ BS fp $ fromIntegral len + pure $ PS fp 0 (fromIntegral len) {-# INLINE getByteString #-} putByteString :: Ptr Word8 -> ByteString -> IO () -putByteString ptr (BS fp len) = - withForeignPtr fp $ \p -> memcpy ptr p len +putByteString ptr (PS fp offset len) = + withForeignPtr fp $ \p -> memcpy ptr (p `plusPtr` offset) len {-# INLINE putByteString #-} putLazyByteString :: Ptr Word8 -> LB.ByteString -> IO () diff --git a/src/Simplex/Chat/Store/Direct.hs b/src/Simplex/Chat/Store/Direct.hs index 7504f19c95..4222994651 100644 --- a/src/Simplex/Chat/Store/Direct.hs +++ b/src/Simplex/Chat/Store/Direct.hs @@ -2,7 +2,6 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE ScopedTypeVariables #-} @@ -489,7 +488,7 @@ createOrUpdateContactRequest db user@User {userId} userContactLinkId invId (Vers ExceptT $ maybeM getContactRequestByXContactId xContactId_ >>= \case Nothing -> createContactRequest - Just cr -> updateContactRequest cr $> Right cr.contactRequestId + Just cr@UserContactRequest {contactRequestId} -> updateContactRequest cr $> Right contactRequestId getContactRequest db user cReqId createContactRequest :: IO (Either StoreError Int64) createContactRequest = do diff --git a/src/Simplex/Chat/Store/Files.hs b/src/Simplex/Chat/Store/Files.hs index 4d419c5727..8789ccd868 100644 --- a/src/Simplex/Chat/Store/Files.hs +++ b/src/Simplex/Chat/Store/Files.hs @@ -2,7 +2,6 @@ {-# LANGUAGE GADTs #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE ScopedTypeVariables #-} @@ -929,7 +928,7 @@ getLocalCryptoFile db userId fileId sent = _ -> do unless sent $ throwError $ SEFileNotFound fileId FileTransferMeta {filePath, xftpSndFile} <- getFileTransferMeta_ db userId fileId - pure $ CryptoFile filePath $ xftpSndFile >>= \f -> f.cryptoArgs + pure $ CryptoFile filePath $ xftpSndFile >>= \XFTPSndFile {cryptoArgs} -> cryptoArgs updateDirectCIFileStatus :: forall d. MsgDirectionI d => DB.Connection -> VersionRange -> User -> Int64 -> CIFileStatus d -> ExceptT StoreError IO AChatItem updateDirectCIFileStatus db vr user fileId fileStatus = do diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index 2066626364..e9ec8be28c 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -2,7 +2,6 @@ {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE RecordWildCards #-} @@ -320,7 +319,7 @@ createNewGroup db vr gVar user@User {userId} groupProfile incognitoProfile = Exc -- | creates a new group record for the group the current user was invited to, or returns an existing one createGroupInvitation :: DB.Connection -> VersionRange -> User -> Contact -> GroupInvitation -> Maybe ProfileId -> ExceptT StoreError IO (GroupInfo, GroupMemberId) createGroupInvitation _ _ _ Contact {localDisplayName, activeConn = Nothing} _ _ = throwError $ SEContactNotReady localDisplayName -createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activeConn = Just hostConn@Connection {customUserProfileId}} GroupInvitation {fromMember, invitedMember, connRequest, groupProfile} incognitoProfileId = do +createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activeConn = Just Connection {customUserProfileId, peerChatVRange}} GroupInvitation {fromMember, invitedMember, connRequest, groupProfile} incognitoProfileId = do liftIO getInvitationGroupId_ >>= \case Nothing -> createGroupInvitation_ Just gId -> do @@ -358,7 +357,7 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ "INSERT INTO groups (group_profile_id, local_display_name, inv_queue_info, host_conn_custom_user_profile_id, user_id, enable_ntfs, created_at, updated_at, chat_ts) VALUES (?,?,?,?,?,?,?,?,?)" (profileId, localDisplayName, connRequest, customUserProfileId, userId, True, currentTs, currentTs, currentTs) insertedRowId db - let JVersionRange hostVRange = hostConn.peerChatVRange + let JVersionRange hostVRange = peerChatVRange GroupMember {groupMemberId} <- createContactMemberInv_ db user groupId Nothing contact fromMember GCHostMember GSMemInvited IBUnknown Nothing currentTs hostVRange membership <- createContactMemberInv_ db user groupId (Just groupMemberId) user invitedMember GCUserMember GSMemInvited (IBContact contactId) incognitoProfileId currentTs vr let chatSettings = ChatSettings {enableNtfs = MFAll, sendRcpts = Nothing, favorite = False} @@ -1041,7 +1040,7 @@ updateIntroStatus db introId introStatus = do [":intro_status" := introStatus, ":updated_at" := currentTs, ":intro_id" := introId] saveIntroInvitation :: DB.Connection -> GroupMember -> GroupMember -> IntroInvitation -> ExceptT StoreError IO GroupMemberIntro -saveIntroInvitation db reMember toMember introInv = do +saveIntroInvitation db reMember toMember introInv@IntroInvitation {groupConnReq} = do intro <- getIntroduction db reMember toMember liftIO $ do currentTs <- getCurrentTime @@ -1056,7 +1055,7 @@ saveIntroInvitation db reMember toMember introInv = do WHERE group_member_intro_id = :intro_id |] [ ":intro_status" := GMIntroInvReceived, - ":group_queue_info" := introInv.groupConnReq, + ":group_queue_info" := groupConnReq, ":direct_queue_info" := directConnReq introInv, ":updated_at" := currentTs, ":intro_id" := introId intro diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index de2dfa8b58..dd832d6fa4 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -9,7 +9,6 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ScopedTypeVariables #-} @@ -61,21 +60,21 @@ class IsContact a where preferences' :: a -> Maybe Preferences instance IsContact User where - contactId' u = u.userContactId + contactId' User {userContactId} = userContactId {-# INLINE contactId' #-} - profile' u = u.profile + profile' User {profile} = profile {-# INLINE profile' #-} - localDisplayName' u = u.localDisplayName + localDisplayName' User {localDisplayName} = localDisplayName {-# INLINE localDisplayName' #-} preferences' User {profile = LocalProfile {preferences}} = preferences {-# INLINE preferences' #-} instance IsContact Contact where - contactId' c = c.contactId + contactId' Contact {contactId} = contactId {-# INLINE contactId' #-} - profile' c = c.profile + profile' Contact {profile} = profile {-# INLINE profile' #-} - localDisplayName' c = c.localDisplayName + localDisplayName' Contact {localDisplayName} = localDisplayName {-# INLINE localDisplayName' #-} preferences' Contact {profile = LocalProfile {preferences}} = preferences {-# INLINE preferences' #-} @@ -196,7 +195,7 @@ directOrUsed ct@Contact {contactUsed} = contactDirect ct || contactUsed anyDirectOrUsed :: Contact -> Bool -anyDirectOrUsed Contact {contactUsed, activeConn} = ((\c -> c.connLevel) <$> activeConn) == Just 0 || contactUsed +anyDirectOrUsed Contact {contactUsed, activeConn} = ((\Connection {connLevel} -> connLevel) <$> activeConn) == Just 0 || contactUsed contactReady :: Contact -> Bool contactReady Contact {activeConn} = maybe False connReady activeConn diff --git a/src/Simplex/Chat/Types/Preferences.hs b/src/Simplex/Chat/Types/Preferences.hs index 5c79680c50..2286ae8f40 100644 --- a/src/Simplex/Chat/Types/Preferences.hs +++ b/src/Simplex/Chat/Types/Preferences.hs @@ -7,7 +7,6 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} @@ -79,12 +78,12 @@ allChatFeatures = ] chatPrefSel :: SChatFeature f -> Preferences -> Maybe (FeaturePreference f) -chatPrefSel f ps = case f of - SCFTimedMessages -> ps.timedMessages - SCFFullDelete -> ps.fullDelete - SCFReactions -> ps.reactions - SCFVoice -> ps.voice - SCFCalls -> ps.calls +chatPrefSel f Preferences {timedMessages, fullDelete, reactions, voice, calls} = case f of + SCFTimedMessages -> timedMessages + SCFFullDelete -> fullDelete + SCFReactions -> reactions + SCFVoice -> voice + SCFCalls -> calls chatFeature :: SChatFeature f -> ChatFeature chatFeature = \case @@ -104,12 +103,12 @@ instance PreferenceI (Maybe Preferences) where getPreference f prefs = fromMaybe (getPreference f defaultChatPrefs) (chatPrefSel f =<< prefs) instance PreferenceI FullPreferences where - getPreference f ps = case f of - SCFTimedMessages -> ps.timedMessages - SCFFullDelete -> ps.fullDelete - SCFReactions -> ps.reactions - SCFVoice -> ps.voice - SCFCalls -> ps.calls + getPreference f FullPreferences {timedMessages, fullDelete, reactions, voice, calls} = case f of + SCFTimedMessages -> timedMessages + SCFFullDelete -> fullDelete + SCFReactions -> reactions + SCFVoice -> voice + SCFCalls -> calls {-# INLINE getPreference #-} setPreference :: forall f. FeatureI f => SChatFeature f -> Maybe FeatureAllowed -> Maybe Preferences -> Preferences @@ -196,14 +195,14 @@ allGroupFeatures = ] groupPrefSel :: SGroupFeature f -> GroupPreferences -> Maybe (GroupFeaturePreference f) -groupPrefSel f ps = case f of - SGFTimedMessages -> ps.timedMessages - SGFDirectMessages -> ps.directMessages - SGFFullDelete -> ps.fullDelete - SGFReactions -> ps.reactions - SGFVoice -> ps.voice - SGFFiles -> ps.files - SGFHistory -> ps.history +groupPrefSel f GroupPreferences {timedMessages, directMessages, fullDelete, reactions, voice, files, history} = case f of + SGFTimedMessages -> timedMessages + SGFDirectMessages -> directMessages + SGFFullDelete -> fullDelete + SGFReactions -> reactions + SGFVoice -> voice + SGFFiles -> files + SGFHistory -> history toGroupFeature :: SGroupFeature f -> GroupFeature toGroupFeature = \case @@ -225,14 +224,14 @@ instance GroupPreferenceI (Maybe GroupPreferences) where getGroupPreference pt prefs = fromMaybe (getGroupPreference pt defaultGroupPrefs) (groupPrefSel pt =<< prefs) instance GroupPreferenceI FullGroupPreferences where - getGroupPreference f ps = case f of - SGFTimedMessages -> ps.timedMessages - SGFDirectMessages -> ps.directMessages - SGFFullDelete -> ps.fullDelete - SGFReactions -> ps.reactions - SGFVoice -> ps.voice - SGFFiles -> ps.files - SGFHistory -> ps.history + getGroupPreference f FullGroupPreferences {timedMessages, directMessages, fullDelete, reactions, voice, files, history} = case f of + SGFTimedMessages -> timedMessages + SGFDirectMessages -> directMessages + SGFFullDelete -> fullDelete + SGFReactions -> reactions + SGFVoice -> voice + SGFFiles -> files + SGFHistory -> history {-# INLINE getGroupPreference #-} -- collection of optional group preferences @@ -382,19 +381,19 @@ class (Eq (FeaturePreference f), HasField "allow" (FeaturePreference f) FeatureA prefParam :: FeaturePreference f -> Maybe Int instance HasField "allow" TimedMessagesPreference FeatureAllowed where - hasField p = (\allow -> p {allow}, p.allow) + hasField p@TimedMessagesPreference {allow} = (\a -> p {allow = a}, allow) instance HasField "allow" FullDeletePreference FeatureAllowed where - hasField p = (\allow -> p {allow}, p.allow) + hasField p@FullDeletePreference {allow} = (\a -> p {allow = a}, allow) instance HasField "allow" ReactionsPreference FeatureAllowed where - hasField p = (\allow -> p {allow}, p.allow) + hasField p@ReactionsPreference {allow} = (\a -> p {allow = a}, allow) instance HasField "allow" VoicePreference FeatureAllowed where - hasField p = (\allow -> p {allow}, p.allow) + hasField p@VoicePreference {allow} = (\a -> p {allow = a}, allow) instance HasField "allow" CallsPreference FeatureAllowed where - hasField p = (\allow -> p {allow}, p.allow) + hasField p@CallsPreference {allow} = (\a -> p {allow = a}, allow) instance FeatureI 'CFTimedMessages where type FeaturePreference 'CFTimedMessages = TimedMessagesPreference @@ -461,28 +460,28 @@ class (Eq (GroupFeaturePreference f), HasField "enable" (GroupFeaturePreference groupPrefParam :: GroupFeaturePreference f -> Maybe Int instance HasField "enable" GroupPreference GroupFeatureEnabled where - hasField p = (\enable -> p {enable}, p.enable) + hasField p@GroupPreference {enable} = (\e -> p {enable = e}, enable) instance HasField "enable" TimedMessagesGroupPreference GroupFeatureEnabled where - hasField p = (\enable -> p {enable}, p.enable) + hasField p@TimedMessagesGroupPreference {enable} = (\e -> p {enable = e}, enable) instance HasField "enable" DirectMessagesGroupPreference GroupFeatureEnabled where - hasField p = (\enable -> p {enable}, p.enable) + hasField p@DirectMessagesGroupPreference {enable} = (\e -> p {enable = e}, enable) instance HasField "enable" ReactionsGroupPreference GroupFeatureEnabled where - hasField p = (\enable -> p {enable}, p.enable) + hasField p@ReactionsGroupPreference {enable} = (\e -> p {enable = e}, enable) instance HasField "enable" FullDeleteGroupPreference GroupFeatureEnabled where - hasField p = (\enable -> p {enable}, p.enable) + hasField p@FullDeleteGroupPreference {enable} = (\e -> p {enable = e}, enable) instance HasField "enable" VoiceGroupPreference GroupFeatureEnabled where - hasField p = (\enable -> p {enable}, p.enable) + hasField p@VoiceGroupPreference {enable} = (\e -> p {enable = e}, enable) instance HasField "enable" FilesGroupPreference GroupFeatureEnabled where - hasField p = (\enable -> p {enable}, p.enable) + hasField p@FilesGroupPreference {enable} = (\e -> p {enable = e}, enable) instance HasField "enable" HistoryGroupPreference GroupFeatureEnabled where - hasField p = (\enable -> p {enable}, p.enable) + hasField p@HistoryGroupPreference {enable} = (\e -> p {enable = e}, enable) instance GroupFeatureI 'GFTimedMessages where type GroupFeaturePreference 'GFTimedMessages = TimedMessagesGroupPreference @@ -720,12 +719,12 @@ preferenceState pref = in (allow, param) getContactUserPreference :: SChatFeature f -> ContactUserPreferences -> ContactUserPreference (FeaturePreference f) -getContactUserPreference f ps = case f of - SCFTimedMessages -> ps.timedMessages - SCFFullDelete -> ps.fullDelete - SCFReactions -> ps.reactions - SCFVoice -> ps.voice - SCFCalls -> ps.calls +getContactUserPreference f ContactUserPreferences {timedMessages, fullDelete, reactions, voice, calls} = case f of + SCFTimedMessages -> timedMessages + SCFFullDelete -> fullDelete + SCFReactions -> reactions + SCFVoice -> voice + SCFCalls -> calls $(J.deriveJSON (enumJSON $ dropPrefix "CF") ''ChatFeature) diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index b0408690ae..bc17432760 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -3,7 +3,6 @@ {-# LANGUAGE GADTs #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ScopedTypeVariables #-} @@ -212,7 +211,9 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRContactConnecting u _ -> ttyUser u [] CRContactConnected u ct userCustomProfile -> ttyUser u $ viewContactConnected ct userCustomProfile testView CRContactAnotherClient u c -> ttyUser u [ttyContact' c <> ": contact is connected to another client"] - CRSubscriptionEnd u acEntity -> ttyUser u [sShow ((entityConnection acEntity).connId) <> ": END"] + CRSubscriptionEnd u acEntity -> + let Connection {connId} = entityConnection acEntity + in ttyUser u [sShow connId <> ": END"] CRContactsDisconnected srv cs -> [plain $ "server disconnected " <> showSMPServer srv <> " (" <> contactList cs <> ")"] CRContactsSubscribed srv cs -> [plain $ "server connected " <> showSMPServer srv <> " (" <> contactList cs <> ")"] CRContactSubError u c e -> ttyUser u [ttyContact' c <> ": contact error " <> sShow e] @@ -494,7 +495,7 @@ viewGroupSubscribed :: GroupInfo -> [StyledString] viewGroupSubscribed g = [membershipIncognito g <> ttyFullGroup g <> ": connected to server(s)"] showSMPServer :: SMPServer -> String -showSMPServer srv = B.unpack $ strEncode srv.host +showSMPServer ProtocolServer {host} = B.unpack $ strEncode host viewHostEvent :: AProtocolType -> TransportHost -> String viewHostEvent p h = map toUpper (B.unpack $ strEncode p) <> " host " <> B.unpack (strEncode h) @@ -953,7 +954,7 @@ viewGroupMembers (Group GroupInfo {membership} members) = map groupMember . filt removedOrLeft m = let s = memberStatus m in s == GSMemRemoved || s == GSMemLeft groupMember m = memIncognito m <> ttyFullMember m <> ": " <> plain (intercalate ", " $ [role m] <> category m <> status m <> muted m) role :: GroupMember -> String - role m = B.unpack . strEncode $ m.memberRole + role GroupMember {memberRole} = B.unpack $ strEncode memberRole category m = case memberCategory m of GCUserMember -> ["you"] GCInviteeMember -> ["invited"] @@ -991,7 +992,7 @@ viewGroupsList [] = ["you have no groups!", "to create: " <> highlight' "/g Text - ldn_ g = T.toLower g.localDisplayName + ldn_ GroupInfo {localDisplayName} = T.toLower localDisplayName groupSS (g@GroupInfo {membership, chatSettings = ChatSettings {enableNtfs}}, GroupSummary {currentMembers}) = case memberStatus membership of GSMemInvited -> groupInvitation' g @@ -1906,7 +1907,7 @@ viewChatError logLevel testView = \case "[" <> connEntityLabel entity <> ", userContactLinkId: " <> sShow userContactLinkId <> ", connId: " <> cId conn <> "] " Nothing -> "" cId :: Connection -> StyledString - cId conn = sShow conn.connId + cId Connection {connId} = sShow connId ChatErrorRemoteCtrl e -> [plain $ "remote controller error: " <> show e] ChatErrorRemoteHost RHNew e -> [plain $ "new remote host error: " <> show e] ChatErrorRemoteHost (RHId rhId) e -> [plain $ "remote host " <> show rhId <> " error: " <> show e] diff --git a/tests/Bots/BroadcastTests.hs b/tests/Bots/BroadcastTests.hs index ed0b9e069a..6eee414c32 100644 --- a/tests/Bots/BroadcastTests.hs +++ b/tests/Bots/BroadcastTests.hs @@ -1,6 +1,5 @@ {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} module Bots.BroadcastTests where @@ -13,7 +12,7 @@ import Control.Concurrent (forkIO, killThread, threadDelay) import Control.Exception (bracket) import Simplex.Chat.Bot.KnownContacts import Simplex.Chat.Core -import Simplex.Chat.Options (ChatOpts (..), CoreChatOpts (..)) +import Simplex.Chat.Options (CoreChatOpts (..)) import Simplex.Chat.Types (Profile (..)) import System.FilePath (()) import Test.Hspec @@ -34,7 +33,7 @@ broadcastBotProfile = Profile {displayName = "broadcast_bot", fullName = "Broadc mkBotOpts :: FilePath -> [KnownContact] -> BroadcastBotOpts mkBotOpts tmp publishers = BroadcastBotOpts - { coreOptions = testOpts.coreOptions {dbFilePrefix = tmp botDbPrefix}, + { coreOptions = testCoreOpts {dbFilePrefix = tmp botDbPrefix}, publishers, welcomeMessage = defaultWelcomeMessage publishers, prohibitedMessage = defaultWelcomeMessage publishers diff --git a/tests/Bots/DirectoryTests.hs b/tests/Bots/DirectoryTests.hs index 3c6991bb52..4ddf9fff1b 100644 --- a/tests/Bots/DirectoryTests.hs +++ b/tests/Bots/DirectoryTests.hs @@ -1,6 +1,5 @@ {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PostfixOperators #-} @@ -19,7 +18,7 @@ import GHC.IO.Handle (hClose) import Simplex.Chat.Bot.KnownContacts import Simplex.Chat.Controller (ChatConfig (..)) import Simplex.Chat.Core -import Simplex.Chat.Options (ChatOpts (..), CoreChatOpts (..)) +import Simplex.Chat.Options (CoreChatOpts (..)) import Simplex.Chat.Types (GroupMemberRole (..), Profile (..)) import System.FilePath (()) import Test.Hspec @@ -64,7 +63,7 @@ directoryProfile = Profile {displayName = "SimpleX-Directory", fullName = "", im mkDirectoryOpts :: FilePath -> [KnownContact] -> DirectoryOpts mkDirectoryOpts tmp superUsers = DirectoryOpts - { coreOptions = testOpts.coreOptions {dbFilePrefix = tmp serviceDbPrefix}, + { coreOptions = testCoreOpts {dbFilePrefix = tmp serviceDbPrefix}, superUsers, directoryLog = Just $ tmp "directory_service.log", serviceName = "SimpleX-Directory", diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index d2165db04f..942f43bbe7 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -58,22 +58,7 @@ serverPort = "7001" testOpts :: ChatOpts testOpts = ChatOpts - { coreOptions = - CoreChatOpts - { dbFilePrefix = undefined, - dbKey = "", - -- dbKey = "this is a pass-phrase to encrypt the database", - smpServers = ["smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001"], - xftpServers = ["xftp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7002"], - networkConfig = defaultNetworkConfig, - logLevel = CLLImportant, - logConnections = False, - logServerHosts = False, - logAgent = Nothing, - logFile = Nothing, - tbqSize = 16, - highlyAvailable = False - }, + { coreOptions = testCoreOpts, deviceName = Nothing, chatCmd = "", chatCmdDelay = 3, @@ -87,8 +72,25 @@ testOpts = maintenance = False } +testCoreOpts :: CoreChatOpts +testCoreOpts = CoreChatOpts + { dbFilePrefix = undefined, + dbKey = "", + -- dbKey = "this is a pass-phrase to encrypt the database", + smpServers = ["smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001"], + xftpServers = ["xftp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7002"], + networkConfig = defaultNetworkConfig, + logLevel = CLLImportant, + logConnections = False, + logServerHosts = False, + logAgent = Nothing, + logFile = Nothing, + tbqSize = 16, + highlyAvailable = False + } + getTestOpts :: Bool -> ScrubbedBytes -> ChatOpts -getTestOpts maintenance dbKey = testOpts {maintenance, coreOptions = (coreOptions testOpts) {dbKey}} +getTestOpts maintenance dbKey = testOpts {maintenance, coreOptions = testCoreOpts {dbKey}} termSettings :: VirtualTerminalSettings termSettings = From 78eefee6cc972ae34b7e36f849f834d1c7266934 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Sat, 30 Dec 2023 04:28:11 +0700 Subject: [PATCH 10/16] android, desktop: search view will be shown always (#3625) * android, desktop: search view will be shown always * rearrange tree * optimization --- .../common/views/chatlist/ChatListView.kt | 19 ++++++++----------- .../commonMain/resources/MR/base/strings.xml | 3 --- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 4280f51368..59c18c9703 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -101,19 +101,16 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf } ) { Box(Modifier.padding(it).padding(end = endPadding)) { - Column( + Box( modifier = Modifier .fillMaxSize() ) { - if (chatModel.chats.isNotEmpty()) { - ChatList(chatModel, searchText = searchText) - } else if (!chatModel.switchingUsersAndHosts.value && !chatModel.desktopNoUserNoRemote) { - Box(Modifier.fillMaxSize()) { - if (!stopped && !newChatSheetState.collectAsState().value.isVisible() && chatModel.chatRunning.value == true) { - OnboardingButtons(showNewChatSheet) - } - Text(stringResource( - if (chatModel.chatRunning.value == null) MR.strings.loading_chats else MR.strings.you_have_no_chats), Modifier.align(Alignment.Center), color = MaterialTheme.colors.secondary) + ChatList(chatModel, searchText = searchText) + if (chatModel.chats.isEmpty() && !chatModel.switchingUsersAndHosts.value && !chatModel.desktopNoUserNoRemote) { + Text(stringResource( + if (chatModel.chatRunning.value == null) MR.strings.loading_chats else MR.strings.you_have_no_chats), Modifier.align(Alignment.Center), color = MaterialTheme.colors.secondary) + if (!stopped && !newChatSheetState.collectAsState().value.isVisible() && chatModel.chatRunning.value == true && searchText.value.text.isEmpty()) { + OnboardingButtons(showNewChatSheet) } } } @@ -481,7 +478,7 @@ private fun ChatList(chatModel: ChatModel, searchText: MutableStateRandom passphrase is stored in settings as plaintext.\nYou can change it later. Use random passphrase - - Paste received link - Incoming video call Incoming audio call From 644169b8358e40bf482738e3600cda69ab9fd792 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat, 30 Dec 2023 10:02:55 +0000 Subject: [PATCH 11/16] cli: prompt for database key entry if required (#3626) --- src/Simplex/Chat/Terminal.hs | 41 ++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/src/Simplex/Chat/Terminal.hs b/src/Simplex/Chat/Terminal.hs index c27675678e..2060e529eb 100644 --- a/src/Simplex/Chat/Terminal.hs +++ b/src/Simplex/Chat/Terminal.hs @@ -1,12 +1,16 @@ {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} module Simplex.Chat.Terminal where import Control.Exception (handle, throwIO) import Control.Monad +import qualified Data.ByteArray as BA import qualified Data.List.NonEmpty as L +import qualified Data.Text as T +import Data.Text.Encoding (encodeUtf8) import Database.SQLite.Simple (SQLError (..)) import qualified Database.SQLite.Simple as DB import Simplex.Chat (defaultChatConfig) @@ -19,7 +23,7 @@ import Simplex.Chat.Terminal.Output import Simplex.FileTransfer.Client.Presets (defaultXFTPServers) import Simplex.Messaging.Client (defaultNetworkConfig) import Simplex.Messaging.Util (raceAny_) -import System.Exit (exitFailure) +import System.IO (hFlush, hSetEcho, stdin, stdout) terminalChatConfig :: ChatConfig terminalChatConfig = @@ -40,18 +44,29 @@ terminalChatConfig = } simplexChatTerminal :: WithTerminal t => ChatConfig -> ChatOpts -> t -> IO () -simplexChatTerminal cfg opts t = - handle checkDBKeyError . simplexChatCore cfg opts $ \u cc -> do - ct <- newChatTerminal t opts - when (firstTime cc) . printToTerminal ct $ chatWelcome u - runChatTerminal ct cc opts - -checkDBKeyError :: SQLError -> IO () -checkDBKeyError e = case sqlError e of - DB.ErrorNotADatabase -> do - putStrLn "Database file is invalid or you passed an incorrect encryption key" - exitFailure - _ -> throwIO e +simplexChatTerminal cfg options t = run options + where + run opts@ChatOpts {coreOptions = coreOptions@CoreChatOpts {dbKey}} = + handle checkDBKeyError . simplexChatCore cfg opts $ \u cc -> do + ct <- newChatTerminal t opts + when (firstTime cc) . printToTerminal ct $ chatWelcome u + runChatTerminal ct cc opts + where + checkDBKeyError :: SQLError -> IO () + checkDBKeyError e = case sqlError e of + DB.ErrorNotADatabase -> do + putStrLn $ "Database file is invalid or " <> if BA.null dbKey then "encrypted." else "you passed an incorrect encryption key." + run =<< getKeyOpts + _ -> throwIO e + getKeyOpts :: IO ChatOpts + getKeyOpts = do + putStr "Enter database encryption key (Ctrl-C to exit):" + hFlush stdout + hSetEcho stdin False + key <- getLine + hSetEcho stdin True + putStrLn "" + pure opts {coreOptions = coreOptions {dbKey = BA.convert $ encodeUtf8 $ T.pack key}} runChatTerminal :: ChatTerminal -> ChatController -> ChatOpts -> IO () runChatTerminal ct cc opts = raceAny_ [runTerminalInput ct cc, runTerminalOutput ct cc opts, runInputLoop ct cc] From 4ab078bd183455c74d632b47b04a65547d0b7417 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat, 30 Dec 2023 14:09:07 +0000 Subject: [PATCH 12/16] ios: show clear search button when search is not empty (#3627) --- apps/ios/Shared/Views/ChatList/ChatListView.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 3d0551de66..62955a1040 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -289,16 +289,16 @@ struct ChatListSearchBar: View { HStack(spacing: 4) { Image(systemName: "magnifyingglass") TextField("Search or paste SimpleX link", text: $searchText) + .foregroundColor(searchShowingSimplexLink ? .secondary : .primary) .disabled(searchShowingSimplexLink) .focused($searchFocussed) .frame(maxWidth: .infinity) - if searchFocussed || searchShowingSimplexLink { + if !searchText.isEmpty { Image(systemName: "xmark.circle.fill") - .opacity(searchText == "" ? 0 : 1) .onTapGesture { searchText = "" } - } else if searchText == "" { + } else if !searchFocussed { HStack(spacing: 24) { if m.pasteboardHasStrings { Image(systemName: "doc") From 809040c7bc40f908a1f16fe5c718be5422aedfc4 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat, 30 Dec 2023 18:57:10 +0000 Subject: [PATCH 13/16] ui: show secrets on tap (#3628) * ios: show secrets on tap * android: show secrets on tap/click * android: clickable links in group descriptions * android: hide secrets one by one * ios: clickable links in welcome message preview * refactor * refactor2 --- .../Views/Chat/ChatItem/FramedItemView.swift | 34 +++++++++++++------ .../Views/Chat/ChatItem/MsgContentView.swift | 22 ++++++++---- .../Shared/Views/Chat/ChatItemInfoView.swift | 15 ++++++-- .../Chat/ComposeMessage/ContextItemView.swift | 3 +- .../Views/Chat/Group/GroupWelcomeView.swift | 3 +- .../Views/ChatList/ChatPreviewView.swift | 4 +-- apps/ios/SimpleXChat/ChatTypes.swift | 4 +++ .../common/views/chat/ChatItemInfoView.kt | 1 + .../common/views/chat/ContextItemView.kt | 1 + .../views/chat/group/WelcomeMessageView.kt | 5 ++- .../common/views/chat/item/FramedItemView.kt | 3 +- .../common/views/chat/item/TextItemView.kt | 27 ++++++++++++--- .../common/views/chatlist/ChatPreviewView.kt | 1 + .../common/views/onboarding/HowItWorks.kt | 11 ------ 14 files changed, 91 insertions(+), 43 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift index 51dfa3cb50..7b5dd40e97 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift @@ -28,7 +28,9 @@ struct FramedItemView: View { @State var metaColor = Color.secondary @State var showFullScreenImage = false @Binding var allowMenu: Bool - + @State private var showSecrets = false + @State private var showQuoteSecrets = false + @Binding var audioPlayer: AudioPlayer? @Binding var playbackState: VoiceMessagePlaybackState @Binding var playbackTime: TimeInterval? @@ -252,10 +254,12 @@ struct FramedItemView: View { } private func ciQuotedMsgTextView(_ qi: CIQuote, lines: Int) -> some View { - MsgContentView(chat: chat, text: qi.text, formattedText: qi.formattedText) - .lineLimit(lines) - .font(.subheadline) - .padding(.bottom, 6) + toggleSecrets(qi.formattedText, $showQuoteSecrets, + MsgContentView(chat: chat, text: qi.text, formattedText: qi.formattedText, showSecrets: showQuoteSecrets) + .lineLimit(lines) + .font(.subheadline) + .padding(.bottom, 6) + ) } private func ciQuoteIconView(_ image: String) -> some View { @@ -278,13 +282,15 @@ struct FramedItemView: View { @ViewBuilder private func ciMsgContentView(_ ci: ChatItem) -> some View { let text = ci.meta.isLive ? ci.content.msgContent?.text ?? ci.text : ci.text let rtl = isRightToLeft(text) - let v = MsgContentView( + let ft = text == "" ? [] : ci.formattedText + let v = toggleSecrets(ft, $showSecrets, MsgContentView( chat: chat, text: text, - formattedText: text == "" ? [] : ci.formattedText, + formattedText: ft, meta: ci.meta, - rightToLeft: rtl - ) + rightToLeft: rtl, + showSecrets: showSecrets + )) .multilineTextAlignment(rtl ? .trailing : .leading) .padding(.vertical, 6) .padding(.horizontal, 12) @@ -298,7 +304,7 @@ struct FramedItemView: View { v } } - + @ViewBuilder private func ciFileView(_ ci: ChatItem, _ text: String) -> some View { CIFileView(file: chatItem.file, edited: chatItem.meta.itemEdited) .overlay(DetermineWidth()) @@ -318,6 +324,14 @@ struct FramedItemView: View { } } +@ViewBuilder func toggleSecrets(_ ft: [FormattedText]?, _ showSecrets: Binding, _ v: V) -> some View { + if let ft = ft, ft.contains(where: { $0.isSecret }) { + v.onTapGesture { showSecrets.wrappedValue.toggle() } + } else { + v + } +} + func isRightToLeft(_ s: String) -> Bool { if let lang = CFStringTokenizerCopyBestStringLanguage(s as CFString, CFRange(location: 0, length: min(s.count, 80))) { return NSLocale.characterDirection(forLanguage: lang as String) == .rightToLeft diff --git a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift index cad6401cc0..ccd7ac0a12 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift @@ -31,6 +31,7 @@ struct MsgContentView: View { var sender: String? = nil var meta: CIMeta? = nil var rightToLeft = false + var showSecrets: Bool @State private var typingIdx = 0 @State private var timer: Timer? @@ -62,7 +63,7 @@ struct MsgContentView: View { } private func msgContentView() -> Text { - var v = messageText(text, formattedText, sender) + var v = messageText(text, formattedText, sender, showSecrets: showSecrets) if let mt = meta { if mt.isLive { v = v + typingIndicator(mt.recent) @@ -84,14 +85,14 @@ struct MsgContentView: View { } } -func messageText(_ text: String, _ formattedText: [FormattedText]?, _ sender: String?, icon: String? = nil, preview: Bool = false) -> Text { +func messageText(_ text: String, _ formattedText: [FormattedText]?, _ sender: String?, icon: String? = nil, preview: Bool = false, showSecrets: Bool) -> Text { let s = text var res: Text if let ft = formattedText, ft.count > 0 && ft.count <= 200 { - res = formatText(ft[0], preview) + res = formatText(ft[0], preview, showSecret: showSecrets) var i = 1 while i < ft.count { - res = res + formatText(ft[i], preview) + res = res + formatText(ft[i], preview, showSecret: showSecrets) i = i + 1 } } else { @@ -110,7 +111,7 @@ func messageText(_ text: String, _ formattedText: [FormattedText]?, _ sender: St } } -private func formatText(_ ft: FormattedText, _ preview: Bool) -> Text { +private func formatText(_ ft: FormattedText, _ preview: Bool, showSecret: Bool) -> Text { let t = ft.text if let f = ft.format { switch (f) { @@ -118,7 +119,13 @@ private func formatText(_ ft: FormattedText, _ preview: Bool) -> Text { case .italic: return Text(t).italic() case .strikeThrough: return Text(t).strikethrough() case .snippet: return Text(t).font(.body.monospaced()) - case .secret: return Text(t).foregroundColor(.clear).underline(color: .primary) + case .secret: return + showSecret + ? Text(t) + : Text(AttributedString(t, attributes: AttributeContainer([ + .foregroundColor: UIColor.clear as Any, + .backgroundColor: UIColor.secondarySystemFill as Any + ]))) case let .colored(color): return Text(t).foregroundColor(color.uiColor) case .uri: return linkText(t, t, preview, prefix: "") case let .simplexLink(linkType, simplexUri, smpHosts): @@ -156,7 +163,8 @@ struct MsgContentView_Previews: PreviewProvider { text: chatItem.text, formattedText: chatItem.formattedText, sender: chatItem.memberDisplayName, - meta: chatItem.meta + meta: chatItem.meta, + showSecrets: false ) .environmentObject(Chat.sampleData) } diff --git a/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift b/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift index 83c4cdcda6..69cfcd2caf 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift @@ -168,7 +168,6 @@ struct ChatItemInfoView: View { @ViewBuilder private func itemVersionView(_ itemVersion: ChatItemVersion, _ maxWidth: CGFloat, current: Bool) -> some View { VStack(alignment: .leading, spacing: 4) { textBubble(itemVersion.msgContent.text, itemVersion.formattedText, nil) - .allowsHitTesting(false) .padding(.horizontal, 12) .padding(.vertical, 6) .background(chatItemFrameColor(ci, colorScheme)) @@ -198,7 +197,7 @@ struct ChatItemInfoView: View { @ViewBuilder private func textBubble(_ text: String, _ formattedText: [FormattedText]?, _ sender: String? = nil) -> some View { if text != "" { - messageText(text, formattedText, sender) + TextBubble(text: text, formattedText: formattedText, sender: sender) } else { Text("no text") .italic() @@ -206,6 +205,17 @@ struct ChatItemInfoView: View { } } + private struct TextBubble: View { + var text: String + var formattedText: [FormattedText]? + var sender: String? = nil + @State private var showSecrets = false + + var body: some View { + toggleSecrets(formattedText, $showSecrets, messageText(text, formattedText, sender, showSecrets: showSecrets)) + } + } + @ViewBuilder private func quoteTab(_ qi: CIQuote) -> some View { GeometryReader { g in let maxWidth = (g.size.width - 32) * 0.84 @@ -227,7 +237,6 @@ struct ChatItemInfoView: View { @ViewBuilder private func quotedMsgView(_ qi: CIQuote, _ maxWidth: CGFloat) -> some View { VStack(alignment: .leading, spacing: 4) { textBubble(qi.text, qi.formattedText, qi.getSender(nil)) - .allowsHitTesting(false) .padding(.horizontal, 12) .padding(.vertical, 6) .background(quotedMsgFrameColor(qi, colorScheme)) diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift index 868ae3274a..3eb128cded 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift @@ -51,7 +51,8 @@ struct ContextItemView: View { MsgContentView( chat: chat, text: contextItem.text, - formattedText: contextItem.formattedText + formattedText: contextItem.formattedText, + showSecrets: false ) .multilineTextAlignment(isRightToLeft(contextItem.text) ? .trailing : .leading) .lineLimit(lines) diff --git a/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift b/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift index 0e47d9dddf..e5ff644a91c 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift @@ -53,8 +53,7 @@ struct GroupWelcomeView: View { } private func textPreview() -> some View { - messageText(welcomeText, parseSimpleXMarkdown(welcomeText), nil) - .allowsHitTesting(false) + messageText(welcomeText, parseSimpleXMarkdown(welcomeText), nil, showSecrets: false) .frame(minHeight: 140, alignment: .topLeading) .frame(maxWidth: .infinity, alignment: .leading) } diff --git a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift index 30068114f3..13d91881e6 100644 --- a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift @@ -150,7 +150,7 @@ struct ChatPreviewView: View { let msg = draft.message return image("rectangle.and.pencil.and.ellipsis", color: .accentColor) + attachment() - + messageText(msg, parseSimpleXMarkdown(msg), nil, preview: true) + + messageText(msg, parseSimpleXMarkdown(msg), nil, preview: true, showSecrets: false) func image(_ s: String, color: Color = Color(uiColor: .tertiaryLabel)) -> Text { Text(Image(systemName: s)).foregroundColor(color) + Text(" ") @@ -169,7 +169,7 @@ struct ChatPreviewView: View { func chatItemPreview(_ cItem: ChatItem) -> Text { let itemText = cItem.meta.itemDeleted == nil ? cItem.text : NSLocalizedString("marked deleted", comment: "marked deleted chat item preview text") let itemFormattedText = cItem.meta.itemDeleted == nil ? cItem.formattedText : nil - return messageText(itemText, itemFormattedText, cItem.memberDisplayName, icon: attachment(), preview: true) + return messageText(itemText, itemFormattedText, cItem.memberDisplayName, icon: attachment(), preview: true, showSecrets: false) func attachment() -> String? { switch cItem.content.msgContent { diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 74e5e4a3cb..96281a5013 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -3131,6 +3131,10 @@ extension MsgContent: Encodable { public struct FormattedText: Decodable { public var text: String public var format: Format? + + public var isSecret: Bool { + if case .secret = format { true } else { false } + } } public enum Format: Decodable, Equatable { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt index 63cd25092e..3754315d05 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt @@ -53,6 +53,7 @@ fun ChatItemInfoView(chatModel: ChatModel, ci: ChatItem, ciInfo: ChatItemInfo, d text, if (text.isEmpty()) emptyList() else formattedText, sender = sender, senderBold = true, + toggleSecrets = true, linkMode = SimplexLinkMode.DESCRIPTION, uriHandler = uriHandler, onLinkLongClick = { showMenu.value = true } ) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContextItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContextItemView.kt index 49203c7cfb..b53574cfa4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContextItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContextItemView.kt @@ -34,6 +34,7 @@ fun ContextItemView( fun msgContentView(lines: Int) { MarkdownText( contextItem.text, contextItem.formattedText, + toggleSecrets = false, maxLines = lines, linkMode = SimplexLinkMode.DESCRIPTION, modifier = Modifier.fillMaxWidth(), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt index 577c19648d..c50a80c4e9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt @@ -15,6 +15,7 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.text.AnnotatedString import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource @@ -119,13 +120,15 @@ private fun GroupWelcomeLayout( @Composable private fun TextPreview(text: String, linkMode: SimplexLinkMode, markdown: Boolean = true) { + val uriHandler = LocalUriHandler.current Column { SelectionContainer(Modifier.fillMaxWidth()) { MarkdownText( text, formattedText = if (markdown) remember(text) { parseToMarkdown(text) } else null, + toggleSecrets = false, modifier = Modifier.fillMaxHeight().padding(horizontal = DEFAULT_PADDING), - linkMode = linkMode, + linkMode = linkMode, uriHandler = uriHandler, style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground, lineHeight = 22.sp) ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt index c391200c2d..475e9779e6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt @@ -54,6 +54,7 @@ fun FramedItemView( MarkdownText( qi.text, qi.formattedText, + toggleSecrets = true, maxLines = lines, overflow = TextOverflow.Ellipsis, style = TextStyle(fontSize = 15.sp, color = MaterialTheme.colors.onSurface), @@ -288,7 +289,7 @@ fun CIMarkdownText( Box(Modifier.padding(vertical = 6.dp, horizontal = 12.dp)) { val text = if (ci.meta.isLive) ci.content.msgContent?.text ?: ci.text else ci.text MarkdownText( - text, if (text.isEmpty()) emptyList() else ci.formattedText, + text, if (text.isEmpty()) emptyList() else ci.formattedText, toggleSecrets = true, meta = ci.meta, chatTTL = chatTTL, linkMode = linkMode, uriHandler = uriHandler, senderBold = true, onLinkLongClick = onLinkLongClick ) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt index ff1267d0fa..5169d944c8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt @@ -60,6 +60,7 @@ fun MarkdownText ( sender: String? = null, meta: CIMeta? = null, chatTTL: Int? = null, + toggleSecrets: Boolean, style: TextStyle = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onSurface, lineHeight = 22.sp), maxLines: Int = Int.MAX_VALUE, overflow: TextOverflow = TextOverflow.Clip, @@ -89,6 +90,7 @@ fun MarkdownText ( ) { var timer: Job? by remember { mutableStateOf(null) } var typingIdx by rememberSaveable { mutableStateOf(0) } + val showSecrets = remember { mutableStateMapOf() } fun stopTyping() { timer?.cancel() timer = null @@ -127,15 +129,22 @@ fun MarkdownText ( } Text(annotatedText, style = style, modifier = modifier, maxLines = maxLines, overflow = overflow, inlineContent = inlineContent ?: mapOf()) } else { - var hasLinks = false + var hasAnnotations = false val annotatedText = buildAnnotatedString { appendSender(this, sender, senderBold) - for (ft in formattedText) { + for ((i, ft) in formattedText.withIndex()) { if (ft.format == null) append(ft.text) - else { + else if (toggleSecrets && ft.format is Format.Secret) { + val ftStyle = ft.format.style + hasAnnotations = true + val key = i.toString() + withAnnotation(tag = "SECRET", annotation = key) { + if (showSecrets[key] == true) append(ft.text) else withStyle(ftStyle) { append(ft.text) } + } + } else { val link = ft.link(linkMode) if (link != null) { - hasLinks = true + hasAnnotations = true val ftStyle = ft.format.style withAnnotation(tag = if (ft.format is Format.SimplexLink) "SIMPLEX_URL" else "URL", annotation = link) { withStyle(ftStyle) { append(ft.viewText(linkMode)) } @@ -153,7 +162,7 @@ fun MarkdownText ( withStyle(reserveTimestampStyle) { append("\n" + metaText) } else */if (meta != null) withStyle(reserveTimestampStyle) { append(reserve) } } - if (hasLinks && uriHandler != null) { + if (hasAnnotations && uriHandler != null) { val icon = remember { mutableStateOf(PointerIcon.Default) } ClickableText(annotatedText, style = style, modifier = modifier.pointerHoverIcon(icon.value), maxLines = maxLines, overflow = overflow, onLongClick = { offset -> @@ -177,12 +186,20 @@ fun MarkdownText ( .firstOrNull()?.let { annotation -> uriHandler.openVerifiedSimplexUri(annotation.item) } + annotatedText.getStringAnnotations(tag = "SECRET", start = offset, end = offset) + .firstOrNull()?.let { annotation -> + val key = annotation.item + showSecrets[key] = !(showSecrets[key] ?: false) + } }, onHover = { offset -> icon.value = annotatedText.getStringAnnotations(tag = "URL", start = offset, end = offset) .firstOrNull()?.let { PointerIcon.Hand } ?: annotatedText.getStringAnnotations(tag = "SIMPLEX_URL", start = offset, end = offset) + .firstOrNull()?.let { + PointerIcon.Hand + } ?: annotatedText.getStringAnnotations(tag = "SECRET", start = offset, end = offset) .firstOrNull()?.let { PointerIcon.Hand } ?: PointerIcon.Default diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt index af1f49a088..d59dac37bf 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt @@ -173,6 +173,7 @@ fun ChatPreviewView( cInfo is ChatInfo.Group && !ci.chatDir.sent -> ci.memberDisplayName else -> null }, + toggleSecrets = false, linkMode = linkMode, senderBold = true, maxLines = 2, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt index 6c76acc3e8..14b36d0718 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt @@ -78,17 +78,6 @@ fun ReadableText(text: String, textAlign: TextAlign = TextAlign.Start, padding: Text(text, modifier = Modifier.padding(padding), textAlign = textAlign, lineHeight = 22.sp) } -@Composable -fun ReadableMarkdownText(text: String, textAlign: TextAlign = TextAlign.Start, padding: PaddingValues = PaddingValues(bottom = 12.dp)) { - MarkdownText( - text, - formattedText = remember(text) { parseToMarkdown(text) }, - modifier = Modifier.padding(padding), - style = TextStyle(textAlign = textAlign, lineHeight = 22.sp, fontSize = 16.sp), - linkMode = ChatController.appPrefs.simplexLinkMode.get(), - ) -} - @Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, From 5399212e482a21f4b3fd77780eb6007613b7efb3 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat, 30 Dec 2023 18:59:00 +0000 Subject: [PATCH 14/16] core: 5.5.0.0 --- package.yaml | 2 +- simplex-chat.cabal | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.yaml b/package.yaml index 4a7a2550fd..889df9db71 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 5.4.2.1 +version: 5.5.0.0 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 114c22f360..5090fc5adc 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 5.4.2.1 +version: 5.5.0.0 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat From 05065e919b9603322312817210648bc3e67d18da Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat, 30 Dec 2023 21:09:01 +0000 Subject: [PATCH 15/16] 5.5-beta.0: ios 187, android 168, desktop 21 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 74 ++++++++++------------ apps/multiplatform/gradle.properties | 8 +-- 2 files changed, 36 insertions(+), 46 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index a6b5eb49e9..2167d4beee 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -42,11 +42,11 @@ 5C3F1D562842B68D00EC8A82 /* IntegrityErrorItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3F1D552842B68D00EC8A82 /* IntegrityErrorItemView.swift */; }; 5C3F1D58284363C400EC8A82 /* PrivacySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3F1D57284363C400EC8A82 /* PrivacySettings.swift */; }; 5C4B3B0A285FB130003915F2 /* DatabaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B3B09285FB130003915F2 /* DatabaseView.swift */; }; - 5C4E80DA2B3CCD090080FAE2 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80D52B3CCD090080FAE2 /* libgmp.a */; }; - 5C4E80DB2B3CCD090080FAE2 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80D62B3CCD090080FAE2 /* libffi.a */; }; - 5C4E80DC2B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80D72B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a */; }; - 5C4E80DD2B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80D82B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a */; }; - 5C4E80DE2B3CCD090080FAE2 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80D92B3CCD090080FAE2 /* libgmpxx.a */; }; + 5C4E80E42B40A96C0080FAE2 /* libHSsimplex-chat-5.5.0.0-FwZXD1cMpkc1VLQMq43OyQ.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80DF2B40A96C0080FAE2 /* libHSsimplex-chat-5.5.0.0-FwZXD1cMpkc1VLQMq43OyQ.a */; }; + 5C4E80E52B40A96C0080FAE2 /* libHSsimplex-chat-5.5.0.0-FwZXD1cMpkc1VLQMq43OyQ-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80E02B40A96C0080FAE2 /* libHSsimplex-chat-5.5.0.0-FwZXD1cMpkc1VLQMq43OyQ-ghc9.6.3.a */; }; + 5C4E80E62B40A96C0080FAE2 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80E12B40A96C0080FAE2 /* libgmp.a */; }; + 5C4E80E72B40A96C0080FAE2 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80E22B40A96C0080FAE2 /* libgmpxx.a */; }; + 5C4E80E82B40A96C0080FAE2 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80E32B40A96C0080FAE2 /* libffi.a */; }; 5C5346A827B59A6A004DF848 /* ChatHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5346A727B59A6A004DF848 /* ChatHelp.swift */; }; 5C55A91F283AD0E400C4E99E /* CallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C55A91E283AD0E400C4E99E /* CallManager.swift */; }; 5C55A921283CCCB700C4E99E /* IncomingCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C55A920283CCCB700C4E99E /* IncomingCallView.swift */; }; @@ -173,11 +173,6 @@ 646BB38E283FDB6D001CE359 /* LocalAuthenticationUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 646BB38D283FDB6D001CE359 /* LocalAuthenticationUtils.swift */; }; 647F090E288EA27B00644C40 /* GroupMemberInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647F090D288EA27B00644C40 /* GroupMemberInfoView.swift */; }; 648010AB281ADD15009009B9 /* CIFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648010AA281ADD15009009B9 /* CIFileView.swift */; }; - 64863B9B2B3C536500714A11 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64863B962B3C536500714A11 /* libgmpxx.a */; }; - 64863B9C2B3C536500714A11 /* libHSsimplex-chat-5.4.2.0-1dXNnkvLJVS8FSAgswHDGD-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64863B972B3C536500714A11 /* libHSsimplex-chat-5.4.2.0-1dXNnkvLJVS8FSAgswHDGD-ghc9.6.3.a */; }; - 64863B9D2B3C536500714A11 /* libHSsimplex-chat-5.4.2.0-1dXNnkvLJVS8FSAgswHDGD.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64863B982B3C536500714A11 /* libHSsimplex-chat-5.4.2.0-1dXNnkvLJVS8FSAgswHDGD.a */; }; - 64863B9E2B3C536500714A11 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64863B992B3C536500714A11 /* libgmp.a */; }; - 64863B9F2B3C536500714A11 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64863B9A2B3C536500714A11 /* libffi.a */; }; 649BCDA0280460FD00C3A862 /* ComposeImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */; }; 649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCDA12805D6EF00C3A862 /* CIImageView.swift */; }; 64AA1C6927EE10C800AC7277 /* ContextItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6827EE10C800AC7277 /* ContextItemView.swift */; }; @@ -294,11 +289,11 @@ 5C3F1D57284363C400EC8A82 /* PrivacySettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacySettings.swift; sourceTree = ""; }; 5C422A7C27A9A6FA0097A1E1 /* SimpleX (iOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "SimpleX (iOS).entitlements"; sourceTree = ""; }; 5C4B3B09285FB130003915F2 /* DatabaseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseView.swift; sourceTree = ""; }; - 5C4E80D52B3CCD090080FAE2 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - 5C4E80D62B3CCD090080FAE2 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 5C4E80D72B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a"; sourceTree = ""; }; - 5C4E80D82B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a"; sourceTree = ""; }; - 5C4E80D92B3CCD090080FAE2 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + 5C4E80DF2B40A96C0080FAE2 /* libHSsimplex-chat-5.5.0.0-FwZXD1cMpkc1VLQMq43OyQ.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.5.0.0-FwZXD1cMpkc1VLQMq43OyQ.a"; sourceTree = ""; }; + 5C4E80E02B40A96C0080FAE2 /* libHSsimplex-chat-5.5.0.0-FwZXD1cMpkc1VLQMq43OyQ-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.5.0.0-FwZXD1cMpkc1VLQMq43OyQ-ghc9.6.3.a"; sourceTree = ""; }; + 5C4E80E12B40A96C0080FAE2 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + 5C4E80E22B40A96C0080FAE2 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + 5C4E80E32B40A96C0080FAE2 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; 5C5346A727B59A6A004DF848 /* ChatHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatHelp.swift; sourceTree = ""; }; 5C55A91E283AD0E400C4E99E /* CallManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallManager.swift; sourceTree = ""; }; 5C55A920283CCCB700C4E99E /* IncomingCallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncomingCallView.swift; sourceTree = ""; }; @@ -461,11 +456,6 @@ 646BB38D283FDB6D001CE359 /* LocalAuthenticationUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAuthenticationUtils.swift; sourceTree = ""; }; 647F090D288EA27B00644C40 /* GroupMemberInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMemberInfoView.swift; sourceTree = ""; }; 648010AA281ADD15009009B9 /* CIFileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIFileView.swift; sourceTree = ""; }; - 64863B962B3C536500714A11 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - 64863B972B3C536500714A11 /* libHSsimplex-chat-5.4.2.0-1dXNnkvLJVS8FSAgswHDGD-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.2.0-1dXNnkvLJVS8FSAgswHDGD-ghc9.6.3.a"; sourceTree = ""; }; - 64863B982B3C536500714A11 /* libHSsimplex-chat-5.4.2.0-1dXNnkvLJVS8FSAgswHDGD.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.2.0-1dXNnkvLJVS8FSAgswHDGD.a"; sourceTree = ""; }; - 64863B992B3C536500714A11 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - 64863B9A2B3C536500714A11 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; 6493D667280ED77F007A76FB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeImageView.swift; sourceTree = ""; }; 649BCDA12805D6EF00C3A862 /* CIImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIImageView.swift; sourceTree = ""; }; @@ -521,13 +511,13 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5C4E80DD2B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a in Frameworks */, + 5C4E80E72B40A96C0080FAE2 /* libgmpxx.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - 5C4E80DA2B3CCD090080FAE2 /* libgmp.a in Frameworks */, - 5C4E80DC2B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a in Frameworks */, + 5C4E80E62B40A96C0080FAE2 /* libgmp.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, - 5C4E80DB2B3CCD090080FAE2 /* libffi.a in Frameworks */, - 5C4E80DE2B3CCD090080FAE2 /* libgmpxx.a in Frameworks */, + 5C4E80E82B40A96C0080FAE2 /* libffi.a in Frameworks */, + 5C4E80E52B40A96C0080FAE2 /* libHSsimplex-chat-5.5.0.0-FwZXD1cMpkc1VLQMq43OyQ-ghc9.6.3.a in Frameworks */, + 5C4E80E42B40A96C0080FAE2 /* libHSsimplex-chat-5.5.0.0-FwZXD1cMpkc1VLQMq43OyQ.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -589,11 +579,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - 5C4E80D62B3CCD090080FAE2 /* libffi.a */, - 5C4E80D52B3CCD090080FAE2 /* libgmp.a */, - 5C4E80D92B3CCD090080FAE2 /* libgmpxx.a */, - 5C4E80D82B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a */, - 5C4E80D72B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a */, + 5C4E80E32B40A96C0080FAE2 /* libffi.a */, + 5C4E80E12B40A96C0080FAE2 /* libgmp.a */, + 5C4E80E22B40A96C0080FAE2 /* libgmpxx.a */, + 5C4E80E02B40A96C0080FAE2 /* libHSsimplex-chat-5.5.0.0-FwZXD1cMpkc1VLQMq43OyQ-ghc9.6.3.a */, + 5C4E80DF2B40A96C0080FAE2 /* libHSsimplex-chat-5.5.0.0-FwZXD1cMpkc1VLQMq43OyQ.a */, ); path = Libraries; sourceTree = ""; @@ -1512,7 +1502,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 186; + CURRENT_PROJECT_VERSION = 187; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; @@ -1534,7 +1524,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 5.4.2; + MARKETING_VERSION = 5.5; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1555,7 +1545,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 186; + CURRENT_PROJECT_VERSION = 187; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; @@ -1577,7 +1567,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 5.4.2; + MARKETING_VERSION = 5.5; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1636,7 +1626,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 186; + CURRENT_PROJECT_VERSION = 187; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GENERATE_INFOPLIST_FILE = YES; @@ -1649,7 +1639,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 5.4.2; + MARKETING_VERSION = 5.5; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1668,7 +1658,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 186; + CURRENT_PROJECT_VERSION = 187; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GENERATE_INFOPLIST_FILE = YES; @@ -1681,7 +1671,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 5.4.2; + MARKETING_VERSION = 5.5; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1700,7 +1690,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 186; + CURRENT_PROJECT_VERSION = 187; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1724,7 +1714,7 @@ "$(inherited)", "$(PROJECT_DIR)/Libraries/sim", ); - MARKETING_VERSION = 5.4.2; + MARKETING_VERSION = 5.5; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -1746,7 +1736,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 186; + CURRENT_PROJECT_VERSION = 187; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1770,7 +1760,7 @@ "$(inherited)", "$(PROJECT_DIR)/Libraries/sim", ); - MARKETING_VERSION = 5.4.2; + MARKETING_VERSION = 5.5; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 9a7b8ab811..d94fe6f915 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -25,11 +25,11 @@ android.nonTransitiveRClass=true android.enableJetifier=true kotlin.mpp.androidSourceSetLayoutVersion=2 -android.version_name=5.4.2 -android.version_code=166 +android.version_name=5.5-beta.0 +android.version_code=168 -desktop.version_name=5.4.2 -desktop.version_code=20 +desktop.version_name=5.5-beta.0 +desktop.version_code=21 kotlin.version=1.8.20 gradle.plugin.version=7.4.2 From c9b1d54f1308841ced44ae4e19c3baa5e60fac83 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat, 30 Dec 2023 22:04:01 +0000 Subject: [PATCH 16/16] docs: update downloads --- docs/DOWNLOADS.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/DOWNLOADS.md b/docs/DOWNLOADS.md index 5362e4f2c3..bd88f7a175 100644 --- a/docs/DOWNLOADS.md +++ b/docs/DOWNLOADS.md @@ -7,7 +7,7 @@ revision: 25.11.2023 | Updated 25.11.2023 | Languages: EN | # Download SimpleX apps -The latest stable version is v5.4.1. +The latest stable version is v5.4.2. You can get the latest beta releases from [GitHub](https://github.com/simplex-chat/simplex-chat/releases). @@ -21,24 +21,24 @@ You can get the latest beta releases from [GitHub](https://github.com/simplex-ch Using the same profile as on mobile device is not yet supported – you need to create a separate profile to use desktop apps. -**Linux**: [AppImage](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-desktop-x86_64.AppImage) (most Linux distros), [Ubuntu 20.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-desktop-ubuntu-20_04-x86_64.deb) (and Debian-based distros), [Ubuntu 22.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-desktop-ubuntu-22_04-x86_64.deb). +**Linux**: [AppImage](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.2/simplex-desktop-x86_64.AppImage) (most Linux distros), [Ubuntu 20.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.2/simplex-desktop-ubuntu-20_04-x86_64.deb) (and Debian-based distros), [Ubuntu 22.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.2/simplex-desktop-ubuntu-22_04-x86_64.deb). -**Mac**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-desktop-macos-x86_64.dmg) (Intel), [aarch64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-desktop-macos-aarch64.dmg) (Apple Silicon). +**Mac**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.2/simplex-desktop-macos-x86_64.dmg) (Intel), [aarch64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.2/simplex-desktop-macos-aarch64.dmg) (Apple Silicon). -**Windows**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-desktop-windows-x86_64.msi). +**Windows**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.2/simplex-desktop-windows-x86_64.msi). ## Mobile apps **iOS**: [App store](https://apps.apple.com/us/app/simplex-chat/id1605771084), [TestFlight](https://testflight.apple.com/join/DWuT2LQu). -**Android**: [Play store](https://play.google.com/store/apps/details?id=chat.simplex.app), [F-Droid](https://simplex.chat/fdroid/), [APK aarch64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex.apk), [APK armv7](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-armv7a.apk). +**Android**: [Play store](https://play.google.com/store/apps/details?id=chat.simplex.app), [F-Droid](https://simplex.chat/fdroid/), [APK aarch64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.2/simplex.apk), [APK armv7](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.2/simplex-armv7a.apk). ## Terminal (console) app See [Using terminal app](/docs/CLI.md). -**Linux**: [Ubuntu 20.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-chat-ubuntu-20_04-x86-64), [Ubuntu 22.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-chat-ubuntu-22_04-x86-64). +**Linux**: [Ubuntu 20.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.2/simplex-chat-ubuntu-20_04-x86-64), [Ubuntu 22.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.2/simplex-chat-ubuntu-22_04-x86-64). -**Mac** [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-chat-macos-x86-64), aarch64 - [compile from source](./CLI.md#). +**Mac** [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.2/simplex-chat-macos-x86-64), aarch64 - [compile from source](./CLI.md#). -**Windows**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-chat-windows-x86-64). +**Windows**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.2/simplex-chat-windows-x86-64).