Compare commits
69 Commits
v4.4.0-bet
...
v4.4.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98417dafc4 | ||
|
|
e8374be19c | ||
|
|
62a2f61751 | ||
|
|
2d47175f94 | ||
|
|
a6d7604d21 | ||
|
|
9e3573fc76 | ||
|
|
13ebaf587e | ||
|
|
61e20550bc | ||
|
|
d1cc5c1769 | ||
|
|
16b041c8c6 | ||
|
|
810f248c74 | ||
|
|
813fecddfe | ||
|
|
5a7d61c964 | ||
|
|
cba24983e6 | ||
|
|
4dc2a1b72d | ||
|
|
3d4e4e2ef9 | ||
|
|
113c67ec95 | ||
|
|
a2e887024f | ||
|
|
37262b3ed5 | ||
|
|
dca4fe7701 | ||
|
|
88c9334d18 | ||
|
|
58f06aa821 | ||
|
|
ae5deab8d3 | ||
|
|
edfece3206 | ||
|
|
c32cf8055d | ||
|
|
72ec03a822 | ||
|
|
d89e0efedd | ||
|
|
707e8592d9 | ||
|
|
a1b27e9a99 | ||
|
|
f68d8fd97c | ||
|
|
abff42a264 | ||
|
|
c1ced70836 | ||
|
|
e14966d36e | ||
|
|
97943fc609 | ||
|
|
0f143b2e77 | ||
|
|
be10dcbcfc | ||
|
|
97dbec927c | ||
|
|
b4879ca2a3 | ||
|
|
15884c0169 | ||
|
|
9f2d5486b6 | ||
|
|
80f0108b41 | ||
|
|
c37a7ebfe7 | ||
|
|
02c2c65d41 | ||
|
|
7c4700b238 | ||
|
|
54190ffff9 | ||
|
|
17eed9662e | ||
|
|
bb116bccb4 | ||
|
|
6cc267689e | ||
|
|
0e6909845f | ||
|
|
96ad9faa85 | ||
|
|
768c497025 | ||
|
|
3ec29d8ef4 | ||
|
|
6c4b92531f | ||
|
|
46d6159da5 | ||
|
|
aab6e1c52f | ||
|
|
c0a01318b5 | ||
|
|
13090ff6ed | ||
|
|
90a20cd52f | ||
|
|
74245d3f2b | ||
|
|
e48452ccff | ||
|
|
39370ba1ef | ||
|
|
a02cfb4f41 | ||
|
|
4370012b8a | ||
|
|
20c33aea72 | ||
|
|
c11a1aa0e6 | ||
|
|
166b789f3c | ||
|
|
bbc26e272c | ||
|
|
6c839f8075 | ||
|
|
be91f97c83 |
39
README.md
39
README.md
@@ -86,13 +86,15 @@ You can use SimpleX with your own servers and still communicate with people usin
|
||||
|
||||
Recent updates:
|
||||
|
||||
[Dec 06, 2022. November reviews and v4.3 released - with instant voice messages, irreversible deletion of sent messages and improved server configuration.](./blog/20221206-simplex-chat-v4.3-voice-messages.md)
|
||||
[Jan 03, 2023. v4.4 released - with disappearing messages, "live" messages, connection security verifications, GIFs and stickers and with French interface language](./blog/20230103-simplex-chat-v4.4-disappearing-messages.md).
|
||||
|
||||
[Nov 08, 2022. Security audit by Trail of Bits, the new website and v4.2 released](./blog/20221108-simplex-chat-v4.2-security-audit-new-website.md)
|
||||
[Dec 06, 2022. November reviews and v4.3 released - with instant voice messages, irreversible deletion of sent messages and improved server configuration](./blog/20221206-simplex-chat-v4.3-voice-messages.md).
|
||||
|
||||
[Sep 28, 2022. v4.0: encrypted local chat database and many other changes](./blog/20220928-simplex-chat-v4-encrypted-database.md)
|
||||
[Nov 08, 2022. Security audit by Trail of Bits, the new website and v4.2 released](./blog/20221108-simplex-chat-v4.2-security-audit-new-website.md).
|
||||
|
||||
[Sep 1, 2022. v3.2: incognito mode, support .onion server hostnames, setting contact names, changing color scheme, etc. Implementation audit is arranged for October!](./blog/20220901-simplex-chat-v3.2-incognito-mode.md)
|
||||
[Sep 28, 2022. v4.0: encrypted local chat database and many other changes](./blog/20220928-simplex-chat-v4-encrypted-database.md).
|
||||
|
||||
[Sep 1, 2022. v3.2: incognito mode, support .onion server hostnames, setting contact names, changing color scheme, etc. Implementation audit is arranged for October!](./blog/20220901-simplex-chat-v3.2-incognito-mode.md).
|
||||
|
||||
[All updates](./blog)
|
||||
|
||||
@@ -191,17 +193,21 @@ If you are considering developing with SimpleX platform please get in touch for
|
||||
- ✅ View deleted messages, full message deletion by sender (with recipient opt-in per contact).
|
||||
- ✅ Block screenshots and view in recent apps.
|
||||
- ✅ Advanced server configuration.
|
||||
- ✅ Disappearing messages (with recipient opt-in per-contact).
|
||||
- ✅ "Live" messages.
|
||||
- ✅ Contact verification via a separate out-of-band channel.
|
||||
- 🏗 Multiple user profiles in the same chat database.
|
||||
- 🏗 Optionally avoid re-using the same TCP session for multiple connections.
|
||||
- 🏗 File server to optimize for efficient and private sending of large files.
|
||||
- 🏗 SMP queue redundancy and rotation (manual is supported).
|
||||
- 🏗 Contact verification via a separate out-of-band channel.
|
||||
- 🏗 Ephemeral/disappearing/OTR conversations with the existing contacts.
|
||||
- Optionally avoid re-using the same TCP session for multiple connections.
|
||||
- 🏗 Reduced battery and traffic usage in large groups.
|
||||
- 🏗 Preserve message drafts.
|
||||
- 🏗 Support older Android OS and 32-bit CPUs.
|
||||
- Ephemeral/disappearing/OTR conversations with the existing contacts.
|
||||
- Access password/pin (with optional alternative access password).
|
||||
- Media server to optimize sending large files to groups.
|
||||
- Video messages.
|
||||
- Message delivery confirmation (with sender opt-in or opt-out per contact, TBC).
|
||||
- Multiple user profiles in the same chat database.
|
||||
- Feeds/broadcasts.
|
||||
- Unconfirmed: disappearing messages (with recipient opt-in per-contact).
|
||||
- Web widgets for custom interactivity in the chats.
|
||||
- Programmable chat automations / rules (automatic replies/forward/deletion/sending, reminders, etc.).
|
||||
- Supporting the same profile on multiple devices.
|
||||
@@ -213,24 +219,22 @@ If you are considering developing with SimpleX platform please get in touch for
|
||||
|
||||
## Join a user group
|
||||
|
||||
You can join a general group with more than 100 members: [#SimpleX-Group](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FWHV0YU1sYlU7NqiEHkHDB6gxO1ofTync%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAWbebOqVYuBXaiqHcXYjEHCpYi6VzDlu6CVaijDTmsQU%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22mL-7Divb94GGmGmRBef5Dg%3D%3D%22%7D).
|
||||
You can join a general English speaking group: [#SimpleX-Group](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FcIS0gu1h0Y8pZpQkDaSz7HZGSHcKpMB9%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAKzzWAJYrVt1zdgRp4pD3FBst6eK7233DJeNElENLJRA%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%228mazMhefXoM5HxWBfZnvwQ%3D%3D%22%7D).
|
||||
|
||||
You can also join smaller groups by countries/languages: [\#SimpleX-DE](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FmIorjTDPG24jdLKXwutS6o9hdQQRZwfQ%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEA9N0BZaECrAw3we3S1Wq4QO7NERBuPt9447immrB50wo%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22S8aISlOgkTMytSox9gAM2Q%3D%3D%22%7D) (German), [\#SimpleX-US](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FlTWmQplLEaoJyHnEL1-B3f2PtDsikcTs%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEA-hMBlsQjNxK2vaVhqW_UyAVtuoYqgYTigK4B9dJ9CGc%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22G0UtRHIn0TmPoo08h_cbTA%3D%3D%22%7D) (US/English), [\#SimpleX-France](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2F11r6XyjwVMj0WDIUMbmNDXO996M_EN_1%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAXDmc2Lrj9WQOjEcWa0DeQHF3HcYOp9b68s8M_BJ7gEk%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22EZCeSYpeIBkaQwCcpcF00w%3D%3D%22%7D), [\#SimpleX-RU](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FZSYM278L5WoZiApx3925EAjSXcsAVNVu%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEA7RJ2wfT8zdfOLyE5OtWLEAPowj-q6F2HB0ExbATw8Gk%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22fsVoklNGptt7n-droqJYUQ%3D%3D%22%7D) (Russian), [#SimpleX-NL](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FmP0LbswSbfxoVkkxiWE2NYnBCgZ9Snvj%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAVwZuSsw4Mf52EaBNdNI3RebsLm0jg65ZIkcmH9E5uy8%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22M9xIULUNZx51Wsa5Kdb0Sg%3D%3D%22%7D) (Netherlands/Dutch), [#SimpleX-IT](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FaZ_wjh6QAYHB-LjyGtp8bllkzoq880u-%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEA-_Wulzc3j16i7t77XJ5wgwxeW8_Ea8GxetMo7K4MgjI%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22QWmXdrFzIeMd2OoEPMFkBQ%3D%3D%22%7D) (Italian).
|
||||
Groups in languages other than English, that we have app interface translated into: [\#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).
|
||||
|
||||
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.
|
||||
|
||||
Let us know if you'd like to add some other countries to the list.
|
||||
|
||||
Join via the app to share what's going on and ask any questions!
|
||||
|
||||
## Contribute
|
||||
|
||||
We would love to have you join the development! You can contribute to SimpleX Chat with:
|
||||
|
||||
- developing features - please connect to us via chat so we can help you get started.
|
||||
- writing a tutorial or recipes about hosting servers, chat bot automations, etc.
|
||||
- translate UI to some language - we are currently setting up the UI to simplify it, please get in touch and let us know if you would be able to support and update the translations.
|
||||
- translate UI to your language - we are using [Weblate](https://hosted.weblate.org/projects/simplex-chat/) to translate the interface, please get in touch if you want to contribute!
|
||||
- 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
|
||||
|
||||
@@ -250,6 +254,7 @@ It is possible to donate via:
|
||||
- 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,
|
||||
|
||||
@@ -11,8 +11,8 @@ android {
|
||||
applicationId "chat.simplex.app"
|
||||
minSdk 29
|
||||
targetSdk 32
|
||||
versionCode 80
|
||||
versionName "4.4-beta.1"
|
||||
versionCode 87
|
||||
versionName "4.4.1"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
ndk {
|
||||
|
||||
@@ -42,7 +42,6 @@ import chat.simplex.app.views.newchat.*
|
||||
import chat.simplex.app.views.onboarding.*
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class MainActivity: FragmentActivity() {
|
||||
@@ -479,7 +478,6 @@ fun processExternalIntent(intent: Intent?, chatModel: ChatModel) {
|
||||
fun connectIfOpenedViaUri(uri: Uri, chatModel: ChatModel) {
|
||||
Log.d(TAG, "connectIfOpenedViaUri: opened via link")
|
||||
if (chatModel.currentUser.value == null) {
|
||||
// TODO open from chat list view
|
||||
chatModel.appOpenUrl.value = uri
|
||||
} else {
|
||||
withUriAction(uri) { linkType ->
|
||||
|
||||
@@ -39,7 +39,7 @@ class SimplexApp: Application(), LifecycleEventObserver {
|
||||
var isAppOnForeground: Boolean = false
|
||||
|
||||
fun initChatController(useKey: String? = null, startChat: Boolean = true) {
|
||||
val dbKey = useKey ?: DatabaseUtils.useDatabaseKey() ?: ""
|
||||
val dbKey = useKey ?: DatabaseUtils.useDatabaseKey()
|
||||
val dbAbsolutePathPrefix = getFilesDirectory(SimplexApp.context)
|
||||
val migrated: Array<Any> = chatMigrateInit(dbAbsolutePathPrefix, dbKey)
|
||||
val res: DBMigrationResult = kotlin.runCatching {
|
||||
|
||||
@@ -4,8 +4,6 @@ import android.net.Uri
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material.icons.outlined.Check
|
||||
import androidx.compose.material.icons.outlined.Close
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
@@ -19,6 +17,7 @@ import chat.simplex.app.views.helpers.*
|
||||
import chat.simplex.app.views.onboarding.OnboardingStage
|
||||
import chat.simplex.app.views.usersettings.NotificationPreviewMode
|
||||
import chat.simplex.app.views.usersettings.NotificationsMode
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.datetime.*
|
||||
import kotlinx.serialization.*
|
||||
import kotlinx.serialization.descriptors.*
|
||||
@@ -26,6 +25,7 @@ import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.json.*
|
||||
import java.io.File
|
||||
import kotlin.random.Random
|
||||
import kotlin.time.*
|
||||
|
||||
/*
|
||||
@@ -181,7 +181,13 @@ class ChatModel(val controller: ChatController) {
|
||||
}
|
||||
// add to current chat
|
||||
if (chatId.value == cInfo.id) {
|
||||
chatItems.add(cItem)
|
||||
runBlocking(Dispatchers.Main) {
|
||||
if (chatItems.lastOrNull()?.id == ChatItem.TEMP_LIVE_CHAT_ITEM_ID) {
|
||||
chatItems.add(kotlin.math.max(0, chatItems.lastIndex), cItem)
|
||||
} else {
|
||||
chatItems.add(cItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +218,9 @@ class ChatModel(val controller: ChatController) {
|
||||
chatItems[itemIndex] = cItem
|
||||
return false
|
||||
} else {
|
||||
chatItems.add(cItem)
|
||||
runBlocking(Dispatchers.Main) {
|
||||
chatItems.add(cItem)
|
||||
}
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
@@ -256,6 +264,20 @@ class ChatModel(val controller: ChatController) {
|
||||
}
|
||||
}
|
||||
|
||||
fun addLiveDummy(chatInfo: ChatInfo): ChatItem {
|
||||
val cItem = ChatItem.liveDummy(chatInfo is ChatInfo.Direct)
|
||||
runBlocking(Dispatchers.Main) {
|
||||
chatItems.add(cItem)
|
||||
}
|
||||
return cItem
|
||||
}
|
||||
|
||||
fun removeLiveDummy() {
|
||||
if (chatItems.lastOrNull()?.id == ChatItem.TEMP_LIVE_CHAT_ITEM_ID) {
|
||||
chatItems.removeLast()
|
||||
}
|
||||
}
|
||||
|
||||
fun markChatItemsRead(cInfo: ChatInfo, range: CC.ItemRange? = null, unreadCountAfter: Int? = null) {
|
||||
val markedRead = markItemsReadInCurrentChat(cInfo, range)
|
||||
// update preview
|
||||
@@ -557,6 +579,30 @@ sealed class ChatInfo: SomeChat, NamedChat {
|
||||
ContactConnection(PendingContactConnection.getSampleData(status, viaContactUri))
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable @SerialName("invalidJSON")
|
||||
class InvalidJSON(val json: String): ChatInfo() {
|
||||
override val chatType get() = ChatType.Direct
|
||||
override val localDisplayName get() = invalidChatName
|
||||
override val id get() = ""
|
||||
override val apiId get() = 0L
|
||||
override val ready get() = false
|
||||
override val sendMsgEnabled get() = false
|
||||
override val ntfsEnabled get() = false
|
||||
override val incognito get() = false
|
||||
override fun featureEnabled(feature: ChatFeature) = false
|
||||
override val timedMessagesTTL: Int? get() = null
|
||||
override val createdAt get() = Clock.System.now()
|
||||
override val updatedAt get() = Clock.System.now()
|
||||
override val displayName get() = invalidChatName
|
||||
override val fullName get() = invalidChatName
|
||||
override val image get() = null
|
||||
override val localAlias get() = ""
|
||||
|
||||
companion object {
|
||||
private val invalidChatName = generalGetString(R.string.invalid_chat)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@@ -733,7 +779,7 @@ data class GroupInfo (
|
||||
override fun featureEnabled(feature: ChatFeature) = when (feature) {
|
||||
ChatFeature.TimedMessages -> fullGroupPreferences.timedMessages.on
|
||||
ChatFeature.FullDelete -> fullGroupPreferences.fullDelete.on
|
||||
ChatFeature.Voice -> fullGroupPreferences.fullDelete.on
|
||||
ChatFeature.Voice -> fullGroupPreferences.voice.on
|
||||
}
|
||||
override val timedMessagesTTL: Int? get() = with(fullGroupPreferences.timedMessages) { if (on) ttl else null }
|
||||
override val displayName get() = groupProfile.displayName
|
||||
@@ -769,6 +815,7 @@ data class GroupInfo (
|
||||
data class GroupProfile (
|
||||
override val displayName: String,
|
||||
override val fullName: String,
|
||||
val description: String? = null,
|
||||
override val image: String? = null,
|
||||
override val localAlias: String = "",
|
||||
val groupPreferences: GroupPreferences? = null
|
||||
@@ -1167,6 +1214,7 @@ data class ChatItem (
|
||||
is CIContent.SndGroupFeature -> showNtfDir
|
||||
is CIContent.RcvChatFeatureRejected -> showNtfDir
|
||||
is CIContent.RcvGroupFeatureRejected -> showNtfDir
|
||||
is CIContent.InvalidJSON -> false
|
||||
}
|
||||
|
||||
fun withStatus(status: CIStatus): ChatItem = this.copy(meta = meta.copy(itemStatus = status))
|
||||
@@ -1253,7 +1301,8 @@ data class ChatItem (
|
||||
}
|
||||
|
||||
private const val TEMP_DELETED_CHAT_ITEM_ID = -1L
|
||||
|
||||
const val TEMP_LIVE_CHAT_ITEM_ID = -2L
|
||||
|
||||
val deletedItemDummy: ChatItem
|
||||
get() = ChatItem(
|
||||
chatDir = CIDirection.DirectRcv(),
|
||||
@@ -1274,6 +1323,35 @@ data class ChatItem (
|
||||
quotedItem = null,
|
||||
file = null
|
||||
)
|
||||
|
||||
fun liveDummy(direct: Boolean): ChatItem = ChatItem(
|
||||
chatDir = if (direct) CIDirection.DirectSnd() else CIDirection.GroupSnd(),
|
||||
meta = CIMeta(
|
||||
itemId = TEMP_LIVE_CHAT_ITEM_ID,
|
||||
itemTs = Clock.System.now(),
|
||||
itemText = "",
|
||||
itemStatus = CIStatus.RcvRead(),
|
||||
createdAt = Clock.System.now(),
|
||||
updatedAt = Clock.System.now(),
|
||||
itemDeleted = false,
|
||||
itemEdited = false,
|
||||
itemTimed = null,
|
||||
itemLive = true,
|
||||
editable = false
|
||||
),
|
||||
content = CIContent.SndMsgContent(MsgContent.MCText("")),
|
||||
quotedItem = null,
|
||||
file = null
|
||||
)
|
||||
|
||||
fun invalidJSON(json: String): ChatItem =
|
||||
ChatItem(
|
||||
chatDir = CIDirection.DirectSnd(),
|
||||
meta = CIMeta.invalidJSON(),
|
||||
content = CIContent.InvalidJSON(json),
|
||||
quotedItem = null,
|
||||
file = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1340,6 +1418,22 @@ data class CIMeta (
|
||||
itemLive = itemLive,
|
||||
editable = editable
|
||||
)
|
||||
|
||||
fun invalidJSON(): CIMeta =
|
||||
CIMeta(
|
||||
// itemId can not be the same for different items, otherwise ChatView will crash
|
||||
itemId = Random.nextLong(-1000000L, -1000L),
|
||||
itemTs = Clock.System.now(),
|
||||
itemText = "invalid JSON",
|
||||
itemStatus = CIStatus.SndNew(),
|
||||
createdAt = Clock.System.now(),
|
||||
updatedAt = Clock.System.now(),
|
||||
itemDeleted = false,
|
||||
itemEdited = false,
|
||||
itemTimed = null,
|
||||
itemLive = false,
|
||||
editable = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1404,6 +1498,7 @@ sealed class CIContent: ItemContent {
|
||||
@Serializable @SerialName("sndGroupFeature") class SndGroupFeature(val groupFeature: GroupFeature, val preference: GroupPreference, val param: Int? = null): CIContent() { override val msgContent: MsgContent? get() = null }
|
||||
@Serializable @SerialName("rcvChatFeatureRejected") class RcvChatFeatureRejected(val feature: ChatFeature): CIContent() { override val msgContent: MsgContent? get() = null }
|
||||
@Serializable @SerialName("rcvGroupFeatureRejected") class RcvGroupFeatureRejected(val groupFeature: GroupFeature): CIContent() { override val msgContent: MsgContent? get() = null }
|
||||
@Serializable @SerialName("invalidJSON") data class InvalidJSON(val json: String): CIContent() { override val msgContent: MsgContent? get() = null }
|
||||
|
||||
override val text: String get() = when (this) {
|
||||
is SndMsgContent -> msgContent.text
|
||||
@@ -1427,11 +1522,12 @@ sealed class CIContent: ItemContent {
|
||||
is SndGroupFeature -> featureText(groupFeature, preference.enable.text, param)
|
||||
is RcvChatFeatureRejected -> "${feature.text}: ${generalGetString(R.string.feature_received_prohibited)}"
|
||||
is RcvGroupFeatureRejected -> "${groupFeature.text}: ${generalGetString(R.string.feature_received_prohibited)}"
|
||||
is InvalidJSON -> "invalid data"
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun featureText(feature: Feature, enabled: String, param: Int?): String =
|
||||
if (feature.hasParam && param != null) {
|
||||
if (feature.hasParam) {
|
||||
"${feature.text}: ${TimedMessagesPreference.ttlText(param)}"
|
||||
} else {
|
||||
"${feature.text}: $enabled"
|
||||
|
||||
@@ -33,8 +33,9 @@ import kotlinx.coroutines.*
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.*
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.builtins.ListSerializer
|
||||
import kotlinx.serialization.builtins.nullable
|
||||
import kotlinx.serialization.json.*
|
||||
import java.util.Date
|
||||
|
||||
typealias ChatCtrl = Long
|
||||
@@ -89,7 +90,7 @@ class AppPreferences(val context: Context) {
|
||||
val webrtcIceServers = mkStrPreference(SHARED_PREFS_WEBRTC_ICE_SERVERS, null)
|
||||
val privacyProtectScreen = mkBoolPreference(SHARED_PREFS_PRIVACY_PROTECT_SCREEN, true)
|
||||
val privacyAcceptImages = mkBoolPreference(SHARED_PREFS_PRIVACY_ACCEPT_IMAGES, true)
|
||||
val privacyTransferImagesInline = mkBoolPreference(SHARED_PREFS_PRIVACY_TRANSFER_IMAGES_INLINE, false)
|
||||
val privacyTransferImagesInline = mkBoolPreference(SHARED_PREFS_PRIVACY_TRANSFER_IMAGES_INLINE, true)
|
||||
val privacyLinkPreviews = mkBoolPreference(SHARED_PREFS_PRIVACY_LINK_PREVIEWS, true)
|
||||
private val _simplexLinkMode = mkStrPreference(SHARED_PREFS_PRIVACY_SIMPLEX_LINK_MODE, SimplexLinkMode.default.name)
|
||||
val simplexLinkMode: SharedPreference<SimplexLinkMode> = SharedPreference(
|
||||
@@ -132,6 +133,8 @@ class AppPreferences(val context: Context) {
|
||||
val currentTheme = mkStrPreference(SHARED_PREFS_CURRENT_THEME, DefaultTheme.SYSTEM.name)
|
||||
val primaryColor = mkIntPreference(SHARED_PREFS_PRIMARY_COLOR, LightColorPalette.primary.toArgb())
|
||||
|
||||
val whatsNewVersion = mkStrPreference(SHARED_PREFS_WHATS_NEW_VERSION, null)
|
||||
|
||||
private fun mkIntPreference(prefName: String, default: Int) =
|
||||
SharedPreference(
|
||||
get = fun() = sharedPreferences.getInt(prefName, default),
|
||||
@@ -223,6 +226,7 @@ class AppPreferences(val context: Context) {
|
||||
private const val SHARED_PREFS_ENCRYPTION_STARTED_AT = "EncryptionStartedAt"
|
||||
private const val SHARED_PREFS_CURRENT_THEME = "CurrentTheme"
|
||||
private const val SHARED_PREFS_PRIMARY_COLOR = "PrimaryColor"
|
||||
private const val SHARED_PREFS_WHATS_NEW_VERSION = "WhatsNewVersion"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,10 +268,6 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
|
||||
chatModel.onboardingStage.value = OnboardingStage.OnboardingComplete
|
||||
chatModel.controller.appPrefs.chatLastStart.set(Clock.System.now())
|
||||
chatModel.chatRunning.value = true
|
||||
chatModel.appOpenUrl.value?.let {
|
||||
chatModel.appOpenUrl.value = null
|
||||
connectIfOpenedViaUri(it, chatModel)
|
||||
}
|
||||
startReceiver()
|
||||
Log.d(TAG, "startChat: started")
|
||||
} else {
|
||||
@@ -985,8 +985,8 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun allowFeatureToContact(contact: Contact, feature: ChatFeature) {
|
||||
val prefs = contact.mergedPreferences.toPreferences().setAllowed(feature)
|
||||
suspend fun allowFeatureToContact(contact: Contact, feature: ChatFeature, param: Int? = null) {
|
||||
val prefs = contact.mergedPreferences.toPreferences().setAllowed(feature, param = param)
|
||||
val toContact = apiSetContactPrefs(contact.contactId, prefs)
|
||||
if (toContact != null) {
|
||||
chatModel.updateContact(toContact)
|
||||
@@ -1952,7 +1952,8 @@ data class NetCfg(
|
||||
val tcpConnectTimeout: Long, // microseconds
|
||||
val tcpTimeout: Long, // microseconds
|
||||
val tcpKeepAlive: KeepAliveOpts?,
|
||||
val smpPingInterval: Long // microseconds
|
||||
val smpPingInterval: Long, // microseconds
|
||||
val logTLSErrors: Boolean = false
|
||||
) {
|
||||
val useSocksProxy: Boolean get() = socksProxy != null
|
||||
val enableKeepAlive: Boolean get() = tcpKeepAlive != null
|
||||
@@ -2045,9 +2046,9 @@ data class ChatPreferences(
|
||||
val fullDelete: SimpleChatPreference?,
|
||||
val voice: SimpleChatPreference?,
|
||||
) {
|
||||
fun setAllowed(feature: ChatFeature, allowed: FeatureAllowed = FeatureAllowed.YES): ChatPreferences =
|
||||
fun setAllowed(feature: ChatFeature, allowed: FeatureAllowed = FeatureAllowed.YES, param: Int? = null): ChatPreferences =
|
||||
when (feature) {
|
||||
ChatFeature.TimedMessages -> this.copy(timedMessages = TimedMessagesPreference(allow = allowed, ttl = this.timedMessages?.ttl))
|
||||
ChatFeature.TimedMessages -> this.copy(timedMessages = TimedMessagesPreference(allow = allowed, ttl = param ?: this.timedMessages?.ttl))
|
||||
ChatFeature.FullDelete -> this.copy(fullDelete = SimpleChatPreference(allow = allowed))
|
||||
ChatFeature.Voice -> this.copy(voice = SimpleChatPreference(allow = allowed))
|
||||
}
|
||||
@@ -2581,8 +2582,29 @@ class APIResponse(val resp: CR, val corr: String? = null) {
|
||||
try {
|
||||
Log.d(TAG, e.localizedMessage ?: "")
|
||||
val data = json.parseToJsonElement(str).jsonObject
|
||||
val resp = data["resp"]!!.jsonObject
|
||||
val type = resp["type"]?.jsonPrimitive?.content ?: "invalid"
|
||||
try {
|
||||
if (type == "apiChats") {
|
||||
val chats: List<Chat> = resp["chats"]!!.jsonArray.map {
|
||||
parseChatData(it)
|
||||
}
|
||||
return APIResponse(
|
||||
resp = CR.ApiChats(chats),
|
||||
corr = data["corr"]?.toString()
|
||||
)
|
||||
} else if (type == "apiChat") {
|
||||
val chat = parseChatData(resp["chat"]!!)
|
||||
return APIResponse(
|
||||
resp = CR.ApiChat(chat),
|
||||
corr = data["corr"]?.toString()
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error while parsing chat(s): " + e.stackTraceToString())
|
||||
}
|
||||
APIResponse(
|
||||
resp = CR.Response(data["resp"]!!.jsonObject["type"]?.toString() ?: "invalid", json.encodeToString(data)),
|
||||
resp = CR.Response(type, json.encodeToString(data)),
|
||||
corr = data["corr"]?.toString()
|
||||
)
|
||||
} catch(e: Exception) {
|
||||
@@ -2593,6 +2615,19 @@ class APIResponse(val resp: CR, val corr: String? = null) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseChatData(chat: JsonElement): Chat {
|
||||
val chatInfo: ChatInfo = decodeObject(ChatInfo.serializer(), chat.jsonObject["chatInfo"])
|
||||
?: ChatInfo.InvalidJSON(json.encodeToString(chat.jsonObject["chatInfo"]))
|
||||
val chatStats = decodeObject(Chat.ChatStats.serializer(), chat.jsonObject["chatStats"])!!
|
||||
val chatItems: List<ChatItem> = chat.jsonObject["chatItems"]!!.jsonArray.map {
|
||||
decodeObject(ChatItem.serializer(), it) ?: ChatItem.invalidJSON(json.encodeToString(it))
|
||||
}
|
||||
return Chat(chatInfo, chatItems, chatStats)
|
||||
}
|
||||
|
||||
private fun <T> decodeObject(deserializer: DeserializationStrategy<T>, obj: JsonElement?): T? =
|
||||
runCatching { json.decodeFromJsonElement(deserializer, obj!!) }.getOrNull()
|
||||
|
||||
// ChatResponse
|
||||
@Serializable
|
||||
sealed class CR {
|
||||
|
||||
@@ -138,7 +138,7 @@ fun TerminalLayout(
|
||||
SendMsgView(
|
||||
composeState = composeState,
|
||||
showVoiceRecordIcon = false,
|
||||
recState = mutableStateOf(RecordingState.NotStarted),
|
||||
recState = remember { mutableStateOf(RecordingState.NotStarted) },
|
||||
isDirectChat = false,
|
||||
liveMessageAlertShown = SharedPreference(get = { false }, set = {}),
|
||||
needToAllowVoiceToContact = false,
|
||||
@@ -147,8 +147,8 @@ fun TerminalLayout(
|
||||
sendMessage = sendCommand,
|
||||
sendLiveMessage = null,
|
||||
updateLiveMessage = null,
|
||||
::onMessageChange,
|
||||
textStyle
|
||||
onMessageChange = ::onMessageChange,
|
||||
textStyle = textStyle
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -226,9 +226,9 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: () -> Unit) {
|
||||
chatModel.callManager.acceptIncomingCall(invitation = invitation)
|
||||
}
|
||||
},
|
||||
acceptFeature = { contact, feature ->
|
||||
acceptFeature = { contact, feature, param ->
|
||||
withApi {
|
||||
chatModel.controller.allowFeatureToContact(contact, feature)
|
||||
chatModel.controller.allowFeatureToContact(contact, feature, param)
|
||||
}
|
||||
},
|
||||
addMembers = { groupInfo ->
|
||||
@@ -287,7 +287,7 @@ fun ChatLayout(
|
||||
joinGroup: (Long) -> Unit,
|
||||
startCall: (CallMediaType) -> Unit,
|
||||
acceptCall: (Contact) -> Unit,
|
||||
acceptFeature: (Contact, ChatFeature) -> Unit,
|
||||
acceptFeature: (Contact, ChatFeature, Int?) -> Unit,
|
||||
addMembers: (GroupInfo) -> Unit,
|
||||
markRead: (CC.ItemRange, unreadCountAfter: Int?) -> Unit,
|
||||
changeNtfsState: (Boolean, currentValue: MutableState<Boolean>) -> Unit,
|
||||
@@ -503,7 +503,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
|
||||
receiveFile: (Long) -> Unit,
|
||||
joinGroup: (Long) -> Unit,
|
||||
acceptCall: (Contact) -> Unit,
|
||||
acceptFeature: (Contact, ChatFeature) -> Unit,
|
||||
acceptFeature: (Contact, ChatFeature, Int?) -> Unit,
|
||||
markRead: (CC.ItemRange, unreadCountAfter: Int?) -> Unit,
|
||||
setFloatingButton: (@Composable () -> Unit) -> Unit,
|
||||
onComposed: () -> Unit,
|
||||
@@ -568,7 +568,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
|
||||
scope.launch {
|
||||
if (composeState.value.editing) {
|
||||
composeState.value = ComposeState(contextItem = ComposeContextItem.QuotedItem(cItem), useLinkPreviews = useLinkPreviews)
|
||||
} else {
|
||||
} else if (cItem.id != ChatItem.TEMP_LIVE_CHAT_ITEM_ID) {
|
||||
composeState.value = composeState.value.copy(contextItem = ComposeContextItem.QuotedItem(cItem))
|
||||
}
|
||||
}
|
||||
@@ -1026,7 +1026,7 @@ fun PreviewChatLayout() {
|
||||
joinGroup = {},
|
||||
startCall = {},
|
||||
acceptCall = { _ -> },
|
||||
acceptFeature = { _, _ -> },
|
||||
acceptFeature = { _, _, _ -> },
|
||||
addMembers = { _ -> },
|
||||
markRead = { _, _ -> },
|
||||
changeNtfsState = { _, _ -> },
|
||||
@@ -1085,7 +1085,7 @@ fun PreviewGroupChatLayout() {
|
||||
joinGroup = {},
|
||||
startCall = {},
|
||||
acceptCall = { _ -> },
|
||||
acceptFeature = { _, _ -> },
|
||||
acceptFeature = { _, _, _ -> },
|
||||
addMembers = { _ -> },
|
||||
markRead = { _, _ -> },
|
||||
changeNtfsState = { _, _ -> },
|
||||
|
||||
@@ -67,7 +67,8 @@ sealed class ComposeContextItem {
|
||||
data class LiveMessage(
|
||||
val chatItem: ChatItem,
|
||||
val typedMsg: String,
|
||||
val sentMsg: String
|
||||
val sentMsg: String,
|
||||
val sent: Boolean
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@@ -103,6 +104,9 @@ data class ComposeState(
|
||||
}
|
||||
hasContent && !inProgress
|
||||
}
|
||||
val endLiveDisabled: Boolean
|
||||
get() = liveMessage != null && message.isEmpty() && preview is ComposePreview.NoPreview && contextItem is ComposeContextItem.NoContextItem
|
||||
|
||||
val linkPreviewAllowed: Boolean
|
||||
get() =
|
||||
when (preview) {
|
||||
@@ -352,6 +356,7 @@ fun ComposeView(
|
||||
chosenContent.value = emptyList()
|
||||
chosenAudio.value = null
|
||||
chosenFile.value = null
|
||||
chatModel.removeLiveDummy()
|
||||
}
|
||||
|
||||
suspend fun send(cInfo: ChatInfo, mc: MsgContent, quoted: Long?, file: String? = null, live: Boolean = false): ChatItem? {
|
||||
@@ -421,15 +426,17 @@ fun ComposeView(
|
||||
return null
|
||||
}
|
||||
|
||||
val liveMessage = cs.liveMessage
|
||||
if (!live) {
|
||||
if (liveMessage != null) composeState.value = cs.copy(liveMessage = null)
|
||||
sending()
|
||||
}
|
||||
|
||||
if (cs.contextItem is ComposeContextItem.EditingItem) {
|
||||
val ei = cs.contextItem.chatItem
|
||||
sent = updateMessage(ei, cInfo, live)
|
||||
} else if (cs.liveMessage != null) {
|
||||
sent = updateMessage(cs.liveMessage.chatItem, cInfo, live)
|
||||
} else if (liveMessage != null && liveMessage.sent) {
|
||||
sent = updateMessage(liveMessage.chatItem, cInfo, live)
|
||||
} else {
|
||||
val msgs: ArrayList<MsgContent> = ArrayList()
|
||||
val files: ArrayList<String> = ArrayList()
|
||||
@@ -567,13 +574,16 @@ fun ComposeView(
|
||||
}
|
||||
|
||||
suspend fun sendLiveMessage() {
|
||||
val typedMsg = composeState.value.message
|
||||
val sentMsg = truncateToWords(typedMsg)
|
||||
if (composeState.value.liveMessage == null) {
|
||||
val ci = sendMessageAsync(sentMsg, live = true)
|
||||
val cs = composeState.value
|
||||
val typedMsg = cs.message
|
||||
if ((cs.sendEnabled() || cs.contextItem is ComposeContextItem.QuotedItem) && (cs.liveMessage == null || !cs.liveMessage?.sent)) {
|
||||
val ci = sendMessageAsync(typedMsg, live = true)
|
||||
if (ci != null) {
|
||||
composeState.value = composeState.value.copy(liveMessage = LiveMessage(ci, typedMsg = typedMsg, sentMsg = sentMsg))
|
||||
composeState.value = composeState.value.copy(liveMessage = LiveMessage(ci, typedMsg = typedMsg, sentMsg = typedMsg, sent = true))
|
||||
}
|
||||
} else if (cs.liveMessage == null) {
|
||||
val cItem = chatModel.addLiveDummy(chat.chatInfo)
|
||||
composeState.value = composeState.value.copy(liveMessage = LiveMessage(cItem, typedMsg = typedMsg, sentMsg = typedMsg, sent = false))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -590,7 +600,7 @@ fun ComposeView(
|
||||
if (sentMsg != null) {
|
||||
val ci = sendMessageAsync(sentMsg, live = true)
|
||||
if (ci != null) {
|
||||
composeState.value = composeState.value.copy(liveMessage = LiveMessage(ci, typedMsg = typedMsg, sentMsg = sentMsg))
|
||||
composeState.value = composeState.value.copy(liveMessage = LiveMessage(ci, typedMsg = typedMsg, sentMsg = sentMsg, sent = true))
|
||||
}
|
||||
} else if (liveMessage.typedMsg != typedMsg) {
|
||||
composeState.value = composeState.value.copy(liveMessage = liveMessage.copy(typedMsg = typedMsg))
|
||||
@@ -699,9 +709,13 @@ fun ComposeView(
|
||||
DisposableEffect(Unit) {
|
||||
val orientation = activity.resources.configuration.orientation
|
||||
onDispose {
|
||||
if (orientation == activity.resources.configuration.orientation && composeState.value.liveMessage != null) {
|
||||
sendMessage()
|
||||
resetLinkPreview()
|
||||
if (orientation == activity.resources.configuration.orientation) {
|
||||
val cs = composeState.value
|
||||
if (cs.liveMessage != null && (cs.message.isNotEmpty() || cs.liveMessage.sent)) {
|
||||
sendMessage()
|
||||
resetLinkPreview()
|
||||
}
|
||||
chatModel.removeLiveDummy()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -721,6 +735,10 @@ fun ComposeView(
|
||||
},
|
||||
sendLiveMessage = ::sendLiveMessage,
|
||||
updateLiveMessage = ::updateLiveMessage,
|
||||
cancelLiveMessage = {
|
||||
composeState.value = composeState.value.copy(liveMessage = null)
|
||||
chatModel.removeLiveDummy()
|
||||
},
|
||||
onMessageChange = ::onMessageChange,
|
||||
textStyle = textStyle
|
||||
)
|
||||
|
||||
@@ -6,8 +6,10 @@ import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.text.InputType
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import android.view.inputmethod.*
|
||||
import android.widget.EditText
|
||||
import androidx.compose.animation.core.*
|
||||
@@ -60,14 +62,15 @@ fun SendMsgView(
|
||||
allowedVoiceByPrefs: Boolean,
|
||||
allowVoiceToContact: () -> Unit,
|
||||
sendMessage: () -> Unit,
|
||||
sendLiveMessage: ( suspend () -> Unit)? = null,
|
||||
sendLiveMessage: (suspend () -> Unit)? = null,
|
||||
updateLiveMessage: (suspend () -> Unit)? = null,
|
||||
cancelLiveMessage: (() -> Unit)? = null,
|
||||
onMessageChange: (String) -> Unit,
|
||||
textStyle: MutableState<TextStyle>
|
||||
) {
|
||||
Box(Modifier.padding(vertical = 8.dp)) {
|
||||
val cs = composeState.value
|
||||
val showProgress = cs.inProgress && (cs.preview is ComposePreview.ImagePreview || cs.preview is ComposePreview.FilePreview)
|
||||
val showProgress = cs.inProgress && (cs.preview is ComposePreview.ImagePreview || cs.preview is ComposePreview.FilePreview)
|
||||
val showVoiceButton = cs.message.isEmpty() && showVoiceRecordIcon && !composeState.value.editing &&
|
||||
cs.liveMessage == null && (cs.preview is ComposePreview.NoPreview || recState.value is RecordingState.Started)
|
||||
NativeKeyboard(composeState, textStyle, onMessageChange)
|
||||
@@ -106,7 +109,10 @@ fun SendMsgView(
|
||||
else ->
|
||||
RecordVoiceView(recState, stopRecOnNextClick)
|
||||
}
|
||||
if (sendLiveMessage != null && updateLiveMessage != null && (cs.preview !is ComposePreview.VoicePreview || !stopRecOnNextClick.value)) {
|
||||
if (sendLiveMessage != null
|
||||
&& updateLiveMessage != null
|
||||
&& (cs.preview !is ComposePreview.VoicePreview || !stopRecOnNextClick.value)
|
||||
&& cs.contextItem is ComposeContextItem.NoContextItem) {
|
||||
Spacer(Modifier.width(10.dp))
|
||||
StartLiveMessageButton {
|
||||
if (composeState.value.preview is ComposePreview.NoPreview) {
|
||||
@@ -116,15 +122,24 @@ fun SendMsgView(
|
||||
}
|
||||
}
|
||||
}
|
||||
cs.liveMessage?.sent == false && cs.message.isEmpty() -> {
|
||||
CancelLiveMessageButton {
|
||||
cancelLiveMessage?.invoke()
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
val cs = composeState.value
|
||||
val icon = if (cs.editing || cs.liveMessage != null) Icons.Filled.Check else Icons.Outlined.ArrowUpward
|
||||
val color = if (cs.sendEnabled()) MaterialTheme.colors.primary else HighOrLowlight
|
||||
if (composeState.value.liveMessage == null &&
|
||||
val disabled = !cs.sendEnabled() ||
|
||||
(!allowedVoiceByPrefs && cs.preview is ComposePreview.VoicePreview) ||
|
||||
cs.endLiveDisabled
|
||||
if (cs.liveMessage == null &&
|
||||
cs.preview !is ComposePreview.VoicePreview && !cs.editing &&
|
||||
cs.contextItem is ComposeContextItem.NoContextItem &&
|
||||
sendLiveMessage != null && updateLiveMessage != null
|
||||
) {
|
||||
var showDropdown by rememberSaveable { mutableStateOf(false) }
|
||||
SendTextButton(icon, color, sendButtonSize, sendButtonAlpha, cs.sendEnabled(), sendMessage) { showDropdown = true }
|
||||
SendMsgButton(icon, sendButtonSize, sendButtonAlpha, !disabled, sendMessage) { showDropdown = true }
|
||||
|
||||
DropdownMenu(
|
||||
expanded = showDropdown,
|
||||
@@ -133,7 +148,7 @@ fun SendMsgView(
|
||||
) {
|
||||
ItemAction(
|
||||
generalGetString(R.string.send_live_message),
|
||||
Icons.Filled.MoreHoriz,
|
||||
Icons.Filled.Bolt,
|
||||
onClick = {
|
||||
startLiveMessage(scope, sendLiveMessage, updateLiveMessage, sendButtonSize, sendButtonAlpha, composeState, liveMessageAlertShown)
|
||||
showDropdown = false
|
||||
@@ -141,7 +156,7 @@ fun SendMsgView(
|
||||
)
|
||||
}
|
||||
} else {
|
||||
SendTextButton(icon, color, sendButtonSize, sendButtonAlpha, cs.sendEnabled(), sendMessage)
|
||||
SendMsgButton(icon, sendButtonSize, sendButtonAlpha, !disabled, sendMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,7 +178,6 @@ private fun NativeKeyboard(
|
||||
val paddingTop = with(LocalDensity.current) { 7.dp.roundToPx() }
|
||||
val paddingEnd = with(LocalDensity.current) { 45.dp.roundToPx() }
|
||||
val paddingBottom = with(LocalDensity.current) { 7.dp.roundToPx() }
|
||||
|
||||
var showKeyboard by remember { mutableStateOf(false) }
|
||||
LaunchedEffect(cs.contextItem) {
|
||||
if (cs.contextItem is ComposeContextItem.QuotedItem) {
|
||||
@@ -184,6 +198,7 @@ private fun NativeKeyboard(
|
||||
) {
|
||||
super.setOnReceiveContentListener(mimeTypes, listener)
|
||||
}
|
||||
|
||||
override fun onCreateInputConnection(editorInfo: EditorInfo): InputConnection {
|
||||
val connection = super.onCreateInputConnection(editorInfo)
|
||||
EditorInfoCompat.setContentMimeTypes(editorInfo, arrayOf("image/*"))
|
||||
@@ -312,7 +327,7 @@ private fun VoiceButtonWithoutPermission(onClick: () -> Unit) {
|
||||
stringResource(R.string.icon_descr_record_voice_message),
|
||||
tint = MaterialTheme.colors.primary,
|
||||
modifier = Modifier
|
||||
.size(36.dp)
|
||||
.size(34.dp)
|
||||
.padding(4.dp)
|
||||
)
|
||||
}
|
||||
@@ -323,7 +338,9 @@ private fun LockToCurrentOrientationUntilDispose() {
|
||||
val context = LocalContext.current
|
||||
DisposableEffect(Unit) {
|
||||
val activity = context as Activity
|
||||
activity.requestedOrientation = when (activity.display?.rotation) {
|
||||
val manager = context.getSystemService(Activity.WINDOW_SERVICE) as WindowManager
|
||||
val rotation = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) manager.defaultDisplay.rotation else activity.display?.rotation
|
||||
activity.requestedOrientation = when (rotation) {
|
||||
android.view.Surface.ROTATION_90 -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
||||
android.view.Surface.ROTATION_180 -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT
|
||||
android.view.Surface.ROTATION_270 -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
|
||||
@@ -334,7 +351,6 @@ private fun LockToCurrentOrientationUntilDispose() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
private fun StopRecordButton(onClick: () -> Unit) {
|
||||
IconButton(onClick, Modifier.size(36.dp)) {
|
||||
@@ -357,7 +373,7 @@ private fun RecordVoiceButton(interactionSource: MutableInteractionSource) {
|
||||
stringResource(R.string.icon_descr_record_voice_message),
|
||||
tint = MaterialTheme.colors.primary,
|
||||
modifier = Modifier
|
||||
.size(36.dp)
|
||||
.size(34.dp)
|
||||
.padding(4.dp)
|
||||
)
|
||||
}
|
||||
@@ -369,9 +385,24 @@ private fun ProgressIndicator() {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SendTextButton(
|
||||
private fun CancelLiveMessageButton(
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
IconButton(onClick, Modifier.size(36.dp)) {
|
||||
Icon(
|
||||
Icons.Filled.Close,
|
||||
stringResource(R.string.icon_descr_cancel_live_message),
|
||||
tint = MaterialTheme.colors.primary,
|
||||
modifier = Modifier
|
||||
.size(36.dp)
|
||||
.padding(4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SendMsgButton(
|
||||
icon: ImageVector,
|
||||
backgroundColor: Color,
|
||||
sizeDp: Animatable<Float, AnimationVector1D>,
|
||||
alpha: Animatable<Float, AnimationVector1D>,
|
||||
enabled: Boolean,
|
||||
@@ -400,7 +431,7 @@ private fun SendTextButton(
|
||||
.padding(4.dp)
|
||||
.alpha(alpha.value)
|
||||
.clip(CircleShape)
|
||||
.background(backgroundColor)
|
||||
.background(if (enabled) MaterialTheme.colors.primary else HighOrLowlight)
|
||||
.padding(3.dp)
|
||||
)
|
||||
}
|
||||
@@ -421,15 +452,12 @@ private fun StartLiveMessageButton(onClick: () -> Unit) {
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
Icons.Filled.MoreHoriz,
|
||||
Icons.Filled.Bolt,
|
||||
stringResource(R.string.icon_descr_send_message),
|
||||
tint = Color.White,
|
||||
tint = MaterialTheme.colors.primary,
|
||||
modifier = Modifier
|
||||
.size(36.dp)
|
||||
.padding(4.dp)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colors.primary)
|
||||
.padding(1.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -457,9 +485,10 @@ private fun startLiveMessage(
|
||||
sendButtonAlpha.snapTo(1f)
|
||||
}
|
||||
scope.launch {
|
||||
delay(3000)
|
||||
while (composeState.value.liveMessage != null) {
|
||||
delay(3000)
|
||||
update()
|
||||
delay(3000)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -521,7 +550,7 @@ fun PreviewSendMsgView() {
|
||||
SendMsgView(
|
||||
composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) },
|
||||
showVoiceRecordIcon = false,
|
||||
recState = mutableStateOf(RecordingState.NotStarted),
|
||||
recState = remember { mutableStateOf(RecordingState.NotStarted) },
|
||||
isDirectChat = true,
|
||||
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
|
||||
needToAllowVoiceToContact = false,
|
||||
@@ -549,7 +578,7 @@ fun PreviewSendMsgViewEditing() {
|
||||
SendMsgView(
|
||||
composeState = remember { mutableStateOf(composeStateEditing) },
|
||||
showVoiceRecordIcon = false,
|
||||
recState = mutableStateOf(RecordingState.NotStarted),
|
||||
recState = remember { mutableStateOf(RecordingState.NotStarted) },
|
||||
isDirectChat = true,
|
||||
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
|
||||
needToAllowVoiceToContact = false,
|
||||
@@ -577,7 +606,7 @@ fun PreviewSendMsgViewInProgress() {
|
||||
SendMsgView(
|
||||
composeState = remember { mutableStateOf(composeStateInProgress) },
|
||||
showVoiceRecordIcon = false,
|
||||
recState = mutableStateOf(RecordingState.NotStarted),
|
||||
recState = remember { mutableStateOf(RecordingState.NotStarted) },
|
||||
isDirectChat = true,
|
||||
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
|
||||
needToAllowVoiceToContact = false,
|
||||
|
||||
@@ -136,7 +136,13 @@ fun GroupProfileLayout(
|
||||
if (enabled) {
|
||||
Text(
|
||||
stringResource(R.string.save_group_profile),
|
||||
modifier = Modifier.clickable { saveProfile(GroupProfile(displayName.value, fullName.value, profileImage.value)) },
|
||||
modifier = Modifier.clickable {
|
||||
saveProfile(groupProfile.copy(
|
||||
displayName = displayName.value,
|
||||
fullName = fullName.value,
|
||||
image = profileImage.value
|
||||
))
|
||||
},
|
||||
color = MaterialTheme.colors.primary
|
||||
)
|
||||
} else {
|
||||
|
||||
@@ -1,26 +1,17 @@
|
||||
package chat.simplex.app.views.chat.item
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.*
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import chat.simplex.app.R
|
||||
import chat.simplex.app.TAG
|
||||
import chat.simplex.app.model.*
|
||||
import chat.simplex.app.ui.theme.HighOrLowlight
|
||||
import chat.simplex.app.ui.theme.SimpleButton
|
||||
import chat.simplex.app.views.helpers.generalGetString
|
||||
|
||||
@Composable
|
||||
@@ -29,7 +20,7 @@ fun CIFeaturePreferenceView(
|
||||
contact: Contact?,
|
||||
feature: ChatFeature,
|
||||
allowed: FeatureAllowed,
|
||||
acceptFeature: (Contact, ChatFeature) -> Unit
|
||||
acceptFeature: (Contact, ChatFeature, Int?) -> Unit
|
||||
) {
|
||||
Row(
|
||||
Modifier.padding(horizontal = 6.dp, vertical = 6.dp),
|
||||
@@ -39,17 +30,20 @@ fun CIFeaturePreferenceView(
|
||||
Icon(feature.icon, feature.text, Modifier.size(18.dp), tint = HighOrLowlight)
|
||||
if (contact != null && allowed != FeatureAllowed.NO && contact.allowsFeature(feature) && !contact.userAllowsFeature(feature)) {
|
||||
val acceptStyle = SpanStyle(color = MaterialTheme.colors.primary, fontSize = 12.sp)
|
||||
val setParam = feature == ChatFeature.TimedMessages && contact.mergedPreferences.timedMessages.userPreference.pref.ttl == null
|
||||
val acceptTextId = if (setParam) R.string.accept_feature_set_1_day else R.string.accept_feature
|
||||
val param = if (setParam) 86400 else null
|
||||
val annotatedText = buildAnnotatedString {
|
||||
withStyle(chatEventStyle) { append(chatItem.content.text + " ") }
|
||||
withAnnotation(tag = "Accept", annotation = "Accept") {
|
||||
withStyle(acceptStyle) { append(generalGetString(R.string.accept) + " ") }
|
||||
withStyle(acceptStyle) { append(generalGetString(acceptTextId) + " ") }
|
||||
}
|
||||
withStyle(chatEventStyle) { append(chatItem.timestampText) }
|
||||
}
|
||||
fun accept(offset: Int): Boolean = annotatedText.getStringAnnotations(tag = "Accept", start = offset, end = offset).isNotEmpty()
|
||||
ClickableText(
|
||||
annotatedText,
|
||||
onClick = { if (accept(it)) { acceptFeature(contact, feature) } },
|
||||
onClick = { if (accept(it)) { acceptFeature(contact, feature, param) } },
|
||||
shouldConsumeEvent = ::accept
|
||||
)
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package chat.simplex.app.views.chat.item
|
||||
|
||||
import SectionSpacer
|
||||
import SectionView
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Share
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.simplex.app.R
|
||||
import chat.simplex.app.ui.theme.DEFAULT_PADDING
|
||||
import chat.simplex.app.views.helpers.*
|
||||
import chat.simplex.app.views.usersettings.SettingsActionItem
|
||||
|
||||
@Composable
|
||||
fun CIInvalidJSONView(json: String) {
|
||||
Row(Modifier
|
||||
.clickable { ModalManager.shared.showModal(true) { InvalidJSONView(json) } }
|
||||
.padding(horizontal = 10.dp, vertical = 6.dp)
|
||||
) {
|
||||
Text(stringResource(R.string.invalid_data), color = Color.Red, fontStyle = FontStyle.Italic)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun InvalidJSONView(json: String) {
|
||||
Column {
|
||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||
SectionView {
|
||||
val context = LocalContext.current
|
||||
SettingsActionItem(Icons.Outlined.Share, generalGetString(R.string.share_verb), click = {
|
||||
shareText(context, json)
|
||||
})
|
||||
}
|
||||
Column(Modifier.padding(DEFAULT_PADDING).fillMaxWidth().verticalScroll(rememberScrollState())) {
|
||||
Text(json)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,7 @@ fun reserveSpaceForMeta(meta: CIMeta, chatTTL: Int?): String {
|
||||
if (meta.itemEdited) res += iconSpace
|
||||
if (meta.itemTimed != null) {
|
||||
res += iconSpace
|
||||
val ttl = meta.itemTimed?.ttl
|
||||
val ttl = meta.itemTimed.ttl
|
||||
if (ttl != chatTTL) {
|
||||
res += TimedMessagesPreference.shortTtlText(ttl)
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ fun ChatItemView(
|
||||
joinGroup: (Long) -> Unit,
|
||||
acceptCall: (Contact) -> Unit,
|
||||
scrollToItem: (Long) -> Unit,
|
||||
acceptFeature: (Contact, ChatFeature) -> Unit
|
||||
acceptFeature: (Contact, ChatFeature, Int?) -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val uriHandler = LocalUriHandler.current
|
||||
@@ -54,6 +54,7 @@ fun ChatItemView(
|
||||
val fullDeleteAllowed = remember(cInfo) { cInfo.featureEnabled(ChatFeature.FullDelete) }
|
||||
val saveFileLauncher = rememberSaveFileLauncher(cxt = context, ciFile = cItem.file)
|
||||
val onLinkLongClick = { _: String -> showMenu.value = true }
|
||||
val live = composeState.value.liveMessage != null
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
@@ -97,7 +98,7 @@ fun ChatItemView(
|
||||
onDismissRequest = { showMenu.value = false },
|
||||
Modifier.width(220.dp)
|
||||
) {
|
||||
if (!cItem.meta.itemDeleted) {
|
||||
if (!cItem.meta.itemDeleted && !live) {
|
||||
ItemAction(stringResource(R.string.reply_verb), Icons.Outlined.Reply, onClick = {
|
||||
if (composeState.value.editing) {
|
||||
composeState.value = ComposeState(contextItem = ComposeContextItem.QuotedItem(cItem), useLinkPreviews = useLinkPreviews)
|
||||
@@ -133,7 +134,7 @@ fun ChatItemView(
|
||||
})
|
||||
}
|
||||
}
|
||||
if (cItem.meta.editable && cItem.content.msgContent !is MsgContent.MCVoice) {
|
||||
if (cItem.meta.editable && cItem.content.msgContent !is MsgContent.MCVoice && !live) {
|
||||
ItemAction(stringResource(R.string.edit_verb), Icons.Filled.Edit, onClick = {
|
||||
composeState.value = ComposeState(editingItem = cItem, useLinkPreviews = useLinkPreviews)
|
||||
showMenu.value = false
|
||||
@@ -149,7 +150,9 @@ fun ChatItemView(
|
||||
}
|
||||
)
|
||||
}
|
||||
DeleteItemAction(cItem, showMenu, questionText = deleteMessageQuestionText(), deleteMessage)
|
||||
if (!(live && cItem.meta.isLive)) {
|
||||
DeleteItemAction(cItem, showMenu, questionText = deleteMessageQuestionText(), deleteMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,6 +238,7 @@ fun ChatItemView(
|
||||
is CIContent.SndGroupFeature -> CIChatFeatureView(cItem, c.groupFeature, c.preference.enable.iconColor)
|
||||
is CIContent.RcvChatFeatureRejected -> CIChatFeatureView(cItem, c.feature, Color.Red)
|
||||
is CIContent.RcvGroupFeatureRejected -> CIChatFeatureView(cItem, c.groupFeature, Color.Red)
|
||||
is CIContent.InvalidJSON -> CIInvalidJSONView(c.json)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -326,7 +330,7 @@ fun PreviewChatItemView() {
|
||||
joinGroup = {},
|
||||
acceptCall = { _ -> },
|
||||
scrollToItem = {},
|
||||
acceptFeature = { _, _ -> }
|
||||
acceptFeature = { _, _, _ -> }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -346,7 +350,7 @@ fun PreviewChatItemViewDeletedContent() {
|
||||
joinGroup = {},
|
||||
acceptCall = { _ -> },
|
||||
scrollToItem = {},
|
||||
acceptFeature = { _, _ -> }
|
||||
acceptFeature = { _, _, _ -> }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.usersettings.MarkdownHelpView
|
||||
import chat.simplex.app.views.usersettings.simplexTeamUri
|
||||
|
||||
val bold = SpanStyle(fontWeight = FontWeight.Bold)
|
||||
@@ -76,6 +77,15 @@ fun ChatHelpView(addContact: (() -> Unit)? = null) {
|
||||
Text(annotatedStringResource(R.string.desktop_scan_QR_code_from_app_via_scan_QR_code), lineHeight = 22.sp)
|
||||
Text(annotatedStringResource(R.string.mobile_tap_open_in_mobile_app_then_tap_connect_in_app), lineHeight = 22.sp)
|
||||
}
|
||||
|
||||
Column(
|
||||
Modifier.padding(vertical = 24.dp),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp)
|
||||
) {
|
||||
Text(stringResource(R.string.markdown_in_messages), style = MaterialTheme.typography.h2)
|
||||
MarkdownHelpView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,15 +11,20 @@ import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import chat.simplex.app.R
|
||||
import chat.simplex.app.model.*
|
||||
import chat.simplex.app.ui.theme.*
|
||||
import chat.simplex.app.views.chat.*
|
||||
import chat.simplex.app.views.chat.group.deleteGroupDialog
|
||||
import chat.simplex.app.views.chat.group.leaveGroupDialog
|
||||
import chat.simplex.app.views.chat.item.InvalidJSONView
|
||||
import chat.simplex.app.views.chat.item.ItemAction
|
||||
import chat.simplex.app.views.helpers.*
|
||||
import chat.simplex.app.views.newchat.ContactConnectionInfoView
|
||||
@@ -75,6 +80,18 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) {
|
||||
showMenu,
|
||||
stopped
|
||||
)
|
||||
is ChatInfo.InvalidJSON ->
|
||||
ChatListNavLinkLayout(
|
||||
chatLinkPreview = {
|
||||
InvalidDataView()
|
||||
},
|
||||
click = {
|
||||
ModalManager.shared.showModal(true) { InvalidJSONView(chat.chatInfo.json) }
|
||||
},
|
||||
dropdownMenuItems = null,
|
||||
showMenu,
|
||||
stopped
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,6 +337,29 @@ fun ContactConnectionMenuItems(chatInfo: ChatInfo.ContactConnection, chatModel:
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InvalidDataView() {
|
||||
Row {
|
||||
ProfileImage(72.dp, null, Icons.Filled.AccountCircle, HighOrLowlight)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 8.dp)
|
||||
.weight(1F)
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.invalid_data),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.h3,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color.Red
|
||||
)
|
||||
val height = with(LocalDensity.current) { 46.sp.toDp() }
|
||||
Spacer(Modifier.height(height))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun markChatRead(c: Chat, chatModel: ChatModel) {
|
||||
var chat = c
|
||||
withApi {
|
||||
|
||||
@@ -22,12 +22,16 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.simplex.app.R
|
||||
import chat.simplex.app.connectIfOpenedViaUri
|
||||
import chat.simplex.app.model.*
|
||||
import chat.simplex.app.ui.theme.*
|
||||
import chat.simplex.app.views.helpers.*
|
||||
import chat.simplex.app.views.newchat.NewChatSheet
|
||||
import chat.simplex.app.views.onboarding.WhatsNewView
|
||||
import chat.simplex.app.views.onboarding.shouldShowWhatsNew
|
||||
import chat.simplex.app.views.usersettings.SettingsView
|
||||
import chat.simplex.app.views.usersettings.simplexTeamUri
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -41,9 +45,22 @@ fun ChatListView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, stopped:
|
||||
if (animated) newChatSheetState.value = NewChatSheetState.HIDING
|
||||
else newChatSheetState.value = NewChatSheetState.GONE
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
if (shouldShowWhatsNew(chatModel)) {
|
||||
delay(1000L)
|
||||
ModalManager.shared.showCustomModal { close -> WhatsNewView(close = close) }
|
||||
}
|
||||
}
|
||||
LaunchedEffect(chatModel.clearOverlays.value) {
|
||||
if (chatModel.clearOverlays.value && newChatSheetState.value.isVisible()) hideNewChatSheet(false)
|
||||
}
|
||||
LaunchedEffect(chatModel.appOpenUrl.value) {
|
||||
val url = chatModel.appOpenUrl.value
|
||||
if (url != null) {
|
||||
chatModel.appOpenUrl.value = null
|
||||
connectIfOpenedViaUri(url, chatModel)
|
||||
}
|
||||
}
|
||||
var searchInList by rememberSaveable { mutableStateOf("") }
|
||||
val scaffoldState = rememberScaffoldState()
|
||||
Scaffold(
|
||||
|
||||
@@ -29,7 +29,7 @@ fun ShareListNavLinkView(chat: Chat, chatModel: ChatModel) {
|
||||
click = { groupChatAction(chat.chatInfo.groupInfo, chatModel) },
|
||||
stopped
|
||||
)
|
||||
is ChatInfo.ContactRequest, is ChatInfo.ContactConnection -> {}
|
||||
is ChatInfo.ContactRequest, is ChatInfo.ContactConnection, is ChatInfo.InvalidJSON -> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ fun CloseSheetBar(close: () -> Unit) {
|
||||
@Composable
|
||||
fun AppBarTitle(title: String, withPadding: Boolean = true) {
|
||||
val padding = if (withPadding)
|
||||
PaddingValues(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING )
|
||||
PaddingValues(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING)
|
||||
else
|
||||
PaddingValues(bottom = DEFAULT_PADDING)
|
||||
Text(
|
||||
|
||||
@@ -35,6 +35,9 @@ fun CreateProfile(chatModel: ChatModel) {
|
||||
.padding(20.dp)
|
||||
) {
|
||||
CreateProfilePanel(chatModel)
|
||||
LaunchedEffect(Unit) {
|
||||
setLastVersionDefault(chatModel)
|
||||
}
|
||||
if (savedKeyboardState != keyboardState) {
|
||||
LaunchedEffect(keyboardState) {
|
||||
scope.launch {
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
package chat.simplex.app.views.onboarding
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.simplex.app.R
|
||||
import chat.simplex.app.model.ChatModel
|
||||
import chat.simplex.app.ui.theme.*
|
||||
import chat.simplex.app.views.helpers.*
|
||||
|
||||
@Composable
|
||||
fun WhatsNewView(viaSettings: Boolean = false, close: () -> Unit) {
|
||||
val currentVersion = remember { mutableStateOf(versionDescriptions.lastIndex) }
|
||||
|
||||
@Composable
|
||||
fun featureDescription(icon: ImageVector, titleId: Int, descrId: Int, link: String?) {
|
||||
@Composable
|
||||
fun linkButton(link: String) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
Icon(
|
||||
Icons.Outlined.OpenInNew, stringResource(titleId), tint = MaterialTheme.colors.primary,
|
||||
modifier = Modifier
|
||||
.clickable { uriHandler.openUri(link) }
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Icon(icon, stringResource(titleId), tint = HighOrLowlight)
|
||||
Text(
|
||||
generalGetString(titleId),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.h3,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
if (link != null) {
|
||||
linkButton(link)
|
||||
}
|
||||
}
|
||||
Text(generalGetString(descrId))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun pagination() {
|
||||
Row(
|
||||
Modifier
|
||||
.padding(bottom = 16.dp)
|
||||
) {
|
||||
if (currentVersion.value > 0) {
|
||||
val prev = currentVersion.value - 1
|
||||
Surface(shape = RoundedCornerShape(20.dp)) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier
|
||||
.clickable { currentVersion.value = prev }
|
||||
.padding(8.dp)
|
||||
) {
|
||||
Icon(Icons.Outlined.ArrowBackIosNew, "previous", tint = MaterialTheme.colors.primary)
|
||||
Text(versionDescriptions[prev].version, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.fillMaxWidth().weight(1f))
|
||||
if (currentVersion.value < versionDescriptions.lastIndex) {
|
||||
val next = currentVersion.value + 1
|
||||
Surface(shape = RoundedCornerShape(20.dp)) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier
|
||||
.clickable { currentVersion.value = next }
|
||||
.padding(8.dp)
|
||||
) {
|
||||
Text(versionDescriptions[next].version, color = MaterialTheme.colors.primary)
|
||||
Icon(Icons.Outlined.ArrowForwardIos, "next", tint = MaterialTheme.colors.primary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val v = versionDescriptions[currentVersion.value]
|
||||
|
||||
ModalView(close = close) {
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = DEFAULT_PADDING),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Text(
|
||||
String.format(generalGetString(R.string.new_in_version), v.version),
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(DEFAULT_PADDING),
|
||||
textAlign = TextAlign.Center,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.h1,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = HighOrLowlight
|
||||
)
|
||||
|
||||
v.features.forEach { feature ->
|
||||
featureDescription(feature.icon, feature.titleId, feature.descrId, feature.link)
|
||||
}
|
||||
|
||||
if (!viaSettings) {
|
||||
Spacer(Modifier.fillMaxHeight().weight(1f))
|
||||
Box(
|
||||
Modifier.fillMaxWidth(), contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
generalGetString(R.string.ok),
|
||||
modifier = Modifier.clickable(onClick = close),
|
||||
style = MaterialTheme.typography.h3,
|
||||
color = MaterialTheme.colors.primary
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.fillMaxHeight().weight(1f))
|
||||
}
|
||||
|
||||
Spacer(Modifier.fillMaxHeight().weight(1f))
|
||||
|
||||
pagination()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class FeatureDescription(
|
||||
val icon: ImageVector,
|
||||
val titleId: Int,
|
||||
val descrId: Int,
|
||||
val link: String? = null
|
||||
)
|
||||
|
||||
private data class VersionDescription(
|
||||
val version: String,
|
||||
val features: List<FeatureDescription>
|
||||
)
|
||||
|
||||
private val versionDescriptions: List<VersionDescription> = listOf(
|
||||
VersionDescription(
|
||||
version = "v4.2",
|
||||
features = listOf(
|
||||
FeatureDescription(
|
||||
icon = Icons.Outlined.VerifiedUser,
|
||||
titleId = R.string.v4_2_security_assessment,
|
||||
descrId = R.string.v4_2_security_assessment_desc,
|
||||
link = "https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html"
|
||||
),
|
||||
FeatureDescription(
|
||||
icon = Icons.Outlined.Group,
|
||||
titleId = R.string.v4_2_group_links,
|
||||
descrId = R.string.v4_2_group_links_desc
|
||||
),
|
||||
FeatureDescription(
|
||||
icon = Icons.Outlined.Check,
|
||||
titleId = R.string.v4_2_auto_accept_contact_requests,
|
||||
descrId = R.string.v4_2_auto_accept_contact_requests_desc
|
||||
),
|
||||
)
|
||||
),
|
||||
VersionDescription(
|
||||
version = "v4.3",
|
||||
features = listOf(
|
||||
FeatureDescription(
|
||||
icon = Icons.Outlined.Mic,
|
||||
titleId = R.string.v4_3_voice_messages,
|
||||
descrId = R.string.v4_3_voice_messages_desc
|
||||
),
|
||||
FeatureDescription(
|
||||
icon = Icons.Outlined.DeleteForever,
|
||||
titleId = R.string.v4_3_irreversible_message_deletion,
|
||||
descrId = R.string.v4_3_irreversible_message_deletion_desc
|
||||
),
|
||||
FeatureDescription(
|
||||
icon = Icons.Outlined.WifiTethering,
|
||||
titleId = R.string.v4_3_improved_server_configuration,
|
||||
descrId = R.string.v4_3_improved_server_configuration_desc
|
||||
),
|
||||
FeatureDescription(
|
||||
icon = Icons.Outlined.VisibilityOff,
|
||||
titleId = R.string.v4_3_improved_privacy_and_security,
|
||||
descrId = R.string.v4_3_improved_privacy_and_security_desc
|
||||
),
|
||||
)
|
||||
),
|
||||
VersionDescription(
|
||||
version = "v4.4",
|
||||
features = listOf(
|
||||
FeatureDescription(
|
||||
icon = Icons.Outlined.Timer,
|
||||
titleId = R.string.v4_4_disappearing_messages,
|
||||
descrId = R.string.v4_4_disappearing_messages_desc
|
||||
),
|
||||
FeatureDescription(
|
||||
icon = Icons.Outlined.Pending,
|
||||
titleId = R.string.v4_4_live_messages,
|
||||
descrId = R.string.v4_4_live_messages_desc
|
||||
),
|
||||
FeatureDescription(
|
||||
icon = Icons.Outlined.VerifiedUser,
|
||||
titleId = R.string.v4_4_verify_connection_security,
|
||||
descrId = R.string.v4_4_verify_connection_security_desc
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
private val lastVersion = versionDescriptions.last().version
|
||||
|
||||
fun setLastVersionDefault(m: ChatModel) {
|
||||
m.controller.appPrefs.whatsNewVersion.set(lastVersion)
|
||||
}
|
||||
|
||||
fun shouldShowWhatsNew(m: ChatModel): Boolean {
|
||||
val v = m.controller.appPrefs.whatsNewVersion.get()
|
||||
setLastVersionDefault(m)
|
||||
return v != lastVersion
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview(
|
||||
uiMode = Configuration.UI_MODE_NIGHT_YES,
|
||||
showBackground = true,
|
||||
name = "Dark Mode"
|
||||
)
|
||||
@Composable
|
||||
fun PreviewWhatsNewView() {
|
||||
SimpleXTheme {
|
||||
WhatsNewView(
|
||||
viaSettings = true,
|
||||
close = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -17,18 +17,13 @@ import chat.simplex.app.model.Format
|
||||
import chat.simplex.app.model.FormatColor
|
||||
import chat.simplex.app.ui.theme.DEFAULT_PADDING
|
||||
import chat.simplex.app.ui.theme.SimpleXTheme
|
||||
import chat.simplex.app.views.helpers.AppBarTitle
|
||||
import chat.simplex.app.views.helpers.generalGetString
|
||||
|
||||
@Composable
|
||||
fun MarkdownHelpView() {
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(horizontal = DEFAULT_PADDING)
|
||||
) {
|
||||
AppBarTitle(stringResource(R.string.how_to_use_markdown), false)
|
||||
Text(stringResource(R.string.you_can_use_markdown_to_format_messages__prompt))
|
||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||
val bold = stringResource(R.string.bold)
|
||||
|
||||
@@ -34,6 +34,7 @@ import chat.simplex.app.views.helpers.*
|
||||
import chat.simplex.app.views.newchat.CreateLinkTab
|
||||
import chat.simplex.app.views.newchat.CreateLinkView
|
||||
import chat.simplex.app.views.onboarding.SimpleXInfo
|
||||
import chat.simplex.app.views.onboarding.WhatsNewView
|
||||
|
||||
@Composable
|
||||
fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit) {
|
||||
@@ -138,9 +139,9 @@ fun SettingsLayout(
|
||||
SectionView(stringResource(R.string.settings_section_title_help)) {
|
||||
SettingsActionItem(Icons.Outlined.HelpOutline, stringResource(R.string.how_to_use_simplex_chat), showModal { HelpView(userDisplayName) }, disabled = stopped)
|
||||
SectionDivider()
|
||||
SettingsActionItem(Icons.Outlined.Info, stringResource(R.string.about_simplex_chat), showModal { SimpleXInfo(it, onboarding = false) })
|
||||
SettingsActionItem(Icons.Outlined.Add, stringResource(R.string.whats_new), showCustomModal { _, close -> WhatsNewView(viaSettings = true, close) }, disabled = stopped)
|
||||
SectionDivider()
|
||||
SettingsActionItem(Icons.Outlined.TextFormat, stringResource(R.string.markdown_in_messages), showModal { MarkdownHelpView() })
|
||||
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)
|
||||
SectionDivider()
|
||||
@@ -488,7 +489,7 @@ fun PreviewSettingsLayout() {
|
||||
setPerformLA = {},
|
||||
showModal = { {} },
|
||||
showSettingsModal = { {} },
|
||||
showCustomModal = { {}},
|
||||
showCustomModal = { {} },
|
||||
showTerminal = {},
|
||||
// showVideoChatPrototype = {}
|
||||
)
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
<string name="simplex_link_mode_browser_warning">Das Öffnen des Links über den Browser kann die Privatsphäre und Sicherheit der Verbindung reduzieren. SimpleX-Links, denen nicht vertraut wird, werden Rot sein.</string>
|
||||
<!-- SimpleXAPI.kt -->
|
||||
<string name="error_saving_smp_servers">Fehler beim Speichern der SMP-Server</string>
|
||||
<string name="ensure_smp_server_address_are_correct_format_and_unique">Stellen Sie sicher, dass die SMP-Server Adressen das richtige Format haben, zeilenweise angeordnet und nicht kopiert sind.</string>
|
||||
<string name="ensure_smp_server_address_are_correct_format_and_unique">Stellen Sie sicher, dass die SMP-Server Adressen das richtige Format haben, zeilenweise angeordnet und nicht doppelt vorhanden sind.</string>
|
||||
<string name="error_setting_network_config">Fehler bei der Aktualisierung der Netzwerk-Konfiguration.</string>
|
||||
<!-- API Error Responses - SimpleXAPI.kt -->
|
||||
<string name="connection_timeout">Verbindungszeitüberschreitung</string>
|
||||
@@ -230,8 +230,8 @@
|
||||
<string name="icon_descr_send_message">Nachricht senden</string>
|
||||
<string name="icon_descr_record_voice_message">Nehme Sprachnachricht auf</string>
|
||||
<string name="allow_voice_messages_question">Sprachnachrichten erlauben?</string>
|
||||
<string name="you_need_to_allow_to_send_voice">Sie müssen Ihrem Kontakt das Senden von Sprachnachrichten erlauben, damit Sie sie senden können.</string>
|
||||
<string name="voice_messages_prohibited">Sprachnachrichten unzulässig!</string>
|
||||
<string name="you_need_to_allow_to_send_voice">Sie müssen Ihrem Kontakt das Senden von Sprachnachrichten erlauben, damit Sie sie versenden können.</string>
|
||||
<string name="voice_messages_prohibited">Sprachnachrichten nicht erlaubt!</string>
|
||||
<string name="ask_your_contact_to_enable_voice">Bitten Sie Ihren Kontakt darum, das Senden von Sprachnachrichten zu aktivieren.</string>
|
||||
<string name="only_group_owners_can_enable_voice">Sprachnachrichten können nur von Gruppen-Eigentümern aktiviert werden.</string>
|
||||
<!-- General Actions / Responses -->
|
||||
@@ -361,7 +361,7 @@
|
||||
<string name="smp_servers_add">Füge Server hinzu…</string>
|
||||
<string name="smp_servers_test_server">Teste Server</string>
|
||||
<string name="smp_servers_test_servers">Teste alle Server</string>
|
||||
<string name="smp_servers_save">Sichere alle Server</string>
|
||||
<string name="smp_servers_save">Alle Server speichern</string>
|
||||
<string name="smp_servers_test_failed">Server Test ist fehlgeschlagen!</string>
|
||||
<string name="smp_servers_test_some_failed">Einige Server haben den Test nicht bestanden:</string>
|
||||
<string name="smp_servers_scan_qr">Scannen Sie den QR-Code des Servers</string>
|
||||
@@ -386,10 +386,10 @@
|
||||
<string name="how_to_use_your_servers">Wie Sie Ihre Server nutzen</string>
|
||||
<string name="saved_ICE_servers_will_be_removed">Gespeicherte WebRTC ICE-Server werden entfernt.</string>
|
||||
<string name="your_ICE_servers">Ihre ICE-Server</string>
|
||||
<string name="configure_ICE_servers">Konfigurieren Sie ICE-Server</string>
|
||||
<string name="configure_ICE_servers">ICE-Server konfigurieren</string>
|
||||
<string name="enter_one_ICE_server_per_line">ICE-Server (einer pro Zeile)</string>
|
||||
<string name="error_saving_ICE_servers">Fehler beim Speichern der ICE-Server</string>
|
||||
<string name="ensure_ICE_server_address_are_correct_format_and_unique">Stellen Sie sicher, dass die WebRTC ICE-Server Adressen das richtige Format haben, zeilenweise angeordnet und nicht kopiert sind.</string>
|
||||
<string name="ensure_ICE_server_address_are_correct_format_and_unique">Stellen Sie sicher, dass die WebRTC ICE-Server Adressen das richtige Format haben, zeilenweise angeordnet und nicht doppelt vorhanden sind.</string>
|
||||
<string name="save_servers_button">Speichern</string>
|
||||
<string name="network_and_servers">Netzwerk & Server</string>
|
||||
<string name="network_settings">Erweiterte Netzwerkeinstellungen</string>
|
||||
@@ -882,28 +882,111 @@
|
||||
<string name="allow_your_contacts_irreversibly_delete">Erlauben Sie Ihren Kontakten gesendete Nachrichten unwiederbringlich zu löschen.</string>
|
||||
<string name="allow_irreversible_message_deletion_only_if">Erlauben Sie das unwiederbringliche Löschen von Nachrichten nur dann, wenn es Ihnen Ihr Kontakt ebenfalls erlaubt.</string>
|
||||
<string name="contacts_can_mark_messages_for_deletion">Ihre Kontakte können Nachrichten zum Löschen markieren. Sie können diese Nachrichten trotzdem anschauen.</string>
|
||||
<string name="allow_your_contacts_to_send_voice_messages">Erlauben Sie Ihre Kontakten Sprachnachrichten zu senden.</string>
|
||||
<string name="allow_your_contacts_to_send_voice_messages">Erlauben Sie Ihre Kontakten Sprachnachrichten zu versenden.</string>
|
||||
<string name="allow_voice_messages_only_if">Erlauben Sie Sprachnachrichten nur dann, wenn Ihr Kontakt diese ebenfalls erlaubt.</string>
|
||||
<string name="prohibit_sending_voice_messages">Das Senden von Sprachnachrichten verbieten.</string>
|
||||
<string name="prohibit_sending_voice_messages">Das Senden von Sprachnachrichten nicht erlauben.</string>
|
||||
<string name="both_you_and_your_contacts_can_delete">Sowohl Ihr Kontakt, als auch Sie können Nachrichten unwiederbringlich löschen.</string>
|
||||
<string name="only_you_can_delete_messages">Nur Sie können Nachrichten unwiederbringlich löschen (Ihr Kontakt kann sie zum Löschen markieren).</string>
|
||||
<string name="only_your_contact_can_delete">Nur Ihr Kontakt kann Nachrichten unwiederbringlich löschen (Sie können sie zum Löschen markieren).</string>
|
||||
<string name="message_deletion_prohibited">In dieser Gruppe ist das unwiederbringliche Löschen von Nachrichten nicht erlaubt.</string>
|
||||
<string name="both_you_and_your_contact_can_send_voice">Sowohl Ihr Kontakt, als auch Sie können Sprachnachrichten senden.</string>
|
||||
<string name="only_you_can_send_voice">Nur Sie können Sprachnachrichten senden.</string>
|
||||
<string name="only_your_contact_can_send_voice">Nur Ihr Kontakt kann Sprachnachrichten senden.</string>
|
||||
<string name="voice_prohibited_in_this_chat">In diesem Chat sind Sprachnachrichten untersagt.</string>
|
||||
<string name="allow_direct_messages">Das Senden von Direktnachrichten an Mitglieder erlauben.</string>
|
||||
<string name="prohibit_direct_messages">Das Senden von Direktnachrichten an Mitglieder verbieten.</string>
|
||||
<string name="allow_to_delete_messages">Unwiederbringliches Löschen von gesendeten Nachrichten erlauben.</string>
|
||||
<string name="prohibit_message_deletion">Unwiederbringliches Löschen von Nachrichten verbieten.</string>
|
||||
<string name="allow_to_send_voice">Senden von Sprachnachrichten erlauben.</string>
|
||||
<string name="prohibit_sending_voice">Senden von Sprachnachrichten untersagen.</string>
|
||||
<string name="both_you_and_your_contact_can_send_voice">Sowohl Ihr Kontakt, als auch Sie können Sprachnachrichten versenden.</string>
|
||||
<string name="only_you_can_send_voice">Nur Sie können Sprachnachrichten versenden.</string>
|
||||
<string name="only_your_contact_can_send_voice">Nur Ihr Kontakt kann Sprachnachrichten versenden.</string>
|
||||
<string name="voice_prohibited_in_this_chat">In diesem Chat sind Sprachnachrichten nicht erlaubt.</string>
|
||||
<string name="allow_direct_messages">Das Senden von Direktnachrichten an Gruppenmitglieder erlauben.</string>
|
||||
<string name="prohibit_direct_messages">Das Senden von Direktnachrichten an Gruppenmitglieder nicht erlauben.</string>
|
||||
<string name="allow_to_delete_messages">Unwiederbringliches löschen von gesendeten Nachrichten erlauben.</string>
|
||||
<string name="prohibit_message_deletion">Unwiederbringliches löschen von Nachrichten nicht erlauben.</string>
|
||||
<string name="allow_to_send_voice">Das Senden von Sprachnachrichten erlauben.</string>
|
||||
<string name="prohibit_sending_voice">Das Senden von Sprachnachrichten nicht erlauben.</string>
|
||||
<string name="group_members_can_send_dms">Gruppenmitglieder können Direktnachrichten versenden.</string>
|
||||
<string name="direct_messages_are_prohibited_in_chat">In dieser Gruppe sind Direktnachrichten zwischen Mitgliedern nicht möglich.</string>
|
||||
<string name="direct_messages_are_prohibited_in_chat">In dieser Gruppe sind Direktnachrichten zwischen Mitgliedern nicht erlaubt.</string>
|
||||
<string name="group_members_can_delete">Gruppenmitglieder können gesendete Nachrichten unwiederbringlich löschen.</string>
|
||||
<string name="message_deletion_prohibited_in_chat">In dieser Gruppe ist das unwiederbringliche Löschen von Nachrichten verboten.</string>
|
||||
<string name="group_members_can_send_voice">Gruppenmitglieder können Sprachnachrichten senden.</string>
|
||||
<string name="voice_messages_are_prohibited">In dieser Gruppe sind Sprachnachrichten untersagt.</string>
|
||||
<string name="message_deletion_prohibited_in_chat">In dieser Gruppe ist das unwiederbringliche Löschen von Nachrichten nicht erlaubt.</string>
|
||||
<string name="group_members_can_send_voice">Gruppenmitglieder können Sprachnachrichten versenden.</string>
|
||||
<string name="voice_messages_are_prohibited">In dieser Gruppe sind Sprachnachrichten nicht erlaubt.</string>
|
||||
<string name="live">LIVE</string>
|
||||
<string name="view_security_code">Schauen Sie sich den Sicherheitscode an</string>
|
||||
<string name="onboarding_notifications_mode_service">Sofort</string>
|
||||
<string name="onboarding_notifications_mode_periodic_desc"><b>Gute Option für die Batterieausdauer</b>. Der Hintergrundservice überprüft alle 10 Minuten nach neuen Nachrichten. Sie können eventuell Anrufe und dringende Nachrichten verpassen.</string>
|
||||
<string name="onboarding_notifications_mode_off_desc"><b>Beste Option für die Batterieausdauer</b>. Sie empfangen Benachrichtigungen nur solange die App abläuft. Der Hintergrundservice wird nicht genutzt!</string>
|
||||
<string name="send_verb">Senden</string>
|
||||
<string name="is_verified">%s wurde überprüft</string>
|
||||
<string name="clear_verification">Überprüfung zurücknehmen</string>
|
||||
<string name="onboarding_notifications_mode_off">Solange die App abläuft</string>
|
||||
<string name="onboarding_notifications_mode_subtitle">Kann später über die Einstellungen geändert werden.</string>
|
||||
<string name="delete_after">Löschen nach</string>
|
||||
<string name="ttl_hour">%d Stunde</string>
|
||||
<string name="ttl_hours">%d Stunden</string>
|
||||
<string name="ttl_m">%dm</string>
|
||||
<string name="ttl_min">%d min</string>
|
||||
<string name="ttl_month">%d Monat</string>
|
||||
<string name="ttl_months">%d Monate</string>
|
||||
<string name="ttl_mth">%dmth</string>
|
||||
<string name="ttl_s">%ds</string>
|
||||
<string name="ttl_sec">%d s</string>
|
||||
<string name="ttl_d">%dd</string>
|
||||
<string name="ttl_day">%d Tag</string>
|
||||
<string name="ttl_days">%d Tage</string>
|
||||
<string name="ttl_w">%dw</string>
|
||||
<string name="ttl_week">%d Woche</string>
|
||||
<string name="ttl_weeks">%d Wochen</string>
|
||||
<string name="timed_messages">Verschwindende Nachrichten</string>
|
||||
<string name="incorrect_code">Falscher Sicherheitscode!</string>
|
||||
<string name="scan_code">Code scannen</string>
|
||||
<string name="mark_code_verified">Als überprüft markieren</string>
|
||||
<string name="scan_code_from_contacts_app">Scannen Sie den Sicherheitscode von der App Ihres Kontakts.</string>
|
||||
<string name="security_code">Sicherheitscode</string>
|
||||
<string name="onboarding_notifications_mode_periodic">Periodisch</string>
|
||||
<string name="allow_to_send_disappearing">Erlauben Sie das Senden von verschwindenden Nachrichten.</string>
|
||||
<string name="disappearing_prohibited_in_this_chat">In diesem Chat sind verschwindende Nachrichten nicht erlaubt.</string>
|
||||
<string name="only_you_can_send_disappearing">Nur Sie können verschwindende Nachrichten senden.</string>
|
||||
<string name="only_your_contact_can_send_disappearing">Nur Ihr Kontakt kann verschwindende Nachrichten senden.</string>
|
||||
<string name="failed_to_parse_chat_title">Fehler beim Laden des Chats</string>
|
||||
<string name="failed_to_parse_chats_title">Fehler beim Laden der Chats</string>
|
||||
<string name="contact_developers">Bitte aktualisieren Sie die App und nehmen Sie Kontakt mit den Entwicklern auf.</string>
|
||||
<string name="onboarding_notifications_mode_service_desc"><b>Benötigt mehr Leistung Ihrer Batterie</b>! Der Hintergrundservice läuft die ganze Zeit ab. Benachrichtigungen werden Ihnen sofort angezeigt, nachdem Sie neue Nachrichten erhalten haben.</string>
|
||||
<string name="create_group_link">Gruppenlink erstellen</string>
|
||||
<string name="allow_your_contacts_to_send_disappearing_messages">Erlauben Sie Ihren Kontakten das Senden von verschwindenden Nachrichten.</string>
|
||||
<string name="prohibit_sending_disappearing_messages">Das Senden von verschwindenden Nachrichten verbieten.</string>
|
||||
<string name="disappearing_messages_are_prohibited">In dieser Gruppe sind verschwindende Nachrichten nicht erlaubt.</string>
|
||||
<string name="group_members_can_send_disappearing">Gruppenmitglieder können verschwindende Nachrichten senden.</string>
|
||||
<string name="v4_3_improved_server_configuration_desc">Fügen Sie Server durch Scannen der QR Codes hinzu.</string>
|
||||
<string name="v4_4_disappearing_messages">Verschwindende Nachrichten</string>
|
||||
<string name="accept_feature">Übernehmen</string>
|
||||
<string name="accept_feature_set_1_day">Einen Tag festlegen</string>
|
||||
<string name="invalid_chat">Ungültiger Chat</string>
|
||||
<string name="live_message">Live Nachricht!</string>
|
||||
<string name="send_live_message_desc">Eine Live Nachricht senden - der/die Empfänger sieht/sehen Nachrichtenaktualisierungen, während Sie sie eingeben.</string>
|
||||
<string name="send_live_message">Live Nachricht senden</string>
|
||||
<string name="verify_security_code">Sicherheitscode überprüfen</string>
|
||||
<string name="is_not_verified">%s wurde nicht überprüft</string>
|
||||
<string name="to_verify_compare">Um die Ende-zu-Ende-Verschlüsselung mit Ihrem Kontakt zu überprüfen, müssen Sie den Sicherheitscode in Ihren Apps vergleichen oder scannen.</string>
|
||||
<string name="onboarding_notifications_mode_title">Private Benachrichtigungen</string>
|
||||
<string name="use_chat">Chat verwenden</string>
|
||||
<string name="both_you_and_your_contact_can_send_disappearing">Ihr Kontakt und Sie können beide verschwindende Nachrichten senden.</string>
|
||||
<string name="ttl_h">%dh</string>
|
||||
<string name="v4_2_group_links">Gruppen-Links</string>
|
||||
<string name="new_in_version">Neu in %s</string>
|
||||
<string name="prohibit_sending_disappearing">Das Senden von verschwindenden Nachrichten verbieten.</string>
|
||||
<string name="v4_2_security_assessment">Sicherheits-Gutachten</string>
|
||||
<string name="v4_2_security_assessment_desc">Die Sicherheit von SimpleX Chat wurde von Trail of Bits überprüft.</string>
|
||||
<string name="whats_new">Was ist neu</string>
|
||||
<string name="v4_2_group_links_desc">Administratoren können Links für den Beitritt zu Gruppen erzeugen.</string>
|
||||
<string name="v4_2_auto_accept_contact_requests">Kontaktanfragen automatisch annehmen</string>
|
||||
<string name="v4_4_verify_connection_security_desc">Vergleichen Sie die Sicherheitscodes mit Ihren Kontakten.</string>
|
||||
<string name="v4_3_improved_privacy_and_security_desc">App-Bildschirm in aktuellen Anwendungen verbergen.</string>
|
||||
<string name="v4_3_improved_privacy_and_security">Verbesserte Privatsphäre und Sicherheit</string>
|
||||
<string name="v4_3_improved_server_configuration">Verbesserte Serverkonfiguration</string>
|
||||
<string name="v4_3_irreversible_message_deletion">Unwiederbringliches löschen einer Nachricht</string>
|
||||
<string name="v4_4_live_messages">Live Nachrichten</string>
|
||||
<string name="v4_3_voice_messages_desc">Max. 40 Sekunden, sofort erhalten.</string>
|
||||
<string name="v4_4_live_messages_desc">Die Empfänger sehen Nachrichtenaktualisierungen, während Sie sie eingeben.</string>
|
||||
<string name="v4_4_disappearing_messages_desc">Gesendete Nachrichten werden nach der eingestellten Zeit gelöscht.</string>
|
||||
<string name="v4_3_voice_messages">Sprachnachrichten</string>
|
||||
<string name="allow_disappearing_messages_only_if">Erlauben Sie verschwindende Nachrichten nur dann, wenn es Ihnen Ihr Kontakt ebenfalls erlaubt.</string>
|
||||
<string name="invalid_data">Ungültige Daten</string>
|
||||
<string name="v4_4_verify_connection_security">Sicherheit der Verbindung überprüfen</string>
|
||||
<string name="v4_2_auto_accept_contact_requests_desc">Mit optionaler Begrüßungsmeldung.</string>
|
||||
<string name="v4_3_irreversible_message_deletion_desc">Ihre Kontakte können die unwiederbringliche Löschung von Nachrichten erlauben.</string>
|
||||
</resources>
|
||||
@@ -21,7 +21,7 @@
|
||||
<string name="error_joining_group">Erreur lors de la liaison avec le groupe</string>
|
||||
<string name="sender_cancelled_file_transfer">L\'expéditeur a annulé le transfert de fichiers.</string>
|
||||
<string name="deleted_description">supprimé</string>
|
||||
<string name="marked_deleted_description">marquer comme supprimé</string>
|
||||
<string name="marked_deleted_description">supprimé</string>
|
||||
<string name="unknown_message_format">format de message inconnu</string>
|
||||
<string name="display_name_connecting">connexion…</string>
|
||||
<string name="description_you_shared_one_time_link_incognito">vous avez partagé un lien unique en incognito</string>
|
||||
@@ -296,7 +296,7 @@
|
||||
<string name="toast_permission_denied">Autorisation refusée !</string>
|
||||
<string name="use_camera_button">Utiliser l\'Appareil photo</string>
|
||||
<string name="thank_you_for_installing_simplex">Merci d\'avoir installé <xliff:g id="appNameFull">SimpleX Chat</xliff:g> !</string>
|
||||
<string name="you_can_connect_to_simplex_chat_founder">Vous pouvez <font color="#0088ff">vous connecter aux développeurs de <xliff:g id="appNameFull">SimpleX Chat</xliff:g> pour leur poser toutes vos questions et pour recevoir des informations sur les mises à jour</font>.</string>
|
||||
<string name="you_can_connect_to_simplex_chat_founder">Vous pouvez <font color="#0088ff">vous connecter aux développeurs de <xliff:g id="appNameFull">SimpleX Chat</xliff:g> pour leur poser des questions et recevoir des réponses :</font>.</string>
|
||||
<string name="above_then_preposition_continuation">ci-dessus, puis :</string>
|
||||
<string name="add_new_contact_to_create_one_time_QR_code"><b>Ajouter un nouveau contact</b> : afin de créer un code QR à usage unique pour votre contact.</string>
|
||||
<string name="if_you_choose_to_reject_the_sender_will_not_be_notified">Si vous choisissez de la rejeter, l\'expéditeur·rice NE sera PAS notifié·e.</string>
|
||||
@@ -328,14 +328,14 @@
|
||||
<string name="network_use_onion_hosts">Utiliser les hôtes .onions</string>
|
||||
<string name="network_use_onion_hosts_prefer_desc_in_alert">Les hôtes .onion seront utilisés lorsqu\'ils sont disponibles.</string>
|
||||
<string name="network_use_onion_hosts_required_desc_in_alert">Les hôtes .onion seront nécessaires pour la connexion.</string>
|
||||
<string name="you_control_servers_to_receive_your_contacts_to_send">Vous contrôlez par quel·s serveur·s vous pouvez <b>transmettre</b> ainsi que par quel·s serveur·s vous pouvez <b>recevoir</b> des messages de vos contacts.</string>
|
||||
<string name="you_control_servers_to_receive_your_contacts_to_send">Vous contrôlez par quel·s serveur·s vous pouvez <b>transmettre</b> ainsi que par quel·s serveur·s vous pouvez <b>recevoir</b> les messages de vos contacts.</string>
|
||||
<string name="your_settings">Vos paramètres</string>
|
||||
<string name="chat_lock">SimpleX Lock</string>
|
||||
<string name="chat_console">Console du chat</string>
|
||||
<string name="smp_servers">Serveurs SMP</string>
|
||||
<string name="smp_servers_test_servers">Tester les serveurs</string>
|
||||
<string name="smp_servers_save">Sauvegarder les serveurs</string>
|
||||
<string name="smp_servers_scan_qr">Scanner le code QR du serveur</string>
|
||||
<string name="smp_servers_scan_qr">Scanner un code QR de serveur</string>
|
||||
<string name="smp_servers_use_server">Utiliser ce serveur</string>
|
||||
<string name="smp_servers_use_server_for_new_conn">Utiliser pour les nouvelles connexions</string>
|
||||
<string name="smp_servers_add_to_another_device">Ajouter à un autre appareil</string>
|
||||
@@ -359,7 +359,7 @@
|
||||
<string name="network_use_onion_hosts_prefer_desc">Les hôtes .onion seront utilisés lorsqu\'ils sont disponibles.</string>
|
||||
<string name="appearance_settings">Apparence</string>
|
||||
<string name="create_address">Créer une adresse</string>
|
||||
<string name="you_can_share_your_address_anybody_will_be_able_to_connect">Vous pouvez partager votre adresse sous forme de lien ou de code QR - n\'importe qui pourra se connecter à vous. Vous ne perdrez pas vos contacts si vous les supprimez par la suite.</string>
|
||||
<string name="you_can_share_your_address_anybody_will_be_able_to_connect">Vous pouvez partager votre adresse sous forme de lien ou de code QR - n\'importe qui pourra se connecter à vous. Vous ne perdrez pas vos contacts si vous la supprimez par la suite.</string>
|
||||
<string name="your_chat_profile">Votre profil de chat</string>
|
||||
<string name="edit_image">Modifier l\'image</string>
|
||||
<string name="save_and_notify_contacts">Sauvegarder et notifier les contacts</string>
|
||||
@@ -380,8 +380,8 @@
|
||||
<string name="callstate_received_answer">réponse reçu…</string>
|
||||
<string name="callstate_received_confirmation">confimation reçu…</string>
|
||||
<string name="callstate_connecting">connexion…</string>
|
||||
<string name="opensource_protocol_and_code_anybody_can_run_servers">Protocole et code open-source – tout le monde peut faire fonctionner les serveurs.</string>
|
||||
<string name="to_protect_privacy_simplex_has_ids_for_queues">Pour protéger la vie privée, au lieu d\'ID d\'utilisateur utilisés par toutes les autres plateformes, <xliff:g id="appName">SimpleX</xliff:g> possède des identifiants pour les files d\'attente de messages, distincts pour chacun de vos contacts.</string>
|
||||
<string name="opensource_protocol_and_code_anybody_can_run_servers">Protocole et code open-source – n\'importe qui peut heberger un serveur.</string>
|
||||
<string name="to_protect_privacy_simplex_has_ids_for_queues">Pour protéger votre vie privée, au lieu d\'IDs utilisés par toutes les autres plateformes, <xliff:g id="appName">SimpleX</xliff:g> possède des IDs pour les queues de messages, distinctes pour chacun de vos contacts.</string>
|
||||
<string name="read_more_in_github">Plus d\'informations sur notre GitHub.</string>
|
||||
<string name="paste_the_link_you_received">Coller le lien reçu</string>
|
||||
<string name="use_chat">Utiliser le chat</string>
|
||||
@@ -467,7 +467,7 @@
|
||||
<string name="make_private_connection">Établir une connexion privée</string>
|
||||
<string name="how_it_works">Comment ça fonctionne</string>
|
||||
<string name="how_simplex_works">Comment <xliff:g id="appName">SimpleX</xliff:g> fonctionne</string>
|
||||
<string name="many_people_asked_how_can_it_deliver">Beaucoup se demande : <i>si <xliff:g id="appName">SimpleX</xliff:g> n\'a pas d\'identifiant d\'utilisateur, comment peut-il transmettre des messages \?</i></string>
|
||||
<string name="many_people_asked_how_can_it_deliver">Beaucoup se demandent : <i>si <xliff:g id="appName">SimpleX</xliff:g> n\'a pas d\'identifiant d\'utilisateur, comment peut-il transmettre des messages \?</i></string>
|
||||
<string name="only_client_devices_store_contacts_groups_e2e_encrypted_messages">Seuls les appareils clients stockent les profils des utilisateurs, les contacts, les groupes et les messages envoyés avec un <b>chiffrement de bout en bout à deux couches</b>.</string>
|
||||
<string name="read_more_in_github_with_link">Pour en savoir plus, consultez notre <font color="#0088ff">GitHub repository</font>.</string>
|
||||
<string name="onboarding_notifications_mode_periodic_desc"><b>Batterie peu utilisée</b>. Le service de fond vérifie les nouveaux messages toutes les 10 minutes. Vous risquez de manquer des appels et des messages urgents.</string>
|
||||
@@ -892,4 +892,30 @@
|
||||
<string name="prohibit_message_deletion">Interdire la suppression irréversible des messages.</string>
|
||||
<string name="group_members_can_send_dms">Les membres du groupe peuvent envoyer des messages directs.</string>
|
||||
<string name="direct_messages_are_prohibited_in_chat">Les messages directs entre membres sont interdits dans ce groupe.</string>
|
||||
<string name="v4_4_live_messages_desc">Les destinataires voient les mises à jour au fur et à mesure que vous les tapez.</string>
|
||||
<string name="v4_4_verify_connection_security">Vérifier la sécurité de la connexion</string>
|
||||
<string name="v4_4_verify_connection_security_desc">Comparez les codes de sécurité avec vos contacts.</string>
|
||||
<string name="new_in_version">Nouveautés de la %s</string>
|
||||
<string name="v4_2_security_assessment">Évaluation de sécurité</string>
|
||||
<string name="v4_2_group_links">Liens de groupe</string>
|
||||
<string name="v4_2_auto_accept_contact_requests_desc">Avec message de bienvenue facultatif.</string>
|
||||
<string name="v4_3_voice_messages">Messages vocaux</string>
|
||||
<string name="v4_3_voice_messages_desc">Max 40 secondes, réception immédiate.</string>
|
||||
<string name="v4_3_irreversible_message_deletion">Suppression irréversible des messages</string>
|
||||
<string name="v4_3_irreversible_message_deletion_desc">Vos contacts peuvent autoriser la suppression complète des messages.</string>
|
||||
<string name="v4_3_improved_privacy_and_security">Une meilleure sécurité et protection de la vie privée</string>
|
||||
<string name="v4_3_improved_privacy_and_security_desc">Masquer l\'écran de l\'app dans les apps récentes.</string>
|
||||
<string name="v4_4_disappearing_messages">Messages éphémères</string>
|
||||
<string name="v4_4_disappearing_messages_desc">Les messages envoyés seront supprimés après une durée déterminée.</string>
|
||||
<string name="v4_4_live_messages">Messages dynamiques</string>
|
||||
<string name="accept_feature">Accepter</string>
|
||||
<string name="v4_2_auto_accept_contact_requests">Demandes de contact auto-acceptées</string>
|
||||
<string name="whats_new">Quoi de neuf \?</string>
|
||||
<string name="v4_2_group_links_desc">Les admins peuvent créer les liens qui permettent de rejoindre les groupes.</string>
|
||||
<string name="accept_feature_set_1_day">Définir 1 jour</string>
|
||||
<string name="v4_2_security_assessment_desc">La sécurité de SimpleX Chat a été auditée par Trail of Bits.</string>
|
||||
<string name="v4_3_improved_server_configuration">Configuration de serveur améliorée</string>
|
||||
<string name="v4_3_improved_server_configuration_desc">Ajoutez des serveurs en scannant des codes QR.</string>
|
||||
<string name="invalid_data">données invalides</string>
|
||||
<string name="invalid_chat">chat invalide</string>
|
||||
</resources>
|
||||
@@ -964,4 +964,30 @@
|
||||
<string name="allow_disappearing_messages_only_if">Разрешить исчезающие сообщения, только если ваш контакт разрешает их вам.</string>
|
||||
<string name="prohibit_sending_disappearing">Запретить посылать исчезающие сообщения.</string>
|
||||
<string name="group_members_can_send_disappearing">Члены группы могут посылать исчезающие сообщения.</string>
|
||||
<string name="whats_new">Новые функции</string>
|
||||
<string name="new_in_version">Новое в %s</string>
|
||||
<string name="v4_2_security_assessment">Аудит безопасности</string>
|
||||
<string name="v4_2_security_assessment_desc">Безопасность SimpleX Chat была проверена Trail of Bits.</string>
|
||||
<string name="v4_3_voice_messages">Голосовые сообщения</string>
|
||||
<string name="v4_3_voice_messages_desc">Макс. 40 секунд, доставляются мгновенно.</string>
|
||||
<string name="v4_3_irreversible_message_deletion">Окончательное удаление сообщений</string>
|
||||
<string name="v4_3_irreversible_message_deletion_desc">Ваши контакты могут разрешить окончательное удаление сообщений.</string>
|
||||
<string name="v4_3_improved_server_configuration_desc">Добавить серверы через QR код.</string>
|
||||
<string name="v4_3_improved_privacy_and_security">Улучшенная безопасность</string>
|
||||
<string name="v4_3_improved_privacy_and_security_desc">Скрыть экран приложения.</string>
|
||||
<string name="v4_4_disappearing_messages">Исчезающие сообщения</string>
|
||||
<string name="v4_4_disappearing_messages_desc">Отправленные сообщения будут удалены через заданное время.</string>
|
||||
<string name="v4_3_improved_server_configuration">Улучшенная конфигурация серверов</string>
|
||||
<string name="v4_4_live_messages">\"Живые\" сообщения</string>
|
||||
<string name="v4_4_live_messages_desc">Получатели видят их в то время как вы их набираете.</string>
|
||||
<string name="v4_4_verify_connection_security">Проверить безопасность соединения</string>
|
||||
<string name="v4_4_verify_connection_security_desc">Сравните код безопасности с вашими контактами.</string>
|
||||
<string name="invalid_chat">ошибка чата</string>
|
||||
<string name="accept_feature">Принять</string>
|
||||
<string name="accept_feature_set_1_day">Установить 1 день</string>
|
||||
<string name="invalid_data">неверные данные</string>
|
||||
<string name="v4_2_group_links">Ссылки групп</string>
|
||||
<string name="v4_2_group_links_desc">Админы могут создать ссылки для вступления в группу.</string>
|
||||
<string name="v4_2_auto_accept_contact_requests">Автоматически принимать запросы контактов</string>
|
||||
<string name="v4_2_auto_accept_contact_requests_desc">С опциональным авто-ответом.</string>
|
||||
</resources>
|
||||
@@ -28,6 +28,8 @@
|
||||
<string name="unknown_message_format">unknown message format</string>
|
||||
<string name="invalid_message_format">invalid message format</string>
|
||||
<string name="live">LIVE</string>
|
||||
<string name="invalid_chat">invalid chat</string>
|
||||
<string name="invalid_data">invalid data</string>
|
||||
|
||||
<!-- PendingContactConnection - ChatModel.kt -->
|
||||
<string name="connection_local_display_name">connection <xliff:g id="connection ID" example="1">%1$d</xliff:g></string>
|
||||
@@ -269,6 +271,7 @@
|
||||
<string name="live_message">Live message!</string>
|
||||
<string name="send_live_message_desc">Send a live message - it will update for the recipient(s) as you type it</string>
|
||||
<string name="send_verb">Send</string>
|
||||
<string name="icon_descr_cancel_live_message">Cancel live message</string>
|
||||
|
||||
<!-- General Actions / Responses -->
|
||||
<string name="back">Back</string>
|
||||
@@ -1001,6 +1004,8 @@
|
||||
<string name="feature_enabled_for_contact">enabled for contact</string>
|
||||
<string name="feature_off">off</string>
|
||||
<string name="feature_received_prohibited">received, prohibited</string>
|
||||
<string name="accept_feature">Accept</string>
|
||||
<string name="accept_feature_set_1_day">Set 1 day</string>
|
||||
<string name="allow_your_contacts_to_send_disappearing_messages">Allow your contacts to send disappearing messages.</string>
|
||||
<string name="allow_disappearing_messages_only_if">Allow disappearing messages only if your contact allows them.</string>
|
||||
<string name="prohibit_sending_disappearing_messages">Prohibit sending disappearing messages.</string>
|
||||
@@ -1055,4 +1060,28 @@
|
||||
<string name="ttl_week">%d week</string>
|
||||
<string name="ttl_weeks">%d weeks</string>
|
||||
<string name="ttl_w">%dw</string>
|
||||
|
||||
<!-- WhatsNewView.kt -->
|
||||
<string name="whats_new">What\'s new</string>
|
||||
<string name="new_in_version">New in %s</string>
|
||||
<string name="v4_2_security_assessment">Security assessment</string>
|
||||
<string name="v4_2_security_assessment_desc">SimpleX Chat security was audited by Trail of Bits.</string>
|
||||
<string name="v4_2_group_links">Group links</string>
|
||||
<string name="v4_2_group_links_desc">Admins can create the links to join groups.</string>
|
||||
<string name="v4_2_auto_accept_contact_requests">Auto-accept contact requests</string>
|
||||
<string name="v4_2_auto_accept_contact_requests_desc">With optional welcome message.</string>
|
||||
<string name="v4_3_voice_messages">Voice messages</string>
|
||||
<string name="v4_3_voice_messages_desc">Max 40 seconds, received instantly.</string>
|
||||
<string name="v4_3_irreversible_message_deletion">Irreversible message deletion</string>
|
||||
<string name="v4_3_irreversible_message_deletion_desc">Your contacts can allow full message deletion.</string>
|
||||
<string name="v4_3_improved_server_configuration">Improved server configuration</string>
|
||||
<string name="v4_3_improved_server_configuration_desc">Add servers by scanning QR codes.</string>
|
||||
<string name="v4_3_improved_privacy_and_security">Improved privacy and security</string>
|
||||
<string name="v4_3_improved_privacy_and_security_desc">Hide app screen in the recent apps.</string>
|
||||
<string name="v4_4_disappearing_messages">Disappearing messages</string>
|
||||
<string name="v4_4_disappearing_messages_desc">Sent messages will be deleted after set time.</string>
|
||||
<string name="v4_4_live_messages">Live messages</string>
|
||||
<string name="v4_4_live_messages_desc">Recipients see updates as you type them.</string>
|
||||
<string name="v4_4_verify_connection_security">Verify connection security</string>
|
||||
<string name="v4_4_verify_connection_security_desc">Compare security codes with your contacts.</string>
|
||||
</resources>
|
||||
|
||||
@@ -17,8 +17,9 @@ struct ContentView: View {
|
||||
@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
|
||||
@AppStorage(DEFAULT_PRIVACY_PROTECT_SCREEN) private var protectScreen = true
|
||||
@AppStorage(DEFAULT_PRIVACY_PROTECT_SCREEN) private var protectScreen = false
|
||||
@AppStorage(DEFAULT_NOTIFICATION_ALERT_SHOWN) private var notificationAlertShown = false
|
||||
@State private var showWhatsNew = false
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
@@ -58,12 +59,19 @@ struct ContentView: View {
|
||||
onAuthorized: { notificationAlertShown = false }
|
||||
)
|
||||
// Local Authentication notice is to be shown on next start after onboarding is complete
|
||||
if (!prefLANoticeShown && prefShowLANotice) {
|
||||
if (!prefLANoticeShown && prefShowLANotice && !chatModel.chats.isEmpty) {
|
||||
prefLANoticeShown = true
|
||||
alertManager.showAlert(laNoticeAlert())
|
||||
} else if !chatModel.showCallView && CallController.shared.activeCallInvitation == nil {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
showWhatsNew = shouldShowWhatsNew()
|
||||
}
|
||||
}
|
||||
prefShowLANotice = true
|
||||
}
|
||||
.sheet(isPresented: $showWhatsNew) {
|
||||
WhatsNewView()
|
||||
}
|
||||
if chatModel.showCallView, let call = chatModel.activeCall {
|
||||
ActiveCallView(call: call)
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@ final class ChatModel: ObservableObject {
|
||||
private func _upsertChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) -> Bool {
|
||||
if let i = reversedChatItems.firstIndex(where: { $0.id == cItem.id }) {
|
||||
let ci = reversedChatItems[i]
|
||||
withAnimation(.default) {
|
||||
withAnimation {
|
||||
self.reversedChatItems[i] = cItem
|
||||
self.reversedChatItems[i].viewTimestamp = .now
|
||||
// on some occasions the confirmation of message being accepted by the server (tick)
|
||||
@@ -230,9 +230,18 @@ final class ChatModel: ObservableObject {
|
||||
}
|
||||
return false
|
||||
} else {
|
||||
withAnimation { reversedChatItems.insert(cItem, at: 0) }
|
||||
withAnimation(itemAnimation()) {
|
||||
reversedChatItems.insert(cItem, at: hasLiveDummy ? 1 : 0)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func itemAnimation() -> Animation? {
|
||||
switch cItem.chatDir {
|
||||
case .directSnd, .groupSnd: return cItem.meta.isLive ? nil : .default
|
||||
default: return .default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func removeChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) {
|
||||
@@ -274,6 +283,28 @@ final class ChatModel: ObservableObject {
|
||||
return nil
|
||||
}
|
||||
|
||||
func addLiveDummy(_ chatInfo: ChatInfo) -> ChatItem {
|
||||
let cItem = ChatItem.liveDummy(chatInfo.chatType)
|
||||
withAnimation {
|
||||
reversedChatItems.insert(cItem, at: 0)
|
||||
}
|
||||
return cItem
|
||||
}
|
||||
|
||||
func removeLiveDummy(animated: Bool = true) {
|
||||
if hasLiveDummy {
|
||||
if animated {
|
||||
withAnimation { _ = reversedChatItems.removeFirst() }
|
||||
} else {
|
||||
_ = reversedChatItems.removeFirst()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var hasLiveDummy: Bool {
|
||||
reversedChatItems.first?.isLiveDummy == true
|
||||
}
|
||||
|
||||
func markChatItemsRead(_ cInfo: ChatInfo) {
|
||||
// update preview
|
||||
_updateChat(cInfo.id) { chat in
|
||||
|
||||
@@ -49,8 +49,9 @@ func saveAnimImage(_ image: UIImage) -> String? {
|
||||
}
|
||||
|
||||
func saveImage(_ uiImage: UIImage) -> String? {
|
||||
if let imageDataResized = resizeImageToDataSize(uiImage, maxDataSize: MAX_IMAGE_SIZE) {
|
||||
let ext = imageHasAlpha(uiImage) ? "png" : "jpg"
|
||||
let hasAlpha = imageHasAlpha(uiImage)
|
||||
let ext = hasAlpha ? "png" : "jpg"
|
||||
if let imageDataResized = resizeImageToDataSize(uiImage, maxDataSize: MAX_IMAGE_SIZE, hasAlpha: hasAlpha) {
|
||||
let fileName = generateNewFileName("IMG", ext)
|
||||
return saveFile(imageDataResized, fileName)
|
||||
}
|
||||
@@ -67,19 +68,18 @@ func cropToSquare(_ image: UIImage) -> UIImage {
|
||||
} else if size.height > side {
|
||||
origin.y -= (size.height - side) / 2
|
||||
}
|
||||
return resizeImage(image, newBounds: CGRect(origin: .zero, size: newSize), drawIn: CGRect(origin: origin, size: size))
|
||||
return resizeImage(image, newBounds: CGRect(origin: .zero, size: newSize), drawIn: CGRect(origin: origin, size: size), hasAlpha: imageHasAlpha(image))
|
||||
}
|
||||
|
||||
func resizeImageToDataSize(_ image: UIImage, maxDataSize: Int64) -> Data? {
|
||||
func resizeImageToDataSize(_ image: UIImage, maxDataSize: Int64, hasAlpha: Bool) -> Data? {
|
||||
var img = image
|
||||
let usePng = imageHasAlpha(image)
|
||||
var data = usePng ? img.pngData() : img.jpegData(compressionQuality: 0.85)
|
||||
var data = hasAlpha ? img.pngData() : img.jpegData(compressionQuality: 0.85)
|
||||
var dataSize = data?.count ?? 0
|
||||
while dataSize != 0 && dataSize > maxDataSize {
|
||||
let ratio = sqrt(Double(dataSize) / Double(maxDataSize))
|
||||
let clippedRatio = min(ratio, 2.0)
|
||||
img = reduceSize(img, ratio: clippedRatio)
|
||||
data = usePng ? img.pngData() : img.jpegData(compressionQuality: 0.85)
|
||||
img = reduceSize(img, ratio: clippedRatio, hasAlpha: hasAlpha)
|
||||
data = hasAlpha ? img.pngData() : img.jpegData(compressionQuality: 0.85)
|
||||
dataSize = data?.count ?? 0
|
||||
}
|
||||
logger.debug("resizeImageToDataSize final \(dataSize)")
|
||||
@@ -88,45 +88,61 @@ func resizeImageToDataSize(_ image: UIImage, maxDataSize: Int64) -> Data? {
|
||||
|
||||
func resizeImageToStrSize(_ image: UIImage, maxDataSize: Int64) -> String? {
|
||||
var img = image
|
||||
var str = compressImageStr(img)
|
||||
let hasAlpha = imageHasAlpha(image)
|
||||
var str = compressImageStr(img, hasAlpha: hasAlpha)
|
||||
var dataSize = str?.count ?? 0
|
||||
while dataSize != 0 && dataSize > maxDataSize {
|
||||
let ratio = sqrt(Double(dataSize) / Double(maxDataSize))
|
||||
let clippedRatio = min(ratio, 2.0)
|
||||
img = reduceSize(img, ratio: clippedRatio)
|
||||
str = compressImageStr(img)
|
||||
img = reduceSize(img, ratio: clippedRatio, hasAlpha: hasAlpha)
|
||||
str = compressImageStr(img, hasAlpha: hasAlpha)
|
||||
dataSize = str?.count ?? 0
|
||||
}
|
||||
logger.debug("resizeImageToStrSize final \(dataSize)")
|
||||
return str
|
||||
}
|
||||
|
||||
func compressImageStr(_ image: UIImage, _ compressionQuality: CGFloat = 0.85) -> String? {
|
||||
let ext = imageHasAlpha(image) ? "png" : "jpg"
|
||||
if let data = imageHasAlpha(image) ? image.pngData() : image.jpegData(compressionQuality: compressionQuality) {
|
||||
func compressImageStr(_ image: UIImage, _ compressionQuality: CGFloat = 0.85, hasAlpha: Bool) -> String? {
|
||||
let ext = hasAlpha ? "png" : "jpg"
|
||||
if let data = hasAlpha ? image.pngData() : image.jpegData(compressionQuality: compressionQuality) {
|
||||
return "data:image/\(ext);base64,\(data.base64EncodedString())"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func reduceSize(_ image: UIImage, ratio: CGFloat) -> UIImage {
|
||||
private func reduceSize(_ image: UIImage, ratio: CGFloat, hasAlpha: Bool) -> UIImage {
|
||||
let newSize = CGSize(width: floor(image.size.width / ratio), height: floor(image.size.height / ratio))
|
||||
let bounds = CGRect(origin: .zero, size: newSize)
|
||||
return resizeImage(image, newBounds: bounds, drawIn: bounds)
|
||||
return resizeImage(image, newBounds: bounds, drawIn: bounds, hasAlpha: hasAlpha)
|
||||
}
|
||||
|
||||
private func resizeImage(_ image: UIImage, newBounds: CGRect, drawIn: CGRect) -> UIImage {
|
||||
private func resizeImage(_ image: UIImage, newBounds: CGRect, drawIn: CGRect, hasAlpha: Bool) -> UIImage {
|
||||
let format = UIGraphicsImageRendererFormat()
|
||||
format.scale = 1.0
|
||||
format.opaque = !imageHasAlpha(image)
|
||||
format.opaque = !hasAlpha
|
||||
return UIGraphicsImageRenderer(bounds: newBounds, format: format).image { _ in
|
||||
image.draw(in: drawIn)
|
||||
}
|
||||
}
|
||||
|
||||
func imageHasAlpha(_ img: UIImage) -> Bool {
|
||||
let alpha = img.cgImage?.alphaInfo
|
||||
return alpha == .first || alpha == .last || alpha == .premultipliedFirst || alpha == .premultipliedLast || alpha == .alphaOnly
|
||||
if let cgImage = img.cgImage {
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue)
|
||||
if let context = CGContext(data: nil, width: cgImage.width, height: cgImage.height, bitsPerComponent: 8, bytesPerRow: cgImage.width * 4, space: colorSpace, bitmapInfo: bitmapInfo.rawValue) {
|
||||
context.draw(cgImage, in: CGRect(x: 0, y: 0, width: cgImage.width, height: cgImage.height))
|
||||
if let data = context.data {
|
||||
let data = data.assumingMemoryBound(to: UInt8.self)
|
||||
let size = cgImage.width * cgImage.height
|
||||
var i = 0
|
||||
while i < size {
|
||||
if data[i] < 255 { return true }
|
||||
i += 4
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func saveFileFromURL(_ url: URL) -> String? {
|
||||
|
||||
@@ -23,9 +23,10 @@ struct CIFeaturePreferenceView: View {
|
||||
.scaleEffect(feature.iconScale)
|
||||
if let ct = chat.chatInfo.contact,
|
||||
allowed != .no && ct.allowsFeature(feature) && !ct.userAllowsFeature(feature) {
|
||||
featurePreferenceView(accept: true)
|
||||
let setParam = feature == .timedMessages && ct.mergedPreferences.timedMessages.userPreference.preference.ttl == nil
|
||||
featurePreferenceView(acceptText: setParam ? "Set 1 day" : "Accept")
|
||||
.onTapGesture {
|
||||
allowFeatureToContact(ct, feature)
|
||||
allowFeatureToContact(ct, feature, param: setParam ? 86400 : nil)
|
||||
}
|
||||
} else {
|
||||
featurePreferenceView()
|
||||
@@ -36,26 +37,28 @@ struct CIFeaturePreferenceView: View {
|
||||
.textSelection(.disabled)
|
||||
}
|
||||
|
||||
private func featurePreferenceView(accept: Bool = false) -> some View {
|
||||
private func featurePreferenceView(acceptText: LocalizedStringKey? = nil) -> some View {
|
||||
var r = Text(CIContent.preferenceText(feature, allowed, param) + " ")
|
||||
.fontWeight(.light)
|
||||
.foregroundColor(.secondary)
|
||||
if accept {
|
||||
r = r + Text("Accept" + " ")
|
||||
.fontWeight(.light)
|
||||
.foregroundColor(.secondary)
|
||||
if let acceptText {
|
||||
r = r
|
||||
+ Text(acceptText)
|
||||
.fontWeight(.medium)
|
||||
.foregroundColor(.accentColor)
|
||||
+ Text(" ")
|
||||
}
|
||||
r = r + chatItem.timestampText
|
||||
.fontWeight(.light)
|
||||
.foregroundColor(.secondary)
|
||||
.fontWeight(.light)
|
||||
.foregroundColor(.secondary)
|
||||
return r.font(.caption)
|
||||
}
|
||||
}
|
||||
|
||||
func allowFeatureToContact(_ contact: Contact, _ feature: ChatFeature) {
|
||||
func allowFeatureToContact(_ contact: Contact, _ feature: ChatFeature, param: Int? = nil) {
|
||||
Task {
|
||||
do {
|
||||
let prefs = contactUserPreferencesToPreferences(contact.mergedPreferences).setAllowed(feature)
|
||||
let prefs = contactUserPreferencesToPreferences(contact.mergedPreferences).setAllowed(feature, param: param)
|
||||
if let toContact = try await apiSetContactPrefs(contactId: contact.contactId, preferences: prefs) {
|
||||
await MainActor.run {
|
||||
ChatModel.shared.updateContact(toContact)
|
||||
|
||||
53
apps/ios/Shared/Views/Chat/ChatItem/CIInvalidJSONView.swift
Normal file
53
apps/ios/Shared/Views/Chat/ChatItem/CIInvalidJSONView.swift
Normal file
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// CIInvalidJSONView.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by JRoberts on 29.12.2022.
|
||||
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct CIInvalidJSONView: View {
|
||||
var json: String
|
||||
@State private var showJSON = false
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .bottom, spacing: 0) {
|
||||
Text("invalid data")
|
||||
.foregroundColor(.red)
|
||||
.italic()
|
||||
}
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 6)
|
||||
.background(Color(uiColor: .tertiarySystemGroupedBackground))
|
||||
.cornerRadius(18)
|
||||
.textSelection(.disabled)
|
||||
.onTapGesture { showJSON = true }
|
||||
.sheet(isPresented: $showJSON) {
|
||||
invalidJSONView(json)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func invalidJSONView(_ json: String) -> some View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Button {
|
||||
showShareSheet(items: [json])
|
||||
} label: {
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
ScrollView {
|
||||
Text(json)
|
||||
}
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
.padding()
|
||||
}
|
||||
|
||||
struct CIInvalidJSONView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
CIInvalidJSONView(json: "{}")
|
||||
}
|
||||
}
|
||||
@@ -72,6 +72,7 @@ struct ChatItemContentView<Content: View>: View {
|
||||
case let .sndGroupFeature(feature, preference, _): chatFeatureView(feature, preference.enable.iconColor)
|
||||
case let .rcvChatFeatureRejected(feature): chatFeatureView(feature, .red)
|
||||
case let .rcvGroupFeatureRejected(feature): chatFeatureView(feature, .red)
|
||||
case let .invalidJSON(json): CIInvalidJSONView(json: json)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ private let memberImageSize: CGFloat = 34
|
||||
struct ChatView: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@ObservedObject var chat: Chat
|
||||
@State @ObservedObject var chat: Chat
|
||||
@State private var showChatInfoSheet: Bool = false
|
||||
@State private var showAddMembersSheet: Bool = false
|
||||
@State private var composeState = ComposeState()
|
||||
@@ -253,9 +253,10 @@ struct ChatView: View {
|
||||
loadChat(chat: chat, search: searchText)
|
||||
}
|
||||
.onChange(of: chatModel.chatId) { _ in
|
||||
if let chatId = chatModel.chatId, let chat = chatModel.getChat(chatId) {
|
||||
if let chatId = chatModel.chatId, let c = chatModel.getChat(chatId) {
|
||||
chat = c
|
||||
showChatInfoSheet = false
|
||||
loadChat(chat: chat)
|
||||
loadChat(chat: c)
|
||||
DispatchQueue.main.async {
|
||||
scrollToBottom(proxy)
|
||||
}
|
||||
@@ -441,9 +442,13 @@ struct ChatView: View {
|
||||
|
||||
var body: some View {
|
||||
let alignment: Alignment = ci.chatDir.sent ? .trailing : .leading
|
||||
|
||||
let uiMenu: Binding<UIMenu> = Binding(
|
||||
get: { UIMenu(title: "", children: menu(live: composeState.liveMessage != nil)) },
|
||||
set: { _ in }
|
||||
)
|
||||
|
||||
ChatItemView(chatInfo: chat.chatInfo, chatItem: ci, showMember: showMember, maxWidth: maxWidth, scrollProxy: scrollProxy, revealed: $revealed)
|
||||
.uiKitContextMenu(actions: menu())
|
||||
.uiKitContextMenu(menu: uiMenu)
|
||||
.confirmationDialog("Delete message?", isPresented: $showDeleteMessage, titleVisibility: .visible) {
|
||||
Button("Delete for me", role: .destructive) {
|
||||
deleteMessage(.cidmInternal)
|
||||
@@ -458,30 +463,34 @@ struct ChatView: View {
|
||||
.frame(minWidth: 0, maxWidth: .infinity, alignment: alignment)
|
||||
}
|
||||
|
||||
private func menu() -> [UIAction] {
|
||||
private func menu(live: Bool) -> [UIAction] {
|
||||
var menu: [UIAction] = []
|
||||
if let mc = ci.content.msgContent, !ci.meta.itemDeleted || revealed {
|
||||
if !ci.meta.itemDeleted {
|
||||
if !ci.meta.itemDeleted && !ci.isLiveDummy && !live {
|
||||
menu.append(replyUIAction())
|
||||
}
|
||||
menu.append(shareUIAction())
|
||||
menu.append(copyUIAction())
|
||||
if let filePath = getLoadedFilePath(ci.file) {
|
||||
if case .image = ci.content.msgContent, let image = getLoadedImage(ci.file) {
|
||||
if image.imageData != nil, let filePath = getLoadedFilePath(ci.file) {
|
||||
if image.imageData != nil {
|
||||
menu.append(saveFileAction(filePath))
|
||||
} else {
|
||||
menu.append(saveImageAction(image))
|
||||
}
|
||||
} else if case .file = ci.content.msgContent, let filePath = getLoadedFilePath(ci.file) {
|
||||
} else {
|
||||
menu.append(saveFileAction(filePath))
|
||||
}
|
||||
if ci.meta.editable && !mc.isVoice {
|
||||
}
|
||||
if ci.meta.editable && !mc.isVoice && !live {
|
||||
menu.append(editAction())
|
||||
}
|
||||
if revealed {
|
||||
menu.append(hideUIAction())
|
||||
}
|
||||
menu.append(deleteUIAction())
|
||||
if !live || !ci.meta.isLive {
|
||||
menu.append(deleteUIAction())
|
||||
}
|
||||
} else if ci.meta.itemDeleted {
|
||||
menu.append(revealUIAction())
|
||||
menu.append(deleteUIAction())
|
||||
|
||||
@@ -34,7 +34,7 @@ enum VoiceMessageRecordingState {
|
||||
struct LiveMessage {
|
||||
var chatItem: ChatItem
|
||||
var typedMsg: String
|
||||
var sentMsg: String
|
||||
var sentMsg: String?
|
||||
}
|
||||
|
||||
struct ComposeState {
|
||||
@@ -96,6 +96,13 @@ struct ComposeState {
|
||||
}
|
||||
}
|
||||
|
||||
var quoting: Bool {
|
||||
switch contextItem {
|
||||
case .quotedItem: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
var sendEnabled: Bool {
|
||||
switch preview {
|
||||
case .imagePreviews: return true
|
||||
@@ -105,6 +112,10 @@ struct ComposeState {
|
||||
}
|
||||
}
|
||||
|
||||
var endLiveDisabled: Bool {
|
||||
liveMessage != nil && message.isEmpty && noPreview && !quoting
|
||||
}
|
||||
|
||||
var linkPreviewAllowed: Bool {
|
||||
switch preview {
|
||||
case .imagePreviews: return false
|
||||
@@ -232,9 +243,9 @@ struct ComposeView: View {
|
||||
VStack(spacing: 0) {
|
||||
contextItemView()
|
||||
switch (composeState.editing, composeState.preview) {
|
||||
case (true, .filePreview): EmptyView()
|
||||
case (true, .voicePreview): EmptyView() // ? we may allow playback when editing is allowed
|
||||
default: previewView()
|
||||
case (true, .filePreview): EmptyView()
|
||||
case (true, .voicePreview): EmptyView() // ? we may allow playback when editing is allowed
|
||||
default: previewView()
|
||||
}
|
||||
HStack (alignment: .bottom) {
|
||||
Button {
|
||||
@@ -255,6 +266,10 @@ struct ComposeView: View {
|
||||
},
|
||||
sendLiveMessage: sendLiveMessage,
|
||||
updateLiveMessage: updateLiveMessage,
|
||||
cancelLiveMessage: {
|
||||
composeState.liveMessage = nil
|
||||
chatModel.removeLiveDummy()
|
||||
},
|
||||
voiceMessageAllowed: chat.chatInfo.featureEnabled(.voice),
|
||||
showEnableVoiceMessagesAlert: chat.chatInfo.showEnableVoiceMessagesAlert,
|
||||
startVoiceMessageRecording: {
|
||||
@@ -371,10 +386,11 @@ struct ComposeView: View {
|
||||
if let fileName = composeState.voiceMessageRecordingFileName {
|
||||
cancelVoiceMessageRecording(fileName)
|
||||
}
|
||||
if composeState.liveMessage != nil {
|
||||
if composeState.liveMessage != nil && (!composeState.message.isEmpty || composeState.liveMessage?.sentMsg != nil) {
|
||||
sendMessage()
|
||||
resetLinkPreview()
|
||||
}
|
||||
chatModel.removeLiveDummy(animated: false)
|
||||
}
|
||||
.onChange(of: chatModel.stopPreviousRecPlay) { _ in
|
||||
if !startingRecording {
|
||||
@@ -395,11 +411,17 @@ struct ComposeView: View {
|
||||
|
||||
private func sendLiveMessage() async {
|
||||
let typedMsg = composeState.message
|
||||
let sentMsg = truncateToWords(typedMsg)
|
||||
if composeState.liveMessage == nil,
|
||||
let ci = await sendMessageAsync(sentMsg, live: true) {
|
||||
let lm = composeState.liveMessage
|
||||
if (composeState.sendEnabled || composeState.quoting)
|
||||
&& (lm == nil || lm?.sentMsg == nil),
|
||||
let ci = await sendMessageAsync(typedMsg, live: true) {
|
||||
await MainActor.run {
|
||||
composeState = composeState.copy(liveMessage: LiveMessage(chatItem: ci, typedMsg: typedMsg, sentMsg: sentMsg))
|
||||
composeState = composeState.copy(liveMessage: LiveMessage(chatItem: ci, typedMsg: typedMsg, sentMsg: typedMsg))
|
||||
}
|
||||
} else if lm == nil {
|
||||
let cItem = chatModel.addLiveDummy(chat.chatInfo)
|
||||
await MainActor.run {
|
||||
composeState = composeState.copy(liveMessage: LiveMessage(chatItem: cItem, typedMsg: typedMsg, sentMsg: nil))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -424,7 +446,7 @@ struct ComposeView: View {
|
||||
|
||||
private func liveMessageToSend(_ lm: LiveMessage, _ t: String) -> String? {
|
||||
let s = t != lm.typedMsg ? truncateToWords(t) : t
|
||||
return s != lm.sentMsg ? s : nil
|
||||
return s != lm.sentMsg && (lm.sentMsg != nil || !s.isEmpty) ? s : nil
|
||||
}
|
||||
|
||||
private func truncateToWords(_ s: String) -> String {
|
||||
@@ -505,10 +527,14 @@ struct ComposeView: View {
|
||||
private func sendMessageAsync(_ text: String?, live: Bool) async -> ChatItem? {
|
||||
var sent: ChatItem?
|
||||
let msgText = text ?? composeState.message
|
||||
if !live { await sending() }
|
||||
let liveMessage = composeState.liveMessage
|
||||
if !live {
|
||||
if liveMessage != nil { composeState = composeState.copy(liveMessage: nil) }
|
||||
await sending()
|
||||
}
|
||||
if case let .editingItem(ci) = composeState.contextItem {
|
||||
sent = await updateMessage(ci, live: live)
|
||||
} else if let liveMessage = composeState.liveMessage {
|
||||
} else if let liveMessage = liveMessage, liveMessage.sentMsg != nil {
|
||||
sent = await updateMessage(liveMessage.chatItem, live: live)
|
||||
} else {
|
||||
var quoted: Int64? = nil
|
||||
@@ -605,6 +631,7 @@ struct ComposeView: View {
|
||||
live: live
|
||||
) {
|
||||
await MainActor.run {
|
||||
chatModel.removeLiveDummy(animated: false)
|
||||
chatModel.addChatItem(chat.chatInfo, chatItem)
|
||||
}
|
||||
return chatItem
|
||||
|
||||
@@ -21,7 +21,6 @@ struct NativeTextEditor: UIViewRepresentable {
|
||||
|
||||
func makeUIView(context: Context) -> UITextView {
|
||||
let field = CustomUITextField()
|
||||
field.allowsEditingTextAttributes = true
|
||||
field.text = text
|
||||
field.font = font
|
||||
field.textAlignment = alignment == .leading ? .left : .right
|
||||
|
||||
@@ -9,11 +9,14 @@
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
private let liveMsgInterval: UInt64 = 3000_000000
|
||||
|
||||
struct SendMessageView: View {
|
||||
@Binding var composeState: ComposeState
|
||||
var sendMessage: () -> Void
|
||||
var sendLiveMessage: (() async -> Void)? = nil
|
||||
var updateLiveMessage: (() async -> Void)? = nil
|
||||
var cancelLiveMessage: (() -> Void)? = nil
|
||||
var showVoiceMessageButton: Bool = true
|
||||
var voiceMessageAllowed: Bool = true
|
||||
var showEnableVoiceMessagesAlert: ChatInfo.ShowEnableVoiceMessagesAlert = .other
|
||||
@@ -97,12 +100,18 @@ struct SendMessageView: View {
|
||||
} else {
|
||||
voiceMessageNotAllowedButton()
|
||||
}
|
||||
if let send = sendLiveMessage, let update = updateLiveMessage {
|
||||
if let send = sendLiveMessage,
|
||||
let update = updateLiveMessage,
|
||||
case .noContextItem = composeState.contextItem {
|
||||
startLiveMessageButton(send: send, update: update)
|
||||
}
|
||||
}
|
||||
} else if vmrs == .recording && !holdingVMR {
|
||||
finishVoiceMessageRecordingButton()
|
||||
} else if composeState.liveMessage != nil && composeState.liveMessage?.sentMsg == nil && composeState.message.isEmpty {
|
||||
cancelLiveMessageButton {
|
||||
cancelLiveMessage?()
|
||||
}
|
||||
} else {
|
||||
sendMessageButton()
|
||||
}
|
||||
@@ -129,11 +138,13 @@ struct SendMessageView: View {
|
||||
.disabled(
|
||||
!composeState.sendEnabled ||
|
||||
composeState.disabled ||
|
||||
(!voiceMessageAllowed && composeState.voicePreview)
|
||||
(!voiceMessageAllowed && composeState.voicePreview) ||
|
||||
composeState.endLiveDisabled
|
||||
)
|
||||
.frame(width: 29, height: 29)
|
||||
|
||||
if composeState.liveMessage == nil,
|
||||
case .noContextItem = composeState.contextItem,
|
||||
!composeState.voicePreview && !composeState.editing,
|
||||
let send = sendLiveMessage,
|
||||
let update = updateLiveMessage {
|
||||
@@ -141,7 +152,7 @@ struct SendMessageView: View {
|
||||
Button {
|
||||
startLiveMessage(send: send, update: update)
|
||||
} label: {
|
||||
Label("Send live message", systemImage: "ellipsis.circle")
|
||||
Label("Send live message", systemImage: "bolt.fill")
|
||||
}
|
||||
}
|
||||
.padding([.bottom, .trailing], 4)
|
||||
@@ -220,6 +231,20 @@ struct SendMessageView: View {
|
||||
.padding([.bottom, .trailing], 4)
|
||||
}
|
||||
|
||||
private func cancelLiveMessageButton(cancel: @escaping () -> Void) -> some View {
|
||||
return Button {
|
||||
cancel()
|
||||
} label: {
|
||||
Image(systemName: "multiply")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.foregroundColor(.accentColor)
|
||||
.frame(width: 15, height: 15)
|
||||
}
|
||||
.frame(width: 29, height: 29)
|
||||
.padding([.bottom, .horizontal], 4)
|
||||
}
|
||||
|
||||
private func startLiveMessageButton(send: @escaping () async -> Void, update: @escaping () async -> Void) -> some View {
|
||||
return Button {
|
||||
switch composeState.preview {
|
||||
@@ -227,9 +252,11 @@ struct SendMessageView: View {
|
||||
default: ()
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "ellipsis.circle.fill")
|
||||
Image(systemName: "bolt.fill")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.foregroundColor(.accentColor)
|
||||
.frame(width: 20, height: 20)
|
||||
}
|
||||
.frame(width: 29, height: 29)
|
||||
.padding([.bottom, .horizontal], 4)
|
||||
@@ -269,9 +296,12 @@ struct SendMessageView: View {
|
||||
sendButtonOpacity = 1
|
||||
}
|
||||
}
|
||||
Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { t in
|
||||
if composeState.liveMessage == nil { t.invalidate() }
|
||||
Task { await update() }
|
||||
Task {
|
||||
_ = try? await Task.sleep(nanoseconds: liveMsgInterval)
|
||||
while composeState.liveMessage != nil {
|
||||
await update()
|
||||
_ = try? await Task.sleep(nanoseconds: liveMsgInterval)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ struct GroupChatInfoView: View {
|
||||
@Environment(\.dismiss) var dismiss: DismissAction
|
||||
@ObservedObject var chat: Chat
|
||||
@State var groupInfo: GroupInfo
|
||||
@State var selectedMember: Int64? = nil
|
||||
@ObservedObject private var alertManager = AlertManager.shared
|
||||
@State private var alert: GroupChatInfoViewAlert? = nil
|
||||
@State private var groupLink: String?
|
||||
@@ -66,22 +65,16 @@ struct GroupChatInfoView: View {
|
||||
}
|
||||
memberView(groupInfo.membership, user: true)
|
||||
ForEach(members) { member in
|
||||
NavLinkPlain(
|
||||
tag: member.groupMemberId,
|
||||
selection: $selectedMember,
|
||||
label: { memberView(member) }
|
||||
)
|
||||
ZStack {
|
||||
NavigationLink {
|
||||
memberInfoView(member.groupMemberId)
|
||||
} label: {
|
||||
EmptyView()
|
||||
}
|
||||
.opacity(0)
|
||||
memberView(member)
|
||||
}
|
||||
}
|
||||
.background(
|
||||
NavigationLink(
|
||||
destination: memberInfoView(selectedMember),
|
||||
isActive: Binding(
|
||||
get: { selectedMember != nil },
|
||||
set: { _, _ in selectedMember = nil }
|
||||
)
|
||||
) { EmptyView() }
|
||||
.opacity(0)
|
||||
)
|
||||
}
|
||||
|
||||
Section {
|
||||
|
||||
@@ -14,6 +14,10 @@ struct ChatHelp: View {
|
||||
@State private var showAddChat = false
|
||||
|
||||
var body: some View {
|
||||
ScrollView { chatHelp() }
|
||||
}
|
||||
|
||||
func chatHelp() -> some View {
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
Text("Thank you for installing SimpleX Chat!")
|
||||
|
||||
@@ -44,6 +48,15 @@ struct ChatHelp: View {
|
||||
Text("**Scan QR code**: to connect to your contact in person or via video call.")
|
||||
}
|
||||
.padding(.top, 24)
|
||||
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
Text("Markdown in messages")
|
||||
.font(.title2)
|
||||
.fontWeight(.bold)
|
||||
|
||||
MarkdownHelp()
|
||||
}
|
||||
.padding(.top, 24)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ struct ChatListNavLink: View {
|
||||
@State private var showContactRequestDialog = false
|
||||
@State private var showJoinGroupDialog = false
|
||||
@State private var showContactConnectionInfo = false
|
||||
@State private var showInvalidJSON = false
|
||||
|
||||
var body: some View {
|
||||
switch chat.chatInfo {
|
||||
@@ -42,6 +43,8 @@ struct ChatListNavLink: View {
|
||||
contactRequestNavLink(cReq)
|
||||
case let .contactConnection(cConn):
|
||||
contactConnectionNavLink(cConn)
|
||||
case let .invalidJSON(json):
|
||||
invalidJSONPreview(json)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,6 +338,17 @@ struct ChatListNavLink: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func invalidJSONPreview(_ json: String) -> some View {
|
||||
Text("invalid chat data")
|
||||
.foregroundColor(.red)
|
||||
.padding(4)
|
||||
.frame(height: rowHeights[dynamicTypeSize])
|
||||
.onTapGesture { showInvalidJSON = true }
|
||||
.sheet(isPresented: $showInvalidJSON) {
|
||||
invalidJSONView(json)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func deleteContactConnectionAlert(_ contactConnection: PendingContactConnection, showError: @escaping (ErrorAlert) -> Void, success: @escaping () -> Void = {}) -> Alert {
|
||||
|
||||
@@ -14,7 +14,6 @@ struct ChatListView: View {
|
||||
// not really used in this view
|
||||
@State private var showSettings = false
|
||||
@State private var searchText = ""
|
||||
@State private var selectedChat: ChatId?
|
||||
@State private var showAddChat = false
|
||||
|
||||
var body: some View {
|
||||
@@ -42,7 +41,6 @@ struct ChatListView: View {
|
||||
}
|
||||
}
|
||||
.onChange(of: chatModel.chatId) { _ in
|
||||
selectedChat = chatModel.chatId
|
||||
if chatModel.chatId == nil, let chatId = chatModel.chatToTop {
|
||||
chatModel.chatToTop = nil
|
||||
chatModel.popChat(chatId)
|
||||
@@ -79,10 +77,10 @@ struct ChatListView: View {
|
||||
}
|
||||
.background(
|
||||
NavigationLink(
|
||||
destination: chatView(selectedChat),
|
||||
destination: chatView(),
|
||||
isActive: Binding(
|
||||
get: { selectedChat != nil },
|
||||
set: { _, _ in selectedChat = nil }
|
||||
get: { chatModel.chatId != nil },
|
||||
set: { _, _ in chatModel.chatId = nil }
|
||||
)
|
||||
) { EmptyView() }
|
||||
)
|
||||
@@ -131,8 +129,8 @@ struct ChatListView: View {
|
||||
.clipShape(RoundedRectangle(cornerRadius: 16))
|
||||
}
|
||||
|
||||
@ViewBuilder private func chatView(_ chatId: ChatId?) -> some View {
|
||||
if let chatId = chatId, let chat = chatModel.getChat(chatId) {
|
||||
@ViewBuilder private func chatView() -> some View {
|
||||
if let chatId = chatModel.chatId, let chat = chatModel.getChat(chatId) {
|
||||
ChatView(chat: chat).onAppear {
|
||||
loadChat(chat: chat)
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ private struct SheetForItem<T, C>: ViewModifier where T: Identifiable, C: View {
|
||||
}
|
||||
|
||||
private struct PrivacySensitive: ViewModifier {
|
||||
@AppStorage(DEFAULT_PRIVACY_PROTECT_SCREEN) private var protectScreen = true
|
||||
@AppStorage(DEFAULT_PRIVACY_PROTECT_SCREEN) private var protectScreen = false
|
||||
@Environment(\.scenePhase) var scenePhase
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
|
||||
@@ -11,10 +11,10 @@ import UIKit
|
||||
import SwiftUI
|
||||
|
||||
extension View {
|
||||
func uiKitContextMenu(title: String = "", actions: [UIAction]) -> some View {
|
||||
func uiKitContextMenu(menu: Binding<UIMenu>) -> some View {
|
||||
self.overlay(Color(uiColor: .systemBackground))
|
||||
.overlay(
|
||||
InteractionView(content: self, menu: UIMenu(title: title, children: actions))
|
||||
InteractionView(content: self, menu: menu)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ private struct InteractionConfig<Content: View> {
|
||||
|
||||
private struct InteractionView<Content: View>: UIViewRepresentable {
|
||||
let content: Content
|
||||
let menu: UIMenu
|
||||
@Binding var menu: UIMenu
|
||||
|
||||
func makeUIView(context: Context) -> UIView {
|
||||
let view = UIView()
|
||||
|
||||
@@ -75,6 +75,7 @@ struct CreateProfile: View {
|
||||
}
|
||||
.onAppear() {
|
||||
focusDisplayName = true
|
||||
setLastVersionDefault()
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
||||
194
apps/ios/Shared/Views/Onboarding/WhatsNewView.swift
Normal file
194
apps/ios/Shared/Views/Onboarding/WhatsNewView.swift
Normal file
@@ -0,0 +1,194 @@
|
||||
//
|
||||
// WhatsNewView.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by Evgeny on 24/12/2022.
|
||||
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
private struct VersionDescription {
|
||||
var version: String
|
||||
var features: [FeatureDescription]
|
||||
}
|
||||
|
||||
private struct FeatureDescription {
|
||||
var icon: String
|
||||
var title: LocalizedStringKey
|
||||
var description: LocalizedStringKey
|
||||
}
|
||||
|
||||
private let versionDescriptions: [VersionDescription] = [
|
||||
VersionDescription(
|
||||
version: "v4.2",
|
||||
features: [
|
||||
FeatureDescription(
|
||||
icon: "checkmark.shield",
|
||||
title: "Security assessment",
|
||||
description: "SimpleX Chat security was [audited by Trail of Bits](https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html)."
|
||||
),
|
||||
FeatureDescription(
|
||||
icon: "person.2",
|
||||
title: "Group links",
|
||||
description: "Admins can create the links to join groups."
|
||||
),
|
||||
FeatureDescription(
|
||||
icon: "checkmark",
|
||||
title: "Auto-accept contact requests",
|
||||
description: "With optional welcome message."
|
||||
),
|
||||
]
|
||||
),
|
||||
VersionDescription(
|
||||
version: "v4.3",
|
||||
features: [
|
||||
FeatureDescription(
|
||||
icon: "mic",
|
||||
title: "Voice messages",
|
||||
description: "Max 30 seconds, received instantly."
|
||||
),
|
||||
FeatureDescription(
|
||||
icon: "trash.slash",
|
||||
title: "Irreversible message deletion",
|
||||
description: "Your contacts can allow full message deletion."
|
||||
),
|
||||
FeatureDescription(
|
||||
icon: "externaldrive.connected.to.line.below",
|
||||
title: "Improved server configuration",
|
||||
description: "Add servers by scanning QR codes."
|
||||
),
|
||||
FeatureDescription(
|
||||
icon: "eye.slash",
|
||||
title: "Improved privacy and security",
|
||||
description: "Hide app screen in the recent apps."
|
||||
),
|
||||
]
|
||||
),
|
||||
VersionDescription(
|
||||
version: "v4.4",
|
||||
features: [
|
||||
FeatureDescription(
|
||||
icon: "stopwatch",
|
||||
title: "Disappearing messages",
|
||||
description: "Sent messages will be deleted after set time."
|
||||
),
|
||||
FeatureDescription(
|
||||
icon: "ellipsis.circle",
|
||||
title: "Live messages",
|
||||
description: "Recipients see updates as you type them."
|
||||
),
|
||||
FeatureDescription(
|
||||
icon: "checkmark.shield",
|
||||
title: "Verify connection security",
|
||||
description: "Compare security codes with your contacts."
|
||||
),
|
||||
FeatureDescription(
|
||||
icon: "camera",
|
||||
title: "GIFs and stickers",
|
||||
description: "Send them from gallery or custom keyboards."
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
private let lastVersion = versionDescriptions.last!.version
|
||||
|
||||
func setLastVersionDefault() {
|
||||
UserDefaults.standard.set(lastVersion, forKey: DEFAULT_WHATS_NEW_VERSION)
|
||||
}
|
||||
|
||||
func shouldShowWhatsNew() -> Bool {
|
||||
let v = UserDefaults.standard.string(forKey: DEFAULT_WHATS_NEW_VERSION)
|
||||
setLastVersionDefault()
|
||||
return v != lastVersion
|
||||
}
|
||||
|
||||
struct WhatsNewView: View {
|
||||
@Environment(\.dismiss) var dismiss: DismissAction
|
||||
@State var currentVersion = versionDescriptions.count - 1
|
||||
@State var currentVersionNav = versionDescriptions.count - 1
|
||||
var viaSettings = false
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
TabView(selection: $currentVersion) {
|
||||
ForEach(0..<3) { i in
|
||||
let v = versionDescriptions[i]
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text("New in \(v.version)")
|
||||
.font(.title)
|
||||
.foregroundColor(.secondary)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical)
|
||||
ForEach(v.features, id: \.icon) { f in
|
||||
featureDescription(f.icon, f.title, f.description)
|
||||
}
|
||||
if !viaSettings {
|
||||
Spacer()
|
||||
Button("Ok") {
|
||||
dismiss()
|
||||
}
|
||||
.font(.title3)
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
||||
.tag(i)
|
||||
}
|
||||
}
|
||||
.tabViewStyle(.page(indexDisplayMode: .never))
|
||||
Spacer()
|
||||
pagination()
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
||||
private func featureDescription(_ icon: String, _ title: LocalizedStringKey, _ description: LocalizedStringKey) -> some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(alignment: .center) {
|
||||
Image(systemName: icon).foregroundColor(.secondary)
|
||||
Text(title).font(.title3).bold()
|
||||
}
|
||||
Text(description)
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
}
|
||||
|
||||
private func pagination() -> some View {
|
||||
HStack {
|
||||
if currentVersionNav > 0 {
|
||||
let prev = currentVersionNav - 1
|
||||
Button {
|
||||
currentVersionNav = prev
|
||||
withAnimation { currentVersion = prev }
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: "chevron.left")
|
||||
Text(versionDescriptions[prev].version)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
if currentVersionNav < versionDescriptions.count - 1 {
|
||||
let next = currentVersionNav + 1
|
||||
Button {
|
||||
currentVersionNav = next
|
||||
withAnimation { currentVersion = next }
|
||||
} label: {
|
||||
HStack {
|
||||
Text(versionDescriptions[next].version)
|
||||
Image(systemName: "chevron.right")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct NewFeaturesView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
WhatsNewView()
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,6 @@ struct MarkdownHelp: View {
|
||||
.textSelection(.enabled)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ struct PreferencesView: View {
|
||||
}
|
||||
|
||||
private func featureFooter(_ feature: ChatFeature, _ allowFeature: Binding<FeatureAllowed>) -> some View {
|
||||
Text(ChatFeature.timedMessages.allowDescription(allowFeature.wrappedValue))
|
||||
Text(feature.allowDescription(allowFeature.wrappedValue))
|
||||
.frame(height: 36, alignment: .topLeading)
|
||||
}
|
||||
|
||||
|
||||
@@ -13,9 +13,9 @@ struct PrivacySettings: View {
|
||||
@AppStorage(DEFAULT_PRIVACY_ACCEPT_IMAGES) private var autoAcceptImages = true
|
||||
@AppStorage(DEFAULT_PRIVACY_LINK_PREVIEWS) private var useLinkPreviews = true
|
||||
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
|
||||
@AppStorage(GROUP_DEFAULT_PRIVACY_TRANSFER_IMAGES_INLINE, store: groupDefaults) private var transferImagesInline = false
|
||||
@AppStorage(GROUP_DEFAULT_PRIVACY_TRANSFER_IMAGES_INLINE, store: groupDefaults) private var transferImagesInline = true
|
||||
@State private var simplexLinkMode = privacySimplexLinkModeDefault.get()
|
||||
@AppStorage(DEFAULT_PRIVACY_PROTECT_SCREEN) private var protectScreen = true
|
||||
@AppStorage(DEFAULT_PRIVACY_PROTECT_SCREEN) private var protectScreen = false
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
|
||||
@@ -39,6 +39,7 @@ 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_WHATS_NEW_VERSION = "defaultWhatsNewVersion"
|
||||
|
||||
let appDefaults: [String: Any] = [
|
||||
DEFAULT_SHOW_LA_NOTICE: false,
|
||||
@@ -49,7 +50,7 @@ let appDefaults: [String: Any] = [
|
||||
DEFAULT_PRIVACY_ACCEPT_IMAGES: true,
|
||||
DEFAULT_PRIVACY_LINK_PREVIEWS: true,
|
||||
DEFAULT_PRIVACY_SIMPLEX_LINK_MODE: "description",
|
||||
DEFAULT_PRIVACY_PROTECT_SCREEN: true,
|
||||
DEFAULT_PRIVACY_PROTECT_SCREEN: false,
|
||||
DEFAULT_EXPERIMENTAL_CALLS: false,
|
||||
DEFAULT_CHAT_V3_DB_MIGRATION: "offer",
|
||||
DEFAULT_DEVELOPER_TOOLS: false,
|
||||
@@ -193,6 +194,12 @@ struct SettingsView: View {
|
||||
} label: {
|
||||
settingsRow("questionmark") { Text("How to use it") }
|
||||
}
|
||||
NavigationLink {
|
||||
WhatsNewView(viaSettings: true)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
} label: {
|
||||
settingsRow("plus") { Text("What's new") }
|
||||
}
|
||||
NavigationLink {
|
||||
SimpleXInfo(onboarding: false)
|
||||
.navigationBarTitle("", displayMode: .inline)
|
||||
@@ -200,13 +207,14 @@ struct SettingsView: View {
|
||||
} label: {
|
||||
settingsRow("info") { Text("About SimpleX Chat") }
|
||||
}
|
||||
NavigationLink {
|
||||
MarkdownHelp()
|
||||
.navigationTitle("How to use markdown")
|
||||
.frame(maxHeight: .infinity, alignment: .top)
|
||||
} label: {
|
||||
settingsRow("textformat") { Text("Markdown in messages") }
|
||||
}
|
||||
// NavigationLink {
|
||||
// MarkdownHelp()
|
||||
// .padding()
|
||||
// .navigationTitle("How to use markdown")
|
||||
// .frame(maxHeight: .infinity, alignment: .top)
|
||||
// } label: {
|
||||
// settingsRow("textformat") { Text("Markdown in messages") }
|
||||
// }
|
||||
settingsRow("number") {
|
||||
Button("Send questions and ideas") {
|
||||
showSettings = false
|
||||
|
||||
@@ -17,6 +17,11 @@
|
||||
<target> </target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id=" " xml:space="preserve">
|
||||
<source> </source>
|
||||
<target> </target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id=" " xml:space="preserve">
|
||||
<source> </source>
|
||||
<target> </target>
|
||||
@@ -64,10 +69,12 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ is not verified" xml:space="preserve">
|
||||
<source>%@ is not verified</source>
|
||||
<target>%@ wurde nicht überprüft</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ is verified" xml:space="preserve">
|
||||
<source>%@ is verified</source>
|
||||
<target>%@ wurde überprüft</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ wants to connect!" xml:space="preserve">
|
||||
@@ -77,22 +84,27 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="%d days" xml:space="preserve">
|
||||
<source>%d days</source>
|
||||
<target>%d Tage</target>
|
||||
<note>message ttl</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%d hours" xml:space="preserve">
|
||||
<source>%d hours</source>
|
||||
<target>%d Stunden</target>
|
||||
<note>message ttl</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%d min" xml:space="preserve">
|
||||
<source>%d min</source>
|
||||
<target>%d min</target>
|
||||
<note>message ttl</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%d months" xml:space="preserve">
|
||||
<source>%d months</source>
|
||||
<target>%d Monate</target>
|
||||
<note>message ttl</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%d sec" xml:space="preserve">
|
||||
<source>%d sec</source>
|
||||
<target>%d s</target>
|
||||
<note>message ttl</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%d skipped message(s)" xml:space="preserve">
|
||||
@@ -132,10 +144,12 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="%lldd" xml:space="preserve">
|
||||
<source>%lldd</source>
|
||||
<target>%lldT</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lldh" xml:space="preserve">
|
||||
<source>%lldh</source>
|
||||
<target>%lldH</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lldk" xml:space="preserve">
|
||||
@@ -145,18 +159,22 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="%lldm" xml:space="preserve">
|
||||
<source>%lldm</source>
|
||||
<target>%lldmin</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lldmth" xml:space="preserve">
|
||||
<source>%lldmth</source>
|
||||
<target>%lldMon</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%llds" xml:space="preserve">
|
||||
<source>%llds</source>
|
||||
<target>%lldsek</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lldw" xml:space="preserve">
|
||||
<source>%lldw</source>
|
||||
<target>%lldW</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="(" xml:space="preserve">
|
||||
@@ -246,6 +264,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="1 hour" xml:space="preserve">
|
||||
<source>1 hour</source>
|
||||
<target>1 Stunde</target>
|
||||
<note>message ttl</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="1 month" xml:space="preserve">
|
||||
@@ -260,6 +279,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="2 weeks" xml:space="preserve">
|
||||
<source>2 weeks</source>
|
||||
<target>2 Wochen</target>
|
||||
<note>message ttl</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="6" xml:space="preserve">
|
||||
@@ -333,6 +353,11 @@
|
||||
<target>Füge voreingestellte Server hinzu</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add servers by scanning QR codes." xml:space="preserve">
|
||||
<source>Add servers by scanning QR codes.</source>
|
||||
<target>Fügen Sie Server durch Scannen der QR Codes hinzu.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add server…" xml:space="preserve">
|
||||
<source>Add server…</source>
|
||||
<target>Füge Server hinzu…</target>
|
||||
@@ -343,6 +368,11 @@
|
||||
<target>Einem anderen Gerät hinzufügen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Admins can create the links to join groups." xml:space="preserve">
|
||||
<source>Admins can create the links to join groups.</source>
|
||||
<target>Administratoren können Links für den Beitritt zu Gruppen erzeugen.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Advanced network settings" xml:space="preserve">
|
||||
<source>Advanced network settings</source>
|
||||
<target>Erweiterte Netzwerkeinstellungen</target>
|
||||
@@ -370,30 +400,32 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow disappearing messages only if your contact allows it to you." xml:space="preserve">
|
||||
<source>Allow disappearing messages only if your contact allows it to you.</source>
|
||||
<target>Verschwindende Nachrichten nur erlauben, wenn Ihr Kontakt das ebenfalls erlaubt.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow irreversible message deletion only if your contact allows it to you." xml:space="preserve">
|
||||
<source>Allow irreversible message deletion only if your contact allows it to you.</source>
|
||||
<target>Erlauben Sie das unwiederbringliche löschen von Nachrichten nur dann, wenn es Ihnen Ihr Kontakt ebenfalls erlaubt.</target>
|
||||
<target>Erlauben Sie das unwiederbringliche Löschen von Nachrichten nur dann, wenn es Ihnen Ihr Kontakt ebenfalls erlaubt.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow sending direct messages to members." xml:space="preserve">
|
||||
<source>Allow sending direct messages to members.</source>
|
||||
<target>Erlauben Sie das Senden von Direktnachrichten an Mitglieder</target>
|
||||
<target>Das Senden von Direktnachrichten an Gruppenmitglieder erlauben.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow sending disappearing messages." xml:space="preserve">
|
||||
<source>Allow sending disappearing messages.</source>
|
||||
<target>Das Senden von verschwindenden Nachrichten erlauben.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow to irreversibly delete sent messages." xml:space="preserve">
|
||||
<source>Allow to irreversibly delete sent messages.</source>
|
||||
<target>Unwiederbringliches Löschen von gesendeten Nachrichten erlauben.</target>
|
||||
<target>Unwiederbringliches löschen von gesendeten Nachrichten erlauben.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow to send voice messages." xml:space="preserve">
|
||||
<source>Allow to send voice messages.</source>
|
||||
<target>Senden von Sprachnachrichten erlauben.</target>
|
||||
<target>Das Senden von Sprachnachrichten erlauben.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow voice messages only if your contact allows them." xml:space="preserve">
|
||||
@@ -413,6 +445,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow your contacts to send disappearing messages." xml:space="preserve">
|
||||
<source>Allow your contacts to send disappearing messages.</source>
|
||||
<target>Erlauben Sie Ihren Kontakten das Senden von verschwindenden Nachrichten.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow your contacts to send voice messages." xml:space="preserve">
|
||||
@@ -460,6 +493,11 @@
|
||||
<target>Authentifizierung nicht verfügbar</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Auto-accept contact requests" xml:space="preserve">
|
||||
<source>Auto-accept contact requests</source>
|
||||
<target>Kontaktanfragen automatisch annehmen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Auto-accept images" xml:space="preserve">
|
||||
<source>Auto-accept images</source>
|
||||
<target>Bilder automatisch akzeptieren</target>
|
||||
@@ -482,6 +520,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Both you and your contact can send disappearing messages." xml:space="preserve">
|
||||
<source>Both you and your contact can send disappearing messages.</source>
|
||||
<target>Ihr Kontakt und Sie können beide verschwindende Nachrichten senden.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Both you and your contact can send voice messages." xml:space="preserve">
|
||||
@@ -631,6 +670,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Clear verification" xml:space="preserve">
|
||||
<source>Clear verification</source>
|
||||
<target>Überprüfung zurücknehmen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Colors" xml:space="preserve">
|
||||
@@ -638,6 +678,11 @@
|
||||
<target>Farben</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Compare security codes with your contacts." xml:space="preserve">
|
||||
<source>Compare security codes with your contacts.</source>
|
||||
<target>Vergleichen Sie die Sicherheitscodes mit Ihren Kontakten.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Configure ICE servers" xml:space="preserve">
|
||||
<source>Configure ICE servers</source>
|
||||
<target>ICE-Server konfigurieren</target>
|
||||
@@ -795,6 +840,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Create group link" xml:space="preserve">
|
||||
<source>Create group link</source>
|
||||
<target>Gruppenlink erstellen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create link" xml:space="preserve">
|
||||
@@ -952,6 +998,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete after" xml:space="preserve">
|
||||
<source>Delete after</source>
|
||||
<target>Löschen nach</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete archive" xml:space="preserve">
|
||||
@@ -1111,7 +1158,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Direct messages between members are prohibited in this group." xml:space="preserve">
|
||||
<source>Direct messages between members are prohibited in this group.</source>
|
||||
<target>In dieser Gruppe sind Direktnachrichten zwischen Mitgliedern nicht möglich.</target>
|
||||
<target>In dieser Gruppe sind Direktnachrichten zwischen Mitgliedern nicht erlaubt.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disable SimpleX Lock" xml:space="preserve">
|
||||
@@ -1121,14 +1168,17 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Disappearing messages" xml:space="preserve">
|
||||
<source>Disappearing messages</source>
|
||||
<target>Verschwindende Nachrichten</target>
|
||||
<note>chat feature</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disappearing messages are prohibited in this chat." xml:space="preserve">
|
||||
<source>Disappearing messages are prohibited in this chat.</source>
|
||||
<target>In diesem Chat sind verschwindende Nachrichten nicht erlaubt.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disappearing messages are prohibited in this group." xml:space="preserve">
|
||||
<source>Disappearing messages are prohibited in this group.</source>
|
||||
<target>In dieser Gruppe sind verschwindende Nachrichten nicht erlaubt.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disconnect" xml:space="preserve">
|
||||
@@ -1383,7 +1433,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Error saving SMP servers" xml:space="preserve">
|
||||
<source>Error saving SMP servers</source>
|
||||
<target>Fehler beim Speichern der SMP Server</target>
|
||||
<target>Fehler beim Speichern der SMP-Server</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error saving group profile" xml:space="preserve">
|
||||
@@ -1496,6 +1546,11 @@
|
||||
<target>Vollständiger Name:</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="GIFs and stickers" xml:space="preserve">
|
||||
<source>GIFs and stickers</source>
|
||||
<target>GIFs und Sticker</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group" xml:space="preserve">
|
||||
<source>Group</source>
|
||||
<target>Gruppe</target>
|
||||
@@ -1536,6 +1591,11 @@
|
||||
<target>Gruppen-Link</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group links" xml:space="preserve">
|
||||
<source>Group links</source>
|
||||
<target>Gruppen-Links</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group members can irreversibly delete sent messages." xml:space="preserve">
|
||||
<source>Group members can irreversibly delete sent messages.</source>
|
||||
<target>Gruppenmitglieder können gesendete Nachrichten unwiederbringlich löschen.</target>
|
||||
@@ -1548,11 +1608,12 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Group members can send disappearing messages." xml:space="preserve">
|
||||
<source>Group members can send disappearing messages.</source>
|
||||
<target>Gruppenmitglieder können verschwindende Nachrichten senden.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group members can send voice messages." xml:space="preserve">
|
||||
<source>Group members can send voice messages.</source>
|
||||
<target>Gruppenmitglieder können Sprachnachrichten senden.</target>
|
||||
<target>Gruppenmitglieder können Sprachnachrichten versenden.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group message:" xml:space="preserve">
|
||||
@@ -1572,7 +1633,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Group profile is stored on members' devices, not on the servers." xml:space="preserve">
|
||||
<source>Group profile is stored on members' devices, not on the servers.</source>
|
||||
<target>Das Gruppenprofil wird nur auf den Mitglieds-Systemen gespeichtert und nicht auf den Servern.</target>
|
||||
<target>Das Gruppenprofil wird nur auf den Mitglieds-Systemen gespeichert und nicht auf den Servern.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group will be deleted for all members - this cannot be undone!" xml:space="preserve">
|
||||
@@ -1600,6 +1661,11 @@
|
||||
<target>Verbergen</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Hide app screen in the recent apps." xml:space="preserve">
|
||||
<source>Hide app screen in the recent apps.</source>
|
||||
<target>App-Bildschirm in aktuellen Anwendungen verbergen.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="How SimpleX works" xml:space="preserve">
|
||||
<source>How SimpleX works</source>
|
||||
<target>Wie SimpleX funktioniert</target>
|
||||
@@ -1620,11 +1686,6 @@
|
||||
<target>Wie man SimpleX nutzt</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="How to use markdown" xml:space="preserve">
|
||||
<source>How to use markdown</source>
|
||||
<target>Markdowns verwenden</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="How to use your servers" xml:space="preserve">
|
||||
<source>How to use your servers</source>
|
||||
<target>Wie Sie Ihre Server nutzen</target>
|
||||
@@ -1685,6 +1746,16 @@
|
||||
<target>Datenbank importieren</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Improved privacy and security" xml:space="preserve">
|
||||
<source>Improved privacy and security</source>
|
||||
<target>Verbesserte Privatsphäre und Sicherheit</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Improved server configuration" xml:space="preserve">
|
||||
<source>Improved server configuration</source>
|
||||
<target>Verbesserte Serverkonfiguration</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incognito" xml:space="preserve">
|
||||
<source>Incognito</source>
|
||||
<target>Inkognito</target>
|
||||
@@ -1722,6 +1793,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Incorrect security code!" xml:space="preserve">
|
||||
<source>Incorrect security code!</source>
|
||||
<target>Falscher Sicherheitscode!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" xml:space="preserve">
|
||||
@@ -1766,14 +1838,19 @@
|
||||
<target>In Gruppe einladen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Irreversible message deletion" xml:space="preserve">
|
||||
<source>Irreversible message deletion</source>
|
||||
<target>Unwiederbringliches löschen einer Nachricht</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Irreversible message deletion is prohibited in this chat." xml:space="preserve">
|
||||
<source>Irreversible message deletion is prohibited in this chat.</source>
|
||||
<target>In diesem Chat ist das unwiederbringliche Löschen von Nachrichten untersagt.</target>
|
||||
<target>In diesem Chat ist das unwiederbringliche Löschen von Nachrichten nicht erlaubt.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Irreversible message deletion is prohibited in this group." xml:space="preserve">
|
||||
<source>Irreversible message deletion is prohibited in this group.</source>
|
||||
<target>In dieser Gruppe ist das unwiederbringliche Löschen von Nachrichten verboten.</target>
|
||||
<target>In dieser Gruppe ist das unwiederbringliche Löschen von Nachrichten nicht erlaubt.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="It allows having many anonymous connections without any shared data between them in a single chat profile." xml:space="preserve">
|
||||
@@ -1863,6 +1940,12 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v
|
||||
</trans-unit>
|
||||
<trans-unit id="Live message!" xml:space="preserve">
|
||||
<source>Live message!</source>
|
||||
<target>Live Nachricht!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Live messages" xml:space="preserve">
|
||||
<source>Live messages</source>
|
||||
<target>Live Nachrichten</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Local name" xml:space="preserve">
|
||||
@@ -1882,7 +1965,7 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v
|
||||
</trans-unit>
|
||||
<trans-unit id="Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." xml:space="preserve">
|
||||
<source>Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated.</source>
|
||||
<target>Stellen Sie sicher, dass die WebRTC ICE-Server Adressen das richtige Format haben, zeilenweise angeordnet und nicht kopiert sind.</target>
|
||||
<target>Stellen Sie sicher, dass die WebRTC ICE-Server Adressen das richtige Format haben, zeilenweise angeordnet und nicht doppelt vorhanden sind.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" xml:space="preserve">
|
||||
@@ -1902,6 +1985,7 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v
|
||||
</trans-unit>
|
||||
<trans-unit id="Mark verified" xml:space="preserve">
|
||||
<source>Mark verified</source>
|
||||
<target>Als überprüft markieren</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Markdown in messages" xml:space="preserve">
|
||||
@@ -1909,6 +1993,11 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v
|
||||
<target>Markdowns in Nachrichten</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Max 30 seconds, received instantly." xml:space="preserve">
|
||||
<source>Max 30 seconds, received instantly.</source>
|
||||
<target>Max. 30 Sekunden, sofort erhalten.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member" xml:space="preserve">
|
||||
<source>Member</source>
|
||||
<target>Mitglied</target>
|
||||
@@ -2009,6 +2098,11 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v
|
||||
<target>Neues Datenbankarchiv</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="New in %@" xml:space="preserve">
|
||||
<source>New in %@</source>
|
||||
<target>Neu in %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="New member role" xml:space="preserve">
|
||||
<source>New member role</source>
|
||||
<target>Neue Mitgliedsrolle</target>
|
||||
@@ -2131,11 +2225,12 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v
|
||||
</trans-unit>
|
||||
<trans-unit id="Only you can send disappearing messages." xml:space="preserve">
|
||||
<source>Only you can send disappearing messages.</source>
|
||||
<target>Nur Sie können verschwindende Nachrichten senden.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Only you can send voice messages." xml:space="preserve">
|
||||
<source>Only you can send voice messages.</source>
|
||||
<target>Nur Sie können Sprachnachrichten senden.</target>
|
||||
<target>Nur Sie können Sprachnachrichten versenden.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Only your contact can irreversibly delete messages (you can mark them for deletion)." xml:space="preserve">
|
||||
@@ -2145,11 +2240,12 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v
|
||||
</trans-unit>
|
||||
<trans-unit id="Only your contact can send disappearing messages." xml:space="preserve">
|
||||
<source>Only your contact can send disappearing messages.</source>
|
||||
<target>Nur Ihr Kontakt kann verschwindende Nachrichten senden.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Only your contact can send voice messages." xml:space="preserve">
|
||||
<source>Only your contact can send voice messages.</source>
|
||||
<target>Nur Ihr Kontakt kann Sprachnachrichten senden.</target>
|
||||
<target>Nur Ihr Kontakt kann Sprachnachrichten versenden.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open Settings" xml:space="preserve">
|
||||
@@ -2289,21 +2385,22 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v
|
||||
</trans-unit>
|
||||
<trans-unit id="Prohibit irreversible message deletion." xml:space="preserve">
|
||||
<source>Prohibit irreversible message deletion.</source>
|
||||
<target>Unwiederbringliches Löschen von Nachrichten verbieten.</target>
|
||||
<target>Unwiederbringliches löschen von Nachrichten nicht erlauben.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Prohibit sending direct messages to members." xml:space="preserve">
|
||||
<source>Prohibit sending direct messages to members.</source>
|
||||
<target>Verbieten Sie das Senden von Direktnachrichten an Mitglieder</target>
|
||||
<target>Das Senden von Direktnachrichten an Gruppenmitglieder nicht erlauben.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Prohibit sending disappearing messages." xml:space="preserve">
|
||||
<source>Prohibit sending disappearing messages.</source>
|
||||
<target>Das Senden von verschwindenden Nachrichten verbieten.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Prohibit sending voice messages." xml:space="preserve">
|
||||
<source>Prohibit sending voice messages.</source>
|
||||
<target>Senden von Sprachnachrichten untersagen.</target>
|
||||
<target>Das Senden von Sprachnachrichten nicht erlauben.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Protect app screen" xml:space="preserve">
|
||||
@@ -2351,6 +2448,11 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v
|
||||
<target>Empfangen über</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Recipients see updates as you type them." xml:space="preserve">
|
||||
<source>Recipients see updates as you type them.</source>
|
||||
<target>Die Empfänger sehen Nachrichtenaktualisierungen, während Sie sie eingeben.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reject" xml:space="preserve">
|
||||
<source>Reject</source>
|
||||
<target>Ablehnen</target>
|
||||
@@ -2523,7 +2625,7 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v
|
||||
</trans-unit>
|
||||
<trans-unit id="Save servers" xml:space="preserve">
|
||||
<source>Save servers</source>
|
||||
<target>Server speichern</target>
|
||||
<target>Alle Server speichern</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Saved WebRTC ICE servers will be removed" xml:space="preserve">
|
||||
@@ -2538,10 +2640,12 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v
|
||||
</trans-unit>
|
||||
<trans-unit id="Scan code" xml:space="preserve">
|
||||
<source>Scan code</source>
|
||||
<target>Code scannen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Scan security code from your contact's app." xml:space="preserve">
|
||||
<source>Scan security code from your contact's app.</source>
|
||||
<target>Scannen Sie den Sicherheitscode von der App Ihres Kontakts.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Scan server QR code" xml:space="preserve">
|
||||
@@ -2559,16 +2663,24 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v
|
||||
<target>Sichere Warteschlange</target>
|
||||
<note>server test step</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Security assessment" xml:space="preserve">
|
||||
<source>Security assessment</source>
|
||||
<target>Sicherheits-Gutachten</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Security code" xml:space="preserve">
|
||||
<source>Security code</source>
|
||||
<target>Sicherheitscode</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Send" xml:space="preserve">
|
||||
<source>Send</source>
|
||||
<target>Senden</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Send a live message - it will update for the recipient(s) as you type it" xml:space="preserve">
|
||||
<source>Send a live message - it will update for the recipient(s) as you type it</source>
|
||||
<target>Eine Live Nachricht senden - der/die Empfänger sieht/sehen Nachrichtenaktualisierungen, während Sie sie eingeben</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Send direct message" xml:space="preserve">
|
||||
@@ -2583,6 +2695,7 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v
|
||||
</trans-unit>
|
||||
<trans-unit id="Send live message" xml:space="preserve">
|
||||
<source>Send live message</source>
|
||||
<target>Live Nachricht senden</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Send notifications" xml:space="preserve">
|
||||
@@ -2600,6 +2713,11 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v
|
||||
<target>Senden Sie Fragen und Ideen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Send them from gallery or custom keyboards." xml:space="preserve">
|
||||
<source>Send them from gallery or custom keyboards.</source>
|
||||
<target>Senden Sie diese aus dem Fotoalbum oder von individuellen Tastaturen.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sender cancelled file transfer." xml:space="preserve">
|
||||
<source>Sender cancelled file transfer.</source>
|
||||
<target>Der Absender hat die Dateiübertragung abgebrochen.</target>
|
||||
@@ -2620,9 +2738,14 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v
|
||||
<target>Datei-Ereignis wurde gesendet</target>
|
||||
<note>notification</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sent messages will be deleted after set time." xml:space="preserve">
|
||||
<source>Sent messages will be deleted after set time.</source>
|
||||
<target>Gesendete Nachrichten werden nach der eingestellten Zeit gelöscht.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Server requires authorization to create queues, check password" xml:space="preserve">
|
||||
<source>Server requires authorization to create queues, check password</source>
|
||||
<target>Um Warteschlangen zu erzeugen benötigt der Server eine Authentifizierung. Bitte überprüfen Sie das Passwort.</target>
|
||||
<target>Um Warteschlangen zu erzeugen benötigt der Server eine Authentifizierung. Bitte überprüfen Sie das Passwort</target>
|
||||
<note>server test error</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Server test failed!" xml:space="preserve">
|
||||
@@ -2635,6 +2758,11 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v
|
||||
<target>Server</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Set 1 day" xml:space="preserve">
|
||||
<source>Set 1 day</source>
|
||||
<target>Einen Tag festlegen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Set contact name…" xml:space="preserve">
|
||||
<source>Set contact name…</source>
|
||||
<target>Kontaktname festlegen…</target>
|
||||
@@ -2690,6 +2818,11 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v
|
||||
<target>Vorschau anzeigen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="SimpleX Chat security was [audited by Trail of Bits](https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html)." xml:space="preserve">
|
||||
<source>SimpleX Chat security was [audited by Trail of Bits](https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html).</source>
|
||||
<target>Die Sicherheit von SimpleX Chat wurde [von Trail of Bits überprüft](https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html).</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="SimpleX Lock" xml:space="preserve">
|
||||
<source>SimpleX Lock</source>
|
||||
<target>SimpleX Sperre</target>
|
||||
@@ -2994,6 +3127,7 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt
|
||||
</trans-unit>
|
||||
<trans-unit id="To verify end-to-end encryption with your contact compare (or scan) the code on your devices." xml:space="preserve">
|
||||
<source>To verify end-to-end encryption with your contact compare (or scan) the code on your devices.</source>
|
||||
<target>Um die Ende-zu-Ende-Verschlüsselung mit Ihrem Kontakt zu überprüfen, müssen Sie den Sicherheitscode in Ihren Apps vergleichen oder scannen.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Transfer images faster" xml:space="preserve">
|
||||
@@ -3125,7 +3259,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
|
||||
</trans-unit>
|
||||
<trans-unit id="Use server" xml:space="preserve">
|
||||
<source>Use server</source>
|
||||
<target>Benutze Server</target>
|
||||
<target>Server nutzen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Using .onion hosts requires compatible VPN provider." xml:space="preserve">
|
||||
@@ -3138,8 +3272,14 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
|
||||
<target>Verwende SimpleX Chat Server.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify connection security" xml:space="preserve">
|
||||
<source>Verify connection security</source>
|
||||
<target>Sicherheit der Verbindung überprüfen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify security code" xml:space="preserve">
|
||||
<source>Verify security code</source>
|
||||
<target>Sicherheitscode überprüfen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Via browser" xml:space="preserve">
|
||||
@@ -3154,6 +3294,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
|
||||
</trans-unit>
|
||||
<trans-unit id="View security code" xml:space="preserve">
|
||||
<source>View security code</source>
|
||||
<target>Schauen Sie sich den Sicherheitscode an</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Voice messages" xml:space="preserve">
|
||||
@@ -3163,17 +3304,17 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
|
||||
</trans-unit>
|
||||
<trans-unit id="Voice messages are prohibited in this chat." xml:space="preserve">
|
||||
<source>Voice messages are prohibited in this chat.</source>
|
||||
<target>In diesem Chat sind Sprachnachrichten untersagt.</target>
|
||||
<target>In diesem Chat sind Sprachnachrichten nicht erlaubt.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Voice messages are prohibited in this group." xml:space="preserve">
|
||||
<source>Voice messages are prohibited in this group.</source>
|
||||
<target>In dieser Gruppe sind Sprachnachrichten untersagt.</target>
|
||||
<target>In dieser Gruppe sind Sprachnachrichten nicht erlaubt.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Voice messages prohibited!" xml:space="preserve">
|
||||
<source>Voice messages prohibited!</source>
|
||||
<target>Sprachnachrichten sind untersagt!</target>
|
||||
<target>Sprachnachrichten sind nicht erlaubt!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Voice message…" xml:space="preserve">
|
||||
@@ -3206,6 +3347,11 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
|
||||
<target>Begrüßungsmeldung</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="What's new" xml:space="preserve">
|
||||
<source>What's new</source>
|
||||
<target>Was ist neu</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="When available" xml:space="preserve">
|
||||
<source>When available</source>
|
||||
<target>Wenn verfügbar</target>
|
||||
@@ -3216,6 +3362,11 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
|
||||
<target>Wenn Sie ein Inkognito-Profil mit Jemandem teilen, wird dieses Profil auch für die Gruppen verwendet, für die Sie von diesem Kontakt eingeladen werden.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="With optional welcome message." xml:space="preserve">
|
||||
<source>With optional welcome message.</source>
|
||||
<target>Mit optionaler Begrüßungsmeldung.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Wrong database passphrase" xml:space="preserve">
|
||||
<source>Wrong database passphrase</source>
|
||||
<target>Falsches Datenbank-Passwort</target>
|
||||
@@ -3458,6 +3609,11 @@ Sie können diese Verbindung abbrechen und den Kontakt entfernen (und es später
|
||||
<target>Ihr Kontakt hat eine Datei gesendet, die größer ist als die derzeit unterstützte maximale Größe (%@).</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your contacts can allow full message deletion." xml:space="preserve">
|
||||
<source>Your contacts can allow full message deletion.</source>
|
||||
<target>Ihre Kontakte können die unwiederbringliche Löschung von Nachrichten erlauben.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your current chat database will be DELETED and REPLACED with the imported one." xml:space="preserve">
|
||||
<source>Your current chat database will be DELETED and REPLACED with the imported one.</source>
|
||||
<target>Ihre aktuelle Chat-Datenbank wird GELÖSCHT und durch die Importierte ERSETZT.</target>
|
||||
@@ -3590,6 +3746,10 @@ SimpleX-Server können Ihr Profil nicht einsehen.</target>
|
||||
<target>Anrufen…</target>
|
||||
<note>call status</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="cancelled %@" xml:space="preserve">
|
||||
<source>cancelled %@</source>
|
||||
<note>feature offered item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="changed address for you" xml:space="preserve">
|
||||
<source>changed address for you</source>
|
||||
<target>wechselte die Adresse für Sie</target>
|
||||
@@ -3795,6 +3955,21 @@ SimpleX-Server können Ihr Profil nicht einsehen.</target>
|
||||
<target>indirekt (%d)</target>
|
||||
<note>connection level description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="invalid chat" xml:space="preserve">
|
||||
<source>invalid chat</source>
|
||||
<target>Ungültiger Chat</target>
|
||||
<note>invalid chat data</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="invalid chat data" xml:space="preserve">
|
||||
<source>invalid chat data</source>
|
||||
<target>Ungültige Chat-Daten</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="invalid data" xml:space="preserve">
|
||||
<source>invalid data</source>
|
||||
<target>Ungültige Daten</target>
|
||||
<note>invalid chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="invitation to group %@" xml:space="preserve">
|
||||
<source>invitation to group %@</source>
|
||||
<target>Einladung zur Gruppe %@</target>
|
||||
@@ -3886,6 +4061,14 @@ SimpleX-Server können Ihr Profil nicht einsehen.</target>
|
||||
<note>enabled status
|
||||
group pref value</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="offered %@" xml:space="preserve">
|
||||
<source>offered %@</source>
|
||||
<note>feature offered item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="offered %@: %@" xml:space="preserve">
|
||||
<source>offered %1$@: %2$@</source>
|
||||
<note>feature offered item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="on" xml:space="preserve">
|
||||
<source>on</source>
|
||||
<target>Ein</target>
|
||||
|
||||
@@ -17,6 +17,11 @@
|
||||
<target> </target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id=" " xml:space="preserve">
|
||||
<source> </source>
|
||||
<target> </target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id=" " xml:space="preserve">
|
||||
<source> </source>
|
||||
<target> </target>
|
||||
@@ -348,6 +353,11 @@
|
||||
<target>Add preset servers</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add servers by scanning QR codes." xml:space="preserve">
|
||||
<source>Add servers by scanning QR codes.</source>
|
||||
<target>Add servers by scanning QR codes.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add server…" xml:space="preserve">
|
||||
<source>Add server…</source>
|
||||
<target>Add server…</target>
|
||||
@@ -358,6 +368,11 @@
|
||||
<target>Add to another device</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Admins can create the links to join groups." xml:space="preserve">
|
||||
<source>Admins can create the links to join groups.</source>
|
||||
<target>Admins can create the links to join groups.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Advanced network settings" xml:space="preserve">
|
||||
<source>Advanced network settings</source>
|
||||
<target>Advanced network settings</target>
|
||||
@@ -478,6 +493,11 @@
|
||||
<target>Authentication unavailable</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Auto-accept contact requests" xml:space="preserve">
|
||||
<source>Auto-accept contact requests</source>
|
||||
<target>Auto-accept contact requests</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Auto-accept images" xml:space="preserve">
|
||||
<source>Auto-accept images</source>
|
||||
<target>Auto-accept images</target>
|
||||
@@ -658,6 +678,11 @@
|
||||
<target>Colors</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Compare security codes with your contacts." xml:space="preserve">
|
||||
<source>Compare security codes with your contacts.</source>
|
||||
<target>Compare security codes with your contacts.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Configure ICE servers" xml:space="preserve">
|
||||
<source>Configure ICE servers</source>
|
||||
<target>Configure ICE servers</target>
|
||||
@@ -1521,6 +1546,11 @@
|
||||
<target>Full name:</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="GIFs and stickers" xml:space="preserve">
|
||||
<source>GIFs and stickers</source>
|
||||
<target>GIFs and stickers</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group" xml:space="preserve">
|
||||
<source>Group</source>
|
||||
<target>Group</target>
|
||||
@@ -1561,6 +1591,11 @@
|
||||
<target>Group link</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group links" xml:space="preserve">
|
||||
<source>Group links</source>
|
||||
<target>Group links</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group members can irreversibly delete sent messages." xml:space="preserve">
|
||||
<source>Group members can irreversibly delete sent messages.</source>
|
||||
<target>Group members can irreversibly delete sent messages.</target>
|
||||
@@ -1626,6 +1661,11 @@
|
||||
<target>Hide</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Hide app screen in the recent apps." xml:space="preserve">
|
||||
<source>Hide app screen in the recent apps.</source>
|
||||
<target>Hide app screen in the recent apps.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="How SimpleX works" xml:space="preserve">
|
||||
<source>How SimpleX works</source>
|
||||
<target>How SimpleX works</target>
|
||||
@@ -1646,11 +1686,6 @@
|
||||
<target>How to use it</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="How to use markdown" xml:space="preserve">
|
||||
<source>How to use markdown</source>
|
||||
<target>How to use markdown</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="How to use your servers" xml:space="preserve">
|
||||
<source>How to use your servers</source>
|
||||
<target>How to use your servers</target>
|
||||
@@ -1711,6 +1746,16 @@
|
||||
<target>Import database</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Improved privacy and security" xml:space="preserve">
|
||||
<source>Improved privacy and security</source>
|
||||
<target>Improved privacy and security</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Improved server configuration" xml:space="preserve">
|
||||
<source>Improved server configuration</source>
|
||||
<target>Improved server configuration</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incognito" xml:space="preserve">
|
||||
<source>Incognito</source>
|
||||
<target>Incognito</target>
|
||||
@@ -1793,6 +1838,11 @@
|
||||
<target>Invite to group</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Irreversible message deletion" xml:space="preserve">
|
||||
<source>Irreversible message deletion</source>
|
||||
<target>Irreversible message deletion</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Irreversible message deletion is prohibited in this chat." xml:space="preserve">
|
||||
<source>Irreversible message deletion is prohibited in this chat.</source>
|
||||
<target>Irreversible message deletion is prohibited in this chat.</target>
|
||||
@@ -1893,6 +1943,11 @@ We will be adding server redundancy to prevent lost messages.</target>
|
||||
<target>Live message!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Live messages" xml:space="preserve">
|
||||
<source>Live messages</source>
|
||||
<target>Live messages</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Local name" xml:space="preserve">
|
||||
<source>Local name</source>
|
||||
<target>Local name</target>
|
||||
@@ -1938,6 +1993,11 @@ We will be adding server redundancy to prevent lost messages.</target>
|
||||
<target>Markdown in messages</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Max 30 seconds, received instantly." xml:space="preserve">
|
||||
<source>Max 30 seconds, received instantly.</source>
|
||||
<target>Max 30 seconds, received instantly.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member" xml:space="preserve">
|
||||
<source>Member</source>
|
||||
<target>Member</target>
|
||||
@@ -2038,6 +2098,11 @@ We will be adding server redundancy to prevent lost messages.</target>
|
||||
<target>New database archive</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="New in %@" xml:space="preserve">
|
||||
<source>New in %@</source>
|
||||
<target>New in %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="New member role" xml:space="preserve">
|
||||
<source>New member role</source>
|
||||
<target>New member role</target>
|
||||
@@ -2383,6 +2448,11 @@ We will be adding server redundancy to prevent lost messages.</target>
|
||||
<target>Receiving via</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Recipients see updates as you type them." xml:space="preserve">
|
||||
<source>Recipients see updates as you type them.</source>
|
||||
<target>Recipients see updates as you type them.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reject" xml:space="preserve">
|
||||
<source>Reject</source>
|
||||
<target>Reject</target>
|
||||
@@ -2593,6 +2663,11 @@ We will be adding server redundancy to prevent lost messages.</target>
|
||||
<target>Secure queue</target>
|
||||
<note>server test step</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Security assessment" xml:space="preserve">
|
||||
<source>Security assessment</source>
|
||||
<target>Security assessment</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Security code" xml:space="preserve">
|
||||
<source>Security code</source>
|
||||
<target>Security code</target>
|
||||
@@ -2638,6 +2713,11 @@ We will be adding server redundancy to prevent lost messages.</target>
|
||||
<target>Send questions and ideas</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Send them from gallery or custom keyboards." xml:space="preserve">
|
||||
<source>Send them from gallery or custom keyboards.</source>
|
||||
<target>Send them from gallery or custom keyboards.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sender cancelled file transfer." xml:space="preserve">
|
||||
<source>Sender cancelled file transfer.</source>
|
||||
<target>Sender cancelled file transfer.</target>
|
||||
@@ -2658,6 +2738,11 @@ We will be adding server redundancy to prevent lost messages.</target>
|
||||
<target>Sent file event</target>
|
||||
<note>notification</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sent messages will be deleted after set time." xml:space="preserve">
|
||||
<source>Sent messages will be deleted after set time.</source>
|
||||
<target>Sent messages will be deleted after set time.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Server requires authorization to create queues, check password" xml:space="preserve">
|
||||
<source>Server requires authorization to create queues, check password</source>
|
||||
<target>Server requires authorization to create queues, check password</target>
|
||||
@@ -2673,6 +2758,11 @@ We will be adding server redundancy to prevent lost messages.</target>
|
||||
<target>Servers</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Set 1 day" xml:space="preserve">
|
||||
<source>Set 1 day</source>
|
||||
<target>Set 1 day</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Set contact name…" xml:space="preserve">
|
||||
<source>Set contact name…</source>
|
||||
<target>Set contact name…</target>
|
||||
@@ -2728,6 +2818,11 @@ We will be adding server redundancy to prevent lost messages.</target>
|
||||
<target>Show preview</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="SimpleX Chat security was [audited by Trail of Bits](https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html)." xml:space="preserve">
|
||||
<source>SimpleX Chat security was [audited by Trail of Bits](https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html).</source>
|
||||
<target>SimpleX Chat security was [audited by Trail of Bits](https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html).</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="SimpleX Lock" xml:space="preserve">
|
||||
<source>SimpleX Lock</source>
|
||||
<target>SimpleX Lock</target>
|
||||
@@ -3177,6 +3272,11 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>Using SimpleX Chat servers.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify connection security" xml:space="preserve">
|
||||
<source>Verify connection security</source>
|
||||
<target>Verify connection security</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify security code" xml:space="preserve">
|
||||
<source>Verify security code</source>
|
||||
<target>Verify security code</target>
|
||||
@@ -3247,6 +3347,11 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>Welcome message</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="What's new" xml:space="preserve">
|
||||
<source>What's new</source>
|
||||
<target>What's new</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="When available" xml:space="preserve">
|
||||
<source>When available</source>
|
||||
<target>When available</target>
|
||||
@@ -3257,6 +3362,11 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>When you share an incognito profile with somebody, this profile will be used for the groups they invite you to.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="With optional welcome message." xml:space="preserve">
|
||||
<source>With optional welcome message.</source>
|
||||
<target>With optional welcome message.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Wrong database passphrase" xml:space="preserve">
|
||||
<source>Wrong database passphrase</source>
|
||||
<target>Wrong database passphrase</target>
|
||||
@@ -3499,6 +3609,11 @@ You can cancel this connection and remove the contact (and try later with a new
|
||||
<target>Your contact sent a file that is larger than currently supported maximum size (%@).</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your contacts can allow full message deletion." xml:space="preserve">
|
||||
<source>Your contacts can allow full message deletion.</source>
|
||||
<target>Your contacts can allow full message deletion.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your current chat database will be DELETED and REPLACED with the imported one." xml:space="preserve">
|
||||
<source>Your current chat database will be DELETED and REPLACED with the imported one.</source>
|
||||
<target>Your current chat database will be DELETED and REPLACED with the imported one.</target>
|
||||
@@ -3631,6 +3746,11 @@ SimpleX servers cannot see your profile.</target>
|
||||
<target>calling…</target>
|
||||
<note>call status</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="cancelled %@" xml:space="preserve">
|
||||
<source>cancelled %@</source>
|
||||
<target>cancelled %@</target>
|
||||
<note>feature offered item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="changed address for you" xml:space="preserve">
|
||||
<source>changed address for you</source>
|
||||
<target>changed address for you</target>
|
||||
@@ -3836,6 +3956,21 @@ SimpleX servers cannot see your profile.</target>
|
||||
<target>indirect (%d)</target>
|
||||
<note>connection level description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="invalid chat" xml:space="preserve">
|
||||
<source>invalid chat</source>
|
||||
<target>invalid chat</target>
|
||||
<note>invalid chat data</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="invalid chat data" xml:space="preserve">
|
||||
<source>invalid chat data</source>
|
||||
<target>invalid chat data</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="invalid data" xml:space="preserve">
|
||||
<source>invalid data</source>
|
||||
<target>invalid data</target>
|
||||
<note>invalid chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="invitation to group %@" xml:space="preserve">
|
||||
<source>invitation to group %@</source>
|
||||
<target>invitation to group %@</target>
|
||||
@@ -3927,6 +4062,16 @@ SimpleX servers cannot see your profile.</target>
|
||||
<note>enabled status
|
||||
group pref value</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="offered %@" xml:space="preserve">
|
||||
<source>offered %@</source>
|
||||
<target>offered %@</target>
|
||||
<note>feature offered item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="offered %@: %@" xml:space="preserve">
|
||||
<source>offered %1$@: %2$@</source>
|
||||
<target>offered %1$@: %2$@</target>
|
||||
<note>feature offered item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="on" xml:space="preserve">
|
||||
<source>on</source>
|
||||
<target>on</target>
|
||||
|
||||
@@ -17,6 +17,11 @@
|
||||
<target> </target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id=" " xml:space="preserve">
|
||||
<source> </source>
|
||||
<target> </target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id=" " xml:space="preserve">
|
||||
<source> </source>
|
||||
<target> </target>
|
||||
@@ -154,7 +159,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="%lldm" xml:space="preserve">
|
||||
<source>%lldm</source>
|
||||
<target>%lldm</target>
|
||||
<target>%lldmn</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lldmth" xml:space="preserve">
|
||||
@@ -348,6 +353,11 @@
|
||||
<target>Ajouter des serveurs prédéfinis</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add servers by scanning QR codes." xml:space="preserve">
|
||||
<source>Add servers by scanning QR codes.</source>
|
||||
<target>Ajoutez des serveurs en scannant des codes QR.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add server…" xml:space="preserve">
|
||||
<source>Add server…</source>
|
||||
<target>Ajouter un serveur…</target>
|
||||
@@ -358,6 +368,11 @@
|
||||
<target>Ajouter à un autre appareil</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Admins can create the links to join groups." xml:space="preserve">
|
||||
<source>Admins can create the links to join groups.</source>
|
||||
<target>Les admins peuvent créer les liens qui permettent de rejoindre les groupes.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Advanced network settings" xml:space="preserve">
|
||||
<source>Advanced network settings</source>
|
||||
<target>Paramètres réseau avancés</target>
|
||||
@@ -478,6 +493,11 @@
|
||||
<target>Authentification indisponible</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Auto-accept contact requests" xml:space="preserve">
|
||||
<source>Auto-accept contact requests</source>
|
||||
<target>Demandes de contact auto-acceptées</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Auto-accept images" xml:space="preserve">
|
||||
<source>Auto-accept images</source>
|
||||
<target>Images auto-acceptées</target>
|
||||
@@ -658,6 +678,11 @@
|
||||
<target>Couleurs</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Compare security codes with your contacts." xml:space="preserve">
|
||||
<source>Compare security codes with your contacts.</source>
|
||||
<target>Comparez les codes de sécurité avec vos contacts.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Configure ICE servers" xml:space="preserve">
|
||||
<source>Configure ICE servers</source>
|
||||
<target>Configurer les serveurs ICE</target>
|
||||
@@ -1521,6 +1546,11 @@
|
||||
<target>Nom complet :</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="GIFs and stickers" xml:space="preserve">
|
||||
<source>GIFs and stickers</source>
|
||||
<target>GIFs et stickers</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group" xml:space="preserve">
|
||||
<source>Group</source>
|
||||
<target>Groupe</target>
|
||||
@@ -1561,6 +1591,11 @@
|
||||
<target>Lien du groupe</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group links" xml:space="preserve">
|
||||
<source>Group links</source>
|
||||
<target>Liens de groupe</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group members can irreversibly delete sent messages." xml:space="preserve">
|
||||
<source>Group members can irreversibly delete sent messages.</source>
|
||||
<target>Les membres du groupe peuvent supprimer de manière irréversible les messages envoyés.</target>
|
||||
@@ -1626,6 +1661,11 @@
|
||||
<target>Cacher</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Hide app screen in the recent apps." xml:space="preserve">
|
||||
<source>Hide app screen in the recent apps.</source>
|
||||
<target>Masquer l'écran de l'app dans les apps récentes.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="How SimpleX works" xml:space="preserve">
|
||||
<source>How SimpleX works</source>
|
||||
<target>Comment SimpleX fonctionne</target>
|
||||
@@ -1646,11 +1686,6 @@
|
||||
<target>Comment l'utiliser</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="How to use markdown" xml:space="preserve">
|
||||
<source>How to use markdown</source>
|
||||
<target>Comment utiliser markdown</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="How to use your servers" xml:space="preserve">
|
||||
<source>How to use your servers</source>
|
||||
<target>Comment utiliser vos serveurs</target>
|
||||
@@ -1711,6 +1746,16 @@
|
||||
<target>Importer la base de données</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Improved privacy and security" xml:space="preserve">
|
||||
<source>Improved privacy and security</source>
|
||||
<target>Une meilleure sécurité et protection de la vie privée</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Improved server configuration" xml:space="preserve">
|
||||
<source>Improved server configuration</source>
|
||||
<target>Configuration de serveur améliorée</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incognito" xml:space="preserve">
|
||||
<source>Incognito</source>
|
||||
<target>Incognito</target>
|
||||
@@ -1793,6 +1838,11 @@
|
||||
<target>Inviter au groupe</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Irreversible message deletion" xml:space="preserve">
|
||||
<source>Irreversible message deletion</source>
|
||||
<target>Suppression irréversible des messages</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Irreversible message deletion is prohibited in this chat." xml:space="preserve">
|
||||
<source>Irreversible message deletion is prohibited in this chat.</source>
|
||||
<target>La suppression irréversible de message est interdite dans ce chat.</target>
|
||||
@@ -1893,6 +1943,11 @@ Nous allons ajouter une redondance des serveurs pour éviter la perte de message
|
||||
<target>Message dynamique !</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Live messages" xml:space="preserve">
|
||||
<source>Live messages</source>
|
||||
<target>Messages dynamiques</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Local name" xml:space="preserve">
|
||||
<source>Local name</source>
|
||||
<target>Nom local</target>
|
||||
@@ -1915,7 +1970,7 @@ Nous allons ajouter une redondance des serveurs pour éviter la perte de message
|
||||
</trans-unit>
|
||||
<trans-unit id="Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" xml:space="preserve">
|
||||
<source>Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*</source>
|
||||
<target>Beaucoup de gens ont demandé : *si SimpleX n'a pas d'identifiants d'utilisateur, comment peut-il délivrer des messages?*</target>
|
||||
<target>Beaucoup se demandent : *si SimpleX n'a pas d'identifiants d'utilisateur, comment peut-il délivrer des messages?*</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Mark deleted for everyone" xml:space="preserve">
|
||||
@@ -1938,6 +1993,11 @@ Nous allons ajouter une redondance des serveurs pour éviter la perte de message
|
||||
<target>Markdown dans les messages</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Max 30 seconds, received instantly." xml:space="preserve">
|
||||
<source>Max 30 seconds, received instantly.</source>
|
||||
<target>Max 30 secondes, réception immédiate.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member" xml:space="preserve">
|
||||
<source>Member</source>
|
||||
<target>Membre</target>
|
||||
@@ -2038,6 +2098,11 @@ Nous allons ajouter une redondance des serveurs pour éviter la perte de message
|
||||
<target>Nouvelle archive de base de données</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="New in %@" xml:space="preserve">
|
||||
<source>New in %@</source>
|
||||
<target>Nouveautés de la %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="New member role" xml:space="preserve">
|
||||
<source>New member role</source>
|
||||
<target>Nouveau rôle</target>
|
||||
@@ -2200,7 +2265,7 @@ Nous allons ajouter une redondance des serveurs pour éviter la perte de message
|
||||
</trans-unit>
|
||||
<trans-unit id="Open-source protocol and code – anybody can run the servers." xml:space="preserve">
|
||||
<source>Open-source protocol and code – anybody can run the servers.</source>
|
||||
<target>Protocole et code open-source – tout le monde peut faire fonctionner les serveurs.</target>
|
||||
<target>Protocole et code open-source – n'importe qui peut heberger un serveur.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red." xml:space="preserve">
|
||||
@@ -2383,6 +2448,11 @@ Nous allons ajouter une redondance des serveurs pour éviter la perte de message
|
||||
<target>Réception via</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Recipients see updates as you type them." xml:space="preserve">
|
||||
<source>Recipients see updates as you type them.</source>
|
||||
<target>Les destinataires voient les mises à jour au fur et à mesure que vous les tapez.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reject" xml:space="preserve">
|
||||
<source>Reject</source>
|
||||
<target>Rejeter</target>
|
||||
@@ -2580,7 +2650,7 @@ Nous allons ajouter une redondance des serveurs pour éviter la perte de message
|
||||
</trans-unit>
|
||||
<trans-unit id="Scan server QR code" xml:space="preserve">
|
||||
<source>Scan server QR code</source>
|
||||
<target>Scanner le code QR du serveur</target>
|
||||
<target>Scanner un code QR de serveur</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search" xml:space="preserve">
|
||||
@@ -2593,6 +2663,11 @@ Nous allons ajouter une redondance des serveurs pour éviter la perte de message
|
||||
<target>File d'attente sécurisée</target>
|
||||
<note>server test step</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Security assessment" xml:space="preserve">
|
||||
<source>Security assessment</source>
|
||||
<target>Évaluation de sécurité</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Security code" xml:space="preserve">
|
||||
<source>Security code</source>
|
||||
<target>Code de sécurité</target>
|
||||
@@ -2638,6 +2713,11 @@ Nous allons ajouter une redondance des serveurs pour éviter la perte de message
|
||||
<target>Envoyez vos questions et idées</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Send them from gallery or custom keyboards." xml:space="preserve">
|
||||
<source>Send them from gallery or custom keyboards.</source>
|
||||
<target>Envoyez-les depuis la phototèque ou des claviers personnalisés.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sender cancelled file transfer." xml:space="preserve">
|
||||
<source>Sender cancelled file transfer.</source>
|
||||
<target>L'expéditeur a annulé le transfert de fichiers.</target>
|
||||
@@ -2658,6 +2738,11 @@ Nous allons ajouter une redondance des serveurs pour éviter la perte de message
|
||||
<target>Événement de fichier envoyé</target>
|
||||
<note>notification</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sent messages will be deleted after set time." xml:space="preserve">
|
||||
<source>Sent messages will be deleted after set time.</source>
|
||||
<target>Les messages envoyés seront supprimés après une durée déterminée.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Server requires authorization to create queues, check password" xml:space="preserve">
|
||||
<source>Server requires authorization to create queues, check password</source>
|
||||
<target>Le serveur requiert une autorisation pour créer des files d'attente, vérifiez le mot de passe</target>
|
||||
@@ -2673,6 +2758,11 @@ Nous allons ajouter une redondance des serveurs pour éviter la perte de message
|
||||
<target>Serveurs</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Set 1 day" xml:space="preserve">
|
||||
<source>Set 1 day</source>
|
||||
<target>Définir 1 jour</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Set contact name…" xml:space="preserve">
|
||||
<source>Set contact name…</source>
|
||||
<target>Définir le nom du contact…</target>
|
||||
@@ -2728,6 +2818,11 @@ Nous allons ajouter une redondance des serveurs pour éviter la perte de message
|
||||
<target>Montrer l'aperçu</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="SimpleX Chat security was [audited by Trail of Bits](https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html)." xml:space="preserve">
|
||||
<source>SimpleX Chat security was [audited by Trail of Bits](https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html).</source>
|
||||
<target>La sécurité de SimpleX Chat a été [auditée par Trail of Bits](https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html).</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="SimpleX Lock" xml:space="preserve">
|
||||
<source>SimpleX Lock</source>
|
||||
<target>SimpleX Lock</target>
|
||||
@@ -2990,7 +3085,7 @@ Nous allons ajouter une redondance des serveurs pour éviter la perte de message
|
||||
</trans-unit>
|
||||
<trans-unit id="To ask any questions and to receive updates:" xml:space="preserve">
|
||||
<source>To ask any questions and to receive updates:</source>
|
||||
<target>Pour poser toute question et recevoir des mises à jour :</target>
|
||||
<target>Si vous avez des questions et que vous souhaitez des réponses :</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="To find the profile used for an incognito connection, tap the contact or group name on top of the chat." xml:space="preserve">
|
||||
@@ -3010,7 +3105,7 @@ Nous allons ajouter une redondance des serveurs pour éviter la perte de message
|
||||
</trans-unit>
|
||||
<trans-unit id="To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." xml:space="preserve">
|
||||
<source>To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts.</source>
|
||||
<target>Pour protéger la vie privée, au lieu des ID utilisateurs utilisés par toutes les autres plateformes, SimpleX a des ID pour les queues de messages, distinctes pour chacun de vos contacts.</target>
|
||||
<target>Pour protéger votre vie privée, au lieu d’IDs utilisés par toutes les autres plateformes, SimpleX a des IDs pour les queues de messages, distinctes pour chacun de vos contacts.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="To protect your information, turn on SimpleX Lock. You will be prompted to complete authentication before this feature is enabled." xml:space="preserve">
|
||||
@@ -3177,6 +3272,11 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien
|
||||
<target>Utilisation des serveurs SimpleX Chat.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify connection security" xml:space="preserve">
|
||||
<source>Verify connection security</source>
|
||||
<target>Vérifier la sécurité de la connexion</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify security code" xml:space="preserve">
|
||||
<source>Verify security code</source>
|
||||
<target>Vérifier le code de sécurité</target>
|
||||
@@ -3247,6 +3347,11 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien
|
||||
<target>Message de bienvenue</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="What's new" xml:space="preserve">
|
||||
<source>What's new</source>
|
||||
<target>Quoi de neuf ?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="When available" xml:space="preserve">
|
||||
<source>When available</source>
|
||||
<target>Quand disponible</target>
|
||||
@@ -3257,6 +3362,11 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien
|
||||
<target>Lorsque vous partagez un profil incognito avec quelqu'un, ce profil sera utilisé pour les groupes auxquels il vous invite.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="With optional welcome message." xml:space="preserve">
|
||||
<source>With optional welcome message.</source>
|
||||
<target>Avec message de bienvenue facultatif.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Wrong database passphrase" xml:space="preserve">
|
||||
<source>Wrong database passphrase</source>
|
||||
<target>Mauvaise phrase secrète pour la base de données</target>
|
||||
@@ -3319,7 +3429,7 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien
|
||||
</trans-unit>
|
||||
<trans-unit id="You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it." xml:space="preserve">
|
||||
<source>You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it.</source>
|
||||
<target>Vous pouvez partager votre adresse sous forme de lien ou de code QR - n'importe qui pourra se connecter à vous. Vous ne perdrez pas vos contacts si vous les supprimez par la suite.</target>
|
||||
<target>Vous pouvez partager votre adresse sous forme de lien ou de code QR - n'importe qui pourra se connecter à vous. Vous ne perdrez pas vos contacts si vous la supprimez par la suite.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can start chat via app Settings / Database or by restarting the app" xml:space="preserve">
|
||||
@@ -3334,7 +3444,7 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien
|
||||
</trans-unit>
|
||||
<trans-unit id="You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them." xml:space="preserve">
|
||||
<source>You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them.</source>
|
||||
<target>Vous contrôlez par quel·s serveur·s vous pouvez **transmettre** ainsi que par quel·s serveur·s vous pouvez **recevoir** des messages de vos contacts.</target>
|
||||
<target>Vous contrôlez par quel·s serveur·s vous pouvez **transmettre** ainsi que par quel·s serveur·s vous pouvez **recevoir** les messages de vos contacts.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You could not be verified; please try again." xml:space="preserve">
|
||||
@@ -3499,6 +3609,11 @@ Vous pouvez annuler la connexion et supprimer le contact (et réessayer plus tar
|
||||
<target>Votre contact a envoyé un fichier plus grand que la taille maximale supportée actuellement(%@).</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your contacts can allow full message deletion." xml:space="preserve">
|
||||
<source>Your contacts can allow full message deletion.</source>
|
||||
<target>Vos contacts peuvent autoriser la suppression complète des messages.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your current chat database will be DELETED and REPLACED with the imported one." xml:space="preserve">
|
||||
<source>Your current chat database will be DELETED and REPLACED with the imported one.</source>
|
||||
<target>Votre base de données de chat actuelle va être SUPPRIMEE et REMPLACEE par celle importée.</target>
|
||||
@@ -3631,6 +3746,10 @@ Les serveurs SimpleX ne peuvent pas voir votre profil.</target>
|
||||
<target>appel…</target>
|
||||
<note>call status</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="cancelled %@" xml:space="preserve">
|
||||
<source>cancelled %@</source>
|
||||
<note>feature offered item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="changed address for you" xml:space="preserve">
|
||||
<source>changed address for you</source>
|
||||
<target>adresse modifiée pour vous</target>
|
||||
@@ -3836,6 +3955,21 @@ Les serveurs SimpleX ne peuvent pas voir votre profil.</target>
|
||||
<target>indirect (%d)</target>
|
||||
<note>connection level description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="invalid chat" xml:space="preserve">
|
||||
<source>invalid chat</source>
|
||||
<target>chat invalide</target>
|
||||
<note>invalid chat data</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="invalid chat data" xml:space="preserve">
|
||||
<source>invalid chat data</source>
|
||||
<target>données de chat invalides</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="invalid data" xml:space="preserve">
|
||||
<source>invalid data</source>
|
||||
<target>données invalides</target>
|
||||
<note>invalid chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="invitation to group %@" xml:space="preserve">
|
||||
<source>invitation to group %@</source>
|
||||
<target>invitation au groupe %@</target>
|
||||
@@ -3878,7 +4012,7 @@ Les serveurs SimpleX ne peuvent pas voir votre profil.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="marked deleted" xml:space="preserve">
|
||||
<source>marked deleted</source>
|
||||
<target>marquer comme supprimé</target>
|
||||
<target>supprimé</target>
|
||||
<note>marked deleted chat item preview text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="member" xml:space="preserve">
|
||||
@@ -3927,6 +4061,14 @@ Les serveurs SimpleX ne peuvent pas voir votre profil.</target>
|
||||
<note>enabled status
|
||||
group pref value</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="offered %@" xml:space="preserve">
|
||||
<source>offered %@</source>
|
||||
<note>feature offered item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="offered %@: %@" xml:space="preserve">
|
||||
<source>offered %1$@: %2$@</source>
|
||||
<note>feature offered item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="on" xml:space="preserve">
|
||||
<source>on</source>
|
||||
<target>on</target>
|
||||
|
||||
@@ -17,6 +17,11 @@
|
||||
<target> </target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id=" " xml:space="preserve">
|
||||
<source> </source>
|
||||
<target> </target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id=" " xml:space="preserve">
|
||||
<source> </source>
|
||||
<target> </target>
|
||||
@@ -348,6 +353,11 @@
|
||||
<target>Добавить серверы по умолчанию</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add servers by scanning QR codes." xml:space="preserve">
|
||||
<source>Add servers by scanning QR codes.</source>
|
||||
<target>Добавить серверы через QR код.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add server…" xml:space="preserve">
|
||||
<source>Add server…</source>
|
||||
<target>Добавить сервер…</target>
|
||||
@@ -358,6 +368,11 @@
|
||||
<target>Добавить на другое устройство</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Admins can create the links to join groups." xml:space="preserve">
|
||||
<source>Admins can create the links to join groups.</source>
|
||||
<target>Админы могут создать ссылки для вступления в группу.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Advanced network settings" xml:space="preserve">
|
||||
<source>Advanced network settings</source>
|
||||
<target>Настройки сети</target>
|
||||
@@ -478,6 +493,11 @@
|
||||
<target>Аутентификация недоступна</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Auto-accept contact requests" xml:space="preserve">
|
||||
<source>Auto-accept contact requests</source>
|
||||
<target>Автоматически принимать запросы контактов</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Auto-accept images" xml:space="preserve">
|
||||
<source>Auto-accept images</source>
|
||||
<target>Автоприем изображений</target>
|
||||
@@ -658,6 +678,11 @@
|
||||
<target>Цвета</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Compare security codes with your contacts." xml:space="preserve">
|
||||
<source>Compare security codes with your contacts.</source>
|
||||
<target>Сравните код безопасности с вашими контактами.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Configure ICE servers" xml:space="preserve">
|
||||
<source>Configure ICE servers</source>
|
||||
<target>Настройка ICE серверов</target>
|
||||
@@ -1521,6 +1546,11 @@
|
||||
<target>Полное имя:</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="GIFs and stickers" xml:space="preserve">
|
||||
<source>GIFs and stickers</source>
|
||||
<target>ГИФ файлы и стикеры</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group" xml:space="preserve">
|
||||
<source>Group</source>
|
||||
<target>Группа</target>
|
||||
@@ -1561,6 +1591,11 @@
|
||||
<target>Ссылка группы</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group links" xml:space="preserve">
|
||||
<source>Group links</source>
|
||||
<target>Ссылки групп</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group members can irreversibly delete sent messages." xml:space="preserve">
|
||||
<source>Group members can irreversibly delete sent messages.</source>
|
||||
<target>Члены группы могут необратимо удалять отправленные сообщения.</target>
|
||||
@@ -1626,6 +1661,11 @@
|
||||
<target>Спрятать</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Hide app screen in the recent apps." xml:space="preserve">
|
||||
<source>Hide app screen in the recent apps.</source>
|
||||
<target>Скрыть экран приложения.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="How SimpleX works" xml:space="preserve">
|
||||
<source>How SimpleX works</source>
|
||||
<target>Как SimpleX работает</target>
|
||||
@@ -1646,11 +1686,6 @@
|
||||
<target>Как использовать</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="How to use markdown" xml:space="preserve">
|
||||
<source>How to use markdown</source>
|
||||
<target>Как форматировать</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="How to use your servers" xml:space="preserve">
|
||||
<source>How to use your servers</source>
|
||||
<target>Как использовать серверы</target>
|
||||
@@ -1711,6 +1746,16 @@
|
||||
<target>Импорт архива чата</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Improved privacy and security" xml:space="preserve">
|
||||
<source>Improved privacy and security</source>
|
||||
<target>Улучшенная безопасность</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Improved server configuration" xml:space="preserve">
|
||||
<source>Improved server configuration</source>
|
||||
<target>Улучшенная конфигурация серверов</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incognito" xml:space="preserve">
|
||||
<source>Incognito</source>
|
||||
<target>Инкогнито</target>
|
||||
@@ -1793,6 +1838,11 @@
|
||||
<target>Пригласить в группу</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Irreversible message deletion" xml:space="preserve">
|
||||
<source>Irreversible message deletion</source>
|
||||
<target>Окончательное удаление сообщений</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Irreversible message deletion is prohibited in this chat." xml:space="preserve">
|
||||
<source>Irreversible message deletion is prohibited in this chat.</source>
|
||||
<target>Необратимое удаление сообщений запрещено в этом чате.</target>
|
||||
@@ -1893,6 +1943,11 @@ We will be adding server redundancy to prevent lost messages.</source>
|
||||
<target>Живое сообщение!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Live messages" xml:space="preserve">
|
||||
<source>Live messages</source>
|
||||
<target>"Живые" сообщения</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Local name" xml:space="preserve">
|
||||
<source>Local name</source>
|
||||
<target>Локальное имя</target>
|
||||
@@ -1938,6 +1993,11 @@ We will be adding server redundancy to prevent lost messages.</source>
|
||||
<target>Форматирование сообщений</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Max 30 seconds, received instantly." xml:space="preserve">
|
||||
<source>Max 30 seconds, received instantly.</source>
|
||||
<target>Макс. 30 секунд, доставляются мгновенно.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member" xml:space="preserve">
|
||||
<source>Member</source>
|
||||
<target>Член группы</target>
|
||||
@@ -2038,6 +2098,11 @@ We will be adding server redundancy to prevent lost messages.</source>
|
||||
<target>Новый архив чата</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="New in %@" xml:space="preserve">
|
||||
<source>New in %@</source>
|
||||
<target>Новое в %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="New member role" xml:space="preserve">
|
||||
<source>New member role</source>
|
||||
<target>Роль члена группы</target>
|
||||
@@ -2383,6 +2448,11 @@ We will be adding server redundancy to prevent lost messages.</source>
|
||||
<target>Получение через</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Recipients see updates as you type them." xml:space="preserve">
|
||||
<source>Recipients see updates as you type them.</source>
|
||||
<target>Получатели видят их в то время как вы их набираете.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reject" xml:space="preserve">
|
||||
<source>Reject</source>
|
||||
<target>Отклонить</target>
|
||||
@@ -2593,6 +2663,11 @@ We will be adding server redundancy to prevent lost messages.</source>
|
||||
<target>Защита очереди</target>
|
||||
<note>server test step</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Security assessment" xml:space="preserve">
|
||||
<source>Security assessment</source>
|
||||
<target>Аудит безопасности</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Security code" xml:space="preserve">
|
||||
<source>Security code</source>
|
||||
<target>Код безопасности</target>
|
||||
@@ -2638,6 +2713,11 @@ We will be adding server redundancy to prevent lost messages.</source>
|
||||
<target>Отправьте вопросы и идеи</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Send them from gallery or custom keyboards." xml:space="preserve">
|
||||
<source>Send them from gallery or custom keyboards.</source>
|
||||
<target>Отправьте из галереи или из дополнительных клавиатур.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sender cancelled file transfer." xml:space="preserve">
|
||||
<source>Sender cancelled file transfer.</source>
|
||||
<target>Отправитель отменил передачу файла.</target>
|
||||
@@ -2658,6 +2738,11 @@ We will be adding server redundancy to prevent lost messages.</source>
|
||||
<target>Отправка файла</target>
|
||||
<note>notification</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sent messages will be deleted after set time." xml:space="preserve">
|
||||
<source>Sent messages will be deleted after set time.</source>
|
||||
<target>Отправленные сообщения будут удалены через заданное время.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Server requires authorization to create queues, check password" xml:space="preserve">
|
||||
<source>Server requires authorization to create queues, check password</source>
|
||||
<target>Сервер требует авторизации для создания очередей, проверьте пароль</target>
|
||||
@@ -2673,6 +2758,11 @@ We will be adding server redundancy to prevent lost messages.</source>
|
||||
<target>Серверы</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Set 1 day" xml:space="preserve">
|
||||
<source>Set 1 day</source>
|
||||
<target>Установить 1 день</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Set contact name…" xml:space="preserve">
|
||||
<source>Set contact name…</source>
|
||||
<target>Имя контакта…</target>
|
||||
@@ -2728,6 +2818,11 @@ We will be adding server redundancy to prevent lost messages.</source>
|
||||
<target>Показывать уведомления</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="SimpleX Chat security was [audited by Trail of Bits](https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html)." xml:space="preserve">
|
||||
<source>SimpleX Chat security was [audited by Trail of Bits](https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html).</source>
|
||||
<target>Безопасность SimpleX Chat была [проверена Trail of Bits](https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html).</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="SimpleX Lock" xml:space="preserve">
|
||||
<source>SimpleX Lock</source>
|
||||
<target>Блокировка SimpleX</target>
|
||||
@@ -3177,6 +3272,11 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>Используются серверы, предоставленные SimpleX Chat.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify connection security" xml:space="preserve">
|
||||
<source>Verify connection security</source>
|
||||
<target>Проверить безопасность соединения</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify security code" xml:space="preserve">
|
||||
<source>Verify security code</source>
|
||||
<target>Подтвердить код безопасности</target>
|
||||
@@ -3247,6 +3347,11 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>Приветственное сообщение</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="What's new" xml:space="preserve">
|
||||
<source>What's new</source>
|
||||
<target>Новые функции</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="When available" xml:space="preserve">
|
||||
<source>When available</source>
|
||||
<target>Когда возможно</target>
|
||||
@@ -3257,6 +3362,11 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>Когда вы соединены с контактом инкогнито, тот же самый инкогнито профиль будет использоваться для групп с этим контактом.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="With optional welcome message." xml:space="preserve">
|
||||
<source>With optional welcome message.</source>
|
||||
<target>С опциональным авто-ответом.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Wrong database passphrase" xml:space="preserve">
|
||||
<source>Wrong database passphrase</source>
|
||||
<target>Неправильный пароль базы данных</target>
|
||||
@@ -3499,6 +3609,11 @@ You can cancel this connection and remove the contact (and try later with a new
|
||||
<target>Ваш контакт отправил файл, размер которого превышает максимальный размер (%@).</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your contacts can allow full message deletion." xml:space="preserve">
|
||||
<source>Your contacts can allow full message deletion.</source>
|
||||
<target>Ваши контакты могут разрешить окончательное удаление сообщений.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your current chat database will be DELETED and REPLACED with the imported one." xml:space="preserve">
|
||||
<source>Your current chat database will be DELETED and REPLACED with the imported one.</source>
|
||||
<target>Текущие данные вашего чата будет УДАЛЕНЫ и ЗАМЕНЕНЫ импортированными.</target>
|
||||
@@ -3631,6 +3746,11 @@ SimpleX серверы не могут получить доступ к ваше
|
||||
<target>входящий звонок…</target>
|
||||
<note>call status</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="cancelled %@" xml:space="preserve">
|
||||
<source>cancelled %@</source>
|
||||
<target>отменил(a) %@</target>
|
||||
<note>feature offered item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="changed address for you" xml:space="preserve">
|
||||
<source>changed address for you</source>
|
||||
<target>поменял(а) адрес для вас</target>
|
||||
@@ -3836,6 +3956,21 @@ SimpleX серверы не могут получить доступ к ваше
|
||||
<target>непрямое (%d)</target>
|
||||
<note>connection level description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="invalid chat" xml:space="preserve">
|
||||
<source>invalid chat</source>
|
||||
<target>ошибка чата</target>
|
||||
<note>invalid chat data</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="invalid chat data" xml:space="preserve">
|
||||
<source>invalid chat data</source>
|
||||
<target>ошибка данных чата</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="invalid data" xml:space="preserve">
|
||||
<source>invalid data</source>
|
||||
<target>ошибка данных</target>
|
||||
<note>invalid chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="invitation to group %@" xml:space="preserve">
|
||||
<source>invitation to group %@</source>
|
||||
<target>приглашение в группу %@</target>
|
||||
@@ -3927,6 +4062,16 @@ SimpleX серверы не могут получить доступ к ваше
|
||||
<note>enabled status
|
||||
group pref value</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="offered %@" xml:space="preserve">
|
||||
<source>offered %@</source>
|
||||
<target>предложил(a) %@</target>
|
||||
<note>feature offered item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="offered %@: %@" xml:space="preserve">
|
||||
<source>offered %1$@: %2$@</source>
|
||||
<target>предложил(a) %1$@: %2$@</target>
|
||||
<note>feature offered item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="on" xml:space="preserve">
|
||||
<source>on</source>
|
||||
<target>да</target>
|
||||
|
||||
@@ -51,11 +51,6 @@
|
||||
5C6AD81327A834E300348BD7 /* NewChatButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6AD81227A834E300348BD7 /* NewChatButton.swift */; };
|
||||
5C6BA667289BD954009B8ECC /* DismissSheets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6BA666289BD954009B8ECC /* DismissSheets.swift */; };
|
||||
5C7031162953C97F00150A12 /* CIFeaturePreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7031152953C97F00150A12 /* CIFeaturePreferenceView.swift */; };
|
||||
5C70311C2955080A00150A12 /* libHSsimplex-chat-4.4.0-AHPp9UIBWT5C2IlT3cD6QO-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C7031172955080900150A12 /* libHSsimplex-chat-4.4.0-AHPp9UIBWT5C2IlT3cD6QO-ghc8.10.7.a */; };
|
||||
5C70311D2955080A00150A12 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C7031182955080900150A12 /* libgmp.a */; };
|
||||
5C70311E2955080A00150A12 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C7031192955080A00150A12 /* libffi.a */; };
|
||||
5C70311F2955080A00150A12 /* libHSsimplex-chat-4.4.0-AHPp9UIBWT5C2IlT3cD6QO.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C70311A2955080A00150A12 /* libHSsimplex-chat-4.4.0-AHPp9UIBWT5C2IlT3cD6QO.a */; };
|
||||
5C7031202955080A00150A12 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C70311B2955080A00150A12 /* libgmpxx.a */; };
|
||||
5C7505A227B65FDB00BE3227 /* CIMetaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7505A127B65FDB00BE3227 /* CIMetaView.swift */; };
|
||||
5C7505A527B679EE00BE3227 /* NavLinkPlain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7505A427B679EE00BE3227 /* NavLinkPlain.swift */; };
|
||||
5C7505A827B6D34800BE3227 /* ChatInfoToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7505A727B6D34800BE3227 /* ChatInfoToolbar.swift */; };
|
||||
@@ -81,6 +76,11 @@
|
||||
5CA059ED279559F40002BEB4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA059C4279559F40002BEB4 /* ContentView.swift */; };
|
||||
5CA059EF279559F40002BEB4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5CA059C5279559F40002BEB4 /* Assets.xcassets */; };
|
||||
5CA7DFC329302AF000F7FDDE /* AppSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA7DFC229302AF000F7FDDE /* AppSheet.swift */; };
|
||||
5CA85D05296F151E0095AF72 /* libHSsimplex-chat-4.4.1-5apk6NMi0TsBQXmy6Jpqfi-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CA85D00296F151E0095AF72 /* libHSsimplex-chat-4.4.1-5apk6NMi0TsBQXmy6Jpqfi-ghc8.10.7.a */; };
|
||||
5CA85D06296F151E0095AF72 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CA85D01296F151E0095AF72 /* libffi.a */; };
|
||||
5CA85D07296F151E0095AF72 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CA85D02296F151E0095AF72 /* libgmpxx.a */; };
|
||||
5CA85D08296F151E0095AF72 /* libHSsimplex-chat-4.4.1-5apk6NMi0TsBQXmy6Jpqfi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CA85D03296F151E0095AF72 /* libHSsimplex-chat-4.4.1-5apk6NMi0TsBQXmy6Jpqfi.a */; };
|
||||
5CA85D09296F151E0095AF72 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CA85D04296F151E0095AF72 /* libgmp.a */; };
|
||||
5CADE79A29211BB900072E13 /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CADE79929211BB900072E13 /* PreferencesView.swift */; };
|
||||
5CADE79C292131E900072E13 /* ContactPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CADE79B292131E900072E13 /* ContactPreferencesView.swift */; };
|
||||
5CB0BA882826CB3A00B3292C /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CB0BA862826CB3A00B3292C /* InfoPlist.strings */; };
|
||||
@@ -101,6 +101,7 @@
|
||||
5CB924E427A8683A00ACCCDD /* UserAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924E327A8683A00ACCCDD /* UserAddress.swift */; };
|
||||
5CB9250D27A9432000ACCCDD /* ChatListNavLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB9250C27A9432000ACCCDD /* ChatListNavLink.swift */; };
|
||||
5CBD285A295711D700EC2CF4 /* ImageUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CBD2859295711D700EC2CF4 /* ImageUtils.swift */; };
|
||||
5CBD285C29575B8E00EC2CF4 /* WhatsNewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CBD285B29575B8E00EC2CF4 /* WhatsNewView.swift */; };
|
||||
5CBE6C12294487F7002D9531 /* VerifyCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CBE6C11294487F7002D9531 /* VerifyCodeView.swift */; };
|
||||
5CBE6C142944CC12002D9531 /* ScanCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CBE6C132944CC12002D9531 /* ScanCodeView.swift */; };
|
||||
5CC1C99227A6C7F5000D9FF6 /* QRCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC1C99127A6C7F5000D9FF6 /* QRCode.swift */; };
|
||||
@@ -137,6 +138,7 @@
|
||||
5CFA59D12864782E00863A68 /* ChatArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFA59CF286477B400863A68 /* ChatArchiveView.swift */; };
|
||||
5CFE0921282EEAF60002594B /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */; };
|
||||
5CFE0922282EEAF60002594B /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */; };
|
||||
6407BA83295DA85D0082BA18 /* CIInvalidJSONView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */; };
|
||||
6432857C2925443C00FBE5C8 /* GroupPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */; };
|
||||
6440CA00288857A10062C672 /* CIEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440C9FF288857A10062C672 /* CIEventView.swift */; };
|
||||
6440CA03288AECA70062C672 /* AddGroupMembersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440CA02288AECA70062C672 /* AddGroupMembersView.swift */; };
|
||||
@@ -266,11 +268,6 @@
|
||||
5C6AD81227A834E300348BD7 /* NewChatButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewChatButton.swift; sourceTree = "<group>"; };
|
||||
5C6BA666289BD954009B8ECC /* DismissSheets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DismissSheets.swift; sourceTree = "<group>"; };
|
||||
5C7031152953C97F00150A12 /* CIFeaturePreferenceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIFeaturePreferenceView.swift; sourceTree = "<group>"; };
|
||||
5C7031172955080900150A12 /* libHSsimplex-chat-4.4.0-AHPp9UIBWT5C2IlT3cD6QO-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-4.4.0-AHPp9UIBWT5C2IlT3cD6QO-ghc8.10.7.a"; sourceTree = "<group>"; };
|
||||
5C7031182955080900150A12 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5C7031192955080A00150A12 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5C70311A2955080A00150A12 /* libHSsimplex-chat-4.4.0-AHPp9UIBWT5C2IlT3cD6QO.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-4.4.0-AHPp9UIBWT5C2IlT3cD6QO.a"; sourceTree = "<group>"; };
|
||||
5C70311B2955080A00150A12 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5C7505A127B65FDB00BE3227 /* CIMetaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIMetaView.swift; sourceTree = "<group>"; };
|
||||
5C7505A427B679EE00BE3227 /* NavLinkPlain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavLinkPlain.swift; sourceTree = "<group>"; };
|
||||
5C7505A727B6D34800BE3227 /* ChatInfoToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatInfoToolbar.swift; sourceTree = "<group>"; };
|
||||
@@ -299,6 +296,11 @@
|
||||
5CA059DB279559F40002BEB4 /* Tests_iOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_iOS.swift; sourceTree = "<group>"; };
|
||||
5CA059DD279559F40002BEB4 /* Tests_iOSLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_iOSLaunchTests.swift; sourceTree = "<group>"; };
|
||||
5CA7DFC229302AF000F7FDDE /* AppSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSheet.swift; sourceTree = "<group>"; };
|
||||
5CA85D00296F151E0095AF72 /* libHSsimplex-chat-4.4.1-5apk6NMi0TsBQXmy6Jpqfi-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-4.4.1-5apk6NMi0TsBQXmy6Jpqfi-ghc8.10.7.a"; sourceTree = "<group>"; };
|
||||
5CA85D01296F151E0095AF72 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5CA85D02296F151E0095AF72 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5CA85D03296F151E0095AF72 /* libHSsimplex-chat-4.4.1-5apk6NMi0TsBQXmy6Jpqfi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-4.4.1-5apk6NMi0TsBQXmy6Jpqfi.a"; sourceTree = "<group>"; };
|
||||
5CA85D04296F151E0095AF72 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5CADE79929211BB900072E13 /* PreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = "<group>"; };
|
||||
5CADE79B292131E900072E13 /* ContactPreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactPreferencesView.swift; sourceTree = "<group>"; };
|
||||
5CB0BA872826CB3A00B3292C /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
@@ -325,6 +327,7 @@
|
||||
5CBD285729565D2600EC2CF4 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = "fr.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
5CBD285829565D2600EC2CF4 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
5CBD2859295711D700EC2CF4 /* ImageUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageUtils.swift; sourceTree = "<group>"; };
|
||||
5CBD285B29575B8E00EC2CF4 /* WhatsNewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewView.swift; sourceTree = "<group>"; };
|
||||
5CBE6C11294487F7002D9531 /* VerifyCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerifyCodeView.swift; sourceTree = "<group>"; };
|
||||
5CBE6C132944CC12002D9531 /* ScanCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanCodeView.swift; sourceTree = "<group>"; };
|
||||
5CC1C99127A6C7F5000D9FF6 /* QRCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCode.swift; sourceTree = "<group>"; };
|
||||
@@ -359,6 +362,7 @@
|
||||
5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateToAppGroupView.swift; sourceTree = "<group>"; };
|
||||
5CFA59CF286477B400863A68 /* ChatArchiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatArchiveView.swift; sourceTree = "<group>"; };
|
||||
5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ZoomableScrollView.swift; path = Shared/Views/ZoomableScrollView.swift; sourceTree = SOURCE_ROOT; };
|
||||
6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIInvalidJSONView.swift; sourceTree = "<group>"; };
|
||||
6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupPreferencesView.swift; sourceTree = "<group>"; };
|
||||
6440C9FF288857A10062C672 /* CIEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIEventView.swift; sourceTree = "<group>"; };
|
||||
6440CA02288AECA70062C672 /* AddGroupMembersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGroupMembersView.swift; sourceTree = "<group>"; };
|
||||
@@ -416,13 +420,13 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5C70311F2955080A00150A12 /* libHSsimplex-chat-4.4.0-AHPp9UIBWT5C2IlT3cD6QO.a in Frameworks */,
|
||||
5CA85D08296F151E0095AF72 /* libHSsimplex-chat-4.4.1-5apk6NMi0TsBQXmy6Jpqfi.a in Frameworks */,
|
||||
5CA85D09296F151E0095AF72 /* libgmp.a in Frameworks */,
|
||||
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
||||
5CA85D07296F151E0095AF72 /* libgmpxx.a in Frameworks */,
|
||||
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
||||
5C70311D2955080A00150A12 /* libgmp.a in Frameworks */,
|
||||
5C70311E2955080A00150A12 /* libffi.a in Frameworks */,
|
||||
5C70311C2955080A00150A12 /* libHSsimplex-chat-4.4.0-AHPp9UIBWT5C2IlT3cD6QO-ghc8.10.7.a in Frameworks */,
|
||||
5C7031202955080A00150A12 /* libgmpxx.a in Frameworks */,
|
||||
5CA85D05296F151E0095AF72 /* libHSsimplex-chat-4.4.1-5apk6NMi0TsBQXmy6Jpqfi-ghc8.10.7.a in Frameworks */,
|
||||
5CA85D06296F151E0095AF72 /* libffi.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -480,11 +484,11 @@
|
||||
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5C7031192955080A00150A12 /* libffi.a */,
|
||||
5C7031182955080900150A12 /* libgmp.a */,
|
||||
5C70311B2955080A00150A12 /* libgmpxx.a */,
|
||||
5C7031172955080900150A12 /* libHSsimplex-chat-4.4.0-AHPp9UIBWT5C2IlT3cD6QO-ghc8.10.7.a */,
|
||||
5C70311A2955080A00150A12 /* libHSsimplex-chat-4.4.0-AHPp9UIBWT5C2IlT3cD6QO.a */,
|
||||
5CA85D01296F151E0095AF72 /* libffi.a */,
|
||||
5CA85D04296F151E0095AF72 /* libgmp.a */,
|
||||
5CA85D02296F151E0095AF72 /* libgmpxx.a */,
|
||||
5CA85D00296F151E0095AF72 /* libHSsimplex-chat-4.4.1-5apk6NMi0TsBQXmy6Jpqfi-ghc8.10.7.a */,
|
||||
5CA85D03296F151E0095AF72 /* libHSsimplex-chat-4.4.1-5apk6NMi0TsBQXmy6Jpqfi.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
@@ -592,6 +596,7 @@
|
||||
5CB0BA992827FD8800B3292C /* HowItWorks.swift */,
|
||||
5CB0BA91282713FD00B3292C /* CreateProfile.swift */,
|
||||
5C9A5BDA2871E05400A5B906 /* SetNotificationsMode.swift */,
|
||||
5CBD285B29575B8E00EC2CF4 /* WhatsNewView.swift */,
|
||||
);
|
||||
path = Onboarding;
|
||||
sourceTree = "<group>";
|
||||
@@ -705,6 +710,7 @@
|
||||
644EFFE1292D089800525D5B /* FramedCIVoiceView.swift */,
|
||||
644EFFE32937BE9700525D5B /* MarkedDeletedItemView.swift */,
|
||||
1841511920742C6E152E469F /* AnimatedImageView.swift */,
|
||||
6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */,
|
||||
);
|
||||
path = ChatItem;
|
||||
sourceTree = "<group>";
|
||||
@@ -1023,6 +1029,7 @@
|
||||
5C9C2DA92899DA6F00CC63B1 /* NetworkAndServers.swift in Sources */,
|
||||
5C6BA667289BD954009B8ECC /* DismissSheets.swift in Sources */,
|
||||
5C577F7D27C83AA10006112D /* MarkdownHelp.swift in Sources */,
|
||||
6407BA83295DA85D0082BA18 /* CIInvalidJSONView.swift in Sources */,
|
||||
644EFFDE292BCD9D00525D5B /* ComposeVoiceView.swift in Sources */,
|
||||
5CA059EB279559F40002BEB4 /* SimpleXApp.swift in Sources */,
|
||||
6448BBB628FA9D56000D2AB9 /* GroupLinkView.swift in Sources */,
|
||||
@@ -1037,6 +1044,7 @@
|
||||
5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */,
|
||||
5C764E89279CBCB3000C6508 /* ChatModel.swift in Sources */,
|
||||
5C971E1D27AEBEF600C8A3CE /* ChatInfoView.swift in Sources */,
|
||||
5CBD285C29575B8E00EC2CF4 /* WhatsNewView.swift in Sources */,
|
||||
5CC1C99527A6CF7F000D9FF6 /* ShareSheet.swift in Sources */,
|
||||
5C5E5D3B2824468B00B0488A /* ActiveCallView.swift in Sources */,
|
||||
5C2E260727A2941F00F70299 /* SimpleXAPI.swift in Sources */,
|
||||
@@ -1297,7 +1305,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 103;
|
||||
CURRENT_PROJECT_VERSION = 113;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -1318,7 +1326,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 4.4;
|
||||
MARKETING_VERSION = 4.4.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = SimpleX;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1339,7 +1347,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 103;
|
||||
CURRENT_PROJECT_VERSION = 113;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -1360,7 +1368,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 4.4;
|
||||
MARKETING_VERSION = 4.4.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = SimpleX;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1418,7 +1426,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 103;
|
||||
CURRENT_PROJECT_VERSION = 113;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -1431,7 +1439,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 4.4;
|
||||
MARKETING_VERSION = 4.4.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1448,7 +1456,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 103;
|
||||
CURRENT_PROJECT_VERSION = 113;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -1461,7 +1469,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 4.4;
|
||||
MARKETING_VERSION = 4.4.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"location" : "https://github.com/kirualex/SwiftyGif",
|
||||
"state" : {
|
||||
"branch" : "master",
|
||||
"revision" : "4a6f5bad863c5365b192f8441f62c713ecff62bd"
|
||||
"revision" : "5e8619335d394901379c9add5c4c1c2f420b3800"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
enableUBSanitizer = "YES"
|
||||
enableAddressSanitizer = "YES"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
||||
@@ -117,28 +117,64 @@ private func fromCString(_ c: UnsafeMutablePointer<CChar>) -> String {
|
||||
|
||||
public func chatResponse(_ s: String) -> ChatResponse {
|
||||
let d = s.data(using: .utf8)!
|
||||
// TODO is there a way to do it without copying the data? e.g:
|
||||
// let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson))
|
||||
// let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free)
|
||||
// TODO is there a way to do it without copying the data? e.g:
|
||||
// let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson))
|
||||
// let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free)
|
||||
do {
|
||||
let r = try jsonDecoder.decode(APIResponse.self, from: d)
|
||||
return r.resp
|
||||
} catch {
|
||||
logger.error("chatResponse jsonDecoder.decode error: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
|
||||
var type: String?
|
||||
var json: String?
|
||||
if let j = try? JSONSerialization.jsonObject(with: d) as? NSDictionary {
|
||||
if let j1 = j["resp"] as? NSDictionary, j1.count == 1 {
|
||||
type = j1.allKeys[0] as? String
|
||||
if let jResp = j["resp"] as? NSDictionary, jResp.count == 1 {
|
||||
type = jResp.allKeys[0] as? String
|
||||
if type == "apiChats" {
|
||||
if let jApiChats = jResp["apiChats"] as? NSDictionary,
|
||||
let jChats = jApiChats["chats"] as? NSArray {
|
||||
let chats = jChats.map { jChat in
|
||||
if let chatData = try? parseChatData(jChat) {
|
||||
return chatData
|
||||
}
|
||||
return ChatData.invalidJSON(prettyJSON(jChat) ?? "")
|
||||
}
|
||||
return .apiChats(chats: chats)
|
||||
}
|
||||
} else if type == "apiChat" {
|
||||
if let jApiChat = jResp["apiChat"] as? NSDictionary,
|
||||
let jChat = jApiChat["chat"] as? NSDictionary,
|
||||
let chat = try? parseChatData(jChat) {
|
||||
return .apiChat(chat: chat)
|
||||
}
|
||||
}
|
||||
}
|
||||
json = prettyJSON(j)
|
||||
}
|
||||
return ChatResponse.response(type: type ?? "invalid", json: json ?? s)
|
||||
}
|
||||
|
||||
func prettyJSON(_ obj: NSDictionary) -> String? {
|
||||
func parseChatData(_ jChat: Any) throws -> ChatData {
|
||||
let jChatDict = jChat as! NSDictionary
|
||||
let chatInfo: ChatInfo = try decodeObject(jChatDict["chatInfo"]!)
|
||||
let chatStats: ChatStats = try decodeObject(jChatDict["chatStats"]!)
|
||||
let jChatItems = jChatDict["chatItems"] as! NSArray
|
||||
let chatItems = jChatItems.map { jCI in
|
||||
if let ci: ChatItem = try? decodeObject(jCI) {
|
||||
return ci
|
||||
}
|
||||
return ChatItem.invalidJSON(prettyJSON(jCI) ?? "")
|
||||
}
|
||||
return ChatData(chatInfo: chatInfo, chatItems: chatItems, chatStats: chatStats)
|
||||
}
|
||||
|
||||
func decodeObject<T: Decodable>(_ obj: Any) throws -> T {
|
||||
try jsonDecoder.decode(T.self, from: JSONSerialization.data(withJSONObject: obj))
|
||||
}
|
||||
|
||||
func prettyJSON(_ obj: Any) -> String? {
|
||||
if let d = try? JSONSerialization.data(withJSONObject: obj, options: .prettyPrinted) {
|
||||
return String(decoding: d, as: UTF8.self)
|
||||
}
|
||||
|
||||
@@ -809,13 +809,15 @@ public struct NetCfg: Codable, Equatable {
|
||||
public var tcpTimeout: Int // microseconds
|
||||
public var tcpKeepAlive: KeepAliveOpts?
|
||||
public var smpPingInterval: Int // microseconds
|
||||
public var logTLSErrors: Bool
|
||||
|
||||
public static let defaults: NetCfg = NetCfg(
|
||||
socksProxy: nil,
|
||||
tcpConnectTimeout: 10_000_000,
|
||||
tcpTimeout: 7_000_000,
|
||||
tcpKeepAlive: KeepAliveOpts.defaults,
|
||||
smpPingInterval: 600_000_000
|
||||
smpPingInterval: 600_000_000,
|
||||
logTLSErrors: false
|
||||
)
|
||||
|
||||
public static let proxyDefaults: NetCfg = NetCfg(
|
||||
@@ -823,7 +825,8 @@ public struct NetCfg: Codable, Equatable {
|
||||
tcpConnectTimeout: 20_000_000,
|
||||
tcpTimeout: 15_000_000,
|
||||
tcpKeepAlive: KeepAliveOpts.defaults,
|
||||
smpPingInterval: 600_000_000
|
||||
smpPingInterval: 600_000_000,
|
||||
logTLSErrors: false
|
||||
)
|
||||
|
||||
public var enableKeepAlive: Bool { tcpKeepAlive != nil }
|
||||
|
||||
@@ -44,7 +44,9 @@ public func registerGroupDefaults() {
|
||||
GROUP_DEFAULT_NETWORK_TCP_KEEP_CNT: KeepAliveOpts.defaults.keepCnt,
|
||||
GROUP_DEFAULT_INCOGNITO: false,
|
||||
GROUP_DEFAULT_STORE_DB_PASSPHRASE: true,
|
||||
GROUP_DEFAULT_INITIAL_RANDOM_DB_PASSPHRASE: false
|
||||
GROUP_DEFAULT_INITIAL_RANDOM_DB_PASSPHRASE: false,
|
||||
GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES: true,
|
||||
GROUP_DEFAULT_PRIVACY_TRANSFER_IMAGES_INLINE: true
|
||||
])
|
||||
}
|
||||
|
||||
@@ -201,7 +203,8 @@ public func getNetCfg() -> NetCfg {
|
||||
tcpConnectTimeout: tcpConnectTimeout,
|
||||
tcpTimeout: tcpTimeout,
|
||||
tcpKeepAlive: tcpKeepAlive,
|
||||
smpPingInterval: smpPingInterval
|
||||
smpPingInterval: smpPingInterval,
|
||||
logTLSErrors: false
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -161,9 +161,9 @@ public struct Preferences: Codable {
|
||||
)
|
||||
}
|
||||
|
||||
public func setAllowed(_ feature: ChatFeature, allowed: FeatureAllowed = .yes) -> Preferences {
|
||||
public func setAllowed(_ feature: ChatFeature, allowed: FeatureAllowed = .yes, param: Int? = nil) -> Preferences {
|
||||
switch feature {
|
||||
case .timedMessages: return copy(timedMessages: TimedMessagesPreference(allow: allowed, ttl: timedMessages?.ttl))
|
||||
case .timedMessages: return copy(timedMessages: TimedMessagesPreference(allow: allowed, ttl: param ?? timedMessages?.ttl))
|
||||
case .fullDelete: return copy(fullDelete: SimplePreference(allow: allowed))
|
||||
case .voice: return copy(voice: SimplePreference(allow: allowed))
|
||||
}
|
||||
@@ -244,7 +244,7 @@ public struct TimedMessagesPreference: Preference {
|
||||
: String.localizedStringWithFormat(NSLocalizedString("%d hours", comment: "message ttl"), h)
|
||||
)
|
||||
+ maybe(m, String.localizedStringWithFormat(NSLocalizedString("%d min", comment: "message ttl"), m))
|
||||
+ maybe (s, String.localizedStringWithFormat(NSLocalizedString("%d sec", comment: "message ttl"), s))
|
||||
+ maybe(s, String.localizedStringWithFormat(NSLocalizedString("%d sec", comment: "message ttl"), s))
|
||||
}
|
||||
|
||||
public static func shortTtlText(_ ttl: Int?) -> LocalizedStringKey {
|
||||
@@ -798,6 +798,9 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
case group(groupInfo: GroupInfo)
|
||||
case contactRequest(contactRequest: UserContactRequest)
|
||||
case contactConnection(contactConnection: PendingContactConnection)
|
||||
case invalidJSON(json: String)
|
||||
|
||||
private static let invalidChatName = NSLocalizedString("invalid chat", comment: "invalid chat data")
|
||||
|
||||
public var localDisplayName: String {
|
||||
get {
|
||||
@@ -806,6 +809,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
case let .group(groupInfo): return groupInfo.localDisplayName
|
||||
case let .contactRequest(contactRequest): return contactRequest.localDisplayName
|
||||
case let .contactConnection(contactConnection): return contactConnection.localDisplayName
|
||||
case .invalidJSON: return ChatInfo.invalidChatName
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -817,6 +821,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
case let .group(groupInfo): return groupInfo.displayName
|
||||
case let .contactRequest(contactRequest): return contactRequest.displayName
|
||||
case let .contactConnection(contactConnection): return contactConnection.displayName
|
||||
case .invalidJSON: return ChatInfo.invalidChatName
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -828,6 +833,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
case let .group(groupInfo): return groupInfo.fullName
|
||||
case let .contactRequest(contactRequest): return contactRequest.fullName
|
||||
case let .contactConnection(contactConnection): return contactConnection.fullName
|
||||
case .invalidJSON: return ChatInfo.invalidChatName
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -839,6 +845,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
case let .group(groupInfo): return groupInfo.image
|
||||
case let .contactRequest(contactRequest): return contactRequest.image
|
||||
case let .contactConnection(contactConnection): return contactConnection.image
|
||||
case .invalidJSON: return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -850,6 +857,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
case let .group(groupInfo): return groupInfo.localAlias
|
||||
case let .contactRequest(contactRequest): return contactRequest.localAlias
|
||||
case let .contactConnection(contactConnection): return contactConnection.localAlias
|
||||
case .invalidJSON: return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -861,6 +869,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
case let .group(groupInfo): return groupInfo.id
|
||||
case let .contactRequest(contactRequest): return contactRequest.id
|
||||
case let .contactConnection(contactConnection): return contactConnection.id
|
||||
case .invalidJSON: return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -872,6 +881,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
case .group: return .group
|
||||
case .contactRequest: return .contactRequest
|
||||
case .contactConnection: return .contactConnection
|
||||
case .invalidJSON: return .direct
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -883,6 +893,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
case let .group(groupInfo): return groupInfo.apiId
|
||||
case let .contactRequest(contactRequest): return contactRequest.apiId
|
||||
case let .contactConnection(contactConnection): return contactConnection.apiId
|
||||
case .invalidJSON: return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -894,6 +905,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
case let .group(groupInfo): return groupInfo.ready
|
||||
case let .contactRequest(contactRequest): return contactRequest.ready
|
||||
case let .contactConnection(contactConnection): return contactConnection.ready
|
||||
case .invalidJSON: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -905,6 +917,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
case let .group(groupInfo): return groupInfo.sendMsgEnabled
|
||||
case let .contactRequest(contactRequest): return contactRequest.sendMsgEnabled
|
||||
case let .contactConnection(contactConnection): return contactConnection.sendMsgEnabled
|
||||
case .invalidJSON: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -916,6 +929,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
case let .group(groupInfo): return groupInfo.membership.memberIncognito
|
||||
case .contactRequest: return false
|
||||
case let .contactConnection(contactConnection): return contactConnection.incognito
|
||||
case .invalidJSON: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1003,6 +1017,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
case let .group(groupInfo): return groupInfo.createdAt
|
||||
case let .contactRequest(contactRequest): return contactRequest.createdAt
|
||||
case let .contactConnection(contactConnection): return contactConnection.createdAt
|
||||
case .invalidJSON: return .now
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1012,6 +1027,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
case let .group(groupInfo): return groupInfo.updatedAt
|
||||
case let .contactRequest(contactRequest): return contactRequest.updatedAt
|
||||
case let .contactConnection(contactConnection): return contactConnection.updatedAt
|
||||
case .invalidJSON: return .now
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1036,6 +1052,14 @@ public struct ChatData: Decodable, Identifiable {
|
||||
public var chatStats: ChatStats
|
||||
|
||||
public var id: ChatId { get { chatInfo.id } }
|
||||
|
||||
public static func invalidJSON(_ json: String) -> ChatData {
|
||||
ChatData(
|
||||
chatInfo: .invalidJSON(json: json),
|
||||
chatItems: [],
|
||||
chatStats: ChatStats()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public struct ChatStats: Decodable {
|
||||
@@ -1364,15 +1388,17 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat {
|
||||
}
|
||||
|
||||
public struct GroupProfile: Codable, NamedChat {
|
||||
public init(displayName: String, fullName: String, image: String? = nil, groupPreferences: GroupPreferences? = nil) {
|
||||
public init(displayName: String, fullName: String, description: String? = nil, image: String? = nil, groupPreferences: GroupPreferences? = nil) {
|
||||
self.displayName = displayName
|
||||
self.fullName = fullName
|
||||
self.description = description
|
||||
self.image = image
|
||||
self.groupPreferences = groupPreferences
|
||||
}
|
||||
|
||||
public var displayName: String
|
||||
public var fullName: String
|
||||
public var description: String?
|
||||
public var image: String?
|
||||
public var groupPreferences: GroupPreferences?
|
||||
public var localAlias: String { "" }
|
||||
@@ -1644,6 +1670,7 @@ public struct ChatItem: Identifiable, Decodable {
|
||||
public var file: CIFile?
|
||||
|
||||
public var viewTimestamp = Date.now
|
||||
public var isLiveDummy: Bool = false
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case chatDir, meta, content, formattedText, quotedItem, file
|
||||
@@ -1712,6 +1739,7 @@ public struct ChatItem: Identifiable, Decodable {
|
||||
case .sndGroupFeature: return showNtfDir
|
||||
case .rcvChatFeatureRejected: return showNtfDir
|
||||
case .rcvGroupFeatureRejected: return showNtfDir
|
||||
case .invalidJSON: return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1834,6 +1862,39 @@ public struct ChatItem: Identifiable, Decodable {
|
||||
file: nil
|
||||
)
|
||||
}
|
||||
|
||||
public static func liveDummy(_ chatType: ChatType) -> ChatItem {
|
||||
var item = ChatItem(
|
||||
chatDir: chatType == ChatType.direct ? CIDirection.directSnd : CIDirection.groupSnd,
|
||||
meta: CIMeta(
|
||||
itemId: -2,
|
||||
itemTs: .now,
|
||||
itemText: "",
|
||||
itemStatus: .rcvRead,
|
||||
createdAt: .now,
|
||||
updatedAt: .now,
|
||||
itemDeleted: false,
|
||||
itemEdited: false,
|
||||
itemLive: true,
|
||||
editable: false
|
||||
),
|
||||
content: .sndMsgContent(msgContent: .text("")),
|
||||
quotedItem: nil,
|
||||
file: nil
|
||||
)
|
||||
item.isLiveDummy = true
|
||||
return item
|
||||
}
|
||||
|
||||
public static func invalidJSON(_ json: String) -> ChatItem {
|
||||
ChatItem(
|
||||
chatDir: CIDirection.directSnd,
|
||||
meta: CIMeta.invalidJSON,
|
||||
content: .invalidJSON(json: json),
|
||||
quotedItem: nil,
|
||||
file: nil
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public enum CIDirection: Decodable {
|
||||
@@ -1901,6 +1962,21 @@ public struct CIMeta: Decodable {
|
||||
editable: editable
|
||||
)
|
||||
}
|
||||
|
||||
public static var invalidJSON: CIMeta {
|
||||
CIMeta(
|
||||
itemId: 0,
|
||||
itemTs: .now,
|
||||
itemText: "invalid JSON",
|
||||
itemStatus: .sndNew,
|
||||
createdAt: .now,
|
||||
updatedAt: .now,
|
||||
itemDeleted: false,
|
||||
itemEdited: false,
|
||||
itemLive: false,
|
||||
editable: false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public struct CITimed: Decodable {
|
||||
@@ -1969,6 +2045,7 @@ public enum CIContent: Decodable, ItemContent {
|
||||
case sndGroupFeature(groupFeature: GroupFeature, preference: GroupPreference, param: Int?)
|
||||
case rcvChatFeatureRejected(feature: ChatFeature)
|
||||
case rcvGroupFeatureRejected(groupFeature: GroupFeature)
|
||||
case invalidJSON(json: String)
|
||||
|
||||
public var text: String {
|
||||
get {
|
||||
@@ -1994,22 +2071,23 @@ public enum CIContent: Decodable, ItemContent {
|
||||
case let .sndGroupFeature(feature, preference, param): return CIContent.featureText(feature, preference.enable.text, param)
|
||||
case let .rcvChatFeatureRejected(feature): return String.localizedStringWithFormat("%@: received, prohibited", feature.text)
|
||||
case let .rcvGroupFeatureRejected(groupFeature): return String.localizedStringWithFormat("%@: received, prohibited", groupFeature.text)
|
||||
case .invalidJSON: return NSLocalizedString("invalid data", comment: "invalid chat item")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func featureText(_ feature: Feature, _ enabled: String, _ param: Int?) -> String {
|
||||
feature.hasParam && param != nil
|
||||
feature.hasParam
|
||||
? "\(feature.text): \(TimedMessagesPreference.ttlText(param))"
|
||||
: "\(feature.text): \(enabled)"
|
||||
}
|
||||
|
||||
public static func preferenceText(_ feature: Feature, _ allowed: FeatureAllowed, _ param: Int?) -> String {
|
||||
allowed != .no && feature.hasParam && param != nil
|
||||
? "offered \(feature.text): \(TimedMessagesPreference.ttlText(param))"
|
||||
? String.localizedStringWithFormat(NSLocalizedString("offered %@: %@", comment: "feature offered item"), feature.text, TimedMessagesPreference.ttlText(param))
|
||||
: allowed != .no
|
||||
? "offered \(feature.text)"
|
||||
: "cancelled \(feature.text)"
|
||||
? String.localizedStringWithFormat(NSLocalizedString("offered %@", comment: "feature offered item"), feature.text)
|
||||
: String.localizedStringWithFormat(NSLocalizedString("cancelled %@", comment: "feature offered item"), feature.text)
|
||||
}
|
||||
|
||||
public var msgContent: MsgContent? {
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
" " = " ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
" " = " ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
" " = " ";
|
||||
|
||||
@@ -94,9 +97,30 @@
|
||||
/* notification title */
|
||||
"%@ is connected!" = "%@ ist mit Ihnen verbunden!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%@ is not verified" = "%@ wurde nicht überprüft";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%@ is verified" = "%@ wurde überprüft";
|
||||
|
||||
/* notification title */
|
||||
"%@ wants to connect!" = "%@ will sich mit Ihnen verbinden!";
|
||||
|
||||
/* message ttl */
|
||||
"%d days" = "%d Tage";
|
||||
|
||||
/* message ttl */
|
||||
"%d hours" = "%d Stunden";
|
||||
|
||||
/* message ttl */
|
||||
"%d min" = "%d min";
|
||||
|
||||
/* message ttl */
|
||||
"%d months" = "%d Monate";
|
||||
|
||||
/* message ttl */
|
||||
"%d sec" = "%d s";
|
||||
|
||||
/* integrity error chat item */
|
||||
"%d skipped message(s)" = "%d übersprungene Nachricht(en)";
|
||||
|
||||
@@ -118,9 +142,27 @@
|
||||
/* No comment provided by engineer. */
|
||||
"%lld second(s)" = "%lld Sekunde(n)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%lldd" = "%lldT";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%lldh" = "%lldH";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%lldk" = "%lldk";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%lldm" = "%lldmin";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%lldmth" = "%lldMon";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%llds" = "%lldsek";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%lldw" = "%lldW";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"`a + b`" = "\\`a + b`";
|
||||
|
||||
@@ -130,12 +172,18 @@
|
||||
/* message ttl */
|
||||
"1 day" = "täglich";
|
||||
|
||||
/* message ttl */
|
||||
"1 hour" = "1 Stunde";
|
||||
|
||||
/* message ttl */
|
||||
"1 month" = "monatlich";
|
||||
|
||||
/* message ttl */
|
||||
"1 week" = "wöchentlich";
|
||||
|
||||
/* message ttl */
|
||||
"2 weeks" = "2 Wochen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"6" = "6";
|
||||
|
||||
@@ -185,12 +233,18 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Add server…" = "Füge Server hinzu…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add servers by scanning QR codes." = "Fügen Sie Server durch Scannen der QR Codes hinzu.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add to another device" = "Einem anderen Gerät hinzufügen";
|
||||
|
||||
/* member role */
|
||||
"admin" = "Admin";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Admins can create the links to join groups." = "Administratoren können Links für den Beitritt zu Gruppen erzeugen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Advanced network settings" = "Erweiterte Netzwerkeinstellungen";
|
||||
|
||||
@@ -207,16 +261,22 @@
|
||||
"Allow" = "Erlauben";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow irreversible message deletion only if your contact allows it to you." = "Erlauben Sie das unwiederbringliche löschen von Nachrichten nur dann, wenn es Ihnen Ihr Kontakt ebenfalls erlaubt.";
|
||||
"Allow disappearing messages only if your contact allows it to you." = "Verschwindende Nachrichten nur erlauben, wenn Ihr Kontakt das ebenfalls erlaubt.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow sending direct messages to members." = "Erlauben Sie das Senden von Direktnachrichten an Mitglieder";
|
||||
"Allow irreversible message deletion only if your contact allows it to you." = "Erlauben Sie das unwiederbringliche Löschen von Nachrichten nur dann, wenn es Ihnen Ihr Kontakt ebenfalls erlaubt.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow to irreversibly delete sent messages." = "Unwiederbringliches Löschen von gesendeten Nachrichten erlauben.";
|
||||
"Allow sending direct messages to members." = "Das Senden von Direktnachrichten an Gruppenmitglieder erlauben.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow to send voice messages." = "Senden von Sprachnachrichten erlauben.";
|
||||
"Allow sending disappearing messages." = "Das Senden von verschwindenden Nachrichten erlauben.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow to irreversibly delete sent messages." = "Unwiederbringliches löschen von gesendeten Nachrichten erlauben.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow to send voice messages." = "Das Senden von Sprachnachrichten erlauben.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow voice messages only if your contact allows them." = "Erlauben Sie Sprachnachrichten nur dann, wenn Ihr Kontakt diese ebenfalls erlaubt.";
|
||||
@@ -227,6 +287,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Allow your contacts to irreversibly delete sent messages." = "Erlauben Sie Ihren Kontakten gesendete Nachrichten unwiederbringlich zu löschen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow your contacts to send disappearing messages." = "Erlauben Sie Ihren Kontakten das Senden von verschwindenden Nachrichten.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow your contacts to send voice messages." = "Erlauben Sie Ihren Kontakten Sprachnachrichten zu senden.";
|
||||
|
||||
@@ -260,6 +323,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Authentication unavailable" = "Authentifizierung nicht verfügbar";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Auto-accept contact requests" = "Kontaktanfragen automatisch annehmen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Auto-accept images" = "Bilder automatisch akzeptieren";
|
||||
|
||||
@@ -281,6 +347,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Both you and your contact can irreversibly delete sent messages." = "Sowohl Ihr Kontakt, als auch Sie können gesendete Nachrichten unwiederbringlich löschen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Both you and your contact can send disappearing messages." = "Ihr Kontakt und Sie können beide verschwindende Nachrichten senden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Both you and your contact can send voice messages." = "Sowohl Ihr Kontakt, als auch Sie können Sprachnachrichten senden.";
|
||||
|
||||
@@ -392,12 +461,18 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Clear conversation?" = "Unterhaltung löschen?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Clear verification" = "Überprüfung zurücknehmen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"colored" = "farbig";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Colors" = "Farben";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Compare security codes with your contacts." = "Vergleichen Sie die Sicherheitscodes mit Ihren Kontakten.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"complete" = "vollständig";
|
||||
|
||||
@@ -533,6 +608,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Create address" = "Adresse erstellen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create group link" = "Gruppenlink erstellen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create link" = "Link erzeugen";
|
||||
|
||||
@@ -623,6 +701,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Delete address?" = "Adresse löschen?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete after" = "Löschen nach";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete archive" = "Archiv löschen";
|
||||
|
||||
@@ -729,11 +810,20 @@
|
||||
"Direct messages" = "Direkte Nachrichten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Direct messages between members are prohibited in this group." = "In dieser Gruppe sind Direktnachrichten zwischen Mitgliedern nicht möglich.";
|
||||
"Direct messages between members are prohibited in this group." = "In dieser Gruppe sind Direktnachrichten zwischen Mitgliedern nicht erlaubt.";
|
||||
|
||||
/* authentication reason */
|
||||
"Disable SimpleX Lock" = "SimpleX Sperre deaktivieren";
|
||||
|
||||
/* chat feature */
|
||||
"Disappearing messages" = "Verschwindende Nachrichten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Disappearing messages are prohibited in this chat." = "In diesem Chat sind verschwindende Nachrichten nicht erlaubt.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Disappearing messages are prohibited in this group." = "In dieser Gruppe sind verschwindende Nachrichten nicht erlaubt.";
|
||||
|
||||
/* server test step */
|
||||
"Disconnect" = "Trennen";
|
||||
|
||||
@@ -915,7 +1005,7 @@
|
||||
"Error saving passphrase to keychain" = "Fehler beim Speichern des Passworts in den Schlüsselbund";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error saving SMP servers" = "Fehler beim Speichern der SMP Server";
|
||||
"Error saving SMP servers" = "Fehler beim Speichern der SMP-Server";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error sending message" = "Fehler beim Senden der Nachricht";
|
||||
@@ -977,6 +1067,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Full name:" = "Vollständiger Name:";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"GIFs and stickers" = "GIFs und Sticker";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Group" = "Gruppe";
|
||||
|
||||
@@ -1004,6 +1097,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Group link" = "Gruppen-Link";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Group links" = "Gruppen-Links";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Group members can irreversibly delete sent messages." = "Gruppenmitglieder können gesendete Nachrichten unwiederbringlich löschen.";
|
||||
|
||||
@@ -1011,7 +1107,10 @@
|
||||
"Group members can send direct messages." = "Gruppenmitglieder können Direktnachrichten versenden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Group members can send voice messages." = "Gruppenmitglieder können Sprachnachrichten senden.";
|
||||
"Group members can send disappearing messages." = "Gruppenmitglieder können verschwindende Nachrichten senden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Group members can send voice messages." = "Gruppenmitglieder können Sprachnachrichten versenden.";
|
||||
|
||||
/* notification */
|
||||
"Group message:" = "Grppennachricht:";
|
||||
@@ -1023,7 +1122,7 @@
|
||||
"Group profile" = "Gruppenprofil";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Group profile is stored on members' devices, not on the servers." = "Das Gruppenprofil wird nur auf den Mitglieds-Systemen gespeichtert und nicht auf den Servern.";
|
||||
"Group profile is stored on members' devices, not on the servers." = "Das Gruppenprofil wird nur auf den Mitglieds-Systemen gespeichert und nicht auf den Servern.";
|
||||
|
||||
/* snd group event chat item */
|
||||
"group profile updated" = "Gruppenprofil aktualisiert";
|
||||
@@ -1043,6 +1142,9 @@
|
||||
/* chat item action */
|
||||
"Hide" = "Verbergen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Hide app screen in the recent apps." = "App-Bildschirm in aktuellen Anwendungen verbergen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"How it works" = "Wie es funktioniert";
|
||||
|
||||
@@ -1055,9 +1157,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"How to use it" = "Wie man SimpleX nutzt";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"How to use markdown" = "Markdowns verwenden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"How to use your servers" = "Wie Sie Ihre Server nutzen";
|
||||
|
||||
@@ -1094,6 +1193,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Import database" = "Datenbank importieren";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Improved privacy and security" = "Verbesserte Privatsphäre und Sicherheit";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Improved server configuration" = "Verbesserte Serverkonfiguration";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Incognito" = "Inkognito";
|
||||
|
||||
@@ -1124,6 +1229,9 @@
|
||||
/* notification */
|
||||
"Incoming video call" = "Eingehender Videoanruf";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Incorrect security code!" = "Falscher Sicherheitscode!";
|
||||
|
||||
/* connection level description */
|
||||
"indirect (%d)" = "indirekt (%d)";
|
||||
|
||||
@@ -1136,9 +1244,18 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Instantly" = "Sofort";
|
||||
|
||||
/* invalid chat data */
|
||||
"invalid chat" = "Ungültiger Chat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"invalid chat data" = "Ungültige Chat-Daten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Invalid connection link" = "Ungültiger Verbindungslink";
|
||||
|
||||
/* invalid chat item */
|
||||
"invalid data" = "Ungültige Daten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Invalid server address!" = "Ungültige Serveradresse!";
|
||||
|
||||
@@ -1173,10 +1290,13 @@
|
||||
"iOS Keychain will be used to securely store passphrase after you restart the app or change passphrase - it will allow receiving push notifications." = "Für die sichere Speicherung des Passworts nach dem Neustart der App und dem Wechsel des Passworts wird der iOS Schlüsselbund verwendet - dies erlaubt den Empfang von Push-Benachrichtigungen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Irreversible message deletion is prohibited in this chat." = "In diesem Chat ist das unwiederbringliche Löschen von Nachrichten untersagt.";
|
||||
"Irreversible message deletion" = "Unwiederbringliches löschen einer Nachricht";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Irreversible message deletion is prohibited in this group." = "In dieser Gruppe ist das unwiederbringliche Löschen von Nachrichten verboten.";
|
||||
"Irreversible message deletion is prohibited in this chat." = "In diesem Chat ist das unwiederbringliche Löschen von Nachrichten nicht erlaubt.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Irreversible message deletion is prohibited in this group." = "In dieser Gruppe ist das unwiederbringliche Löschen von Nachrichten nicht erlaubt.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"It allows having many anonymous connections without any shared data between them in a single chat profile." = "Er ermöglicht mehrere anonyme Verbindungen in einem einzigen Chat-Profil ohne Daten zwischen diesen zu teilen.";
|
||||
@@ -1232,6 +1352,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"LIVE" = "LIVE";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Live message!" = "Live Nachricht!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Live messages" = "Live Nachrichten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Local name" = "Lokaler Name";
|
||||
|
||||
@@ -1242,7 +1368,7 @@
|
||||
"Make sure SMP server addresses are in correct format, line separated and are not duplicated (%@)." = "Stellen Sie sicher, dass die SMP-Server-Adressen das richtige Format haben, zeilenweise getrennt und nicht doppelt vorhanden sind (%@).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Stellen Sie sicher, dass die WebRTC ICE-Server Adressen das richtige Format haben, zeilenweise angeordnet und nicht kopiert sind.";
|
||||
"Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Stellen Sie sicher, dass die WebRTC ICE-Server Adressen das richtige Format haben, zeilenweise angeordnet und nicht doppelt vorhanden sind.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Viele Menschen haben gefragt: *Wie kann SimpleX Nachrichten zustellen, wenn es keine Benutzerkennungen gibt?*";
|
||||
@@ -1253,12 +1379,18 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Mark read" = "Als gelesen markieren";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Mark verified" = "Als überprüft markieren";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Markdown in messages" = "Markdowns in Nachrichten";
|
||||
|
||||
/* marked deleted chat item preview text */
|
||||
"marked deleted" = "als gelöscht markiert";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Max 30 seconds, received instantly." = "Max. 30 Sekunden, sofort erhalten.";
|
||||
|
||||
/* member role */
|
||||
"member" = "Mitglied";
|
||||
|
||||
@@ -1334,6 +1466,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"New database archive" = "Neues Datenbankarchiv";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"New in %@" = "Neu in %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"New member role" = "Neue Mitgliedsrolle";
|
||||
|
||||
@@ -1423,13 +1558,19 @@
|
||||
"Only you can irreversibly delete messages (your contact can mark them for deletion)." = "Nur Sie können Nachrichten unwiederbringlich löschen (Ihr Kontakt kann sie zum Löschen markieren).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only you can send voice messages." = "Nur Sie können Sprachnachrichten senden.";
|
||||
"Only you can send disappearing messages." = "Nur Sie können verschwindende Nachrichten senden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only you can send voice messages." = "Nur Sie können Sprachnachrichten versenden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only your contact can irreversibly delete messages (you can mark them for deletion)." = "Nur Ihr Kontakt kann Nachrichten unwiederbringlich löschen (Sie können sie zum Löschen markieren).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only your contact can send voice messages." = "Nur Ihr Kontakt kann Sprachnachrichten senden.";
|
||||
"Only your contact can send disappearing messages." = "Nur Ihr Kontakt kann verschwindende Nachrichten senden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only your contact can send voice messages." = "Nur Ihr Kontakt kann Sprachnachrichten versenden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open chat" = "Chat öffnen";
|
||||
@@ -1522,13 +1663,16 @@
|
||||
"Profile image" = "Profilbild";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Prohibit irreversible message deletion." = "Unwiederbringliches Löschen von Nachrichten verbieten.";
|
||||
"Prohibit irreversible message deletion." = "Unwiederbringliches löschen von Nachrichten nicht erlauben.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Prohibit sending direct messages to members." = "Verbieten Sie das Senden von Direktnachrichten an Mitglieder";
|
||||
"Prohibit sending direct messages to members." = "Das Senden von Direktnachrichten an Gruppenmitglieder nicht erlauben.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Prohibit sending voice messages." = "Senden von Sprachnachrichten untersagen.";
|
||||
"Prohibit sending disappearing messages." = "Das Senden von verschwindenden Nachrichten verbieten.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Prohibit sending voice messages." = "Das Senden von Sprachnachrichten nicht erlauben.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Protect app screen" = "App-Bildschirm schützen";
|
||||
@@ -1563,6 +1707,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Receiving via" = "Empfangen über";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Recipients see updates as you type them." = "Die Empfänger sehen Nachrichtenaktualisierungen, während Sie sie eingeben.";
|
||||
|
||||
/* reject incoming call via notification */
|
||||
"Reject" = "Ablehnen";
|
||||
|
||||
@@ -1675,14 +1822,20 @@
|
||||
"Save preferences?" = "Präferenzen speichern?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Save servers" = "Server speichern";
|
||||
"Save servers" = "Alle Server speichern";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Saved WebRTC ICE servers will be removed" = "Gespeicherte WebRTC ICE-Server werden entfernt";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Scan code" = "Code scannen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Scan QR code" = "QR-Code scannen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Scan security code from your contact's app." = "Scannen Sie den Sicherheitscode von der App Ihres Kontakts.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Scan server QR code" = "Scannen Sie den QR-Code des Servers";
|
||||
|
||||
@@ -1698,12 +1851,27 @@
|
||||
/* server test step */
|
||||
"Secure queue" = "Sichere Warteschlange";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Security assessment" = "Sicherheits-Gutachten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Security code" = "Sicherheitscode";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send" = "Senden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send a live message - it will update for the recipient(s) as you type it" = "Eine Live Nachricht senden - der/die Empfänger sieht/sehen Nachrichtenaktualisierungen, während Sie sie eingeben";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message" = "Direktnachricht senden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send link previews" = "Link-Vorschau senden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send live message" = "Live Nachricht senden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send notifications" = "Benachrichtigungen senden";
|
||||
|
||||
@@ -1713,6 +1881,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Send questions and ideas" = "Senden Sie Fragen und Ideen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send them from gallery or custom keyboards." = "Senden Sie diese aus dem Fotoalbum oder von individuellen Tastaturen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sender cancelled file transfer." = "Der Absender hat die Dateiübertragung abgebrochen.";
|
||||
|
||||
@@ -1725,8 +1896,11 @@
|
||||
/* notification */
|
||||
"Sent file event" = "Datei-Ereignis wurde gesendet";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sent messages will be deleted after set time." = "Gesendete Nachrichten werden nach der eingestellten Zeit gelöscht.";
|
||||
|
||||
/* server test error */
|
||||
"Server requires authorization to create queues, check password" = "Um Warteschlangen zu erzeugen benötigt der Server eine Authentifizierung. Bitte überprüfen Sie das Passwort.";
|
||||
"Server requires authorization to create queues, check password" = "Um Warteschlangen zu erzeugen benötigt der Server eine Authentifizierung. Bitte überprüfen Sie das Passwort";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Server test failed!" = "Server Test ist fehlgeschlagen!";
|
||||
@@ -1734,6 +1908,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Servers" = "Server";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Set 1 day" = "Einen Tag festlegen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Set contact name…" = "Kontaktname festlegen…";
|
||||
|
||||
@@ -1767,6 +1944,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Show QR code" = "QR-Code anzeigen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"SimpleX Chat security was [audited by Trail of Bits](https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html)." = "Die Sicherheit von SimpleX Chat wurde [von Trail of Bits überprüft](https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html).";
|
||||
|
||||
/* simplex link type */
|
||||
"SimpleX contact address" = "SimpleX Kontaktadressen-Link";
|
||||
|
||||
@@ -1959,6 +2139,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"To support instant push notifications the chat database has to be migrated." = "Um sofortige Push-Benachrichtigungen zu unterstützen, muss die Chat-Datenbank migriert werden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Um die Ende-zu-Ende-Verschlüsselung mit Ihrem Kontakt zu überprüfen, müssen Sie den Sicherheitscode in Ihren Apps vergleichen oder scannen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Transfer images faster" = "Bilder schneller übertragen";
|
||||
|
||||
@@ -2038,7 +2221,7 @@
|
||||
"Use for new connections" = "Für neue Verbindungen nutzen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use server" = "Benutze Server";
|
||||
"Use server" = "Server nutzen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use SimpleX Chat servers?" = "Verwenden Sie SimpleX Chat Server?";
|
||||
@@ -2052,6 +2235,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"v%@ (%@)" = "v%@ (%@)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Verify connection security" = "Sicherheit der Verbindung überprüfen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Verify security code" = "Sicherheitscode überprüfen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Via browser" = "Über den Browser";
|
||||
|
||||
@@ -2073,6 +2262,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"video call (not e2e encrypted)" = "Videoanruf (nicht E2E verschlüsselt)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"View security code" = "Schauen Sie sich den Sicherheitscode an";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Voice message…" = "Sprachnachrichten…";
|
||||
|
||||
@@ -2080,13 +2272,13 @@
|
||||
"Voice messages" = "Sprachnachrichten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Voice messages are prohibited in this chat." = "In diesem Chat sind Sprachnachrichten untersagt.";
|
||||
"Voice messages are prohibited in this chat." = "In diesem Chat sind Sprachnachrichten nicht erlaubt.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Voice messages are prohibited in this group." = "In dieser Gruppe sind Sprachnachrichten untersagt.";
|
||||
"Voice messages are prohibited in this group." = "In dieser Gruppe sind Sprachnachrichten nicht erlaubt.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Voice messages prohibited!" = "Sprachnachrichten sind untersagt!";
|
||||
"Voice messages prohibited!" = "Sprachnachrichten sind nicht erlaubt!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"waiting for answer…" = "Warten auf Antwort…";
|
||||
@@ -2112,12 +2304,18 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Welcome message" = "Begrüßungsmeldung";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"What's new" = "Was ist neu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"When available" = "Wenn verfügbar";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Wenn Sie ein Inkognito-Profil mit Jemandem teilen, wird dieses Profil auch für die Gruppen verwendet, für die Sie von diesem Kontakt eingeladen werden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"With optional welcome message." = "Mit optionaler Begrüßungsmeldung.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Wrong database passphrase" = "Falsches Datenbank-Passwort";
|
||||
|
||||
@@ -2286,6 +2484,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Your contact sent a file that is larger than currently supported maximum size (%@)." = "Ihr Kontakt hat eine Datei gesendet, die größer ist als die derzeit unterstützte maximale Größe (%@).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contacts can allow full message deletion." = "Ihre Kontakte können die unwiederbringliche Löschung von Nachrichten erlauben.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your current chat database will be DELETED and REPLACED with the imported one." = "Ihre aktuelle Chat-Datenbank wird GELÖSCHT und durch die Importierte ERSETZT.";
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
" " = " ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
" " = " ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
" " = " ";
|
||||
|
||||
@@ -149,7 +152,7 @@
|
||||
"%lldk" = "%lldk";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%lldm" = "%lldm";
|
||||
"%lldm" = "%lldmn";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%lldmth" = "%lldmois";
|
||||
@@ -230,12 +233,18 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Add server…" = "Ajouter un serveur…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add servers by scanning QR codes." = "Ajoutez des serveurs en scannant des codes QR.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add to another device" = "Ajouter à un autre appareil";
|
||||
|
||||
/* member role */
|
||||
"admin" = "admin";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Admins can create the links to join groups." = "Les admins peuvent créer les liens qui permettent de rejoindre les groupes.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Advanced network settings" = "Paramètres réseau avancés";
|
||||
|
||||
@@ -314,6 +323,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Authentication unavailable" = "Authentification indisponible";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Auto-accept contact requests" = "Demandes de contact auto-acceptées";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Auto-accept images" = "Images auto-acceptées";
|
||||
|
||||
@@ -458,6 +470,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Colors" = "Couleurs";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Compare security codes with your contacts." = "Comparez les codes de sécurité avec vos contacts.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"complete" = "complet";
|
||||
|
||||
@@ -1052,6 +1067,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Full name:" = "Nom complet :";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"GIFs and stickers" = "GIFs et stickers";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Group" = "Groupe";
|
||||
|
||||
@@ -1079,6 +1097,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Group link" = "Lien du groupe";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Group links" = "Liens de groupe";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Group members can irreversibly delete sent messages." = "Les membres du groupe peuvent supprimer de manière irréversible les messages envoyés.";
|
||||
|
||||
@@ -1121,6 +1142,9 @@
|
||||
/* chat item action */
|
||||
"Hide" = "Cacher";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Hide app screen in the recent apps." = "Masquer l'écran de l'app dans les apps récentes.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"How it works" = "Comment ça fonctionne";
|
||||
|
||||
@@ -1133,9 +1157,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"How to use it" = "Comment l'utiliser";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"How to use markdown" = "Comment utiliser markdown";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"How to use your servers" = "Comment utiliser vos serveurs";
|
||||
|
||||
@@ -1172,6 +1193,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Import database" = "Importer la base de données";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Improved privacy and security" = "Une meilleure sécurité et protection de la vie privée";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Improved server configuration" = "Configuration de serveur améliorée";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Incognito" = "Incognito";
|
||||
|
||||
@@ -1217,9 +1244,18 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Instantly" = "Instantanément";
|
||||
|
||||
/* invalid chat data */
|
||||
"invalid chat" = "chat invalide";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"invalid chat data" = "données de chat invalides";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Invalid connection link" = "Lien de connection invalide";
|
||||
|
||||
/* invalid chat item */
|
||||
"invalid data" = "données invalides";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Invalid server address!" = "Adresse de serveur invalide !";
|
||||
|
||||
@@ -1253,6 +1289,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"iOS Keychain will be used to securely store passphrase after you restart the app or change passphrase - it will allow receiving push notifications." = "La keychain d'iOS sera utilisée pour stocker en toute sécurité la phrase secrète après le redémarrage de l'app ou la modification de la phrase secrète - il permettra de recevoir les notifications push.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Irreversible message deletion" = "Suppression irréversible des messages";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Irreversible message deletion is prohibited in this chat." = "La suppression irréversible de message est interdite dans ce chat.";
|
||||
|
||||
@@ -1316,6 +1355,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Live message!" = "Message dynamique !";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Live messages" = "Messages dynamiques";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Local name" = "Nom local";
|
||||
|
||||
@@ -1329,7 +1371,7 @@
|
||||
"Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Assurez-vous que les adresses des serveurs WebRTC ICE sont au bon format et ne sont pas dupliquées, un par ligne.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Beaucoup de gens ont demandé : *si SimpleX n'a pas d'identifiants d'utilisateur, comment peut-il délivrer des messages?*";
|
||||
"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Beaucoup se demandent : *si SimpleX n'a pas d'identifiants d'utilisateur, comment peut-il délivrer des messages?*";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Mark deleted for everyone" = "Marquer comme supprimé pour tout le monde";
|
||||
@@ -1344,7 +1386,10 @@
|
||||
"Markdown in messages" = "Markdown dans les messages";
|
||||
|
||||
/* marked deleted chat item preview text */
|
||||
"marked deleted" = "marquer comme supprimé";
|
||||
"marked deleted" = "supprimé";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Max 30 seconds, received instantly." = "Max 30 secondes, réception immédiate.";
|
||||
|
||||
/* member role */
|
||||
"member" = "membre";
|
||||
@@ -1421,6 +1466,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"New database archive" = "Nouvelle archive de base de données";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"New in %@" = "Nouveautés de la %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"New member role" = "Nouveau rôle";
|
||||
|
||||
@@ -1534,7 +1582,7 @@
|
||||
"Open Settings" = "Ouvrir les Paramètres";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open-source protocol and code – anybody can run the servers." = "Protocole et code open-source – tout le monde peut faire fonctionner les serveurs.";
|
||||
"Open-source protocol and code – anybody can run the servers." = "Protocole et code open-source – n'importe qui peut heberger un serveur.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red." = "Ouvrir le lien dans le navigateur peut réduire la confidentialité et la sécurité de la connexion. Les liens SimpleX non fiables seront en rouge.";
|
||||
@@ -1659,6 +1707,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Receiving via" = "Réception via";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Recipients see updates as you type them." = "Les destinataires voient les mises à jour au fur et à mesure que vous les tapez.";
|
||||
|
||||
/* reject incoming call via notification */
|
||||
"Reject" = "Rejeter";
|
||||
|
||||
@@ -1786,7 +1837,7 @@
|
||||
"Scan security code from your contact's app." = "Scannez le code de sécurité depuis l'application de votre contact.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Scan server QR code" = "Scanner le code QR du serveur";
|
||||
"Scan server QR code" = "Scanner un code QR de serveur";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Search" = "Chercher";
|
||||
@@ -1800,6 +1851,9 @@
|
||||
/* server test step */
|
||||
"Secure queue" = "File d'attente sécurisée";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Security assessment" = "Évaluation de sécurité";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Security code" = "Code de sécurité";
|
||||
|
||||
@@ -1827,6 +1881,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Send questions and ideas" = "Envoyez vos questions et idées";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send them from gallery or custom keyboards." = "Envoyez-les depuis la phototèque ou des claviers personnalisés.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sender cancelled file transfer." = "L'expéditeur a annulé le transfert de fichiers.";
|
||||
|
||||
@@ -1839,6 +1896,9 @@
|
||||
/* notification */
|
||||
"Sent file event" = "Événement de fichier envoyé";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sent messages will be deleted after set time." = "Les messages envoyés seront supprimés après une durée déterminée.";
|
||||
|
||||
/* server test error */
|
||||
"Server requires authorization to create queues, check password" = "Le serveur requiert une autorisation pour créer des files d'attente, vérifiez le mot de passe";
|
||||
|
||||
@@ -1848,6 +1908,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Servers" = "Serveurs";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Set 1 day" = "Définir 1 jour";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Set contact name…" = "Définir le nom du contact…";
|
||||
|
||||
@@ -1881,6 +1944,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Show QR code" = "Afficher le code QR";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"SimpleX Chat security was [audited by Trail of Bits](https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html)." = "La sécurité de SimpleX Chat a été [auditée par Trail of Bits](https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html).";
|
||||
|
||||
/* simplex link type */
|
||||
"SimpleX contact address" = "Adresse de contact SimpleX";
|
||||
|
||||
@@ -2050,7 +2116,7 @@
|
||||
"This group no longer exists." = "Ce groupe n'existe plus.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"To ask any questions and to receive updates:" = "Pour poser toute question et recevoir des mises à jour :";
|
||||
"To ask any questions and to receive updates:" = "Si vous avez des questions et que vous souhaitez des réponses :";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"To find the profile used for an incognito connection, tap the contact or group name on top of the chat." = "Pour trouver le profil utilisé lors d'une connexion incognito, appuyez sur le nom du contact ou du groupe en haut du chat.";
|
||||
@@ -2062,7 +2128,7 @@
|
||||
"To prevent the call interruption, enable Do Not Disturb mode." = "Pour éviter l'interruption des appels, activez le mode Ne pas déranger.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." = "Pour protéger la vie privée, au lieu des ID utilisateurs utilisés par toutes les autres plateformes, SimpleX a des ID pour les queues de messages, distinctes pour chacun de vos contacts.";
|
||||
"To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." = "Pour protéger votre vie privée, au lieu d’IDs utilisés par toutes les autres plateformes, SimpleX a des IDs pour les queues de messages, distinctes pour chacun de vos contacts.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"To protect your information, turn on SimpleX Lock.\nYou will be prompted to complete authentication before this feature is enabled." = "Pour protéger vos informations, activez la fonction SimpleX Lock.\nVous serez invité à confirmer l'authentification avant que cette fonction ne soit activée.";
|
||||
@@ -2169,6 +2235,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"v%@ (%@)" = "v%@ (%@)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Verify connection security" = "Vérifier la sécurité de la connexion";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Verify security code" = "Vérifier le code de sécurité";
|
||||
|
||||
@@ -2235,12 +2304,18 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Welcome message" = "Message de bienvenue";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"What's new" = "Quoi de neuf ?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"When available" = "Quand disponible";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Lorsque vous partagez un profil incognito avec quelqu'un, ce profil sera utilisé pour les groupes auxquels il vous invite.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"With optional welcome message." = "Avec message de bienvenue facultatif.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Wrong database passphrase" = "Mauvaise phrase secrète pour la base de données";
|
||||
|
||||
@@ -2284,7 +2359,7 @@
|
||||
"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." = "Vous pouvez partager un lien ou un code QR - n'importe qui pourra rejoindre le groupe. Vous ne perdrez pas les membres du groupe si vous le supprimez par la suite.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it." = "Vous pouvez partager votre adresse sous forme de lien ou de code QR - n'importe qui pourra se connecter à vous. Vous ne perdrez pas vos contacts si vous les supprimez par la suite.";
|
||||
"You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it." = "Vous pouvez partager votre adresse sous forme de lien ou de code QR - n'importe qui pourra se connecter à vous. Vous ne perdrez pas vos contacts si vous la supprimez par la suite.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can start chat via app Settings / Database or by restarting the app" = "Vous pouvez lancer le chat via Paramètres / Base de données ou en redémarrant l'app";
|
||||
@@ -2305,7 +2380,7 @@
|
||||
"you changed role of %@ to %@" = "vous avez modifié le rôle de %1$@ pour %2$@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them." = "Vous contrôlez par quel·s serveur·s vous pouvez **transmettre** ainsi que par quel·s serveur·s vous pouvez **recevoir** des messages de vos contacts.";
|
||||
"You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them." = "Vous contrôlez par quel·s serveur·s vous pouvez **transmettre** ainsi que par quel·s serveur·s vous pouvez **recevoir** les messages de vos contacts.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You could not be verified; please try again." = "Vous n'avez pas pu être vérifié·e ; veuillez réessayer.";
|
||||
@@ -2409,6 +2484,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Your contact sent a file that is larger than currently supported maximum size (%@)." = "Votre contact a envoyé un fichier plus grand que la taille maximale supportée actuellement(%@).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contacts can allow full message deletion." = "Vos contacts peuvent autoriser la suppression complète des messages.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your current chat database will be DELETED and REPLACED with the imported one." = "Votre base de données de chat actuelle va être SUPPRIMEE et REMPLACEE par celle importée.";
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
" " = " ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
" " = " ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
" " = " ";
|
||||
|
||||
@@ -230,12 +233,18 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Add server…" = "Добавить сервер…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add servers by scanning QR codes." = "Добавить серверы через QR код.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add to another device" = "Добавить на другое устройство";
|
||||
|
||||
/* member role */
|
||||
"admin" = "админ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Admins can create the links to join groups." = "Админы могут создать ссылки для вступления в группу.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Advanced network settings" = "Настройки сети";
|
||||
|
||||
@@ -314,6 +323,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Authentication unavailable" = "Аутентификация недоступна";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Auto-accept contact requests" = "Автоматически принимать запросы контактов";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Auto-accept images" = "Автоприем изображений";
|
||||
|
||||
@@ -365,6 +377,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Cancel" = "Отменить";
|
||||
|
||||
/* feature offered item */
|
||||
"cancelled %@" = "отменил(a) %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cannot access keychain to save database password" = "Ошибка доступа к Keychain при сохранении пароля";
|
||||
|
||||
@@ -458,6 +473,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Colors" = "Цвета";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Compare security codes with your contacts." = "Сравните код безопасности с вашими контактами.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"complete" = "соединение завершено";
|
||||
|
||||
@@ -1052,6 +1070,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Full name:" = "Полное имя:";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"GIFs and stickers" = "ГИФ файлы и стикеры";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Group" = "Группа";
|
||||
|
||||
@@ -1079,6 +1100,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Group link" = "Ссылка группы";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Group links" = "Ссылки групп";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Group members can irreversibly delete sent messages." = "Члены группы могут необратимо удалять отправленные сообщения.";
|
||||
|
||||
@@ -1121,6 +1145,9 @@
|
||||
/* chat item action */
|
||||
"Hide" = "Спрятать";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Hide app screen in the recent apps." = "Скрыть экран приложения.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"How it works" = "Как это работает";
|
||||
|
||||
@@ -1133,9 +1160,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"How to use it" = "Как использовать";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"How to use markdown" = "Как форматировать";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"How to use your servers" = "Как использовать серверы";
|
||||
|
||||
@@ -1172,6 +1196,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Import database" = "Импорт архива чата";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Improved privacy and security" = "Улучшенная безопасность";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Improved server configuration" = "Улучшенная конфигурация серверов";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Incognito" = "Инкогнито";
|
||||
|
||||
@@ -1217,9 +1247,18 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Instantly" = "Мгновенно";
|
||||
|
||||
/* invalid chat data */
|
||||
"invalid chat" = "ошибка чата";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"invalid chat data" = "ошибка данных чата";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Invalid connection link" = "Ошибка в ссылке контакта";
|
||||
|
||||
/* invalid chat item */
|
||||
"invalid data" = "ошибка данных";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Invalid server address!" = "Ошибка в адресе сервера!";
|
||||
|
||||
@@ -1253,6 +1292,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"iOS Keychain will be used to securely store passphrase after you restart the app or change passphrase - it will allow receiving push notifications." = "Пароль базы данных будет безопасно сохранен в iOS Keychain после запуска чата или изменения пароля - это позволит получать мгновенные уведомления.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Irreversible message deletion" = "Окончательное удаление сообщений";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Irreversible message deletion is prohibited in this chat." = "Необратимое удаление сообщений запрещено в этом чате.";
|
||||
|
||||
@@ -1316,6 +1358,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Live message!" = "Живое сообщение!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Live messages" = "\"Живые\" сообщения";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Local name" = "Локальное имя";
|
||||
|
||||
@@ -1346,6 +1391,9 @@
|
||||
/* marked deleted chat item preview text */
|
||||
"marked deleted" = "помечено к удалению";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Max 30 seconds, received instantly." = "Макс. 30 секунд, доставляются мгновенно.";
|
||||
|
||||
/* member role */
|
||||
"member" = "член группы";
|
||||
|
||||
@@ -1421,6 +1469,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"New database archive" = "Новый архив чата";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"New in %@" = "Новое в %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"New member role" = "Роль члена группы";
|
||||
|
||||
@@ -1473,6 +1524,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Off (Local)" = "Выключить (Локальные)";
|
||||
|
||||
/* feature offered item */
|
||||
"offered %@" = "предложил(a) %@";
|
||||
|
||||
/* feature offered item */
|
||||
"offered %@: %@" = "предложил(a) %1$@: %2$@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Ok" = "Ок";
|
||||
|
||||
@@ -1659,6 +1716,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Receiving via" = "Получение через";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Recipients see updates as you type them." = "Получатели видят их в то время как вы их набираете.";
|
||||
|
||||
/* reject incoming call via notification */
|
||||
"Reject" = "Отклонить";
|
||||
|
||||
@@ -1800,6 +1860,9 @@
|
||||
/* server test step */
|
||||
"Secure queue" = "Защита очереди";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Security assessment" = "Аудит безопасности";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Security code" = "Код безопасности";
|
||||
|
||||
@@ -1827,6 +1890,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Send questions and ideas" = "Отправьте вопросы и идеи";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send them from gallery or custom keyboards." = "Отправьте из галереи или из дополнительных клавиатур.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sender cancelled file transfer." = "Отправитель отменил передачу файла.";
|
||||
|
||||
@@ -1839,6 +1905,9 @@
|
||||
/* notification */
|
||||
"Sent file event" = "Отправка файла";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sent messages will be deleted after set time." = "Отправленные сообщения будут удалены через заданное время.";
|
||||
|
||||
/* server test error */
|
||||
"Server requires authorization to create queues, check password" = "Сервер требует авторизации для создания очередей, проверьте пароль";
|
||||
|
||||
@@ -1848,6 +1917,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Servers" = "Серверы";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Set 1 day" = "Установить 1 день";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Set contact name…" = "Имя контакта…";
|
||||
|
||||
@@ -1881,6 +1953,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Show QR code" = "Показать QR код";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"SimpleX Chat security was [audited by Trail of Bits](https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html)." = "Безопасность SimpleX Chat была [проверена Trail of Bits](https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html).";
|
||||
|
||||
/* simplex link type */
|
||||
"SimpleX contact address" = "SimpleX ссылка-контакт";
|
||||
|
||||
@@ -2169,6 +2244,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"v%@ (%@)" = "v%@ (%@)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Verify connection security" = "Проверить безопасность соединения";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Verify security code" = "Подтвердить код безопасности";
|
||||
|
||||
@@ -2235,12 +2313,18 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Welcome message" = "Приветственное сообщение";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"What's new" = "Новые функции";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"When available" = "Когда возможно";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Когда вы соединены с контактом инкогнито, тот же самый инкогнито профиль будет использоваться для групп с этим контактом.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"With optional welcome message." = "С опциональным авто-ответом.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Wrong database passphrase" = "Неправильный пароль базы данных";
|
||||
|
||||
@@ -2409,6 +2493,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Your contact sent a file that is larger than currently supported maximum size (%@)." = "Ваш контакт отправил файл, размер которого превышает максимальный размер (%@).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contacts can allow full message deletion." = "Ваши контакты могут разрешить окончательное удаление сообщений.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your current chat database will be DELETED and REPLACED with the imported one." = "Текущие данные вашего чата будет УДАЛЕНЫ и ЗАМЕНЕНЫ импортированными.";
|
||||
|
||||
|
||||
103
blog/20230103-simplex-chat-v4.4-disappearing-messages.md
Normal file
103
blog/20230103-simplex-chat-v4.4-disappearing-messages.md
Normal file
@@ -0,0 +1,103 @@
|
||||
---
|
||||
layout: layouts/article.html
|
||||
title: "SimpleX Chat v4.4 released – with disappearing messages, live messages, connection security verification and French language!"
|
||||
date: 2023-01-03
|
||||
image: images/20230103-disappearing1.png
|
||||
imageBottom: true
|
||||
previewBody: blog_previews/20230103.html
|
||||
permalink: "/blog/20230103-simplex-chat-v4.4-disappearing-messages.html"
|
||||
---
|
||||
|
||||
# SimpleX Chat v4.4 released – with disappearing messages, live messages, connection security verification and French language!
|
||||
|
||||
**Published:** Jan 3, 2023
|
||||
|
||||
## What's new in v4.4
|
||||
|
||||
- [disappearing messages](#disappearing-messages).
|
||||
- ["live" messages](#live-messages).
|
||||
- [connection security verification](#connection-security-verification).
|
||||
- [animated images and stickers](#animated-images-and-stickers) – now on iOS too.
|
||||
|
||||
Also, we added [French language interface](#french-language-interface), thanks to the users' community and Weblate!
|
||||
|
||||
### Disappearing messages
|
||||
|
||||
<img src="./images/20230103-disappearing1.png" width="288"> <img src="./images/20230103-disappearing2.png" width="288">
|
||||
|
||||
It is now possible to send the messages that will be deleted from both sender and recipient device after set time – for the sender from the time they were sent, and for the recipient - from the time they were read.
|
||||
|
||||
Unlike in most other messengers, it requires agreement of both sides, not just the sender decision. I [wrote previously](./20221206-simplex-chat-v4.3-voice-messages.md#irreversible-message-deletion) why we believe it is wrong to allow the senders to delete their messages without recipient consent, and the same logic applies here – if you want to send the message that will disappear after some time, your contact should be ok with that too.
|
||||
|
||||
In group conversations disappearing messages can be enabled by the group owners, by default they are disabled.
|
||||
|
||||
### "Live" messages
|
||||
|
||||
<img src="./images/20230103-live.png" width="288">
|
||||
|
||||
Pressing "bolt" button before you start typing the message will start a "live" message. Now, as you type it, it will be updated for all recipients every several seconds, including only complete words. To finish the message you need to press "checkmark" button.
|
||||
|
||||
You can also start a live message after you started typing or after you chose the image – long-press send button and then press "Send live message".
|
||||
|
||||
### Connection security verification
|
||||
|
||||
<img src="./images/20230103-verification.png" width="288">
|
||||
|
||||
SimpleX Chat design prevents the possibility of messaging servers substituting the key during the initial connection (man-in-the-middle attack) by requiring that the invitation link is passed via another channel. I wrote more about how MITM attack works in [this post](https://www.poberezkin.com/posts/2022-12-07-why-privacy-needs-to-be-redefined.html). But this other channel, however unlikely, could still have been compromised by an attacker to replace the invitation link you sent. That is the reason why we recommend sharing QR code in a video call – this is very complex for an attacker to replace it in this case.
|
||||
|
||||
This new feature allows you to verify, via yet another channel, that the connection is secure and the keys were not replaced. You can either scan the security code from your contact's app, or compare codes visually, or even read it in a voice call – if your and your contact's app have the same security code for each other then the connection is secure.
|
||||
|
||||
If you are sending direct messages to some group members then it might also be important to verify security of these connections, as in this case the invitations were exchanged via the member who added you or another member, and if this member's client was modified, they could have replaced the keys and the addresses, and intercept the entire conversation.
|
||||
|
||||
Regardless how connection is established, verifying the connection proves its security. Technically, this security code is the hash of associated data used in the end-to-end encryption, which in turn is taken by combining public keys from the initial key exchange.
|
||||
|
||||
### Animated images and stickers
|
||||
|
||||
<img src="./images/20230103-stickers1.png" width="288"> <img src="./images/20230103-stickers2.png" width="303">
|
||||
|
||||
Android app supported GIFs and stickers for some time, now you can view and send them from iOS app as well, e.g. using GIPHY keyboard - you no longer need to choose between privacy and stickers. Just bear in mind, that third party keyboards can be insecure, so you should not be using them for typing sensitive information.
|
||||
|
||||
### French language interface
|
||||
|
||||
Thanks to our users' community and to [Weblate](https://weblate.org/en-gb/) kindly providing a free hosting plan for SimpleX Chat translations we can now support more languages in the interface – this version adds French.
|
||||
|
||||
Please get in touch if you want to translate the interface into your language!
|
||||
|
||||
## SimpleX platform
|
||||
|
||||
Some links to answer the most common questions:
|
||||
|
||||
[How can SimpleX deliver messages without user identifiers](./20220511-simplex-chat-v2-images-files.md#the-first-messaging-platform-without-user-identifiers).
|
||||
|
||||
[What are the risks to have identifiers assigned to the users](./20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md#why-having-users-identifiers-is-bad-for-the-users).
|
||||
|
||||
[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-technical-details-and-limitations).
|
||||
|
||||
[How SimpleX is different from Session, Matrix, Signal, etc.](https://github.com/simplex-chat/simplex-chat/blob/stable/README.md#frequently-asked-questions).
|
||||
|
||||
Please also see our [website](https://simplex.chat).
|
||||
|
||||
## 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, makes 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
|
||||
- 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
|
||||
@@ -1,6 +1,15 @@
|
||||
# Blog
|
||||
|
||||
Dec 12, 2022 [SimpleX Chat reviews and v4.3 released]
|
||||
Jan 3, 2023 [SimpleX Chat v4.4 released](./20230103-simplex-chat-v4.4-disappearing-messages.md)
|
||||
|
||||
- disappearing messages.
|
||||
- "live" messages.
|
||||
- connection security verification.
|
||||
- animated images and stickers – now on iOS too.
|
||||
|
||||
Also, we added [French language interface](#french-language-interface), thanks to our users and Weblate!
|
||||
|
||||
Dec 6, 2022 [SimpleX Chat reviews and v4.3 released](./20221206-simplex-chat-v4.3-voice-messages.md)
|
||||
|
||||
November reviews:
|
||||
|
||||
|
||||
BIN
blog/images/20230103-disappearing1.png
Normal file
BIN
blog/images/20230103-disappearing1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 353 KiB |
BIN
blog/images/20230103-disappearing2.png
Normal file
BIN
blog/images/20230103-disappearing2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 145 KiB |
BIN
blog/images/20230103-live.png
Normal file
BIN
blog/images/20230103-live.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 141 KiB |
BIN
blog/images/20230103-stickers1.png
Normal file
BIN
blog/images/20230103-stickers1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 343 KiB |
BIN
blog/images/20230103-stickers2.png
Normal file
BIN
blog/images/20230103-stickers2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 480 KiB |
BIN
blog/images/20230103-verification.png
Normal file
BIN
blog/images/20230103-verification.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 229 KiB |
@@ -7,7 +7,7 @@ constraints: zip +disable-bzip2 +disable-zstd
|
||||
source-repository-package
|
||||
type: git
|
||||
location: https://github.com/simplex-chat/simplexmq.git
|
||||
tag: fb21d9836e07706c7498baa967f932cb11b818e5
|
||||
tag: 058e3ac55e8577280267f9341ccd7d3e971bc51a
|
||||
|
||||
source-repository-package
|
||||
type: git
|
||||
|
||||
130
docs/rfcs/2022-12-17-user-profiles.md
Normal file
130
docs/rfcs/2022-12-17-user-profiles.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# User profiles
|
||||
|
||||
## Problem
|
||||
|
||||
Convenience of having different communication contexts in a single client without shared metadata.
|
||||
|
||||
Currently requires changing chat database and restarting chat.
|
||||
|
||||
Events (notifications) for databases other than current are not received.
|
||||
|
||||
## Solution
|
||||
|
||||
Support for multiple user profiles in a single database.
|
||||
|
||||
Separate transport connections with the same servers not shared between profiles.
|
||||
|
||||
## Design
|
||||
|
||||
### API
|
||||
|
||||
- new users are created using existing CreateActiveUser API
|
||||
|
||||
- APIListUsers
|
||||
|
||||
- APISetActiveUser UserId
|
||||
|
||||
- CRUsersList {users :: [User]}
|
||||
|
||||
- terminal APIs
|
||||
|
||||
### Subscriptions
|
||||
|
||||
- get connections for all users, subscribe to all - to receive events for users other than active
|
||||
|
||||
- map subscriptions to users?
|
||||
|
||||
- option to subscribe to connections for only one user? `StartChat {allUsers :: Bool}` API
|
||||
|
||||
- if more than one user profiles - start with last active user (persist?), or always load list of users first and wait for choice? same for terminal?
|
||||
|
||||
### Chat items expiration
|
||||
|
||||
- store supports this configuration per user
|
||||
|
||||
- expire chat items for all users with different ttl
|
||||
|
||||
- in `runExpireCIs` get list of users, for each - `getChatItemTTL` and run `expireChatItems`
|
||||
|
||||
### Disappearing messages
|
||||
|
||||
- `cleanupManager` - remove parameterization by User
|
||||
|
||||
- either `getTimedItems` and start deletion threads for all users, or similar to expiration get list of users and run `cleanupTimedItems` per user
|
||||
|
||||
### Events
|
||||
|
||||
- events need to have information for which user they happened
|
||||
|
||||
```haskell
|
||||
data UserChatResponse = UserChatResponse
|
||||
{ user :: User,
|
||||
chatResponse :: ChatResponse
|
||||
}
|
||||
```
|
||||
|
||||
- replace ChatResponse with UserChatResponse in APIResponse, toView, etc.
|
||||
|
||||
- in `agentSubscriber` don't get currentUser from controller, instead either:
|
||||
|
||||
- get user from database by agent connection id
|
||||
|
||||
- get from subscriptions user-connections map - either requires scan or keys and values have to be inverted, also key would have to be agent connection id not database connection id - probably easier/better to read from db
|
||||
|
||||
- non active user profile can be shown in notifications
|
||||
|
||||
- interactions via notifications - prohibit for simplicity? or change APIs to allow specifying User?
|
||||
|
||||
- changes to chat model are applied only for current user
|
||||
|
||||
### ChatController
|
||||
|
||||
- currentCalls:
|
||||
|
||||
- change `currentCalls` key? `currentCalls :: TMap (UserId, ContactId) Call`
|
||||
|
||||
- `restoreCalls` - for all users?
|
||||
|
||||
- interaction via notification - prohibit for non active user? change current user before accepting?
|
||||
|
||||
- `incognitoMode` - save setting or share between users?
|
||||
|
||||
- when changing active user reset `activeTo`?
|
||||
|
||||
- in `newChatController` when creating smpAgent - `getSMPServers` have to get servers for all users, or depending on allUsers flag in StartChat; // drop known_servers table?
|
||||
|
||||
- `AgentConfig` should depend on network configuration per user?
|
||||
|
||||
- double check other state
|
||||
|
||||
### Storage
|
||||
|
||||
- schema already has support for multiple users
|
||||
|
||||
- persist last active user?
|
||||
|
||||
- persist user settings? (see below)
|
||||
|
||||
### Frontend
|
||||
|
||||
- view with list of user profiles available from settings, change active user from there
|
||||
|
||||
- do we need additional information in that view? for example, number of unread messages
|
||||
|
||||
- network configuration is stored in app preferences - if unchanged will be the same across users
|
||||
|
||||
- persist in database per user and load?
|
||||
|
||||
- different configuration across platforms
|
||||
|
||||
- save network settings in separate preference for each user as json? dynamic preferences or predefined number of users?
|
||||
|
||||
- same for other settings - auto-accept images, send link previews, etc.
|
||||
|
||||
### Terminal view
|
||||
|
||||
- for users other than active view responses to have indication that it's for a different user profile, e.g. `[<user_name>]`, `[user: <user_name>]`
|
||||
|
||||
- don't set `activeTo` for non active user profiles
|
||||
|
||||
- only way to reply to a message in other user profile is to manually switch current active user first?
|
||||
12
flake.lock
generated
12
flake.lock
generated
@@ -302,11 +302,11 @@
|
||||
"hackage": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1669857312,
|
||||
"narHash": "sha256-m0jYF2gOKTaCcedV+dZkCjVbfv0CWkRziCeEk/NF/34=",
|
||||
"lastModified": 1672446463,
|
||||
"narHash": "sha256-N5dcK1V+BLQeri5oB0ZSk2ABPs/hTGBC7jZvcY9CVLs=",
|
||||
"owner": "input-output-hk",
|
||||
"repo": "hackage.nix",
|
||||
"rev": "8299f5acc68f0e91563e7688f24cbc70391600bf",
|
||||
"rev": "7289869780da23633bc193e11d2da94ecee1489d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -343,11 +343,11 @@
|
||||
"tullia": "tullia"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1669979126,
|
||||
"narHash": "sha256-CZBKwljLLv2GhE7jq7Gr54rJhxM0TRTFSM+Ix5H52ak=",
|
||||
"lastModified": 1672501055,
|
||||
"narHash": "sha256-Wy6KqoYqQOP1rBvfHUvM3ex8HIdBA4kwnvZj2qQ1VLU=",
|
||||
"owner": "simplex-chat",
|
||||
"repo": "haskell.nix",
|
||||
"rev": "3634dc742f197396880593d9007465bccbb7292c",
|
||||
"rev": "dc719cd6dc318923c4a524d005c13f474c00d3d3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: simplex-chat
|
||||
version: 4.4.0
|
||||
version: 4.4.1
|
||||
#synopsis:
|
||||
#description:
|
||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||
|
||||
@@ -7,7 +7,7 @@ function readlink() {
|
||||
}
|
||||
|
||||
if [ -z ${1} ]; then
|
||||
echo "Job repo is unset. Provide it via first argument like: $(readlink $0)/download_libs_aarch64.sh https://something.com/job/something"
|
||||
echo "Job repo is unset. Provide it via first argument like: $(readlink $0)/download_libs_aarch64.sh https://something.com/job/something/{master,stable}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -22,12 +22,12 @@ output_dir="$root_dir/apps/android/app/src/main/cpp/libs/$output_arch/"
|
||||
|
||||
mkdir -p "$output_dir" 2> /dev/null
|
||||
|
||||
curl --location -o libsupport.zip $job_repo/simplex-chat/$arch-android:lib:support.x86_64-linux/latest/download/1 && \
|
||||
curl --location -o libsupport.zip $job_repo/$arch-android:lib:support.x86_64-linux/latest/download/1 && \
|
||||
unzip -o libsupport.zip && \
|
||||
mv libsupport.so "$output_dir" && \
|
||||
rm libsupport.zip
|
||||
|
||||
curl --location -o libsimplex.zip $job_repo/simplex-chat/$arch-android:lib:simplex-chat.x86_64-linux/latest/download/1 && \
|
||||
curl --location -o libsimplex.zip $job_repo/$arch-android:lib:simplex-chat.x86_64-linux/latest/download/1 && \
|
||||
unzip -o libsimplex.zip && \
|
||||
mv libsimplex.so "$output_dir" && \
|
||||
rm libsimplex.zip
|
||||
|
||||
@@ -7,7 +7,7 @@ function readlink() {
|
||||
}
|
||||
|
||||
if [ -z ${1} ]; then
|
||||
echo "Job repo is unset. Provide it via first argument like: $(readlink $0)/download_libs_aarch64.sh https://something.com/job/something"
|
||||
echo "Job repo is unset. Provide it via first argument like: $(readlink $0)/download_libs_aarch64.sh https://something.com/job/something/{master,stable}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -15,10 +15,10 @@ job_repo=$1
|
||||
|
||||
root_dir="$(dirname $(dirname $(readlink $0)))"
|
||||
|
||||
curl --location -o ~/Downloads/pkg-ios-aarch64-swift-json.zip $job_repo/simplex-chat/aarch64-darwin-ios:lib:simplex-chat.aarch64-darwin/latest/download/1 && \
|
||||
curl --location -o ~/Downloads/pkg-ios-aarch64-swift-json.zip $job_repo/aarch64-darwin-ios:lib:simplex-chat.aarch64-darwin/latest/download/1 && \
|
||||
unzip -o ~/Downloads/pkg-ios-aarch64-swift-json.zip -d ~/Downloads/pkg-ios-aarch64-swift-json
|
||||
|
||||
curl --location -o ~/Downloads/pkg-ios-x86_64-swift-json.zip $job_repo/simplex-chat/x86_64-darwin-ios:lib:simplex-chat.x86_64-darwin/latest/download/1 && \
|
||||
curl --location -o ~/Downloads/pkg-ios-x86_64-swift-json.zip $job_repo/x86_64-darwin-ios:lib:simplex-chat.x86_64-darwin/latest/download/1 && \
|
||||
unzip -o ~/Downloads/pkg-ios-x86_64-swift-json.zip -d ~/Downloads/pkg-ios-x86_64-swift-json
|
||||
|
||||
sh $root_dir/scripts/ios/prepare-x86_64.sh
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"https://github.com/simplex-chat/simplexmq.git"."fb21d9836e07706c7498baa967f932cb11b818e5" = "0dl08ag38d1azzil1xxi6xrzqwfcv550wi5kjdmxn4h820icl2ja";
|
||||
"https://github.com/simplex-chat/simplexmq.git"."058e3ac55e8577280267f9341ccd7d3e971bc51a" = "1rw0j3d5higdrq5klsgnj8b8zfh08g5zv72hqcm7wkw1mmllpfrk";
|
||||
"https://github.com/simplex-chat/direct-sqlcipher.git"."34309410eb2069b029b8fc1872deb1e0db123294" = "0kwkmhyfsn2lixdlgl15smgr1h5gjk7fky6abzh8rng2h5ymnffd";
|
||||
"https://github.com/simplex-chat/sqlcipher-simple.git"."5e154a2aeccc33ead6c243ec07195ab673137221" = "1d1gc5wax4vqg0801ajsmx1sbwvd9y7p7b8mmskvqsmpbwgbh0m0";
|
||||
"https://github.com/simplex-chat/aeson.git"."3eb66f9a68f103b5f1489382aad89f5712a64db7" = "0kilkx59fl6c3qy3kjczqvm8c3f4n3p0bdk9biyflf51ljnzp4yp";
|
||||
|
||||
@@ -5,7 +5,7 @@ cabal-version: 1.12
|
||||
-- see: https://github.com/sol/hpack
|
||||
|
||||
name: simplex-chat
|
||||
version: 4.4.0
|
||||
version: 4.4.1
|
||||
category: Web, System, Services, Cryptography
|
||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||
author: simplex.chat
|
||||
@@ -72,6 +72,8 @@ library
|
||||
Simplex.Chat.Migrations.M20221214_live_message
|
||||
Simplex.Chat.Migrations.M20221222_chat_ts
|
||||
Simplex.Chat.Migrations.M20221223_idx_chat_items_item_status
|
||||
Simplex.Chat.Migrations.M20221230_idxs
|
||||
Simplex.Chat.Migrations.M20230107_connections_auth_err_counter
|
||||
Simplex.Chat.Mobile
|
||||
Simplex.Chat.Options
|
||||
Simplex.Chat.ProfileGenerator
|
||||
|
||||
@@ -57,6 +57,7 @@ import Simplex.Chat.Store
|
||||
import Simplex.Chat.Types
|
||||
import Simplex.Chat.Util (diffInMicros, diffInSeconds)
|
||||
import Simplex.Messaging.Agent as Agent
|
||||
import Simplex.Messaging.Agent.Client (AgentStatsKey (..))
|
||||
import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), AgentDatabase (..), InitialAgentServers (..), createAgentStore, defaultAgentConfig)
|
||||
import Simplex.Messaging.Agent.Lock
|
||||
import Simplex.Messaging.Agent.Protocol
|
||||
@@ -134,7 +135,7 @@ createChatDatabase filePrefix key yesToMigrations = do
|
||||
pure ChatDatabase {chatStore, agentStore}
|
||||
|
||||
newChatController :: ChatDatabase -> Maybe User -> ChatConfig -> ChatOpts -> Maybe (Notification -> IO ()) -> IO ChatController
|
||||
newChatController ChatDatabase {chatStore, agentStore} user cfg@ChatConfig {agentConfig = aCfg, tbqSize, defaultServers, inlineFiles} ChatOpts {smpServers, networkConfig, logConnections, logServerHosts, allowInstantFiles} sendToast = do
|
||||
newChatController ChatDatabase {chatStore, agentStore} user cfg@ChatConfig {agentConfig = aCfg, tbqSize, defaultServers, inlineFiles} ChatOpts {smpServers, networkConfig, logConnections, logServerHosts, optFilesFolder, allowInstantFiles} sendToast = do
|
||||
let inlineFiles' = if allowInstantFiles then inlineFiles else inlineFiles {sendChunks = 0, receiveInstant = False}
|
||||
config = cfg {subscriptionEvents = logConnections, hostEvents = logServerHosts, defaultServers = configServers, inlineFiles = inlineFiles'}
|
||||
sendNotification = fromMaybe (const $ pure ()) sendToast
|
||||
@@ -151,7 +152,7 @@ newChatController ChatDatabase {chatStore, agentStore} user cfg@ChatConfig {agen
|
||||
sndFiles <- newTVarIO M.empty
|
||||
rcvFiles <- newTVarIO M.empty
|
||||
currentCalls <- atomically TM.empty
|
||||
filesFolder <- newTVarIO Nothing
|
||||
filesFolder <- newTVarIO optFilesFolder
|
||||
incognitoMode <- newTVarIO False
|
||||
chatStoreChanged <- newTVarIO False
|
||||
expireCIsAsync <- newTVarIO Nothing
|
||||
@@ -226,13 +227,21 @@ restoreCalls user = do
|
||||
calls <- asks currentCalls
|
||||
atomically $ writeTVar calls callsMap
|
||||
|
||||
stopChatController :: MonadUnliftIO m => ChatController -> m ()
|
||||
stopChatController ChatController {smpAgent, agentAsync = s, expireCIs} = do
|
||||
stopChatController :: forall m. MonadUnliftIO m => ChatController -> m ()
|
||||
stopChatController ChatController {smpAgent, agentAsync = s, sndFiles, rcvFiles, expireCIs} = do
|
||||
disconnectAgentClient smpAgent
|
||||
readTVarIO s >>= mapM_ (\(a1, a2) -> uninterruptibleCancel a1 >> mapM_ uninterruptibleCancel a2)
|
||||
closeFiles sndFiles
|
||||
closeFiles rcvFiles
|
||||
atomically $ do
|
||||
writeTVar expireCIs False
|
||||
writeTVar s Nothing
|
||||
where
|
||||
closeFiles :: TVar (Map Int64 Handle) -> m ()
|
||||
closeFiles files = do
|
||||
fs <- readTVarIO files
|
||||
mapM_ hClose fs
|
||||
atomically $ writeTVar files M.empty
|
||||
|
||||
execChatCommand :: (MonadUnliftIO m, MonadReader ChatController m) => ByteString -> m ChatResponse
|
||||
execChatCommand s = case parseChatCommand s of
|
||||
@@ -831,6 +840,17 @@ processChatCommand = \case
|
||||
case activeConn of
|
||||
Just conn -> verifyConnectionCode user conn code
|
||||
_ -> throwChatError CEGroupMemberNotActive
|
||||
APIEnableContact contactId -> withUser $ \user -> do
|
||||
Contact {activeConn} <- withStore $ \db -> getContact db user contactId
|
||||
withStore' $ \db -> setConnectionAuthErrCounter db user activeConn 0
|
||||
pure CRCmdOk
|
||||
APIEnableGroupMember gId gMemberId -> withUser $ \user -> do
|
||||
GroupMember {activeConn} <- withStore $ \db -> getGroupMember db user gId gMemberId
|
||||
case activeConn of
|
||||
Just conn -> do
|
||||
withStore' $ \db -> setConnectionAuthErrCounter db user conn 0
|
||||
pure CRCmdOk
|
||||
_ -> throwChatError CEGroupMemberNotActive
|
||||
ShowMessages (ChatName cType name) ntfOn -> withUser $ \user -> do
|
||||
chatId <- case cType of
|
||||
CTDirect -> withStore $ \db -> getContactIdByName db user name
|
||||
@@ -845,6 +865,8 @@ processChatCommand = \case
|
||||
GetGroupMemberCode gName mName -> withMemberName gName mName APIGetGroupMemberCode
|
||||
VerifyContact cName code -> withContactName cName (`APIVerifyContact` code)
|
||||
VerifyGroupMember gName mName code -> withMemberName gName mName $ \gId mId -> APIVerifyGroupMember gId mId code
|
||||
EnableContact cName -> withContactName cName APIEnableContact
|
||||
EnableGroupMember gName mName -> withMemberName gName mName $ \gId mId -> APIEnableGroupMember gId mId
|
||||
ChatHelp section -> pure $ CRChatHelp section
|
||||
Welcome -> withUser $ pure . CRWelcome
|
||||
AddContact -> withUser $ \User {userId} -> withChatLock "addContact" . procCmd $ do
|
||||
@@ -1135,8 +1157,8 @@ processChatCommand = \case
|
||||
where
|
||||
processError ft = \case
|
||||
-- TODO AChatItem in Cancelled events
|
||||
ChatErrorAgent (SMP SMP.AUTH) -> pure $ CRRcvFileAcceptedSndCancelled ft
|
||||
ChatErrorAgent (CONN DUPLICATE) -> pure $ CRRcvFileAcceptedSndCancelled ft
|
||||
ChatErrorAgent (SMP SMP.AUTH) _ -> pure $ CRRcvFileAcceptedSndCancelled ft
|
||||
ChatErrorAgent (CONN DUPLICATE) _ -> pure $ CRRcvFileAcceptedSndCancelled ft
|
||||
e -> throwError e
|
||||
CancelFile fileId -> withUser $ \user@User {userId} ->
|
||||
withChatLock "cancelFile" . procCmd $
|
||||
@@ -1198,6 +1220,11 @@ processChatCommand = \case
|
||||
chatLockName <- atomically . tryReadTMVar =<< asks chatLock
|
||||
agentLocks <- withAgent debugAgentLocks
|
||||
pure CRDebugLocks {chatLockName, agentLocks}
|
||||
GetAgentStats -> CRAgentStats . map stat <$> withAgent getAgentStats
|
||||
where
|
||||
stat (AgentStatsKey {host, clientTs, cmd, res}, count) =
|
||||
map B.unpack [host, clientTs, cmd, res, bshow count]
|
||||
ResetAgentStats -> CRCmdOk <$ withAgent resetAgentStats
|
||||
where
|
||||
withChatLock name action = asks chatLock >>= \l -> withLock l name action
|
||||
-- below code would make command responses asynchronous where they can be slow
|
||||
@@ -1718,7 +1745,7 @@ subscribeUserConnections agentBatchSubscribe user = do
|
||||
pendingConnSubsToView :: Map ConnId (Either AgentErrorType ()) -> Map ConnId PendingContactConnection -> m ()
|
||||
pendingConnSubsToView rs = toView . CRPendingSubSummary . map (uncurry PendingSubStatus) . resultsFor rs
|
||||
withStore_ :: (DB.Connection -> User -> IO [a]) -> m [a]
|
||||
withStore_ a = withStore' (`a` user) `catchError` \_ -> pure []
|
||||
withStore_ a = withStore' (`a` user) `catchError` \e -> toView (CRChatError e) >> pure []
|
||||
filterErrors :: [(a, Maybe ChatError)] -> [(a, ChatError)]
|
||||
filterErrors = mapMaybe (\(a, e_) -> (a,) <$> e_)
|
||||
resultsFor :: Map ConnId (Either AgentErrorType ()) -> Map ConnId a -> [(a, Maybe ChatError)]
|
||||
@@ -1728,7 +1755,7 @@ subscribeUserConnections agentBatchSubscribe user = do
|
||||
addResult connId = (:) . (,err)
|
||||
where
|
||||
err = case M.lookup connId rs of
|
||||
Just (Left e) -> Just $ ChatErrorAgent e
|
||||
Just (Left e) -> Just $ ChatErrorAgent e Nothing
|
||||
Just _ -> Nothing
|
||||
_ -> Just . ChatError . CEAgentNoSubResult $ AgentConnId connId
|
||||
|
||||
@@ -1839,7 +1866,7 @@ expireChatItems user ttl sync = do
|
||||
(Just ts, Just count) -> when (count == 0) $ updateGroupTs db user gInfo ts
|
||||
_ -> pure ()
|
||||
|
||||
processAgentMessage :: forall m. ChatMonad m => Maybe User -> ConnId -> ACorrId -> ACommand 'Agent -> m ()
|
||||
processAgentMessage :: forall m. ChatMonad m => Maybe User -> ACorrId -> ConnId -> ACommand 'Agent -> m ()
|
||||
processAgentMessage Nothing _ _ _ = throwChatError CENoActiveUser
|
||||
processAgentMessage (Just User {userId}) _ "" agentMessage = case agentMessage of
|
||||
CONNECT p h -> hostEvent $ CRHostConnected p h
|
||||
@@ -1861,18 +1888,19 @@ processAgentMessage (Just user) _ agentConnId END =
|
||||
showToast (c <> "> ") "connected to another client"
|
||||
unsetActive $ ActiveC c
|
||||
entity -> toView $ CRSubscriptionEnd entity
|
||||
processAgentMessage (Just user@User {userId}) corrId agentConnId agentMessage =
|
||||
(withStore (\db -> getConnectionEntity db user $ AgentConnId agentConnId) >>= updateConnStatus) >>= \case
|
||||
processAgentMessage (Just user@User {userId}) corrId agentConnId agentMessage = do
|
||||
entity <- withStore (\db -> getConnectionEntity db user $ AgentConnId agentConnId) >>= updateConnStatus
|
||||
case entity of
|
||||
RcvDirectMsgConnection conn contact_ ->
|
||||
processDirectMessage agentMessage conn contact_
|
||||
processDirectMessage agentMessage entity conn contact_
|
||||
RcvGroupMsgConnection conn gInfo m ->
|
||||
processGroupMessage agentMessage conn gInfo m
|
||||
processGroupMessage agentMessage entity conn gInfo m
|
||||
RcvFileConnection conn ft ->
|
||||
processRcvFileConn agentMessage conn ft
|
||||
processRcvFileConn agentMessage entity conn ft
|
||||
SndFileConnection conn ft ->
|
||||
processSndFileConn agentMessage conn ft
|
||||
processSndFileConn agentMessage entity conn ft
|
||||
UserContactConnection conn uc ->
|
||||
processUserContactRequest agentMessage conn uc
|
||||
processUserContactRequest agentMessage entity conn uc
|
||||
where
|
||||
updateConnStatus :: ConnectionEntity -> m ConnectionEntity
|
||||
updateConnStatus acEntity = case agentMsgConnStatus agentMessage of
|
||||
@@ -1893,8 +1921,8 @@ processAgentMessage (Just user@User {userId}) corrId agentConnId agentMessage =
|
||||
CON -> Just ConnReady
|
||||
_ -> Nothing
|
||||
|
||||
processDirectMessage :: ACommand 'Agent -> Connection -> Maybe Contact -> m ()
|
||||
processDirectMessage agentMsg conn@Connection {connId, viaUserContactLink, groupLinkId, customUserProfileId} = \case
|
||||
processDirectMessage :: ACommand 'Agent -> ConnectionEntity -> Connection -> Maybe Contact -> m ()
|
||||
processDirectMessage agentMsg connEntity conn@Connection {connId, viaUserContactLink, groupLinkId, customUserProfileId} = \case
|
||||
Nothing -> case agentMsg of
|
||||
CONF confId _ connInfo -> do
|
||||
-- [incognito] send saved profile
|
||||
@@ -1910,15 +1938,16 @@ processAgentMessage (Just user@User {userId}) corrId agentConnId agentMessage =
|
||||
_ <- saveRcvMSG conn (ConnectionId connId) meta msgBody cmdId
|
||||
withAckMessage agentConnId cmdId meta $ pure ()
|
||||
SENT msgId ->
|
||||
-- ? updateDirectChatItemStatus
|
||||
sentMsgDeliveryEvent conn msgId
|
||||
OK ->
|
||||
-- [async agent commands] continuation on receiving OK
|
||||
withCompletedCommand conn agentMsg $ \CommandData {cmdFunction, cmdId} ->
|
||||
when (cmdFunction == CFAckMessage) $ ackMsgDeliveryEvent conn cmdId
|
||||
MERR _ err -> toView . CRChatError $ ChatErrorAgent err -- ? updateDirectChatItemStatus
|
||||
MERR _ err -> do
|
||||
toView . CRChatError $ ChatErrorAgent err (Just connEntity)
|
||||
incAuthErrCounter connEntity conn err
|
||||
ERR err -> do
|
||||
toView . CRChatError $ ChatErrorAgent err
|
||||
toView . CRChatError $ ChatErrorAgent err (Just connEntity)
|
||||
when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure ()
|
||||
-- TODO add debugging output
|
||||
_ -> pure ()
|
||||
@@ -2031,14 +2060,16 @@ processAgentMessage (Just user@User {userId}) corrId agentConnId agentMessage =
|
||||
forM_ chatItemId_ $ \chatItemId -> do
|
||||
chatItem <- withStore $ \db -> updateDirectChatItemStatus db user contactId chatItemId (agentErrToItemStatus err)
|
||||
toView $ CRChatItemStatusUpdated (AChatItem SCTDirect SMDSnd (DirectChat ct) chatItem)
|
||||
toView . CRChatError $ ChatErrorAgent err (Just connEntity)
|
||||
incAuthErrCounter connEntity conn err
|
||||
ERR err -> do
|
||||
toView . CRChatError $ ChatErrorAgent err
|
||||
toView . CRChatError $ ChatErrorAgent err (Just connEntity)
|
||||
when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure ()
|
||||
-- TODO add debugging output
|
||||
_ -> pure ()
|
||||
|
||||
processGroupMessage :: ACommand 'Agent -> Connection -> GroupInfo -> GroupMember -> m ()
|
||||
processGroupMessage agentMsg conn@Connection {connId} gInfo@GroupInfo {groupId, localDisplayName = gName, groupProfile, membership, chatSettings} m = case agentMsg of
|
||||
processGroupMessage :: ACommand 'Agent -> ConnectionEntity -> Connection -> GroupInfo -> GroupMember -> m ()
|
||||
processGroupMessage agentMsg connEntity conn@Connection {connId} gInfo@GroupInfo {groupId, localDisplayName = gName, groupProfile, membership, chatSettings} m = case agentMsg of
|
||||
INV (ACR _ cReq) ->
|
||||
withCompletedCommand conn agentMsg $ \CommandData {cmdFunction} ->
|
||||
case cReq of
|
||||
@@ -2108,7 +2139,8 @@ processAgentMessage (Just user@User {userId}) corrId agentConnId agentMessage =
|
||||
updateGroupMemberStatus db userId m GSMemConnected
|
||||
unless (memberActive membership) $
|
||||
updateGroupMemberStatus db userId membership GSMemConnected
|
||||
sendPendingGroupMessages m conn
|
||||
-- possible improvement: check for each pending message, requires keeping track of connection state
|
||||
unless (connDisabled conn) $ sendPendingGroupMessages m conn
|
||||
withAgent $ \a -> toggleConnectionNtfs a (aConnId conn) $ enableNtfs chatSettings
|
||||
case memberCategory m of
|
||||
GCHostMember -> do
|
||||
@@ -2177,15 +2209,17 @@ processAgentMessage (Just user@User {userId}) corrId agentConnId agentMessage =
|
||||
-- [async agent commands] continuation on receiving OK
|
||||
withCompletedCommand conn agentMsg $ \CommandData {cmdFunction, cmdId} ->
|
||||
when (cmdFunction == CFAckMessage) $ ackMsgDeliveryEvent conn cmdId
|
||||
MERR _ err -> toView . CRChatError $ ChatErrorAgent err
|
||||
MERR _ err -> do
|
||||
toView . CRChatError $ ChatErrorAgent err (Just connEntity)
|
||||
incAuthErrCounter connEntity conn err
|
||||
ERR err -> do
|
||||
toView . CRChatError $ ChatErrorAgent err
|
||||
toView . CRChatError $ ChatErrorAgent err (Just connEntity)
|
||||
when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure ()
|
||||
-- TODO add debugging output
|
||||
_ -> pure ()
|
||||
|
||||
processSndFileConn :: ACommand 'Agent -> Connection -> SndFileTransfer -> m ()
|
||||
processSndFileConn agentMsg conn ft@SndFileTransfer {fileId, fileName, fileStatus} =
|
||||
processSndFileConn :: ACommand 'Agent -> ConnectionEntity -> Connection -> SndFileTransfer -> m ()
|
||||
processSndFileConn agentMsg connEntity conn ft@SndFileTransfer {fileId, fileName, fileStatus} =
|
||||
case agentMsg of
|
||||
-- SMP CONF for SndFileConnection happens for direct file protocol
|
||||
-- when recipient of the file "joins" connection created by the sender
|
||||
@@ -2223,13 +2257,13 @@ processAgentMessage (Just user@User {userId}) corrId agentConnId agentMessage =
|
||||
-- [async agent commands] continuation on receiving OK
|
||||
withCompletedCommand conn agentMsg $ \_cmdData -> pure ()
|
||||
ERR err -> do
|
||||
toView . CRChatError $ ChatErrorAgent err
|
||||
toView . CRChatError $ ChatErrorAgent err (Just connEntity)
|
||||
when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure ()
|
||||
-- TODO add debugging output
|
||||
_ -> pure ()
|
||||
|
||||
processRcvFileConn :: ACommand 'Agent -> Connection -> RcvFileTransfer -> m ()
|
||||
processRcvFileConn agentMsg conn ft@RcvFileTransfer {fileId, fileInvitation = FileInvitation {fileName}, grpMemberId} =
|
||||
processRcvFileConn :: ACommand 'Agent -> ConnectionEntity -> Connection -> RcvFileTransfer -> m ()
|
||||
processRcvFileConn agentMsg connEntity conn ft@RcvFileTransfer {fileId, fileInvitation = FileInvitation {fileName}, grpMemberId} =
|
||||
case agentMsg of
|
||||
INV (ACR _ cReq) ->
|
||||
withCompletedCommand conn agentMsg $ \CommandData {cmdFunction} ->
|
||||
@@ -2268,9 +2302,11 @@ processAgentMessage (Just user@User {userId}) corrId agentConnId agentMessage =
|
||||
OK ->
|
||||
-- [async agent commands] continuation on receiving OK
|
||||
withCompletedCommand conn agentMsg $ \_cmdData -> pure ()
|
||||
MERR _ err -> toView . CRChatError $ ChatErrorAgent err
|
||||
MERR _ err -> do
|
||||
toView . CRChatError $ ChatErrorAgent err (Just connEntity)
|
||||
incAuthErrCounter connEntity conn err
|
||||
ERR err -> do
|
||||
toView . CRChatError $ ChatErrorAgent err
|
||||
toView . CRChatError $ ChatErrorAgent err (Just connEntity)
|
||||
when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure ()
|
||||
-- TODO add debugging output
|
||||
_ -> pure ()
|
||||
@@ -2317,8 +2353,8 @@ processAgentMessage (Just user@User {userId}) corrId agentConnId agentMessage =
|
||||
RcvChunkDuplicate -> pure ()
|
||||
RcvChunkError -> badRcvFileChunk ft $ "incorrect chunk number " <> show chunkNo
|
||||
|
||||
processUserContactRequest :: ACommand 'Agent -> Connection -> UserContact -> m ()
|
||||
processUserContactRequest agentMsg conn UserContact {userContactLinkId} = case agentMsg of
|
||||
processUserContactRequest :: ACommand 'Agent -> ConnectionEntity -> Connection -> UserContact -> m ()
|
||||
processUserContactRequest agentMsg connEntity conn UserContact {userContactLinkId} = case agentMsg of
|
||||
REQ invId _ connInfo -> do
|
||||
ChatMessage {chatMsgEvent} <- parseChatMessage connInfo
|
||||
case chatMsgEvent of
|
||||
@@ -2326,9 +2362,11 @@ processAgentMessage (Just user@User {userId}) corrId agentConnId agentMessage =
|
||||
XInfo p -> profileContactRequest invId p Nothing
|
||||
-- TODO show/log error, other events in contact request
|
||||
_ -> pure ()
|
||||
MERR _ err -> toView . CRChatError $ ChatErrorAgent err
|
||||
MERR _ err -> do
|
||||
toView . CRChatError $ ChatErrorAgent err (Just connEntity)
|
||||
incAuthErrCounter connEntity conn err
|
||||
ERR err -> do
|
||||
toView . CRChatError $ ChatErrorAgent err
|
||||
toView . CRChatError $ ChatErrorAgent err (Just connEntity)
|
||||
when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure ()
|
||||
-- TODO add debugging output
|
||||
_ -> pure ()
|
||||
@@ -2357,6 +2395,15 @@ processAgentMessage (Just user@User {userId}) corrId agentConnId agentMessage =
|
||||
showToast (localDisplayName <> "> ") "wants to connect to you"
|
||||
_ -> pure ()
|
||||
|
||||
incAuthErrCounter :: ConnectionEntity -> Connection -> AgentErrorType -> m ()
|
||||
incAuthErrCounter connEntity conn err = do
|
||||
case err of
|
||||
SMP SMP.AUTH -> do
|
||||
authErrCounter' <- withStore' $ \db -> incConnectionAuthErrCounter db user conn
|
||||
when (authErrCounter' >= authErrDisableCount) $ do
|
||||
toView $ CRConnectionDisabled connEntity
|
||||
_ -> pure ()
|
||||
|
||||
updateChatLock :: MsgEncodingI e => String -> ChatMsgEvent e -> m ()
|
||||
updateChatLock name event = do
|
||||
l <- asks chatLock
|
||||
@@ -2392,11 +2439,15 @@ processAgentMessage (Just user@User {userId}) corrId agentConnId agentMessage =
|
||||
|
||||
ackMsgDeliveryEvent :: Connection -> CommandId -> m ()
|
||||
ackMsgDeliveryEvent Connection {connId} ackCmdId =
|
||||
withStore' $ \db -> createRcvMsgDeliveryEvent db connId ackCmdId MDSRcvAcknowledged
|
||||
withStoreCtx'
|
||||
(Just $ "createRcvMsgDeliveryEvent, connId: " <> show connId <> ", ackCmdId: " <> show ackCmdId <> ", msgDeliveryStatus: MDSRcvAcknowledged")
|
||||
$ \db -> createRcvMsgDeliveryEvent db connId ackCmdId MDSRcvAcknowledged
|
||||
|
||||
sentMsgDeliveryEvent :: Connection -> AgentMsgId -> m ()
|
||||
sentMsgDeliveryEvent Connection {connId} msgId =
|
||||
withStore $ \db -> createSndMsgDeliveryEvent db connId msgId MDSSndSent
|
||||
withStoreCtx
|
||||
(Just $ "createSndMsgDeliveryEvent, connId: " <> show connId <> ", msgId: " <> show msgId <> ", msgDeliveryStatus: MDSSndSent")
|
||||
$ \db -> createSndMsgDeliveryEvent db connId msgId MDSSndSent
|
||||
|
||||
agentErrToItemStatus :: AgentErrorType -> CIStatus 'MDSnd
|
||||
agentErrToItemStatus (SMP AUTH) = CISSndErrorAuth
|
||||
@@ -3209,8 +3260,7 @@ getFileHandle fileId filePath files ioMode = do
|
||||
maybe (newHandle fs) pure h_
|
||||
where
|
||||
newHandle fs = do
|
||||
-- TODO handle errors
|
||||
h <- liftIO (openFile filePath ioMode)
|
||||
h <- liftIO (openFile filePath ioMode) `E.catch` (throwChatError . CEFileInternal . (show :: E.SomeException -> String))
|
||||
atomically . modifyTVar fs $ M.insert fileId h
|
||||
pure h
|
||||
|
||||
@@ -3270,13 +3320,14 @@ deleteOrUpdateMemberRecord user@User {userId} member =
|
||||
Nothing -> deleteGroupMember db user member
|
||||
|
||||
sendDirectContactMessage :: (MsgEncodingI e, ChatMonad m) => Contact -> ChatMsgEvent e -> m (SndMessage, Int64)
|
||||
sendDirectContactMessage ct@Contact {activeConn = conn@Connection {connId, connStatus}} chatMsgEvent = do
|
||||
if connStatus == ConnReady || connStatus == ConnSndReady
|
||||
then sendDirectMessage conn chatMsgEvent (ConnectionId connId)
|
||||
else throwChatError $ CEContactNotReady ct
|
||||
sendDirectContactMessage ct@Contact {activeConn = conn@Connection {connId, connStatus}} chatMsgEvent
|
||||
| connStatus /= ConnReady && connStatus /= ConnSndReady = throwChatError $ CEContactNotReady ct
|
||||
| connDisabled conn = throwChatError $ CEContactDisabled ct
|
||||
| otherwise = sendDirectMessage conn chatMsgEvent (ConnectionId connId)
|
||||
|
||||
sendDirectMessage :: (MsgEncodingI e, ChatMonad m) => Connection -> ChatMsgEvent e -> ConnOrGroupId -> m (SndMessage, Int64)
|
||||
sendDirectMessage conn chatMsgEvent connOrGroupId = do
|
||||
when (connDisabled conn) $ throwChatError (CEConnectionDisabled conn)
|
||||
msg@SndMessage {msgId, msgBody} <- createSndMessage chatMsgEvent connOrGroupId
|
||||
(msg,) <$> deliverMessage conn (toCMEventTag chatMsgEvent) msgBody msgId
|
||||
|
||||
@@ -3295,7 +3346,9 @@ deliverMessage conn@Connection {connId} cmEventTag msgBody msgId = do
|
||||
let msgFlags = MsgFlags {notification = hasNotification cmEventTag}
|
||||
agentMsgId <- withAgent $ \a -> sendMessage a (aConnId conn) msgFlags msgBody
|
||||
let sndMsgDelivery = SndMsgDelivery {connId, agentMsgId}
|
||||
withStore' $ \db -> createSndMsgDelivery db sndMsgDelivery msgId
|
||||
withStoreCtx'
|
||||
(Just $ "createSndMsgDelivery, sndMsgDelivery: " <> show sndMsgDelivery <> ", msgId: " <> show msgId <> ", cmEventTag: " <> show cmEventTag <> ", msgDeliveryStatus: MDSSndAgent")
|
||||
$ \db -> createSndMsgDelivery db sndMsgDelivery msgId
|
||||
|
||||
sendGroupMessage :: (MsgEncodingI e, ChatMonad m) => GroupInfo -> [GroupMember] -> ChatMsgEvent e -> m SndMessage
|
||||
sendGroupMessage GroupInfo {groupId} members chatMsgEvent =
|
||||
@@ -3309,10 +3362,10 @@ sendGroupMessage' members chatMsgEvent groupId introId_ postDeliver = do
|
||||
case memberConn m of
|
||||
Nothing -> withStore' $ \db -> createPendingGroupMessage db groupMemberId msgId introId_
|
||||
Just conn@Connection {connStatus}
|
||||
| connDisabled conn || connStatus == ConnDeleted -> pure ()
|
||||
| connStatus == ConnSndReady || connStatus == ConnReady -> do
|
||||
let tag = toCMEventTag chatMsgEvent
|
||||
(deliverMessage conn tag msgBody msgId >> postDeliver) `catchError` const (pure ())
|
||||
| connStatus == ConnDeleted -> pure ()
|
||||
| otherwise -> withStore' $ \db -> createPendingGroupMessage db groupMemberId msgId introId_
|
||||
pure msg
|
||||
|
||||
@@ -3335,7 +3388,9 @@ saveRcvMSG Connection {connId} connOrGroupId agentMsgMeta msgBody agentAckCmdId
|
||||
let agentMsgId = fst $ recipient agentMsgMeta
|
||||
newMsg = NewMessage {chatMsgEvent, msgBody}
|
||||
rcvMsgDelivery = RcvMsgDelivery {connId, agentMsgId, agentMsgMeta, agentAckCmdId}
|
||||
withStore' $ \db -> createNewMessageAndRcvMsgDelivery db connOrGroupId newMsg sharedMsgId_ rcvMsgDelivery
|
||||
withStoreCtx'
|
||||
(Just $ "createNewMessageAndRcvMsgDelivery, rcvMsgDelivery: " <> show rcvMsgDelivery <> ", sharedMsgId_: " <> show sharedMsgId_ <> ", msgDeliveryStatus: MDSRcvAgent")
|
||||
$ \db -> createNewMessageAndRcvMsgDelivery db connOrGroupId newMsg sharedMsgId_ rcvMsgDelivery
|
||||
|
||||
saveSndChatItem :: ChatMonad m => User -> ChatDirection c 'MDSnd -> SndMessage -> CIContent 'MDSnd -> m (ChatItem c 'MDSnd)
|
||||
saveSndChatItem user cd msg content = saveSndChatItem' user cd msg content Nothing Nothing Nothing False
|
||||
@@ -3598,22 +3653,25 @@ withAgent :: ChatMonad m => (AgentClient -> ExceptT AgentErrorType m a) -> m a
|
||||
withAgent action =
|
||||
asks smpAgent
|
||||
>>= runExceptT . action
|
||||
>>= liftEither . first ChatErrorAgent
|
||||
>>= liftEither . first (\e -> ChatErrorAgent e Nothing)
|
||||
|
||||
withStore' :: ChatMonad m => (DB.Connection -> IO a) -> m a
|
||||
withStore' action = withStore $ liftIO . action
|
||||
|
||||
withStore ::
|
||||
ChatMonad m =>
|
||||
(DB.Connection -> ExceptT StoreError IO a) ->
|
||||
m a
|
||||
withStore action = do
|
||||
withStore :: ChatMonad m => (DB.Connection -> ExceptT StoreError IO a) -> m a
|
||||
withStore = withStoreCtx Nothing
|
||||
|
||||
withStoreCtx' :: ChatMonad m => Maybe String -> (DB.Connection -> IO a) -> m a
|
||||
withStoreCtx' ctx_ action = withStoreCtx ctx_ $ liftIO . action
|
||||
|
||||
withStoreCtx :: ChatMonad m => Maybe String -> (DB.Connection -> ExceptT StoreError IO a) -> m a
|
||||
withStoreCtx ctx_ action = do
|
||||
ChatController {chatStore} <- ask
|
||||
liftEitherError ChatErrorStore $
|
||||
withTransaction chatStore (runExceptT . action) `E.catch` handleInternal
|
||||
where
|
||||
handleInternal :: E.SomeException -> IO (Either StoreError a)
|
||||
handleInternal = pure . Left . SEInternalError . show
|
||||
handleInternal e = pure . Left . SEInternalError $ show e <> maybe "" (\ctx -> " (" <> ctx <> ")") ctx_
|
||||
|
||||
chatCommandP :: Parser ChatCommand
|
||||
chatCommandP =
|
||||
@@ -3703,10 +3761,14 @@ chatCommandP =
|
||||
"/_get code #" *> (APIGetGroupMemberCode <$> A.decimal <* A.space <*> A.decimal),
|
||||
"/_verify code @" *> (APIVerifyContact <$> A.decimal <*> optional (A.space *> textP)),
|
||||
"/_verify code #" *> (APIVerifyGroupMember <$> A.decimal <* A.space <*> A.decimal <*> optional (A.space *> textP)),
|
||||
"/_enable @" *> (APIEnableContact <$> A.decimal),
|
||||
"/_enable #" *> (APIEnableGroupMember <$> A.decimal <* A.space <*> A.decimal),
|
||||
"/code " *> char_ '@' *> (GetContactCode <$> displayName),
|
||||
"/code #" *> (GetGroupMemberCode <$> displayName <* A.space <* char_ '@' <*> displayName),
|
||||
"/verify " *> char_ '@' *> (VerifyContact <$> displayName <*> optional (A.space *> textP)),
|
||||
"/verify #" *> (VerifyGroupMember <$> displayName <* A.space <* char_ '@' <*> displayName <*> optional (A.space *> textP)),
|
||||
"/enable " *> char_ '@' *> (EnableContact <$> displayName),
|
||||
"/enable #" *> (EnableGroupMember <$> displayName <* A.space <* char_ '@' <*> displayName),
|
||||
("/help files" <|> "/help file" <|> "/hf") $> ChatHelp HSFiles,
|
||||
("/help groups" <|> "/help group" <|> "/hg") $> ChatHelp HSGroups,
|
||||
("/help address" <|> "/ha") $> ChatHelp HSMyAddress,
|
||||
@@ -3786,7 +3848,9 @@ chatCommandP =
|
||||
"/incognito " *> (SetIncognito <$> onOffP),
|
||||
("/quit" <|> "/q" <|> "/exit") $> QuitChat,
|
||||
("/version" <|> "/v") $> ShowVersion,
|
||||
"/debug locks" $> DebugLocks
|
||||
"/debug locks" $> DebugLocks,
|
||||
"/get stats" $> GetAgentStats,
|
||||
"/reset stats" $> ResetAgentStats
|
||||
]
|
||||
where
|
||||
choice = A.choice . map (\p -> p <* A.takeWhile (== ' ') <* A.endOfInput)
|
||||
@@ -3862,8 +3926,9 @@ chatCommandP =
|
||||
netCfgP = do
|
||||
socksProxy <- "socks=" *> ("off" $> Nothing <|> "on" $> Just defaultSocksProxy <|> Just <$> strP)
|
||||
t_ <- optional $ " timeout=" *> A.decimal
|
||||
logErrors <- " log=" *> onOffP <|> pure False
|
||||
let tcpTimeout = 1000000 * fromMaybe (maybe 5 (const 10) socksProxy) t_
|
||||
pure $ fullNetworkConfig socksProxy tcpTimeout
|
||||
pure $ fullNetworkConfig socksProxy tcpTimeout logErrors
|
||||
dbKeyP = nonEmptyKey <$?> strP
|
||||
nonEmptyKey k@(DBEncryptionKey s) = if null s then Left "empty key" else Right k
|
||||
autoAcceptP =
|
||||
|
||||
@@ -210,6 +210,8 @@ data ChatCommand
|
||||
| APIGetGroupMemberCode GroupId GroupMemberId
|
||||
| APIVerifyContact ContactId (Maybe Text)
|
||||
| APIVerifyGroupMember GroupId GroupMemberId (Maybe Text)
|
||||
| APIEnableContact ContactId
|
||||
| APIEnableGroupMember GroupId GroupMemberId
|
||||
| ShowMessages ChatName Bool
|
||||
| ContactInfo ContactName
|
||||
| GroupMemberInfo GroupName ContactName
|
||||
@@ -219,6 +221,8 @@ data ChatCommand
|
||||
| GetGroupMemberCode GroupName ContactName
|
||||
| VerifyContact ContactName (Maybe Text)
|
||||
| VerifyGroupMember GroupName ContactName (Maybe Text)
|
||||
| EnableContact ContactName
|
||||
| EnableGroupMember GroupName ContactName
|
||||
| ChatHelp HelpSection
|
||||
| Welcome
|
||||
| AddContact
|
||||
@@ -280,6 +284,8 @@ data ChatCommand
|
||||
| QuitChat
|
||||
| ShowVersion
|
||||
| DebugLocks
|
||||
| GetAgentStats
|
||||
| ResetAgentStats
|
||||
deriving (Show)
|
||||
|
||||
data ChatResponse
|
||||
@@ -411,6 +417,8 @@ data ChatResponse
|
||||
| CRContactConnectionDeleted {connection :: PendingContactConnection}
|
||||
| CRSQLResult {rows :: [Text]}
|
||||
| CRDebugLocks {chatLockName :: Maybe String, agentLocks :: AgentLocks}
|
||||
| CRAgentStats {agentStats :: [[String]]}
|
||||
| CRConnectionDisabled {connectionEntity :: ConnectionEntity}
|
||||
| CRMessageError {severity :: Text, errorMessage :: Text}
|
||||
| CRChatCmdError {chatError :: ChatError}
|
||||
| CRChatError {chatError :: ChatError}
|
||||
@@ -537,7 +545,7 @@ tmeToPref currentTTL tme = uncurry TimedMessagesPreference $ case tme of
|
||||
|
||||
data ChatError
|
||||
= ChatError {errorType :: ChatErrorType}
|
||||
| ChatErrorAgent {agentError :: AgentErrorType}
|
||||
| ChatErrorAgent {agentError :: AgentErrorType, connectionEntity_ :: Maybe ConnectionEntity}
|
||||
| ChatErrorStore {storeError :: StoreError}
|
||||
| ChatErrorDatabase {databaseError :: DatabaseError}
|
||||
deriving (Show, Exception, Generic)
|
||||
@@ -555,6 +563,8 @@ data ChatErrorType
|
||||
| CEInvalidConnReq
|
||||
| CEInvalidChatMessage {message :: String}
|
||||
| CEContactNotReady {contact :: Contact}
|
||||
| CEContactDisabled {contact :: Contact}
|
||||
| CEConnectionDisabled {connection :: Connection}
|
||||
| CEGroupUserRole
|
||||
| CEContactIncognitoCantInvite
|
||||
| CEGroupIncognitoCantInvite
|
||||
|
||||
@@ -1157,6 +1157,7 @@ data SndMsgDelivery = SndMsgDelivery
|
||||
{ connId :: Int64,
|
||||
agentMsgId :: AgentMsgId
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
data RcvMsgDelivery = RcvMsgDelivery
|
||||
{ connId :: Int64,
|
||||
@@ -1164,6 +1165,7 @@ data RcvMsgDelivery = RcvMsgDelivery
|
||||
agentMsgMeta :: MsgMeta,
|
||||
agentAckCmdId :: CommandId
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
data MsgMetaJSON = MsgMetaJSON
|
||||
{ integrity :: Text,
|
||||
|
||||
14
src/Simplex/Chat/Migrations/M20221230_idxs.hs
Normal file
14
src/Simplex/Chat/Migrations/M20221230_idxs.hs
Normal file
@@ -0,0 +1,14 @@
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
|
||||
module Simplex.Chat.Migrations.M20221230_idxs where
|
||||
|
||||
import Database.SQLite.Simple (Query)
|
||||
import Database.SQLite.Simple.QQ (sql)
|
||||
|
||||
m20221230_idxs :: Query
|
||||
m20221230_idxs =
|
||||
[sql|
|
||||
CREATE INDEX idx_connections_group_member ON connections(user_id, group_member_id);
|
||||
|
||||
CREATE INDEX idx_commands_connection_id ON commands(connection_id);
|
||||
|]
|
||||
@@ -0,0 +1,17 @@
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
|
||||
module Simplex.Chat.Migrations.M20230107_connections_auth_err_counter where
|
||||
|
||||
import Database.SQLite.Simple (Query)
|
||||
import Database.SQLite.Simple.QQ (sql)
|
||||
|
||||
m20230107_connections_auth_err_counter :: Query
|
||||
m20230107_connections_auth_err_counter =
|
||||
[sql|
|
||||
PRAGMA ignore_check_constraints=ON;
|
||||
|
||||
ALTER TABLE connections ADD COLUMN auth_err_counter INTEGER DEFAULT 0 CHECK (auth_err_counter NOT NULL);
|
||||
UPDATE connections SET auth_err_counter = 0;
|
||||
|
||||
PRAGMA ignore_check_constraints=OFF;
|
||||
|]
|
||||
@@ -263,6 +263,7 @@ CREATE TABLE connections(
|
||||
group_link_id BLOB,
|
||||
security_code TEXT NULL,
|
||||
security_code_verified_at TEXT NULL,
|
||||
auth_err_counter INTEGER DEFAULT 0 CHECK(auth_err_counter NOT NULL),
|
||||
FOREIGN KEY(snd_file_id, connection_id)
|
||||
REFERENCES snd_files(file_id, connection_id)
|
||||
ON DELETE CASCADE
|
||||
@@ -463,3 +464,8 @@ CREATE INDEX idx_chat_items_group_member_id ON chat_items(group_member_id);
|
||||
CREATE INDEX idx_chat_items_contact_id ON chat_items(contact_id);
|
||||
CREATE INDEX idx_chat_items_timed_delete_at ON chat_items(timed_delete_at);
|
||||
CREATE INDEX idx_chat_items_item_status ON chat_items(item_status);
|
||||
CREATE INDEX idx_connections_group_member ON connections(
|
||||
user_id,
|
||||
group_member_id
|
||||
);
|
||||
CREATE INDEX idx_commands_connection_id ON commands(connection_id);
|
||||
|
||||
@@ -129,6 +129,7 @@ mobileChatOpts =
|
||||
chatCmd = "",
|
||||
chatCmdDelay = 3,
|
||||
chatServerPort = Nothing,
|
||||
optFilesFolder = Nothing,
|
||||
allowInstantFiles = True,
|
||||
maintenance = True
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ data ChatOpts = ChatOpts
|
||||
chatCmd :: String,
|
||||
chatCmdDelay :: Int,
|
||||
chatServerPort :: Maybe String,
|
||||
optFilesFolder :: Maybe FilePath,
|
||||
allowInstantFiles :: Bool,
|
||||
maintenance :: Bool
|
||||
}
|
||||
@@ -83,6 +84,11 @@ chatOpts appDir defaultDbFileName = do
|
||||
<> help "TCP timeout, seconds (default: 5/10 without/with SOCKS5 proxy)"
|
||||
<> value 0
|
||||
)
|
||||
logTLSErrors <-
|
||||
switch
|
||||
( long "log-tls-errors"
|
||||
<> help "Log TLS errors"
|
||||
)
|
||||
logConnections <-
|
||||
switch
|
||||
( long "connections"
|
||||
@@ -127,9 +133,16 @@ chatOpts appDir defaultDbFileName = do
|
||||
<> help "Run chat server on specified port"
|
||||
<> value Nothing
|
||||
)
|
||||
optFilesFolder <-
|
||||
optional $
|
||||
strOption
|
||||
( long "files-folder"
|
||||
<> metavar "FOLDER"
|
||||
<> help "Folder to use for sent and received files"
|
||||
)
|
||||
allowInstantFiles <-
|
||||
switch
|
||||
( long "--allow-instant-files"
|
||||
( long "allow-instant-files"
|
||||
<> short 'f'
|
||||
<> help "Send and receive instant files without acceptance"
|
||||
)
|
||||
@@ -144,13 +157,14 @@ chatOpts appDir defaultDbFileName = do
|
||||
{ dbFilePrefix,
|
||||
dbKey,
|
||||
smpServers,
|
||||
networkConfig = fullNetworkConfig socksProxy $ useTcpTimeout socksProxy t,
|
||||
networkConfig = fullNetworkConfig socksProxy (useTcpTimeout socksProxy t) logTLSErrors,
|
||||
logConnections,
|
||||
logServerHosts,
|
||||
logAgent,
|
||||
chatCmd,
|
||||
chatCmdDelay,
|
||||
chatServerPort,
|
||||
optFilesFolder,
|
||||
allowInstantFiles,
|
||||
maintenance
|
||||
}
|
||||
@@ -158,10 +172,10 @@ chatOpts appDir defaultDbFileName = do
|
||||
useTcpTimeout p t = 1000000 * if t > 0 then t else maybe 5 (const 10) p
|
||||
defaultDbFilePath = combine appDir defaultDbFileName
|
||||
|
||||
fullNetworkConfig :: Maybe SocksProxy -> Int -> NetworkConfig
|
||||
fullNetworkConfig socksProxy tcpTimeout =
|
||||
fullNetworkConfig :: Maybe SocksProxy -> Int -> Bool -> NetworkConfig
|
||||
fullNetworkConfig socksProxy tcpTimeout logTLSErrors =
|
||||
let tcpConnectTimeout = (tcpTimeout * 3) `div` 2
|
||||
in defaultNetworkConfig {socksProxy, tcpTimeout, tcpConnectTimeout}
|
||||
in defaultNetworkConfig {socksProxy, tcpTimeout, tcpConnectTimeout, logTLSErrors}
|
||||
|
||||
parseSMPServers :: ReadM [SMPServerWithAuth]
|
||||
parseSMPServers = eitherReader $ parseAll smpServersP . B.pack
|
||||
|
||||
@@ -48,6 +48,8 @@ module Simplex.Chat.Store
|
||||
updateContactUnreadChat,
|
||||
updateGroupUnreadChat,
|
||||
setConnectionVerified,
|
||||
incConnectionAuthErrCounter,
|
||||
setConnectionAuthErrCounter,
|
||||
getUserContacts,
|
||||
getUserContactProfiles,
|
||||
createUserContactLink,
|
||||
@@ -323,6 +325,8 @@ import Simplex.Chat.Migrations.M20221212_chat_items_timed
|
||||
import Simplex.Chat.Migrations.M20221214_live_message
|
||||
import Simplex.Chat.Migrations.M20221222_chat_ts
|
||||
import Simplex.Chat.Migrations.M20221223_idx_chat_items_item_status
|
||||
import Simplex.Chat.Migrations.M20221230_idxs
|
||||
import Simplex.Chat.Migrations.M20230107_connections_auth_err_counter
|
||||
import Simplex.Chat.Protocol
|
||||
import Simplex.Chat.Types
|
||||
import Simplex.Chat.Util (week)
|
||||
@@ -381,7 +385,9 @@ schemaMigrations =
|
||||
("20221212_chat_items_timed", m20221212_chat_items_timed),
|
||||
("20221214_live_message", m20221214_live_message),
|
||||
("20221222_chat_ts", m20221222_chat_ts),
|
||||
("20221223_idx_chat_items_item_status", m20221223_idx_chat_items_item_status)
|
||||
("20221223_idx_chat_items_item_status", m20221223_idx_chat_items_item_status),
|
||||
("20221230_idxs", m20221230_idxs),
|
||||
("20230107_connections_auth_err_counter", m20230107_connections_auth_err_counter)
|
||||
]
|
||||
|
||||
-- | The list of migrations in ascending order by date
|
||||
@@ -493,7 +499,7 @@ getConnReqContactXContactId db user@User {userId} cReqHash = do
|
||||
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts,
|
||||
-- Connection
|
||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias,
|
||||
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at
|
||||
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter
|
||||
FROM contacts ct
|
||||
JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id
|
||||
JOIN connections c ON c.contact_id = ct.contact_id
|
||||
@@ -568,7 +574,7 @@ createConnection_ db userId connType entityId acId viaContact viaUserContactLink
|
||||
:. (ent ConnContact, ent ConnMember, ent ConnSndFile, ent ConnRcvFile, ent ConnUserContact, currentTs, currentTs)
|
||||
)
|
||||
connId <- insertedRowId db
|
||||
pure Connection {connId, agentConnId = AgentConnId acId, connType, entityId, viaContact, viaUserContactLink, viaGroupLink, groupLinkId = Nothing, customUserProfileId, connLevel, connStatus = ConnNew, localAlias = "", createdAt = currentTs, connectionCode = Nothing}
|
||||
pure Connection {connId, agentConnId = AgentConnId acId, connType, entityId, viaContact, viaUserContactLink, viaGroupLink, groupLinkId = Nothing, customUserProfileId, connLevel, connStatus = ConnNew, localAlias = "", createdAt = currentTs, connectionCode = Nothing, authErrCounter = 0}
|
||||
where
|
||||
ent ct = if connType == ct then entityId else Nothing
|
||||
|
||||
@@ -762,6 +768,19 @@ setConnectionVerified db User {userId} connId code = do
|
||||
updatedAt <- getCurrentTime
|
||||
DB.execute db "UPDATE connections SET security_code = ?, security_code_verified_at = ?, updated_at = ? WHERE user_id = ? AND connection_id = ?" (code, code $> updatedAt, updatedAt, userId, connId)
|
||||
|
||||
incConnectionAuthErrCounter :: DB.Connection -> User -> Connection -> IO Int
|
||||
incConnectionAuthErrCounter db User {userId} Connection {connId, authErrCounter} = do
|
||||
updatedAt <- getCurrentTime
|
||||
(counter_ :: Maybe Int) <- maybeFirstRow fromOnly $ DB.query db "SELECT auth_err_counter FROM connections WHERE user_id = ? AND connection_id = ?" (userId, connId)
|
||||
let counter' = fromMaybe authErrCounter counter_ + 1
|
||||
DB.execute db "UPDATE connections SET auth_err_counter = ?, updated_at = ? WHERE user_id = ? AND connection_id = ?" (counter', updatedAt, userId, connId)
|
||||
pure counter'
|
||||
|
||||
setConnectionAuthErrCounter :: DB.Connection -> User -> Connection -> Int -> IO ()
|
||||
setConnectionAuthErrCounter db User {userId} Connection {connId} counter = do
|
||||
updatedAt <- getCurrentTime
|
||||
DB.execute db "UPDATE connections SET auth_err_counter = ?, updated_at = ? WHERE user_id = ? AND connection_id = ?" (counter, updatedAt, userId, connId)
|
||||
|
||||
updateContactProfile_ :: DB.Connection -> UserId -> ProfileId -> Profile -> IO ()
|
||||
updateContactProfile_ db userId profileId profile = do
|
||||
currentTs <- getCurrentTime
|
||||
@@ -859,7 +878,7 @@ getUserAddressConnections db User {userId} = do
|
||||
db
|
||||
[sql|
|
||||
SELECT c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
|
||||
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at
|
||||
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter
|
||||
FROM connections c
|
||||
JOIN user_contact_links uc ON c.user_contact_link_id = uc.user_contact_link_id
|
||||
WHERE c.user_id = ? AND uc.user_id = ? AND uc.local_display_name = '' AND uc.group_id IS NULL
|
||||
@@ -873,7 +892,7 @@ getUserContactLinks db User {userId} =
|
||||
db
|
||||
[sql|
|
||||
SELECT c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
|
||||
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at,
|
||||
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter,
|
||||
uc.user_contact_link_id, uc.conn_req_contact, uc.group_id
|
||||
FROM connections c
|
||||
JOIN user_contact_links uc ON c.user_contact_link_id = uc.user_contact_link_id
|
||||
@@ -1006,7 +1025,7 @@ getGroupLinkConnection db User {userId} groupInfo@GroupInfo {groupId} =
|
||||
db
|
||||
[sql|
|
||||
SELECT c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
|
||||
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at
|
||||
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter
|
||||
FROM connections c
|
||||
JOIN user_contact_links uc ON c.user_contact_link_id = uc.user_contact_link_id
|
||||
WHERE c.user_id = ? AND uc.user_id = ? AND uc.group_id = ?
|
||||
@@ -1110,7 +1129,7 @@ createOrUpdateContactRequest db user@User {userId} userContactLinkId invId Profi
|
||||
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts,
|
||||
-- Connection
|
||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias,
|
||||
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at
|
||||
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter
|
||||
FROM contacts ct
|
||||
JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id
|
||||
LEFT JOIN connections c ON c.contact_id = ct.contact_id
|
||||
@@ -1248,7 +1267,7 @@ getLiveSndFileTransfers db User {userId} = do
|
||||
FROM files f
|
||||
JOIN snd_files s USING (file_id)
|
||||
WHERE f.user_id = ? AND s.file_status IN (?, ?, ?) AND s.file_inline IS NULL
|
||||
AND created_at > ?
|
||||
AND s.created_at > ?
|
||||
|]
|
||||
(userId, FSNew, FSAccepted, FSConnected, cutoffTs)
|
||||
concatMap (filter liveTransfer) . rights <$> mapM (getSndFileTransfers_ db userId) fileIds
|
||||
@@ -1268,7 +1287,7 @@ getLiveRcvFileTransfers db user@User {userId} = do
|
||||
FROM files f
|
||||
JOIN rcv_files r USING (file_id)
|
||||
WHERE f.user_id = ? AND r.file_status IN (?, ?) AND r.rcv_file_inline IS NULL
|
||||
AND created_at > ?
|
||||
AND r.created_at > ?
|
||||
|]
|
||||
(userId, FSAccepted, FSConnected, cutoffTs)
|
||||
rights <$> mapM (runExceptT . getRcvFileTransfer db user) fileIds
|
||||
@@ -1309,7 +1328,7 @@ getContactConnections db userId Contact {contactId} =
|
||||
db
|
||||
[sql|
|
||||
SELECT c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
|
||||
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at
|
||||
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter
|
||||
FROM connections c
|
||||
JOIN contacts ct ON ct.contact_id = c.contact_id
|
||||
WHERE c.user_id = ? AND ct.user_id = ? AND ct.contact_id = ?
|
||||
@@ -1320,15 +1339,15 @@ getContactConnections db userId Contact {contactId} =
|
||||
|
||||
type EntityIdsRow = (Maybe Int64, Maybe Int64, Maybe Int64, Maybe Int64, Maybe Int64)
|
||||
|
||||
type ConnectionRow = (Int64, ConnId, Int, Maybe Int64, Maybe Int64, Bool, Maybe GroupLinkId, Maybe Int64, ConnStatus, ConnType, LocalAlias) :. EntityIdsRow :. (UTCTime, Maybe Text, Maybe UTCTime)
|
||||
type ConnectionRow = (Int64, ConnId, Int, Maybe Int64, Maybe Int64, Bool, Maybe GroupLinkId, Maybe Int64, ConnStatus, ConnType, LocalAlias) :. EntityIdsRow :. (UTCTime, Maybe Text, Maybe UTCTime, Int)
|
||||
|
||||
type MaybeConnectionRow = (Maybe Int64, Maybe ConnId, Maybe Int, Maybe Int64, Maybe Int64, Maybe Bool, Maybe GroupLinkId, Maybe Int64, Maybe ConnStatus, Maybe ConnType, Maybe LocalAlias) :. EntityIdsRow :. (Maybe UTCTime, Maybe Text, Maybe UTCTime)
|
||||
type MaybeConnectionRow = (Maybe Int64, Maybe ConnId, Maybe Int, Maybe Int64, Maybe Int64, Maybe Bool, Maybe GroupLinkId, Maybe Int64, Maybe ConnStatus, Maybe ConnType, Maybe LocalAlias) :. EntityIdsRow :. (Maybe UTCTime, Maybe Text, Maybe UTCTime, Maybe Int)
|
||||
|
||||
toConnection :: ConnectionRow -> Connection
|
||||
toConnection ((connId, acId, connLevel, viaContact, viaUserContactLink, viaGroupLink, groupLinkId, customUserProfileId, connStatus, connType, localAlias) :. (contactId, groupMemberId, sndFileId, rcvFileId, userContactLinkId) :. (createdAt, code_, verifiedAt_)) =
|
||||
toConnection ((connId, acId, connLevel, viaContact, viaUserContactLink, viaGroupLink, groupLinkId, customUserProfileId, connStatus, connType, localAlias) :. (contactId, groupMemberId, sndFileId, rcvFileId, userContactLinkId) :. (createdAt, code_, verifiedAt_, authErrCounter)) =
|
||||
let entityId = entityId_ connType
|
||||
connectionCode = SecurityCode <$> code_ <*> verifiedAt_
|
||||
in Connection {connId, agentConnId = AgentConnId acId, connLevel, viaContact, viaUserContactLink, viaGroupLink, groupLinkId, customUserProfileId, connStatus, connType, localAlias, entityId, connectionCode, createdAt}
|
||||
in Connection {connId, agentConnId = AgentConnId acId, connLevel, viaContact, viaUserContactLink, viaGroupLink, groupLinkId, customUserProfileId, connStatus, connType, localAlias, entityId, connectionCode, authErrCounter, createdAt}
|
||||
where
|
||||
entityId_ :: ConnType -> Maybe Int64
|
||||
entityId_ ConnContact = contactId
|
||||
@@ -1338,8 +1357,8 @@ toConnection ((connId, acId, connLevel, viaContact, viaUserContactLink, viaGroup
|
||||
entityId_ ConnUserContact = userContactLinkId
|
||||
|
||||
toMaybeConnection :: MaybeConnectionRow -> Maybe Connection
|
||||
toMaybeConnection ((Just connId, Just agentConnId, Just connLevel, viaContact, viaUserContactLink, Just viaGroupLink, groupLinkId, customUserProfileId, Just connStatus, Just connType, Just localAlias) :. (contactId, groupMemberId, sndFileId, rcvFileId, userContactLinkId) :. (Just createdAt, code_, verifiedAt_)) =
|
||||
Just $ toConnection ((connId, agentConnId, connLevel, viaContact, viaUserContactLink, viaGroupLink, groupLinkId, customUserProfileId, connStatus, connType, localAlias) :. (contactId, groupMemberId, sndFileId, rcvFileId, userContactLinkId) :. (createdAt, code_, verifiedAt_))
|
||||
toMaybeConnection ((Just connId, Just agentConnId, Just connLevel, viaContact, viaUserContactLink, Just viaGroupLink, groupLinkId, customUserProfileId, Just connStatus, Just connType, Just localAlias) :. (contactId, groupMemberId, sndFileId, rcvFileId, userContactLinkId) :. (Just createdAt, code_, verifiedAt_, Just authErrCounter)) =
|
||||
Just $ toConnection ((connId, agentConnId, connLevel, viaContact, viaUserContactLink, viaGroupLink, groupLinkId, customUserProfileId, connStatus, connType, localAlias) :. (contactId, groupMemberId, sndFileId, rcvFileId, userContactLinkId) :. (createdAt, code_, verifiedAt_, authErrCounter))
|
||||
toMaybeConnection _ = Nothing
|
||||
|
||||
getMatchingContacts :: DB.Connection -> User -> Contact -> IO [Contact]
|
||||
@@ -1510,7 +1529,7 @@ getConnectionEntity db user@User {userId, userContactId} agentConnId = do
|
||||
db
|
||||
[sql|
|
||||
SELECT connection_id, agent_conn_id, conn_level, via_contact, via_user_contact_link, via_group_link, group_link_id, custom_user_profile_id,
|
||||
conn_status, conn_type, local_alias, contact_id, group_member_id, snd_file_id, rcv_file_id, user_contact_link_id, created_at, security_code, security_code_verified_at
|
||||
conn_status, conn_type, local_alias, contact_id, group_member_id, snd_file_id, rcv_file_id, user_contact_link_id, created_at, security_code, security_code_verified_at, auth_err_counter
|
||||
FROM connections
|
||||
WHERE user_id = ? AND agent_conn_id = ?
|
||||
|]
|
||||
@@ -1610,7 +1629,7 @@ getConnectionById db User {userId} connId = ExceptT $ do
|
||||
db
|
||||
[sql|
|
||||
SELECT connection_id, agent_conn_id, conn_level, via_contact, via_user_contact_link, via_group_link, group_link_id, custom_user_profile_id,
|
||||
conn_status, conn_type, local_alias, contact_id, group_member_id, snd_file_id, rcv_file_id, user_contact_link_id, created_at, security_code, security_code_verified_at
|
||||
conn_status, conn_type, local_alias, contact_id, group_member_id, snd_file_id, rcv_file_id, user_contact_link_id, created_at, security_code, security_code_verified_at, auth_err_counter
|
||||
FROM connections
|
||||
WHERE user_id = ? AND connection_id = ?
|
||||
|]
|
||||
@@ -1655,7 +1674,7 @@ getGroupAndMember db User {userId, userContactId} groupMemberId =
|
||||
m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status,
|
||||
m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.local_alias, p.preferences,
|
||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
|
||||
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at
|
||||
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter
|
||||
FROM group_members m
|
||||
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
|
||||
JOIN groups g ON g.group_id = m.group_id
|
||||
@@ -1935,7 +1954,7 @@ groupMemberQuery =
|
||||
m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status,
|
||||
m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.local_alias, p.preferences,
|
||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
|
||||
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at
|
||||
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter
|
||||
FROM group_members m
|
||||
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
|
||||
LEFT JOIN connections c ON c.connection_id = (
|
||||
@@ -2095,7 +2114,7 @@ getContactViaMember db user@User {userId} GroupMember {groupMemberId} =
|
||||
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts,
|
||||
-- Connection
|
||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias,
|
||||
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at
|
||||
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter
|
||||
FROM contacts ct
|
||||
JOIN contact_profiles cp ON cp.contact_profile_id = ct.contact_profile_id
|
||||
JOIN connections c ON c.connection_id = (
|
||||
@@ -2400,7 +2419,7 @@ getViaGroupMember db User {userId, userContactId} Contact {contactId} =
|
||||
m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status,
|
||||
m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.local_alias, p.preferences,
|
||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
|
||||
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at
|
||||
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter
|
||||
FROM group_members m
|
||||
JOIN contacts ct ON ct.contact_id = m.contact_id
|
||||
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
|
||||
@@ -2433,7 +2452,7 @@ getViaGroupContact db user@User {userId} GroupMember {groupMemberId} =
|
||||
ct.contact_id, ct.contact_profile_id, ct.local_display_name, p.display_name, p.full_name, p.image, p.local_alias, ct.via_group, ct.contact_used, ct.enable_ntfs,
|
||||
p.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts,
|
||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
|
||||
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at
|
||||
c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter
|
||||
FROM contacts ct
|
||||
JOIN contact_profiles p ON ct.contact_profile_id = p.contact_profile_id
|
||||
JOIN connections c ON c.connection_id = (
|
||||
@@ -3348,7 +3367,7 @@ getDirectChatPreviews_ db user@User {userId} = do
|
||||
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts,
|
||||
-- Connection
|
||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias,
|
||||
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at,
|
||||
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter,
|
||||
-- ChatStats
|
||||
COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), ct.unread_chat,
|
||||
-- ChatItem
|
||||
@@ -3671,7 +3690,7 @@ getContact db user@User {userId} contactId =
|
||||
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts,
|
||||
-- Connection
|
||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias,
|
||||
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at
|
||||
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter
|
||||
FROM contacts ct
|
||||
JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id
|
||||
LEFT JOIN connections c ON c.contact_id = ct.contact_id
|
||||
|
||||
@@ -1603,10 +1603,17 @@ data Connection = Connection
|
||||
localAlias :: Text,
|
||||
entityId :: Maybe Int64, -- contact, group member, file ID or user contact ID
|
||||
connectionCode :: Maybe SecurityCode,
|
||||
authErrCounter :: Int,
|
||||
createdAt :: UTCTime
|
||||
}
|
||||
deriving (Eq, Show, Generic)
|
||||
|
||||
authErrDisableCount :: Int
|
||||
authErrDisableCount = 10
|
||||
|
||||
connDisabled :: Connection -> Bool
|
||||
connDisabled Connection {authErrCounter} = authErrCounter >= authErrDisableCount
|
||||
|
||||
data SecurityCode = SecurityCode {securityCode :: Text, verifiedAt :: UTCTime}
|
||||
deriving (Eq, Show, Generic)
|
||||
|
||||
|
||||
@@ -216,6 +216,8 @@ responseToView user_ testView liveItems ts = \case
|
||||
[ maybe "no chat lock" (("chat lock: " <>) . plain) chatLockName,
|
||||
plain $ "agent locks: " <> LB.unpack (J.encode agentLocks)
|
||||
]
|
||||
CRAgentStats stats -> map (plain . intercalate ",") stats
|
||||
CRConnectionDisabled entity -> viewConnectionEntityDisabled entity
|
||||
CRMessageError prefix err -> [plain prefix <> ": " <> plain err]
|
||||
CRChatError e -> viewChatError e
|
||||
where
|
||||
@@ -1136,6 +1138,8 @@ viewChatError = \case
|
||||
CEInvalidConnReq -> viewInvalidConnReq
|
||||
CEInvalidChatMessage e -> ["chat message error: " <> sShow e]
|
||||
CEContactNotReady c -> [ttyContact' c <> ": not ready"]
|
||||
CEContactDisabled Contact {localDisplayName = c} -> [ttyContact c <> ": disabled, to enable: " <> highlight ("/enable " <> c) <> ", to delete: " <> highlight ("/d " <> c)]
|
||||
CEConnectionDisabled _ -> []
|
||||
CEGroupDuplicateMember c -> ["contact " <> ttyContact c <> " is already in the group"]
|
||||
CEGroupDuplicateMemberId -> ["cannot add member - duplicate member ID"]
|
||||
CEGroupUserRole -> ["you have insufficient permissions for this group command"]
|
||||
@@ -1200,21 +1204,56 @@ viewChatError = \case
|
||||
DBErrorExport e -> ["error encrypting database: " <> sqliteError' e]
|
||||
DBErrorOpen e -> ["error opening database after encryption: " <> sqliteError' e]
|
||||
e -> ["chat database error: " <> sShow e]
|
||||
ChatErrorAgent err -> case err of
|
||||
ChatErrorAgent err entity_ -> case err of
|
||||
SMP SMP.AUTH ->
|
||||
[ "error: connection authorization failed - this could happen if connection was deleted,\
|
||||
\ secured with different credentials, or due to a bug - please re-create the connection"
|
||||
[ withConnEntity
|
||||
<> "error: connection authorization failed - this could happen if connection was deleted,\
|
||||
\ secured with different credentials, or due to a bug - please re-create the connection"
|
||||
]
|
||||
AGENT A_DUPLICATE -> []
|
||||
AGENT A_PROHIBITED -> []
|
||||
CONN NOT_FOUND -> []
|
||||
e -> ["smp agent error: " <> sShow e]
|
||||
e -> [withConnEntity <> "smp agent error: " <> sShow e]
|
||||
where
|
||||
withConnEntity = case entity_ of
|
||||
Just entity@(RcvDirectMsgConnection conn contact_) -> case contact_ of
|
||||
Just Contact {contactId} ->
|
||||
"[" <> connEntityLabel entity <> ", contactId: " <> sShow contactId <> ", connId: " <> cId conn <> "] "
|
||||
Nothing ->
|
||||
"[" <> connEntityLabel entity <> ", connId: " <> cId conn <> "] "
|
||||
Just entity@(RcvGroupMsgConnection conn GroupInfo {groupId} GroupMember {groupMemberId}) ->
|
||||
"[" <> connEntityLabel entity <> ", groupId: " <> sShow groupId <> ", memberId: " <> sShow groupMemberId <> ", connId: " <> cId conn <> "] "
|
||||
Just entity@(RcvFileConnection conn RcvFileTransfer {fileId}) ->
|
||||
"[" <> connEntityLabel entity <> ", fileId: " <> sShow fileId <> ", connId: " <> cId conn <> "] "
|
||||
Just entity@(SndFileConnection conn SndFileTransfer {fileId}) ->
|
||||
"[" <> connEntityLabel entity <> ", fileId: " <> sShow fileId <> ", connId: " <> cId conn <> "] "
|
||||
Just entity@(UserContactConnection conn UserContact {userContactLinkId}) ->
|
||||
"[" <> connEntityLabel entity <> ", userContactLinkId: " <> sShow userContactLinkId <> ", connId: " <> cId conn <> "] "
|
||||
Nothing -> ""
|
||||
cId conn = sShow (connId (conn :: Connection))
|
||||
where
|
||||
fileNotFound fileId = ["file " <> sShow fileId <> " not found"]
|
||||
sqliteError' = \case
|
||||
SQLiteErrorNotADatabase -> "wrong passphrase or invalid database file"
|
||||
SQLiteError e -> sShow e
|
||||
|
||||
viewConnectionEntityDisabled :: ConnectionEntity -> [StyledString]
|
||||
viewConnectionEntityDisabled entity = case entity of
|
||||
RcvDirectMsgConnection _ (Just Contact {localDisplayName = c}) -> ["[" <> entityLabel <> "] connection is disabled, to enable: " <> highlight ("/enable " <> c) <> ", to delete: " <> highlight ("/d " <> c)]
|
||||
RcvGroupMsgConnection _ GroupInfo {localDisplayName = g} GroupMember {localDisplayName = m} -> ["[" <> entityLabel <> "] connection is disabled, to enable: " <> highlight ("/enable #" <> g <> " " <> m)]
|
||||
_ -> ["[" <> entityLabel <> "] connection is disabled"]
|
||||
where
|
||||
entityLabel = connEntityLabel entity
|
||||
|
||||
connEntityLabel :: ConnectionEntity -> StyledString
|
||||
connEntityLabel = \case
|
||||
RcvDirectMsgConnection _ (Just Contact {localDisplayName = c}) -> plain c
|
||||
RcvDirectMsgConnection _ Nothing -> "rcv direct msg"
|
||||
RcvGroupMsgConnection _ GroupInfo {localDisplayName = g} GroupMember {localDisplayName = m} -> plain $ "#" <> g <> " " <> m
|
||||
RcvFileConnection _ RcvFileTransfer {fileInvitation = FileInvitation {fileName}} -> plain $ "rcv file " <> T.pack fileName
|
||||
SndFileConnection _ SndFileTransfer {fileName} -> plain $ "snd file " <> T.pack fileName
|
||||
UserContactConnection _ UserContact {} -> "contact address"
|
||||
|
||||
ttyContact :: ContactName -> StyledString
|
||||
ttyContact = styled $ colored Green
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ extra-deps:
|
||||
# - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561
|
||||
# - ../simplexmq
|
||||
- github: simplex-chat/simplexmq
|
||||
commit: fb21d9836e07706c7498baa967f932cb11b818e5
|
||||
commit: 058e3ac55e8577280267f9341ccd7d3e971bc51a
|
||||
# - ../direct-sqlcipher
|
||||
- github: simplex-chat/direct-sqlcipher
|
||||
commit: 34309410eb2069b029b8fc1872deb1e0db123294
|
||||
|
||||
@@ -59,6 +59,7 @@ testOpts =
|
||||
chatCmd = "",
|
||||
chatCmdDelay = 3,
|
||||
chatServerPort = Nothing,
|
||||
optFilesFolder = Nothing,
|
||||
allowInstantFiles = True,
|
||||
maintenance = False
|
||||
}
|
||||
@@ -286,7 +287,8 @@ serverCfg =
|
||||
logStatsStartTime = 0,
|
||||
serverStatsLogFile = "tests/smp-server-stats.daily.log",
|
||||
serverStatsBackupFile = Nothing,
|
||||
smpServerVRange = supportedSMPServerVRange
|
||||
smpServerVRange = supportedSMPServerVRange,
|
||||
logTLSErrors = True
|
||||
}
|
||||
|
||||
withSmpServer :: IO a -> IO a
|
||||
|
||||
@@ -57,6 +57,8 @@ chatTests = do
|
||||
it "direct message quoted replies" testDirectMessageQuotedReply
|
||||
it "direct message update" testDirectMessageUpdate
|
||||
it "direct message delete" testDirectMessageDelete
|
||||
it "direct live message" testDirectLiveMessage
|
||||
it "repeat AUTH errors disable contact" testRepeatAuthErrorsDisableContact
|
||||
describe "chat groups" $ do
|
||||
describe "add contacts, create group and send/receive messages" testGroup
|
||||
it "add contacts, create group and send/receive messages, check messages" testGroupCheckMessages
|
||||
@@ -73,6 +75,7 @@ chatTests = do
|
||||
it "group message quoted replies" testGroupMessageQuotedReply
|
||||
it "group message update" testGroupMessageUpdate
|
||||
it "group message delete" testGroupMessageDelete
|
||||
it "group live message" testGroupLiveMessage
|
||||
it "update group profile" testUpdateGroupProfile
|
||||
it "update member role" testUpdateMemberRole
|
||||
it "unused contacts are deleted after all their groups are deleted" testGroupDeleteUnusedContacts
|
||||
@@ -156,10 +159,12 @@ chatTests = do
|
||||
-- it "v1 to v2" testFullAsyncV1toV2
|
||||
-- it "v2 to v1" testFullAsyncV2toV1
|
||||
describe "async sending and receiving files" $ do
|
||||
it "send and receive file, sender restarts" testAsyncFileTransferSenderRestarts
|
||||
it "send and receive file, receiver restarts" testAsyncFileTransferReceiverRestarts
|
||||
xdescribe "send and receive file, fully asynchronous" $ do
|
||||
it "v2" testAsyncFileTransfer
|
||||
it "v1" testAsyncFileTransferV1
|
||||
xit "send and receive file to group, fully asynchronous" testAsyncGroupFileTransfer
|
||||
it "send and receive file to group, fully asynchronous" testAsyncGroupFileTransfer
|
||||
describe "webrtc calls api" $ do
|
||||
it "negotiate call" testNegotiateCall
|
||||
describe "maintenance mode" $ do
|
||||
@@ -495,6 +500,42 @@ testDirectMessageDelete =
|
||||
bob #$> ("/_delete item @2 " <> itemId 4 <> " internal", id, "message deleted")
|
||||
bob #$> ("/_get chat @2 count=100", chat', chatFeatures' <> [((0, "hello 🙂"), Nothing), ((1, "do you receive my messages?"), Just (0, "hello 🙂"))])
|
||||
|
||||
testDirectLiveMessage :: IO ()
|
||||
testDirectLiveMessage =
|
||||
testChat2 aliceProfile bobProfile $ \alice bob -> do
|
||||
connectUsers alice bob
|
||||
-- non-empty live message is sent instantly
|
||||
alice `send` "/live @bob hello"
|
||||
bob <# "alice> [LIVE started] use /show [on/off/4] hello"
|
||||
alice ##> ("/_update item @2 " <> itemId 1 <> " text hello there")
|
||||
alice <# "@bob [LIVE] hello there"
|
||||
bob <# "alice> [LIVE ended] hello there"
|
||||
-- empty live message is also sent instantly
|
||||
alice `send` "/live @bob"
|
||||
bob <# "alice> [LIVE started] use /show [on/off/5]"
|
||||
alice ##> ("/_update item @2 " <> itemId 2 <> " text hello 2")
|
||||
alice <# "@bob [LIVE] hello 2"
|
||||
bob <# "alice> [LIVE ended] hello 2"
|
||||
|
||||
testRepeatAuthErrorsDisableContact :: IO ()
|
||||
testRepeatAuthErrorsDisableContact =
|
||||
testChat2 aliceProfile bobProfile $ \alice bob -> do
|
||||
connectUsers alice bob
|
||||
alice <##> bob
|
||||
bob ##> "/d alice"
|
||||
bob <## "alice: contact is deleted"
|
||||
forM_ [1 .. authErrDisableCount] $ \_ -> sendAuth alice
|
||||
alice <## "[bob] connection is disabled, to enable: /enable bob, to delete: /d bob"
|
||||
alice ##> "@bob hey"
|
||||
alice <## "bob: disabled, to enable: /enable bob, to delete: /d bob"
|
||||
alice ##> "/enable bob"
|
||||
alice <## "ok"
|
||||
sendAuth alice
|
||||
where
|
||||
sendAuth alice = do
|
||||
alice #> "@bob hey"
|
||||
alice <## "[bob, contactId: 2, connId: 1] error: connection authorization failed - this could happen if connection was deleted, secured with different credentials, or due to a bug - please re-create the connection"
|
||||
|
||||
testGroup :: Spec
|
||||
testGroup = versionTestMatrix3 runTestGroup
|
||||
where
|
||||
@@ -1004,6 +1045,7 @@ testGroupDeleteInvitedContact =
|
||||
alice ##> "@bob hey"
|
||||
alice <## "no contact bob"
|
||||
bob #> "@alice hey"
|
||||
bob <## "[alice, contactId: 2, connId: 1] error: connection authorization failed - this could happen if connection was deleted, secured with different credentials, or due to a bug - please re-create the connection"
|
||||
(alice </)
|
||||
|
||||
testDeleteGroupMemberProfileKept :: IO ()
|
||||
@@ -1055,6 +1097,7 @@ testDeleteGroupMemberProfileKept =
|
||||
alice ##> "@bob hey"
|
||||
alice <## "no contact bob"
|
||||
bob #> "@alice hey"
|
||||
bob <## "[alice, contactId: 2, connId: 1] error: connection authorization failed - this could happen if connection was deleted, secured with different credentials, or due to a bug - please re-create the connection"
|
||||
(alice </)
|
||||
-- delete group 1
|
||||
alice ##> "/d #team"
|
||||
@@ -1371,6 +1414,30 @@ testGroupMessageDelete =
|
||||
bob #$> ("/_get chat #1 count=3", chat', [((0, "hello!"), Nothing), ((1, "hi alice"), Just (0, "hello!")), ((0, "how are you? [marked deleted]"), Nothing)])
|
||||
cath #$> ("/_get chat #1 count=3", chat', [((0, "hello!"), Nothing), ((0, "hi alice"), Just (0, "hello!")), ((1, "how are you? [marked deleted]"), Nothing)])
|
||||
|
||||
testGroupLiveMessage :: IO ()
|
||||
testGroupLiveMessage =
|
||||
testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do
|
||||
createGroup3 "team" alice bob cath
|
||||
threadDelay 500000
|
||||
-- non-empty live message is sent instantly
|
||||
alice `send` "/live #team hello"
|
||||
msgItemId1 <- lastItemId alice
|
||||
bob <#. "#team alice> [LIVE started]"
|
||||
cath <#. "#team alice> [LIVE started]"
|
||||
alice ##> ("/_update item #1 " <> msgItemId1 <> " text hello there")
|
||||
alice <# "#team [LIVE] hello there"
|
||||
bob <# "#team alice> [LIVE ended] hello there"
|
||||
cath <# "#team alice> [LIVE ended] hello there"
|
||||
-- empty live message is also sent instantly
|
||||
alice `send` "/live #team"
|
||||
msgItemId2 <- lastItemId alice
|
||||
bob <#. "#team alice> [LIVE started]"
|
||||
cath <#. "#team alice> [LIVE started]"
|
||||
alice ##> ("/_update item #1 " <> msgItemId2 <> " text hello 2")
|
||||
alice <# "#team [LIVE] hello 2"
|
||||
bob <# "#team alice> [LIVE ended] hello 2"
|
||||
cath <# "#team alice> [LIVE ended] hello 2"
|
||||
|
||||
testUpdateGroupProfile :: IO ()
|
||||
testUpdateGroupProfile =
|
||||
testChat3 aliceProfile bobProfile cathProfile $
|
||||
@@ -3945,6 +4012,34 @@ testFullAsyncV2toV1 = withTmpFiles $ do
|
||||
withNewBob = withNewTestChat "bob" bobProfile
|
||||
withBob = withTestChat "bob"
|
||||
|
||||
testAsyncFileTransferSenderRestarts :: IO ()
|
||||
testAsyncFileTransferSenderRestarts = withTmpFiles $ do
|
||||
withNewTestChat "bob" bobProfile $ \bob -> do
|
||||
withNewTestChat "alice" aliceProfile $ \alice -> do
|
||||
connectUsers alice bob
|
||||
startFileTransfer' alice bob "test_1MB.pdf" "1017.7 KiB / 1042157 bytes"
|
||||
threadDelay 100000
|
||||
withTestChatContactConnected "alice" $ \alice -> do
|
||||
alice <## "completed sending file 1 (test_1MB.pdf) to bob"
|
||||
bob <## "completed receiving file 1 (test_1MB.pdf) from alice"
|
||||
src <- B.readFile "./tests/fixtures/test_1MB.pdf"
|
||||
dest <- B.readFile "./tests/tmp/test_1MB.pdf"
|
||||
dest `shouldBe` src
|
||||
|
||||
testAsyncFileTransferReceiverRestarts :: IO ()
|
||||
testAsyncFileTransferReceiverRestarts = withTmpFiles $ do
|
||||
withNewTestChat "alice" aliceProfile $ \alice -> do
|
||||
withNewTestChat "bob" bobProfile $ \bob -> do
|
||||
connectUsers alice bob
|
||||
startFileTransfer' alice bob "test_1MB.pdf" "1017.7 KiB / 1042157 bytes"
|
||||
threadDelay 100000
|
||||
withTestChatContactConnected "bob" $ \bob -> do
|
||||
alice <## "completed sending file 1 (test_1MB.pdf) to bob"
|
||||
bob <## "completed receiving file 1 (test_1MB.pdf) from alice"
|
||||
src <- B.readFile "./tests/fixtures/test_1MB.pdf"
|
||||
dest <- B.readFile "./tests/tmp/test_1MB.pdf"
|
||||
dest `shouldBe` src
|
||||
|
||||
testAsyncFileTransfer :: IO ()
|
||||
testAsyncFileTransfer = withTmpFiles $ do
|
||||
withNewTestChat "alice" aliceProfile $ \alice ->
|
||||
@@ -5062,6 +5157,13 @@ cc <##. line = do
|
||||
unless prefix $ print ("expected to start from: " <> line, ", got: " <> l)
|
||||
prefix `shouldBe` True
|
||||
|
||||
(<#.) :: TestCC -> String -> Expectation
|
||||
cc <#. line = do
|
||||
l <- dropTime <$> getTermLine cc
|
||||
let prefix = line `isPrefixOf` l
|
||||
unless prefix $ print ("expected to start from: " <> line, ", got: " <> l)
|
||||
prefix `shouldBe` True
|
||||
|
||||
(<##..) :: TestCC -> [String] -> Expectation
|
||||
cc <##.. ls = do
|
||||
l <- getTermLine cc
|
||||
|
||||
10
website/src/_includes/blog_previews/20230103.html
Normal file
10
website/src/_includes/blog_previews/20230103.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<p>v4.4 is released:</p>
|
||||
|
||||
<ul class="mb-[12px]">
|
||||
<li>disappearing messages!</li>
|
||||
<li>live messages</li>
|
||||
<li>connection security verification</li>
|
||||
<li>support for GIFs and stickers</li>
|
||||
</ul>
|
||||
|
||||
<p>Also, the app interface is now available in French - thanks to Weblate and our users!</p>
|
||||
Reference in New Issue
Block a user