diff --git a/README.md b/README.md
index 13a43eaa8..3d661a13c 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,29 @@
-| Updated 07.02.2023 | Languages: EN, [FR](/docs/lang/fr/README.md) |
-
-
-
-# SimpleX - the first messaging platform that has no user identifiers of any kind - 100% private by design!
-
[](https://github.com/simplex-chat/simplex-chat/actions/workflows/build.yml)
[](https://github.com/simplex-chat/simplex-chat/releases)
[](https://github.com/simplex-chat/simplex-chat/releases)
[](https://www.reddit.com/r/SimpleXChat)
[](https://mastodon.social/@simplex)
+| 19/03/2023 | EN, [FR](/docs/lang/fr/README.md), [CZ](/docs/lang/cs/README.md) |
+
+
+
+# SimpleX - the first messaging platform that has no user identifiers of any kind - 100% private by design!
+
+[
](http://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html) [
](https://www.privacyguides.org/en/real-time-communication/#simplex-chat) [
](https://www.kuketz-blog.de/simplex-eindruecke-vom-messenger-ohne-identifier/)
+
+## Welcome to SimpleX Chat!
+
+1. 📲 [Install the app](#install-the-app).
+2. ↔️ [Connect to the team](#connect-to-the-team-via-the-app) and [join user groups](#join-user-groups).
+3. 🤝 [Make a private connection](#make-a-private-connection) with a friend.
+4. 🔤 [Help translating SimpleX Chat](#help-translating-simplex-chat).
+5. ⚡️ [Contribute](#contribute) and [help us with donations](#help-us-with-donations).
+
+[Learn more about SimpleX Chat](#contents).
+
+## Install the app
+
[
](https://apps.apple.com/us/app/simplex-chat/id1605771084)
[](https://play.google.com/store/apps/details?id=chat.simplex.app)
@@ -26,7 +40,85 @@
- 🚀 [TestFlight preview for iOS](https://testflight.apple.com/join/DWuT2LQu) with the new features 1-2 weeks earlier - **limited to 10,000 users**!
- 🖥 Available as a terminal (console) [app / CLI](#zap-quick-installation-of-a-terminal-app) on Linux, MacOS, Windows.
-**NEW**: Security audit by [Trail of Bits](https://www.trailofbits.com/about), the [new website](https://simplex.chat) and v4.2 released! [See the announcement](./blog/20221108-simplex-chat-v4.2-security-audit-new-website.md)
+## Connect to the team via the app
+
+- to ask any questions
+- to suggest any improvements
+- to share anything relevant
+
+## Join user groups
+
+You can join an English-speaking users group if you want to ask any questions: [#SimpleX-Group-2](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FQP8zaGjjmlXV-ix_Er4JgJ0lNPYGS1KX%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEApAgBkRZ3x12ayZ7sHrjHQWNMvqzZpWUgM_fFCUdLXwo%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22xWpPXEZZsQp_F7vwAcAYDw%3D%3D%22%7D)
+
+There are groups in other languages, that we have the apps interface translated into. These groups are for testing, and asking questions to other SimpleX Chat users:
+
+[\#SimpleX-DE](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FkIEl7OQzcp-J6aDmjdlQbRJwqkcZE7XR%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAR16PCu02MobRmKAsjzhDWMZcWP9hS8l5AUZi-Gs8z18%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22puYPMCQt11yPUvgmI5jCiw%3D%3D%22%7D) (German-speaking), [\#SimpleX-FR](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FvIHQDxTor53nwnWWTy5cHNwQQAdWN5Hw%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAPdgK1eBnETmgiqEQufbUkydKBJafoRx4iRrtrC2NAGc%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%221FyUryBPza-1ZFFE80Ekbg%3D%3D%22%7D) (French-speaking), [\#SimpleX-RU](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FXZyt3hJmWsycpN7Dqve_wbrAqb6myk1R%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAMFVIoytozTEa_QXOgoZFq_oe0IwZBYKvW50trSFXzXo%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22xz05ngjA3pNIxLZ32a8Vxg%3D%3D%22%7D) (Russian-speaking), [\#SimpleX-IT](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2F0weR-ZgDUl7ruOtI_8TZwEsnJP6UiImA%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAq4PSThO9Fvb5ydF48wB0yNbpzCbuQJCW3vZ9BGUfcxk%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22e-iceLA0SctC62eARgYDWg%3D%3D%22%7D) (Italian-speaking).
+
+You can join either by opening these links in the app or by opening them in a desktop browser and scanning the QR code.
+
+## Make a private connection
+
+You need to share a link with your friend or scan a QR code from their phone, in person or during a video call, to make a connection and start messaging.
+
+The channel through which you share the link does not have to be secure - it is enough that you can confirm who sent you the message and that your SimpleX connection is established.
+
+
+
+After you connect, you can [verify connection security code](./blog/20230103-simplex-chat-v4.4-disappearing-messages.md#connection-security-verification).
+
+## Help translating SimpleX Chat
+
+Thanks to our users and [Weblate](https://hosted.weblate.org/engage/simplex-chat/), SimpleX Chat apps, website and documents are translated to many other languages.
+
+Join our translators to help SimpleX grow!
+
+|locale|language |contributor|[Android](https://play.google.com/store/apps/details?id=chat.simplex.app) and [iOS](https://apps.apple.com/us/app/simplex-chat/id1605771084)|[website](https://simplex.chat)|Github docs|
+|:----:|:-------:|:---------:|:---------:|:---------:|:---------:|
+|🇬🇧 en|English | |✓|✓|✓|✓|
+|🇨🇿 cs|Čeština |[zen0bit](https://github.com/zen0bit)|[](https://hosted.weblate.org/projects/simplex-chat/android/cs/)
[](https://hosted.weblate.org/projects/simplex-chat/ios/cs/)|[](https://hosted.weblate.org/projects/simplex-chat/website/cs/)|[✓](https://github.com/simplex-chat/simplex-chat/tree/master/docs/lang/cs)|
+|🇩🇪 de|Deutsch |[mlanp](https://github.com/mlanp)|[](https://hosted.weblate.org/projects/simplex-chat/android/de/)
[](https://hosted.weblate.org/projects/simplex-chat/ios/de/)|[](https://hosted.weblate.org/projects/simplex-chat/website/de/)||
+|🇪🇸 es|Español ||[](https://hosted.weblate.org/projects/simplex-chat/android/es/)
[](https://hosted.weblate.org/projects/simplex-chat/ios/es/)|||
+|🇫🇷 fr|Français |[ishi_sama](https://github.com/ishi_sama)|[](https://hosted.weblate.org/projects/simplex-chat/android/fr/)
[](https://hosted.weblate.org/projects/simplex-chat/ios/fr/)|[](https://hosted.weblate.org/projects/simplex-chat/website/fr/)|[✓](https://github.com/simplex-chat/simplex-chat/tree/master/docs/lang/fr)|
+|🇮🇹 it|Italiano |[unbranched](https://github.com/unbranched)|[](https://hosted.weblate.org/projects/simplex-chat/android/it/)
[](https://hosted.weblate.org/projects/simplex-chat/ios/it/)|[](https://hosted.weblate.org/projects/simplex-chat/website/it/)||
+|🇳🇱 nl|Nederlands|[mika-nl](https://github.com/mika-nl)|[](https://hosted.weblate.org/projects/simplex-chat/android/nl/)
[](https://hosted.weblate.org/projects/simplex-chat/ios/nl/)|[](https://hosted.weblate.org/projects/simplex-chat/website/nl/)||
+|🇷🇺 ru|Русский ||[](https://hosted.weblate.org/projects/simplex-chat/android/ru/)
[](https://hosted.weblate.org/projects/simplex-chat/ios/ru/)|||
+|🇨🇳 zh-CHS|简体中文|[sith-on-mars](https://github.com/sith-on-mars)|[](https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/)
[](https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hans/)|||
+
+Languages in progress: Arabic, Hindi, Japanese, Spanish and [many others](https://hosted.weblate.org/projects/simplex-chat/#languages). We will be adding more languages as some of the already added are completed – please suggest new languages, review the [translation guide](./docs/TRANSLATIONS.md) and get in touch with us!
+
+## Contribute
+
+We would love to have you join the development! You can help us with:
+
+- writing a tutorial or recipes about hosting servers, chat bot automations, etc.
+- contributing to SimpleX Chat knowledge-base.
+- developing features - please connect to us via chat so we can help you get started.
+
+## Help us with donations
+
+Huge thank you to everybody who donated to SimpleX Chat!
+
+We are prioritizing users privacy and security - it would be impossible without your support.
+
+Our pledge to our users is that SimpleX protocols are and will remain open, and in public domain, - so anybody can build the future implementations of the clients and the servers. We are building SimpleX platform based on the same principles as email and web, but much more private and secure.
+
+Your donations help us raise more funds – any amount, even the price of the cup of coffee, would make a big difference for us.
+
+It is possible to donate via:
+
+- [GitHub](https://github.com/sponsors/simplex-chat) - it is commission-free for us.
+- [OpenCollective](https://opencollective.com/simplex-chat) - it charges a commission, and also accepts donations in crypto-currencies.
+- Monero address: 8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt
+- Bitcoin address: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG
+- BCH address: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG
+- Ethereum address: 0x83fd788f7241a2be61780ea9dc72d2151e6843e2
+- Solana address: 43tWFWDczgAcn4Rzwkpqg2mqwnQETSiTwznmCgA2tf1L
+
+Thank you,
+
+Evgeny
+
+SimpleX Chat founder
## Contents
@@ -38,16 +130,11 @@
- [Users own SimpleX network](#users-own-simplex-network)
- [Frequently asked questions](#frequently-asked-questions)
- [News and updates](#news-and-updates)
-- [Make a private connection](#make-a-private-connection)
- [Quick installation of a terminal app](#zap-quick-installation-of-a-terminal-app)
- [SimpleX Platform design](#simplex-platform-design)
- [Privacy: technical details and limitations](#privacy-technical-details-and-limitations)
- [For developers](#for-developers)
- [Roadmap](#roadmap)
-- [Join a user group](#join-a-user-group)
-- [Translate the apps](#translate-the-apps)
-- [Contribute](#contribute)
-- [Help us with donations](#help-us-with-donations)
- [Disclaimers, Security contact, License](#disclaimers)
## Why privacy matters
@@ -100,14 +187,6 @@ Recent updates:
[All updates](./blog)
-## Make a private connection
-
-You need to share a link or scan a QR code (in person or during a video call) to make a connection and start messaging.
-
-The channel through which you share the link does not have to be secure - it is enough that you can confirm who sent you the message and that your SimpleX connection is established.
-
-
-
## :zap: Quick installation of a terminal app
```sh
@@ -225,70 +304,6 @@ If you are considering developing with SimpleX platform please get in touch for
- Message delivery relay for senders (to conceal IP address from the recipients' servers and to reduce the traffic).
- High capacity multi-node SMP relays.
-## Join a user group
-
-You can join an English-speaking group if you want to ask any questions: [#SimpleX-Group-2](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FQP8zaGjjmlXV-ix_Er4JgJ0lNPYGS1KX%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEApAgBkRZ3x12ayZ7sHrjHQWNMvqzZpWUgM_fFCUdLXwo%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22xWpPXEZZsQp_F7vwAcAYDw%3D%3D%22%7D)
-
-There are also several groups in languages other than English, that we have the apps interface translated into. These groups are for testing, and asking questions to other SimpleX Chat users. We do not always answer questions there, so please ask them in one of the English-speaking groups.
-
-- [\#SimpleX-DE](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FkIEl7OQzcp-J6aDmjdlQbRJwqkcZE7XR%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAR16PCu02MobRmKAsjzhDWMZcWP9hS8l5AUZi-Gs8z18%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22puYPMCQt11yPUvgmI5jCiw%3D%3D%22%7D) (German-speaking).
-- [\#SimpleX-FR](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FvIHQDxTor53nwnWWTy5cHNwQQAdWN5Hw%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAPdgK1eBnETmgiqEQufbUkydKBJafoRx4iRrtrC2NAGc%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%221FyUryBPza-1ZFFE80Ekbg%3D%3D%22%7D) (French-speaking).
-- [\#SimpleX-RU](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FXZyt3hJmWsycpN7Dqve_wbrAqb6myk1R%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAMFVIoytozTEa_QXOgoZFq_oe0IwZBYKvW50trSFXzXo%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22xz05ngjA3pNIxLZ32a8Vxg%3D%3D%22%7D) (Russian-speaking).
-- [\#SimpleX-IT](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2F0weR-ZgDUl7ruOtI_8TZwEsnJP6UiImA%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAq4PSThO9Fvb5ydF48wB0yNbpzCbuQJCW3vZ9BGUfcxk%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22e-iceLA0SctC62eARgYDWg%3D%3D%22%7D) (Italian-speaking).
-
-You can join these groups either by opening these links in the app or by opening them in a desktop browser and scanning QR code.
-
-Join via the app to share what's going on and ask any questions!
-
-## Translate the apps
-
-Thanks to our users and [Weblate](https://hosted.weblate.org/engage/simplex-chat/), SimpleX Chat apps are translated to many other languages. Join our translators to help SimpleX grow faster!
-
-Current interface languages:
-
-- English (development language)
-- German: [@mlanp](https://github.com/mlanp)
-- French: [@ishi_sama](https://github.com/ishi-sama)
-- Italian: [@unbranched](https://github.com/unbranched)
-- Russian: project team
-
-Languages in progress: Chinese, Hindi, Czech, Japanese, Dutch and [many others](https://hosted.weblate.org/projects/simplex-chat/#languages). We will be adding more languages as some of the already added are completed – please suggest new languages, review the [translation guide](./docs/TRANSLATIONS.md) and get in touch with us!
-
-## Contribute
-
-We would love to have you join the development! You can contribute to SimpleX Chat with:
-
-- translate website homepage - there is a lot of content we would like to share, it would help to bring the new users.
-- writing a tutorial or recipes about hosting servers, chat bot automations, etc.
-- developing features - please connect to us via chat so we can help you get started.
-
-## Help us with donations
-
-Huge thank you to everybody who donated to SimpleX Chat!
-
-We are prioritizing users privacy and security - it would be impossible without your support.
-
-Our pledge to our users is that SimpleX protocols are and will remain open, and in public domain, - so anybody can build the future implementations of the clients and the servers. We are building SimpleX platform based on the same principles as email and web, but much more private and secure.
-
-Your donations help us raise more funds – any amount, even the price of the cup of coffee, would make a big difference for us.
-
-It is possible to donate via:
-
-- [GitHub](https://github.com/sponsors/simplex-chat) - it is commission-free for us.
-- [OpenCollective](https://opencollective.com/simplex-chat) - it charges a commission, and also accepts donations in crypto-currencies.
-- Monero address: 8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt
-- Bitcoin address: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG
-- BCH address: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG
-- Ethereum address: 0x83fd788f7241a2be61780ea9dc72d2151e6843e2
-- Solana address: 43tWFWDczgAcn4Rzwkpqg2mqwnQETSiTwznmCgA2tf1L
-- please let us know, via GitHub issue or chat, if you want to create a donation in some other cryptocurrency - we will add the address to the list.
-
-Thank you,
-
-Evgeny
-
-SimpleX Chat founder
-
## Disclaimers
[SimpleX protocols and security model](https://github.com/simplex-chat/simplexmq/blob/master/protocol/overview-tjr.md) was reviewed, and had many breaking changes and improvements in v1.0.0.
diff --git a/apps/android/app/build.gradle b/apps/android/app/build.gradle
index b5cdd23ea..1b81bf7d5 100644
--- a/apps/android/app/build.gradle
+++ b/apps/android/app/build.gradle
@@ -5,14 +5,14 @@ plugins {
}
android {
- compileSdk 32
+ compileSdk 33
defaultConfig {
applicationId "chat.simplex.app"
minSdk 29
- targetSdk 32
- versionCode 103
- versionName "4.5.4"
+ targetSdk 33
+ versionCode 104
+ versionName "4.6-beta.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
ndk {
@@ -77,10 +77,10 @@ android {
jniLibs.useLegacyPackaging = compression_level != "0"
}
def isRelease = gradle.getStartParameter().taskNames.find({ it.toLowerCase().contains("release") }) != null
- if (isRelease) {
+// if (isRelease) {
// Comma separated list of languages that will be included in the apk
- android.defaultConfig.resConfigs("en", "ru", "de", "fr", "it", "nl", "cs")
- }
+ android.defaultConfig.resConfigs("en", "cs", "de", "es", "fr", "it", "nl", "ru", "zh-rCN")
+// }
}
dependencies {
diff --git a/apps/android/app/src/main/AndroidManifest.xml b/apps/android/app/src/main/AndroidManifest.xml
index eb5f13055..3476fe605 100644
--- a/apps/android/app/src/main/AndroidManifest.xml
+++ b/apps/android/app/src/main/AndroidManifest.xml
@@ -30,6 +30,7 @@
android:label="${app_name}"
android:extractNativeLibs="${extract_native_libs}"
android:supportsRtl="true"
+ android:localeConfig="@xml/locales_config"
android:theme="@style/Theme.SimpleX">
diff --git a/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt b/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt
index e26fa02eb..c868a9731 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt
@@ -3,9 +3,7 @@ package chat.simplex.app
import android.app.Application
import android.content.Intent
import android.net.Uri
-import android.os.Build
-import android.os.Bundle
-import android.os.Parcelable
+import android.os.*
import android.os.SystemClock.elapsedRealtime
import android.util.Log
import android.view.WindowManager
@@ -68,6 +66,7 @@ class MainActivity: FragmentActivity() {
super.onCreate(savedInstanceState)
// testJson()
val m = vm.chatModel
+ applyAppLocale(m.controller.appPrefs.appLanguage)
// When call ended and orientation changes, it re-process old intent, it's unneeded.
// Only needed to be processed on first creation of activity
if (savedInstanceState == null) {
diff --git a/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt
index 0af27cc12..923f71b97 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt
@@ -38,6 +38,8 @@ class SimplexApp: Application(), LifecycleEventObserver {
var isAppOnForeground: Boolean = false
+ val defaultLocale: Locale = Locale.getDefault()
+
fun initChatController(useKey: String? = null, startChat: Boolean = true) {
val dbKey = useKey ?: DatabaseUtils.useDatabaseKey()
val dbAbsolutePathPrefix = getFilesDirectory(SimplexApp.context)
diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt
index e3d10e1ad..b573a1375 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt
@@ -950,7 +950,7 @@ data class GroupMember (
fun canChangeRoleTo(groupInfo: GroupInfo): List? =
if (!canBeRemoved(groupInfo)) null
else groupInfo.membership.memberRole.let { userRole ->
- GroupMemberRole.values().filter { it <= userRole && it != GroupMemberRole.Observer }
+ GroupMemberRole.values().filter { it <= userRole }
}
val memberIncognito = memberProfile.profileId != memberContactProfileId
diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt
index 5b2872724..35b918776 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt
@@ -133,6 +133,7 @@ class AppPreferences(val context: Context) {
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 appLanguage = mkStrPreference(SHARED_PREFS_APP_LANGUAGE, null)
val storeDBPassphrase = mkBoolPreference(SHARED_PREFS_STORE_DB_PASSPHRASE, true)
val initialRandomDBPassphrase = mkBoolPreference(SHARED_PREFS_INITIAL_RANDOM_DB_PASSPHRASE, false)
@@ -214,6 +215,7 @@ class AppPreferences(val context: Context) {
private const val SHARED_PREFS_EXPERIMENTAL_CALLS = "ExperimentalCalls"
private const val SHARED_PREFS_CHAT_ARCHIVE_NAME = "ChatArchiveName"
private const val SHARED_PREFS_CHAT_ARCHIVE_TIME = "ChatArchiveTime"
+ private const val SHARED_PREFS_APP_LANGUAGE = "AppLanguage"
private const val SHARED_PREFS_CHAT_LAST_START = "ChatLastStart"
private const val SHARED_PREFS_DEVELOPER_TOOLS = "DeveloperTools"
private const val SHARED_PREFS_NETWORK_USE_SOCKS_PROXY = "NetworkUseSocksProxy"
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/call/CallView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/call/CallView.kt
index 1952cd80c..26a3c23c6 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/call/CallView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/call/CallView.kt
@@ -3,11 +3,12 @@ package chat.simplex.app.views.call
import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
-import android.content.Context
+import android.content.*
import android.content.pm.ActivityInfo
-import android.media.AudioDeviceInfo
-import android.media.AudioManager
+import android.media.*
import android.os.Build
+import android.os.PowerManager
+import android.os.PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK
import android.util.Log
import android.view.ViewGroup
import android.webkit.*
@@ -19,6 +20,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.*
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
@@ -37,8 +39,7 @@ import androidx.webkit.WebViewClientCompat
import chat.simplex.app.*
import chat.simplex.app.R
import chat.simplex.app.model.*
-import chat.simplex.app.ui.theme.DEFAULT_BOTTOM_PADDING
-import chat.simplex.app.ui.theme.SimpleXTheme
+import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.ProfileImage
import chat.simplex.app.views.helpers.withApi
import chat.simplex.app.views.usersettings.NotificationsMode
@@ -54,6 +55,7 @@ fun ActiveCallView(chatModel: ChatModel) {
val call = chatModel.activeCall.value
if (call != null) withApi { chatModel.callManager.endCall(call) }
})
+ val audioViaBluetooth = rememberSaveable { mutableStateOf(false) }
val ntfModeService = remember { chatModel.controller.appPrefs.notificationsMode.get() == NotificationsMode.SERVICE.name }
LaunchedEffect(Unit) {
// Start service when call happening since it's not already started.
@@ -61,17 +63,48 @@ fun ActiveCallView(chatModel: ChatModel) {
if (!ntfModeService) SimplexService.start(SimplexApp.context)
}
DisposableEffect(Unit) {
+ val am = SimplexApp.context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
+ var btDeviceCount = 0
+ val audioCallback = object: AudioDeviceCallback() {
+ override fun onAudioDevicesAdded(addedDevices: Array) {
+ Log.d(TAG, "Added audio devices: ${addedDevices.map { it.type }}")
+ super.onAudioDevicesAdded(addedDevices)
+ val addedCount = addedDevices.count { it.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO }
+ btDeviceCount += addedCount
+ audioViaBluetooth.value = btDeviceCount > 0
+ if (addedCount > 0 && chatModel.activeCall.value?.callState == CallState.Connected) {
+ // Setting params in Connected state makes sure that Bluetooth will NOT be broken on Android < 12
+ setCallSound(chatModel.activeCall.value?.soundSpeaker ?: return, audioViaBluetooth)
+ }
+ }
+ override fun onAudioDevicesRemoved(removedDevices: Array) {
+ Log.d(TAG, "Removed audio devices: ${removedDevices.map { it.type }}")
+ super.onAudioDevicesRemoved(removedDevices)
+ val removedCount = removedDevices.count { it.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO }
+ btDeviceCount -= removedCount
+ audioViaBluetooth.value = btDeviceCount > 0
+ if (btDeviceCount == 0 && chatModel.activeCall.value?.callState == CallState.Connected) {
+ // Setting params in Connected state makes sure that Bluetooth will NOT be broken on Android < 12
+ setCallSound(chatModel.activeCall.value?.soundSpeaker ?: return, audioViaBluetooth)
+ }
+ }
+ }
+ am.registerAudioDeviceCallback(audioCallback, null)
+ val pm = (SimplexApp.context.getSystemService(Context.POWER_SERVICE) as PowerManager)
+ val proximityLock = if (pm.isWakeLockLevelSupported(PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
+ pm.newWakeLock(PROXIMITY_SCREEN_OFF_WAKE_LOCK, "proximityLock")
+ } else {
+ null
+ }
+ proximityLock?.acquire()
onDispose {
// Stop it when call ended
if (!ntfModeService) SimplexService.safeStopService(SimplexApp.context)
- // Clear selected communication device to default value after we changed it in call
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- val am = SimplexApp.context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
- am.clearCommunicationDevice()
- }
+ dropAudioManagerOverrides()
+ am.unregisterAudioDeviceCallback(audioCallback)
+ proximityLock?.release()
}
}
- val cxt = LocalContext.current
val scope = rememberCoroutineScope()
Box(Modifier.fillMaxSize()) {
WebRTCView(chatModel.callCommand) { apiMsg ->
@@ -101,6 +134,7 @@ fun ActiveCallView(chatModel: ChatModel) {
val callStatus = json.decodeFromString("\"${r.state.connectionState}\"")
if (callStatus == WebRTCCallStatus.Connected) {
chatModel.activeCall.value = call.copy(callState = CallState.Connected)
+ setCallSound(call.soundSpeaker, audioViaBluetooth)
}
withApi { chatModel.controller.apiCallStatus(call.contact, callStatus) }
} catch (e: Error) {
@@ -109,8 +143,7 @@ fun ActiveCallView(chatModel: ChatModel) {
is WCallResponse.Connected -> {
chatModel.activeCall.value = call.copy(callState = CallState.Connected, connectionInfo = r.connectionInfo)
scope.launch {
- delay(2000L)
- setCallSound(cxt, call)
+ setCallSound(call.soundSpeaker, audioViaBluetooth)
}
}
is WCallResponse.Ended -> {
@@ -144,15 +177,18 @@ fun ActiveCallView(chatModel: ChatModel) {
}
}
val call = chatModel.activeCall.value
- if (call != null) ActiveCallOverlay(call, chatModel)
+ if (call != null) ActiveCallOverlay(call, chatModel, audioViaBluetooth)
}
val context = LocalContext.current
DisposableEffect(Unit) {
val activity = context as? Activity ?: return@DisposableEffect onDispose {}
+ val prevVolumeControlStream = activity.volumeControlStream
+ activity.volumeControlStream = AudioManager.STREAM_VOICE_CALL
// Lock orientation to portrait in order to have good experience with calls
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
onDispose {
+ activity.volumeControlStream = prevVolumeControlStream
// Unlock orientation
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
}
@@ -160,10 +196,10 @@ fun ActiveCallView(chatModel: ChatModel) {
}
@Composable
-private fun ActiveCallOverlay(call: Call, chatModel: ChatModel) {
- var cxt = LocalContext.current
+private fun ActiveCallOverlay(call: Call, chatModel: ChatModel, audioViaBluetooth: MutableState) {
ActiveCallOverlayLayout(
call = call,
+ speakerCanBeEnabled = !audioViaBluetooth.value,
dismiss = { withApi { chatModel.callManager.endCall(call) } },
toggleAudio = { chatModel.callCommand.value = WCallCommand.Media(CallMediaType.Audio, enable = !call.audioEnabled) },
toggleVideo = { chatModel.callCommand.value = WCallCommand.Media(CallMediaType.Video, enable = !call.videoEnabled) },
@@ -172,38 +208,55 @@ private fun ActiveCallOverlay(call: Call, chatModel: ChatModel) {
if (call != null) {
call = call.copy(soundSpeaker = !call.soundSpeaker)
chatModel.activeCall.value = call
- setCallSound(cxt, call)
+ setCallSound(call.soundSpeaker, audioViaBluetooth)
}
},
flipCamera = { chatModel.callCommand.value = WCallCommand.Camera(call.localCamera.flipped) }
)
}
-private fun setCallSound(cxt: Context, call: Call) {
- Log.d(TAG, "setCallSound: set audio mode")
- val am = cxt.getSystemService(Context.AUDIO_SERVICE) as AudioManager
- if (call.soundSpeaker) {
- am.mode = AudioManager.MODE_NORMAL
- am.isSpeakerphoneOn = true
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- am.availableCommunicationDevices.firstOrNull { it.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER }?.let {
+private fun setCallSound(speaker: Boolean, audioViaBluetooth: MutableState) {
+ val am = SimplexApp.context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
+ Log.d(TAG, "setCallSound: set audio mode, speaker enabled: $speaker")
+ am.mode = AudioManager.MODE_IN_COMMUNICATION
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ val btDevice = am.availableCommunicationDevices.lastOrNull { it.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO }
+ val preferredSecondaryDevice = if (speaker) AudioDeviceInfo.TYPE_BUILTIN_SPEAKER else AudioDeviceInfo.TYPE_BUILTIN_EARPIECE
+ if (btDevice != null) {
+ am.setCommunicationDevice(btDevice)
+ } else if (am.communicationDevice?.type != preferredSecondaryDevice) {
+ am.availableCommunicationDevices.firstOrNull { it.type == preferredSecondaryDevice }?.let {
am.setCommunicationDevice(it)
}
}
} else {
- am.mode = AudioManager.MODE_IN_CALL
- am.isSpeakerphoneOn = false
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- am.availableCommunicationDevices.firstOrNull { it.type == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE }?.let {
- am.setCommunicationDevice(it)
- }
+ if (audioViaBluetooth.value) {
+ am.isSpeakerphoneOn = false
+ am.startBluetoothSco()
+ } else {
+ am.stopBluetoothSco()
+ am.isSpeakerphoneOn = speaker
}
+ am.isBluetoothScoOn = am.isBluetoothScoAvailableOffCall && audioViaBluetooth.value
+ }
+}
+
+private fun dropAudioManagerOverrides() {
+ val am = SimplexApp.context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
+ am.mode = AudioManager.MODE_NORMAL
+ // Clear selected communication device to default value after we changed it in call
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ am.clearCommunicationDevice()
+ } else {
+ am.isSpeakerphoneOn = false
+ am.stopBluetoothSco()
}
}
@Composable
private fun ActiveCallOverlayLayout(
call: Call,
+ speakerCanBeEnabled: Boolean,
dismiss: () -> Unit,
toggleAudio: () -> Unit,
toggleVideo: () -> Unit,
@@ -252,7 +305,7 @@ private fun ActiveCallOverlayLayout(
}
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.CenterEnd) {
Box(Modifier.padding(end = 32.dp)) {
- ToggleSoundButton(call, toggleSound)
+ ToggleSoundButton(call, speakerCanBeEnabled, toggleSound)
}
}
}
@@ -262,10 +315,10 @@ private fun ActiveCallOverlayLayout(
}
@Composable
-private fun ControlButton(call: Call, icon: ImageVector, @StringRes iconText: Int, action: () -> Unit) {
+private fun ControlButton(call: Call, icon: ImageVector, @StringRes iconText: Int, action: () -> Unit, enabled: Boolean = true) {
if (call.hasMedia) {
- IconButton(onClick = action) {
- Icon(icon, stringResource(iconText), tint = Color(0xFFFFFFD8), modifier = Modifier.size(40.dp))
+ IconButton(onClick = action, enabled = enabled) {
+ Icon(icon, stringResource(iconText), tint = if (enabled) Color(0xFFFFFFD8) else HighOrLowlight, modifier = Modifier.size(40.dp))
}
} else {
Spacer(Modifier.size(40.dp))
@@ -282,11 +335,11 @@ private fun ToggleAudioButton(call: Call, toggleAudio: () -> Unit) {
}
@Composable
-private fun ToggleSoundButton(call: Call, toggleSound: () -> Unit) {
+private fun ToggleSoundButton(call: Call, enabled: Boolean, toggleSound: () -> Unit) {
if (call.soundSpeaker) {
- ControlButton(call, Icons.Outlined.VolumeUp, R.string.icon_descr_speaker_off, toggleSound)
+ ControlButton(call, Icons.Outlined.VolumeUp, R.string.icon_descr_speaker_off, toggleSound, enabled)
} else {
- ControlButton(call, Icons.Outlined.VolumeDown, R.string.icon_descr_speaker_on, toggleSound)
+ ControlButton(call, Icons.Outlined.VolumeDown, R.string.icon_descr_speaker_on, toggleSound, enabled)
}
}
@@ -486,6 +539,7 @@ fun PreviewActiveCallOverlayVideo() {
RTCIceCandidate(RTCIceCandidateType.Host, "tcp", null)
)
),
+ speakerCanBeEnabled = true,
dismiss = {},
toggleAudio = {},
toggleVideo = {},
@@ -510,6 +564,7 @@ fun PreviewActiveCallOverlayAudio() {
RTCIceCandidate(RTCIceCandidateType.Host, "udp", null)
)
),
+ speakerCanBeEnabled = true,
dismiss = {},
toggleAudio = {},
toggleVideo = {},
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/AddGroupMembersView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/AddGroupMembersView.kt
index 753b2746b..1ff21e665 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/AddGroupMembersView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/AddGroupMembersView.kt
@@ -166,7 +166,7 @@ private fun RoleSelectionRow(groupInfo: GroupInfo, selectedRole: MutableState GroupProfileView(groupInfo, chatModel, close) }
},
+ addOrEditWelcomeMessage = {
+ ModalManager.shared.showCustomModal { close -> GroupWelcomeView(chatModel, groupInfo, close) }
+ },
openPreferences = {
ModalManager.shared.showCustomModal { close ->
GroupPreferencesView(
@@ -147,6 +150,7 @@ fun GroupChatInfoLayout(
addMembers: () -> Unit,
showMemberInfo: (GroupMember) -> Unit,
editGroupProfile: () -> Unit,
+ addOrEditWelcomeMessage: () -> Unit,
openPreferences: () -> Unit,
deleteGroup: () -> Unit,
clearChat: () -> Unit,
@@ -171,6 +175,8 @@ fun GroupChatInfoLayout(
if (groupInfo.canEdit) {
SectionItemView(editGroupProfile) { EditGroupProfileButton() }
SectionDivider()
+ SectionItemView(addOrEditWelcomeMessage) { AddOrEditWelcomeMessage(groupInfo.groupProfile.description) }
+ SectionDivider()
}
GroupPreferencesButton(openPreferences)
}
@@ -387,6 +393,28 @@ fun EditGroupProfileButton() {
}
}
+@Composable
+private fun AddOrEditWelcomeMessage(welcomeMessage: String?) {
+ val text = if (welcomeMessage == null) {
+ stringResource(R.string.button_add_welcome_message)
+ } else {
+ stringResource(R.string.button_welcome_message)
+ }
+ Row(
+ Modifier
+ .fillMaxSize(),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ Icons.Outlined.MapsUgc,
+ text,
+ tint = HighOrLowlight
+ )
+ Spacer(Modifier.size(8.dp))
+ Text(text)
+ }
+}
+
@Composable
private fun LeaveGroupButton() {
Row(
@@ -432,7 +460,7 @@ fun PreviewGroupChatInfoLayout() {
members = listOf(GroupMember.sampleData, GroupMember.sampleData, GroupMember.sampleData),
developerTools = false,
groupLink = null,
- addMembers = {}, showMemberInfo = {}, editGroupProfile = {}, openPreferences = {}, deleteGroup = {}, clearChat = {}, leaveGroup = {}, manageGroupLink = {},
+ addMembers = {}, showMemberInfo = {}, editGroupProfile = {}, addOrEditWelcomeMessage = {}, openPreferences = {}, deleteGroup = {}, clearChat = {}, leaveGroup = {}, manageGroupLink = {},
)
}
}
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupLinkView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupLinkView.kt
index 1b20eb7a8..ab8e35a9c 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupLinkView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupLinkView.kt
@@ -120,9 +120,9 @@ fun GroupLinkLayout(
if (groupLink == null) {
SimpleButton(stringResource(R.string.button_create_group_link), icon = Icons.Outlined.AddLink, disabled = creatingLink, click = createLink)
} else {
-// SectionItemView(padding = PaddingValues(bottom = DEFAULT_PADDING)) {
-// RoleSelectionRow(groupInfo, groupLinkMemberRole)
-// }
+ SectionItemView(padding = PaddingValues(bottom = DEFAULT_PADDING)) {
+ RoleSelectionRow(groupInfo, groupLinkMemberRole)
+ }
var initialLaunch by remember { mutableStateOf(true) }
LaunchedEffect(groupLinkMemberRole.value) {
if (!initialLaunch) {
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/WelcomeMessageView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/WelcomeMessageView.kt
new file mode 100644
index 000000000..f20630b94
--- /dev/null
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/WelcomeMessageView.kt
@@ -0,0 +1,94 @@
+package chat.simplex.app.views.chat.group
+
+import SectionItemView
+import SectionSpacer
+import SectionView
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.runtime.*
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import chat.simplex.app.R
+import chat.simplex.app.model.*
+import chat.simplex.app.ui.theme.*
+import chat.simplex.app.views.helpers.*
+
+@Composable
+fun GroupWelcomeView(m: ChatModel, groupInfo: GroupInfo, close: () -> Unit) {
+ var groupInfo by remember { mutableStateOf(groupInfo) }
+ val welcomeText = remember { mutableStateOf(groupInfo.groupProfile.description ?: "") }
+
+ fun save(afterSave: () -> Unit = {}) {
+ withApi {
+ var welcome: String? = welcomeText.value.trim('\n', ' ')
+ if (welcome?.length == 0) {
+ welcome = null
+ }
+ val groupProfileUpdated = groupInfo.groupProfile.copy(description = welcome)
+ val res = m.controller.apiUpdateGroup(groupInfo.groupId, groupProfileUpdated)
+ if (res != null) {
+ groupInfo = res
+ m.updateGroup(res)
+ welcomeText.value = welcome ?: ""
+ }
+ afterSave()
+ }
+ }
+
+ ModalView(
+ close = {
+ if (welcomeText.value == groupInfo.groupProfile.description || (welcomeText.value == "" && groupInfo.groupProfile.description == null)) close()
+ else showUnsavedChangesAlert({ save(close) }, close)
+ },
+ background = if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
+ ) {
+ GroupWelcomeLayout(
+ welcomeText,
+ groupInfo,
+ save = ::save
+ )
+ }
+}
+
+@Composable
+private fun GroupWelcomeLayout(
+ welcomeText: MutableState,
+ groupInfo: GroupInfo,
+ save: () -> Unit,
+) {
+ Column(
+ Modifier.fillMaxWidth().verticalScroll(rememberScrollState()),
+ ) {
+ AppBarTitle(stringResource(R.string.group_welcome_title))
+ val welcomeText = remember { welcomeText }
+ TextEditor(Modifier.padding(horizontal = DEFAULT_PADDING).height(160.dp), text = welcomeText)
+ SectionSpacer()
+ SaveButton(
+ save = save,
+ disabled = welcomeText.value == groupInfo.groupProfile.description || (welcomeText.value == "" && groupInfo.groupProfile.description == null)
+ )
+ }
+}
+
+@Composable
+private fun SaveButton(save: () -> Unit, disabled: Boolean) {
+ SectionView {
+ SectionItemView(save, disabled = disabled) {
+ Text(stringResource(R.string.save_and_update_group_profile), color = if (disabled) HighOrLowlight else MaterialTheme.colors.primary)
+ }
+ }
+}
+
+private fun showUnsavedChangesAlert(save: () -> Unit, revert: () -> Unit) {
+ AlertManager.shared.showAlertDialogStacked(
+ title = generalGetString(R.string.save_welcome_message_question),
+ confirmText = generalGetString(R.string.save_and_update_group_profile),
+ dismissText = generalGetString(R.string.exit_without_saving),
+ onConfirm = save,
+ onDismiss = revert,
+ )
+}
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatHelpView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatHelpView.kt
index 7d36074a3..4807241cd 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatHelpView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatHelpView.kt
@@ -19,6 +19,7 @@ import androidx.compose.ui.unit.sp
import chat.simplex.app.R
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.helpers.annotatedStringResource
+import chat.simplex.app.views.helpers.openUriCatching
import chat.simplex.app.views.usersettings.MarkdownHelpView
import chat.simplex.app.views.usersettings.simplexTeamUri
@@ -36,7 +37,7 @@ fun ChatHelpView(addContact: (() -> Unit)? = null) {
Text(
annotatedStringResource(R.string.you_can_connect_to_simplex_chat_founder),
modifier = Modifier.clickable(onClick = {
- uriHandler.openUri(simplexTeamUri)
+ uriHandler.openUriCatching(simplexTeamUri)
}),
lineHeight = 22.sp
)
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt
index 3e6ec6ae8..8f3d7e685 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt
@@ -132,7 +132,7 @@ private fun OnboardingButtons(openNewChatSheet: () -> Unit) {
Column(Modifier.fillMaxSize().padding(DEFAULT_PADDING), horizontalAlignment = Alignment.End, verticalArrangement = Arrangement.Bottom) {
val uriHandler = LocalUriHandler.current
ConnectButton(generalGetString(R.string.chat_with_developers)) {
- uriHandler.openUri(simplexTeamUri)
+ uriHandler.openUriCatching(simplexTeamUri)
}
Spacer(Modifier.height(DEFAULT_PADDING))
ConnectButton(generalGetString(R.string.tap_to_start_new_chat), openNewChatSheet)
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt
index 777f7d110..74eae06e0 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt
@@ -1,12 +1,16 @@
package chat.simplex.app.views.helpers
+import android.app.Activity
import android.app.Application
+import android.app.LocaleManager
+import android.content.ActivityNotFoundException
import android.content.Context
+import android.content.res.Configuration
import android.content.res.Resources
import android.graphics.*
import android.graphics.Typeface
import android.net.Uri
-import android.os.FileUtils
+import android.os.*
import android.provider.OpenableColumns
import android.text.Spanned
import android.text.SpannedString
@@ -29,8 +33,7 @@ import androidx.compose.ui.unit.*
import androidx.core.content.FileProvider
import androidx.core.text.HtmlCompat
import chat.simplex.app.*
-import chat.simplex.app.model.CIFile
-import chat.simplex.app.model.json
+import chat.simplex.app.model.*
import kotlinx.coroutines.*
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
@@ -501,3 +504,42 @@ inline fun serializableSaver(): Saver = Saver(
save = { json.encodeToString(it) },
restore = { json.decodeFromString(it) }
)
+
+fun saveAppLocale(pref: SharedPreference, activity: Activity, languageCode: String? = null) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ val localeManager = SimplexApp.context.getSystemService(LocaleManager::class.java)
+ localeManager.applicationLocales = LocaleList(Locale.forLanguageTag(languageCode ?: return))
+ } else {
+ pref.set(languageCode)
+ if (languageCode == null) {
+ activity.applyLocale(SimplexApp.context.defaultLocale)
+ }
+ activity.recreate()
+ }
+}
+
+fun Activity.applyAppLocale(pref: SharedPreference) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+ val lang = pref.get()
+ if (lang == null || lang == Locale.getDefault().language) return
+ applyLocale(Locale.forLanguageTag(lang))
+ }
+}
+
+private fun Activity.applyLocale(locale: Locale) {
+ Locale.setDefault(locale)
+ val appConf = Configuration(SimplexApp.context.resources.configuration).apply { setLocale(locale) }
+ val activityConf = Configuration(resources.configuration).apply { setLocale(locale) }
+ @Suppress("DEPRECATION")
+ SimplexApp.context.resources.updateConfiguration(appConf, resources.displayMetrics)
+ @Suppress("DEPRECATION")
+ resources.updateConfiguration(activityConf, resources.displayMetrics)
+}
+
+fun UriHandler.openUriCatching(uri: String) {
+ try {
+ openUri(uri)
+ } catch (e: ActivityNotFoundException) {
+ Log.e(TAG, e.stackTraceToString())
+ }
+}
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/HowItWorks.kt b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/HowItWorks.kt
index a00a7ae65..3e13b7cf5 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/HowItWorks.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/HowItWorks.kt
@@ -36,7 +36,7 @@ fun HowItWorks(user: User?, onboardingStage: MutableState? = n
val uriHandler = LocalUriHandler.current
Text(
annotatedStringResource(R.string.read_more_in_github_with_link),
- modifier = Modifier.padding(bottom = 12.dp).clickable { uriHandler.openUri("https://github.com/simplex-chat/simplex-chat#readme") },
+ modifier = Modifier.padding(bottom = 12.dp).clickable { uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat#readme") },
lineHeight = 22.sp
)
} else {
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/WhatsNewView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/WhatsNewView.kt
index b6c218eff..c112f8ff2 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/WhatsNewView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/WhatsNewView.kt
@@ -35,7 +35,7 @@ fun WhatsNewView(viaSettings: Boolean = false, close: () -> Unit) {
Icon(
Icons.Outlined.OpenInNew, stringResource(titleId), tint = MaterialTheme.colors.primary,
modifier = Modifier
- .clickable { uriHandler.openUri(link) }
+ .clickable { uriHandler.openUriCatching(link) }
)
}
@@ -270,8 +270,45 @@ private val versionDescriptions: List = listOf(
link = "https://github.com/simplex-chat/simplex-chat/tree/stable#translate-the-apps"
)
)
- )
+ ),
+ VersionDescription(
+ version = "v4.6",
+ features = listOf(
+ FeatureDescription(
+ icon = Icons.Outlined.Lock,
+ titleId = R.string.v4_6_hidden_chat_profiles,
+ descrId = R.string.v4_6_hidden_chat_profiles_descr
+ ),
+ FeatureDescription(
+ icon = Icons.Outlined.Flag,
+ titleId = R.string.v4_6_group_moderation,
+ descrId = R.string.v4_6_group_moderation_descr
+ ),
+ FeatureDescription(
+ icon = Icons.Outlined.MapsUgc,
+ titleId = R.string.v4_6_group_welcome_message,
+ descrId = R.string.v4_6_group_welcome_message_descr
+ ),
+ FeatureDescription(
+ icon = Icons.Outlined.Call,
+ titleId = R.string.v4_6_audio_video_calls,
+ descrId = R.string.v4_6_audio_video_calls_descr
+ ),
+ FeatureDescription(
+ icon = Icons.Outlined.Battery3Bar,
+ titleId = R.string.v4_6_reduced_battery_usage,
+ descrId = R.string.v4_6_reduced_battery_usage_descr
+ ),
+ FeatureDescription(
+ icon = Icons.Outlined.Translate,
+ titleId = R.string.v4_6_chinese_spanish_interface,
+ descrId = R.string.v4_6_chinese_spanish_interface_descr,
+ link = "https://github.com/simplex-chat/simplex-chat/tree/stable#translate-the-apps"
+ )
+ )
+ ),
)
+
private val lastVersion = versionDescriptions.last().version
fun setLastVersionDefault(m: ChatModel) {
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/Appearance.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/Appearance.kt
index d63989438..2ff6c498b 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/Appearance.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/Appearance.kt
@@ -2,13 +2,20 @@ package chat.simplex.app.views.usersettings
import SectionCustomFooter
import SectionDivider
+import SectionItemView
import SectionItemViewSpaceBetween
+import SectionItemWithValue
import SectionSpacer
import SectionView
+import android.app.Activity
import android.content.ComponentName
+import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+import android.net.Uri
+import android.os.Build
+import android.provider.Settings
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
@@ -17,6 +24,7 @@ import androidx.compose.material.MaterialTheme.colors
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Circle
import androidx.compose.runtime.*
+import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
@@ -30,9 +38,13 @@ import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmap
import chat.simplex.app.*
import chat.simplex.app.R
+import chat.simplex.app.model.ChatModel
+import chat.simplex.app.model.SharedPreference
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.*
import com.godaddy.android.colorpicker.*
+import kotlinx.coroutines.delay
+import java.util.*
enum class AppIcon(val resId: Int) {
DEFAULT(R.mipmap.icon),
@@ -40,7 +52,7 @@ enum class AppIcon(val resId: Int) {
}
@Composable
-fun AppearanceView() {
+fun AppearanceView(m: ChatModel) {
val appIcon = remember { mutableStateOf(findEnabledIcon()) }
fun setAppIcon(newIcon: AppIcon) {
@@ -62,6 +74,7 @@ fun AppearanceView() {
AppearanceLayout(
appIcon,
+ m.controller.appPrefs.appLanguage,
changeIcon = ::setAppIcon,
editPrimaryColor = { primary ->
ModalManager.shared.showModalCloseable { close ->
@@ -73,6 +86,7 @@ fun AppearanceView() {
@Composable fun AppearanceLayout(
icon: MutableState,
+ languagePref: SharedPreference,
changeIcon: (AppIcon) -> Unit,
editPrimaryColor: (Color) -> Unit,
) {
@@ -81,6 +95,37 @@ fun AppearanceView() {
horizontalAlignment = Alignment.Start,
) {
AppBarTitle(stringResource(R.string.appearance_settings))
+ SectionView(stringResource(R.string.settings_section_title_language), padding = PaddingValues()) {
+ val context = LocalContext.current
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ SectionItemWithValue(
+ generalGetString(R.string.settings_section_title_language).lowercase().replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.US) else it.toString() },
+ remember { mutableStateOf("system") },
+ listOf(ValueTitleDesc("system", generalGetString(R.string.change_verb), "")),
+ onSelected = { openSystemLangPicker(context as? Activity ?: return@SectionItemWithValue) }
+ )
+ } else {
+ val state = rememberSaveable { mutableStateOf(languagePref.get() ?: "system") }
+ SectionItemView {
+ LangSelector(state) {
+ state.value = it
+ withApi {
+ delay(200)
+ val activity = context as? Activity
+ if (activity != null) {
+ if (it == "system") {
+ saveAppLocale(languagePref, activity)
+ } else {
+ saveAppLocale(languagePref, activity, it)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ SectionSpacer()
+
SectionView(stringResource(R.string.settings_section_title_icon), padding = PaddingValues(horizontal = DEFAULT_PADDING_HALF)) {
LazyRow {
items(AppIcon.values().size, { index -> AppIcon.values()[index] }) { index ->
@@ -179,6 +224,32 @@ fun ColorPicker(initialColor: Color, onColorChanged: (Color) -> Unit) {
)
}
+@Composable
+private fun LangSelector(state: State, onSelected: (String) -> Unit) {
+ // Should be the same as in app/build.gradle's `android.defaultConfig.resConfigs`
+ val supportedLanguages = mapOf(
+ "system" to generalGetString(R.string.language_system),
+ "en" to "English",
+ "cs" to "Čeština",
+ "de" to "Deutsch",
+ "es" to "Español",
+ "fr" to "Français",
+ "it" to "Italiano",
+ "nl" to "Nederlands",
+ "ru" to "Русский",
+ "zh-CN" to "简体中文"
+ )
+ val values by remember { mutableStateOf(supportedLanguages.map { it.key to it.value }) }
+ ExposedDropDownSettingRow(
+ generalGetString(R.string.settings_section_title_language).lowercase().replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.US) else it.toString() },
+ values,
+ state,
+ icon = null,
+ enabled = remember { mutableStateOf(true) },
+ onSelected = onSelected
+ )
+}
+
@Composable
private fun ThemeSelector(state: State, onSelected: (DefaultTheme) -> Unit) {
val darkTheme = isSystemInDarkTheme()
@@ -193,6 +264,9 @@ private fun ThemeSelector(state: State, onSelected: (DefaultTheme)
)
}
+private fun openSystemLangPicker(activity: Activity) {
+ activity.startActivity(Intent(Settings.ACTION_APP_LOCALE_SETTINGS, Uri.parse("package:" + SimplexApp.context.packageName)))
+}
private fun findEnabledIcon(): AppIcon = AppIcon.values().first { icon ->
SimplexApp.context.packageManager.getComponentEnabledSetting(
@@ -206,6 +280,7 @@ fun PreviewAppearanceSettings() {
SimpleXTheme {
AppearanceLayout(
icon = remember { mutableStateOf(AppIcon.DARK_BLUE) },
+ languagePref = SharedPreference({ null }, {}),
changeIcon = {},
editPrimaryColor = {},
)
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/CallSettings.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/CallSettings.kt
index d5873f35b..c1fa4a04b 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/CallSettings.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/CallSettings.kt
@@ -2,6 +2,7 @@ package chat.simplex.app.views.usersettings
import SectionDivider
import SectionItemView
+import SectionTextFooter
import SectionView
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
@@ -43,16 +44,23 @@ fun CallSettingsLayout(
AppBarTitle(stringResource(R.string.your_calls))
val lockCallState = remember { mutableStateOf(callOnLockScreen.get()) }
SectionView(stringResource(R.string.settings_section_title_settings)) {
- SectionItemView() {
- SharedPreferenceToggle(stringResource(R.string.connect_calls_via_relay), webrtcPolicyRelay)
- }
+ SectionItemView(editIceServers) { Text(stringResource(R.string.webrtc_ice_servers)) }
SectionDivider()
val enabled = remember { mutableStateOf(true) }
SectionItemView { LockscreenOpts(lockCallState, enabled, onSelected = { callOnLockScreen.set(it); lockCallState.value = it }) }
SectionDivider()
- SectionItemView(editIceServers) { Text(stringResource(R.string.webrtc_ice_servers)) }
+ SectionItemView() {
+ SharedPreferenceToggle(stringResource(R.string.always_use_relay), webrtcPolicyRelay)
+ }
}
+ SectionTextFooter(
+ if (remember { webrtcPolicyRelay.state }.value) {
+ generalGetString(R.string.relay_server_protects_ip)
+ } else {
+ generalGetString(R.string.relay_server_if_necessary)
+ }
+ )
}
}
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/NetworkAndServers.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/NetworkAndServers.kt
index 7ab7a9419..36cffb693 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/NetworkAndServers.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/NetworkAndServers.kt
@@ -25,6 +25,7 @@ fun NetworkAndServersView(
chatModel: ChatModel,
showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
+ showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit),
) {
// It's not a state, just a one-time value. Shouldn't be used in any state-related situations
val netCfg = remember { chatModel.controller.getNetCfg() }
@@ -44,6 +45,7 @@ fun NetworkAndServersView(
sessionMode = sessionMode,
showModal = showModal,
showSettingsModal = showSettingsModal,
+ showCustomModal = showCustomModal,
toggleSocksProxy = { enable ->
if (enable) {
AlertManager.shared.showAlertMsg(
@@ -138,6 +140,7 @@ fun NetworkAndServersView(
sessionMode: MutableState,
showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
+ showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit),
toggleSocksProxy: (Boolean) -> Unit,
useOnion: (OnionHosts) -> Unit,
updateSessionMode: (TransportSessionMode) -> Unit,
@@ -149,7 +152,7 @@ fun NetworkAndServersView(
) {
AppBarTitle(stringResource(R.string.network_and_servers))
SectionView(generalGetString(R.string.settings_section_title_messages)) {
- SettingsActionItem(Icons.Outlined.Dns, stringResource(R.string.smp_servers), showSettingsModal { SMPServersView(it) })
+ SettingsActionItem(Icons.Outlined.Dns, stringResource(R.string.smp_servers), showCustomModal { m, close -> SMPServersView(m, close) })
SectionDivider()
SectionItemView {
UseSocksProxySwitch(networkUseSocksProxy, toggleSocksProxy)
@@ -297,6 +300,7 @@ fun PreviewNetworkAndServersLayout() {
networkUseSocksProxy = remember { mutableStateOf(true) },
showModal = { {} },
showSettingsModal = { {} },
+ showCustomModal = { {} },
toggleSocksProxy = {},
onionHosts = remember { mutableStateOf(OnionHosts.PREFER) },
sessionMode = remember { mutableStateOf(TransportSessionMode.User) },
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/RTCServers.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/RTCServers.kt
index fbcd5008e..31a3dd58e 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/RTCServers.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/RTCServers.kt
@@ -198,7 +198,7 @@ private fun howToButton() {
val uriHandler = LocalUriHandler.current
Row(
verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier.clickable { uriHandler.openUri("https://github.com/simplex-chat/simplex-chat/blob/stable/docs/WEBRTC.md#configure-mobile-apps") }
+ modifier = Modifier.clickable { uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat/blob/stable/docs/WEBRTC.md#configure-mobile-apps") }
) {
Text(stringResource(R.string.how_to), color = MaterialTheme.colors.primary)
Icon(
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SMPServersView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SMPServersView.kt
index d8df8db83..8286121c3 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SMPServersView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SMPServersView.kt
@@ -27,7 +27,7 @@ import chat.simplex.app.views.helpers.*
import kotlinx.coroutines.launch
@Composable
-fun SMPServersView(m: ChatModel) {
+fun SMPServersView(m: ChatModel, close: () -> Unit) {
var servers by remember {
mutableStateOf(m.userSMPServersUnsaved.value ?: m.userSMPServers.value ?: emptyList())
}
@@ -72,83 +72,90 @@ fun SMPServersView(m: ChatModel) {
}
}
val scope = rememberCoroutineScope()
-
- SMPServersLayout(
- testing = testing.value,
- servers = servers,
- serversUnchanged = serversUnchanged.value,
- saveDisabled = saveDisabled.value,
- allServersDisabled = allServersDisabled.value,
- m.currentUser.value,
- addServer = {
- AlertManager.shared.showAlertDialogButtonsColumn(
- title = generalGetString(R.string.smp_servers_add),
- buttons = {
- Column {
- SectionItemView({
- AlertManager.shared.hideAlert()
- servers = servers + ServerCfg.empty
- // No saving until something will be changed on the next screen to prevent blank servers on the list
- showServer(servers.last())
- }) {
- Text(stringResource(R.string.smp_servers_enter_manually))
- }
- SectionItemView({
- AlertManager.shared.hideAlert()
- ModalManager.shared.showModalCloseable { close ->
- ScanSMPServer {
- close()
- servers = servers + it
- m.userSMPServersUnsaved.value = servers
+ ModalView(
+ close = {
+ if (saveDisabled.value) close()
+ else showUnsavedChangesAlert({ saveSMPServers(servers, m, close) }, close)
+ },
+ background = if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
+ ) {
+ SMPServersLayout(
+ testing = testing.value,
+ servers = servers,
+ serversUnchanged = serversUnchanged.value,
+ saveDisabled = saveDisabled.value,
+ allServersDisabled = allServersDisabled.value,
+ m.currentUser.value,
+ addServer = {
+ AlertManager.shared.showAlertDialogButtonsColumn(
+ title = generalGetString(R.string.smp_servers_add),
+ buttons = {
+ Column {
+ SectionItemView({
+ AlertManager.shared.hideAlert()
+ servers = servers + ServerCfg.empty
+ // No saving until something will be changed on the next screen to prevent blank servers on the list
+ showServer(servers.last())
+ }) {
+ Text(stringResource(R.string.smp_servers_enter_manually))
+ }
+ SectionItemView({
+ AlertManager.shared.hideAlert()
+ ModalManager.shared.showModalCloseable { close ->
+ ScanSMPServer {
+ close()
+ servers = servers + it
+ m.userSMPServersUnsaved.value = servers
+ }
+ }
+ }
+ ) {
+ Text(stringResource(R.string.smp_servers_scan_qr))
+ }
+ val hasAllPresets = hasAllPresets(servers, m)
+ if (!hasAllPresets) {
+ SectionItemView({
+ AlertManager.shared.hideAlert()
+ servers = (servers + addAllPresets(servers, m)).sortedByDescending { it.preset }
+ }) {
+ Text(stringResource(R.string.smp_servers_preset_add), color = MaterialTheme.colors.onBackground)
}
}
}
- ) {
- Text(stringResource(R.string.smp_servers_scan_qr))
- }
- val hasAllPresets = hasAllPresets(servers, m)
- if (!hasAllPresets) {
- SectionItemView({
- AlertManager.shared.hideAlert()
- servers = (servers + addAllPresets(servers, m)).sortedByDescending { it.preset }
- }) {
- Text(stringResource(R.string.smp_servers_preset_add), color = MaterialTheme.colors.onBackground)
- }
- }
+ }
+ )
+ },
+ testServers = {
+ scope.launch {
+ testServers(testing, servers, m) {
+ servers = it
+ m.userSMPServersUnsaved.value = servers
}
}
- )
- },
- testServers = {
- scope.launch {
- testServers(testing, servers, m) {
- servers = it
- m.userSMPServersUnsaved.value = servers
- }
- }
- },
- resetServers = {
- servers = m.userSMPServers.value ?: emptyList()
- m.userSMPServersUnsaved.value = null
- },
- saveSMPServers = {
- saveSMPServers(servers, m)
- },
- showServer = ::showServer,
- )
+ },
+ resetServers = {
+ servers = m.userSMPServers.value ?: emptyList()
+ m.userSMPServersUnsaved.value = null
+ },
+ saveSMPServers = {
+ saveSMPServers(servers, m)
+ },
+ showServer = ::showServer,
+ )
- if (testing.value) {
- Box(
- Modifier.fillMaxSize(),
- contentAlignment = Alignment.Center
- ) {
- CircularProgressIndicator(
- Modifier
- .padding(horizontal = 2.dp)
- .size(30.dp),
- color = HighOrLowlight,
- strokeWidth = 2.5.dp
- )
+ if (testing.value) {
+ Box(
+ Modifier.fillMaxSize(),
+ contentAlignment = Alignment.Center
+ ) {
+ CircularProgressIndicator(
+ Modifier
+ .padding(horizontal = 2.dp)
+ .size(30.dp),
+ color = HighOrLowlight,
+ strokeWidth = 2.5.dp
+ )
+ }
}
}
}
@@ -247,7 +254,7 @@ private fun HowToButton() {
SettingsActionItem(
Icons.Outlined.OpenInNew,
stringResource(R.string.how_to_use_your_servers),
- { uriHandler.openUri("https://github.com/simplex-chat/simplex-chat/blob/stable/docs/SERVER.md") },
+ { uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat/blob/stable/docs/SERVER.md") },
textColor = MaterialTheme.colors.primary,
iconColor = MaterialTheme.colors.primary
)
@@ -324,12 +331,22 @@ private suspend fun runServersTest(servers: List, m: ChatModel, onUpd
return fs
}
-private fun saveSMPServers(servers: List, m: ChatModel) {
+private fun saveSMPServers(servers: List, m: ChatModel, afterSave: () -> Unit = {}) {
withApi {
if (m.controller.setUserSMPServers(servers)) {
m.userSMPServers.value = servers
m.userSMPServersUnsaved.value = null
}
+ afterSave()
}
}
+private fun showUnsavedChangesAlert(save: () -> Unit, revert: () -> Unit) {
+ AlertManager.shared.showAlertDialogStacked(
+ title = generalGetString(R.string.smp_save_servers_question),
+ confirmText = generalGetString(R.string.save_verb),
+ dismissText = generalGetString(R.string.exit_without_saving),
+ onConfirm = save,
+ onDismiss = revert,
+ )
+}
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt
index f6d4e6fc9..c586aaf36 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt
@@ -154,13 +154,13 @@ fun SettingsLayout(
SectionView(stringResource(R.string.settings_section_title_settings)) {
SettingsActionItem(Icons.Outlined.Bolt, stringResource(R.string.notifications), showSettingsModal { NotificationsSettingsView(it) }, disabled = stopped)
SectionDivider()
- SettingsActionItem(Icons.Outlined.WifiTethering, stringResource(R.string.network_and_servers), showSettingsModal { NetworkAndServersView(it, showModal, showSettingsModal) }, disabled = stopped)
+ SettingsActionItem(Icons.Outlined.WifiTethering, stringResource(R.string.network_and_servers), showSettingsModal { NetworkAndServersView(it, showModal, showSettingsModal, showCustomModal) }, disabled = stopped)
SectionDivider()
SettingsActionItem(Icons.Outlined.Videocam, stringResource(R.string.settings_audio_video_calls), showSettingsModal { CallSettingsView(it, showModal) }, disabled = stopped)
SectionDivider()
SettingsActionItem(Icons.Outlined.Lock, stringResource(R.string.privacy_and_security), showSettingsModal { PrivacySettingsView(it, setPerformLA) }, disabled = stopped)
SectionDivider()
- SettingsActionItem(Icons.Outlined.LightMode, stringResource(R.string.appearance_settings), showSettingsModal { AppearanceView() }, disabled = stopped)
+ SettingsActionItem(Icons.Outlined.LightMode, stringResource(R.string.appearance_settings), showSettingsModal { AppearanceView(it) }, disabled = stopped)
SectionDivider()
DatabaseItem(encrypted, showSettingsModal { DatabaseView(it, showSettingsModal) }, stopped)
}
@@ -173,9 +173,9 @@ fun SettingsLayout(
SectionDivider()
SettingsActionItem(Icons.Outlined.Info, stringResource(R.string.about_simplex_chat), showModal { SimpleXInfo(it, onboarding = false) })
SectionDivider()
- SettingsActionItem(Icons.Outlined.Tag, stringResource(R.string.chat_with_the_founder), { uriHandler.openUri(simplexTeamUri) }, textColor = MaterialTheme.colors.primary, disabled = stopped)
+ SettingsActionItem(Icons.Outlined.Tag, stringResource(R.string.chat_with_the_founder), { uriHandler.openUriCatching(simplexTeamUri) }, textColor = MaterialTheme.colors.primary, disabled = stopped)
SectionDivider()
- SettingsActionItem(Icons.Outlined.Email, stringResource(R.string.send_us_an_email), { uriHandler.openUri("mailto:chat@simplex.chat") }, textColor = MaterialTheme.colors.primary)
+ SettingsActionItem(Icons.Outlined.Email, stringResource(R.string.send_us_an_email), { uriHandler.openUriCatching("mailto:chat@simplex.chat") }, textColor = MaterialTheme.colors.primary)
}
SectionSpacer()
@@ -313,7 +313,7 @@ fun MaintainIncognitoState(chatModel: ChatModel) {
}
@Composable private fun ContributeItem(uriHandler: UriHandler) {
- SectionItemView({ uriHandler.openUri("https://github.com/simplex-chat/simplex-chat#contribute") }) {
+ SectionItemView({ uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat#contribute") }) {
Icon(
Icons.Outlined.Keyboard,
contentDescription = "GitHub",
@@ -326,8 +326,8 @@ fun MaintainIncognitoState(chatModel: ChatModel) {
@Composable private fun RateAppItem(uriHandler: UriHandler) {
SectionItemView({
- runCatching { uriHandler.openUri("market://details?id=chat.simplex.app") }
- .onFailure { uriHandler.openUri("https://play.google.com/store/apps/details?id=chat.simplex.app") }
+ runCatching { uriHandler.openUriCatching("market://details?id=chat.simplex.app") }
+ .onFailure { uriHandler.openUriCatching("https://play.google.com/store/apps/details?id=chat.simplex.app") }
}
) {
Icon(
@@ -341,7 +341,7 @@ fun MaintainIncognitoState(chatModel: ChatModel) {
}
@Composable private fun StarOnGithubItem(uriHandler: UriHandler) {
- SectionItemView({ uriHandler.openUri("https://github.com/simplex-chat/simplex-chat") }) {
+ SectionItemView({ uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat") }) {
Icon(
painter = painterResource(id = R.drawable.ic_github),
contentDescription = "GitHub",
@@ -365,7 +365,7 @@ fun MaintainIncognitoState(chatModel: ChatModel) {
}
@Composable private fun InstallTerminalAppItem(uriHandler: UriHandler) {
- SectionItemView({ uriHandler.openUri("https://github.com/simplex-chat/simplex-chat") }) {
+ SectionItemView({ uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat") }) {
Icon(
painter = painterResource(id = R.drawable.ic_github),
contentDescription = "GitHub",
diff --git a/apps/android/app/src/main/res/values-ar/strings.xml b/apps/android/app/src/main/res/values-ar/strings.xml
index a6b3daec9..c3c9af8f8 100644
--- a/apps/android/app/src/main/res/values-ar/strings.xml
+++ b/apps/android/app/src/main/res/values-ar/strings.xml
@@ -1,2 +1,29 @@
-
\ No newline at end of file
+
+ اقبل
+ عن ٍSimpleX
+ a + b
+ اقبل
+ اسبوع 1
+ شهر 1
+ لون تمييزي
+ يوم 1
+ اقبل
+ عن SimpleX
+ أعلاه ، ثم:
+ اقبل
+ لا يمكن التراجع عن هذا الإجراء - سيتم فقد ملف التعريف وجهات الاتصال والرسائل والملفات الخاصة بك بشكل نهائي.
+ هذه المجموعة لم تعد موجودة.
+ رمز QR هذا ليس رابطًا!
+ الجيل القادم من الرسائل الخاصة
+ لا يمكن التراجع عن هذا الإجراء - سيتم حذف جميع الملفات والوسائط المستلمة والمرسلة. ستبقى الصور منخفضة الدقة.
+ لا يمكن التراجع عن هذا الإجراء - سيتم حذف الرسائل المرسلة والمستلمة قبل التحديد. قد تأخذ عدة دقائق.
+ ينطبق هذا الإعداد على الرسائل الموجودة في ملف تعريف الدردشة الحالي الخاص بك
+ منصة الرسائل والتطبيقات تحمي خصوصيتك وأمنك.
+ يتم مشاركة ملف التعريف مع جهات الاتصال الخاصة بك فقط.
+ سيتم تغيير الدور إلى \"%s\". سيتم إبلاغ كل فرد في المجموعة.
+ سيتم تغيير الدور إلى \"%s\". سيتلقى العضو دعوة جديدة.
+ خوادم الاتصالات الجديدة لملف تعريف الدردشة الحالي الخاص بك
+ هذه الميزة تجريبية! ستعمل فقط إذا كان لدى العميل الآخر الإصدار 4.2 مثبتًا. يجب أن ترى الرسالة في المحادثة بمجرد اكتمال تغيير العنوان - يرجى التحقق من أنه لا يزال بإمكانك تلقي الرسائل من جهة الاتصال هذه (أو عضو المجموعة).
+ هذا الارتباط ليس ارتباط اتصال صالح!
+
\ No newline at end of file
diff --git a/apps/android/app/src/main/res/values-cs/strings.xml b/apps/android/app/src/main/res/values-cs/strings.xml
index a4fd0b06f..d72c313d8 100644
--- a/apps/android/app/src/main/res/values-cs/strings.xml
+++ b/apps/android/app/src/main/res/values-cs/strings.xml
@@ -1,8 +1,8 @@
Povolte hlasové zprávy, pouze pokud je váš kontakt povolí.
- Povolit odesílání mizejících zpráv.
- Povolit odesílání hlasových zpráv.
+ Mizící zprávy povoleny.
+ Hlasové zprávy povoleny.
Správci mohou vytvářet odkazy pro připojení ke skupinám.
Přijmout
Přidejte přednastavené servery
@@ -11,7 +11,7 @@
Přidat server…
Přistupovat k serverům přes SOCKS proxy na portu 9050\? Před povolením této možnosti musí být spuštěna proxy.
Přijmout
- Umožněte svým kontaktům odesílat mizející zprávy.
+ Umožněte svým kontaktům odesílat mizící zprávy.
O SimpleX Chat
Přidat do jiného zařízení
Přijímat žádosti
@@ -24,7 +24,7 @@
správce
Přidat profil
Všechny chaty a zprávy budou smazány – tuto akci nelze vrátit zpět!
- Povolte mizející zprávy, pouze pokud to váš kontakt povolí.
+ Povolte mizící zprávy, pouze pokud to váš kontakt povolí.
Přidejte servery skenováním QR kódů.
1 měsíci
1 týdnu
@@ -33,8 +33,8 @@
Přijmout žádost o připojení\?
Všichni členové skupiny zůstanou připojeni.
Povolte nevratné smazání zprávy pouze v případě, že vám to váš kontakt dovolí.
- Povolit odesílání přímých zpráv členům.
- Povolit nevratné smazání odeslaných zpráv.
+ Přímé zprávy členům povoleny.
+ Nevratné mazání odeslaných zpráv povoleno.
Všechny zprávy budou smazány – tuto akci nelze vrátit zpět! Zprávy budou smazány POUZE pro vás.
Umožněte svým kontaktům nevratně odstranit odeslané zprávy.
Povolte svým kontaktům odesílání hlasových zpráv.
@@ -43,14 +43,14 @@
Odeslat přímou zprávu
ČLEN
Změnit roli ve skupině\?
- Připojení
+ Připoj
nepřímé (%1$s)
SERVERY
Příjímáno přez
Vytvoření tajné skupiny
Zobrazení názvu skupiny:
Úplný název skupiny:
- Váš profil v chatu bude zaslán členům skupiny
+ Váš chat profil bude zaslán členům skupiny
Profil skupiny je uložen v zařízeních členů, nikoli na serverech.
Uložit
Aktualizovat nastavení sítě\?
@@ -58,8 +58,8 @@
Váš náhodný profil
Vašemu kontaktu bude zaslán náhodný profil
Uložit barvu
- Obnovení barev
- Přízvuk
+ Obnovit barvu
+ Zbarvení
Povolujete
výchozí (%s)
ano
@@ -67,29 +67,29 @@
vždy
Nastavení skupinových předvoleb
Vaše preference
- Mizející zprávy
+ Mizící zprávy
povoleno pro kontakt
přijaté, zakázané
- Vy i váš kontakt můžete posílat mizející zprávy.
+ Vy i váš kontakt můžete posílat mizící zprávy.
Zmizelé zprávy může odesílat pouze váš kontakt.
Nevratně mazat zprávy můžete pouze vy (váš kontakt je může označit ke smazání).
Nevratné mazání zpráv je v tomto chatu zakázáno.
- Zakázat odesílání přímých zpráv členům.
+ Přímé zprávy členům zakázány.
%d sec
%ds
%d min
- %d hodina
+ %d hodinu
offered %s: %2s
Odkazy na skupiny
Hlasové zprávy
- Vaše kontakty mohou povolit úplné vymazání zpráv.
- Zmizení zpráv
+ Vaše kontakty mohou povolit úplné mazání zpráv.
+ Mizící zprávy
Porovnejte bezpečnostní kódy se svými kontakty.
SimpleX
k
- Připojit se přes kontaktní odkaz\?
- Připojit se přes odkaz na pozvánku\?
- Připojit se přes odkaz skupiny\?
+ Připojit se odkazem\?
+ Připojit se pozvánkou\?
+ Připojit se odkazem skupiny\?
Váš profil bude odeslán kontaktu, od kterého jste obdrželi tento odkaz.
připojeno
chyba
@@ -97,19 +97,19 @@
Pokus o připojení k serveru používanému pro příjem zpráv od tohoto kontaktu.
smazáno
neplatný chat
- neplatné údaje
+ neplatná data
spojení %1$d
spojení navázáno
pozvánka k připojení
připojení…
- jste sdíleli jednorázové spojení
+ sdíleli jste jednorázový odkaz
sdíleli jste jednorázový odkaz inkognito
prostřednictvím skupinového odkazu
- prostřednictvím odkazu na kontaktní adresu
- inkognito přes odkaz na kontaktní adresu
- prostřednictvím jednorázového odkazu
- inkognito přes jednorázový odkaz
- SimpleX kontaktní adresa
+ prostřednictvím odkazu
+ inkognito přes odkaz
+ jednorázovým odkazem
+ inkognito jednorázovým odkazem
+ SimpleX adresa
Jednorázová pozvánka SimpleX
Skupinový odkaz SimpleX
prostřednictvím %1$s
@@ -118,31 +118,31 @@
Úplný odkaz
Prostřednictvím prohlížeče
Otevření odkazu v prohlížeči může snížit soukromí a bezpečnost připojení. Nedůvěryhodné odkazy SimpleX budou červené.
- Chyba při ukládání serverů SMP
- Chyba při aktualizaci konfigurace sítě
+ Chyba ukládání serverů SMP
+ Chyba změny konfigurace sítě
Nepodařilo se načíst chat
Nepodařilo se načíst chaty
Aktualizujte aplikaci a kontaktujte vývojáře.
Časový limit připojení
Chyba připojení
Zkontrolujte prosím své síťové připojení pomocí %1$s a zkuste to znovu.
- Chyba při odesílání zprávy
- Chyba při přidávání členů
+ Chyba odesílání zprávy
+ Chyba přidávání členů
Kontakt již existuje
Jste již připojeni k %1$s!.
Neplatný odkaz na spojení
Chyba příjmu požadavku od kontaktu
Chyba změny adresy
- Služba oznamování
+ Oznamovací služba
Služba na pozadí je spuštěna vždy - oznámení se zobrazí, jakmile jsou zprávy k dispozici.
Text zprávy
Jméno kontaktu
Skryté
- Zobrazit kontakt a zprávu
- Připojeno
+ Zobrazí kontakt a zprávu
+ Připojen
SimpleX Zámek
- Přihlaste se pomocí svého pověření
- Zapnutí zámku SimpleX
+ Přihlásit pomocí ověření
+ Zapnout zámek SimpleX
Odpověď
Sdílet
Kopírovat
@@ -159,11 +159,11 @@
Čekání na obrázek
Požádáno o přijetí obrázku
Obrázek odeslán
- Čekáme na obrázek
+ Čekám na obrázek
Obrázek bude přijat, až bude váš kontakt online, vyčkejte prosím nebo se podívejte později!
Váš kontakt odeslal soubor, který je větší než aktuálně podporovaná maximální velikost (%1$s).
V současné době je maximální podporovaná velikost souboru %1$s.
- Chyba při ukládání souboru
+ Chyba ukládání souboru
Hlasová zpráva
Hlasová zpráva…
Připojeno
@@ -179,9 +179,9 @@
Obnovit
OK
bez podrobností
- Jednorázový zvací odkaz
- Zkopírováno do schránky
- Začít novou konverzaci
+ Jednorázová pozvánka
+ Zkopírováno
+ Nová konverzace
Vytvořit tajnou skupinu
(sdílet s kontaktem)
(uloženo pouze členy skupiny)
@@ -191,17 +191,17 @@
Vybrat soubor
Pro zahájení nové konverzace
Klepněte na tlačítko
- nad, potom:
- Přidat nový kontakt: pro vytvoření jednorázového QR kódu pro váš kontakt.
- Skenovat QR kód: připojení ke kontaktu, který vám ukáže QR kód.
- 💻 desktop: scan displayed QR code from the app, via Scan QR code.
+ 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.
+ 💻 počítač: naskenujte QR kód z aplikace přez Skenovat QR kód.
Vyčistit chat\?
Vyčistit
Označit jako přečteno
Označit jako nepřečteno
Ztlumit
Zrušit ztlumení
- Pozvali jste svůj kontakt
+ Pozvali jste kontakt
Kontakt, se kterým jste tento odkaz sdíleli, se NEBUDE moci připojit!
Připojení, které jste přijali, bude zrušeno!
help
@@ -210,9 +210,9 @@
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!
- Váš profil v chatu bude odeslán vašemu kontaktu
- Vytvořte jednorázový odkaz na pozvánku
- Vytvořit jednorázový zvací odkaz
+ Váš chat profil bude odeslán vašemu kontaktu
+ Vytvořit jednorázovou pozvánku
+ Jednorázová pozvánka
Bezpečnostní kód
%s je ověřeno
Chat konzole
@@ -225,7 +225,7 @@
Neplatná adresa serveru!
Zkontrolujte adresu serveru a zkuste to znovu.
Smazat server
- Přispějte
+ Přispět
Jak
Jak používat servery
Vaše servery ICE
@@ -246,18 +246,18 @@
Platforma pro zasílání zpráv a aplikace chránící vaše soukromí a bezpečnost.
Vytvořit profil
Profil je sdílen pouze s vašimi kontakty.
- Zobrazované jméno nesmí obsahovat prázdné znaky.
+ Zobrazované jméno nesmí obsahovat mezery.
tučně
probíhající hovor
Decentralizovaná
Jak to funguje
Jak funguje SimpleX
- Pouze klientská zařízení ukládají uživatelské profily, kontakty, skupiny a zprávy odesílané pomocí 2 vrstvého end-to-end šifrování.
- Soukromá oznámení
+ Pouze klientská zařízení ukládají uživatelské profily, kontakty, skupiny a zprávy odesílané pomocí 2 vrstvého koncového šifrování.
+ Oznámení
Pravidelné
Ignorovat
Hovor již skončil!
- videohovor
+ video hovor
audio hovor
Audio a video hovory
Hovory na uzamčené obrazovce:
@@ -272,7 +272,7 @@
NASTAVENÍ
NÁPOVĚDA
ZAŘÍZENÍ
- CHATS
+ CHATY
Experimentální funkce
SOCKS PROXY
IKONA APLIKACE
@@ -281,11 +281,11 @@
VOLÁNÍ
Export databáze
Import databáze
- Smazání databáze
- Chyba při exportu chat databáze
+ Smazat databázi
+ Chyba exportu chat databáze
Import
Restartujte aplikaci, abyste mohli používat importovanou chat databázi.
- Smazat profil chatu\?
+ Smazat chat profil\?
Tuto akci nelze vzít zpět! Váš profil, kontakty, zprávy a soubory budou nenávratně ztraceny.
Restartujte aplikaci a vytvořte nový chat profil.
Nejnovější verzi chat databáze musíte používat POUZE v jednom zařízení, jinak se může stát, že přestanete přijímat zprávy od některých kontaktů.
@@ -293,24 +293,24 @@
Soubory a média
Smazat soubory a média\?
Odstranit zprávy
- Odstranit přístupovou frázi z úložiště klíčů\?
+ Odstranit frázi z úložiště klíčů\?
Oznámení budou doručována pouze do doby, než se aplikace zastaví!
Odstranit
Aktualizovat
- Aktuální přístupová fráze…
- Aktualizovat přístupovou frázi databáze
+ Aktuální fráze…
+ Aktualizovat přístupovou frázi
Zadejte prosím správnou aktuální přístupovou frázi.
- Váš chat databáze není šifrována - nastavte přístupovou frázi pro její ochranu.
- K bezpečnému uložení heslové fráze slouží úložiště klíčů Android - umožňuje fungování služby oznámení.
+ Chat databáze není šifrována - nastavte přístupovou frázi pro její ochranu.
+ K bezpečnému uložení přístupové fráze slouží úložiště klíčů Android - umožňuje fungování služby oznámení.
Upozornění: pokud přístupovou frázi ztratíte, NEBUDE možné ji obnovit ani změnit.
Databáze bude šifrována a přístupová fráze bude uložena v úložišti klíčů.
Heslo uložte bezpečně, v případě jeho ztráty jej NEBUDE možné změnit.
Soubor: %s
- Pro otevření chatu je vyžadována přístupová fráze databáze.
+ Pro otevření chatu je vyžadována přístupová fráze.
Neznámá chyba
Otevřete chat
Obnovte zálohu databáze
- Po obnovení zálohy databáze zadejte předchozí heslo. Tuto akci nelze vrátit zpět.
+ Po obnovení zálohy databáze zadejte předchozí frázi. Tuto akci nelze vrátit zpět.
Chat je zastaven
Chat se archivuje
Smazat chat archiv\?
@@ -327,7 +327,7 @@
změnila se vaše adresa
Rozšířit výběr rolí
Nelze pozvat kontakt!
- Již máte profil chatu se stejným názvem. Zvolte prosím jiné jméno.
+ Již máte chat profil se stejným názvem. Zvolte prosím jiné jméno.
Vytvořit frontu
Zabezpečit frontu
Okamžitá oznámení!
@@ -360,7 +360,7 @@
špatný kontrolní součet zprávy
Chat databáze importována
Nová přístupová fráze…
- Uložte heslo a otevřete chat
+ Uložte frázi a otevřete chat
CHAT ARCHIV
Nebyl vybrán žádný kontakt
Snažíte se pozvat kontakt se kterým jste sdíleli inkognito profil, do skupiny ve které používáte svůj hlavní profil
@@ -385,9 +385,9 @@
Jasné ověření
Chcete-li ověřit koncové šifrování u svého kontaktu, porovnejte (nebo naskenujte) kód na svých zařízeních.
Vaše nastavení
- Vaše kontaktní adresa SimpleX
+ Vaše adresa SimpleX
Heslo databáze a export
- Vaše chat profily
+ Chat profily
Zaslat otázky a nápady
Test serveru
Servery ICE (jeden na řádek)
@@ -406,14 +406,14 @@
Zprávy SimpleX Chat
Zobrazení náhledu
Náhled oznámení
- Spustí se při otevření aplikace
+ Spuštěna při otevřené aplikaci
Spouští se pravidelně
- Vždy zapnuto
+ Vždy zapnuta
Každých 10 minut kontroluje nové zprávy, po dobu až 1 minuty
- Zobrazit pouze kontakt
+ Zobrazí pouze kontakt
Skrytý kontakt:
Zapněte funkci
- Zapnutý zámek SimpleX Lock
+ Zámek SimpleX zapnut
Odemknout
Ověřování zařízení není povoleno. Jakmile povolíte ověřování zařízení, můžete zámek SimpleX Lock zapnout prostřednictvím Nastavení.
Ověřování zařízení je zakázáno. Zámek SimpleX je vypnut.
@@ -439,7 +439,7 @@
Hlasové zprávy jsou zakázány!
Prosím, požádejte kontaktní osobu, aby umožnila odesílání hlasových zpráv.
Poslat živou zprávu - zpráva se bude aktualizovat pro příjemce během psaní.
- Vytvořte jednorázový odkaz na pozvánku
+ Vytvořit jednorázovou pozvánku
Skenovat QR kód
( skenovat nebo vložit ze schránky)
Upravit obrázek
@@ -453,7 +453,7 @@
kontakt má šifrování e2e
kontakt nemá šifrování e2e
peer-to-peer
- přes relay
+ přes relé
Zavěsit
Video vypnuto
Video zapnuto
@@ -463,14 +463,14 @@
Přeskočené zprávy
Ochrana osobních údajů a zabezpečení
Vaše soukromí
- Ochrana obrazovky aplikace
- Odesílání náhledů odkazů
- Zálohování dat aplikace
- Potvrdit novou heslovou frázi…
+ Skrývat aplikaci
+ Odesílat náhledy odkazů
+ Zálohovat data aplikace
+ Potvrdit frázi…
Chyba: %s
Opustit skupinu\?
Skupina je neaktivní
- vlevo
+ odešel
Vyčistit
Přepnout
Role bude změněna na \"%s\". Všichni ve skupině budou informováni.
@@ -479,7 +479,7 @@
vteřin
Umožňuje mít v jednom chat profilu mnoho anonymních spojení bez sdílení údajů mezi nimi.
Pokud s někým sdílíte inkognito profil, bude použit pro skupiny, do kterých vás pozve.
- Systém
+ Systémové
Hlasové zprávy
Vy i váš kontakt můžete nevratně mazat odeslané zprávy.
%dm
@@ -518,10 +518,10 @@
ŽIVĚ
inkognito přes skupinový odkaz
Ujistěte se, že adresy serverů SMP jsou ve správném formátu, oddělené na řádcích a nejsou duplicitní.
- Chyba při vytváření profilu!
+ Chyba vytváření profilu!
Duplicitní zobrazované jméno!
- Chyba při přepínání profilu!
- Chyba při připojování ke skupině
+ Chyba přepínání profilu!
+ Chyba připojování ke skupině
Nelze přijmout soubor
Odesílatel zrušil přenos souboru.
Chyba příjmu souboru
@@ -544,12 +544,12 @@
Pravidelná oznámení jsou vypnuta!
Databáze nefunguje správně. Klepnutím se dozvíte více
Aplikace může přijímat oznámení pouze při svém běhu, žádná služba na pozadí nebude spuštěna
- Skrýt kontakt a zprávu
+ Skryje kontakt i zprávu
Chcete-li chránit své informace, zapněte zámek SimpleX Lock.
\nPřed zapnutím této funkce budete vyzváni k dokončení ověření.
- Při spuštění nebo obnovení aplikace po 30 sekundách na pozadí budete vyzváni k ověření.
+ Při spuštění, nebo obnovení aplikace po 30 sekundách, budete vyzváni k odemčení.
Vypnout zámek SimpleX
- Potvrďte své pověření
+ Potvrďte vzor
Ověřování není k dispozici
Zastavit chat
Otevřete chat konzoli
@@ -565,7 +565,7 @@
neautorizované odeslání
jste pozváni do skupiny
připojit jako %s
- připojuje se…
+ připojuji…
Začněte nový chat
Chat s vývojáři
Nemáte žádné konverzace
@@ -585,25 +585,25 @@
Živá zpráva!
Připojit se prostřednictvím odkazu / QR kódu
Děkujeme za instalaci SimpleX Chat!
- Můžete se připojit k SimpleX Chat vývojářům a položit jim případné dotazy a získat aktualizace.
+ Můžete se připojit k SimpleX Chat vývojářům, položit jim případné dotazy a získat aktualizace.
Připojení prostřednictvím odkazu
- Pokud jste dostali SimpleX Chat zvací odkaz, Můžete ho otevřít v prohlížeči:
+ Pokud jste dostali SimpleX Chat pozvánku, můžete ji otevřít v prohlížeči:
Pokud zvolíte odmítnutí, odesílatel NEBUDE upozorněn.
- 📱 mobilní telefon: tap Otevřete v mobilní aplikaci, potom klikněte Připojit.
+ 📱 telefon: Otevřete v mobilní aplikaci, potom klikněte na Připojit.
Odmítnout
Smazat chat
Vyčistit
Smazat
Nastavit jméno kontaktu
Přijali jste spojení
- Aby se připojení dokončilo, musí být váš kontakt online.
+ K dokončení připojení, musí být váš kontakt online.
\nToto připojení můžete zrušit a kontakt odebrat (a zkusit to později s novým odkazem).
Chce se s vámi spojit!
Zástupný symbol profilového obrázku
profilový obrázek
Tlačítko Zavřít
Kontakt ještě není připojen!
- náhledový obrázek odkazu
+ náhled odkazu
Zrušit náhled odkazu
SimpleX Logo
E-mail
@@ -634,7 +634,7 @@
Použít SimpleX Chat servery\?
Použití SimpleX Chat serverů.
Uložené servery WebRTC ICE budou odstraněny.
- Chyba při ukládání serverů ICE
+ Chyba ukládání serverů ICE
Ujistěte se, že adresy serverů WebRTC ICE jsou ve správném formátu, oddělené na řádcích a nejsou duplicitní.
Uložit
Síť a servery
@@ -655,7 +655,7 @@
Verze aplikace
Verze aplikace: v%s
Verze jádra: v%s
- Jádro sestaveno na: %s
+ Jádro sestaveno: %s
Smazat adresu\?
Všechny vaše kontakty zůstanou připojeny.
Žádosti o kontakt
@@ -709,18 +709,20 @@
zvukový hovor (nešifrováno e2e)
Odmítnout
Vaše hovory
- Spojení přes relay
+ Spojení přes relé
Zobrazit
Zakázat
Vaše servery ICE
WebRTC servery ICE
+ Přenosový server chrání vaši IP adresu, ale může sledovat dobu trvání hovoru.
+ Přenosový server se používá pouze v případě potřeby. Jiná strana může sledovat vaši IP adresu.
bez šifrování e2e
- Otočit foťák
+ Druhý foťák
Čekající hovor
Zmeškaný hovor
Odmítnutý hovor
Spojování hovoru
- Ukončený hovor
+ Skončený hovor
Přijmout hovor
%1$d přeskočená zpráva (zprávy)
Může se to stát, když:
@@ -731,31 +733,31 @@
\nBudeme přidávat redundantní servery, abychom zabránili ztrátě zpráv.
VY
PODPOŘIT SIMPLEX CHAT
- ROZVÍJET
+ VÝVOJ
Nástroje pro vývojáře
- Inkognito mód
- Vaše chat databáze
+ inkognito mód
+ Chat databáze
SPUSTIT CHAT
Chat je spuštěn
Chat je zastaven
CHAT DATABÁZE
- Heslo databáze
+ přístupová fráze databáze
Archiv nové databáze
Archiv staré databáze
- Chyba při spuštění chatu
+ Chyba spouštění chatu
Zastavit chat\?
Zastavte chat pro export, import nebo smazání chat databáze. Během zastavení, nebudete moci přijímat ani odesílat zprávy.
Zastavit
- Nastavení přístupové fráze pro export
+ Nastavte přístupovou frázi pro export
Databáze je šifrována pomocí náhodné přístupové fráze. Před exportem ji změňte.
- Chyba při zastavení chatu
+ Chyba zastavování chatu
Importovat chat databázi\?
Vaše aktuální chat databáze bude SMAZÁNA a NAHRAZENA importovanou databází.
-\nTuto akci nelze vzít zpět - váš profily, kontakty, zprávy a soubory budou nenávratně ztraceny.
+\nTuto akci nelze vzít zpět - vaše profily, kontakty, zprávy a soubory budou nenávratně ztraceny.
Chyba mazání chat databáze
Chyba importu chat databáze
Chat databáze odstraněna
- Odstranění souborů pro všechny chat profily
+ Odstranit soubory všech chat profilů
Odstranit všechny soubory
Tuto akci nelze vrátit zpět - všechny přijaté a odeslané soubory a média budou smazány. Obrázky s nízkým rozlišením zůstanou zachovány.
Žádné přijaté ani odeslané soubory
@@ -768,11 +770,11 @@
Povolit automatické mazání zpráv\?
Tuto akci nelze vzít zpět - zprávy odeslané a přijaté dříve, než bylo zvoleno, budou smazány. Může to trvat několik minut.
Chyba změny nastavení
- Uložit přístupovou frázi do úložiště klíčů
+ Uložit frázi do úložiště klíčů
Databáze šifrována!
Chyba šifrování databáze
Šifrovat
- Databáze je šifrována pomocí náhodné přístupové fráze, můžete ji změnit.
+ Databáze je šifrována pomocí náhodné přístupové fráze, musíte ji změnit.
K bezpečnému uložení přístupové fráze se použije úložiště klíčů Android, po restartování aplikace nebo změně přístupové fráze - umožní přijímání oznámení.
Musíte zadat přístupovou frázi při každém spuštění aplikace - není uložena v zařízení.
Šifrovat databázi\?
@@ -786,7 +788,7 @@
Chyba databáze
Chyba klíčenky
Přístupová fráze databáze se liší od té uložené v klíčence.
- Nelze získat přístup k úložišti klíčů pro uložení přístupová fráze k databázi.
+ Nelze získat přístup ke klíčence pro uložení přístupová fráze k databázi.
Neznámá chyba databáze: %s
Špatná přístupová fráze!
Zadejte správnou přístupovou frázi.
@@ -795,13 +797,13 @@
Obnovit zálohu databáze\?
Obnovit
Chyba obnovení databáze
- Heslo nebylo nalezeno v úložišti klíčů, zadejte jej prosím ručně. K této situaci mohlo dojít, pokud jste obnovili data aplikace pomocí zálohovacího nástroje. Pokud tomu tak není, obraťte se na vývojáře.
- Chat můžete spustit přes Nastavení aplikace / Databáze nebo restartováním aplikace.
+ Heslo nebylo v klíčence nalezeno, zadejte jej prosím ručně. K této situaci mohlo dojít, pokud jste obnovili data aplikace pomocí zálohovacího nástroje. Pokud tomu tak není, obraťte se na vývojáře.
+ Chat můžete spustit v Nastavení / Databáze nebo restartováním aplikace.
Uložit archiv
Smazat archiv
pozvánka do skupiny %1$s
Vytvořeno dne %1$s
- Jste zváni do skupiny. Připojte se a spojte se s členy skupiny.
+ Jste zváni do skupiny. Připojte se k členům skupiny.
Připojit se inkognito
Připojit ke skupině
Připojili jste se k této skupině. Připojení k pozvání člena skupiny.
@@ -835,15 +837,15 @@
člen
vlastník
odstraněn
- vlevo
+ odešel
skupina smazána
- pozvánka
- připojující (zavedený)
+ pozván
+ představen (zaveden)
připojení (pozvánka na představení)
- připojení (přijato)
- připojení (oznámeno)
+ připojen (přijat)
+ připojen (oznámen)
připojen
- kompletní
+ komplet
tvůrce
připojování
Žádné kontakty k přidání
@@ -859,11 +861,11 @@
Smazat skupinu
Smazat skupinu\?
Skupina bude smazána pro všechny členy - nelze to vzít zpět!
- Skupina bude smazána pro vás - toto nelze vzít zpět!
+ Skupina bude smazána pouze pro vás - toto nelze vzít zpět!
Opustit skupinu
Upravit profil skupiny
- Odkaz na skupinu
- Vytvořit odkaz na skupinu
+ Odkaz skupiny
+ Vytvořit odkaz skupiny
Smazat odkaz
Můžete sdílet odkaz nebo QR kód - ke skupině se bude moci připojit kdokoli. O členy skupiny nepřijdete, pokud ji později odstraníte.
Chyba vytváření odkazu skupiny
@@ -899,44 +901,44 @@
Profil a připojení k serveru
Pouze místní data profilu
Náhodný profil bude zaslán kontaktu, od kterého jste obdrželi tento odkaz.
- Režim inkognito chrání soukromí vašeho hlavního profilového jména a obrázku - pro každý nový kontakt je vytvořen nový náhodný profil.
- Chcete-li najít profil použitý pro inkognito připojení, klepněte na název kontaktu nebo skupiny v horní části chatu.
- Světlý
- Tmavý
+ Režim inkognito chrání soukromí vašeho hlavního profilu, jména a obrázku - pro každý nový kontakt je vytvořen nový náhodný profil.
+ Chcete-li najít profil použitý pro inkognito připojení, klepněte na název kontaktu nebo skupiny v horní části.
+ Světlé
+ Tmavé
Téma
- Kontakt povolil
+ Kontakt povolen
zapnuto
vypnuto
Chat předvolby
Předvolby kontaktu
Předvolby skupiny
Přímé zprávy
- Smazat pro všechny
- povoleno
- povoleno pro vás
- vypnuto
- Zakázat zasílání mizejících zpráv.
+ Mazání všem
+ zapnuty
+ povoleno vám
+ vypnuty
+ Mizící zprávy zakázány.
Kontakty mohou označit zprávy ke smazání; vy je budete moci zobrazit.
- Zakázat odesílání hlasových zpráv.
- Mizející zprávy můžete odesílat pouze vy.
- Mizející zprávy jsou v tomto chatu zakázány.
+ Hlasové zprávy zakázány.
+ Pouze vy můžete odesílat mizící zprávy.
+ Mizící zprávy jsou v tomto chatu zakázány.
Nevratně mazat zprávy může pouze váš kontakt (vy je můžete označit ke smazání).
Hlasové zprávy můžete posílat vy i váš kontakt.
Hlasové zprávy můžete posílat pouze vy.
Hlasové zprávy může odesílat pouze váš kontakt.
Hlasové zprávy jsou v tomto chatu zakázány.
- Zakázat posílání mizejících zpráv.
- Zakázat nevratné mazání zpráv.
- Zakázat odesílání hlasových zpráv.
- Členové skupiny mohou posílat mizející zprávy.
- Mizející zprávy jsou v této skupině zakázány.
+ Posílání mizících zpráv zakázáno.
+ Nevratné mazání odeslaných zpráv zakázáno.
+ Hlasové zprávy zakázány.
+ Členové skupiny mohou posílat mizící zprávy.
+ Mizící zprávy jsou v této skupině zakázány.
Členové skupiny mohou posílat přímé zprávy.
Přímé zprávy mezi členy jsou v této skupině zakázány.
Členové skupiny mohou nevratně mazat odeslané zprávy.
Nevratné mazání zpráv je v této skupině zakázáno.
Členové skupiny mohou posílat hlasové zprávy.
Hlasové zprávy jsou v této skupině zakázány.
- Smazat po
+ Smazat za
%d měsíc
%d měsíců
%d den
@@ -947,9 +949,9 @@
nabízeno %s
zrušeno %s
Co je nového
- Novinky v %s
+ Novinky %s
Automatické přijímání žádostí o kontakt
- S volitelnou uvítací zprávou.
+ Volitelná uvítací zpráva.
Max. 40 sekund, přijímá se okamžitě.
Soukromé názvy souborů
Pro ochranu časového pásma, obrazové/hlasové soubory používají UTC.
@@ -957,14 +959,26 @@
Další vylepšení se chystají již brzy!
Italské rozhraní
Díky uživatelům - překládejte prostřednictvím Weblate!
- Budete připojeni, až bude zařízení vašeho kontaktu online, vyčkejte prosím nebo se podívejte později!
- Vaše kontaktní adresa
+ Budete připojeni, jakmile bude zařízení vašeho kontaktu online, vyčkejte prosím nebo se podívejte později!
+ Vaše adresa
Váš chat profil bude odeslán
\nvašemu kontaktu
Vaše chat profily jsou uloženy lokálně, pouze ve vašem zařízení.
Vaše konverzace
Do níže uvedeného pole vložte odkaz, který jste obdrželi pro spojení s kontaktem.
- Sdílet zvací odkaz
- end-to-end šifrované
+ Sdílet pozvánku
+ koncově šifrované
moderované
+ moderovaný %s
+ Smazat zprávu člena\?
+ moderovaný
+ Kontaktujte prosím správce skupiny.
+ jste pozorovatel
+ pozorovatel
+ Zpráva bude smazána pro všechny členy.
+ Zpráva bude pro všechny členy označena jako moderovaná.
+ Nemůžete posílat zprávy!
+ Chyba aktualizace odkazu skupiny
+ Počáteční role
+ Systém
\ No newline at end of file
diff --git a/apps/android/app/src/main/res/values-de/strings.xml b/apps/android/app/src/main/res/values-de/strings.xml
index 803fd5377..960c3b2ea 100644
--- a/apps/android/app/src/main/res/values-de/strings.xml
+++ b/apps/android/app/src/main/res/values-de/strings.xml
@@ -513,13 +513,15 @@
Audio- & Videoanrufe
Ihre Anrufe
- Über ein Relais verbinden
+ Über ein Relais verbinden
Anrufe auf Sperrbildschirm:
Akzeptieren
Anzeigen
Deaktivieren
Ihre ICE-Server
WebRTC ICE-Server
+ Relais-Server schützen Ihre IP-Adresse, aber sie können die Anrufdauer erfassen.
+ Relais-Server werden nur genutzt, wenn sie benötigt werden. Ihre IP-Adresse kann von Anderen erfasst werden.
Öffnen Sie SimpleX Chat, um den Anruf anzunehmen.
Aktivieren Sie Anrufe vom Sperrbildschirm über die Einstellungen.
@@ -1041,4 +1043,16 @@
Dank der Nutzer - Tragen Sie per Weblate bei!
Bild- und Sprachdateinamen enthalten UTC, um Informationen zur Zeitzone zu schützen.
Moderiert
+ Von %s moderiert
+ Moderieren
+ Diese Nachricht wird für alle Mitglieder als moderiert gekennzeichnet.
+ Sie sind Beobachter
+ Sie können keine Nachrichten versenden!
+ Beobachter
+ Anfängliche Rolle
+ Nachricht des Mitglieds löschen\?
+ Fehler beim Aktualisieren des Gruppen-Links
+ Bitte kontaktieren Sie den Gruppen-Administrator.
+ Diese Nachricht wird für alle Gruppenmitglieder gelöscht.
+ System
\ No newline at end of file
diff --git a/apps/android/app/src/main/res/values-es/strings.xml b/apps/android/app/src/main/res/values-es/strings.xml
index 328e46183..6eda6906e 100644
--- a/apps/android/app/src/main/res/values-es/strings.xml
+++ b/apps/android/app/src/main/res/values-es/strings.xml
@@ -3,7 +3,7 @@
Autenticación no disponible
Aceptar
Configuración de red avanzada
- Mejor para la batería. Recibirás notificaciones sólo cuando la aplicación se esté ejecutando, NO se utilizará el servicio en segundo plano.
+ Mejor para la batería. Recibirás notificaciones sólo cuando la aplicación se esté ejecutando, el servicio NO se usará en segundo plano.
Bueno para la batería. El servicio en segundo plano comprueba si hay mensajes nuevos cada 10 minutos. Puedes perderte llamadas y mensajes urgentes.
Aceptar
Copia de seguridad de los datos de la aplicación
@@ -14,34 +14,34 @@
Añadir servidores escaneando códigos QR.
Añadir servidores predefinidos
Todos los miembros del grupo permanecerán conectados.
- Permitir el borrado irreversible de mensajes sólo si su contacto se lo permite.
- Android Keystore se utilizará para almacenar de forma segura la frase de contraseña después de reiniciar la aplicación o cambiar la frase de contraseña - permitirá recibir notificaciones.
+ Permitir la eliminación irreversible de mensajes sólo si tu contacto también lo permite para tí.
+ Android Keystore se usará para almacenar de forma segura la frase de contraseña después de reiniciar la aplicación o cambiar la frase de contraseña - permitirá recibir notificaciones.
Permitir a tus contactos enviar mensajes que desaparecen.
Permitir a tus contactos enviar mensajes de voz.
siempre
La aplicación sólo puede recibir notificaciones cuando se está ejecutando, no se iniciará ningún servicio en segundo plano.
ICONO DE LA APLICACIÓN
Se enviará un perfil aleatorio al contacto del que recibió este enlace
- La optimización de la batería está activa, desactivando el servicio en segundo plano y las solicitudes periódicas de nuevos mensajes. Puedes volver a activarlos a través de los ajustes.
+ La optimización de la batería está activa, desactivando el servicio en segundo plano y las solicitudes periódicas de nuevos mensajes. Puedes volver a activarlos en Configuración.
El servicio en segundo plano está siempre en funcionamiento – las notificaciones se mostrarán en cuanto los mensajes estén disponibles.
- Se puede desactivar a través de los ajustes – las notificaciones se seguirán mostrando mientras la app esté en funcionamiento.
+ Se puede desactivar en Configuración – las notificaciones se seguirán mostrando mientras la app esté en funcionamiento.
Siempre activo
Permitir
arriba, entonces:
Añadir nuevo contacto: para crear tu código QR de un solo uso para tu contacto.
- ¿Conectar mediante enlace de contacto\?
+ ¿Aceptar solicitud de conexión\?
Aceptar incógnito
- Se borrarán todos los mensajes - ¡esto no se puede deshacer! Los mensajes se borrarán SOLO para ti.
+ Se eliminarán todos los mensajes SOLO para tí. ¡No puede deshacerse!
Añadir servidor…
¿Acceder a los servidores a través del proxy SOCKS en el puerto 9050\? El proxy debe iniciarse antes de activar esta opción.
Todos tus contactos permanecerán conectados.
Apariencia
- Versión de la aplicación
- Se utilizará una conexión TCP independiente (y una credencial SOCKS) para cada perfil de chat que tengas en la aplicación.
- Se utilizará una conexión TCP independiente (y una credencial SOCKS) para cada contacto y miembro del grupo.
+ Versión
+ Se usará una conexión TCP independiente (y una credencial SOCKS) para cada perfil de chat que tengas en la aplicación.
+ Se usará una conexión TCP independiente (y una credencial SOCKS) para cada contacto y miembro del grupo.
\nTen en cuenta: si tienes muchas conexiones, tu consumo de batería y tráfico puede ser sustancialmente mayor y algunas conexiones pueden fallar.
a + b
- Sobre SimpleX
+ Acerca de SimpleX
negrita
llamada aceptada
Aceptar
@@ -52,26 +52,26 @@
Audio activado
ID de mensaje erróneo
Aceptar automáticamente imágenes
- Se borrarán todos los chats y mensajes - ¡esto no se puede deshacer!
+ Se eliminarán todos los chats y mensajes. ¡No puede deshacerse!
Aceptar
- Permitir enviar mensajes que desaparecen.
+ Permitir enviar mensajes temporales.
Android Keystore se utiliza para almacenar de forma segura la frase de contraseña - permite que el servicio de notificación funcione.
Añadir perfil
Se enviará un perfil aleatorio a tu contacto
Acento
- Permitir a tus contactos borrar irreversiblemente los mensajes enviados.
- Permitir mensajes de voz sólo si tu contacto lo permite.
+ Permitir a tus contactos eliminar irreversiblemente los mensajes enviados.
+ Permitir mensajes de voz sólo si tu contacto los permite.
Permitir el envío de mensajes directos a los miembros.
- Permitir borrar irreversiblemente los mensajes enviados.
+ Permitir la eliminación irreversible de los mensajes enviados.
Permitir enviar mensajes de voz.
Los administradores pueden crear los enlaces para unirse a los grupos.
- Aceptar automáticamente solicitudes de contacto
+ Aceptar automáticamente solicitudes del contacto
Adjuntar
hash de mensaje erróneo
Responder llamada
- admin
+ administrador
¿Permitir mensajes de voz\?
- Atrás
+ Volver
Sobre SimpleX Chat
Añadir a otro dispositivo
Versión de la aplicación: v%s
@@ -81,7 +81,902 @@
Ten en cuenta: NO podrás recuperar o cambiar la contraseña si la pierdes.
Tanto tú como tu contacto podéis enviar mensajes de voz.
¡Usa más batería! El servicio en segundo plano está siempre en funcionamiento - las notificaciones se mostrarán tan pronto como los mensajes estén disponibles.
- Tanto tú como tu contacto podéis borrar de forma irreversible los mensajes enviados.
- Tanto tú como tu contacto podéis enviar mensajes de desaparición.
+ 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 que te muestre código QR.
+ Crear
+ Crear enlace único de invitación
+ Crear grupo secreto
+ La contraseña de cifrado de la base de datos será actualizada.
+ ID de la base de datos
+ Los mensajes directos entre miembros del grupo están prohibidos.
+ La contraseña es distinta a la almacenada en Keystore
+ La base de datos será cifrada y la contraseña se guardará en Keystore.
+ ¿Eliminar contacto\?
+ Eliminar mensaje\?
+ ¿Eliminar el perfil de chat\?
+ grupo eliminado
+ ¿Eliminar grupo\?
+ Eliminar mensaje después
+ Autenticación de dispositivo desactivada. Puedes habilitar SimpleX Lock en Configuración, después de activar la autenticación de dispositivo.
+ Desactivar
+ Los mensajes temporales están prohibidos en este chat.
+ Los mensajes temporales están prohibidos en este grupo.
+ El nombre mostrado no puede contener espacios en blanco.
+ Videollamada con cifrado e2e
+ conexión establecida
+ Descripción
+ Conectar
+ Conectado
+ Desactivar SimpleX Lock
+ Autenticación de dispositivo desactivada. SimpleX Lock deshabilitado.
+ El tamaño máximo de archivo admitido es %1$s
+ Eliminar verificación
+ Crear perfil
+ llamada con cifrado e2e
+ Cifrado e2e
+ mensaje duplicado
+ DESARROLLO
+ Herramientas desarrollo
+ Eliminar archivos para todos los perfiles de chat
+ Eliminar mensaje
+ ¡Base de datos cifrada!
+ La base de datos está cifrada con una contraseña aleatoria, puedes cambiarla.
+ Error en base de datos
+ Para abrir la aplicación se requiere la contraseña de la base de datos
+ conectado
+ Crear enlace
+ ¿Eliminar enlace\?
+ Mensajes temporales
+ Mensajes directos
+ %dm
+ %d mes
+ %d meses
+ %dmes
+ %dh
+ %d horas
+ %d semana
+ %d semanas
+ Mensajes temporales
+ Canfirma tus credenciales
+ conectando (presentado)
+ conectando (invitación de presentación )
+ conectando (aceptado)
+ conectando (anunciado)
+ conexión<xliff:g xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\" id=\"connection ID\" example=\"1\">
+ Conectar mediante enlace / Código QR
+ El contacto y todos los mensajes serán eliminados. ¡No puede deshacerse!
+ Contacto verificado
+ El contacto dispone de cifrado e2e
+ Desconectar
+ Nombre del contacto
+ Copiar
+ Crear tu perfil
+ Conectar mediante relay
+ La base de datos está cifrada con una contraseña aleatoria. Cámbiala antes de exportar.
+ %d archivo(s) con tamaño total de %s
+ ¿Activar eliminación automática de mensajes\?
+ Preferencias de contacto
+ %ds
+ Eliminar después
+ %d seg
+ El contácto ya existe
+ Error de conexión (Autenticación)
+ Eliminar para mí
+ Desconectado
+ Conectado
+ Copiado en portapapeles
+ Crear enlace único de invitación
+ 💻 PC: escanéa el código QR desde la app mediante Escanéo de código QR
+ Eliminar
+ Eliminar
+ ¡El contacto aun no se ha conectado!
+ ¿Eliminar conexion pendiente\?
+ Botón cerrar
+ ¡Solicitud de conexión enviada!
+ Conexión
+ conectando llamada…
+ Activar llamadas desde la pantalla de bloqueo en Configuración
+ La contraseña de cifrado de la base de datos será actualizada y almacenada en Keystore.
+ conectando
+ creador
+ %d min
+ Tiempo de conexión expirado
+ ¡Nombre mostrado duplicado!
+ Error de conexión
+ Crear cola
+ Eliminar cola
+ Contacto oculto:
+ Eliminar
+ Editar
+ ¿Eliminar el mensaje de miembro\?
+ editado
+ Error de decodificación
+ Crear dirección
+ Eliminar dirección
+ ¿Eliminar la dirección\?
+ Nombre mostrado:
+ conectando…
+ Descentralizado
+ La base de datos será cifrada.
+ ¿Eliminar archivo del chat\?
+ Crear enlace de grupo
+ Eliminar enlace
+ ¿Eliminar el perfil de chat\?
+ %d hora
+ %d día
+ Nombre y avatar diferentes, aislamiento de transporte.
+ Eliminar contacto
+ Eliminar servidor
+ Nombre Mostrado
+ conectado
+ DISPOSITIVO
+ Contraseña base de datos
+ Eliminar base de datos
+ Eliminar todos los archivos
+ Contraseña actual…
+ Eliminar grupo
+ Editar perfil de grupo
+ Conexión
+ Eliminar el perfil de chat para
+ Oscuro
+ %dd
+ %d días
+ ¿Eliminar archivos y multimedia\?
+ Creado: %1$s
+ Eliminar archivo
+ conectado
+ directo
+ El contacto permite
+ predefinido (%s)
+ Eliminar para todos
+ activado
+ El contacto puede marcar los mensajes para eliminar; tu podrás verlos.
+ %ds
+ eliminado
+ ¿Conectar mediante enlace de contacto\?
+ ¿Conectar mediante enlace de grupo\?
+ ¿Conectar mediante enlace de invitación\?
+ Conectar
+ conectado
+ conectando
+ conectando…
+ conectando…
+ conectando…
+ Icono contextual
+ El contacto no dispone de cifrado e2e
+ Conectando llamada
+ Crear grupo secreto
+ Email
+ Conectar
+ Conectar mediante enlace
+ Contraseña y exportar la base de datos
+ Contribuye
+ Core compilado: %s
+ Core versión: v%s
+ Solicitud del contacto
+ Eliminar imagen
+ Editar imagen
+ CHATS
+ Cambiar
+ Comprueba mensajes nuevos de hasta un minuto cada 10 minutos
+ Limpiar
+ ¿Cambiar rol de grupo\?
+ Compara los códigos de seguridad con tus contactos
+ Elije archivo
+ Limpiar
+ Limpiar chat
+ Configurar servidores ICE
+ Llamada terminada %1$s
+ error en la llamada
+ llamando
+ llamada en curso
+ coloreado
+ su rol a cambiado a %s
+ cambiando dirección por %s
+ completado
+ ¡No se puede invitar el contacto!
+ No se puede recibir el archivo
+ No se puede iniciar la base de datos
+ Limpiar chat\?
+ Perfil de Chat
+ El chat está detenido
+ rol de %s cambiado a %s
+ Cambiar rol
+ A través del perfil de chat (por defecto) or a través de conexión (BETA)
+ cambiando dirección…
+ Preferencias de chat
+ cancelado %s
+ El chat está detenido
+ LLAMADAS
+ El chat está en ejecución
+ cambiando dirección…
+ habla con los desarrolladores
+ Cancelar vista previa del archivo
+ Cancelar vista previa de la imagen
+ Llamada terminada
+ Llamada en curso
+ ¿Cambiar contraseña de la base de datos\?
+ No se puede acceder a Keystore para guardar la base de datos de contraseñas
+ Archivo del chat
+ ARCHIVOS DE CHAT
+ Cancelar
+ Cancelar mensaje en directo
+ Confirmar
+ Limpiar
+ Build de la aplicación
+ ¡La llamada ha terminado!
+ su dirección ha cambiado para tí
+ cancelar vista previa del enlace
+ Llamadas en la ventana de bloqueo
+ ¡No se puede invitar a los contactos!
+ Consola del chat
+ BASE DE DATOS DE CHAT
+ Base de datos eliminada
+ Base de datos importada
+ Comprueba la dirección del servidor e inténtalo de nuevo.
+ Confirma la contraseña nueva…
+ Si confirmas los servidores de mensajería podrán ver tu IP, y tu proveedor de acceso a internet a qué servidores te estás conectando.
+ Imagen guardada en la Galería
+ El archivo se recibirá cuando tu contacto esté en línea, por favor espera o compruébalo más tarde.
+ Enlace único de invitación
+ Pegar enlace recibido
+ Error guardando perfil de grupo
+ Salir sin guardar
+ Archivo guardado
+ Voltear la cámara
+ Invitación de grupo caducada
+ La invitación al grupo ya no es válida, ha sido eliminada por el remitente.
+ El grupo se eliminará para tí. ¡No puede deshacerse!
+ Cómo usar el marcador
+ Incógnito mediante enlace único
+ Dirección de contacto SimpleX
+ Error guardando servidores SMP
+ Abrir el enlace en el navegador puede reducir la privacidad y seguridad de la conexión. Los enlaces SimpleX que no son de confianza aparecerán en rojo.
+ Error al actualizar la configuración de red
+ Error creando dirección
+ Error eliminando perfil de usuario
+ Activar SimpleX Lock
+ Enlace único de invitación
+ Servidores SMP
+ Características experimentales
+ Error importando la base de datos
+ Error cambiando configuración
+ Archivo: %s
+ ¡Error cambiando perfil!
+ Introduce el servidor manualmente
+ Cómo usar tus servidores
+ Error deteniendo el chat
+ Introduce la contraseña correcta.
+ Introduce la contraseña…
+ Grupo inactivo
+ grupo eliminado
+ Los miembros del grupo pueden enviar mensajes temporales.
+ Enlaces de grupo
+ Enlace de conexión no válido
+ Error aceptando la solicitud del contacto
+ Error cambiando la dirección
+ Error al guardar archivo
+ Error
+ De la Galería
+ Si has recibido el enlace de invitación a SimpleX Chat, puedes abrirlo en tu navegador:
+ Si eliges rechazar, el remitente NO será notificado.
+ ¡Enlace no válido!
+ Si no puedes reunirte en persona, puedes escanear el código QR en la videollamada, o tu contacto puede compartir un enlace de invitación.
+ Si no puedes reunirte en persona, muestra el código QR en la videollamada, o comparte el enlace.
+ Cómo
+ Ignorar
+ Error eliminando la base de datos
+ Base de datos cifrada
+ Error eliminando miembro
+ Los miembros del grupo pueden mandar mensajes de voz.
+ Incógnito mediante enlace de dirección del contacto
+ Error creando perfil!
+ No se pudo cargar el chat
+ No se pudieron cargar los chats
+ Enlace completo
+ Error eliminando contacto
+ Error uniéndose al grupo
+ Error recibiendo archivo
+ Comprueba que has usado el enlace correcto o pide a tu contacto que te envíe otro.
+ Error eliminando la solicitud de contacto
+ Error al eliminar grupo
+ Error al eliminar conexión de contacto pendiente
+ Ocultar
+ Oculto
+ Ocultar contacto y mensaje
+ Ocultar
+ Para todos
+ Archivo
+ Imagen enviada
+ La imagen se recibirá cuando tu contacto esté en línea, por favor espera o compruébalo más tarde.
+ vista previa del enlace
+ Error guardando servidores ICE
+ Servidores ICE (uno por línea)
+ Nombre completo:
+ Las personas pueden conectarse contigo solo mediante los enlaces que compartes.
+ Cómo funciona SimpleX
+ Colgar
+ Archivo y multimedia
+ ¡Grupo no encontrado!
+ perfil de grupo actualizado
+ Error al crear enlace de grupo
+ Error al eliminar enlace de grupo
+ activado para el contacto
+ Interfaz en francés
+ Imagen
+ Archivo no encontrado
+ Cómo usar
+ Nombre completo (opcional)
+ finalizado
+ AYUDA
+ Exportar base de datos
+ Error exportando la base de datos
+ Error iniciando chat
+ Invitado mediante tu enlace de grupo
+ Error actualizando el enlace de grupo
+ PARA CONSOLA
+ Error cambiando rol
+ SERVIDORES
+ Nombre mostrado del grupo:
+ Preferencias del grupo
+ Los miembros del grupo pueden enviar mensajes directos.
+ Los miembros del grupo pueden eliminar mensajes de forma irreversible.
+ Ocultar pantalla de aplicaciones en aplicaciones recientes.
+ Cifrar
+ Ampliar la selección de roles
+ El grupo se eliminará para todos los miembros. ¡No puede deshacerse!
+ Activar TCP keep-alive
+ activado para tí
+ error
+ Incógnito mediante enlace de grupo
+ Error al añadir miembro(s)
+ Error enviando mensaje
+ Error cifrando la base de datos
+ ¿Cifrar base de datos\?
+ Error: %s
+ Enlace de grupo
+ Grupo
+ Nombre completo del grupo:
+ El perfil de grupo se almacena en los dispositivos, no en los servidores.
+ ayuda
+ Pega el enlace que has recibido en el recuadro para conectar con tu contacto.
+ Compartir enlace
+ Cómo funciona
+ El mensaje se eliminará. ¡No puede deshacerse!
+ El modo incógnito protege la identidad del perfil principal, por cada contacto nuevo se genera un perfil nuevo aleatorio.
+ Para poder usarse deshabilita la optimización de batería para SimpleX en el siguiente cuadro de diálogo. De lo contrario las notificaciones estarán desactivadas.
+ Instalar SimpleX Chat para terminal
+ invitación al grupo %1$s
+ invitado %1$s
+ Permite tener muchas conexiones anónimas sin datos compartidos entre estas en un único perfil de chat.
+ Invitar al grupo
+ Para comprobar el cifrado de extremo a extremo con su contacto compare (o escanee) el código en sus dispositivos.
+ La base de datos no está cifrada. Establece una contraseña para protegerla.
+ Asegúrate de que las direcciones del servidor SMP tienen el formato correcto, están separadas por líneas y no duplicadas.
+ Notificación instantánea
+ Configuración de red
+ No se usarán hosts .onion
+ Sólo los dispositivos cliente almacenan perfiles de usuario, contactos, grupos y mensajes enviados con cifrado de extremo a extremo de 2 capas .
+ Puede cambiarse más tarde en Configuración.
+ Instantánea
+ Únete
+ Únete en modo incógnito
+ indirecto (%1$s)
+ Claro
+ Activado
+ La eliminación irreversible de mensajes está prohibida en este chat.
+ La eliminación irreversible de mensajes está prohibida en este grupo.
+ Configuración del servidor mejorada
+ Esto puede suceder cuando:
+\n1. Los mensajes caducan en el servidor si no se han recibido durante 30 días.
+\n2. El servidor que utiliza para recibir los mensajes de este contacto fue actualizado y reiniciado.
+\n3. La conexión está comprometida.
+\nPor favor, contacta con los desarrolladores a través del menú Configuración para recibir actualizaciones sobre los servidores.
+\nAñadiremos redundancia de servidores para evitar la pérdida de mensajes.
+ Texto del mensaje
+ MIEMBRO
+ nunca
+ Se requieren hosts .onion para la conexión
+ No se usarán hosts .onion
+ Vista previa de notificación
+ ¡Invitación caducada!
+ ha salido
+ Mensajes en vivo
+ Notificación instantánea
+ Servicio de notificaciones
+ El mensaje se marcará para eliminar. El destinatario o destinatarios podrán revelar este mensaje.
+ ¡Mensaje en vivo!
+ 📱 móvil: pulse Abrir en aplicación móvil, después pulse Conectar en la aplicación.
+ Marcar como leído
+ Marcar como no leído
+ Código QR inválido
+ ¡Código de seguridad incorrecto!
+ Marcadores en mensajes
+ No
+ llamada perdida
+ Importar
+ has cambiado tu rol a %s
+ Invitar miembros
+ Ningún contacto seleccionado
+ ofrecido %s
+ ofrecido %s: %2s
+ moderado por %s
+ chat no válido
+ datos no válidos
+ formato de mensaje no válido
+ EN VIVO
+ moderado
+ invitado a conectarse
+ ¡Las notificaciones instantáneas están desactivadas!
+ mensaje nuevo
+ Nueva solicitud de contacto
+ Inicie sesión con sus credenciales
+ Error en la entrega del mensaje
+ Lo más probable es que este contacto haya eliminado la conexión contigo.
+ Moderar
+ unirse como %s
+ Sólo se pueden enviar 10 imágenes al mismo tiempo
+ ¡Archivo grande!
+ Silenciar
+ Asegúrate de que las direcciones del servidor WebRTC ICE tienen el formato correcto, están separadas por líneas y no duplicadas.
+ Se requieren hosts .onion para la conexión
+ Se usarán hosts .onion cuando estén disponibles.
+ Inmune a spam y abuso
+ Muchos se preguntarán: si SimpleX no tiene identificadores de usuario, ¿cómo puede entregar los mensajes\?
+ Videollamada entrante
+ has salido
+ has cambiado la dirección
+ apagado
+ Eliminación del mensaje irreversible
+ Máximo 40 segundos, recibido al instante.
+ Interfaz en italiano
+ Borrador de mensaje
+ ¡Pronto habrá más mejoras!
+ Múltiples perfiles de chat
+ Notificaciones
+ sin detalles
+ OK
+ (sólo almacenado por miembros del grupo)
+ Ayuda marcadores
+ Redes y servidores
+ Se usarán hosts .onion cuando estén disponibles.
+ cursiva
+ Llamada entrante
+ videollamada (sin cifrado e2e)
+ sin cifrado e2e
+ Importar base de datos
+ MENSAJES
+ ¿Importar base de datos\?
+ Sin archivos recibidos o enviados
+ Mensajes
+ Contraseña nueva…
+ ¿Unirse al grupo\?
+ Únete al grupo
+ Error en Keystore
+ Invitar miembros
+ observador
+ miembro
+ eliminado
+ invitado
+ Sin contactos que añadir
+ Nuevo rol de miembro
+ Rol inicial
+ Nombre local
+ Estado de la red
+ Incógnito
+ apagado
+ Nuevo en %s
+ Seguridad y privacidad mejoradas
+ Modo incógnito
+ Nuevo archivo de base de datos
+ Archivo de base de datos antiguo
+ has cambiado la dirección por %s
+ ha salido
+ Salir del grupo
+ Sólo los propietarios del grupo pueden cambiar las preferencias de grupo.
+ Sólo datos del perfil local
+ no
+ k
+ marcado eliminado
+ Llamada perdida
+ ¡Las notificaciones seguirán enviándose hasta que la aplicación se detenga!
+ Salir
+ ¿Salir del grupo\?
+ propietario
+ El miembro será eliminado del grupo. ¡No puede deshacerse!
+ Sólo los propietarios del grupo pueden activar los mensajes de voz.
+ El modo incógnito no se admite aquí, tu perfil principal aparecerá en miembros del grupo
+ Más
+ Marcar como verificado
+ ¡Dirección de servidor no válida!
+ Establecer una conexión privada
+ Comprueba su conexión de red con %1$s e inténtelo de nuevo.
+ El remitente puede haber eliminado la solicitud de conexión.
+ Posiblemente la huella digital del certificado en la dirección del servidor es incorrecta
+ Responder
+ Guardar contraseña en Keystore
+ Error al restaurar la base de datos
+ Seleccionar contactos
+ Guardar perfil de grupo
+ Restablecer colores
+ Sólo tú puedes enviar mensajes temporales
+ Sólo tu contacto puede enviar mensajes temporales.
+ Prohibir el envío de mensajes de voz.
+ Se ejecuta cuando la aplicación está abierta
+ Abrir la consola de chat
+ Guardar
+ Restaurar
+ Guardar
+ Prohibir la eliminación irreversible de mensajes.
+ Los destinatarios ven la actualizacion mientras escribes.
+ Enviar Mensaje
+ ¡Permiso denegado!
+ Escanear código
+ Se eliminarán los servidores WebRTC ICE guardados.
+ llamada rechazada
+ secreto
+ Abrir SimpleX Chat para aceptar llamada
+ Restablecer valores por defecto
+ Guardar color
+ Pendiente
+ Notificaciones periódicas
+ Guarda la contraseña de forma segura, NO podrás cambiarla si la pierdes.
+ Reinicia la aplicación para crear un nuevo perfil de chat.
+ ¿Restaurar copia de seguridad de la base de datos\?
+ Notificaciones privadas
+ imagen del perfil
+ Prohibir el envío de mensajes de voz.
+ Proteger la pantalla de la aplicación
+ Más información en nuestro repositorio GitHub .
+ Grabar mensaje de voz
+ eliminado %1$s
+ Enviar previsualizaciones de enlaces
+ Envía un mensaje en vivo: se actualizará para el(los) destinatario(s) a medida que se escribe
+ error de envío
+ Enviando mediante
+ Actualiza la aplicación y ponte en contacto con los desarrolladores.
+ El remitente ha cancelado la transferencia de archivos.
+ Cola segura
+ Se necesita contraseña
+ Notificaciones periódicas desactivadas
+ Recibiendo mensajes…
+ Revelar
+ enviado
+ Rechazar
+ Obligatorio
+ Guardar y notificar contactos
+ Protocolo y código abiertos: cualquiera puede usar los servidores.
+ Rol
+ Revertir
+ Intervalo PING
+ Contador PING
+ Sólo tu contacto puede eliminar mensajes de forma irreversible (tu puedes marcar para eliminar).
+ Conserva el último borrador del mensaje con los datos adjuntos.
+ Nombres de archivos privados
+ Reducción del uso de la batería
+ Póngase en contacto con el administrador del grupo.
+ Solicita que tu contacto habilite el envío de mensajes de voz.
+ Enviar mensaje en vivo
+ Escanear código QR
+ Enviar
+ (escanear o pegar desde el portapapeles)
+ Espacio reservado para la imagen del perfil
+ Código QR
+ Envía consultas e ideas
+ Dirección del servidor predefinida
+ Contacta por email
+ Valora la aplicación
+ Guardar
+ respuesta recibida…
+ confirmación recibida…
+ Periódico
+ Privacidad redefinida
+ Más información en nuestro repositorio GitHub.
+ Rechazar
+ Abrir
+ Llamada pendiente
+ Privacidad y seguridad
+ Guarda la contraseña de forma segura, NO podrás acceder al chat si la pierdes.
+ Guardar archivo
+ Introduce la contraseña anterior después de restaurar la copia de seguridad de la base de datos. Esta acción no se puede deshacer.
+ te ha eliminado
+ Recibiendo mediante
+ Tiempo de espera del protocolo
+ seg
+ Perfil y conexiones de servidor
+ Prohibir el envío de mensajes temporales
+ Sólo tú puedes enviar mensajes de voz.
+ Sólo tu contacto puede enviar mensajes de voz.
+ EJECUTAR CHAT
+ Reinicia la aplicación para poder usar la base de datos importada.
+ Introduce la contraseña actual correcta.
+ recepción prohibida
+ Sólo tú puedes eliminar mensajes de forma irreversible (tu contacto puede marcar para eliminar).
+ Prohibir el envío de mensajes directos a los miembros.
+ Prohibir el envío de mensajes temporales
+ Evaluación de la seguridad
+ la recepción de archivos aún no está disponible
+ el envío de archivos aún no está disponible
+ entre particulares
+ Llamada rechazada
+ Eliminar
+ ¿Eliminar contraseña de Keystore\?
+ Abrir chat
+ Restaurar copia de seguridad de la base de datos
+ Guardar contraseña y abrir el chat
+ La contraseña no se ha encontrado en Keystore, introdúzcala manualmente. Esto puede haber ocurrido si has restaurado los datos de la aplicación con una herramienta de copia de seguridad. Si no es así, ponte en contacto con los desarrolladores.
+ Eliminar
+ Eliminar miembro
+ Enviar mensaje directo
+ Restablecer
+ Pegar
+ Código de seguridad
+ Escanea el código de seguridad desde la aplicación de tu contacto.
+ Guardar servidores
+ Escanear código QR del servidor
+ Servidor predefinido
+ Guardar y notificar contacto
+ ¿Guardar preferencias\?
+ 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 utilizado, podría tratarse de 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 utiliza 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.
+ Bloqueo SimpleX
+ Desbloquear
+ Este texto está disponible en Configuración
+ ¡Experimental! Sólo funcionará si el otro cliente tiene instalada la versión 4.2. Deberías ver el mensaje en la conversación una vez completado el cambio de dirección. Comprueba que puedes seguir recibiendo mensajes de este contacto (o miembro del grupo).
+ Bloqueo SimpleX
+ Usando servidores SimpleX Chat.
+ Aislamiento de transporte
+ tachado
+ Usar Chat
+ PROXY SOCKS
+ TEMAS
+ Detener
+ Esta acción no se puede deshacer. Tu perfil, contactos, mensajes y archivos se perderán irreversiblemente.
+ Omitir invitación a miembros
+ Mostrar vista previa
+ Activar
+ Compartir
+ envío no autorizado
+ Introduce el nombre del contacto
+ Usar proxy SOCKS (puerto 9050)
+ Error desconocido
+ El rol cambiará a \"%s\". Se notificará a todos los miembros del grupo.
+ La seguridad de SimpleX Chat fue auditada por Trail of Bits.
+ Los mensajes enviados se eliminarán una vez transcurrido el tiempo establecido.
+ Mensajes de chat SimpleX
+ no leído
+ Introduce el nombre del contacto…
+ ¿Cambiar dirección de recepción\?
+ Usar cámara
+ ¡El contacto con el que has compartido este enlace NO podrá conectarse!
+ Mostrar código QR
+ ¡El enlace no es un enlace de conexión válido!
+ ¡El código QR no es un enlace!
+ Compartir enlace de invitación
+ ¿Actualizar el modo de aislamiento de transporte\?
+ Altavoz activado
+ Detener Chat para habilitar acciones sobre la base de datos.
+ ¡La conexión que has aceptado se cancelará!
+ La base de datos no funciona correctamente. Pulsa para obtener más información
+ El mensaje será marcado como moderado para todos los miembros.
+ La próxima generación de mensajería privada
+ Esta acción no se puede deshacer. Se eliminarán todos los archivos y multimedia recibidos y enviados. Las imágenes de baja resolución permanecerán.
+ Esta acción no se puede deshacer. Se eliminarán los mensajes enviados y recibidos anteriores a la selección. Puede tardar varios minutos.
+ Esta configuración se aplica a los mensajes en su perfil actual de Chat
+ ¡Esta cadena no es un enlace de conexión!
+ Para preservar tu privacidad, en lugar de notificaciones automáticas la aplicación cuenta con un servicio en segundo planoSimpleX, utiliza un pequeño porcentaje de la batería al día.
+ Configuración
+ Altavoz apagado
+ Inciar chat nuevo
+ Detener Chat para exportar, importar o eliminar la base de datos del chat. No podrá recibir ni enviar mensajes mientras el chat esté detenido.
+ Gracias por instalar SimpleX Chat!
+ Para proteger la privacidad, en lugar de los identificadores de usuario que utilizan 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 SimpleX Lock.
+\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\?
+ Enlace de grupo SimpleX
+ Invitación única SimpleX
+ Enlaces SimpleX
+ El servidor requiere autorización para crear colas, comprueba la contraseña
+ Para recibir notificaciones, introduce la contraseña de la base de datos
+ Llamadas de chat SimpleX
+ Se inicia periódicamente
+ Mostrar contacto y mensaje
+ Mostrar sólo contacto
+ Bloqueo SimpleX activado
+ Detener chat
+ El mensaje se eliminará para todos los miembros.
+ Compartir archivo…
+ ¡Demasiadas imágenes!
+ La imagen no se puede decodificar. Pruebe otra imagen o pónte en contacto con los desarrolladores.
+ ¿Usa proxy SOCKS\?
+ Usar hosts .onion
+ simplexmq: v%s (%2s)
+ La plataforma de mensajería y aplicaciones que protege tu privacidad y seguridad.
+ La primera plataforma sin identificadores de usuario: diseñada para la privacidad.
+ Este grupo ya no existe.
+ Para encontrar el perfil usado en una conexión en modo incógnito, pulsa el nombre del contacto o del grupo en la parte superior del chat.
+ Establecer 1 día
+ Gracias a los usuarios: ¡contribuye a través de Weblate!
+ Gracias a los usuarios: ¡contribuye a través de Weblate!
+ Para proteger la zona horaria, los archivos de imagen/voz usan la hora UTC.
+ Aislamiento de transporte
+ (para compartir con tu contacto)
+ Activar audio
+ %s está verificado
+ %s no está verificado
+ Probar servidor
+ Probar servidores
+ Comienza en GitHub
+ Los servidores para nuevas conexiones de tu perfil de Chat actual
+ ¿Usar conexión directa a Internet\?
+ ¿Actualizar la configuración de los hosts .onion\?
+ El perfil sólo se comparte con tus contactos.
+ inicializando…
+ Mensajes omitidos
+ CONFIGURACIÓN
+ ¿Detener Chat\?
+ %s segundo(s)
+ Pulsa para unirse
+ perfil de grupo actualizado
+ Tiempo de espera de la conexión TCP agotado
+ Tema
+ Establecer preferencias de grupo
+ SOPORTE SIMPLEX CHAT
+ Seleccióna contraseña para exportar
+ Actualizar
+ Actualizar contraseña base de datos
+ Pulsa para unirte en modo incógnito
+ Cambiar
+ El rol cambiará a \"%s\". El miembro recibirá una nueva invitación.
+ Actualizar
+ ¿Actualizar la configuración de red\?
+ Intentando conectar con el servidor utilizado para recibir mensajes de este contacto.
+ formato de mensaje desconocido
+ Intentando conectar con el servidor utilizado para recibir mensajes de este contacto (error: %1$s ).
+ Prueba fallida en el paso %s.
+ Pulsa para iniciar chat nuevo
+ Compartir mensaje…
+ Compartir imagen…
+ Mostrar
+ Error desconocido en la base de datos: %s
+ El intento de cambiar la contraseña de la base de datos no se ha completado.
+ Pulsa el botón
+ Para iniciar un chat nuevo
+ Cambiar dirección de recepción
+ El grupo está totalmente descentralizado: sólo es visible para los miembros.
+ Para conectarse mediante enlace
+ ¡Error en prueba del servidor!
+ Algunos servidores no superaron la prueba:
+ Usar servidor
+ Usar para conexiones nuevas
+ Sistema
+ mediante enlace único
+ Tus chats
+ Mensaje de voz…
+ Tu dirección de contacto
+ Desactivar vídeo
+ Activar vídeo
+ Contraseña de base de datos incorrecta
+ ¡Contraseña incorrecta!
+ Te has unido a este grupo. Conectando con miembro del grupo invitado.
+ Estás utilizando un perfil incógnito para este grupo. Para evitar compartir tu perfil principal, invitar contactos no está permitido
+ Has sido invitado al grupo
+ Mensajes de voz
+ Tus contactos pueden permitir la eliminación completa de mensajes.
+ Tú controlas a través de qué servidor(es) recibes los mensajes. Tus contactos controlan a través de qué servidor(es) envías tus mensajes.
+ Mensajes de voz
+ Los mensajes de voz están prohibidos en este grupo.
+ Comprobar la seguridad de la conexión
+ ¡Ya estás conectado a %1$s! .
+ ¡Bienvenido!
+ Tu perfil de chat será enviado
+\na tu contacto
+ Tus servidores ICE
+ Has rechazado la invitación del grupo.
+ has cambiado el rol de %s a %s
+ mediante servidor de retransmisión
+ ¡quiere contactar contigo!
+ Mensaje de voz
+ Esperando imagen
+ Servidores WebRTC ICE
+ %1$s quiere conectarse contigo mediante
+ Tienes un perfil de chat con el mismo nombre mostrado. Debes elegir otro nombre.
+ También puedes conectarte haciendo clic en el enlace. Si se abre en el navegador, haz clic en Abrir en aplicación móvil.
+ Puedes conectar con los desarrolladores de SimpleX Chat para hacer cualquier pregunta y recibir actualizaciones.
+ Puedes compartir tu dirección como enlace o como código QR: cualquiera podrá conectarse contigo. Si lo eliminas más tarde tus contactos no se perderán.
+ ¡No puedes enviar mensajes!
+ Puedes usar marcadores para dar formato a los mensajes:
+ Debes usar la versión más reciente de tu base de datos SÓLO en un dispositivo, de lo contrario podrías dejar de recibir mensajes de algunos contactos.
+ 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.
+ Tu perfil aleatorio
+ Te conectarás cuando se acepte tu solicitud de conexión, por favor espere 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.
+ Se te pedirá identificarte cuándo inicies o continues usando la aplicación tras 30 segundos en segundo plano.
+ Estás intentando invitar a un contacto con el que has compartido un perfil incógnito, al grupo en el que utilizas tu perfil principal
+ mediante navegador
+ mediante %1$s
+ Servicio SimpleX Chat
+ ¡Bienvenido %1$s !
+ has sido invitado al grupo
+ Esperando archivo
+ Esperando imagen
+ Mensaje de voz (%1$s )
+ Cuando esté disponible
+ No almacenamos ninguno de tus contactos o mensajes (una vez entregados) en los servidores.
+ Has sido invitado al grupo. Únete para conectarte con los miembros del grupo.
+ has eliminado %1$s
+ Tú: %1$s
+ Puedes compartir un enlace o un código QR: cualquiera podrá unirse al grupo. Si lo eliminas más tarde los miembros del grupo no se perderán.
+ Cuando compartes un perfil incógnito con alguien, este perfil se usará para los grupos a los que te inviten.
+ Tus preferencias
+ Con mensaje de bienvenida opcional.
+ Tu rol es observador
+ Comprobar código de seguridad
+ Has aceptado la conexión
+ Has invitado a tu contacto
+ Te conectarás al grupo cuando el dispositivo del anfitrión esté en línea, por favor espera o compruébalo más tarde.
+ Tu contacto puede escanear el código QR desde la aplicación.
+ Tu configuración
+ Tus servidores SMP
+ ¡Tú controlas tu chat!
+ Tu perfil, contactos y mensajes entregados se almacenan en tu dispositivo.
+ esperando respuesta…
+ esperando confirmación…
+ Cuando la aplicación se está ejecutando
+ Videollamada
+ Tus llamadas
+ Tus servidores ICE
+ Tu privacidad
+ TU
+ Base de datos de Chat
+ Puedes iniciar el chat en Configuración / Base de datos o reiniciando la aplicación.
+ Has enviado una invitación de grupo
+ %1$s contacto(s) seleccionado(s)
+ %1$s MIEMBROS
+ Tus perfiles de chat se almacenan localmente, sólo en tu dispositivo
+ Los mensajes de voz están prohibidos en este chat.
+ Novedades
+ La contraseña no se almacena en el dispositivo, tienes que introducirla cada vez que inicies la aplicación.
+ Te has unido a este grupo
+ sí
+ 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.
+ tú
+ Estás conectado al servidor utilizado para recibir mensajes de este contacto.
+ mediante enlace de dirección de contacto
+ mediante enlace de grupo
+ has compartido un enlace único
+ has compartido un enlace único en módo incógnito
+ No tienes chats
+ El contacto ha enviado un archivo mayor al máximo admitido (%1$s ).
+ %1$d mensaje(s) omitido(s)
+ Dejarás de recibir mensajes de este grupo. El historial del chat se conservará.
+ Ver código de seguridad
+ Para poder enviar mensajes de voz debes permitir que tu contacto pueda enviarlos.
+ ¡Mensajes de voz prohibidos!
+ Tu perfil de chat se enviará a los miembros del grupo
+ Dirección SimpleX
+ Logo SimpleX
+ Equipo SimpleX
+ Tu perfil de chat se enviará a tu contacto
+ Tus perfiles de chat
+ Tu dirección de contacto SimpleX
+ Tu servidor
+ Dirección de tu servidor
+ MENSAJE DE BIENVENIDA
+ Tu perfil actual
+ Tu perfil se almacena en tu dispositivo y sólo se comparte con tus contactos.
+\n
+\nLos servidores SimpleX no pueden ver tu perfil.
+ Sistema
\ No newline at end of file
diff --git a/apps/android/app/src/main/res/values-fr/strings.xml b/apps/android/app/src/main/res/values-fr/strings.xml
index fb3c2471a..b0efa0df9 100644
--- a/apps/android/app/src/main/res/values-fr/strings.xml
+++ b/apps/android/app/src/main/res/values-fr/strings.xml
@@ -598,12 +598,14 @@
Appel audio entrant
%1$s veut se connecter à vous via
Vos appels
- Se connecter via relais
+ Se connecter via relais
Appels en écran verrouillé :
Montrer
Désactiver
Vos serveurs ICE
Serveurs WebRTC ICE
+ Le serveur relais protège votre adresse IP, mais il peut observer la durée de l\'appel.
+ Le serveur relais n\'est utilisé que si nécessaire. Un tiers peut observer votre adresse IP.
Ouvrez SimpleX Chat pour décrocher
sans chiffrement de bout en bout
Ce contact a le chiffrement de bout en bout
@@ -967,4 +969,16 @@
Isolation du transport
Différents noms, avatars et mode d\'isolation de transport.
modéré
+ modéré par %s
+ Supprimer le message de ce membre \?
+ Modéré
+ Le message sera supprimé pour tous les membres.
+ Le message sera marqué comme modéré pour tous les membres.
+ vous êtes observateur
+ Erreur lors de la mise à jour du lien de groupe
+ Rôle initial
+ Veuillez contacter l\'administrateur du groupe.
+ Vous ne pouvez pas envoyer de messages !
+ observateur
+ Système
\ No newline at end of file
diff --git a/apps/android/app/src/main/res/values-it/strings.xml b/apps/android/app/src/main/res/values-it/strings.xml
index e9ff25511..0d980a3c2 100644
--- a/apps/android/app/src/main/res/values-it/strings.xml
+++ b/apps/android/app/src/main/res/values-it/strings.xml
@@ -378,7 +378,7 @@
Chiamata in corso
Chiamate sulla schermata di blocco:
Connessione chiamata
- Connetti via relay
+ Connetti via relay
il contatto ha la crittografia e2e
il contatto non ha la crittografia e2e
Disattiva
@@ -718,6 +718,8 @@
Video off
Video on
Server WebRTC ICE
+ Il server relay protegge il tuo indirizzo IP, ma può osservare la durata della chiamata.
+ Il server relay viene usato solo se necessario. Un altro utente può osservare il tuo indirizzo IP.
%1$d messaggio/i saltato/i
Le tue chiamate
I tuoi server ICE
@@ -967,4 +969,16 @@
Per proteggere il fuso orario, i file immagine/vocali usano UTC.
Nomi e avatar diversi, isolamento del trasporto.
moderato
+ moderato da %s
+ Eliminare il messaggio del membro\?
+ Modera
+ Il messaggio verrà eliminato per tutti i membri.
+ Il messaggio sarà segnato come moderato per tutti i membri.
+ sei un osservatore
+ Ruolo iniziale
+ Errore nell\'aggiornamento del link del gruppo
+ osservatore
+ Contatta l\'amministratore del gruppo.
+ Non puoi inviare messaggi!
+ Sistema
\ No newline at end of file
diff --git a/apps/android/app/src/main/res/values-ja/strings.xml b/apps/android/app/src/main/res/values-ja/strings.xml
index 62b89baa5..0530ae4ec 100644
--- a/apps/android/app/src/main/res/values-ja/strings.xml
+++ b/apps/android/app/src/main/res/values-ja/strings.xml
@@ -459,7 +459,7 @@
コアのバージョン: v%s
画像を編集
不在着信
- リレー経由で繋がる。
+ リレー経由で繋がる。
エンドツーエンド暗号化済み
チャットのデータベースが削除されました。
次の期間が経ったら、メッセージを削除:
diff --git a/apps/android/app/src/main/res/values-lt/strings.xml b/apps/android/app/src/main/res/values-lt/strings.xml
new file mode 100644
index 000000000..40df1e30c
--- /dev/null
+++ b/apps/android/app/src/main/res/values-lt/strings.xml
@@ -0,0 +1,11 @@
+
+
+ 1 mėnuo
+ 1 savaitė
+ Apie SimpleX Chat
+ 1 diena
+ a + b
+ Apie SimpleX
+ Pridėti serverį…
+ Pridėti serverius skenuojant QR kodus.
+
\ No newline at end of file
diff --git a/apps/android/app/src/main/res/values-nb-rNO/strings.xml b/apps/android/app/src/main/res/values-nb-rNO/strings.xml
deleted file mode 100644
index a6b3daec9..000000000
--- a/apps/android/app/src/main/res/values-nb-rNO/strings.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
\ No newline at end of file
diff --git a/apps/android/app/src/main/res/values-night/themes.xml b/apps/android/app/src/main/res/values-night/themes.xml
new file mode 100644
index 000000000..788ccfee7
--- /dev/null
+++ b/apps/android/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/apps/android/app/src/main/res/values-nl/strings.xml b/apps/android/app/src/main/res/values-nl/strings.xml
index a647fc5ef..455f75671 100644
--- a/apps/android/app/src/main/res/values-nl/strings.xml
+++ b/apps/android/app/src/main/res/values-nl/strings.xml
@@ -11,7 +11,7 @@
Annuleer afbeeldingsvoorbeeld
geannuleerd %s
Live bericht annuleren
- veranderend adres…
+ adres wijzigen…
Altijd aan
Gevraagd om de afbeelding te ontvangen
Wijziging
@@ -21,7 +21,7 @@
Sta het verzenden van directe berichten naar leden toe.
Sta toe om verzonden berichten onomkeerbaar te verwijderen.
Sta toe om spraak berichten te verzenden.
- Chat is aktief
+ Chat is actief
Wissen
CHAT DATABASE
CHAT ARCHIEF
@@ -57,7 +57,7 @@
Afbeeldingen automatisch accepteren
Verificatie niet beschikbaar
Terug
- Contactverzoeken automatisch accepteren
+ Contact verzoeken automatisch accepteren
vetgedrukt
Er wordt een willekeurig profiel naar uw contactpersoon verzonden
Bijvoegen
@@ -67,17 +67,17 @@
Al uw contacten blijven verbonden.
Spraak berichten toestaan\?
Goed voor batterij. Achtergrondservice controleert elke 10 minuten op nieuwe berichten. U kunt oproepen en dringende berichten missen.
- Onjuiste bericht-hash
+ Onjuiste bericht hash
Scan QR-code: om verbinding te maken met uw contactpersoon die u de QR-code laat zien.
- Onjuiste bericht-ID
+ Onjuiste bericht ID
Oproep al beëindigd!
1 maand
Over SimpleX
Over SimpleX Chat
hierboven, dan:
Verzoeken accepteren
- Alle gesprekken en berichten worden verwijderd - dit kan niet ongedaan worden gemaakt!
- Alle berichten worden verwijderd - dit kan niet ongedaan worden gemaakt! De berichten worden ALLEEN voor jou verwijderd.
+ Alle gesprekken en berichten worden verwijderd, dit kan niet ongedaan worden gemaakt!
+ Alle berichten worden verwijderd, dit kan niet ongedaan worden gemaakt! De berichten worden ALLEEN voor jou verwijderd.
Sta verdwijnende berichten alleen toe als uw contactpersoon dit toestaat.
Sta spraak berichten alleen toe als uw contactpersoon ze toestaat.
Laat uw contacten verzonden berichten onomkeerbaar verwijderen.
@@ -86,28 +86,28 @@
Geluid uit
Back-up van app gegevens
Beantwoord oproep
- Android Keychain wordt gebruikt om het wachtwoord veilig op te slaan - hierdoor kan de meldings service werken.
- Android Keychain wordt gebruikt om het wachtwoord veilig op te slaan nadat u de app opnieuw hebt opgestart of het wachtwoord heeft gewijzigd - hiermee kunt u meldingen ontvangen.
+ Android Keychain wordt gebruikt om het wachtwoord veilig op te slaan, hierdoor kan de meldings service werken.
+ Android Keychain wordt gebruikt om het wachtwoord veilig op te slaan nadat u de app opnieuw hebt opgestart of het wachtwoord heeft gewijzigd, hiermee kunt u meldingen ontvangen.
App build: %s
App kan alleen meldingen ontvangen wanneer deze actief is, er wordt geen achtergrondservice gestart
Uiterlijk
APP ICON
App versie
- App-versie: v%s
+ App versie: v%s
Er wordt een aparte TCP-verbinding (en SOCKS-referentie) gebruikt voor elk chat profiel dat je in de app hebt .
audio oproep (niet e2e versleuteld)
Automatisch
- Achtergrondservice is altijd actief - meldingen worden weergegeven zodra de berichten beschikbaar zijn.
+ Achtergrondservice is altijd actief, meldingen worden weergegeven zodra de berichten beschikbaar zijn.
Nieuw contact toevoegen: om uw 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.
Beste voor de batterij. U ontvangt alleen meldingen als de app draait, de achtergronddienst wordt NIET gebruikt.
- Het kan worden uitgeschakeld via instellingen - meldingen worden nog steeds weergegeven terwijl de app actief is.
+ Het kan worden uitgeschakeld via instellingen, meldingen worden nog steeds weergegeven terwijl de app actief is.
Zowel jij als je contactpersoon kunnen verzonden berichten onherroepelijk verwijderen.
Zowel jij als je contactpersoon kunnen verdwijnende berichten sturen.
Zowel jij als je contactpersoon kunnen spraak berichten verzenden.
Let op: u kunt het wachtwoord NIET herstellen of wijzigen als u het kwijt raakt.
- Gebruikt meer batterij! Achtergrondservice is altijd actief - meldingen worden weergegeven zodra de berichten beschikbaar zijn.
+ Gebruikt meer batterij! Achtergrondservice is altijd actief, meldingen worden weergegeven zodra de berichten beschikbaar zijn.
link voorbeeld annuleren
oproep beëindigd %1$s
Kan de database niet initialiseren
@@ -116,7 +116,7 @@
Geen toegang tot Keystore om database wachtwoord op te slaan
Kan bestand niet ontvangen
Rol wijzigen
- veranderend adres…
+ adres wijzigen…
adres voor u gewijzigd
veranderde rol van %s naar %s
Groep rol wijzigen\?
@@ -130,8 +130,8 @@
Chat profiel
GESPREKKEN
Praat met de ontwikkelaars
- Controleer het serveradres en probeer het opnieuw.
- Kies bestand
+ Controleer het server adres en probeer het opnieuw.
+ Bestand
Wissen
Vergelijk beveiligingscodes met je contacten.
Contact gecontroleerd
@@ -143,7 +143,7 @@
verbinden (introductie uitnodiging)
verbinding gemaakt
Verbindingsverzoek verzonden!
- Time-out verbinding
+ Timeout verbinding
Maak een eenmalige uitnodiging link
Adres aanmaken
Groep link maken
@@ -166,13 +166,13 @@
Contact verborgen:
Decodeerfout
De momenteel maximaal ondersteunde bestandsgrootte is %1$s.
- Contact en alle berichten worden verwijderd - dit kan niet ongedaan worden gemaakt!
+ Contact en alle berichten worden verwijderd, dit kan niet ongedaan worden gemaakt!
Verbonden
Bevestigen
Maak verbinding via link / QR-code
Gekopieerd naar het klembord
Bijdragen
- ICE-servers configureren
+ ICE servers configureren
Verbinding
Core gebouwd op: %s
Core versie: v%s
@@ -215,9 +215,9 @@
Maak
Maak een profiel aan
Adres verwijderen
- Verbinden via relais
+ Verbinden via relais
contact heeft e2e-codering
- contact heeft geen e2e-encryptie
+ contact heeft geen e2e versleuteling
De database is versleuteld met een willekeurige wachtwoord. Wijzig dit voordat u exporteert.
Database wachtwoord
Bevestig nieuw wachtwoord…
@@ -267,16 +267,16 @@
Wachtende verbinding verwijderen\?
💻 desktop: scan weergegeven QR-code vanuit de app, via Scan QR-code.
Ontwikkel gereedschap
- Apparaatverificatie is uitgeschakeld. SimpleX Lock uitschakelen.
+ Apparaatverificatie is uitgeschakeld. SimpleX Vergrendelen uitschakelen.
Weergavenaam
- Apparaatverificatie is niet ingeschakeld. Je kunt SimpleX Lock inschakelen via Instellingen zodra je apparaatverificatie hebt ingeschakeld.
+ Apparaatverificatie is niet ingeschakeld. Je kunt SimpleX Vergrendelen inschakelen via Instellingen zodra je apparaatverificatie hebt ingeschakeld.
Directe berichten tussen leden zijn verboden in deze groep.
%d bestand(en) met een totale grootte van %s
%d uur
Uitzetten
Verdwijnende berichten
Verdwijnende berichten zijn verboden in dit gesprek.
- SimpleX Lock uitschakelen
+ SimpleX Vergrendelen uitschakelen
Verdwijnende berichten
Verbinding verbreken
Verbinding verbroken
@@ -314,14 +314,14 @@
Fout bij wijzigen van adres
Fout bij het verwijderen van in behandeling zijnde contact verbinding
Fout bij het verwijderen van gebruikers profiel
- SimpleX Lock inschakelen
+ SimpleX Vergrendelen inschakelen
Verbergen
bewerkt
Voor iedereen
Fout
Email
Bewerk afbeelding
- Afsluiten zonder op te slaan
+ Afsluiten zonder opslaan
Volledige naam (optioneel)
e2e versleuteld video gesprek
Schakel oproepen vanaf het vergrendelscherm in via Instellingen.
@@ -343,7 +343,7 @@
groep profiel bijgewerkt
groep verwijderd
Vouw de rolselectie uit
- Groep wordt verwijderd voor alle leden - dit kan niet ongedaan worden gemaakt!
+ Groep wordt verwijderd voor alle leden, dit kan niet ongedaan worden gemaakt!
Fout bij maken van groep link
Fout bij verwijderen groep link
Groep link
@@ -356,7 +356,7 @@
ingeschakeld voor contact
voor u ingeschakeld
Groepsleden kunnen verzonden berichten onherroepelijk verwijderen.
- Groepsleden kunnen directe berichten sturen.
+ Groepsleden kunnen directe berichten sturen
Groepsleden kunnen spraak berichten verzenden.
Per chat profiel (standaard) of per verbinding (BETA).
Verschillende namen, avatars en transportisolatie.
@@ -368,7 +368,7 @@
Automatisch verwijderen van berichten aanzetten\?
Voer het juiste wachtwoord in.
Groep profiel bewerken
- Schakel TCP-keep-alive in
+ Schakel TCP keep-alive in
Versleutelen
Fout bij het toevoegen van gebruiker(s)
Voer de server handmatig in
@@ -378,15 +378,15 @@
VOOR CONSOLE
Groep profiel wordt opgeslagen op de apparaten van de leden, niet op de servers.
Verborgen
- De groep wordt voor u verwijderd - dit kan niet ongedaan worden gemaakt!
+ De groep wordt voor u verwijderd, dit kan niet ongedaan worden gemaakt!
Verbergen
fout
Het bestand wordt ontvangen wanneer uw contact persoon online is, even geduld a.u.b. of controleer later!
Fout bij opslaan van bestand
Bestand niet gevonden
Bestand opgeslagen
- Uit galerij
- Fout bij opslaan van ICE-servers
+ Galerij
+ Fout bij opslaan van ICE servers
geëindigd
Groepsleden kunnen verdwijnende berichten sturen.
%d week
@@ -397,8 +397,8 @@
Fout: %s
Fout bij aanmaken van adres
help
- Flip-camera
- Fout bij opslaan van SMP-servers
+ Draai camera
+ Fout bij opslaan van SMP servers
Fout bij updaten van netwerk configuratie
Kan het gesprek niet laden
Kan de gesprekken niet laden
@@ -420,7 +420,7 @@
Nieuw contactverzoek
Log in met uw inloggegevens
Hoogstwaarschijnlijk heeft dit contact de verbinding met jou verwijderd.
- Bericht wordt verwijderd - dit kan niet ongedaan worden gemaakt!
+ Bericht wordt verwijderd, dit kan niet ongedaan worden gemaakt!
Groot bestand!
Markeer gelezen
Markeer als ongelezen
@@ -440,11 +440,11 @@
Hoe het werkt
gemiste oproep
Hoe SimpleX werkt
- Veel mensen vroegen: als SimpleX geen gebruikers-ID\'s heeft, hoe kan het dan berichten bezorgen\?
+ Veel mensen vroegen: als SimpleX geen gebruikers ID\'s heeft, hoe kan het dan berichten bezorgen\?
Inkomende audio oproep
Inkomend video gesprek
Negeren
- geen e2e-encryptie
+ geen e2e versleuteling
Chat database importeren\?
nooit
Geen ontvangen of verzonden bestanden
@@ -454,17 +454,17 @@
uitgenodigd
Groep verlaten
Lokale naam
- Incognito modus wordt hier niet ondersteund - uw hoofdprofiel wordt naar groepsleden verzonden
+ Incognito modus wordt hier niet ondersteund, uw hoofdprofiel wordt naar groepsleden verzonden
Alleen lokale profielgegevens
Het onomkeerbaar verwijderen van berichten is verboden in deze groep.
- App-scherm verbergen in de recente apps.
+ App scherm verbergen in de recente apps.
Incognito modus
Berichten
Nieuw wachtwoord…
Keychain fout
Word lid van
Groep verlaten\?
- Nieuwe ledenrol
+ Nieuwe leden rol
Geen contacten om toe te voegen
Het maakt het mogelijk om veel anonieme verbindingen te hebben zonder enige gedeelde gegevens tussen hen in een enkel chat profiel.
Licht
@@ -496,7 +496,7 @@
GEBRUIKER
BERICHTEN
📱 mobiel: tik op Openen in mobiele app en tik vervolgens op Verbinden in de app.
- Gebruiker wordt uit de groep verwijderd - dit kan niet ongedaan worden gemaakt!
+ Gebruiker wordt uit de groep verwijderd, dit kan niet ongedaan worden gemaakt!
Fout bij bezorging van bericht
Bericht wordt gemarkeerd voor verwijdering. De ontvanger(s) kunnen dit bericht onthullen.
Netwerk status
@@ -506,7 +506,7 @@
Database importeren
Verbeterde privacy en veiligheid
De afbeelding wordt ontvangen wanneer uw contact online is, even geduld a.u.b. of kijk later!
- De incognito modus beschermt de privacy van uw hoofdprofielnaam en -afbeelding - voor elk nieuw contact wordt een nieuw willekeurig profiel gemaakt.
+ De incognito modus beschermt de privacy van uw hoofdprofielnaam en afbeelding, voor elk nieuw contact wordt een nieuw willekeurig profiel gemaakt.
Nieuw database archief
geen details
indirect (%1$s)
@@ -516,14 +516,14 @@
Meldingsservice
Meldingen
Ongeldige link!
- Ongeldig serveradres!
+ Ongeldig server adres!
Installeer SimpleX Chat voor terminal
Hoe
Hoe u uw servers gebruikt
Netwerk & servers
- ICE-servers (één per lijn)
- Zorg ervoor dat WebRTC ICE-serveradressen de juiste indeling hebben, regelgescheiden zijn en niet gedupliceerd zijn.
- Als u bevestigt, kunnen de berichtenservers uw IP-adres zien en uw provider - met welke servers u verbinding maakt.
+ ICE servers (één per lijn)
+ Zorg ervoor dat WebRTC ICE server adressen de juiste indeling hebben, regel gescheiden zijn en niet gedupliceerd zijn.
+ Als u bevestigt, kunnen de berichten servers uw IP-adres zien en uw provider, met welke servers u verbinding maakt.
Nee
Immuun voor spam en misbruik
Maak een privéverbinding
@@ -535,7 +535,7 @@
uitgenodigd via je groep link
Incognito
Gemiste oproep
- incognito via link naar contactadres
+ incognito via contact adres link
incognito via groep link
incognito via eenmalige link
ongeldige gesprek
@@ -543,21 +543,21 @@
ongeldig berichtformaat
uitgenodigd om te verbinden
LIVE
- Zorg ervoor dat SMP-serveradressen de juiste indeling hebben, regelgescheiden zijn en niet gedupliceerd zijn.
+ Zorg ervoor dat SMP server adressen de juiste indeling hebben, regel gescheiden zijn en niet gedupliceerd zijn.
gemarkeerd als verwijderd
Controleer of u de juiste link heeft gebruikt of vraag uw contactpersoon om u een andere te sturen.
profielfoto
Privacy opnieuw gedefinieerd
Privacy en beveiliging
Controleer uw netwerkverbinding met %1$s en probeer het opnieuw.
- Mogelijk is de certificaatvingerafdruk in het serveradres onjuist
+ Mogelijk is de certificaat vingerafdruk in het server adres onjuist
Periodieke meldingen
Chat console openen
Geen toestemming!
profielafbeelding tijdelijke aanduiding
Eenmalige uitnodiging link
Plakken
- Vooraf ingesteld serveradres
+ Vooraf ingesteld server adres
Onion hosts worden niet gebruikt.
Privé meldingen
Plak de ontvangen link
@@ -573,12 +573,12 @@
Alleen jij kunt verdwijnende berichten verzenden.
Alleen uw contactpersoon kan verdwijnende berichten verzenden.
Alleen jij kunt berichten onomkeerbaar verwijderen (je contactpersoon kan ze markeren voor verwijdering).
- aangeboden %s: %2s
+ voorgesteld %s: %2s
Oud database archief
Voer het juiste huidige wachtwoord in.
eigenaar
- PING-telling
- PING-interval
+ PING telling
+ PING interval
Bewaar het laatste berichtconcept, met bijlagen.
Privé bestandsnamen
Er kunnen slechts 10 afbeeldingen tegelijk worden verzonden
@@ -599,13 +599,13 @@
Onion hosts worden gebruikt indien beschikbaar.
Onion hosts worden gebruikt indien beschikbaar.
Onion hosts worden niet gebruikt.
- Open-source protocol en code – iedereen kan de servers draaien.
+ Open-source protocol en code. Iedereen kan de servers draaien.
Mensen kunnen alleen verbinding met u maken via de links die u deelt.
Alleen uw contactpersoon kan berichten onherroepelijk verwijderen (u kunt ze markeren voor verwijdering).
Alleen jij kunt spraak berichten verzenden.
Alleen uw contactpersoon kan spraak berichten verzenden.
Verbied het onomkeerbaar verwijderen van berichten.
- aangeboden %s
+ voorgesteld %s
Sla het wachtwoord veilig op. Als u deze kwijtraakt, heeft u GEEN toegang tot de gesprekken.
Bewaar het wachtwoord veilig, u kunt deze NIET wijzigen als u deze kwijtraakt.
Gesprekken openen
@@ -613,9 +613,9 @@
Oproep in behandeling
Het openen van de link in de browser kan de privacy en beveiliging van de verbinding verminderen. Niet vertrouwde SimpleX links worden rood weergegeven.
Werk de app bij en neem contact op met de ontwikkelaars.
- Alleen client-apparaten slaan gebruikersprofielen, contacten, groepen en berichten op die zijn verzonden met 2-laags end-to-end-codering.
+ Alleen client apparaten slaan gebruikers profielen, contacten, groepen en berichten op die zijn verzonden met 2-laags end-to-end codering.
De afzender heeft mogelijk het verbindingsverzoek verwijderd.
- Schakel SimpleX Lock in om uw informatie te beschermen.
+ Schakel SimpleX Vergrendelen in om uw informatie te beschermen.
\nU wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingeschakeld.
Aanzetten
Ontgrendelen
@@ -629,15 +629,15 @@
Uw contactpersoon kan de QR-code vanuit de app scannen.
Uitnodiging link delen
Code scannen
- Uw contactadres
+ Uw contact adres
Uw instellingen
Deel link
Jij beheert je gesprek!
Uw profiel, contacten en afgeleverde berichten worden op uw apparaat opgeslagen.
beginnen…
- U bepaalt via welke server(s) je de berichten ontvangt, uw contacten – de servers die u gebruikt om ze berichten te sturen.
+ U bepaalt via welke server(s) je de berichten ontvangt, uw contacten de servers die u gebruikt om ze berichten te sturen.
Video aan
- Deze actie kan niet ongedaan worden gemaakt - uw profiel, contacten, berichten en bestanden gaan onomkeerbaar verloren.
+ Deze actie kan niet ongedaan worden gemaakt. Uw profiel, contacten, berichten en bestanden gaan onomkeerbaar verloren.
Deze instelling is van toepassing op berichten in jou huidige chat profiel
Bewaar archief
bijgewerkt groep profiel
@@ -654,7 +654,7 @@
Toon contact en bericht
Toon alleen contactpersoon
SimpleX Chat berichten
- SimpleX Lock ingeschakeld
+ SimpleX Vergrendelen ingeschakeld
Stop chat
Antwoord
Opslaan
@@ -669,7 +669,7 @@
Welkom!
je bent uitgenodigd voor de groep
Je hebt geen gesprekken
- Je gesprekken
+ Jouw gesprekken
Deel bestand…
Afbeelding delen…
Wachten op afbeelding
@@ -706,14 +706,14 @@
Beveiligingscode
%s is niet geverifieerd
%s is geverifieerd
- Vergelijk (of scan) de code op uw apparaten om end-to-end-codering met uw contactpersoon te verifiëren.
+ Vergelijk (of scan) de code op uw apparaten om end-to-end codering met uw contactpersoon te verifiëren.
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 .
Uw chat profiel wordt naar uw contactpersoon verzonden
- Je chat profielen
- Uw SimpleX contactadres
+ Uw chat profielen
+ Uw SimpleX contact adres
Stuur vragen en ideeën
Stuur ons een e-mail
- SimpleX Lock
+ SimpleX Vergrendelen
SMP servers
Bewaar servers
Servertest mislukt!
@@ -722,8 +722,8 @@
Server test
Beoordeel de app
Gebruik server
- Gebruik van SimpleX Chat-servers.
- Uw serveradres
+ Gebruik van SimpleX Chat servers.
+ Uw server adres
Uw server
Transport isolation
SOCKS-proxy gebruiken (poort 9050)
@@ -732,7 +732,7 @@
WELKOMS BERICHT
Je huidige profiel
Opslaan en Contacten melden
- Opslaan en Groepleden melden
+ Opslaan en Groepsleden melden
staking
Het berichten- en applicatieplatform dat uw privacy en veiligheid beschermt.
Het profiel wordt alleen gedeeld met uw contacten.
@@ -742,7 +742,7 @@
geheim
De volgende generatie privéberichten
wachten op antwoord…
- Om de privacy te beschermen, heeft SimpleX in plaats van gebruikers-ID\'s die door alle andere platforms worden gebruikt, ID\'s voor berichtenwachtrijen, afzonderlijk voor elk van uw contacten.
+ Om de privacy te beschermen, heeft SimpleX in plaats van gebruikers ID\'s die door alle andere platforms worden gebruikt, ID\'s voor berichten wachtrijen, afzonderlijk voor elk van uw contacten.
Gebruik chat
Wanneer de app actief is
video gesprek (niet e2e versleuteld)
@@ -751,8 +751,10 @@
video oproep
Uw oproepen
Toon
- WebRTC ICE-servers
- Uw ICE-servers
+ WebRTC ICE servers
+ Relay server beschermt uw IP-adres, maar kan de duur van het gesprek observeren.
+ Relay server wordt alleen gebruikt als dat nodig is. Een andere partij kan uw IP-adres zien.
+ Uw ICE servers
Overgeslagen berichten
via relais
Video uit
@@ -769,7 +771,7 @@
U mag de meest recente versie van uw chat database ALLEEN op één apparaat gebruiken, anders ontvangt u mogelijk geen berichten meer van sommige contacten.
Start de app opnieuw om de geïmporteerde chat database te gebruiken.
Stop de chat om database acties mogelijk te maken.
- Deze actie kan niet ongedaan worden gemaakt - alle ontvangen en verzonden bestanden en media worden verwijderd. Foto\'s met een lage resolutie blijven behouden.
+ Deze actie kan niet ongedaan worden gemaakt, alle ontvangen en verzonden bestanden en media worden verwijderd. Foto\'s met een lage resolutie blijven behouden.
Wachtwoord verwijderen uit Keychain\?
Verwijderen
Update
@@ -783,13 +785,13 @@
heeft je verwijderd
jij: %1$s
%1$s LEDEN
- U kunt een link of een QR-code delen - iedereen kan lid worden van de groep. U verliest geen leden van de groep als u deze later verwijdert.
+ U kunt een link of een QR-code delen. Iedereen kan lid worden van de groep. U verliest geen leden van de groep als u deze later verwijdert.
Wijzig
De rol wordt gewijzigd in \"%s\". Iedereen in de groep wordt op de hoogte gebracht.
Ontvang via
Groep profiel opslaan
De groep is volledig gedecentraliseerd – het is alleen zichtbaar voor de leden.
- Time-out van TCP-verbinding
+ Timeout van TCP-verbinding
Spraak berichten
Spraak berichten zijn verboden in dit gesprek.
Verbied het verzenden van verdwijnende berichten.
@@ -800,7 +802,7 @@
Stop de chat om de chat database te exporteren, importeren of verwijderen. U kunt geen berichten ontvangen en verzenden terwijl de chat is gestopt.
%s seconde(n)
Database wachtwoord bijwerken
- Uw chat database is niet versleuteld - stel een wachtwoord in om deze te beschermen.
+ Uw chat database is niet versleuteld, stel een wachtwoord in om deze te beschermen.
Databasefout herstellen
Onbekende fout
Verkeerd wachtwoord!
@@ -823,7 +825,7 @@
SERVERS
Resetten naar standaardwaarden
Ontvangst adres wijzigen
- Protocol time-out
+ Protocol timeout
Terugdraaien
Opslaan
sec
@@ -857,16 +859,16 @@
Spraak berichten verboden!
Resetten
Verstuur
- Stuur een live bericht - het wordt bijgewerkt voor de ontvanger(s) terwijl u het typt
+ Stuur een live bericht, het wordt bijgewerkt voor de ontvanger(s) terwijl u het typt
(om te delen met uw contact)
Bedankt voor het installeren van SimpleX Chat!
- Gebruik camera
+ Camera
Gebruik voor nieuwe verbindingen
Star on GitHub
De servers voor nieuwe verbindingen van je huidige chat profiel
- Uw SMP-servers
- Opgeslagen WebRTC ICE-servers worden verwijderd.
- Uw ICE-servers
+ Uw SMP servers
+ Opgeslagen WebRTC ICE servers worden verwijderd.
+ Uw ICE servers
Opslaan
SOCKS-proxy gebruiken\?
Gebruik .onion-hosts
@@ -877,19 +879,19 @@
Transportisolatiemodus updaten\?
bevestiging ontvangen…
Wachten op bevestiging…
- Het eerste platform zonder gebruikers-ID\'s - privé door ontwerp.
+ Het eerste platform zonder gebruikers ID\'s, privé door ontwerp.
Verbied het sturen van directe berichten naar leden.
Verbieden het verzenden van spraak berichten.
De beveiliging van SimpleX Chat is gecontroleerd door Trail of Bits.
Met optioneel welkomstbericht.
Spraak berichten
Uw contacten kunnen volledige verwijdering van berichten toestaan.
- U moet elke keer dat de app start het wachtwoord invoeren - deze wordt niet op het apparaat opgeslagen.
+ U moet elke keer dat de app start het wachtwoord invoeren, deze wordt niet op het apparaat opgeslagen.
Verkeerd wachtwoord voor de database
Bewaar het wachtwoord en open je gesprekken
De poging om het wachtwoord van de database te wijzigen is niet voltooid.
- Databaseback-up terugzetten
- Databaseback-up terugzetten\?
+ Database back-up terugzetten
+ Database back-up terugzetten\?
Herstellen
je veranderde de rol van %s in %s
je veranderde de rol voor jezelf naar %s
@@ -906,38 +908,38 @@
Server vereist autorisatie om wachtrijen te maken, controleer wachtwoord
Groep voorkeuren instellen
Bericht delen…
- SimpleX Lock
+ SimpleX Vergrendelen
Sla het wachtwoord op in Keychain
SOCKS PROXY
Dank aan de gebruikers – draag bij via Weblate!
- De app haalt regelmatig nieuwe berichten op - het gebruikt een paar procent van de batterij per dag. De app maakt geen gebruik van push meldingen - gegevens van uw apparaat worden niet naar de servers verzonden.
+ De app haalt regelmatig nieuwe berichten op - het gebruikt een paar procent van de batterij per dag. De app maakt geen gebruik van push meldingen, gegevens van uw apparaat worden niet naar de servers verzonden.
De afbeelding kan niet worden gedecodeerd. Probeer een andere afbeelding of neem contact op met de ontwikkelaars.
THEMA\'S
Scan server QR-code
Deze string is geen verbinding link!
- Deze actie kan niet ongedaan worden gemaakt - de berichten die eerder zijn verzonden en ontvangen dan geselecteerd, worden verwijderd. Het kan enkele minuten duren.
+ Deze actie kan niet ongedaan worden gemaakt, de berichten die eerder zijn verzonden en ontvangen dan geselecteerd, worden verwijderd. Het kan enkele minuten duren.
Deze functie is experimenteel! Het werkt alleen als op de andere client versie 4.2 is geïnstalleerd. U zou het bericht in het gesprek moeten zien zodra de adreswijziging is voltooid. Controleer of u nog steeds berichten van dit contact (of groepslid) kunt ontvangen.
Om je privacy te behouden, heeft de app in plaats van push meldingen een SimpleX achtergrondservice – deze gebruikt een paar procent van de batterij per dag.
Jij staat toe
Je bent uitgenodigd voor de groep
U kunt verbinding maken met SimpleX Chat ontwikkelaars om vragen te stellen en updates te ontvangen.
- Tenzij uw contactpersoon de verbinding heeft verwijderd of deze link al is gebruikt, kan het een bug zijn - meld het alstublieft.
+ Tenzij uw contactpersoon de verbinding heeft verwijderd of deze link al is gebruikt, kan het een bug zijn. Meld het alstublieft.
\nOm verbinding te maken, vraagt u uw contactpersoon om een andere verbinding link te maken en te controleren of u een stabiele netwerkverbinding heeft.
.onion hosts-instelling updaten\?
- SimpleX Chat-servers gebruiken\?
+ SimpleX Chat servers gebruiken\?
Spraak berichten zijn verboden in deze groep.
Welkom %1$s!
- U kunt uw adres delen als een link of als een QR-code - iedereen kan verbinding met u maken. U verliest uw contacten niet als u deze later verwijdert.
+ U kunt uw adres delen als een link of als een QR-code. Iedereen kan verbinding met u maken. U verliest uw contacten niet als u deze later verwijdert.
U kunt de chat starten via app Instellingen / Database of door de app opnieuw op te starten.
je hebt het adres gewijzigd voor %s
je hebt %1$s verwijderd
Je contactpersoon heeft een bestand verzonden dat groter is dan de momenteel ondersteunde maximale grootte (%1$s).
- Uw huidige chat database wordt VERWIJDERD en VERVANGEN door de geïmporteerde.
-\nDeze actie kan niet ongedaan worden gemaakt - uw profiel, contacten, berichten en bestanden gaan onomkeerbaar verloren.
+ Uw huidige chatdatabase wordt VERWIJDERD en VERVANGEN door de geïmporteerde.
+\nDeze actie kan niet ongedaan worden gemaakt. Uw profiel, contacten, berichten en bestanden gaan onomkeerbaar verloren.
Je probeert een contact met wie je een incognito profiel hebt gedeeld, uit te nodigen voor de groep waarin je je hoofdprofiel gebruikt
Uw profiel wordt op uw apparaat opgeslagen en alleen gedeeld met uw contacten.
\n
-\nSimpleX-servers kunnen uw profiel niet zien.
+\nSimpleX servers kunnen uw profiel niet zien.
U moet zich authenticeren wanneer u de app na 30 seconden op de achtergrond start of hervat.
Te veel afbeeldingen!
Luidspreker uit
@@ -946,7 +948,7 @@
Afzender heeft bestandsoverdracht geannuleerd.
het ontvangen van bestanden wordt nog niet ondersteund
het verzenden van bestanden wordt nog niet ondersteund
- SimpleX contactadres
+ SimpleX contact adres
SimpleX groep link
SimpleX links
Eenmalige SimpleX uitnodiging
@@ -954,7 +956,7 @@
Er wordt geprobeerd verbinding te maken met de server die wordt gebruikt om berichten van dit contact te ontvangen (fout: %1$s).
onbekend berichtformaat
Via browser
- via contactadres link
+ via contact adres link
via groep link
via een eenmalige link
via %1$s
@@ -962,8 +964,20 @@
je hebt een eenmalige link gedeeld
je hebt een eenmalige link incognito gedeeld
Tik op de knop
- Lees meer in onze GitHub-repository.
- Lees meer in onze GitHub-repository.
+ Lees meer in onze GitHub repository.
+ Lees meer in onze GitHub repository.
%1$d bericht(en) overgeslagen
gemodereerd
+ gemodereerd door %s
+ Bericht van lid verwijderen\?
+ Modereren
+ Het bericht wordt verwijderd voor alle leden.
+ Het bericht wordt gemarkeerd als gemodereerd voor alle leden.
+ Neem contact op met de groep beheerder.
+ Fout bij bijwerken van groep link
+ Initiële rol
+ waarnemer
+ Je kunt geen berichten versturen!
+ jij bent waarnemer
+ Systeem
\ No newline at end of file
diff --git a/apps/android/app/src/main/res/values-pl/strings.xml b/apps/android/app/src/main/res/values-pl/strings.xml
deleted file mode 100644
index a6b3daec9..000000000
--- a/apps/android/app/src/main/res/values-pl/strings.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
\ No newline at end of file
diff --git a/apps/android/app/src/main/res/values-pt-rBR/strings.xml b/apps/android/app/src/main/res/values-pt-rBR/strings.xml
index 99bca53d8..be7ee5a5a 100644
--- a/apps/android/app/src/main/res/values-pt-rBR/strings.xml
+++ b/apps/android/app/src/main/res/values-pt-rBR/strings.xml
@@ -6,4 +6,433 @@
1 semana
1 mês
a + b
+ Não é possível convidar contatos!
+ mudando endereço…
+ mudando endereço para %s…
+ "mudando endereço…"
+ Mudar regra
+ Mudar
+ Um perfil aleatório será enviado para o contato do qual você recebeu este link
+ Você e seu contato podem excluir mensagens enviadas de forma irreversível.
+ Pode ser desativado nas configurações – as notificações ainda serão exibidas enquanto o aplicativo estiver em execução.
+ Você e seu contato podem enviar mensagens de voz.
+ O aplicativo pode receber notificações apenas quando estiver em execução, nenhum serviço em segundo plano será iniciado
+ Sempre On
+ Verifica novas mensagens a cada 10 minutos por até 1 minuto
+ Autenticação indisponível
+ Cancelar visualização do arquivo
+ Pediu para receber a imagem
+ Cancelar mensagem ao vivo
+ Voltar
+ Selecione o arquivo
+ Adicionar novo contato: para criar seu QR code único para seu contato.
+ Escanear \u0020QR code: para se conectar ao seu contato que mostra o código QR para você.
+ Aceitar
+ Limpar bate-papo\?
+ Limpar
+ Limpar bate-papo
+ Limpar
+ cancelar pré-visualização do link
+ cancelado %s
+ Versão do App: v%s
+ chamando…
+ chamada em andamento
+ Aceitar
+ Chamada já encerrada!
+ Chamada em andamento
+ Chamada finalizada
+ Atender ligação
+ hash de mensagem ruim
+ ID de mensagem incorreta
+ Observação: você NÃO poderá recuperar ou alterar a senha se a perder.
+ Não é possível receber o arquivo
+ Cancelar visualização da imagem
+ Botão Fechar
+ Limpar verificação
+ Versão do App
+ Automaticamente
+ negrito
+ erro de chamada
+ Chamadas de áudio e vídeo
+ Aceitar
+ Chamadas na tela de bloqueio:
+ Áudio ligado
+ Banco de dados de bate-papo importado
+ "Android Keystore é usado para armazenar passphrase com segurança - permite que o serviço de notificação funcione."
+ "O Android Keystore será usado para armazenar passphrase com segurança depois que você reiniciar o aplicativo ou alterar a senha - isso permitirá o recebimento de notificações."
+ Não é possível acessar o Keystore para salvar a senha do banco de dados
+ ARQUIVO DE BATE-PAPO
+ O bate-papo está parado
+ Limpar
+ Um perfil aleatório será enviado para o seu contato
+ Preferências de bate-papo
+ perfil de bate-papo
+ Aceitar pedidos
+ Áudio desligado
+ Aceitar imagens automaticamente
+ Banco de dados de bate-papo excluído
+ Não é possível convidar o contato!
+ A otimização da bateria está ativa, desligando o serviço em segundo plano e as solicitações periódicas de novas mensagens. Você pode reativá-los através das configurações.
+ Não é possível inicializar o banco de dados
+ Anexar
+ Cancelar
+ Console de bate-papo
+ Verifique o endereço do servidor e tente novamente.
+ Uma conexão TCP separada (e credencial SOCKS) será usada para cada perfil de bate-papo que você tiver no aplicativo.
+ Melhor para bateria. Você receberá notificações apenas quando o aplicativo estiver em execução, o serviço em segundo plano NÃO será usado.
+ Consome mais bateria! O serviço em segundo plano está sempre em execução - as notificações serão exibidas assim que as mensagens estiverem disponíveis.
+ CONVERSAS
+ ÍCONE DO APP
+ BANCO DE DADOS DE BATE-PAPO
+ O bate-papo está em execução
+ O bate-papo está parado
+ Alterar passphrase do banco de dados\?
+ Arquivo de bate-papo
+ endereço alterado para você
+ Você e seu contato podem enviar mensagens que desaparecem.
+ Backup de dados do aplicativo
+ CHAMADAS
+ Aceitar solicitações de contato automaticamente
+ Aparência
+ O serviço em segundo plano está sempre em execução - as notificações serão exibidas assim que as mensagens estiverem disponíveis.
+ Uma conexão TCP separada (e credencial SOCKS) será usada para cada contato e membro do grupo. Observação: se você tiver muitas conexões, o consumo de bateria e tráfego pode ser substancialmente maior e algumas conexões podem falhar.
+ Bom para bateria. O serviço em segundo plano verifica novas mensagens a cada 10 minutos. Você pode perder chamadas e mensagens urgentes.
+ chamada finalizada %1$s
+ Bate-papo com os desenvolvedores
+ Criar link de grupo
+ Criar link
+ Criar grupo secreto
+ Escuro
+ Conectar via link de convite\?
+ Conectar via link de contato\?
+ Criar fila
+ Nome de contato
+ Contato oculto:
+ Copiar
+ Permitir
+ Permitir enviar mensagens que desaparecem.
+ Permitir o envio de mensagens diretas aos membros.
+ Conectar via link/ QR Code
+ "Todas as mensagens serão excluídas - isso não pode ser desfeito! As mensagens serão excluídas APENAS para você."
+ Adicionar servidores predefinidos
+ Adicionar servidor…
+ Crie seu perfil
+ Ícone de contexto
+ Contato e todas as mensagens serão excluídas - isso não pode ser desfeito!
+ Copiado para a área de transferência
+ Aceitar solicitação de conexão\?
+ Configurações de rede avançadas
+ Solicitações de contato
+ Criar endereço
+ Todos os seus contatos permanecerão conectados.
+ chamada aceita
+ Contato tem criptografia e2e
+ contato não tem criptografia e2e
+ Preferências de contato
+ Permite excluir irreversivelmente as mensagens enviadas.
+ sempre
+ Adicione servidores digitalizando QR code.
+ Permitir enviar mensagens de voz.
+ Criar grupo secreto
+ Conectar via relay
+ Adicionar perfil
+ Conectar via link
+ Criar perfil
+ Banco de dados criptografado!
+ criador
+ Todos os bate-papos e mensagens serão excluídos - isso não pode ser desfeito!
+ Aceitar
+ Permitir mensagens que desaparecem apenas se o seu contato permitir.
+ Permita a exclusão irreversível da mensagem somente se o seu contato permitir.
+ Permitir que seus contatos enviem mensagens que desaparecem.
+ Permitir mensagens de voz somente se o seu contato permitir.
+ Permitir que seus contatos enviem mensagens de voz.
+ admin
+ Todos os membros do grupo permanecerão conectados.
+ "Contatos podem marcar mensagens para exclusão; você será capaz de visualizá-los."
+ Conectar via link do grupo\?
+ Contato já existe
+ Contato verificado
+ Contato ainda não está conectado!
+ Contribuir
+ Criar
+ "Acessar os servidores via proxy SOCKS na porta 9050\? O proxy deve ser iniciado antes de habilitar esta opção."
+ Permitir que seus contatos excluam de forma irreversível as mensagens enviadas.
+ Adicionar a outro dispositivo
+ Os administradores podem criar os links para ingressar em grupos.
+ Permitir mensagens de voz\?
+ Excluir grupo
+ Conexão
+ Excluir perfil de bate-papo\?
+ Excluir para todos
+ Conectar
+ conectado
+ conectando
+ excluído
+ Conectar
+ Excluir
+ Conectar
+ conectando chamada…
+ Excluir perfil de bate-papo\?
+ Excluir arquivos de todos os perfis de bate-papo
+ conectando…
+ Erro de conexão
+ Excluir contato
+ Configurar servidores ICE
+ Excluir endereço\?
+ Descentralizado
+ Excluir banco de dados
+ "O banco de dados é criptografado usando um passphrase aleatório. Por favor, altere-o antes de exportar."
+ Confirmar nova passphrase…
+ Passphrase atual…
+ Passphrase do banco de dados é necessária para abrir o chat.
+ Excluir arquivo
+ Excluir arquivo de bate-papo\?
+ regra alterada de %s para %s
+ conectado
+ Excluir link
+ Excluir link\?
+ padrão (%s)
+ Solicitação de conexão enviada!
+ conectando
+ conectando…
+ Excluir para mim
+ conectando…
+ Excluir contato\?
+ confirmar
+ Passphrase e exportação do banco de dados
+ Conectando chamada
+ Excluir mensagens
+ Passphrase de criptografia do banco de dados será atualizada.
+ Erro de banco de dados
+ Passphrase do banco de dados é diferente da salva no Keystore.
+ completo
+ ID do banco de dados
+ colorido
+ conectado
+ Conectado
+ Conectado
+ chamada de áudio (não criptografada em e2e)
+ Alterar a regra do grupo\?
+ chamada de áudio
+ mudou sua regra para %s
+ Compare os códigos de segurança com seus contatos.
+ Confirme sua credencial
+ conectando…
+ conectando (anunciado)
+ Conexão
+ Erro de conexão (AUTH)
+ conexão estabelecida
+ conexão %1$d
+ Criado em %1$s
+ Atualmente, o tamanho máximo de arquivo suportado é %1$s.
+ Excluir
+ Passphase de criptografia do banco de dados será atualizada e armazenada no Keystore.
+ Excluir endereço
+ "O banco de dados é criptografado usando um passphrase aleatório, você pode alterá-la."
+ Passphrase do banco de dados
+ O banco de dados será criptografado e o passphrase armazenado no Keystore.
+ O banco de dados será criptografado.
+ %d dia
+ Erro de decodificação
+ Excluir
+ %d dias
+ Excluir todos os arquivos
+ Excluir mensagem\?
+ Excluir depois
+ Excluir perfil de bate-papo para
+ grupo excluído
+ Excluir grupo\?
+ Excluir imagem
+ Excluir arquivos e mídia\?
+ conectado
+ conectando (aceito)
+ %dd
+ Por perfil de bate-papo (padrão) ou por conexão (BETA).
+ Aceitar anônimo
+ Excluir mensagens após
+ 💻 desktop: Scaneie o QR code exibido no aplicativo, via Scan QR code
+ Excluir conexão pendente\?
+ Descrição
+ Excluir servidor
+ conectando (introduction invitation)
+ Tempo de conexão esgotado
+ Excluir mensagem do membro\?
+ Excluir fila
+ DISPOSITIVO
+ Ferramentas de desenvolvimento
+ conectando (introduced)
+ Realçar
+ Erro ao remover membro
+ Erro ao alterar regra
+ direto
+ erro
+ Falha ao carregar o bate-papo
+ Erro ao atualizar a configuração de rede
+ Erro ao enviar mensagem
+ Erro ao adicionar membro(s)
+ Desconectar
+ Erro ao excluir perfil do usuário
+ %ds
+ %d meses
+ %d semanas
+ Criptografar banco de dados\?
+ Erro ao receber arquivo
+ Erro ao criar endereço
+ Nome de exibição:
+ Erro ao iniciar o bate-papo
+ Erro ao excluir banco de dados de bate-papo
+ Criptografar
+ Ativar TCP keep-alive
+ Erro ao criar perfil!
+ Erro ao ingressar no grupo
+ Nome de exibição duplicado!
+ Erro ao excluir contato
+ Erro ao alterar endereço
+ Erro ao excluir conexão de contato pendente
+ Editar
+ Ativar exclusão automática de mensagens\?
+ %d sec
+ Erro ao salvar servidores SMP
+ Erro ao aceitar solicitação de contato
+ Erro ao excluir solicitação de contato
+ Erro ao trocar de perfil!
+ Desativar Bloqueio SimpleX
+ Ativar Bloqueio SimpleX
+ editado
+ Erro
+ Email
+ Erro ao salvar servidores ICE
+ Sair sem salvar
+ Nome de exibição
+ chamada de vídeo criptografada e2e
+ mensagem duplicada
+ criptografado e2e
+ Exportar banco de dados
+ %d arquivo(s) com tamanho total de %s
+ Erro ao exportar banco de dados de bate-papo
+ Erro ao importar banco de dados de bate-papo
+ Erro ao interromper o bate-papo
+ Erro ao alterar configuração
+ Erro ao criptografar o banco de dados
+ Banco de dados criptografado
+ Digite o passphrase correto.
+ Digite o passphrase…
+ Erro: %s
+ Editar perfil do grupo
+ Expandir seleção de regra
+ Erro ao salvar o perfil do grupo
+ Mensagens diretas
+ habilitado
+ habilitado para contato
+ ativado para você
+ %dm
+ %d min
+ %d mês
+ %d semana
+ %d hora
+ A autenticação do dispositivo não está habilitada. Você pode ativar o Bloqueio SimpleX em Configurações, depois de ativar a autenticação do dispositivo.
+ Desativar
+ Desconectado
+ Mensagens que desaparecem(temporárias) são proibidas neste grupo.
+ Erro ao salvar arquivo
+ O nome de exibição não pode conter espaços em branco.
+ chamada de áudio criptografada e2e
+ Editar imagem
+ Insira o servidor manualmente
+ Erro ao excluir grupo
+ Funcionalidades experimentais
+ Erro ao criar o link de grupo
+ Erro ao excluir o link de grupo
+ Mensagens diretas entre membros são proibidas neste grupo.
+ %dh
+ %d horas
+ anônimo via link de endereço de contato
+ anônimo via link único
+ Esconder
+ Da Galeria
+ Os membros do grupo podem enviar mensagens que desaparecem.
+ Arquivo
+ Nome completo:
+ Chamada de áudio recebida
+ Mensagens que desaparecem
+ "Ocultar aplicativo nos aplicativos recentes."
+ Imagem enviada
+ Link do grupo
+ Importar banco de dados
+ O convite de grupo não é mais válido, foi removido pelo remetente.
+ Grupo inativo
+ Grupo não encontrado!
+ O grupo será excluído para você - isso não pode ser desfeito!
+ Grupo
+ indireto (%1$s)
+ Anônimo
+ Mensagens que desaparecem
+ Preferências de grupo
+ Mensagens que desaparecem são proibidas nesse bate-papo.
+ Os membros do grupo podem enviar mensagens diretas.
+ %dmês
+ Link completo
+ Esconder
+ A autenticação do dispositivo está desativada. Desativando o SimpleX Lock.
+ Para todos
+ Escondido
+ Gerar um link de convite único.
+ Como usar seus servidores
+ Importar
+ Importar banco de dados de bate-papo\?
+ Nome de exibição do grupo:
+ Nome completo do grupo:
+ Links de grupo
+ Privacidade e segurança aprimoradas
+ Falha ao carregar bate-papos
+ Arquivo: %s
+ Arquivo salvo
+ Os membros do grupo podem enviar mensagens de voz.
+ O grupo será excluído para todos os membros - isso não pode ser desfeito!
+ AJUDA
+ Ocultar contato e mensagem
+ Como usar
+ Como usar markdown
+ Se você não puder se encontrar pessoalmente, mostre o QR code na videochamada ou compartilhe o link.
+ Se você não puder encontrar pessoalmente, você pode escanear o QR code na videochamada ou seu contato pode compartilhar um link de convite.
+ Se você confirmar, os servidores de mensagens poderão ver seu endereço IP e seu provedor - e quais servidores você está se conectando.
+ Imagem
+ Se você recebeu o link de convite SimpleX Chat, você pode abri-lo em seu navegador:
+ Imagem salva na galeria
+ O modo de navegação anônima não é suportado aqui - seu perfil principal será enviado aos membros do grupo
+ anônimo via link do grupo
+ Chamada de vídeo recebida
+ Para usá-lo, por favor desative a otimização da bateria para SimpleX na próxima caixa de diálogo. Caso contrário, as notificações serão desativadas.
+ Gerar um link de convite único
+ Arquivo não encontrado
+ Se você optar por rejeitar o remetente NÃO será notificado.
+ Código de segurança incorreto!
+ Instale o SimpleX Chat para o terminal
+ Nome Completo (opcional)
+ Como funciona
+ Imune a spam e abuso
+ Vire a câmera
+ Desligar
+ Modo anônimo
+ Regra inicial
+ perfil do grupo atualizado
+ Grupo excluído
+ O modo de navegação anônima protege a privacidade do nome e da imagem do seu perfil principal — para cada novo contato, um novo perfil aleatório é criado.
+ Os membros do grupo podem excluir mensagens enviadas de forma irreversível.
+ %dsemana
+ Configuração de servidor aprimorada
+ Interface francesa
+ terminou
+ Ative as chamadas pela tela de bloqueio nas Configurações.
+ Arquivos & mídia
+ Erro ao atualizar o link do grupo
+ O convite do grupo expirou
+ O arquivo será recebido quando seu contato estiver online, aguarde ou verifique mais tarde!
+ O perfil do grupo é armazenado nos dispositivos dos membros, não nos servidores.
+ ajuda
+ Como SimpleX funciona
+ Servidores ICE (um por linha)
+ Ignorar
+ A imagem será recebida quando seu contato estiver online, aguarde ou verifique mais tarde!
\ No newline at end of file
diff --git a/apps/android/app/src/main/res/values-ru/strings.xml b/apps/android/app/src/main/res/values-ru/strings.xml
index 376334dfb..c61e70b49 100644
--- a/apps/android/app/src/main/res/values-ru/strings.xml
+++ b/apps/android/app/src/main/res/values-ru/strings.xml
@@ -514,13 +514,15 @@
Аудио- и видеозвонки
Ваши звонки
- Соединяться через сервер (relay)
+ Всегда соединяться через relay
Звонки на экране блокировки:
Принимать
Показывать
Выключить
Ваши ICE серверы
WebRTC ICE серверы
+ Relay сервер защищает ваш IP адрес, но может отслеживать продолжительность звонка.
+ Relay сервер используется только при необходимости. Другая сторона может видеть ваш IP адрес.
Откройте SimpleX Chat\nчтобы принять звонок
Вы можете разрешить принимать звонки на экране блокировки через Настройки.
@@ -1039,4 +1041,15 @@
Уменьшенное потребление батареи
Отдельные транспортные сессии
удалено
+ удалено %s
+ Удалить сообщение участника\?
+ Модерировать
+ Сообщение будет удалено для всех членов группы.
+ Сообщение будет помечено как удаленное для всех членов группы.
+ Пожалуйста, свяжитесь с админом группы.
+ Вы не можете отправлять сообщения!
+ только чтение сообщений
+ читатель
+ Роль при вступлении
+ Ошибка обновления ссылки группы
\ No newline at end of file
diff --git a/apps/android/app/src/main/res/values-bg/strings.xml b/apps/android/app/src/main/res/values-uk/strings.xml
similarity index 100%
rename from apps/android/app/src/main/res/values-bg/strings.xml
rename to apps/android/app/src/main/res/values-uk/strings.xml
diff --git a/apps/android/app/src/main/res/values-zh-rCN/strings.xml b/apps/android/app/src/main/res/values-zh-rCN/strings.xml
index 4319a9de7..87f833b99 100644
--- a/apps/android/app/src/main/res/values-zh-rCN/strings.xml
+++ b/apps/android/app/src/main/res/values-zh-rCN/strings.xml
@@ -9,9 +9,9 @@
接受
接受
接受
- 1月
+ 1个月
1周
- 强化
+ 色调
已接受通话
接受
通过 SOCKS 代理访问服务器在端口9050?允许该选项前必须开始代理。
@@ -38,7 +38,7 @@
消息
在此后删除消息
消息
- 添加资料
+ 添加个人资料
所有聊天记录和消息将被删除——这一行为无法撤销!
所有聊天记录和消息将被删除——这一行为无法撤销!只有您的消息会被删除。
允许发送语音消息。
@@ -67,7 +67,7 @@
通过联系人链接连接?
通过群组链接连接?
通过群组链接/二维码连接
- 通过中继连接
+ 通过中继连接
允许您的联系人不可撤回地删除已发送消息。
联系人允许
仅有您的联系人许可后才允许语音消息。
@@ -99,7 +99,7 @@
应用程序版本
应用程序数据备份
应用程序图标
- 一个随机资料将被发送到收到您链接的联系人那里
+ 随机配置文件将发送给您从中收到此链接的联系人
应用程序版本:v%s
仅在运行时应用程序可以接受通知,不会启动后台服务
一个随机资料将发送给您的联系人
@@ -185,20 +185,20 @@
隐身聊天
加入群组
加入隐身聊天
- 隐身聊天模式
- 这里不支持隐身聊天模式——您的主要资料将被发送给群组成员
+ 隐身模式
+ 此处不支持隐身模式——您的主要个人资料将发送给群组成员
点击开始一个新聊天
您的随机资料
- 通过联系人地址链接隐身聊天
- 通过群组链接隐身聊天
+ 通过联系地址链接隐身
+ 通过群组链接隐身
您分享了一次性链接隐身聊天
点击以加入隐身聊天
您的聊天资料将被发送给群组成员
- 您正在尝试邀请与您共享隐身聊天资料的联系人加入您使用主要资料的群组
- 隐身聊天模式可以保护您的主要资料名和头像的隐私——为每个新联系人创建一个新的随机资料。
- 您正在为该群组使用隐身聊天资料——为防止共享您的主要资料,邀请联系人是不允许的
+ 您正在尝试邀请与您共享隐身个人资料的联系人加入您使用主要个人资料的群组
+ 隐身模式可以保护你的主要个人资料名称和图像的隐私——对于每个新的联系人,都会创建一个新的随机个人资料。
+ 您正在为该群组使用隐身个人资料——为防止共享您的主要个人资料,不允许邀请联系人
您的聊天资料将被发送给您的联系人
- 通过一次性链接隐身聊天
+ 通过一次性链接隐身
只有群主可以启用语音信息。
您的隐私设置
隐私和安全
@@ -218,7 +218,7 @@
数据库密码不同于保存在密钥库中的密码。
数据库加密密码将被更新并存储在密钥库中。
数据库将被加密,密码存储在密钥库中。
- 在密匙库中没有找到密码,请手动输入。如果你使用备份工具恢复了应用程序的数据,可能会发生这种情况。如果不是这种情况,请联系开发者。
+ 在密匙库中没有找到密码,请手动输入。如果您使用备份工具恢复了应用程序的数据,可能会发生这种情况。如果不是这种情况,请联系开发者。
从密钥库中删除密码?
在密钥库中保存密码
SimpleX Chat 服务
@@ -335,7 +335,7 @@
已检查联系人
联系人已隐藏:
联系人尚未连接!
- 背景图标
+ 上下文图标
复制到剪贴板
连接中……
创建群组链接
@@ -373,7 +373,7 @@
群组已删除
将为所有成员删除群组——此操作无法撤消!
直接
- 私聊
+ 直接信息
已启用
群组成员可以发送语音消息。
群组链接
@@ -441,7 +441,7 @@
启用自动删除消息?
用于控制台
此群中禁止成员之间私聊。
- 此群组中禁止显示限时消息。
+ 该组禁止限时消息。
群组成员可以不可撤回地删除已发送的消息。
群组成员可以私信。
限时消息
@@ -463,7 +463,7 @@
本地名称
无效的消息格式
无效数据
- LIVE
+ 实时
已邀请连接
无效的连接链接
斜体
@@ -540,7 +540,7 @@
通知
正在接收消息……
要接收通知,请输入数据库密码
- 为了保护您的隐私,该应用程序没有推送通知,而是一个 SimpleX 后台服务 ——它每天使用百分之几的电池。
+ 为了保护您的隐私,该应用程序没有推送通知,而是具有 SimpleX 后台服务 ——它每天使用百分之几的电池。
您的设置
为了使用它,请 禁用电池优化为SimpleX在下一个对话框。否则通知将被禁用。
通知预览
@@ -549,7 +549,7 @@
在应用程序打开时运行
显示预览
该应用程序会定期获取新消息——它每天会消耗百分之几的电量。该应用程序不使用推送通知——您设备中的数据不会发送到服务器。
- 你的联系人可以允许完全删除消息。
+ 您的联系人可以允许完全删除消息。
已发送的消息将在设定的时间后被删除。
您的聊天数据库
密码错误!
@@ -576,7 +576,7 @@
更新
打开 SimpleX Chat 来接听电话
视频通话
- %1$s 想通过该方式与您联系
+ %1$s 想通过以下方式与您联系
拒接来电
点对点
错误:%s
@@ -592,12 +592,12 @@
您的聊天资料
未接来电
待定来电
- 您的聊天资料存储在本地,只在您的设备上
+ 您的聊天资料存储在本地,仅存储在您的设备上
除非您的联系人已删除此连接或此链接已被使用,否则它可能是一个错误——请报告。
\n如果要连接,请让您的联系人创建另一个连接链接,并检查您的网络连接是否稳定。
您已经连接到 %1$s!。
您的聊天资料将被发送
-\n给你的联系人
+\n给您的联系人
资料和服务器连接
更新网络设置?
只有您可以不可撤回地删除消息(您的联系人可以将它们标记为删除)。
@@ -615,7 +615,7 @@
已更新的群组资料
已删除 %1$s
您删除了 %1$s
- 您的资料将被发送到收到您链接的联系人那里。
+ 您的个人资料将发送给您收到此链接的联系人。
正在尝试连接到用于从该联系人接收消息的服务器(错误:%1$s)。
您已连接到用于接收该联系人消息的服务器。
您分享了一次性链接
@@ -644,4 +644,341 @@
多个聊天资料
数据库不能正常工作。点击了解更多
消息传递错误
+ %d 小时
+ %d 小时
+ %d 月
+ %d 秒
+ SimpleX 是如何工作的
+ 确保 WebRTC ICE 服务器地址格式正确、每行分开且不重复。
+ 许多人问:如果SimpleX没有用户标识符,它是怎样传递信息的?
+ 确保 SMP 服务器地址格式正确、每行分开且不重复。
+ Markdown 帮助
+ 标记为已验证
+ 建立私密连接
+ 以 %s 身份加入
+ 如果您收到 SimpleX Chat 邀请链接,您可以在浏览器中打开它:
+ 标记为已读
+ 标记为未读
+ 在消息中使用 Markdown
+ 文件:%s
+ 成员将被移出群组——此操作无法撤消!
+ 消息草稿
+ k
+ 标记为已删除
+ %d 星期
+ 您将停止接收来自该群组的消息。聊天记录将被保留。
+ 成员
+ 成员
+ %d 星期
+ %d 分钟
+ %d 月
+ 网络和服务器
+ 网络设置
+ 已被管理员移除
+ Onion 主机将在可用时使用。
+ 将不会使用 Onion 主机。
+ 📱 手机:点击在手机应用程序中打开,然后在应用程序中点击连接。
+ 消息将被标记为删除。收件人将能够揭示此消息。
+ 更多改进即将推出!
+ 未选择联系人
+ 一次性邀请链接
+ 关闭
+ 连接需要 Onion 主机。
+ Onion 主机将在可用时使用。
+ 从不
+ 已提供 %s
+ 已提供 %s:%2s
+ %s 中的新功能
+ 一次性邀请链接
+ 好的
+ 没有细节
+ (仅由群组成员存储)
+ 只有您可以发送语音消息。
+ 消息将被删除——此操作无法撤消!
+ 一次只能发送10张图片
+ 更多
+ 新数据库存档
+ 旧数据库存档
+ 新成员角色
+ 没有联系人可添加
+ 网络状态
+ 关闭
+ 将不会使用 Onion 主机。
+ 连接需要 Onion 主机。
+ 没有收到或发送的文件
+ 发送人已取消文件传输。
+ 分享
+ 发送实时消息
+ 此文本在设置中可用
+ 未读
+ 保存的 WebRTC ICE 服务器将被删除。
+ %s 已验证
+ 用于新连接
+ 使用直接互联网连接?
+ 必要
+ 保存并通知联系人
+ 保存并通知联系人
+ 在我们的 GitHub 仓库中阅读更多内容。
+ 拒绝
+ 为了保护隐私,而不是所有其他平台使用的用户 ID,SimpleX 具有消息队列的标识符,每个联系人都是分开的。
+ TCP 连接超时
+ 收到,禁止
+ 设定1天
+ SimpleX Chat 安全性由 Trail of Bits 审核。
+ 在浏览器中打开链接可能会降低连接的隐私和安全性。SimpleX 上不受信任的链接将显示为红色。
+ 恢复数据库备份后请输入之前的密码。 此操作无法撤消。
+ 请更新应用程序并联系开发者。
+ 开源协议和代码——任何人都可以运行服务器。
+ 粘贴
+ PING 次数
+ 禁止发送语音消息。
+ PING 间隔
+ 请检查您使用的链接是否正确,或者让您的联系人给您发送另一个链接。
+ 协议超时
+ 拒绝
+ 回复
+ 重置为默认
+ 运行聊天程序
+ 保存存档
+ 扫码
+ 从您联系人的应用程序中扫描安全码。
+ 安全码
+ 秘密
+ 安全评估
+ SimpleX 消息
+ %s 未验证
+ 感谢用户——通过 Weblate 做出贡献!
+ 第一个没有任何用户标识符的平台——专为隐私保护设计。
+ 该小组是完全分散式的——它只对成员可见。
+ 图像无法解码。 请尝试不同的图像或联系开发者。
+ 主题
+ 此操作无法撤消——所有接收和发送的文件和媒体都将被删除。 低分辨率图片将保留。
+ 角色将更改为“%s”。 该成员将收到新的邀请。
+ 此操作无法撤消——早于所选的发送和接收的消息将被删除。 这可能需要几分钟时间。
+ 此二维码不是链接!
+ 此功能是实验性的! 它仅在其他客户端安装了 4.2 版时才有效。 地址更改完成后,您应该会在对话中看到该消息——请检查您是否仍能收到来自该联系人(或群组成员)的消息。
+ 此链接不是有效的连接链接!
+ 开始新的聊天
+ 要与您的联系人验证端到端加密,请比较(或扫描)您设备上的代码。
+ 取消静音
+ 更新 .onion 主机设置?
+ 更新传输隔离模式?
+ (从剪贴板扫描或粘贴)
+ 保护队列
+ 揭示
+ 打开
+ 发送失败
+ 未经授权发送
+ 太多图片!
+ 待办的
+ 切换接收地址吗?
+ 请让您的联系人启用发送语音消息。
+ 录制语音消息
+ 发消息
+ 重置
+ 发送
+ 发送实时消息——它会在您键入时为收件人更新
+ 开始新聊天
+ (与您的联系人分享)
+ 通过链接连接
+ 设置联系人姓名
+ 您接受的连接将被取消!
+ 您与之共享此链接的联系人将无法连接!
+ 显示二维码
+ 发送问题和想法
+ 保护您的隐私和安全的消息传递和应用程序平台。
+ 删去
+ 人们只能通过您共享的链接与您建立联系。
+ 下一代私密通讯软件
+ 粘贴收到的链接
+ 已跳过消息
+ 支持 SIMPLEX CHAT
+ 发送链接预览
+ SOCKS 代理
+ 停止聊天程序?
+ 停止聊天以便导出、导入或删除聊天数据库。在聊天停止期间,您将无法收发消息。
+ 恢复数据库备份
+ 恢复数据库备份?
+ 删除成员
+ 发送私信
+ 接收通过
+ 发送通过
+ 服务器
+ 切换接收地址
+ 保留最后的消息草稿及其附件。
+ 私密文件名
+ 感谢用户——通过 Weblate 做出贡献!
+ 传输隔离
+ 将您收到的链接粘贴到下面的框中以与您的联系人联系。
+ 分享邀请链接
+ 此字符串不是连接链接!
+ 给我们发电子邮件
+ SMP 服务器
+ 尚不支持发送文件
+ 正在尝试连接到用于从该联系人接收消息的服务器。
+ 尚不支持接收文件
+ 未知消息格式
+ 测试服务器
+ 测试服务器
+ SimpleX 联系地址
+ SimpleX 一次性邀请
+ SimpleX 群组链接
+ SimpleX 链接
+ 发送人可能已删除连接请求。
+ 预设服务器
+ 二维码
+ 传输隔离
+ 分享链接
+ 选择联系人
+ 跳过邀请成员
+ 转变
+ 禁止发送语音消息。
+ 只有您的联系人可以发送语音消息。
+ 禁止直接向成员发送私信。
+ 保护应用程序屏幕
+ 主题
+ 停止聊天以启用数据库操作。
+ %s 秒
+ 该群组已不存在。
+ 点击加入
+ 停止
+ 重新启动应用程序以使用导入的聊天数据库。
+ 所有者
+ 已删除
+ 角色
+ 秒
+ 恢复
+ 重置颜色
+ 保存颜色
+ 减少电池使用量
+ 为了保护时区,图像/语音文件使用 UTC。
+ 使用聊天
+ 在我们的 GitHub 存储库 中阅读更多内容。
+ 打开聊天控制台
+ Stop chat
+ 权限被拒绝!
+ 点击按钮
+ 感谢您安装 SimpleX Chat!
+ 使用相机
+ 预设服务器地址
+ 扫描服务器二维码
+ 服务器测试失败!
+ 一些服务器未通过测试:
+ 在 GitHub 上加星
+ 保存并通知群组成员
+ 扬声器关闭
+ 扬声器开启
+ 已将您移除
+ 更新设置会将客户端重新连接到所有服务器。
+ 系统
+ 对方会在您键入时看到更新。
+ 查看安全码
+ 语音消息 (%1$s)
+ 等待图像中
+ 欢迎!
+ 欢迎 %1$s!
+ 当您的联系人设备在线时,您将可以连接,请稍等或稍后查看!
+ 评价此应用程序
+ 使用 SOCKS 代理?
+ 使用 SOCKS 代理(端口 9050)
+ %d 个文件,总大小为 %s
+ 您已加入此群组
+ 您被邀请加入群组
+ 默认(%s)
+ 此聊天中禁止语音消息。
+ 语音信息在该群组中被禁用。
+ 验证安全码
+ 使用 SimpleX Chat 服务器。
+ 通过 %1$s
+ 邀请至群组 %1$s
+ SimpleX 地址
+ SimpleX 团队
+ %1$s 成员
+ 是
+ 您可以将您的地址作为链接或二维码共享——任何人都可以连接到您。 如果您以后删除它,您不会丢失您的联系人。
+ 您可以控制通过哪些服务器接收消息,您的联系人 - 您用来向他们发送消息的服务器。
+ 您的联系人可以从应用程序中扫描二维码。
+ 您将在组主设备上线时连接到该群组,请稍等或稍后再检查!
+ 当您启动应用或在应用程序驻留后台超过30 秒后,您将需要进行身份验证。
+ 创建于 %1$s
+ 您可以 连接到 SimpleX Chat 开发者提出任何问题并接收更新 。
+ 您已接受连接
+ 您的 SMP 服务器
+ %1$d 已跳过消息
+ %ds
+ 更新内容
+ 您被邀请加入群组
+ 您没有聊天记录
+ 等待图像中
+ 语音消息
+ 语音消息禁止发送!
+ 您需要允许您的联系人发送语音消息才能发送它们。
+ 扫描二维码
+ 您邀请了您的联系人
+ 想要与您连接!
+ 您的联系人需要在线才能完成连接。
+\n您可以取消此连接并删除联系人(稍后尝试使用新链接)。
+ SimpleX 标志
+ 您的 SimpleX 联系地址
+ 为终端安装 SimpleX Chat
+ 使用 SimpleX Chat 服务器?
+ 我们不会在服务器上存储您的任何联系人或消息(一旦发送)。
+ WebRTC ICE 服务器
+ 中继服务器保护您的 IP 地址,但它可以观察通话的持续时间。
+ 中继服务器仅在必要时使用。其他人可能会观察到您的IP地址。
+ 您的 ICE 服务器
+ 视频关闭
+ 您可以通过应用设置/数据库或重启应用开始聊天。
+ 您将 %s 的角色更改为 %s
+ 您将自己的角色更改为 %s
+ 您已更改地址
+ 您可以共享链接或二维码——任何人都可以加入该群组。如果您稍后将其删除,您不会失去该组的成员。
+ 间接(%1$s)
+ 您也可以通过点击链接进行连接。 如果它在浏览器中打开,请单击在移动应用程序中打开按钮。
+ SimpleX
+ 您将加入此链接指向的群组并连接到其群组成员。
+ 通过群组链接
+ 通过一次性链接
+ 通过联系地址链接
+ 通过浏览器
+ 您的服务器
+ 当可用时
+ 使用 .onion 主机
+ 您的 ICE 服务器
+ simplexmq: v%s (%2s)
+ 欢迎消息
+ 您的聊天由您掌控!
+ 您可以使用 markdown 来编排消息格式:
+ %dh
+ %d 天
+ %dw
+ 您被邀请加入群组。 加入以与群组成员联系。
+ 你加入了这个群组。连接到邀请组成员。
+ 您更改了 %s 的地址
+ 您已离开
+ %1$d 已选择联系人
+ 您允许
+ 带有可选的欢迎消息。
+ %dm
+ %dmth
+ 等待文件中
+ 您的联系人发送的文件大于当前支持的最大大小 (<xliff:g xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\" id=\"maxFileSize\">%1$s</xliff :g>).
+ 当您的连接请求被接受后,您将可以连接,请稍等或稍后检查!
+ 使用服务器
+ 您的服务器地址
+ 视频开启
+ 最多 40 秒,立即收到。
+ 验证连接安全
+ 由 %s 审核
+ 管理员移除
+ 将为所有成员删除该消息。
+ 该消息将对所有成员显示为已审核。
+ 删除成员消息?
+ 观察者
+ 您是观察者
+ 更新群组链接错误
+ 您无法发送消息!
+ 初始角色
+ 请联系群组管理员。
+ 系统
\ No newline at end of file
diff --git a/apps/android/app/src/main/res/values-zh-rTW/strings.xml b/apps/android/app/src/main/res/values-zh-rTW/strings.xml
index 61b0068fd..3fb0959a6 100644
--- a/apps/android/app/src/main/res/values-zh-rTW/strings.xml
+++ b/apps/android/app/src/main/res/values-zh-rTW/strings.xml
@@ -6,7 +6,7 @@
關於 SimpleX
接受
接受
- 1 天
+ 1 日
1 個月
接受
關於 SimpleX Chat
@@ -23,16 +23,16 @@
顯示名稱:
全名:
使用更多電量!通知服務會長期在背景中運行 – 一但有訊息就會顯示在通知內。
- 對電池好。通知服務會每十分鐘檢查一次。你可能會錯過電話通話或訊息。
- 回應電話請求
+ 對電量也不錯。通知服務會每十分鐘運行一次。你可能會錯過電話通話或訊息。
+ 回應通話請求
清除
- 允許在群組內選擇成員後傳送訊息
+ 允許在群組內選擇成員後傳送訊息。
%d 秒
已取消 %s
自動接受聯絡人請求
- 匿名者模式在這𥚃是不支援 - 你的真實帳號名稱會顯示於群組內
+ 匿名聊天模式在這裡是不支援 - 你的個人檔案會顯示於群組內
不要
- 允許傳送自動銷毀的訊息
+ 允許傳送自動銷毀的訊息。
無法初始化數據庫
一直開啟
關閉 SimpleX 鎖定
@@ -41,20 +41,20 @@
回覆
分享
附件
- 和開發者對話
+ 和開發人員對話
你的對話
- 分享檔案…
+ 分享檔案 …
分享訊息
取消圖片預覽
允許使用語音訊息?
取消
- 取消即時顯示訊息
+ 取消實況訊息
新增新的聯絡人:新增新的聯絡人可以使用二維碼建立你的一次性二維碼。
掃描二維碼:連接到向你出示二維碼的聯絡人。
選擇檔案
使用相機
- 選擇來自圖片庫
- 接受匿名者的對話
+ 從圖片庫選擇圖片
+ 接受匿名聊天模式
所有訊息記錄會刪除 - 這不能還原!這些訊息只會在你裝置中刪除。
清除
要清除對話記錄?
@@ -62,7 +62,7 @@
清除
清除對話記錄
分享邀請連結
- 傳送問題和想法
+ 傳送問題和想法給開發者
如何使用
使用終端機安裝 SimpleX Chat
應用程式版本
@@ -70,53 +70,53 @@
程式建構:%s
應用程式版本:v%s
分享連結
- 自動化
- 你目前的帳號
+ 自動接受所有請求
+ 你目前的個人檔案
顯示的名稱字與字中間不能有空白。
儲存設定?
顯示名稱
全名(可選)
- 通話有錯誤
- 打電話中…
+ 通話出現錯誤
+ 正在撥打 …
通話中
私密
已連接
- 對電池最好。只有在應用程式運行時才收到通知,這樣背景就不會運行通知服務
- 端對端加密視像通話
- 視像通話(沒有端對端加密)
- 通話已經結束了。
+ 對電量最好。 只有在應用程式運行中的時侯才可以接收訊息,後台服務並不會使用。
+ 端對端加密視訊通話
+ 視訊通話(沒有端對端加密)
+ 通話已經結束了!
關閉語音
開啟語音
- 通話已經取消了。
+ 通話已經取消了
通話中
開啟喇叭
通話
主題
語音通話(沒有端對端加密)
語音通話
- 語音 & 視像通話
- 視像通話
+ 語音 & 視訊通話
+ 視訊通話
粗體
- 圖片自動接受
- 備份資料數據
+ 自動接收圖片
+ 備份應用程式資料
應用程式圖示
已匯入對話資料庫
- Android Keystore 是用於安全地儲存密碼 - 確保通知服務的運作
+ Android 金鑰庫是用於安全地儲存密碼 - 確保通知服務的運作
請注意:如果你忘記了密碼你將不能再次復原或更改密碼。
- 當你重新啟動應用程式或更改密碼後, Android Keystore 將會用來安全地儲存密碼 - 確保收到通知。
+ 當你重新啟動應用程式或更改密碼後, Android 金鑰庫將會用來安全地儲存密碼 - 將會允許接到通知。
聊天室已停止運作
- 只有這個群組的創建人才能更改群組內的設定。
+ 只有這個群組的負責人才能更改群組內的設定。
修改
- 加入新帳號
- 所有聊天室和聊天記錄會刪除 - 這不能還原!
- 匿名者
+ 加入新的個人檔案
+ 所有對話和對話記錄會刪除 - 這不能還原!
+ 匿名聊天模式
經常
開啟
你的設定
允許你的聯絡人傳送語音訊息。
- 只能在你的聯絡人允許使用語言訊息的時候才能使用語音訊息。
+ 只能在你的聯絡人允許使用語言訊息的時候,才能使用語音訊息。
你和你的聯絡人都可以傳送自動銷毀的訊息。
- 只有你能傳送可以自動銷毀的訊息。
+ 只有你能傳送自動銷毀的訊息。
你和你的聯絡人都可以傳送語音訊息。
管理員可以建立可以加入群組的連結。
使用二維碼掃描並新增伺服器。
@@ -142,7 +142,7 @@
關閉
好
自動銷毀訊息
- 群組內的會員可以傳送私人訊息。
+ 群組內的成員可以私訊群組內的成員。
%d 分鐘
%d 月
通話結束 %1$s
@@ -153,26 +153,26 @@
\n請注意:如果你有很多連接,你的電話電量和數據流量的消耗率會大大增加,一些連接有機會會連接失敗。
一個單獨的 TCP 連接(和 SOCKS 憑證)將用於<b>每個聊天室的設定<b>。
返回
- 省電模式運行中,關閉了背景通知服務和新訊息的定期檢查。你可以在通知設定內重新啟用。
- 匿名者模式
+ 省電模式運行中,關閉了背景通知服務和定期更新接收訊息。你可以在通知設定內重新啟用。
+ 匿名聊天模式
預設 (%s)
群組設定
聯絡人設定
- 分享圖片…
- 你和你的聯絡人都可以不可逆的刪除已經傳送的訊息
+ 分享圖片 …
+ 你和你的聯絡人都可以不可逆地刪除已經傳送的訊息
已連接
簡介
完整連結
- 你可以通過設定關閉 - 當應用程式運行時,通知服務會仍然運行。
- 背景通知服務會一直運行 - 一但有新訊息就會顯示在通知。
- 程式只會在運行中的時候才會收到訊息的通知,不會在背景中運行通知服務。
+ 你可以通過設定關閉 – 當應用程式運行時,通知服務仍然會運行。
+ 通知服務會一直在後台中運行 - 一但接收到新訊息就會顯示於通知內。
+ 應用程式只會在運行中的時候才會收到訊息的通知。
要求接收圖片
進階網路設定
- 將你的地址修改
- 更改地址中…
- 更改地址中…
+ 已修改你的地址
+ 更改地址中 …
+ 更改地址中 …
自動銷毀訊息
- 開始新對話
+ 開始新對話/創建新群組
建立私密群組
建立一次性邀請連結
(分享給你的聯絡人)
@@ -185,13 +185,13 @@
檢查輸入的伺服器地址然後再試一次。
終端機對話
於 Github 給個星星
- 匿名者模式會保護你的真實帳號名稱和頭像 — 當有新聯絡人的時候會自動建立一個隨機性的名字。
+ 匿名聊天模式會保護你的真實個人檔案名稱和頭像 — 當有新聯絡人的時候會自動建立一個隨機性的個人檔案
這樣就會每一個對話中也擁有不同的顯示名稱並且沒有任何的個人資料可用於分享或有機會外洩
- 要找到用於匿名連接的個人資料,請點擊上方的聯絡人或群組名稱。
- 只有在你的聯絡人允許的情況下才允許自動銷毀訊息。
+ 若要查找用於匿名聊天模式連接的個人檔案,請點擊聯絡人或群組名稱。
+ 只有你的聯絡人允許的情況下,才允許自動銷毀訊息。
允許你的聯絡人傳送自動銷毀的訊息。
- 只有在你的聯絡人允許的情況下,才允許將不可撤銷的訊息刪除。
- 允許你的聯絡人可以不可逆的刪除已發送的訊息。
+ 只有你的聯絡人允許的情況下,才允許不可逆地將訊息刪除。
+ 允許你的聯絡人可以不可逆地刪除已發送的訊息。
允許將不可撤銷的訊息刪除。
允許傳送語音訊息。
多久後刪除
@@ -200,98 +200,98 @@
即時顯示訊息
SimpleX 群組連結
私人檔案名稱
- 傳送訊息時出錯
- 建立帳號失敗!
- 你已經有一個帳號的顯示名稱和現在選擇建立的帳號名稱相同。請顯示其他名稱。
- 切換帳號失敗!
- 在群組新增成員時出錯
+ 加入成員(s) 時出錯
+ 個人檔案建立失敗!
+ 你已經有一個個人檔案的顯示名稱和現在選擇建立的個人檔案名稱相同。請選擇其他名稱。
+ 個人檔案切換失敗!
+ 加入群組時出錯
傳送者已取消傳送檔案。
接收檔案時出錯
建立地址時出錯
- 為了保護時區,圖片或語音文件使用 UTC。
+ 為了保護私人檔案,圖片或語音文件使用 UTC。
目前還不支援傳送檔案
- 目前還不技援接收檔案
+ 目前還不支援接收檔案
你
未知的訊息格式
無效的訊息格式
- 直播
+ 實況
無效聊天
無效數據
連接 %1$d
已建立連線
邀請連線
- 連接中…
- 你建立了一次性的連接連結
- 你建立了使用匿名者模式的一次性連接連結
- SimpleX 聯絡人連結
+ 連接中 …
+ 你分享了一個一次性連結
+ 你分享了一個匿名聊天模式的一次性連結
+ SimpleX 聯絡人地址
SimpleX 一次性連結
SimpleX 連結
通過 %1$s
通過瀏覽器
- 請使用 %1$s 檢查你的網路連線並且再試一次。
+ 傳送訊息時出錯
聯絡人已存在
你已經連接到 %1$s!。
- 通過帳號文件(默認)或通過連接(測試版)。
+ 使用個人檔案(預設值)或使用連接(測試版)。
減少電量使用
更多功能即將推出!
意大利語言界面
感謝用戶 - 使用 Weblate 的翻譯貢獻!
SimpleX
k
- 使用連結連接聯絡人 \?
- 通過邀請的
- 通過使用邀請連結連接 \?
- 你的個人資料將發送給你收到此連結的聯絡人。
+ 透過連結連接聯絡人?
+ 透過邀請連結連接?
+ 通過邀請連結連接群組?
+ 你的個人檔案將會傳送給你收到此連結的聯絡人
你將會加入此連結內的群組並且連接到此群組成為群組內的成員。
連接
錯誤
連接中
- 你已連線到用於接收來自此聯絡人的訊息伺服器。
- 正在嘗試連接到這聯絡人接收的訊息伺服器 (錯誤: %1$s).
- 正在嘗試連接到這聯絡人接收的訊息伺服器。
+ 你已連接到此聯絡人使用的伺服器以接收訊息。
+ 嘗試連接至用於接收此聯絡人訊息的伺服器 (錯誤:%1$s).
+ 正在嘗試連接到用於接收此聯絡人訊息伺服器。
已刪除
已標記為刪除
- 在瀏覽器中開啟連結可能會降低私隱和安全性。不受 SimpleX 信任的連結會顯示紅色。
+ 在瀏覽器中開啟連結可能會有私隱疑慮和不確定性。不受 SimpleX 信任的連結會顯示紅色。
儲存 SMP 伺服器時出錯
- 請確保 SMP 伺服器連結格式正確,行分隔且不重複。
+ 請確保 SMP 伺服器連結格式正確,隔行顯示且不重複。
更新網路配置時出錯
聯絡人載入失敗
多個聯絡人載入失敗
- 請更新應用程式或聯絡開發者。
+ 請更新應用程式或聯絡開發人員。
連線超時
連線失敗
- 請檢查你的網路
+ 請使用 %1$s 檢查你的網路連線並且再試一次。
連接
定期通知已禁用!
需要使用密碼
只顯示聯絡人名稱
- 裝置內的螢幕鎖定已關閉。已關閉 SimpleX 鎖定。
+ 裝置內的螢幕鎖定已關閉。正在關閉 SimpleX 鎖定。
儲存
太多圖片!
- 儲存檣案的時候有錯誤
+ 儲存檔案的時候出現錯誤
有新的聯絡人連線請求
- 登入以使用憑據
+ 確認你的憑據
隱藏
- 檔案已儲存
- 無效連線連結
+ 已儲存檔案
+ 無效的連線連結
傳送者似乎已經刪除了連接的請求。
刪除聯絡人時出錯
刪除群組時出錯
刪除聯絡人
測試在步驟 %s 失敗。
- 伺服器需要投權才能建立佇列,請檢查密碼
+ 伺服器需要授權才能建立佇列,請檢查密碼
伺服器地址的憑證指紋可能不正確
即時收到通知
- 即時收到通知!
- 即時收到通知已禁用!
+ 即時通知!
+ 已禁用即時收到通知!
數據庫目前沒有正常運作。點擊查看更多
- SimpleX Chat 電話
+ SimpleX Chat 電話來電
通知服務
顯示預覽
通知預覽
- 如果你解鎖程式後三十秒後再次啟動或返回應用程式,你將需要進行多一次解鎖程式
+ 如果你解鎖程式三十秒後再次啟動或返回應用程式,你會需要進行多一次解鎖程式。
已解鎖
- 登入
+ 使用你的憑據登入
使用終端機開啟對話
傳送訊息有錯誤
大概你的聯絡人已經刪除了和你的對話並且已經沒有和你有連線。
@@ -303,100 +303,100 @@
傳送失敗
歡迎 %1$s!
歡迎!
- 這文字在設定中可用
- 連接中…
+ 在設定中可用這文字
+ 連接中 …
你被邀請加入至群組
加入為 %s
- 連接中…
+ 連接中 …
點擊開始新對話
- 檔案找不到
+ 找不到檔案
語音訊息 (%1$s)
- 語音訊息…
+ 語音訊息 …
錄製語音訊息
- 你需要允許你的聯絡人傳送語音訊息才能使他們可以傳送給你。
+ 你需要在設定上開放權限允許你的聯絡人傳送語音訊息。
禁止語音訊息!
- 請詢問你的聯絡人啟用語音訊息
- 只有群組的創建人才能啟用語音訊息
+ 請你的聯絡人開放權限允許你傳送語音訊息
+ 只有群組的負責人才能啟用語音訊息
傳送
- 請確定你使用了正確的連結或者叫你的聯絡人傳送一個新的連結給你。
+ 請你確認使用的是正確的連結,或者請你的聯絡人傳送一個新的連結給你。
連接錯誤 (AUTH)
- 除非你的聯絡人刪除了連結或此連結已經被使用,否則它可能是一個錯誤 - 請匯報問題。
-\n要連線,請詢問你的聯絡人建立新的連結和確保你的網路是隱定的。
- 接受聯絡人的連接時出錯
- 刪除聯絡人請求時出錯
+ 除非你的聯絡人刪除了連結或此連結已經被使用,否則它可能是一個錯誤 - 請報告問題。
+\n要連線,請詢問你的聯絡人建立新的連結和確保你的網路是穩定的。
+ 接受聯絡人的連接請求時出錯
+ 刪除待處理的聊絡人連接時出錯
更改地址時出錯
建立佇列
安全佇列
刪除佇列
斷開連接
- 為了使用它,請 禁用電量優化 SimpleX 在下一個對話中。否則,通知將會禁用。
- 在接收通知之前,請你確入數據庫的密碼
+ 為了使用它,請 禁用電量優化 為了 SimpleX 在下一個對話中。否則,將會禁用通知。
+ 在接收通知之前,請你輸入數據庫的密碼
應用程式會定期推送新訊息 — 它每天會消耗百分之幾的電量。 應用程式將不使用推送通知 — 你裝置中的數據不會傳送至伺服器。
SimpleX Chat 服務
- 正在接收訊息…
+ 正在接收訊息 …
隱藏
SimpleX Chat 訊息
當應用程式是開啟
定期啟用
顯示聯絡人名稱和訊息內容
隱藏聯絡人名稱和訊息內容
- 聯絡人隱藏:
+ 隱藏聯絡人:
有新訊息
已連線
SimpleX 鎖定
為了保護你的個人訊息,開啟 SimpleX 鎖定。
-\n在啟用此功能之前,系統將提示您完成螢幕鎖定。
+\n在啟用此功能之前,系統將提示你完成螢幕鎖定的功能。
打開
SimpleX 鎖定已開啟
- 你的裝置沒有啟動螢幕鎖定。您可以通過設定內啟動螢幕鎖定,當你啟動後就可以使用 SimpleX 鎖定
+ 你的裝置沒有啟動螢幕鎖定。你可以通過設定內啟動螢幕鎖定,當你啟動後就可以使用 SimpleX 鎖定。
修改
刪除
- 透露
+ 展露
確定要刪除訊息?
- 訊息會刪除 - 並且不能撤消!
- 訊息將被標記為刪除。 接收訊息的人(多個) 能夠透露此訊息。
- 未讀取
+ 訊息會刪除 - 並且不能還原!
+ 訊息將被標記為刪除。 接收訊息的人(多個) 能夠展露此訊息。
+ 未讀
你沒有聯絡人
解碼錯誤
- 你的的聯絡人傳送的檔案大於目前支持的最大大小 (%1$s).
+ 你的聯絡人傳送的檔案大於目前支持的最大上限 (%1$s).
只能同一時間傳送十張圖片
- 目前支援的最大檣案大小為 %1$s。
- 下載檔案需要傳送者上線的時候才能下載檣案,請等待對方上線!
+ 目前支援的最大檔案大小為 %1$s。
+ 下載檔案需要傳送者上線的時候才能下載檔案,請等待對方上線!
語音訊息
- 傳送即時顯示的訊息
- 即時顯示的訊息!
- 傳送即時顯示的訊息 - 這會即時顯示你在輸入中的文字
+ 傳送實況的訊息
+ 實況訊息!
+ 傳送實況訊息 - 這會即時顯示你在輸入中的文字
(掃描或使用剪貼薄貼上)
- 沒有權限
- 為了保護你的私隱,即時推送通知沒有使用 <b xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\"><xliff:g id=\"appName\">SimpleX</xliff:g> 後台通知 - 它每天會使用你電量的百分之幾。
+ 權限拒絕!
+ 為了保護你的私隱,<b xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">SimpleX</xliff:g> 有一個後台通知服務 – 它每天使用你幾 % 的電量。
定期通知
- 每十分鐘會檢查一次訊息,最多一分鐘
+ 每十分鐘會檢查一次訊息,最快可設為每分鐘檢查一次
訊息文字
聯絡人名稱
隱藏
圖片
等待圖片
- 圖片已傳送
+ 已傳送圖片
等待圖片
下載圖片需要傳送者上線的時候才能下載圖片,請等待對方上線!
圖片已儲存至圖片庫
通知
- 刪除聯給人?
+ 刪除聯絡人?
此聯絡人和此聯絡人的訊息會全部刪除 - 這不能還原!
刪除聯絡人
- 設置聯絡人名稱…
+ 設置聯絡人名稱 …
已連接
已斷開連接
錯誤
待處理
- 切換接收位置?
+ 切換接收地址?
此功能目前還是實驗階段! 對方需要使用 4.2 版本或更高的版本才能成功生效。 地址更改後,你會在對話中看到新訊息 - 請測試你更改地址後是否仍然能夠收到來自這聯絡人(或群組內的成員)的訊息。
查看安全碼
驗證安全碼
傳送訊息
- 刪除帳號時出現錯誤
+ 刪除個人檔案時出現錯誤
停止對話
- 圖片不能解碼,嘗試其他圖片或聯絡開發者。
+ 圖片不能解碼,嘗試其他圖片或聯絡開發人員。
檔案
大型檔案
等待檔案中
@@ -406,12 +406,12 @@
沒有詳細資料
一次性邀請連結
已複製至你的剪貼薄
- 使用連結連接 / 使用二維碼
+ 使用連結連接 / 使用二維碼連接
掃描二維碼
- (僅由群組成員存儲)
+ (僅由群組成員儲存)
想和你對話!
SimpleX 徽標
- 通過使用連結連接
+ 透過連結連接
預設伺服器
你的伺服器位址
刪除伺服器
@@ -419,15 +419,15 @@
ICE 伺服器(每行一個)
使用 .onion 主機
Onion 主機不會啟用
- 當有的時候,Onion 將會啟用。
+ 當有的時候 Onion 將會啟用。
Onion 主機不會被啟用。
連接
- 刪除址址
+ 刪除地址
顏色
如何使用你的伺服器
核心建立於:%s
使用連結連接
- 💻 桌面版:於程式內掃描一個已存在的二維碼,通過使用二維碼掃描。
+ 💻 桌面版:於應用程式內掃描一個已存在的二維碼,透過二維碼掃描。
設定
這個二維碼不是一個連結!
建立一次性邀請連結
@@ -440,7 +440,7 @@
編輯圖片
斜體
已拒絕收聽電話
- 連接收聽電話 …
+ 連接電話中 …
等待對方確定 …
貢獻
評價程式
@@ -452,9 +452,9 @@
刪除圖片
開始中 …
等待對方回應 …
- 如果你收到 <xliff:g xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\" id=\"appName\">SimepleX Chat 邀請連結,你可以使用瀏覧器開啟
+ 如果你收到 <xliff:g xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\" id=\"appName\">SimepleX Chat 邀請連結,你可以使用瀏覧器開啟:
📱 電話版: 點擊 使用電話程式開啟, 然後在程式內點擊 連接.
- 如果您選擇拒絕傳送者將不會收到通知。
+ 如果你選擇拒絕,傳送者將不會收到通知。
拒絕
刪除
刪除
@@ -463,7 +463,7 @@
你的聯絡人需要上線才能連接成功。
\n你可以取消此連線和刪除此聯絡人(或者你可以稍後使用新的連結再試一次)。
二維碼
- 個人檣案頭像
+ 個人檔案頭像
預覧連結圖片
SimpleX 地址
幫助
@@ -475,22 +475,23 @@
無效連結!
這個連結不是一個有效的連接連結!
連線請求已傳送
- 當群組的建立人上線的時侯便會成功連接至群組,請耐心等待!
- 當你的連接請求已被接受的時候便會連接成功,請耐心等待!
- 當你的聯絡人上線後便會連線成功,請耐心等待!
- 如果你沒有聯絡人,於視像對話內出示你的二維碼,或者分享連結。
- 你的個人檔案會傳送結你的聯絡人!
- 將你收到的連結貼上至下面的框內以與你的聯絡人對話。
- 如果你沒有聯絡人,你可以於 視像對話中掃描二維碼,或者你可以分享一個邀請連結給你的聯絡人。
+ 當群組的建立人上線,你便會成功連接至群組,請耐心等待!
+ 當你的連接請求已被接受,你便會連接成功,請耐心等待!
+ 當你的聯絡人上線,你便會連線成功,請耐心等待!
+ 如果你不能面對面接觸此聯絡人,可於視訊通話中出示你的二維碼,或者分享連結。
+ 你的個人檔案會傳送給
+\n你的聯絡人
+ 將你收到的連結貼上至下面的框內以開展你與你的聯絡人對話。
+ 如果你不能面對面接觸此聯絡人,你可以於 視訊對話中掃描二維碼,或者你可以分享一個邀請連結給此聯絡人。
你的個人檔案會傳送給你的聯絡人。
貼上
這些字串不是連接連結!
- 你也可以點擊連結連接。如果連線於瀏覧器中開啟,點擊 於程式內開啟 按扭。
+ 你也可以點擊連結連接。如果在瀏覧器中開啟,點擊 程式內的開啟 按扭。
一次性邀請連結
你的聯絡人地址
掃描二碼碼
錯誤的安全碼!
- 於你的聯絡人的程式內掃描安全碼
+ 在你聯絡人的程式內掃描安全碼
安全碼
Markdown 幫助
於訊息中使用 Markdown 語法
@@ -498,10 +499,10 @@
使用伺服器
用於新的連接
無效的伺服器地址
- 此伺服器用於你目前的帳號
+ 此伺服器用於你目前的個人檔案
使用 SimpleX Chat 伺服器?
你的 SMP 伺服器
- 目前使用中 SimpleX Chat 伺服器。
+ 目前使用 SimpleX Chat 伺服器。
如何?
配置 ICE 伺服器
已儲存的 WebRTC ICE 伺服器將會移除。
@@ -526,33 +527,33 @@
你錯過了電話
收到回應 …
連接中 …
- 完結
- 你的聯絡人可以使用二維碼在此程式
+ 通話完結
+ 你的聯絡人可以在程式內使用二維碼
連接
網路 & 伺服器
網路設定
使用 SOCKS 代理伺服器 (端口 9050)
使用 SOCKS 代理伺服器
儲存並通知群組內的聯絡人
- 退出並且不儲存
- 對話由你控制!
- 一個保護你的隱私和傳送安全的通訊應用程式平台。
+ 退出並且不儲存紀錄
+ 你的對話由你控制!
+ 一個保護你的隱私和傳送安全通訊的應用程式平台。
我們不會在伺服器內儲存你的任何聯絡人和消息(一旦傳送)。
- 建立帳號
+ 建立個人檔案
回應已確認
- 您可以透過 連接到 SimpleX Chat 開發人員提出任何問題並接收更新。
+ 你可以透過 連接到 SimpleX Chat 開發人員提出任何問題並同意更新。
開始新的對話
- 設定聯聯人名稱
+ 設定聯絡人名稱
你已邀請了你的聯絡人
你接受了連接
刪除等待中的連接?
- 當聯絡人發現此連結後,嘗試點擊的聯絡人將無法連結!
+ 當聯絡人發現此連結後,嘗試點擊的聯絡人將無法連線!
你所接受的連接將會被取消!
你的聯絡人現在還沒連接!
關閉按鈕
已標記為已驗證
- 清除驗認
- 如要和你的聯絡人驗認端對端加密,於對方的程式內掃描二維碼。
+ 清除驗證
+ 如果要和你的聯絡人驗認端對端加密,在對方的程式內掃描二維碼。
%s 已驗證
%s 並未驗證
你的 SimpleX 聯絡地址
@@ -571,4 +572,415 @@
你的伺服器
連接時將會需要使用 Onion 主機
對話檔案
+ 透過群組連結
+ 透過群組連結使用匿名聊天模式
+ 一個使用了匿名聊天模式的人透過連結加入了群組。
+ 透過使用一次性連結匿名聊天模式連線
+ 有很多人問:
+\nxmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">如果 <xliff:g id=\"appName\">SimpleX</xliff:g> 沒有任何的用戶標識符,應如何傳送訊息?</i>
+ 即時
+ 定期的
+ 關閉
+ 你錯過此通話
+ 開啟
+ 開啟
+\n
+\nSimpleX Chat 以接受通話
+ 已拒絕通話
+ 顯示
+ 連線通話中
+ 檔案及媒體檔案
+ 重新啟動應用程式以建立新的個人檔案。
+ 刪除所有檔案及媒體檔案?
+ 刪除所有你的個人檔案及媒體檔案
+ %d 檔案(s) 的總共大小為 %s
+ 訊息
+ 永不
+ 沒有收到或傳送的檔案
+ 目前的密碼 …
+ 加密
+ 修改設定時出錯
+ 通知服務只會在應用程式關閉前才會傳送
+ 移除
+ 要從金鑰庫移除密碼?
+ 確定新密碼 …
+ 新密碼 …
+ 點擊加入
+ 點擊以使用匿名聊天模式加入
+ %s 的身份已更改為 %s
+ 已經邀請 %1$s
+ 已更改你的身份為 %s
+ 連線中 (已接受)
+ 退出
+ 負責人
+ 已移除
+ 完成
+ 連線中
+ 創建人
+ 新成員的身份
+ %1$s 位成員
+ 修改群組內的設定
+ 建立群組連結
+ 建立連結
+ 確定要刪除連結?
+ 移除成員
+ 成員的身份會修改為 \"%s\". 所有在群組內的成員都會收到通知
+ 成員的身份會修改為 \"%s\". 該成員將收到新的邀請
+ 網路狀態
+ 重置為預設值
+ 當你與某人分享已啟用匿名聊天模式的個人檔案時,此個人檔案將用於他們邀請你參加的群組。
+ 黑暗
+ 為所有人刪除
+ 語音訊息
+ 提供 %s: %2s
+ 安全性評估
+ SimpleX Chat 的安全性由 Trail of Bits 審核。
+ 群組連結
+ 多個個人檔案
+ 不同的名稱、頭像和傳輸隔離。
+ 訊息草稿
+ 保留最後一則帶附件的訊息草稿。
+ 傳輸隔離
+ 匯入對話資料庫?
+ 請放置你的密碼於安全的地方,如果你遺失了密碼將不可能再次存取它。
+ 鎖匙鏈錯誤
+ 沒有聯絡人可選擇
+ 群組連結
+ 刪除連結
+ 群組是完全去中心化的 - 只有群組內的成員能看到。
+ 禁止傳送自動銷毀的訊息。
+ %d 個小時
+ 新功能
+ 帶有可選即時性的訊息
+ 刪除數據庫時出現錯誤
+ 匯入
+ %s 秒(s)
+ 加密數據庫時出錯
+ 於金鑰庫儲存密碼
+ 建立於 %1$s
+ 還原數據庫的備份
+ 群組為不活躍狀態
+ 邀請連結過時!
+ 刪除群組?
+ 群組會於所有成員刪除 - 這不能還原!
+ 群組只會為你刪除 - 這不能還原!
+ 你可以分享連結或二維碼 - 任何人也可以加入至此群組內。直到你刪除群組前你也不會失去遺失群組。
+ 你的個人檔案會傳送給群組內的成員。
+ 儲存群組檔案
+ 語音訊息於這個聊天窒是禁止的。
+ 允許你的聯絡人可以完全刪除訊息
+ 第一個沒有任何用戶識別符的通訊平台 – 以私隱為設計
+ 新一代的私人訊息平台
+ 去中心化的
+ 人們只能在你分享了連結後,才能和你連接。
+ 重新定義私隱
+ 建立你的個人檔案
+ 這是如何運作
+ 你可以之後透過設定修改。
+ 私下連接
+ 開放源碼協議和程式碼 – 任何人也可以運行伺服器。
+ 無視
+ 語音通話來電
+ 貼上你收到的連結
+ 已經端對端加密
+ 沒有端對端加密
+ 關閉喇叭
+ 訊息
+ 私隱 & 安全性
+ 主題
+ 法國語言界面
+ 已經完成端對端加密的語音通話
+ 拒絕
+ 錯誤的訊息 ID
+ 重覆的訊息
+ 保護應用程式螢幕
+ 傳送可以預覽的連結
+ 開發
+ 裝置
+ 幫助
+ 設定
+ 幫助 SIMPLEX CHAT
+ 對話
+ 開發者工具
+ SOCKS 代理伺服器
+ 重新啟動應用程式以匯入對話數據庫
+ 刪除所有檔案
+ 啟用自動銷毀訊息?
+ 刪除訊息
+ 於多久後刪除訊息
+ 請輸入正確的密碼。
+ 已受加密的數據庫密碼是使用隨機性的文字,你可以修改它。
+ 數據庫將會加密。
+ 數據庫將會加密並且密碼會儲存於金鑰庫。
+ 數據庫錯誤
+ 不能讀取金鑰庫以儲存資料庫密碼
+ 錯誤:%s
+ 檔案:%s
+ 需要數據庫的密碼以開啟對話。
+ 輸入密碼 …
+ 輸入正確的密碼。
+ 開啟對話
+ 儲存密碼和開啟對話
+ 你未完成更改數據庫密碼的程序
+ 密碼不存在於金鑰庫,請手動輸入它,有這種情況你可能是使用了備份用的工具。如果不是請聯絡開發人員。
+ 還原
+ 還原數據庫的備份?
+ 還原數據庫時出錯
+ 儲存存檔
+ 刪除封存
+ 加入
+ 確定要加入群組?
+ 加入匿名聊天模式
+ 加入群組中
+ 群組的邀請連結無效,這連結已經被群組管理人刪除。
+ 群組邀請連結已經過時
+ 移除了你
+ 沒有聯絡人可以新增
+ 選擇聯絡人
+ 跳過邀請成員
+ 邀請至群組
+ 邀請成員
+ 你正在嘗試邀請一位與你分享了匿名聊天模式的聯絡人進入至你正在使用真實的個人檔案聊天模式的群組中
+ 建立群組邀請連結時出錯
+ 刪除群組邀請連結時出錯
+ 為終端機
+ 本機名稱
+ 數據庫 ID
+ 身份
+ 傳送私人訊息
+ 成員將會被移除於此群組 - 這不能還原!
+ 移除
+ 成員
+ 修改身份
+ 切換
+ 修改這位成員的身份?
+ 切換接收訊息的伺服器
+ 群組的檔案只會儲存於成員內的本機裝置,不會儲存於伺服器內。
+ 秒
+ TCP 連線超時
+ 協議超時
+ PING 的間隔時間
+ PING 的次數
+ 啟用 TCP keep-alive
+ 儲存
+ 更新網路設定?
+ 更新
+ 你的個人檔案只會儲存於你的本機裝置內。
+ 更新設定會將客戶端重新連接到所有的伺服器。
+ 刪除個人檔案?
+ 刪除個人檔案
+ 檔案和伺服器連接
+ 只有本機檔案
+ 你的隨機個人檔案
+ 隨機的個人檔案將會傳送給你的聯絡人。
+ 系統
+ 明亮
+ 重設顏色
+ 關閉
+ 已接收,已禁止
+ 設定為一日
+ 聯絡人可以標記訊息為已刪除;你可以看到
+ 禁止傳送語音訊息
+ 只有你的聯絡人可以傳送自動銷毀的訊息
+ 自動銷毀訊息已被禁止於此聊天室。
+ 此聊天室禁止不可逆的訊息刪除。
+ 只有你可以傳送語音訊息。
+ 私訊群組內的成員於這個群組內是禁用的。
+ 群組內的成員可以不可逆地刪除訊息。
+ 語音訊息
+ 改善伺服器配置
+ 當你切換至最近應用程式版面時,程式畫面會自動隱藏,無法預覽。
+ 運行對話
+ 數據庫密碼
+ 匯出數據庫
+ 匯入資料庫
+ 新的數據庫存檔
+ 舊的數據庫存檔
+ 刪除數據庫
+ 開始新對話時出錯
+ 停止對話?
+ 設定密碼以匯出
+ 停止對話時出現錯誤
+ 已受加密的數據庫是使用一個隨機性的文字。請在修改前將它匯出。
+ 匯出數據庫時出現錯誤
+ 匯入數據庫時出現錯誤
+ 受加密的數據庫密碼會再次更新。
+ 刪除封存對話?
+ 加密數據庫?
+ 邀請至群組 %1$s
+ 邀請成員
+ 群組找不到!
+ 已移除
+\n<xliff:g xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\" id=\"member profile\" example=\"alice (Alice)\">
+ 已刪除群組
+ 群組已經刪除
+ 已經邀請
+ 已經透過連結邀請了你進群組
+ 聯絡人允許
+ %ds
+ 私人通知
+ 於 GitHub \u0020查看更多。
+ 於 GitHub 查看更多
+ 視訊通話來電
+ 掛斷電話來電
+ 點對點
+ 對話已經過端對端加密
+ 對話沒有經過端對端加密
+ 數據庫已加密!
+ 已加密數據庫
+ 對話封存
+ 群組資料已經更新
+ 成員
+ 你:%1$s
+ 刪除群組
+ 即時訊息
+ 對話封存
+ 移除成員時出現錯誤
+ 修改身份時出現錯誤
+ 群組
+ 連線
+ 直接
+ 間接 (%1$s)
+ 伺服器
+ 接收訊息透過
+ 傳送訊息透過
+ 隨機的個人檔案將傳送給收到此連結的聯絡人
+ 禁止傳送自動銷毀的訊息。
+ 禁止不可逆的訊息刪除。
+ 禁止傳送語音訊息。
+ 群組內的成員可以傳送自動銷毀的訊息。
+ 自動銷毀訊息於這個群組內是禁用的。
+ 提供 %s
+ 儲存群組檔案時有錯誤
+ 恢復
+ 主題
+ 儲存顏色
+ 你允許
+ 修改群組內的設定
+ 私訊
+ 已啟用
+ 已為你啟用
+ 已為聯絡人啟用
+ 只有你能不可逆地刪除訊息(你的聯絡人可以將它標記為刪除)
+ 只有你的聊絡人可以不可逆的刪除訊息(你可以將它標記為刪除)
+ 只有你的聯絡人可以傳送語音訊息。
+ 禁止私訊群組內的成員。
+ 不可逆地刪除訊息於這個群組內是禁用的。
+ 群組內的成員可以傳送語音訊息。
+ 語音訊息於這個群組內是禁用的。
+ %d 個月
+ %dm
+ %dmth
+ %d 小時
+ %dh
+ %d 日
+ %d 天
+ %dd
+ %d 星期
+ %d 個星期
+ %dw
+ 新的更新 %s
+ 最多四十秒後刪除,接收人立即收到。
+ 不可逆地刪除訊息
+ 增強隱私和安全性
+ 已傳送的訊息將在設定的時間後被刪除。
+ 當你輸入訊息時侯,對方將可以即時看到你輸入的內容。
+ 驗證連線安全性。
+ 驗證你與聯絡人的安全碼。
+ 感謝用戶 - 使用 Weblate 的翻譯貢獻!
+ 正在更改地址為 %s …
+ 受加密的數據庫密碼會再次更新和儲存於金鑰庫。
+ SimpleX 是怎樣運作
+ 當發生:
+\n1. 如果三十天內沒有收到訊息,那麼這些訊息將會在伺服器內過時。
+\n2. 你用來接收該聯絡人的訊息伺服器已更新並且重新啟動。
+\n3. 連接受到影響或被破壞。
+\n請透過設定列尋找開發人員並且報告你的伺服器問題。
+\n我們將會新增重複的伺服器以防止遺失伺服器。
+ 只有對方的裝置才會儲存你的個人檔案,聯絡人,群組,所有訊息都會經過兩層的端對端加密
+ 請放置你的密碼於安全的地方,如果你遺失了密碼,將不可能更改你的密碼。
+ 停止聊天室以匯出對話,匯入或刪除對話存檔。當聊天室停止後你將不能接收或傳送訊息。
+ 你正在使用匿名聊天模式進入此群組,為了避免分享你的真實個人檔案,邀請聯絡人是不允許的。
+ 你傳送了一個群組連結
+ 你移除了 %1$s
+ 你退出了群組
+ 你修改了地址為 %s
+ 連線中(邀請介紹階段)
+ 你目前的對話數據庫會刪除並且以你匯入的對話數據庫頂替上。
+\n這操作不能還原 - 你現有的個人檔案,聯絡人,訊息和檔案將會不可逆地遺失。
+ 透過聯絡人的邀請連結連線
+ 透過一次性連結連線
+ 傳輸隔離
+ 更新傳輸隔離模式?
+ 為了保護隱私,而不像是其他平台般需要提取和存儲用戶的 ID資料,SimpleX 本平台具有SimpleX自家隊列的標識符,對於你的每個聯絡人也是獨一無二的。
+ 當應用程式是開啟
+ 你可以控制通過哪一個伺服器 來接收 你的聯絡人訊息 – 這些伺服器用來接收他們傳送給你的訊息。
+ 透過設定啟用於上鎖畫面顯示來電通知
+ 這操作不能還原 - 你現有的個人檔案,聯絡人,訊息和檔案將會不可逆地的失去。
+ 你必須在裝置上使用最新版本的對話數據庫,否則你可能會停止接收某些聯絡人的訊息。
+ 這操作不能還原 - 所有已經接收和傳送的檔案和媒體檔案將會刪除。低解析度圖片將保留。
+ 這設置適用於你當前的個人檔案
+ 這操作無法撤銷 - 早於所選時間的發送和接收的訊息將被刪除。可能需要幾分鐘的時間。
+ 更新數據庫密碼
+ 當每次啟動應用程式後你會需要輸入密碼 - 這不是儲存於你的個人裝置上。
+ 你已經被邀請至群組
+ 你將停止接收來自此群組的訊息。群組內的記錄會保留。
+ 你已拒絕加入群組
+ 連線中(宣布階段)
+ %1$s 已選擇多個聊絡人
+ 你的 ICE 伺服器
+ WebRTC ICE 伺服器
+ 更新
+ 添加更多身份選項
+ 聯絡人頭像
+ 個人資料頭像占位符
+ 不受垃圾郵件和濫用行為影響
+ %1$s 希望透過以下方式聯絡你
+ 開啟視訊
+ 翻轉相機
+ 待確認通話
+ 你的私隱
+ 你的通話
+ 經由分程傳遞連接
+ 在上鎖畫面顯示來電通知:
+ %1$d 你錯過了多個訊息
+ 錯誤訊息雜湊值
+ 你錯過了多個訊息
+ 你
+ 實驗性功能
+ 你對話的數據庫並未加受加密 - 設置密碼保護它。
+ 資料庫密碼與保存在金鑰庫中的密碼不同。
+ 不明的錯誤
+ 還原數據庫備份後請輸入舊密碼。這個操作是不能撤銷的!
+ 你可以透過應用程式的設置或重新啟動應用程式來開始新的對話。
+ 你已經被邀请加入至群組。加入後可與群組內的成員對話。
+ 你已加入至群組
+ 已確認聯絡人
+ 你的對話數據庫
+ 刪除個人檔案?
+ 錯誤的數據庫密碼
+ 未知的數據庫錯誤:%s
+ 密碼錯誤!
+ 你已經加入至群組。正在連線至群組內的成員。
+ 這群組已經不存在。
+ 更新群組檔案
+ 你修改了 %s 的身份為 %s
+ 連線中(介紹階段)
+ 使用對話
+ 透過轉送
+ 關閉視訊
+ 你修改了自己的身份為 %s
+ 你修改了地址
+ 由 %s 管理
+ 將為所有成員刪除該消息。
+ 刪除成員消息?
+ 主持
+ 該消息將對所有成員標記為已審核。
+ 您不能發送消息!
+ 您是觀察者
+ 觀察者
+ 更新群組鏈接時出錯
+ 請聯繫群管理員。
+ 初始角色
+ 系統
\ No newline at end of file
diff --git a/apps/android/app/src/main/res/values/colors.xml b/apps/android/app/src/main/res/values/colors.xml
index 2e02f493b..1833a6d9a 100644
--- a/apps/android/app/src/main/res/values/colors.xml
+++ b/apps/android/app/src/main/res/values/colors.xml
@@ -2,4 +2,5 @@
#FF000000
#FFFFFFFF
+ #121212
\ No newline at end of file
diff --git a/apps/android/app/src/main/res/values/strings.xml b/apps/android/app/src/main/res/values/strings.xml
index 17b37d9cd..1e0564e74 100644
--- a/apps/android/app/src/main/res/values/strings.xml
+++ b/apps/android/app/src/main/res/values/strings.xml
@@ -103,6 +103,7 @@
Delete queue
Disconnect
Error deleting user profile
+ Error updating user privacy
Instant notifications
@@ -459,6 +460,7 @@
Check server address and try again.
Delete server
The servers for new connections of your current chat profile
+ Save servers?
Install SimpleX Chat for terminal
Star on GitHub
Contribute
@@ -535,6 +537,16 @@
Save and notify group members
Exit without saving
+
+
+ Hide profile
+ Password to show
+ Save profile password
+ Hidden profile password
+ Confirm password
+ To reveal your hidden profile, enter a full password into a search field in "Your chat profiles" page.
+ Error saving user password
+
You control your chat!
The messaging and application platform protecting your privacy and security.
@@ -631,13 +643,15 @@
Audio & video calls
Your calls
- Connect via relay
+ Always use relay
Calls on lock screen:
Accept
Show
Disable
Your ICE servers
WebRTC ICE servers
+ Relay server protects your IP address, but it can observe the duration of the call.
+ Relay server is only used if necessary. Another party can observe your IP address.
Open SimpleX Chat to accept call
@@ -696,6 +710,7 @@
Developer tools
Experimental features
SOCKS PROXY
+ LANGUAGE
APP ICON
THEMES
MESSAGES
@@ -921,6 +936,8 @@
Group will be deleted for you - this cannot be undone!
Leave group
Edit group profile
+ Add welcome message
+ Welcome message
Group link
Create group link
Create link
@@ -958,6 +975,11 @@
direct
indirect (%1$s)
+
+ Welcome message
+ Save welcome message?
+ Save and update group profile
+
SERVERS
Receiving via
@@ -1001,6 +1023,20 @@
Delete chat profile for
Profile and server connections
Local profile data only
+ Hide
+ Unhide
+ Mute
+ Unmute
+ Enter password above to show!
+ Tap to activate profile.
+ Can\'t delete user profile!
+ There should be at least one visible user profile.
+ There should be at least one user profile.
+ Make profile private!
+ You can hide or mute a user profile - hold it for the menu.\nSimpleX Lock must be enabled.
+ Don\'t show again
+ Muted when inactive!
+ You will still receive calls and notifications from muted profiles when they are active.
Incognito
@@ -1018,6 +1054,9 @@
Light
Dark
+
+ System
+
Theme
Save color
@@ -1144,4 +1183,16 @@
More improvements are coming soon!
Italian interface
Thanks to the users – contribute via Weblate!
+ Hidden chat profiles
+ Protect your chat profiles with a password!
+ Audio and video calls
+ Support bluetooth and other improvements.
+ Group moderation
+ Now admins can:\n- delete members\' messages.\n- disable members (\"observer\" role)
+ Group welcome message
+ Set the message shown to new members!
+ Further reduced battery usage
+ More improvements are coming soon!
+ Chinese and Spanish interface
+ Thanks to the users – contribute via Weblate!
diff --git a/apps/android/app/src/main/res/xml/locales_config.xml b/apps/android/app/src/main/res/xml/locales_config.xml
new file mode 100644
index 000000000..22fe04d1e
--- /dev/null
+++ b/apps/android/app/src/main/res/xml/locales_config.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/ios/Shared/Assets.xcassets/icon-transparent.imageset/120.png b/apps/ios/Shared/Assets.xcassets/icon-transparent.imageset/120.png
new file mode 100644
index 000000000..9cbe08ed8
Binary files /dev/null and b/apps/ios/Shared/Assets.xcassets/icon-transparent.imageset/120.png differ
diff --git a/apps/ios/Shared/Assets.xcassets/icon-transparent.imageset/180.png b/apps/ios/Shared/Assets.xcassets/icon-transparent.imageset/180.png
new file mode 100644
index 000000000..4c23ec8f2
Binary files /dev/null and b/apps/ios/Shared/Assets.xcassets/icon-transparent.imageset/180.png differ
diff --git a/apps/ios/Shared/Assets.xcassets/icon-transparent.imageset/60.png b/apps/ios/Shared/Assets.xcassets/icon-transparent.imageset/60.png
new file mode 100644
index 000000000..ce0940364
Binary files /dev/null and b/apps/ios/Shared/Assets.xcassets/icon-transparent.imageset/60.png differ
diff --git a/apps/ios/Shared/Assets.xcassets/icon-transparent.imageset/Contents.json b/apps/ios/Shared/Assets.xcassets/icon-transparent.imageset/Contents.json
new file mode 100644
index 000000000..b0e2cd5eb
--- /dev/null
+++ b/apps/ios/Shared/Assets.xcassets/icon-transparent.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "60.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "120.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "180.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/apps/ios/Shared/ContentView.swift b/apps/ios/Shared/ContentView.swift
index 10930ac31..aeccaf934 100644
--- a/apps/ios/Shared/ContentView.swift
+++ b/apps/ios/Shared/ContentView.swift
@@ -6,14 +6,18 @@
//
import SwiftUI
+import Intents
import SimpleXChat
struct ContentView: View {
@EnvironmentObject var chatModel: ChatModel
@ObservedObject var alertManager = AlertManager.shared
@ObservedObject var callController = CallController.shared
+ @Environment(\.colorScheme) var colorScheme
@Binding var doAuthenticate: Bool
@Binding var userAuthorized: Bool?
+ @Binding var canConnectCall: Bool
+ @Binding var lastSuccessfulUnlock: TimeInterval?
@AppStorage(DEFAULT_SHOW_LA_NOTICE) private var prefShowLANotice = false
@AppStorage(DEFAULT_LA_NOTICE_SHOWN) private var prefLANoticeShown = false
@AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false
@@ -23,41 +27,64 @@ struct ContentView: View {
var body: some View {
ZStack {
- if prefPerformLA && userAuthorized != true {
- Button(action: runAuthenticate) { Label("Unlock", systemImage: "lock") }
- } else if let status = chatModel.chatDbStatus, status != .ok {
- DatabaseErrorView(status: status)
- } else if !chatModel.v3DBMigration.startChat {
- MigrateToAppGroupView()
- } else if let step = chatModel.onboardingStage {
- if case .onboardingComplete = step,
- chatModel.currentUser != nil {
- mainView().privacySensitive(protectScreen)
- } else {
- OnboardingView(onboarding: step)
- }
+ contentView()
+ if chatModel.showCallView, let call = chatModel.activeCall {
+ callView(call)
}
}
.onAppear {
- if doAuthenticate { runAuthenticate() }
+ if prefPerformLA { requestNtfAuthorization() }
+ initAuthenticate()
+ }
+ .onChange(of: doAuthenticate) { _ in
+ initAuthenticate()
}
- .onChange(of: doAuthenticate) { _ in if doAuthenticate { runAuthenticate() } }
.alert(isPresented: $alertManager.presentAlert) { alertManager.alertView! }
}
+ @ViewBuilder private func contentView() -> some View {
+ if prefPerformLA && userAuthorized != true {
+ lockButton()
+ } else if let status = chatModel.chatDbStatus, status != .ok {
+ DatabaseErrorView(status: status)
+ } else if !chatModel.v3DBMigration.startChat {
+ MigrateToAppGroupView()
+ } else if let step = chatModel.onboardingStage {
+ if case .onboardingComplete = step,
+ chatModel.currentUser != nil {
+ mainView()
+ } else {
+ OnboardingView(onboarding: step)
+ }
+ }
+ }
+
+ @ViewBuilder private func callView(_ call: Call) -> some View {
+ if CallController.useCallKit() {
+ ActiveCallView(call: call, canConnectCall: Binding.constant(true))
+ .onDisappear {
+ if userAuthorized == false && doAuthenticate { runAuthenticate() }
+ }
+ } else {
+ ActiveCallView(call: call, canConnectCall: $canConnectCall)
+ if prefPerformLA && userAuthorized != true {
+ Rectangle()
+ .fill(colorScheme == .dark ? .black : .white)
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
+ lockButton()
+ }
+ }
+ }
+
+ private func lockButton() -> some View {
+ Button(action: runAuthenticate) { Label("Unlock", systemImage: "lock") }
+ }
+
private func mainView() -> some View {
ZStack(alignment: .top) {
- ChatListView()
+ ChatListView().privacySensitive(protectScreen)
.onAppear {
- NtfManager.shared.requestAuthorization(
- onDeny: {
- if (!notificationAlertShown) {
- notificationAlertShown = true
- alertManager.showAlert(notificationAlert())
- }
- },
- onAuthorized: { notificationAlertShown = false }
- )
+ if !prefPerformLA { requestNtfAuthorization() }
// Local Authentication notice is to be shown on next start after onboarding is complete
if (!prefLANoticeShown && prefShowLANotice && !chatModel.chats.isEmpty) {
prefLANoticeShown = true
@@ -74,11 +101,42 @@ struct ContentView: View {
.sheet(isPresented: $showWhatsNew) {
WhatsNewView()
}
- if chatModel.showCallView, let call = chatModel.activeCall {
- ActiveCallView(call: call)
- }
IncomingCallView()
}
+ .onContinueUserActivity("INStartCallIntent", perform: processUserActivity)
+ .onContinueUserActivity("INStartAudioCallIntent", perform: processUserActivity)
+ .onContinueUserActivity("INStartVideoCallIntent", perform: processUserActivity)
+ }
+
+ private func processUserActivity(_ activity: NSUserActivity) {
+ let intent = activity.interaction?.intent
+ if let intent = intent as? INStartCallIntent {
+ callToRecentContact(intent.contacts, intent.callCapability == .videoCall ? .video : .audio)
+ } else if let intent = intent as? INStartAudioCallIntent {
+ callToRecentContact(intent.contacts, .audio)
+ } else if let intent = intent as? INStartVideoCallIntent {
+ callToRecentContact(intent.contacts, .video)
+ }
+ }
+
+ private func callToRecentContact(_ contacts: [INPerson]?, _ mediaType: CallMediaType) {
+ logger.debug("callToRecentContact")
+ if let contactId = contacts?.first?.personHandle?.value,
+ let chat = chatModel.getChat(contactId),
+ case let .direct(contact) = chat.chatInfo {
+ logger.debug("callToRecentContact: schedule call")
+ DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
+ CallController.shared.startCall(contact, mediaType)
+ }
+ }
+ }
+
+ private func initAuthenticate() {
+ if CallController.useCallKit() && chatModel.showCallView && chatModel.activeCall != nil {
+ userAuthorized = false
+ } else if doAuthenticate {
+ runAuthenticate()
+ }
}
private func runAuthenticate() {
@@ -98,16 +156,31 @@ struct ContentView: View {
switch (laResult) {
case .success:
userAuthorized = true
+ canConnectCall = true
+ lastSuccessfulUnlock = ProcessInfo.processInfo.systemUptime
case .failed:
break
case .unavailable:
userAuthorized = true
prefPerformLA = false
+ canConnectCall = true
AlertManager.shared.showAlert(laUnavailableTurningOffAlert())
}
}
}
+ func requestNtfAuthorization() {
+ NtfManager.shared.requestAuthorization(
+ onDeny: {
+ if (!notificationAlertShown) {
+ notificationAlertShown = true
+ alertManager.showAlert(notificationAlert())
+ }
+ },
+ onAuthorized: { notificationAlertShown = false }
+ )
+ }
+
func laNoticeAlert() -> Alert {
Alert(
title: Text("SimpleX Lock"),
diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift
index adec3f54f..e8f917af3 100644
--- a/apps/ios/Shared/Model/ChatModel.swift
+++ b/apps/ios/Shared/Model/ChatModel.swift
@@ -9,7 +9,6 @@
import Foundation
import Combine
import SwiftUI
-import WebKit
import SimpleXChat
final class ChatModel: ObservableObject {
@@ -59,7 +58,6 @@ final class ChatModel: ObservableObject {
@Published var stopPreviousRecPlay: Bool = false // value is not taken into account, only the fact it switches
@Published var draft: ComposeState?
@Published var draftChatId: String?
- var callWebView: WKWebView?
var messageDelivery: Dictionary Void> = [:]
@@ -69,6 +67,31 @@ final class ChatModel: ObservableObject {
static var ok: Bool { ChatModel.shared.chatDbStatus == .ok }
+ func getUser(_ userId: Int64) -> User? {
+ currentUser?.userId == userId
+ ? currentUser
+ : users.first { $0.user.userId == userId }?.user
+ }
+
+ func getUserIndex(_ user: User) -> Int? {
+ users.firstIndex { $0.user.userId == user.userId }
+ }
+
+ func updateUser(_ user: User) {
+ if let i = getUserIndex(user) {
+ users[i].user = user
+ }
+ if currentUser?.userId == user.userId {
+ currentUser = user
+ }
+ }
+
+ func removeUser(_ user: User) {
+ if let i = getUserIndex(user), users[i].user.userId != currentUser?.userId {
+ users.remove(at: i)
+ }
+ }
+
func hasChat(_ id: String) -> Bool {
chats.first(where: { $0.id == id }) != nil
}
diff --git a/apps/ios/Shared/Model/NtfManager.swift b/apps/ios/Shared/Model/NtfManager.swift
index 9663a7319..4a4511eae 100644
--- a/apps/ios/Shared/Model/NtfManager.swift
+++ b/apps/ios/Shared/Model/NtfManager.swift
@@ -39,7 +39,7 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
logger.debug("NtfManager.userNotificationCenter: didReceive: action \(action), categoryIdentifier \(content.categoryIdentifier)")
if let userId = content.userInfo["userId"] as? Int64,
userId != chatModel.currentUser?.userId {
- changeActiveUser(userId)
+ changeActiveUser(userId, viewPwd: nil)
}
if content.categoryIdentifier == ntfCategoryContactRequest && action == ntfActionAcceptContact,
let chatId = content.userInfo["chatId"] as? String {
@@ -87,13 +87,17 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
switch content.categoryIdentifier {
case ntfCategoryMessageReceived:
let recent = recentInTheSameChat(content)
- if model.chatId == nil {
+ let userId = content.userInfo["userId"] as? Int64
+ if let userId = userId, let user = model.getUser(userId), !user.showNotifications {
+ // ... inactive user with disabled notifications
+ return []
+ } else if model.chatId == nil {
// in the chat list...
- if model.currentUser?.userId == (content.userInfo["userId"] as? Int64) {
- // ... of the current user
+ if model.currentUser?.userId == userId {
+ // ... of the active user
return recent ? [] : [.sound, .list]
} else {
- // ... of different user
+ // ... of inactive user
return recent ? [.banner] : [.sound, .banner, .list]
}
} else if model.chatId == content.targetContentIdentifier {
diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift
index 2dcf79922..d5ad40b85 100644
--- a/apps/ios/Shared/Model/SimpleXAPI.swift
+++ b/apps/ios/Shared/Model/SimpleXAPI.swift
@@ -132,21 +132,56 @@ func apiCreateActiveUser(_ p: Profile) throws -> User {
}
func listUsers() throws -> [UserInfo] {
- let r = chatSendCmdSync(.listUsers)
+ return try listUsersResponse(chatSendCmdSync(.listUsers))
+}
+
+func listUsersAsync() async throws -> [UserInfo] {
+ return try listUsersResponse(await chatSendCmd(.listUsers))
+}
+
+private func listUsersResponse(_ r: ChatResponse) throws -> [UserInfo] {
if case let .usersList(users) = r {
return users.sorted { $0.user.chatViewName.compare($1.user.chatViewName) == .orderedAscending }
}
throw r
}
-func apiSetActiveUser(_ userId: Int64) throws -> User {
- let r = chatSendCmdSync(.apiSetActiveUser(userId: userId))
+func apiSetActiveUser(_ userId: Int64, viewPwd: String?) throws -> User {
+ let r = chatSendCmdSync(.apiSetActiveUser(userId: userId, viewPwd: viewPwd))
if case let .activeUser(user) = r { return user }
throw r
}
-func apiDeleteUser(_ userId: Int64, _ delSMPQueues: Bool) throws {
- let r = chatSendCmdSync(.apiDeleteUser(userId: userId, delSMPQueues: delSMPQueues))
+func apiSetActiveUserAsync(_ userId: Int64, viewPwd: String?) async throws -> User {
+ let r = await chatSendCmd(.apiSetActiveUser(userId: userId, viewPwd: viewPwd))
+ if case let .activeUser(user) = r { return user }
+ throw r
+}
+
+func apiHideUser(_ userId: Int64, viewPwd: String) async throws -> User {
+ try await setUserPrivacy_(.apiHideUser(userId: userId, viewPwd: viewPwd))
+}
+
+func apiUnhideUser(_ userId: Int64, viewPwd: String?) async throws -> User {
+ try await setUserPrivacy_(.apiUnhideUser(userId: userId, viewPwd: viewPwd))
+}
+
+func apiMuteUser(_ userId: Int64, viewPwd: String?) async throws -> User {
+ try await setUserPrivacy_(.apiMuteUser(userId: userId, viewPwd: viewPwd))
+}
+
+func apiUnmuteUser(_ userId: Int64, viewPwd: String?) async throws -> User {
+ try await setUserPrivacy_(.apiUnmuteUser(userId: userId, viewPwd: viewPwd))
+}
+
+func setUserPrivacy_(_ cmd: ChatCommand) async throws -> User {
+ let r = await chatSendCmd(cmd)
+ if case let .userPrivacy(user) = r { return user }
+ throw r
+}
+
+func apiDeleteUser(_ userId: Int64, _ delSMPQueues: Bool, viewPwd: String?) async throws {
+ let r = await chatSendCmd(.apiDeleteUser(userId: userId, delSMPQueues: delSMPQueues, viewPwd: viewPwd))
if case .cmdOk = r { return }
throw r
}
@@ -209,8 +244,16 @@ func apiStorageEncryption(currentKey: String = "", newKey: String = "") async th
}
func apiGetChats() throws -> [ChatData] {
- guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("apiGetChats: no current user") }
- let r = chatSendCmdSync(.apiGetChats(userId: userId))
+ let userId = try currentUserId("apiGetChats")
+ return try apiChatsResponse(chatSendCmdSync(.apiGetChats(userId: userId)))
+}
+
+func apiGetChatsAsync() async throws -> [ChatData] {
+ let userId = try currentUserId("apiGetChats")
+ return try apiChatsResponse(await chatSendCmd(.apiGetChats(userId: userId)))
+}
+
+private func apiChatsResponse(_ r: ChatResponse) throws -> [ChatData] {
if case let .apiChats(_, chats) = r { return chats }
throw r
}
@@ -337,19 +380,27 @@ func apiDeleteToken(token: DeviceToken) async throws {
}
func getUserSMPServers() throws -> ([ServerCfg], [String]) {
- guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("getUserSMPServers: no current user") }
- let r = chatSendCmdSync(.apiGetUserSMPServers(userId: userId))
+ let userId = try currentUserId("getUserSMPServers")
+ return try userSMPServersResponse(chatSendCmdSync(.apiGetUserSMPServers(userId: userId)))
+}
+
+func getUserSMPServersAsync() async throws -> ([ServerCfg], [String]) {
+ let userId = try currentUserId("getUserSMPServersAsync")
+ return try userSMPServersResponse(await chatSendCmd(.apiGetUserSMPServers(userId: userId)))
+}
+
+private func userSMPServersResponse(_ r: ChatResponse) throws -> ([ServerCfg], [String]) {
if case let .userSMPServers(_, smpServers, presetServers) = r { return (smpServers, presetServers) }
throw r
}
func setUserSMPServers(smpServers: [ServerCfg]) async throws {
- guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("setUserSMPServers: no current user") }
+ let userId = try currentUserId("setUserSMPServers")
try await sendCommandOkResp(.apiSetUserSMPServers(userId: userId, smpServers: smpServers))
}
func testSMPServer(smpServer: String) async throws -> Result<(), SMPTestFailure> {
- guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("testSMPServer: no current user") }
+ let userId = try currentUserId("testSMPServer")
let r = await chatSendCmd(.apiTestSMPServer(userId: userId, smpServer: smpServer))
if case let .smpTestResult(_, testFailure) = r {
if let t = testFailure {
@@ -361,14 +412,22 @@ func testSMPServer(smpServer: String) async throws -> Result<(), SMPTestFailure>
}
func getChatItemTTL() throws -> ChatItemTTL {
- guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("getChatItemTTL: no current user") }
- let r = chatSendCmdSync(.apiGetChatItemTTL(userId: userId))
+ let userId = try currentUserId("getChatItemTTL")
+ return try chatItemTTLResponse(chatSendCmdSync(.apiGetChatItemTTL(userId: userId)))
+}
+
+func getChatItemTTLAsync() async throws -> ChatItemTTL {
+ let userId = try currentUserId("getChatItemTTLAsync")
+ return try chatItemTTLResponse(await chatSendCmd(.apiGetChatItemTTL(userId: userId)))
+}
+
+private func chatItemTTLResponse(_ r: ChatResponse) throws -> ChatItemTTL {
if case let .chatItemTTL(_, chatItemTTL) = r { return ChatItemTTL(chatItemTTL) }
throw r
}
func setChatItemTTL(_ chatItemTTL: ChatItemTTL) async throws {
- guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("setChatItemTTL: no current user") }
+ let userId = try currentUserId("setChatItemTTL")
try await sendCommandOkResp(.apiSetChatItemTTL(userId: userId, seconds: chatItemTTL.seconds))
}
@@ -539,14 +598,14 @@ func clearChat(_ chat: Chat) async {
}
func apiListContacts() throws -> [Contact] {
- guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("apiListContacts: no current user") }
+ let userId = try currentUserId("apiListContacts")
let r = chatSendCmdSync(.apiListContacts(userId: userId))
if case let .contactsList(_, contacts) = r { return contacts }
throw r
}
func apiUpdateProfile(profile: Profile) async throws -> Profile? {
- guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("apiUpdateProfile: no current user") }
+ let userId = try currentUserId("apiUpdateProfile")
let r = await chatSendCmd(.apiUpdateProfile(userId: userId, profile: profile))
switch r {
case .userProfileNoChange: return nil
@@ -574,22 +633,30 @@ func apiSetConnectionAlias(connId: Int64, localAlias: String) async throws -> Pe
}
func apiCreateUserAddress() async throws -> String {
- guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("apiCreateUserAddress: no current user") }
+ let userId = try currentUserId("apiCreateUserAddress")
let r = await chatSendCmd(.apiCreateMyAddress(userId: userId))
if case let .userContactLinkCreated(_, connReq) = r { return connReq }
throw r
}
func apiDeleteUserAddress() async throws {
- guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("apiDeleteUserAddress: no current user") }
+ let userId = try currentUserId("apiDeleteUserAddress")
let r = await chatSendCmd(.apiDeleteMyAddress(userId: userId))
if case .userContactLinkDeleted = r { return }
throw r
}
func apiGetUserAddress() throws -> UserContactLink? {
- guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("apiGetUserAddress: no current user") }
- let r = chatSendCmdSync(.apiShowMyAddress(userId: userId))
+ let userId = try currentUserId("apiGetUserAddress")
+ return try userAddressResponse(chatSendCmdSync(.apiShowMyAddress(userId: userId)))
+}
+
+func apiGetUserAddressAsync() async throws -> UserContactLink? {
+ let userId = try currentUserId("apiGetUserAddressAsync")
+ return try userAddressResponse(await chatSendCmd(.apiShowMyAddress(userId: userId)))
+}
+
+private func userAddressResponse(_ r: ChatResponse) throws -> UserContactLink? {
switch r {
case let .userContactLink(_, contactLink): return contactLink
case .chatCmdError(_, chatError: .errorStore(storeError: .userContactLinkNotFound)): return nil
@@ -598,7 +665,7 @@ func apiGetUserAddress() throws -> UserContactLink? {
}
func userAddressAutoAccept(_ autoAccept: AutoAccept?) async throws -> UserContactLink? {
- guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("userAddressAutoAccept: no current user") }
+ let userId = try currentUserId("userAddressAutoAccept")
let r = await chatSendCmd(.apiAddressAutoAccept(userId: userId, autoAccept: autoAccept))
switch r {
case let .userContactLinkUpdated(_, contactLink): return contactLink
@@ -793,7 +860,7 @@ private func sendCommandOkResp(_ cmd: ChatCommand) async throws {
}
func apiNewGroup(_ p: GroupProfile) throws -> GroupInfo {
- guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("apiNewGroup: no current user") }
+ let userId = try currentUserId("apiNewGroup")
let r = chatSendCmdSync(.apiNewGroup(userId: userId, groupProfile: p))
if case let .groupCreated(_, groupInfo) = r { return groupInfo }
throw r
@@ -909,7 +976,14 @@ func apiGetVersion() throws -> CoreVersionInfo {
throw r
}
-func initializeChat(start: Bool, dbKey: String? = nil) throws {
+private func currentUserId(_ funcName: String) throws -> Int64 {
+ if let userId = ChatModel.shared.currentUser?.userId {
+ return userId
+ }
+ throw RuntimeError("\(funcName): no current user")
+}
+
+func initializeChat(start: Bool, dbKey: String? = nil, refreshInvitations: Bool = true) throws {
logger.debug("initializeChat")
let m = ChatModel.shared
(m.chatDbEncrypted, m.chatDbStatus) = chatMigrateInit(dbKey)
@@ -925,13 +999,13 @@ func initializeChat(start: Bool, dbKey: String? = nil) throws {
if m.currentUser == nil {
m.onboardingStage = .step1_SimpleXInfo
} else if start {
- try startChat()
+ try startChat(refreshInvitations: refreshInvitations)
} else {
m.chatRunning = false
}
}
-func startChat() throws {
+func startChat(refreshInvitations: Bool = true) throws {
logger.debug("startChat")
let m = ChatModel.shared
try setNetworkConfig(getNetCfg())
@@ -940,7 +1014,9 @@ func startChat() throws {
if justStarted {
try getUserChatData()
NtfManager.shared.setNtfBadgeCount(m.totalUnreadCountForAllUsers())
- try refreshCallInvitations()
+ if (refreshInvitations) {
+ try refreshCallInvitations()
+ }
(m.savedToken, m.tokenStatus, m.notificationMode) = apiGetNtfToken()
if let token = m.deviceToken {
registerToken(token: token)
@@ -956,21 +1032,38 @@ func startChat() throws {
chatLastStartGroupDefault.set(Date.now)
}
-func changeActiveUser(_ userId: Int64) {
+func changeActiveUser(_ userId: Int64, viewPwd: String?) {
do {
- try changeActiveUser_(userId)
+ try changeActiveUser_(userId, viewPwd: viewPwd)
} catch let error {
logger.error("Unable to set active user: \(responseError(error))")
}
}
-func changeActiveUser_(_ userId: Int64) throws {
+private func changeActiveUser_(_ userId: Int64, viewPwd: String?) throws {
let m = ChatModel.shared
- m.currentUser = try apiSetActiveUser(userId)
+ m.currentUser = try apiSetActiveUser(userId, viewPwd: viewPwd)
m.users = try listUsers()
try getUserChatData()
}
+func changeActiveUserAsync_(_ userId: Int64, viewPwd: String?) async throws {
+ let currentUser = try await apiSetActiveUserAsync(userId, viewPwd: viewPwd)
+ let users = try await listUsersAsync()
+ await MainActor.run {
+ let m = ChatModel.shared
+ m.currentUser = currentUser
+ m.users = users
+ }
+ try await getUserChatDataAsync()
+ await MainActor.run {
+ if var (_, invitation) = ChatModel.shared.callInvitations.first(where: { _, inv in inv.user.userId == userId }) {
+ invitation.user = currentUser
+ activateCall(invitation)
+ }
+ }
+}
+
func getUserChatData() throws {
let m = ChatModel.shared
m.userAddress = try apiGetUserAddress()
@@ -980,6 +1073,20 @@ func getUserChatData() throws {
m.chats = chats.map { Chat.init($0) }
}
+private func getUserChatDataAsync() async throws {
+ let userAddress = try await apiGetUserAddressAsync()
+ let servers = try await getUserSMPServersAsync()
+ let chatItemTTL = try await getChatItemTTLAsync()
+ let chats = try await apiGetChatsAsync()
+ await MainActor.run {
+ let m = ChatModel.shared
+ m.userAddress = userAddress
+ (m.userSMPServers, m.presetSMPServers) = servers
+ m.chatItemTTL = chatItemTTL
+ m.chats = chats.map { Chat.init($0) }
+ }
+}
+
class ChatReceiver {
private var receiveLoop: Task?
private var receiveMessages = true
@@ -1048,18 +1155,18 @@ func processReceivedMsg(_ res: ChatResponse) async {
m.removeChat(contact.activeConn.id)
}
case let .receivedContactRequest(user, contactRequest):
- if !active(user) { return }
-
- let cInfo = ChatInfo.contactRequest(contactRequest: contactRequest)
- if m.hasChat(contactRequest.id) {
- m.updateChatInfo(cInfo)
- } else {
- m.addChat(Chat(
- chatInfo: cInfo,
- chatItems: []
- ))
- NtfManager.shared.notifyContactRequest(user, contactRequest)
+ if active(user) {
+ let cInfo = ChatInfo.contactRequest(contactRequest: contactRequest)
+ if m.hasChat(contactRequest.id) {
+ m.updateChatInfo(cInfo)
+ } else {
+ m.addChat(Chat(
+ chatInfo: cInfo,
+ chatItems: []
+ ))
+ }
}
+ NtfManager.shared.notifyContactRequest(user, contactRequest)
case let .contactUpdated(user, toContact):
if active(user) && m.hasChat(toContact.id) {
let cInfo = ChatInfo.direct(contact: toContact)
@@ -1214,19 +1321,6 @@ func processReceivedMsg(_ res: ChatResponse) async {
case let .callInvitation(invitation):
m.callInvitations[invitation.contact.id] = invitation
activateCall(invitation)
-
-// This will be called from notification service extension
-// CXProvider.reportNewIncomingVoIPPushPayload([
-// "displayName": contact.displayName,
-// "contactId": contact.id,
-// "uuid": invitation.callkitUUID
-// ]) { error in
-// if let error = error {
-// logger.error("reportNewIncomingVoIPPushPayload error \(error.localizedDescription)")
-// } else {
-// logger.debug("reportNewIncomingVoIPPushPayload success for \(contact.id)")
-// }
-// }
case let .callOffer(_, contact, callType, offer, sharedKey, _):
withCall(contact) { call in
call.callState = .offerReceived
@@ -1259,7 +1353,7 @@ func processReceivedMsg(_ res: ChatResponse) async {
}
withCall(contact) { call in
m.callCommand = .end
-// CallController.shared.reportCallRemoteEnded(call: call)
+ CallController.shared.reportCallRemoteEnded(call: call)
}
case .chatSuspended:
chatSuspended()
@@ -1310,18 +1404,25 @@ func processContactSubError(_ contact: Contact, _ chatError: ChatError) {
func refreshCallInvitations() throws {
let m = ChatModel.shared
- let callInvitations = try apiGetCallInvitations()
- m.callInvitations = callInvitations.reduce(into: [ChatId: RcvCallInvitation]()) { result, inv in result[inv.contact.id] = inv }
+ let callInvitations = try justRefreshCallInvitations()
if let (chatId, ntfAction) = m.ntfCallInvitationAction,
let invitation = m.callInvitations.removeValue(forKey: chatId) {
m.ntfCallInvitationAction = nil
CallController.shared.callAction(invitation: invitation, action: ntfAction)
- } else if let invitation = callInvitations.last {
+ } else if let invitation = callInvitations.last(where: { $0.user.showNotifications }) {
activateCall(invitation)
}
}
+func justRefreshCallInvitations() throws -> [RcvCallInvitation] {
+ let m = ChatModel.shared
+ let callInvitations = try apiGetCallInvitations()
+ m.callInvitations = callInvitations.reduce(into: [ChatId: RcvCallInvitation]()) { result, inv in result[inv.contact.id] = inv }
+ return callInvitations
+}
+
func activateCall(_ callInvitation: RcvCallInvitation) {
+ if !callInvitation.user.showNotifications { return }
let m = ChatModel.shared
CallController.shared.reportNewIncomingCall(invitation: callInvitation) { error in
if let error = error {
diff --git a/apps/ios/Shared/Model/SuspendChat.swift b/apps/ios/Shared/Model/SuspendChat.swift
index 499dbbb1f..6d8108a3e 100644
--- a/apps/ios/Shared/Model/SuspendChat.swift
+++ b/apps/ios/Shared/Model/SuspendChat.swift
@@ -81,3 +81,24 @@ func activateChat(appState: AppState = .active) {
if ChatModel.ok { apiActivateChat() }
}
}
+
+func initChatAndMigrate(refreshInvitations: Bool = true) {
+ let m = ChatModel.shared
+ if (!m.chatInitialized) {
+ do {
+ m.v3DBMigration = v3DBMigrationDefault.get()
+ try initializeChat(start: m.v3DBMigration.startChat, refreshInvitations: refreshInvitations)
+ } catch let error {
+ fatalError("Failed to start or load chats: \(responseError(error))")
+ }
+ }
+}
+
+func startChatAndActivate() {
+ if ChatModel.shared.chatRunning == true {
+ ChatReceiver.shared.start()
+ }
+ if .active != appStateGroupDefault.get() {
+ activateChat()
+ }
+}
\ No newline at end of file
diff --git a/apps/ios/Shared/SimpleXApp.swift b/apps/ios/Shared/SimpleXApp.swift
index c8b641d20..b93d402a8 100644
--- a/apps/ios/Shared/SimpleXApp.swift
+++ b/apps/ios/Shared/SimpleXApp.swift
@@ -21,6 +21,8 @@ struct SimpleXApp: App {
@State private var userAuthorized: Bool?
@State private var doAuthenticate = false
@State private var enteredBackground: TimeInterval? = nil
+ @State private var canConnectCall = false
+ @State private var lastSuccessfulUnlock: TimeInterval? = nil
init() {
hs_init(0, nil)
@@ -34,44 +36,43 @@ struct SimpleXApp: App {
var body: some Scene {
return WindowGroup {
- ContentView(doAuthenticate: $doAuthenticate, userAuthorized: $userAuthorized)
+ ContentView(doAuthenticate: $doAuthenticate, userAuthorized: $userAuthorized, canConnectCall: $canConnectCall, lastSuccessfulUnlock: $lastSuccessfulUnlock)
.environmentObject(chatModel)
.onOpenURL { url in
logger.debug("ContentView.onOpenURL: \(url)")
chatModel.appOpenUrl = url
}
.onAppear() {
- if (!chatModel.chatInitialized) {
- do {
- chatModel.v3DBMigration = v3DBMigrationDefault.get()
- try initializeChat(start: chatModel.v3DBMigration.startChat)
- } catch let error {
- fatalError("Failed to start or load chats: \(responseError(error))")
- }
- }
+ initChatAndMigrate()
}
.onChange(of: scenePhase) { phase in
- logger.debug("scenePhase \(String(describing: scenePhase))")
+ logger.debug("scenePhase was \(String(describing: scenePhase)), now \(String(describing: phase))")
switch (phase) {
case .background:
- suspendChat()
- BGManager.shared.schedule()
+ if CallController.useCallKit() && chatModel.activeCall != nil {
+ CallController.shared.shouldSuspendChat = true
+ } else {
+ suspendChat()
+ BGManager.shared.schedule()
+ }
if userAuthorized == true {
enteredBackground = ProcessInfo.processInfo.systemUptime
}
doAuthenticate = false
+ canConnectCall = false
NtfManager.shared.setNtfBadgeCount(chatModel.totalUnreadCountForAllUsers())
case .active:
- if chatModel.chatRunning == true {
- ChatReceiver.shared.start()
- }
+ CallController.shared.shouldSuspendChat = false
let appState = appStateGroupDefault.get()
- activateChat()
+ startChatAndActivate()
if appState.inactive && chatModel.chatRunning == true {
updateChats()
- updateCallInvitations()
+ if !chatModel.showCallView && !CallController.shared.hasActiveCalls() {
+ updateCallInvitations()
+ }
}
doAuthenticate = authenticationExpired()
+ canConnectCall = !(doAuthenticate && prefPerformLA) || unlockedRecently()
default:
break
}
@@ -111,6 +112,14 @@ struct SimpleXApp: App {
}
}
+ private func unlockedRecently() -> Bool {
+ if let lastSuccessfulUnlock = lastSuccessfulUnlock {
+ return ProcessInfo.processInfo.systemUptime - lastSuccessfulUnlock < 2
+ } else {
+ return false
+ }
+ }
+
private func updateChats() {
do {
let chats = try apiGetChats()
diff --git a/apps/ios/Shared/Views/Call/ActiveCallView.swift b/apps/ios/Shared/Views/Call/ActiveCallView.swift
index d53b351de..393a370ee 100644
--- a/apps/ios/Shared/Views/Call/ActiveCallView.swift
+++ b/apps/ios/Shared/Views/Call/ActiveCallView.swift
@@ -13,9 +13,11 @@ import SimpleXChat
struct ActiveCallView: View {
@EnvironmentObject var m: ChatModel
@ObservedObject var call: Call
+ @Environment(\.scenePhase) var scenePhase
@State private var client: WebRTCClient? = nil
@State private var activeCall: WebRTCClient.Call? = nil
@State private var localRendererAspectRatio: CGFloat? = nil
+ @Binding var canConnectCall: Bool
var body: some View {
ZStack(alignment: .bottom) {
@@ -36,12 +38,16 @@ struct ActiveCallView: View {
}
}
.onAppear {
- if client == nil {
- client = WebRTCClient($activeCall, { msg in await MainActor.run { processRtcMessage(msg: msg) } }, $localRendererAspectRatio)
- sendCommandToClient()
- }
+ logger.debug("ActiveCallView: appear client is nil \(client == nil), scenePhase \(String(describing: scenePhase), privacy: .public), canConnectCall \(canConnectCall)")
+ createWebRTCClient()
+ dismissAllSheets()
+ }
+ .onChange(of: canConnectCall) { _ in
+ logger.debug("ActiveCallView: canConnectCall changed to \(canConnectCall, privacy: .public)")
+ createWebRTCClient()
}
.onDisappear {
+ logger.debug("ActiveCallView: disappear")
client?.endCall()
}
.onChange(of: m.callCommand) { _ in sendCommandToClient()}
@@ -49,6 +55,13 @@ struct ActiveCallView: View {
.preferredColorScheme(.dark)
}
+ private func createWebRTCClient() {
+ if client == nil && canConnectCall {
+ client = WebRTCClient($activeCall, { msg in await MainActor.run { processRtcMessage(msg: msg) } }, $localRendererAspectRatio)
+ sendCommandToClient()
+ }
+ }
+
private func sendCommandToClient() {
if call == m.activeCall,
m.activeCall != nil,
@@ -117,9 +130,9 @@ struct ActiveCallView: View {
case let .connection(state):
if let callStatus = WebRTCCallStatus.init(rawValue: state.connectionState),
case .connected = callStatus {
-// if case .outgoing = call.direction {
-// CallController.shared.reportOutgoingCall(call: call, connectedAt: nil)
-// }
+ call.direction == .outgoing
+ ? CallController.shared.reportOutgoingCall(call: call, connectedAt: nil)
+ : CallController.shared.reportIncomingCall(call: call, connectedAt: nil)
call.callState = .connected
}
if state.connectionState == "closed" {
@@ -252,7 +265,7 @@ struct ActiveCallOverlay: View {
private func endCallButton() -> some View {
let cc = CallController.shared
- return callButton("phone.down.fill", size: 60) {
+ return callButton("phone.down.fill", width: 60, height: 60) {
if let uuid = call.callkitUUID {
cc.endCall(callUUID: uuid)
} else {
@@ -274,7 +287,7 @@ struct ActiveCallOverlay: View {
}
private func toggleSpeakerButton() -> some View {
- controlButton(call, call.speakerEnabled ? "speaker.fill" : "speaker.slash") {
+ controlButton(call, call.speakerEnabled ? "speaker.wave.2.fill" : "speaker.wave.1.fill") {
Task {
client.setSpeakerEnabledAndConfigureSession(!call.speakerEnabled)
DispatchQueue.main.async {
@@ -305,22 +318,22 @@ struct ActiveCallOverlay: View {
@ViewBuilder private func controlButton(_ call: Call, _ imageName: String, _ perform: @escaping () -> Void) -> some View {
if call.hasMedia {
- callButton(imageName, size: 40, perform)
+ callButton(imageName, width: 50, height: 38, perform)
.foregroundColor(.white)
.opacity(0.85)
} else {
- Color.clear.frame(width: 40, height: 40)
+ Color.clear.frame(width: 50, height: 38)
}
}
- private func callButton(_ imageName: String, size: CGFloat, _ perform: @escaping () -> Void) -> some View {
+ private func callButton(_ imageName: String, width: CGFloat, height: CGFloat, _ perform: @escaping () -> Void) -> some View {
Button {
perform()
} label: {
Image(systemName: imageName)
.resizable()
.scaledToFit()
- .frame(maxWidth: size, maxHeight: size)
+ .frame(maxWidth: width, maxHeight: height)
}
}
}
diff --git a/apps/ios/Shared/Views/Call/CallController.swift b/apps/ios/Shared/Views/Call/CallController.swift
index 15332ef32..6a20eee59 100644
--- a/apps/ios/Shared/Views/Call/CallController.swift
+++ b/apps/ios/Shared/Views/Call/CallController.swift
@@ -7,192 +7,317 @@
//
import Foundation
-//import CallKit
+import CallKit
+import StoreKit
+import PushKit
import AVFoundation
import SimpleXChat
+import WebRTC
-//class CallController: NSObject, CXProviderDelegate, ObservableObject {
-class CallController: NSObject, ObservableObject {
- static let useCallKit = false
+class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, ObservableObject {
static let shared = CallController()
-// private let provider = CXProvider(configuration: CallController.configuration)
-// private let controller = CXCallController()
+ static let isInChina = SKStorefront().countryCode == "CHN"
+ static func useCallKit() -> Bool { !isInChina && callKitEnabledGroupDefault.get() }
+
+ private let provider = CXProvider(configuration: {
+ let configuration = CXProviderConfiguration()
+ configuration.supportsVideo = true
+ configuration.supportedHandleTypes = [.generic]
+ configuration.includesCallsInRecents = UserDefaults.standard.bool(forKey: DEFAULT_CALL_KIT_CALLS_IN_RECENTS)
+ configuration.maximumCallGroups = 1
+ configuration.maximumCallsPerCallGroup = 1
+ configuration.iconTemplateImageData = UIImage(named: "icon-transparent")?.pngData()
+ return configuration
+ }())
+ private let controller = CXCallController()
private let callManager = CallManager()
@Published var activeCallInvitation: RcvCallInvitation?
+ var shouldSuspendChat: Bool = false
+ var fulfillOnConnect: CXAnswerCallAction? = nil
-// PKPushRegistry will be used from notification service extension
-// let registry = PKPushRegistry(queue: nil)
-
-// static let configuration: CXProviderConfiguration = {
-// let configuration = CXProviderConfiguration()
-// configuration.supportsVideo = true
-// configuration.supportedHandleTypes = [.generic]
-// configuration.includesCallsInRecents = true // TODO disable or add option
-// configuration.maximumCallsPerCallGroup = 1
-// return configuration
-// }()
+ // PKPushRegistry is used from notification service extension
+ private let registry = PKPushRegistry(queue: nil)
override init() {
super.init()
-// self.provider.setDelegate(self, queue: nil)
-// self.registry.delegate = self
-// self.registry.desiredPushTypes = [.voIP]
+ provider.setDelegate(self, queue: nil)
+ registry.delegate = self
+ registry.desiredPushTypes = [.voIP]
}
-// func providerDidReset(_ provider: CXProvider) {
-// }
+ func providerDidReset(_ provider: CXProvider) {
+ logger.debug("CallController.providerDidReset")
+ }
-// func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
-// logger.debug("CallController.provider CXStartCallAction")
-// if callManager.startOutgoingCall(callUUID: action.callUUID) {
-// action.fulfill()
-// provider.reportOutgoingCall(with: action.callUUID, startedConnectingAt: nil)
-// } else {
-// action.fail()
-// }
-// }
+ func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
+ logger.debug("CallController.provider CXStartCallAction")
+ if callManager.startOutgoingCall(callUUID: action.callUUID) {
+ action.fulfill()
+ provider.reportOutgoingCall(with: action.callUUID, startedConnectingAt: nil)
+ } else {
+ action.fail()
+ }
+ }
-// func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
-// logger.debug("CallController.provider CXAnswerCallAction")
-// if callManager.answerIncomingCall(callUUID: action.callUUID) {
-// action.fulfill()
-// } else {
-// action.fail()
-// }
-// }
+ func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
+ logger.debug("CallController.provider CXAnswerCallAction")
+ if callManager.answerIncomingCall(callUUID: action.callUUID) {
+ // WebRTC call should be in connected state to fulfill.
+ // Otherwise no audio and mic working on lockscreen
+ fulfillOnConnect = action
+ } else {
+ action.fail()
+ }
+ }
-// func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
-// logger.debug("CallController.provider CXEndCallAction")
-// callManager.endCall(callUUID: action.callUUID) { ok in
-// if ok {
-// action.fulfill()
-// } else {
-// action.fail()
-// }
-// }
-// }
+ func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
+ logger.debug("CallController.provider CXEndCallAction")
+ // Should be nil here if connection was in connected state
+ fulfillOnConnect?.fail()
+ fulfillOnConnect = nil
+ callManager.endCall(callUUID: action.callUUID) { ok in
+ if ok {
+ action.fulfill()
+ } else {
+ action.fail()
+ }
+ self.suspendOnEndCall()
+ }
+ }
-// func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) {
-// print("timed out", #function)
-// action.fulfill()
-// }
+ func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
+ if callManager.enableMedia(media: .audio, enable: !action.isMuted, callUUID: action.callUUID) {
+ action.fulfill()
+ } else {
+ action.fail()
+ }
+ }
-// func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
-// print("received", #function)
-//// do {
-//// try audioSession.setCategory(.playAndRecord, mode: .voiceChat, options: .mixWithOthers)
-//// logger.debug("audioSession category set")
-//// try audioSession.setActive(true)
-//// logger.debug("audioSession activated")
-//// } catch {
-//// print(error)
-//// logger.error("failed activating audio session")
-//// }
-// }
+ func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) {
+ logger.debug("timed out: \(String(describing: action))")
+ action.fulfill()
+ }
-// func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
-// print("received", #function)
-// }
+ func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
+ logger.debug("CallController: activating audioSession and audio in WebRTCClient")
+ RTCAudioSession.sharedInstance().audioSessionDidActivate(audioSession)
+ RTCAudioSession.sharedInstance().isAudioEnabled = true
+ do {
+ try audioSession.setCategory(.playAndRecord, mode: .voiceChat, options: .mixWithOthers)
+ logger.debug("audioSession category set")
+ try audioSession.setActive(true)
+ logger.debug("audioSession activated")
+ } catch {
+ print(error)
+ logger.error("failed activating audio session")
+ }
+ }
-// func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
-//
-// }
+ func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
+ logger.debug("CallController: deactivating audioSession and audio in WebRTCClient")
+ RTCAudioSession.sharedInstance().audioSessionDidDeactivate(audioSession)
+ RTCAudioSession.sharedInstance().isAudioEnabled = false
+ do {
+ try audioSession.setActive(false)
+ logger.debug("audioSession deactivated")
+ } catch {
+ print(error)
+ logger.error("failed deactivating audio session")
+ }
+ suspendOnEndCall()
+ }
-// This will be needed when we have notification service extension
-// func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
-// if type == .voIP {
-// // Extract the call information from the push notification payload
-// if let displayName = payload.dictionaryPayload["displayName"] as? String,
-// let contactId = payload.dictionaryPayload["contactId"] as? String,
-// let uuidStr = payload.dictionaryPayload["uuid"] as? String,
-// let uuid = UUID(uuidString: uuidStr) {
-// let callUpdate = CXCallUpdate()
-// callUpdate.remoteHandle = CXHandle(type: .phoneNumber, value: displayName)
-// provider.reportNewIncomingCall(with: uuid, update: callUpdate, completion: { error in
-// if error != nil {
-// let m = ChatModel.shared
-// m.callInvitations.removeValue(forKey: contactId)
-// }
-// // Tell PushKit that the notification is handled.
-// completion()
-// })
-// }
-// }
-// }
+ func suspendOnEndCall() {
+ if shouldSuspendChat {
+ // The delay allows to accept the second call before suspending a chat
+ // see `.onChange(of: scenePhase)` in SimpleXApp
+ DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in
+ logger.debug("CallController: shouldSuspendChat \(String(describing: self?.shouldSuspendChat), privacy: .public)")
+ if ChatModel.shared.activeCall == nil && self?.shouldSuspendChat == true {
+ self?.shouldSuspendChat = false
+ suspendChat()
+ BGManager.shared.schedule()
+ }
+ }
+ }
+ }
+
+ @objc(pushRegistry:didUpdatePushCredentials:forType:)
+ func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
+ logger.debug("CallController: didUpdate push credentials for type \(type.rawValue, privacy: .public)")
+ }
+
+ func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
+ logger.debug("CallController: did receive push with type \(type.rawValue, privacy: .public)")
+ if type != .voIP {
+ completion()
+ return
+ }
+ logger.debug("CallController: initializing chat")
+ if (!ChatModel.shared.chatInitialized) {
+ initChatAndMigrate(refreshInvitations: false)
+ }
+ startChatAndActivate()
+ shouldSuspendChat = true
+ // There are no invitations in the model, as it was processed by NSE
+ _ = try? justRefreshCallInvitations()
+ // logger.debug("CallController justRefreshCallInvitations: \(String(describing: m.callInvitations))")
+ // Extract the call information from the push notification payload
+ let m = ChatModel.shared
+ if let contactId = payload.dictionaryPayload["contactId"] as? String,
+ let invitation = m.callInvitations[contactId] {
+ let update = cxCallUpdate(invitation: invitation)
+ if let uuid = invitation.callkitUUID {
+ logger.debug("CallController: report pushkit call via CallKit")
+ let update = cxCallUpdate(invitation: invitation)
+ provider.reportNewIncomingCall(with: uuid, update: update) { error in
+ if error != nil {
+ m.callInvitations.removeValue(forKey: contactId)
+ }
+ // Tell PushKit that the notification is handled.
+ completion()
+ }
+ } else {
+ reportExpiredCall(update: update, completion)
+ }
+ } else {
+ reportExpiredCall(payload: payload, completion)
+ }
+ }
+
+ // This function fulfils the requirement to always report a call when PushKit notification is received,
+ // even when there is no more active calls by the time PushKit payload is processed.
+ // See the note in the bottom of this article:
+ // https://developer.apple.com/documentation/pushkit/pkpushregistrydelegate/2875784-pushregistry
+ private func reportExpiredCall(update: CXCallUpdate, _ completion: @escaping () -> Void) {
+ logger.debug("CallController: report expired pushkit call via CallKit")
+ let uuid = UUID()
+ provider.reportNewIncomingCall(with: uuid, update: update) { error in
+ if error == nil {
+ DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
+ self.provider.reportCall(with: uuid, endedAt: nil, reason: .remoteEnded)
+ }
+ }
+ completion()
+ }
+ }
+
+ private func reportExpiredCall(payload: PKPushPayload, _ completion: @escaping () -> Void) {
+ let update = CXCallUpdate()
+ let displayName = payload.dictionaryPayload["displayName"] as? String
+ let media = payload.dictionaryPayload["media"] as? String
+ update.localizedCallerName = displayName ?? NSLocalizedString("Unknown caller", comment: "callkit banner")
+ update.hasVideo = media == CallMediaType.video.rawValue
+ reportExpiredCall(update: update, completion)
+ }
func reportNewIncomingCall(invitation: RcvCallInvitation, completion: @escaping (Error?) -> Void) {
- logger.debug("CallController.reportNewIncomingCall")
-// if CallController.useCallKit, let uuid = invitation.callkitUUID {
-// let update = CXCallUpdate()
-// update.remoteHandle = CXHandle(type: .generic, value: invitation.contact.displayName)
-// update.hasVideo = invitation.peerMedia == .video
-// provider.reportNewIncomingCall(with: uuid, update: update, completion: completion)
-// } else {
+ logger.debug("CallController.reportNewIncomingCall, UUID=\(String(describing: invitation.callkitUUID), privacy: .public)")
+ if CallController.useCallKit(), let uuid = invitation.callkitUUID {
+ if invitation.callTs.timeIntervalSinceNow >= -180 {
+ let update = cxCallUpdate(invitation: invitation)
+ provider.reportNewIncomingCall(with: uuid, update: update, completion: completion)
+ }
+ } else {
NtfManager.shared.notifyCallInvitation(invitation)
if invitation.callTs.timeIntervalSinceNow >= -180 {
activeCallInvitation = invitation
}
-// }
+ }
}
-// func reportOutgoingCall(call: Call, connectedAt dateConnected: Date?) {
-// if CallController.useCallKit, let uuid = call.callkitUUID {
-// provider.reportOutgoingCall(with: uuid, connectedAt: dateConnected)
-// }
-// }
+ private func cxCallUpdate(invitation: RcvCallInvitation) -> CXCallUpdate {
+ let update = CXCallUpdate()
+ update.remoteHandle = CXHandle(type: .generic, value: invitation.contact.id)
+ update.hasVideo = invitation.callType.media == .video
+ update.localizedCallerName = invitation.contact.displayName
+ return update
+ }
+
+ func reportIncomingCall(call: Call, connectedAt dateConnected: Date?) {
+ logger.debug("CallController: reporting incoming call connected")
+ if CallController.useCallKit() {
+ // Fulfilling this action only after connect, otherwise there are no audio and mic on lockscreen
+ fulfillOnConnect?.fulfill()
+ fulfillOnConnect = nil
+ }
+ }
+
+ func reportOutgoingCall(call: Call, connectedAt dateConnected: Date?) {
+ logger.debug("CallController: reporting outgoing call connected")
+ if CallController.useCallKit(), let uuid = call.callkitUUID {
+ provider.reportOutgoingCall(with: uuid, connectedAt: dateConnected)
+ }
+ }
func reportCallRemoteEnded(invitation: RcvCallInvitation) {
-// if CallController.useCallKit, let uuid = invitation.callkitUUID {
-// provider.reportCall(with: uuid, endedAt: nil, reason: .remoteEnded)
-// } else if invitation.contact.id == activeCallInvitation?.contact.id {
+ logger.debug("CallController: reporting remote ended")
+ if CallController.useCallKit(), let uuid = invitation.callkitUUID {
+ provider.reportCall(with: uuid, endedAt: nil, reason: .remoteEnded)
+ } else if invitation.contact.id == activeCallInvitation?.contact.id {
activeCallInvitation = nil
-// }
+ }
}
-// func reportCallRemoteEnded(call: Call) {
-// if CallController.useCallKit, let uuid = call.callkitUUID {
-// provider.reportCall(with: uuid, endedAt: nil, reason: .remoteEnded)
-// }
-// }
+ func reportCallRemoteEnded(call: Call) {
+ logger.debug("CallController: reporting remote ended")
+ if CallController.useCallKit(), let uuid = call.callkitUUID {
+ provider.reportCall(with: uuid, endedAt: nil, reason: .remoteEnded)
+ }
+ }
func startCall(_ contact: Contact, _ media: CallMediaType) {
logger.debug("CallController.startCall")
let uuid = callManager.newOutgoingCall(contact, media)
-// if CallController.useCallKit {
-// let handle = CXHandle(type: .generic, value: contact.displayName)
-// let action = CXStartCallAction(call: uuid, handle: handle)
-// action.isVideo = media == .video
-// requestTransaction(with: action)
-// } else if callManager.startOutgoingCall(callUUID: uuid) {
- if callManager.startOutgoingCall(callUUID: uuid) {
- logger.debug("CallController.startCall: call started")
- } else {
- logger.error("CallController.startCall: no active call")
+ if CallController.useCallKit() {
+ let handle = CXHandle(type: .generic, value: contact.id)
+ let action = CXStartCallAction(call: uuid, handle: handle)
+ action.isVideo = media == .video
+ requestTransaction(with: action) {
+ let update = CXCallUpdate()
+ update.remoteHandle = CXHandle(type: .generic, value: contact.id)
+ update.hasVideo = media == .video
+ update.localizedCallerName = contact.displayName
+ self.provider.reportCall(with: uuid, updated: update)
+ }
+ } else if callManager.startOutgoingCall(callUUID: uuid) {
+ if callManager.startOutgoingCall(callUUID: uuid) {
+ logger.debug("CallController.startCall: call started")
+ } else {
+ logger.error("CallController.startCall: no active call")
+ }
}
}
func answerCall(invitation: RcvCallInvitation) {
- callManager.answerIncomingCall(invitation: invitation)
+ logger.debug("CallController: answering a call")
+ if CallController.useCallKit(), let callUUID = invitation.callkitUUID {
+ requestTransaction(with: CXAnswerCallAction(call: callUUID))
+ } else {
+ callManager.answerIncomingCall(invitation: invitation)
+ }
if invitation.contact.id == self.activeCallInvitation?.contact.id {
self.activeCallInvitation = nil
}
}
func endCall(callUUID: UUID) {
-// if CallController.useCallKit {
-// requestTransaction(with: CXEndCallAction(call: callUUID))
-// } else {
+ logger.debug("CallController: ending the call with UUID \(callUUID.uuidString)")
+ if CallController.useCallKit() {
+ requestTransaction(with: CXEndCallAction(call: callUUID))
+ } else {
callManager.endCall(callUUID: callUUID) { ok in
if ok {
logger.debug("CallController.endCall: call ended")
} else {
- logger.error("CallController.endCall: no actove call pr call invitation to end")
+ logger.error("CallController.endCall: no active call pr call invitation to end")
}
}
-// }
+ }
}
func endCall(invitation: RcvCallInvitation) {
+ logger.debug("CallController: ending the call with invitation")
callManager.endCall(invitation: invitation) {
if invitation.contact.id == self.activeCallInvitation?.contact.id {
DispatchQueue.main.async {
@@ -203,6 +328,7 @@ class CallController: NSObject, ObservableObject {
}
func endCall(call: Call, completed: @escaping () -> Void) {
+ logger.debug("CallController: ending the call with call instance")
callManager.endCall(call: call, completed: completed)
}
@@ -213,15 +339,24 @@ class CallController: NSObject, ObservableObject {
}
}
-// private func requestTransaction(with action: CXAction) {
-// let t = CXTransaction()
-// t.addAction(action)
-// controller.request(t) { error in
-// if let error = error {
-// logger.error("CallController.requestTransaction error requesting transaction: \(error.localizedDescription)")
-// } else {
-// logger.debug("CallController.requestTransaction requested transaction successfully")
-// }
-// }
-// }
+ func showInRecents(_ show: Bool) {
+ let conf = provider.configuration
+ conf.includesCallsInRecents = show
+ provider.configuration = conf
+ }
+
+ func hasActiveCalls() -> Bool {
+ controller.callObserver.calls.count > 0
+ }
+
+ private func requestTransaction(with action: CXAction, onSuccess: @escaping () -> Void = {}) {
+ controller.request(CXTransaction(action: action)) { error in
+ if let error = error {
+ logger.error("CallController.requestTransaction error requesting transaction: \(error.localizedDescription, privacy: .public)")
+ } else {
+ logger.debug("CallController.requestTransaction requested transaction successfully")
+ onSuccess()
+ }
+ }
+ }
}
diff --git a/apps/ios/Shared/Views/Call/CallManager.swift b/apps/ios/Shared/Views/Call/CallManager.swift
index a87fbc425..6e3066d1a 100644
--- a/apps/ios/Shared/Views/Call/CallManager.swift
+++ b/apps/ios/Shared/Views/Call/CallManager.swift
@@ -48,18 +48,31 @@ class CallManager {
sharedKey: invitation.sharedKey
)
call.speakerEnabled = invitation.callType.media == .video
- m.activeCall = call
- m.showCallView = true
let useRelay = UserDefaults.standard.bool(forKey: DEFAULT_WEBRTC_POLICY_RELAY)
let iceServers = getIceServers()
logger.debug("answerIncomingCall useRelay: \(useRelay)")
logger.debug("answerIncomingCall iceServers: \(String(describing: iceServers))")
- m.callCommand = .start(
- media: invitation.callType.media,
- aesKey: invitation.sharedKey,
- iceServers: iceServers,
- relay: useRelay
- )
+ // When in active call user wants to accept another call, this can only work after delay (to hide and show activeCallView)
+ DispatchQueue.main.asyncAfter(deadline: .now() + (m.activeCall == nil ? 0 : 1)) {
+ m.activeCall = call
+ m.showCallView = true
+
+ m.callCommand = .start(
+ media: invitation.callType.media,
+ aesKey: invitation.sharedKey,
+ iceServers: iceServers,
+ relay: useRelay
+ )
+ }
+ }
+
+ func enableMedia(media: CallMediaType, enable: Bool, callUUID: UUID) -> Bool {
+ if let call = ChatModel.shared.activeCall, call.callkitUUID == callUUID {
+ let m = ChatModel.shared
+ m.callCommand = .media(media: media, enable: enable)
+ return true
+ }
+ return false
}
func endCall(callUUID: UUID, completed: @escaping (Bool) -> Void) {
@@ -82,17 +95,15 @@ class CallManager {
} else {
logger.debug("CallManager.endCall: ending call...")
m.callCommand = .end
+ m.activeCall = nil
m.showCallView = false
+ completed()
Task {
do {
try await apiEndCall(call.contact)
} catch {
logger.error("CallController.provider apiEndCall error: \(responseError(error))")
}
- DispatchQueue.main.async {
- m.activeCall = nil
- completed()
- }
}
}
}
diff --git a/apps/ios/Shared/Views/Call/IncomingCallView.swift b/apps/ios/Shared/Views/Call/IncomingCallView.swift
index 0044434ef..c2d5dabd4 100644
--- a/apps/ios/Shared/Views/Call/IncomingCallView.swift
+++ b/apps/ios/Shared/Views/Call/IncomingCallView.swift
@@ -65,6 +65,7 @@ struct IncomingCallView: View {
.padding(.vertical, 12)
.frame(maxWidth: .infinity)
.background(Color(uiColor: .tertiarySystemGroupedBackground))
+ .onAppear { dismissAllSheets() }
}
private func callButton(_ text: LocalizedStringKey, _ image: String, _ color: Color, action: @escaping () -> Void) -> some View {
diff --git a/apps/ios/Shared/Views/Call/WebRTCClient.swift b/apps/ios/Shared/Views/Call/WebRTCClient.swift
index 582faef73..f64276f9b 100644
--- a/apps/ios/Shared/Views/Call/WebRTCClient.swift
+++ b/apps/ios/Shared/Views/Call/WebRTCClient.swift
@@ -47,6 +47,9 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
self.sendCallResponse = sendCallResponse
self.activeCall = activeCall
self.localRendererAspectRatio = localRendererAspectRatio
+ rtcAudioSession.useManualAudio = CallController.useCallKit()
+ rtcAudioSession.isAudioEnabled = !CallController.useCallKit()
+ logger.debug("WebRTCClient: rtcAudioSession has manual audio \(self.rtcAudioSession.useManualAudio) and audio enabled \(self.rtcAudioSession.isAudioEnabled)}")
super.init()
}
@@ -239,6 +242,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
}
func enableMedia(_ media: CallMediaType, _ enable: Bool) {
+ logger.debug("WebRTCClient: enabling media \(media.rawValue) \(enable)")
media == .video ? setVideoEnabled(enable) : setAudioEnabled(enable)
}
@@ -361,6 +365,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
func endCall() {
guard let call = activeCall.wrappedValue else { return }
+ logger.debug("WebRTCClient: ending the call")
activeCall.wrappedValue = nil
call.connection.close()
call.connection.delegate = nil
@@ -532,6 +537,7 @@ extension WebRTCClient {
}
func setSpeakerEnabledAndConfigureSession( _ enabled: Bool) {
+ logger.debug("WebRTCClient: configuring session with speaker enabled \(enabled)")
audioQueue.async { [weak self] in
guard let self = self else { return }
self.rtcAudioSession.lockForConfiguration()
@@ -543,6 +549,7 @@ extension WebRTCClient {
try self.rtcAudioSession.setMode(AVAudioSession.Mode.voiceChat.rawValue)
try self.rtcAudioSession.overrideOutputAudioPort(enabled ? .speaker : .none)
try self.rtcAudioSession.setActive(true)
+ logger.debug("WebRTCClient: configuring session with speaker enabled \(enabled) success")
} catch let error {
logger.debug("Error configuring AVAudioSession: \(error)")
}
@@ -550,6 +557,7 @@ extension WebRTCClient {
}
func audioSessionToDefaults() {
+ logger.debug("WebRTCClient: audioSession to defaults")
audioQueue.async { [weak self] in
guard let self = self else { return }
self.rtcAudioSession.lockForConfiguration()
@@ -561,8 +569,9 @@ extension WebRTCClient {
try self.rtcAudioSession.setMode(AVAudioSession.Mode.default.rawValue)
try self.rtcAudioSession.overrideOutputAudioPort(.none)
try self.rtcAudioSession.setActive(false)
+ logger.debug("WebRTCClient: audioSession to defaults success")
} catch let error {
- logger.debug("Error configuring AVAudioSession: \(error)")
+ logger.debug("Error configuring AVAudioSession with defaults: \(error)")
}
}
}
diff --git a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift
index 613ae37b4..e2ce4fc64 100644
--- a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift
+++ b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift
@@ -140,7 +140,7 @@ struct AddGroupMembersView: View {
private func rolePicker() -> some View {
Picker("New member role", selection: $selectedRole) {
ForEach(GroupMemberRole.allCases) { role in
- if role <= groupInfo.membership.memberRole && role != .observer {
+ if role <= groupInfo.membership.memberRole {
Text(role.text)
}
}
diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift
index 569be904a..ed8dfd922 100644
--- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift
+++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift
@@ -45,6 +45,7 @@ struct GroupChatInfoView: View {
Section {
if groupInfo.canEdit {
editGroupButton()
+ addOrEditWelcomeMessage()
}
groupPreferencesButton($groupInfo)
} header: {
@@ -215,6 +216,18 @@ struct GroupChatInfoView: View {
}
}
+ private func addOrEditWelcomeMessage() -> some View {
+ NavigationLink {
+ GroupWelcomeView(groupId: groupInfo.groupId, groupInfo: $groupInfo)
+ .navigationTitle("Welcome message")
+ .navigationBarTitleDisplayMode(.large)
+ } label: {
+ groupInfo.groupProfile.description == nil
+ ? Label("Add welcome message", systemImage: "plus.message")
+ : Label("Welcome message", systemImage: "message")
+ }
+ }
+
private func deleteGroupButton() -> some View {
Button(role: .destructive) {
alert = .deleteGroupAlert
diff --git a/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift b/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift
index 0c4c1c839..aeef91212 100644
--- a/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift
+++ b/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift
@@ -34,15 +34,15 @@ struct GroupLinkView: View {
Text("You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it.")
.padding(.bottom)
if let groupLink = groupLink {
-// HStack {
-// Text("Initial role")
-// Picker("Initial role", selection: $groupLinkMemberRole) {
-// ForEach([GroupMemberRole.member, GroupMemberRole.observer]) { role in
-// Text(role.text)
-// }
-// }
-// }
-// .frame(maxWidth: .infinity, alignment: .leading)
+ HStack {
+ Text("Initial role")
+ Picker("Initial role", selection: $groupLinkMemberRole) {
+ ForEach([GroupMemberRole.member, GroupMemberRole.observer]) { role in
+ Text(role.text)
+ }
+ }
+ }
+ .frame(maxWidth: .infinity, alignment: .leading)
QRCode(uri: groupLink)
HStack {
Button {
diff --git a/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift b/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift
new file mode 100644
index 000000000..7aa372d5d
--- /dev/null
+++ b/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift
@@ -0,0 +1,84 @@
+//
+// GroupWelcomeView.swift
+// SimpleX (iOS)
+//
+// Created by Avently on 21/03/2022.
+// Copyright © 2023 SimpleX Chat. All rights reserved.
+//
+
+import SwiftUI
+import SimpleXChat
+
+struct GroupWelcomeView: View {
+ @Environment(\.dismiss) var dismiss: DismissAction
+ @EnvironmentObject private var m: ChatModel
+ var groupId: Int64
+ @Binding var groupInfo: GroupInfo
+ @State private var welcomeText: String = ""
+ @FocusState private var keyboardVisible: Bool
+ @State private var showSaveDialog = false
+
+ var body: some View {
+ List {
+ Section {
+ TextEditor(text: $welcomeText)
+ .focused($keyboardVisible)
+ .padding(.horizontal, -5)
+ .padding(.top, -8)
+ .frame(height: 90, alignment: .topLeading)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ }
+ Section {
+ saveButton()
+ }
+ }
+ .onAppear {
+ welcomeText = groupInfo.groupProfile.description ?? ""
+ }
+ .modifier(BackButton {
+ if welcomeText == groupInfo.groupProfile.description || (welcomeText == "" && groupInfo.groupProfile.description == nil) {
+ dismiss()
+ } else {
+ showSaveDialog = true
+ }
+ })
+ .confirmationDialog("Save welcome message?", isPresented: $showSaveDialog) {
+ Button("Save and update group profile") {
+ save()
+ dismiss()
+ }
+ Button("Exit without saving") { dismiss() }
+ }
+ }
+
+ @ViewBuilder private func saveButton() -> some View {
+ Button("Save and update group profile") {
+ save()
+ }
+ .disabled(welcomeText == groupInfo.groupProfile.description || (welcomeText == "" && groupInfo.groupProfile.description == nil))
+ }
+
+ private func save() {
+ Task {
+ do {
+ var welcome: String? = welcomeText.trimmingCharacters(in: .whitespacesAndNewlines)
+ if welcome?.count == 0 {
+ welcome = nil
+ }
+ var groupProfileUpdated = groupInfo.groupProfile
+ groupProfileUpdated.description = welcome
+ groupInfo = try await apiUpdateGroup(groupId, groupProfileUpdated)
+ m.updateGroup(groupInfo)
+ welcomeText = welcome ?? ""
+ } catch let error {
+ logger.error("apiUpdateGroup error: \(responseError(error))")
+ }
+ }
+ }
+}
+
+struct GroupWelcomeView_Previews: PreviewProvider {
+ static var previews: some View {
+ GroupWelcomeView(groupId: 1, groupInfo: Binding.constant(GroupInfo.sampleData))
+ }
+}
diff --git a/apps/ios/Shared/Views/ChatList/UserPicker.swift b/apps/ios/Shared/Views/ChatList/UserPicker.swift
index b9f3c3bc4..76bac7a28 100644
--- a/apps/ios/Shared/Views/ChatList/UserPicker.swift
+++ b/apps/ios/Shared/Views/ChatList/UserPicker.swift
@@ -29,7 +29,9 @@ struct UserPicker: View {
VStack(spacing: 0) {
ScrollView {
ScrollViewReader { sp in
- let users = m.users.sorted { u, _ in u.user.activeUser }
+ let users = m.users
+ .filter({ u in u.user.activeUser || !u.user.hidden })
+ .sorted { u, _ in u.user.activeUser }
VStack(spacing: 0) {
ForEach(users) { u in
userView(u)
@@ -97,14 +99,18 @@ struct UserPicker: View {
userPickerVisible.toggle()
}
} else {
- do {
- try changeActiveUser_(user.userId)
- userPickerVisible = false
- } catch {
- AlertManager.shared.showAlertMsg(
- title: "Error switching profile!",
- message: "Error: \(responseError(error))"
- )
+ Task {
+ do {
+ try await changeActiveUserAsync_(user.userId, viewPwd: nil)
+ await MainActor.run { userPickerVisible = false }
+ } catch {
+ await MainActor.run {
+ AlertManager.shared.showAlertMsg(
+ title: "Error switching profile!",
+ message: "Error: \(responseError(error))"
+ )
+ }
+ }
}
}
}, label: {
diff --git a/apps/ios/Shared/Views/Database/DatabaseEncryptionView.swift b/apps/ios/Shared/Views/Database/DatabaseEncryptionView.swift
index 22ab2a4ed..acc86ff78 100644
--- a/apps/ios/Shared/Views/Database/DatabaseEncryptionView.swift
+++ b/apps/ios/Shared/Views/Database/DatabaseEncryptionView.swift
@@ -73,11 +73,11 @@ struct DatabaseEncryptionView: View {
}
if !initialRandomDBPassphrase && m.chatDbEncrypted == true {
- DatabaseKeyField(key: $currentKey, placeholder: "Current passphrase…", valid: validKey(currentKey))
+ PassphraseField(key: $currentKey, placeholder: "Current passphrase…", valid: validKey(currentKey))
}
- DatabaseKeyField(key: $newKey, placeholder: "New passphrase…", valid: validKey(newKey), showStrength: true)
- DatabaseKeyField(key: $confirmNewKey, placeholder: "Confirm new passphrase…", valid: confirmNewKey == "" || newKey == confirmNewKey)
+ PassphraseField(key: $newKey, placeholder: "New passphrase…", valid: validKey(newKey), showStrength: true)
+ PassphraseField(key: $confirmNewKey, placeholder: "Confirm new passphrase…", valid: confirmNewKey == "" || newKey == confirmNewKey)
settingsRow("lock.rotation") {
Button("Update database passphrase") {
@@ -255,7 +255,7 @@ struct DatabaseEncryptionView: View {
}
-struct DatabaseKeyField: View {
+struct PassphraseField: View {
@Binding var key: String
var placeholder: LocalizedStringKey
var valid: Bool
diff --git a/apps/ios/Shared/Views/Database/DatabaseErrorView.swift b/apps/ios/Shared/Views/Database/DatabaseErrorView.swift
index 5c6451e50..4830c8172 100644
--- a/apps/ios/Shared/Views/Database/DatabaseErrorView.swift
+++ b/apps/ios/Shared/Views/Database/DatabaseErrorView.swift
@@ -66,7 +66,7 @@ struct DatabaseErrorView: View {
}
private func databaseKeyField(onSubmit: @escaping () -> Void) -> some View {
- DatabaseKeyField(key: $dbKey, placeholder: "Enter passphrase…", valid: validKey(dbKey), onSubmit: onSubmit)
+ PassphraseField(key: $dbKey, placeholder: "Enter passphrase…", valid: validKey(dbKey), onSubmit: onSubmit)
}
private func saveAndOpenButton() -> some View {
diff --git a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift
index adb625b93..06a32a7c2 100644
--- a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift
+++ b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift
@@ -129,6 +129,41 @@ private let versionDescriptions: [VersionDescription] = [
description: "Thanks to the users – [contribute via Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#translate-the-apps)!"
)
]
+ ),
+ VersionDescription(
+ version: "v4.6",
+ features: [
+ FeatureDescription(
+ icon: "lock",
+ title: "Hidden chat profiles",
+ description: "Protect your chat profiles with a password!"
+ ),
+ FeatureDescription(
+ icon: "phone.arrow.up.right",
+ title: "Audio and video calls",
+ description: "Fully re-implemented - work in background!"
+ ),
+ FeatureDescription(
+ icon: "flag",
+ title: "Group moderation",
+ description: "Now admins can:\n- delete members' messages.\n- disable members (\"observer\" role)"
+ ),
+ FeatureDescription(
+ icon: "plus.message",
+ title: "Group welcome message",
+ description: "Set the message shown to new members!"
+ ),
+ FeatureDescription(
+ icon: "battery.50",
+ title: "Further reduced battery usage",
+ description: "More improvements are coming soon!"
+ ),
+ FeatureDescription(
+ icon: "character",
+ title: "Chinese and Spanish interface",
+ description: "Thanks to the users – [contribute via Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#translate-the-apps)!"
+ ),
+ ]
)
]
@@ -182,6 +217,9 @@ struct WhatsNewView: View {
pagination()
}
.padding()
+ .onChange(of: currentVersion) { _ in
+ currentVersionNav = currentVersion
+ }
}
private func featureDescription(_ icon: String, _ title: LocalizedStringKey, _ description: LocalizedStringKey) -> some View {
diff --git a/apps/ios/Shared/Views/TerminalView.swift b/apps/ios/Shared/Views/TerminalView.swift
index 5b063e8ba..4174e5b43 100644
--- a/apps/ios/Shared/Views/TerminalView.swift
+++ b/apps/ios/Shared/Views/TerminalView.swift
@@ -100,7 +100,7 @@ struct TerminalView: View {
func sendMessage() {
let cmd = ChatCommand.string(composeState.message)
if composeState.message.starts(with: "/sql") && (!prefPerformLA || !developerTools) {
- let resp = ChatResponse.chatCmdError(user: nil, chatError: ChatError.error(errorType: ChatErrorType.commandError(message: "Failed reading: empty")))
+ let resp = ChatResponse.chatCmdError(user_: nil, chatError: ChatError.error(errorType: ChatErrorType.commandError(message: "Failed reading: empty")))
DispatchQueue.main.async {
ChatModel.shared.addTerminalItem(.cmd(.now, cmd))
ChatModel.shared.addTerminalItem(.resp(.now, resp))
diff --git a/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift b/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift
index 712dfe43c..1f648b09d 100644
--- a/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift
+++ b/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift
@@ -14,6 +14,8 @@ let interfaceStyles: [UIUserInterfaceStyle] = [.unspecified, .light, .dark]
let interfaceStyleNames: [LocalizedStringKey] = ["System", "Light", "Dark"]
+let appSettingsURL = URL(string: UIApplication.openSettingsURLString)!
+
struct AppearanceSettings: View {
@EnvironmentObject var sceneDelegate: SceneDelegate
@State private var iconLightTapped = false
@@ -24,6 +26,16 @@ struct AppearanceSettings: View {
var body: some View {
VStack{
List {
+ Section(String("Language")) {
+ HStack {
+ Text(currentLanguage)
+ Spacer()
+ Button("Change") {
+ UIApplication.shared.open(appSettingsURL)
+ }
+ }
+ }
+
Section("App icon") {
HStack {
updateAppIcon(image: "icon-light", icon: nil, tapped: $iconLightTapped)
@@ -62,6 +74,11 @@ struct AppearanceSettings: View {
}
}
+ private var currentLanguage: String {
+ let lang = Bundle.main.preferredLocalizations.first ?? "en"
+ return Locale.current.localizedString(forIdentifier: lang)?.localizedCapitalized ?? lang
+ }
+
private func updateAppIcon(image: String, icon: String?, tapped: Binding) -> some View {
Image(image)
.resizable()
diff --git a/apps/ios/Shared/Views/UserSettings/CallSettings.swift b/apps/ios/Shared/Views/UserSettings/CallSettings.swift
index 254820be3..ca43faab0 100644
--- a/apps/ios/Shared/Views/UserSettings/CallSettings.swift
+++ b/apps/ios/Shared/Views/UserSettings/CallSettings.swift
@@ -7,22 +7,26 @@
//
import SwiftUI
+import SimpleXChat
struct CallSettings: View {
@AppStorage(DEFAULT_WEBRTC_POLICY_RELAY) private var webrtcPolicyRelay = true
+ @AppStorage(GROUP_DEFAULT_CALL_KIT_ENABLED, store: UserDefaults(suiteName: APP_GROUP_NAME)!) private var callKitEnabled = true
+ @AppStorage(DEFAULT_CALL_KIT_CALLS_IN_RECENTS) private var callKitCallsInRecents = false
+ @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
+ private let allowChangingCallsHistory = false
var body: some View {
VStack {
List {
Section {
- Toggle("Connect via relay", isOn: $webrtcPolicyRelay)
-
NavigationLink {
RTCServers()
.navigationTitle("Your ICE servers")
} label: {
Text("WebRTC ICE servers")
}
+ Toggle("Always use relay", isOn: $webrtcPolicyRelay)
} header: {
Text("Settings")
} footer: {
@@ -33,12 +37,29 @@ struct CallSettings: View {
}
}
+ if !CallController.isInChina {
+ Section {
+ Toggle("Use iOS call interface", isOn: $callKitEnabled)
+ Toggle("Show calls in phone history", isOn: $callKitCallsInRecents)
+ .disabled(!callKitEnabled)
+ .onChange(of: callKitCallsInRecents) { value in
+ CallController.shared.showInRecents(value)
+ }
+ } header: {
+ Text("Interface")
+ } footer: {
+ if callKitEnabled {
+ Text("You can accept calls from lock screen, without device and app authentication.")
+ } else {
+ Text("Authentication is required before the call is connected, but you may miss calls.")
+ }
+ }
+ }
+
Section("Limitations") {
VStack(alignment: .leading, spacing: 8) {
textListItem("1.", "Do NOT use SimpleX for emergency calls.")
- textListItem("2.", "The microphone does not work when the app is in the background.")
- textListItem("3.", "To prevent the call interruption, enable Do Not Disturb mode.")
- textListItem("4.", "If the video fails to connect, flip the camera to resolve it.")
+ textListItem("2.", "Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions.")
}
.font(.callout)
.padding(.vertical, 8)
diff --git a/apps/ios/Shared/Views/UserSettings/HiddenProfileView.swift b/apps/ios/Shared/Views/UserSettings/HiddenProfileView.swift
new file mode 100644
index 000000000..d01fee92f
--- /dev/null
+++ b/apps/ios/Shared/Views/UserSettings/HiddenProfileView.swift
@@ -0,0 +1,84 @@
+//
+// ProfilePrivacyView.swift
+// SimpleX (iOS)
+//
+// Created by Evgeny on 17/03/2023.
+// Copyright © 2023 SimpleX Chat. All rights reserved.
+//
+
+import SwiftUI
+import SimpleXChat
+
+struct HiddenProfileView: View {
+ @State var user: User
+ @Binding var profileHidden: Bool
+ @EnvironmentObject private var m: ChatModel
+ @Environment(\.dismiss) var dismiss: DismissAction
+ @State private var hidePassword = ""
+ @State private var confirmHidePassword = ""
+ @State private var saveErrorAlert = false
+ @State private var savePasswordError: String?
+
+ var body: some View {
+ List {
+ Text("Hide profile")
+ .font(.title)
+ .bold()
+ .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
+ .listRowBackground(Color.clear)
+
+ Section() {
+ ProfilePreview(profileOf: user)
+ .padding(.leading, -8)
+ }
+
+ Section {
+ PassphraseField(key: $hidePassword, placeholder: "Password to show", valid: true, showStrength: true)
+ PassphraseField(key: $confirmHidePassword, placeholder: "Confirm password", valid: confirmValid)
+
+ settingsRow("lock") {
+ Button("Save profile password") {
+ Task {
+ do {
+ let u = try await apiHideUser(user.userId, viewPwd: hidePassword)
+ await MainActor.run {
+ m.updateUser(u)
+ dismiss()
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
+ withAnimation { profileHidden = true }
+ }
+ }
+ } catch let error {
+ saveErrorAlert = true
+ savePasswordError = responseError(error)
+ }
+ }
+ }
+ }
+ .disabled(saveDisabled)
+ } header: {
+ Text("Hidden profile password")
+ } footer: {
+ Text("To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page.")
+ .font(.body)
+ .padding(.top, 8)
+ }
+ }
+ .alert(isPresented: $saveErrorAlert) {
+ Alert(
+ title: Text("Error saving user password"),
+ message: Text(savePasswordError ?? "")
+ )
+ }
+ }
+
+ var confirmValid: Bool { confirmHidePassword == "" || hidePassword == confirmHidePassword }
+
+ var saveDisabled: Bool { hidePassword == "" || confirmHidePassword == "" || !confirmValid }
+}
+
+struct ProfilePrivacyView_Previews: PreviewProvider {
+ static var previews: some View {
+ HiddenProfileView(user: User.sampleData, profileHidden: Binding.constant(false))
+ }
+}
diff --git a/apps/ios/Shared/Views/UserSettings/SMPServersView.swift b/apps/ios/Shared/Views/UserSettings/SMPServersView.swift
index 7cea87cb6..ab6b95477 100644
--- a/apps/ios/Shared/Views/UserSettings/SMPServersView.swift
+++ b/apps/ios/Shared/Views/UserSettings/SMPServersView.swift
@@ -12,6 +12,7 @@ import SimpleXChat
private let howToUrl = URL(string: "https://github.com/simplex-chat/simplex-chat/blob/stable/docs/SERVER.md")!
struct SMPServersView: View {
+ @Environment(\.dismiss) var dismiss: DismissAction
@EnvironmentObject private var m: ChatModel
@Environment(\.editMode) private var editMode
@State private var servers = ChatModel.shared.userSMPServers ?? []
@@ -20,6 +21,7 @@ struct SMPServersView: View {
@State private var showScanSMPServer = false
@State private var testing = false
@State private var alert: SMPServerAlert? = nil
+ @State private var showSaveDialog = false
var body: some View {
ZStack {
@@ -87,6 +89,20 @@ struct SMPServersView: View {
.sheet(isPresented: $showScanSMPServer) {
ScanSMPServer(servers: $servers)
}
+ .modifier(BackButton {
+ if saveDisabled {
+ dismiss()
+ } else {
+ showSaveDialog = true
+ }
+ })
+ .confirmationDialog("Save servers?", isPresented: $showSaveDialog) {
+ Button("Save") {
+ saveSMPServers()
+ dismiss()
+ }
+ Button("Exit without saving") { dismiss() }
+ }
.alert(item: $alert) { a in
switch a {
case let .testsFailed(fs):
diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift
index cb08dab92..cb58d2fea 100644
--- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift
+++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift
@@ -22,6 +22,7 @@ let DEFAULT_PERFORM_LA = "performLocalAuthentication"
let DEFAULT_NOTIFICATION_ALERT_SHOWN = "notificationAlertShown"
let DEFAULT_WEBRTC_POLICY_RELAY = "webrtcPolicyRelay"
let DEFAULT_WEBRTC_ICE_SERVERS = "webrtcICEServers"
+let DEFAULT_CALL_KIT_CALLS_IN_RECENTS = "callKitCallsInRecents"
let DEFAULT_PRIVACY_ACCEPT_IMAGES = "privacyAcceptImages"
let DEFAULT_PRIVACY_LINK_PREVIEWS = "privacyLinkPreviews"
let DEFAULT_PRIVACY_SIMPLEX_LINK_MODE = "privacySimplexLinkMode"
@@ -39,6 +40,8 @@ let DEFAULT_ACCENT_COLOR_BLUE = "accentColorBlue"
let DEFAULT_USER_INTERFACE_STYLE = "userInterfaceStyle"
let DEFAULT_CONNECT_VIA_LINK_TAB = "connectViaLinkTab"
let DEFAULT_LIVE_MESSAGE_ALERT_SHOWN = "liveMessageAlertShown"
+let DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE = "showHiddenProfilesNotice"
+let DEFAULT_SHOW_MUTE_PROFILE_ALERT = "showMuteProfileAlert"
let DEFAULT_WHATS_NEW_VERSION = "defaultWhatsNewVersion"
let appDefaults: [String: Any] = [
@@ -47,6 +50,7 @@ let appDefaults: [String: Any] = [
DEFAULT_PERFORM_LA: false,
DEFAULT_NOTIFICATION_ALERT_SHOWN: false,
DEFAULT_WEBRTC_POLICY_RELAY: true,
+ DEFAULT_CALL_KIT_CALLS_IN_RECENTS: false,
DEFAULT_PRIVACY_ACCEPT_IMAGES: true,
DEFAULT_PRIVACY_LINK_PREVIEWS: true,
DEFAULT_PRIVACY_SIMPLEX_LINK_MODE: "description",
@@ -60,7 +64,9 @@ let appDefaults: [String: Any] = [
DEFAULT_ACCENT_COLOR_BLUE: 1.000,
DEFAULT_USER_INTERFACE_STYLE: 0,
DEFAULT_CONNECT_VIA_LINK_TAB: "scan",
- DEFAULT_LIVE_MESSAGE_ALERT_SHOWN: false
+ DEFAULT_LIVE_MESSAGE_ALERT_SHOWN: false,
+ DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE: true,
+ DEFAULT_SHOW_MUTE_PROFILE_ALERT: true,
]
enum SimpleXLinkMode: String, Identifiable {
diff --git a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift
index d78b7ea66..01de1a8b3 100644
--- a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift
+++ b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift
@@ -9,19 +9,31 @@ import SimpleXChat
struct UserProfilesView: View {
@EnvironmentObject private var m: ChatModel
@Environment(\.editMode) private var editMode
+ @AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false
+ @AppStorage(DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE) private var showHiddenProfilesNotice = true
+ @AppStorage(DEFAULT_SHOW_MUTE_PROFILE_ALERT) private var showMuteProfileAlert = true
@State private var showDeleteConfirmation = false
- @State private var userToDelete: Int?
+ @State private var userToDelete: UserInfo?
@State private var alert: UserProfilesAlert?
- @State var authorized = !UserDefaults.standard.bool(forKey: DEFAULT_PERFORM_LA)
+ @State private var authorized = !UserDefaults.standard.bool(forKey: DEFAULT_PERFORM_LA)
+ @State private var searchTextOrPassword = ""
+ @State private var selectedUser: User?
+ @State private var profileHidden = false
private enum UserProfilesAlert: Identifiable {
- case deleteUser(index: Int, delSMPQueues: Bool)
+ case deleteUser(userInfo: UserInfo, delSMPQueues: Bool)
+ case cantDeleteLastUser
+ case hiddenProfilesNotice
+ case muteProfileAlert
case activateUserError(error: String)
case error(title: LocalizedStringKey, error: LocalizedStringKey = "")
var id: String {
switch self {
- case let .deleteUser(index, delSMPQueues): return "deleteUser \(index) \(delSMPQueues)"
+ case let .deleteUser(userInfo, delSMPQueues): return "deleteUser \(userInfo.user.userId) \(delSMPQueues)"
+ case .cantDeleteLastUser: return "cantDeleteLastUser"
+ case .hiddenProfilesNotice: return "hiddenProfilesNotice"
+ case .muteProfileAlert: return "muteProfileAlert"
case let .activateUserError(err): return "activateUserError \(err)"
case let .error(title, _): return "error \(title)"
}
@@ -41,45 +53,103 @@ struct UserProfilesView: View {
private func userProfilesView() -> some View {
List {
+ if profileHidden {
+ Button {
+ withAnimation { profileHidden = false }
+ } label: {
+ Label("Enter password above to show!", systemImage: "lock.open")
+ }
+ }
Section {
- ForEach(m.users) { u in
+ let users = filteredUsers()
+ ForEach(users) { u in
userView(u.user)
}
.onDelete { indexSet in
if let i = indexSet.first {
- showDeleteConfirmation = true
- userToDelete = i
+ if m.users.count > 1 && (m.users[i].user.hidden || visibleUsersCount > 1) {
+ showDeleteConfirmation = true
+ userToDelete = users[i]
+ } else {
+ alert = .cantDeleteLastUser
+ }
}
}
- NavigationLink {
- CreateProfile()
- } label: {
- Label("Add profile", systemImage: "plus")
+ if searchTextOrPassword == "" {
+ NavigationLink {
+ CreateProfile()
+ } label: {
+ Label("Add profile", systemImage: "plus")
+ }
+ .frame(height: 44)
+ .padding(.vertical, 4)
}
- .frame(height: 44)
- .padding(.vertical, 4)
} footer: {
- Text("Your chat profiles are stored locally, only on your device.")
+ Text("Tap to activate profile.")
+ .font(.body)
+ .padding(.top, 8)
+
}
}
.toolbar { EditButton() }
.navigationTitle("Your chat profiles")
+ .searchable(text: $searchTextOrPassword, placement: .navigationBarDrawer(displayMode: .always))
+ .autocorrectionDisabled(true)
+ .textInputAutocapitalization(.never)
+ .onAppear {
+ if showHiddenProfilesNotice && m.users.count > 1 {
+ alert = .hiddenProfilesNotice
+ }
+ }
.confirmationDialog("Delete chat profile?", isPresented: $showDeleteConfirmation, titleVisibility: .visible) {
deleteModeButton("Profile and server connections", true)
deleteModeButton("Local profile data only", false)
}
+ .sheet(item: $selectedUser) { user in
+ HiddenProfileView(user: user, profileHidden: $profileHidden)
+ }
+ .onChange(of: profileHidden) { _ in
+ DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
+ withAnimation { profileHidden = false }
+ }
+ }
.alert(item: $alert) { alert in
switch alert {
- case let .deleteUser(index, delSMPQueues):
+ case let .deleteUser(userInfo, delSMPQueues):
return Alert(
title: Text("Delete user profile?"),
message: Text("All chats and messages will be deleted - this cannot be undone!"),
primaryButton: .destructive(Text("Delete")) {
- removeUser(index, delSMPQueues)
+ Task { await removeUser(userInfo, delSMPQueues) }
},
secondaryButton: .cancel()
)
+ case .cantDeleteLastUser:
+ return Alert(
+ title: Text("Can't delete user profile!"),
+ message: m.users.count > 1
+ ? Text("There should be at least one visible user profile.")
+ : Text("There should be at least one user profile.")
+ )
+ case .hiddenProfilesNotice:
+ return Alert(
+ title: Text("Make profile private!"),
+ message: Text("You can hide or mute a user profile - swipe it to the right.\nSimpleX Lock must be enabled."),
+ primaryButton: .default(Text("Don't show again")) {
+ showHiddenProfilesNotice = false
+ },
+ secondaryButton: .default(Text("Ok"))
+ )
+ case .muteProfileAlert:
+ return Alert(
+ title: Text("Muted when inactive!"),
+ message: Text("You will still receive calls and notifications from muted profiles when they are active."),
+ primaryButton: .default(Text("Don't show again")) {
+ showMuteProfileAlert = false
+ },
+ secondaryButton: .default(Text("Ok"))
+ )
case let .activateUserError(error: err):
return Alert(
title: Text("Error switching profile!"),
@@ -91,43 +161,66 @@ struct UserProfilesView: View {
}
}
+ private func filteredUsers() -> [UserInfo] {
+ let s = searchTextOrPassword.trimmingCharacters(in: .whitespaces)
+ let lower = s.localizedLowercase
+ return m.users.filter { u in
+ if (u.user.activeUser || u.user.viewPwdHash == nil) && (s == "" || u.user.chatViewName.localizedLowercase.contains(lower)) {
+ return true
+ }
+ if let ph = u.user.viewPwdHash {
+ return s != "" && chatPasswordHash(s, ph.salt) == ph.hash
+ }
+ return false
+ }
+ }
+
+ private var visibleUsersCount: Int {
+ m.users.filter({ u in !u.user.hidden }).count
+ }
+
+ private func userViewPassword(_ user: User) -> String? {
+ user.activeUser || !user.hidden ? nil : searchTextOrPassword
+ }
+
private func deleteModeButton(_ title: LocalizedStringKey, _ delSMPQueues: Bool) -> some View {
Button(title, role: .destructive) {
- if let i = userToDelete {
- alert = .deleteUser(index: i, delSMPQueues: delSMPQueues)
+ if let userInfo = userToDelete {
+ alert = .deleteUser(userInfo: userInfo, delSMPQueues: delSMPQueues)
}
}
}
- private func removeUser(_ index: Int, _ delSMPQueues: Bool) {
- if index >= m.users.count { return }
+ private func removeUser(_ userInfo: UserInfo, _ delSMPQueues: Bool) async {
do {
- let u = m.users[index].user
+ let u = userInfo.user
if u.activeUser {
- if let newActive = m.users.first(where: { !$0.user.activeUser }) {
- try changeActiveUser_(newActive.user.userId)
- try deleteUser(u.userId)
+ if let newActive = m.users.first(where: { u in !u.user.activeUser && !u.user.hidden }) {
+ try await changeActiveUserAsync_(newActive.user.userId, viewPwd: nil)
+ try await deleteUser(u)
}
} else {
- try deleteUser(u.userId)
+ try await deleteUser(u)
}
} catch let error {
let a = getErrorAlert(error, "Error deleting user profile")
alert = .error(title: a.title, error: a.message)
}
- func deleteUser(_ userId: Int64) throws {
- try apiDeleteUser(userId, delSMPQueues)
- m.users.remove(at: index)
+ func deleteUser(_ user: User) async throws {
+ try await apiDeleteUser(user.userId, delSMPQueues, viewPwd: userViewPassword(user))
+ await MainActor.run { withAnimation { m.removeUser(user) } }
}
}
private func userView(_ user: User) -> some View {
Button {
- do {
- try changeActiveUser_(user.userId)
- } catch {
- alert = .activateUserError(error: responseError(error))
+ Task {
+ do {
+ try await changeActiveUserAsync_(user.userId, viewPwd: userViewPassword(user))
+ } catch {
+ await MainActor.run { alert = .activateUserError(error: responseError(error)) }
+ }
}
} label: {
HStack {
@@ -137,14 +230,75 @@ struct UserProfilesView: View {
.padding(.trailing, 12)
Text(user.chatViewName)
Spacer()
- Image(systemName: "checkmark")
- .foregroundColor(user.activeUser ? .primary : .clear)
+ if user.activeUser {
+ Image(systemName: "checkmark").foregroundColor(.primary)
+ } else if user.hidden {
+ Image(systemName: "lock").foregroundColor(.secondary)
+ } else if !user.showNtfs {
+ Image(systemName: "speaker.slash").foregroundColor(.secondary)
+ } else {
+ Image(systemName: "checkmark").foregroundColor(.clear)
+ }
}
}
.disabled(user.activeUser)
.foregroundColor(.primary)
.deleteDisabled(m.users.count <= 1)
+ .swipeActions(edge: .leading, allowsFullSwipe: true) {
+ if user.hidden {
+ Button("Unhide") {
+ setUserPrivacy(user) { try await apiUnhideUser(user.userId, viewPwd: userViewPassword(user)) }
+ }
+ .tint(.green)
+ } else {
+ if visibleUsersCount > 1 && prefPerformLA {
+ Button("Hide") {
+ selectedUser = user
+ }
+ .tint(.gray)
+ }
+ Group {
+ if user.showNtfs {
+ Button("Mute") {
+ setUserPrivacy(user, successAlert: showMuteProfileAlert ? .muteProfileAlert : nil) {
+ try await apiMuteUser(user.userId, viewPwd: userViewPassword(user))
+ }
+ }
+ } else {
+ Button("Unmute") {
+ setUserPrivacy(user) { try await apiUnmuteUser(user.userId, viewPwd: userViewPassword(user)) }
+ }
+ }
+ }
+ .tint(.accentColor)
+ }
+ }
}
+
+ private func setUserPrivacy(_ user: User, successAlert: UserProfilesAlert? = nil, _ api: @escaping () async throws -> User) {
+ Task {
+ do {
+ let u = try await api()
+ await MainActor.run {
+ withAnimation { m.updateUser(u) }
+ if successAlert != nil {
+ alert = successAlert
+ }
+ }
+ } catch let error {
+ let a = getErrorAlert(error, "Error updating user privacy")
+ alert = .error(title: a.title, error: a.message)
+ }
+ }
+ }
+}
+
+public func chatPasswordHash(_ pwd: String, _ salt: String) -> String {
+ var cPwd = pwd.cString(using: .utf8)!
+ var cSalt = salt.cString(using: .utf8)!
+ let cHash = chat_password_hash(&cPwd, &cSalt)!
+ let hash = fromCString(cHash)
+ return hash
}
struct UserProfilesView_Previews: PreviewProvider {
diff --git a/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff b/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff
index bf42c4057..678d25b4b 100644
--- a/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff
+++ b/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff
@@ -5,97 +5,120 @@
-
+
+
+
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.
-
+
(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
-
+
%@ is not verified
+ %@ لم يتم التحقق منها
No comment provided by engineer.
-
+
%@ is verified
+ %@ تم التحقق منه
No comment provided by engineer.
-
+
%@ wants to connect!
+ %@ يريد الاتصال!
notification title
-
+
%d days
+ %d أيام
message ttl
-
+
%d hours
+ %d ساعات
message ttl
-
+
%d min
+ %d دقيقة
message ttl
-
+
%d months
+ %d شهور
message ttl
-
+
%d sec
+ %d ثانية
message ttl
%d skipped message(s)
integrity error chat item
-
+
%lld
+ %lld
No comment provided by engineer.
-
+
%lld %@
+ %lld %@
No comment provided by engineer.
@@ -106,182 +129,226 @@
%lld file(s) with total size of %@
No comment provided by engineer.
-
+
%lld members
+ %lld أعضاء
No comment provided by engineer.
%lld second(s)
No comment provided by engineer.
-
+
%lldd
+ %lldd
No comment provided by engineer.
-
+
%lldh
+ %lldh
No comment provided by engineer.
-
+
%lldk
+ %lldk
No comment provided by engineer.
-
+
%lldm
+ %lldm
No comment provided by engineer.
-
+
%lldmth
+ %lldmth
No comment provided by engineer.
-
+
%llds
+ %llds
No comment provided by engineer.
-
+
%lldw
+ %lldw
No comment provided by engineer.
-
+
(
+ (
No comment provided by engineer.
-
+
)
+ )
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 ** لتستخدمه جهة الاتصال الخاصة بك.
No comment provided by engineer.
-
+
**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have.
+ ** المزيد من الخصوصية **: تحقق من الرسائل الجديدة كل 20 دقيقة. تتم مشاركة رمز الجهاز مع خادم SimpleX Chat ، ولكن ليس عدد جهات الاتصال أو الرسائل لديك.
No comment provided by engineer.
-
+
**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app).
+ ** الأكثر خصوصية **: لا تستخدم خادم إشعارات 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.
+ ** يرجى ملاحظة **: لن تتمكن من استعادة أو تغيير عبارة المرور إذا فقدتها.
No comment provided by engineer.
-
+
**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from.
+ ** موصى به **: يتم إرسال رمز الجهاز والإشعارات إلى خادم إشعارات 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.
No comment provided by engineer.
-
+
**e2e encrypted** audio call
+ ** تشفير ** e2e ** مكالمة صوتية
No comment provided by engineer.
-
+
**e2e encrypted** video call
+ ** مكالمة فيديو ** مشفرة e2e **
No comment provided by engineer.
-
+
\*bold*
+ \*عريض*
No comment provided by engineer.
-
+
,
+ ,
No comment provided by engineer.
-
+
.
+ .
No comment provided by engineer.
-
+
1 day
+ يوم 1
message ttl
-
+
1 hour
+ 1 ساعة
message ttl
-
+
1 month
+ شهر 1
message ttl
-
+
1 week
+ اسبوع 1
message ttl
-
+
2 weeks
+ 2 أسابيع
message ttl
-
+
6
+ 6
No comment provided by engineer.
-
+
:
+ :
No comment provided by engineer.
-
+
A new contact
+ جهة اتصال جديدة
notification title
-
+
A random profile will be sent to the contact that you received this link from
+ سيتم إرسال ملف تعريف عشوائي إلى جهة الاتصال التي تلقيت منها هذا الرابط
No comment provided by engineer.
-
+
A random profile will be sent to your contact
+ سيتم إرسال ملف تعريف عشوائي إلى جهة الاتصال الخاصة بك
No comment provided by engineer.
-
+
A separate TCP connection will be used **for each chat profile you have in the app**.
+ سيتم استخدام اتصال TCP منفصل ** لكل ملف تعريف دردشة لديك في التطبيق **.
No comment provided by engineer.
-
+
A separate TCP connection will be used **for each contact and group member**.
**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail.
+ سيتم استخدام اتصال TCP منفصل ** لكل جهة اتصال وعضو في المجموعة **.
+** يرجى الملاحظة **: إذا كان لديك العديد من التوصيلات ، فقد يكون استهلاك البطارية وحركة المرور أعلى بكثير وقد تفشل بعض الاتصالات.
No comment provided by engineer.
-
+
About SimpleX
+ عن SimpleX
No comment provided by engineer.
-
+
About SimpleX Chat
+ عن SimpleX Chat
No comment provided by engineer.
-
+
Accent color
+ لون التمييز
No comment provided by engineer.
-
+
Accept
+ قبول
accept contact request via notification
accept incoming call via notification
-
+
Accept contact
+ قبول الاتصال
No comment provided by engineer.
-
+
Accept contact request from %@?
+ قبول طلب الاتصال من %@?
notification body
-
+
Accept incognito
+ قبول التخفي
No comment provided by engineer.
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 b6611764d..364fba900 100644
--- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff
+++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff
@@ -2,1940 +2,2041 @@
-
+
-
+
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.
-
+
(can be copied)
- (lze kopírovat)
+ (lze kopírovat)
No comment provided by engineer.
-
+
!1 colored!
- !1 barevný!
+ !1 barevný!
No comment provided by engineer.
-
+
#secret#
- #tajný#
+ #tajný#
No comment provided by engineer.
-
+
%@
- %@
+ %@
No comment provided by engineer.
-
+
%@ %@
- %@ %@
+ %@ %@
No comment provided by engineer.
-
+
%@ / %@
- %@ / %@
+ %@ / %@
No comment provided by engineer.
-
+
%@ is connected!
- %@ je připojen!
+ %@ je připojen!
notification title
-
+
%@ is not verified
- %@ není ověřeno
+ %@ není ověřeno
No comment provided by engineer.
-
+
%@ is verified
- %@ je ověřený
+ %@ je ověřený
No comment provided by engineer.
-
+
%@ wants to connect!
- %@ se chce připojit!
+ %@ se chce připojit!
notification title
-
+
%d days
- %d dní
+ %d dní
message ttl
-
+
%d hours
- %d hodin
+ %d hodin
message ttl
-
+
%d min
- %d minuty
+ %d minuty
message ttl
-
+
%d months
- %d měsíce
+ %d měsíce
message ttl
-
+
%d sec
- %d sek
+ %d sek
message ttl
-
+
%d skipped message(s)
- %d přeskočené zprávy
+ %d přeskočené zprávy
integrity error chat item
-
+
%lld
- %lld
+ %lld
No comment provided by engineer.
-
+
%lld %@
- %lld %@
+ %lld %@
No comment provided by engineer.
-
+
%lld contact(s) selected
- %lld kontakt(y) vybrané
+ %lld kontakt(y) vybrané
No comment provided by engineer.
-
+
%lld file(s) with total size of %@
- %lld soubor(y) s celkovou velikostí %@
+ %lld soubor(y) s celkovou velikostí %@
No comment provided by engineer.
-
+
%lld members
- %lld členové
+ %lld členové
No comment provided by engineer.
-
+
%lld second(s)
- %lld vteřin
+ %lld vteřin
No comment provided by engineer.
-
+
%lldd
- %lldd
+ %lldd
No comment provided by engineer.
-
+
%lldh
- %lldh
+ %lldh
No comment provided by engineer.
-
+
%lldk
- %lldk
+ %lldk
No comment provided by engineer.
-
+
%lldm
- %lldm
+ %lldm
No comment provided by engineer.
-
+
%lldmth
- %lldmth
+ %lldmth
No comment provided by engineer.
-
+
%llds
- %llds
+ %llds
No comment provided by engineer.
-
+
%lldw
- %lldw
+ %lldw
No comment provided by engineer.
-
+
(
- (
+ (
No comment provided by engineer.
-
+
)
- )
+ )
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.
+ **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.
+ **Vytvořte odkaz / QR kód** pro váš kontakt.
No comment provided by engineer.
-
+
**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have.
- **Soukromější**: kontrolovat nové zprávy každých 20 minut. Token zařízení je sdílen se serverem SimpleX Chat, ale ne kolik máte kontaktů nebo zpráv.
+ **Soukromější**: kontrolovat nové zprávy každých 20 minut. Token zařízení je sdílen se serverem SimpleX Chat, ale ne kolik máte kontaktů nebo zpráv.
No comment provided by engineer.
-
+
**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app).
- **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).
+ **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**.
+ **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.
+ **Upozornění**: Pokud heslo ztratíte, NEBUDETE jej moci obnovit ani změnit.
No comment provided by engineer.
-
+
**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from.
- **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.
+ **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.
+ ** 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.
+ **Upozornění**: Okamžitě doručovaná oznámení vyžadují přístupové heslo uložené v Klíčence.
No comment provided by engineer.
-
+
**e2e encrypted** audio call
- **e2e šifrovaný** audio hovor
+ **e2e šifrovaný** audio hovor
No comment provided by engineer.
-
+
**e2e encrypted** video call
- **e2e šifrovaný** videohovor
+ **e2e šifrovaný** videohovor
No comment provided by engineer.
-
+
\*bold*
- \*tučně*
+ \*tučně*
No comment provided by engineer.
-
+
,
- ,
+ ,
No comment provided by engineer.
-
+
.
- .
+ .
No comment provided by engineer.
-
+
1 day
- 1 den
+ 1 den
message ttl
-
+
1 hour
- 1 hodina
+ 1 hodina
message ttl
-
+
1 month
- 1 měsíc
+ 1 měsíc
message ttl
-
+
1 week
- 1 týden
+ 1 týden
message ttl
-
+
2 weeks
- 2 týdny
+ 2 týdny
message ttl
-
+
6
- 6
+ 6
No comment provided by engineer.
-
+
:
- :
+ :
No comment provided by engineer.
-
+
A new contact
- Nový kontakt
+ Nový kontakt
notification title
-
+
A random profile will be sent to the contact that you received this link from
- Náhodný profil bude zaslán kontaktu, od kterého jste obdrželi tento odkaz
+ Náhodný profil bude zaslán kontaktu, od kterého jste obdrželi tento odkaz
No comment provided by engineer.
-
+
A random profile will be sent to your contact
- Vašemu kontaktu bude zaslán náhodný profil
+ Vašemu kontaktu bude zaslán náhodný profil
No comment provided by engineer.
-
+
A separate TCP connection will be used **for each chat profile you have in the app**.
- Samostatné připojení TCP bude použito **pro každý chat profil, který máte v aplikaci**.
+ Samostatné připojení TCP bude použito **pro každý chat profil, který máte v aplikaci**.
No comment provided by engineer.
-
+
A separate TCP connection will be used **for each contact and group member**.
**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail.
- **pro každý kontakt a člena skupiny** bude použito samostatné připojení TCP.
+ **pro každý kontakt a člena skupiny** bude použito samostatné připojení TCP.
**Upozornění**: Pokud máte mnoho připojení, spotřeba baterie a provozu může být podstatně vyšší a některá připojení mohou selhat.
No comment provided by engineer.
-
+
About SimpleX
- O SimpleX
+ O SimpleX
No comment provided by engineer.
-
+
About SimpleX Chat
- O SimpleX chat
+ O SimpleX chat
No comment provided by engineer.
-
+
Accent color
- Barva přízvuku
+ Zbarvení
No comment provided by engineer.
-
+
Accept
- Přijmout
+ Přijmout
accept contact request via notification
accept incoming call via notification
-
+
Accept contact
- Přijmout kontakt
+ Přijmout kontakt
No comment provided by engineer.
-
+
Accept contact request from %@?
- Přijmout žádost o kontakt od %@?
+ Přijmout žádost o kontakt od %@?
notification body
-
+
Accept incognito
- Přijmout inkognito
+ Přijmout inkognito
No comment provided by engineer.
-
+
Accept requests
- Přijímat žádosti
+ Přijímat žádosti
No comment provided by engineer.
-
+
Add preset servers
- Přidejte přednastavené servery
+ Přidejte přednastavené servery
No comment provided by engineer.
-
+
Add profile
- Přidat profil
+ Přidat profil
No comment provided by engineer.
-
+
Add servers by scanning QR codes.
- Přidejte servery skenováním QR kódů.
+ Přidejte servery skenováním QR kódů.
No comment provided by engineer.
-
+
Add server…
- Přidat server…
+ Přidat server…
No comment provided by engineer.
-
+
Add to another device
- Přidat do jiného zařízení
+ Přidat do jiného zařízení
No comment provided by engineer.
-
+
+ Add welcome message
+ No comment provided by engineer.
+
+
Admins can create the links to join groups.
- Správci mohou vytvářet odkazy pro připojení ke skupinám.
+ Správci mohou vytvářet odkazy pro připojení ke skupinám.
No comment provided by engineer.
-
+
Advanced network settings
- Pokročilá nastavení sítě
+ Pokročilá nastavení sítě
No comment provided by engineer.
-
+
All chats and messages will be deleted - this cannot be undone!
- Všechny chaty a zprávy budou smazány – tuto akci nelze vrátit zpět!
+ Všechny chaty a zprávy budou smazány – tuto akci nelze vrátit zpět!
No comment provided by engineer.
-
+
All group members will remain connected.
- Všichni členové skupiny zůstanou připojeni.
+ Všichni členové skupiny zůstanou připojeni.
No comment provided by engineer.
-
+
All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you.
- Všechny zprávy budou smazány – tuto akci nelze vrátit zpět! Zprávy budou smazány POUZE pro vás.
+ Všechny zprávy budou smazány – tuto akci nelze vrátit zpět! Zprávy budou smazány POUZE pro vás.
No comment provided by engineer.
-
+
All your contacts will remain connected
- Všechny vaše kontakty zůstanou připojeny
+ Všechny vaše kontakty zůstanou připojeny
No comment provided by engineer.
-
+
Allow
- Povolit
+ Povolit
No comment provided by engineer.
-
+
Allow disappearing messages only if your contact allows it to you.
- Povolte mizící zprávy, pouze pokud vám to váš kontakt dovolí.
+ Povolte mizící zprávy, pouze pokud vám to váš kontakt dovolí.
No comment provided by engineer.
-
+
Allow irreversible message deletion only if your contact allows it to you.
- Povolte nevratné smazání zprávy pouze v případě, že vám to váš kontakt dovolí.
+ Povolte nevratné smazání zprávy pouze v případě, že vám to váš kontakt dovolí.
No comment provided by engineer.
-
+
Allow sending direct messages to members.
- Povolit odesílání přímých zpráv členům.
+ Povolit odesílání přímých zpráv členům.
No comment provided by engineer.
-
+
Allow sending disappearing messages.
- Povolit odesílání mizících zpráv.
+ Povolit odesílání mizících zpráv.
No comment provided by engineer.
-
+
Allow to irreversibly delete sent messages.
- Povolit nevratné smazání odeslaných zpráv.
+ Povolit nevratné smazání odeslaných zpráv.
No comment provided by engineer.
-
+
Allow to send voice messages.
- Povolit odesílání hlasových zpráv.
+ Povolit odesílání hlasových zpráv.
No comment provided by engineer.
-
+
Allow voice messages only if your contact allows them.
- Povolte hlasové zprávy, pouze pokud je váš kontakt povolí.
+ Povolte hlasové zprávy, pouze pokud je váš kontakt povolí.
No comment provided by engineer.
-
+
Allow voice messages?
- Povolit hlasové zprávy?
+ Povolit hlasové zprávy?
No comment provided by engineer.
-
+
Allow your contacts to irreversibly delete sent messages.
- Umožněte svým kontaktům nevratně odstranit odeslané zprávy.
+ Umožněte svým kontaktům nevratně odstranit odeslané zprávy.
No comment provided by engineer.
-
+
Allow your contacts to send disappearing messages.
- Umožněte svým kontaktům odesílat mizející zprávy.
+ Umožněte svým kontaktům odesílat mizící zprávy.
No comment provided by engineer.
-
+
Allow your contacts to send voice messages.
- Povolte svým kontaktům odesílání hlasových zpráv.
+ Povolte svým kontaktům odesílání hlasových zpráv.
No comment provided by engineer.
-
+
Already connected?
- Již připojeno?
+ Již připojeno?
No comment provided by engineer.
-
+
+ Always use relay
+ Spojení přes relé
+ No comment provided by engineer.
+
+
Answer call
- Přijmout hovor
+ Přijmout hovor
No comment provided by engineer.
-
+
App build: %@
- Sestavení aplikace: %@
+ Sestavení aplikace: %@
No comment provided by engineer.
-
+
App icon
- Ikona aplikace
+ Ikona aplikace
No comment provided by engineer.
-
+
App version
- Verze aplikace
+ Verze aplikace
No comment provided by engineer.
-
+
App version: v%@
- Verze aplikace: v%@
+ Verze aplikace: v%@
No comment provided by engineer.
-
+
Appearance
- Vzhled
+ Vzhled
No comment provided by engineer.
-
+
Attach
- Připojit
+ Připojit
No comment provided by engineer.
-
+
Audio & video calls
- Audio a video hovory
+ Audio a video hovory
No comment provided by engineer.
-
+
+ Audio and video calls
+ No comment provided by engineer.
+
+
Authentication failed
- Ověření selhalo
+ Ověření selhalo
No comment provided by engineer.
-
+
+ Authentication is required before the call is connected, but you may miss calls.
+ Před spojením hovoru je vyžadováno ověření, ale můžete zmeškat hovory.
+ No comment provided by engineer.
+
+
Authentication unavailable
- Ověřování není k dispozici
+ Ověřování není k dispozici
No comment provided by engineer.
-
+
Auto-accept contact requests
- Automatické přijímání žádostí o kontakt
+ Automatické přijímání žádostí o kontakt
No comment provided by engineer.
-
+
Auto-accept images
- Automaticky přijímat obrázky
+ Automaticky přijímat obrázky
No comment provided by engineer.
-
+
Automatically
- Automaticky
+ Automaticky
No comment provided by engineer.
-
+
Back
- Zpět
+ Zpět
No comment provided by engineer.
-
+
Both you and your contact can irreversibly delete sent messages.
- Vy i váš kontakt můžete nevratně mazat odeslané zprávy.
+ Vy i váš kontakt můžete nevratně mazat odeslané zprávy.
No comment provided by engineer.
-
+
Both you and your contact can send disappearing messages.
- Vy i váš kontakt můžete posílat mizející zprávy.
+ Vy i váš kontakt můžete posílat mizící zprávy.
No comment provided by engineer.
-
+
Both you and your contact can send voice messages.
- Hlasové zprávy můžete posílat vy i váš kontakt.
+ Hlasové zprávy můžete posílat vy i váš kontakt.
No comment provided by engineer.
-
+
+ By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).
+ Podle chat profilu (výchozí) nebo [podle připojení](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).
+ No comment provided by engineer.
+
+
Call already ended!
- Hovor již skončil!
+ Hovor již skončil!
No comment provided by engineer.
-
+
Calls
- Hovory
+ Hovory
No comment provided by engineer.
-
+
+ Can't delete user profile!
+ No comment provided by engineer.
+
+
Can't invite contact!
- Nelze pozvat kontakt!
+ Nelze pozvat kontakt!
No comment provided by engineer.
-
+
Can't invite contacts!
- Nelze pozvat kontakty!
+ Nelze pozvat kontakty!
No comment provided by engineer.
-
+
Cancel
- Zrušit
+ Zrušit
No comment provided by engineer.
-
+
Cannot access keychain to save database password
- Nelze získat přístup ke klíčence pro uložení hesla databáze
+ Nelze získat přístup ke klíčence pro uložení hesla databáze
No comment provided by engineer.
-
+
Cannot receive file
- Nelze přijmout soubor
+ Nelze přijmout soubor
No comment provided by engineer.
-
+
Change
- Změnit
+ Změnit
No comment provided by engineer.
-
+
Change database passphrase?
- Změnit přístupovou frázi databáze?
+ Změnit přístupovou frázi databáze?
No comment provided by engineer.
-
+
Change member role?
- Změnit roli člena?
+ Změnit roli člena?
No comment provided by engineer.
-
+
Change receiving address
- Změna adresy příjemce
+ Změna adresy příjemce
No comment provided by engineer.
-
+
Change receiving address?
- Změnit přijímající adresu?
+ Změnit přijímající adresu?
No comment provided by engineer.
-
+
Change role
- Změnit roli
+ Změnit roli
No comment provided by engineer.
-
+
Chat archive
- Chat se archivuje
+ Chat se archivuje
No comment provided by engineer.
-
+
Chat console
- Konzola pro chat
+ Konzola pro chat
No comment provided by engineer.
-
+
Chat database
- Chat databáze
+ Chat databáze
No comment provided by engineer.
-
+
Chat database deleted
- Databáze chatu odstraněna
+ Databáze chatu odstraněna
No comment provided by engineer.
-
+
Chat database imported
- Importovaná databáze chatu
+ Importovaná databáze chatu
No comment provided by engineer.
-
+
Chat is running
- Chat je spuštěn
+ Chat je spuštěn
No comment provided by engineer.
-
+
Chat is stopped
- Chat je zastaven
+ Chat je zastaven
No comment provided by engineer.
-
+
Chat preferences
- Předvolby chatu
+ Předvolby chatu
No comment provided by engineer.
-
+
Chats
- Chaty
+ Chaty
No comment provided by engineer.
-
+
Check server address and try again.
- Zkontrolujte adresu serveru a zkuste to znovu.
+ Zkontrolujte adresu serveru a zkuste to znovu.
No comment provided by engineer.
-
+
+ Chinese and Spanish interface
+ No comment provided by engineer.
+
+
Choose file
- Vybrat soubor
+ Vybrat soubor
No comment provided by engineer.
-
+
Choose from library
- Vybrat z knihovny
+ Vybrat z knihovny
No comment provided by engineer.
-
+
Clear
- Vyčistit
+ Vyčistit
No comment provided by engineer.
-
+
Clear conversation
- Vyčistit konverzaci
+ Vyčistit konverzaci
No comment provided by engineer.
-
+
Clear conversation?
- Vyčistit konverzaci?
+ Vyčistit konverzaci?
No comment provided by engineer.
-
+
Clear verification
- Zrušte ověření
+ Zrušte ověření
No comment provided by engineer.
-
+
Colors
- Barvy
+ Barvy
No comment provided by engineer.
-
+
Compare security codes with your contacts.
- Porovnejte bezpečnostní kódy se svými kontakty.
+ Porovnejte bezpečnostní kódy se svými kontakty.
No comment provided by engineer.
-
+
Configure ICE servers
- Konfigurace serverů ICE
+ Konfigurace serverů ICE
No comment provided by engineer.
-
+
Confirm
- Potvrdit
+ Potvrdit
No comment provided by engineer.
-
+
Confirm new passphrase…
- Potvrdit novou heslovou frázi…
+ Potvrdit novou heslovou frázi…
No comment provided by engineer.
-
+
+ Confirm password
+ No comment provided by engineer.
+
+
Connect
- Připojit
+ Připojit
server test step
-
+
Connect via contact link?
- Připojit se přes kontaktní odkaz?
+ Připojit se přes kontaktní odkaz?
No comment provided by engineer.
-
+
Connect via group link?
- Připojit se přes odkaz skupiny?
+ Připojit se přes odkaz skupiny?
No comment provided by engineer.
-
+
Connect via link
- Připojte se prostřednictvím odkazu
+ 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
+ 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?
+ Připojit se jednorázovým odkazem?
No comment provided by engineer.
-
- Connect via relay
- Spojení přes relé
- No comment provided by engineer.
-
-
+
Connecting to server…
- Připojování k serveru…
+ Připojování k serveru…
No comment provided by engineer.
-
+
Connecting to server… (error: %@)
- Připojování k serveru... (chyba: %@)
+ Připojování k serveru... (chyba: %@)
No comment provided by engineer.
-
+
Connection
- Připojení
+ Připojení
No comment provided by engineer.
-
+
Connection error
- Chyba připojení
+ Chyba připojení
No comment provided by engineer.
-
+
Connection error (AUTH)
- Chyba spojení (AUTH)
+ Chyba spojení (AUTH)
No comment provided by engineer.
-
+
Connection request
- Žádost o připojení
+ Žádost o připojení
No comment provided by engineer.
-
+
Connection request sent!
- Požadavek na připojení byl odeslán!
+ Požadavek na připojení byl odeslán!
No comment provided by engineer.
-
+
Connection timeout
- Časový limit připojení
+ Časový limit připojení
No comment provided by engineer.
-
+
Contact allows
- Kontakt povoluje
+ Kontakt povolil
No comment provided by engineer.
-
+
Contact already exists
- Kontakt již existuje
+ Kontakt již existuje
No comment provided by engineer.
-
+
Contact and all messages will be deleted - this cannot be undone!
- Kontakt a všechny zprávy budou smazány - nelze to vzít zpět!
+ Kontakt a všechny zprávy budou smazány - nelze to vzít zpět!
No comment provided by engineer.
-
+
Contact hidden:
- Skrytý kontakt:
+ Skrytý kontakt:
notification
-
+
Contact is connected
- Kontakt je připojen
+ Kontakt je připojen
notification
-
+
Contact is not connected yet!
- Kontakt ještě není připojen!
+ Kontakt ještě není připojen!
No comment provided by engineer.
-
+