mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b027708828 | |||
| ab4e4e1db9 | |||
| a393bc8163 | |||
| 3bc4fd222c | |||
| 1b01dcec6d | |||
| 5d12217eab | |||
| 4b0046a60b | |||
| 114b76e3f8 | |||
| 8ff8f9d695 | |||
| 1e3c2024bb | |||
| f4ffa5237c | |||
| c409f58067 | |||
| 0a9aa8b3d2 | |||
| 69196084fc | |||
| 7c9c358ce2 | |||
| 80e0bfb61f | |||
| 19881703e5 | |||
| 7eca44bb84 | |||
| 774af334fd | |||
| af414d7f6e | |||
| 9dad55ce8d | |||
| a3283708e7 | |||
| e833d66557 | |||
| 71f5b51350 | |||
| 20cec4db11 | |||
| 2085dc5d60 | |||
| 6d5c3ae484 | |||
| e73f5c40cf | |||
| 4c960bdc44 | |||
| dcb82951ed | |||
| 138cce4436 | |||
| 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 | |||
| 768c497025 |
@@ -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)
|
||||
|
||||
@@ -149,7 +151,7 @@ What is already implemented:
|
||||
|
||||
We plan to add soon:
|
||||
|
||||
1. Message queue rotation. Currently the queues created between two users are used until the contact is deleted, providing a long-term pairwise identifiers of the conversation. We are planning to add queue rotation to make these identifiers termporary and rotate based on some schedule TBC (e.g., every X messages, or every X hours/days).
|
||||
1. Message queue rotation. Currently the queues created between two users are used until the contact is deleted, providing a long-term pairwise identifiers of the conversation. We are planning to add queue rotation to make these identifiers temporary and rotate based on some schedule TBC (e.g., every X messages, or every X hours/days).
|
||||
2. Local files encryption. Currently the images and files you send and receive are stored in the app unencrypted, you can delete them via `Settings / Database passphrase & export`.
|
||||
3. Message "mixing" - adding latency to message delivery, to protect against traffic correlation by message time.
|
||||
|
||||
@@ -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 84
|
||||
versionName "4.4"
|
||||
versionCode 92
|
||||
versionName "4.4.4"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
ndk {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -17,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.*
|
||||
@@ -156,7 +157,7 @@ class ChatModel(val controller: ChatController) {
|
||||
}
|
||||
}
|
||||
|
||||
fun addChatItem(cInfo: ChatInfo, cItem: ChatItem) {
|
||||
suspend fun addChatItem(cInfo: ChatInfo, cItem: ChatItem) {
|
||||
// update previews
|
||||
val i = getChatIndex(cInfo.id)
|
||||
val chat: Chat
|
||||
@@ -180,11 +181,17 @@ class ChatModel(val controller: ChatController) {
|
||||
}
|
||||
// add to current chat
|
||||
if (chatId.value == cInfo.id) {
|
||||
chatItems.add(cItem)
|
||||
withContext(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun upsertChatItem(cInfo: ChatInfo, cItem: ChatItem): Boolean {
|
||||
suspend fun upsertChatItem(cInfo: ChatInfo, cItem: ChatItem): Boolean {
|
||||
// update previews
|
||||
val i = getChatIndex(cInfo.id)
|
||||
val chat: Chat
|
||||
@@ -211,7 +218,9 @@ class ChatModel(val controller: ChatController) {
|
||||
chatItems[itemIndex] = cItem
|
||||
return false
|
||||
} else {
|
||||
chatItems.add(cItem)
|
||||
withContext(Dispatchers.Main) {
|
||||
chatItems.add(cItem)
|
||||
}
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
@@ -255,6 +264,20 @@ class ChatModel(val controller: ChatController) {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun addLiveDummy(chatInfo: ChatInfo): ChatItem {
|
||||
val cItem = ChatItem.liveDummy(chatInfo is ChatInfo.Direct)
|
||||
withContext(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
|
||||
@@ -1278,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(),
|
||||
@@ -1300,6 +1324,26 @@ data class ChatItem (
|
||||
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(),
|
||||
@@ -1491,11 +1535,11 @@ sealed class CIContent: ItemContent {
|
||||
|
||||
fun preferenceText(feature: Feature, allowed: FeatureAllowed, param: Int?): String = when {
|
||||
allowed != FeatureAllowed.NO && feature.hasParam && param != null ->
|
||||
"offered ${feature.text}: ${TimedMessagesPreference.ttlText(param)}"
|
||||
String.format(generalGetString(R.string.feature_offered_item_with_param), feature.text, TimedMessagesPreference.ttlText(param))
|
||||
allowed != FeatureAllowed.NO ->
|
||||
"offered ${feature.text}"
|
||||
String.format(generalGetString(R.string.feature_offered_item), feature.text, TimedMessagesPreference.ttlText(param))
|
||||
else ->
|
||||
"cancelled ${feature.text}"
|
||||
String.format(generalGetString(R.string.feature_cancelled_item), feature.text, TimedMessagesPreference.ttlText(param))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,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(
|
||||
@@ -116,6 +116,7 @@ class AppPreferences(val context: Context) {
|
||||
val networkTCPConnectTimeout = mkTimeoutPreference(SHARED_PREFS_NETWORK_TCP_CONNECT_TIMEOUT, NetCfg.defaults.tcpConnectTimeout, NetCfg.proxyDefaults.tcpConnectTimeout)
|
||||
val networkTCPTimeout = mkTimeoutPreference(SHARED_PREFS_NETWORK_TCP_TIMEOUT, NetCfg.defaults.tcpTimeout, NetCfg.proxyDefaults.tcpTimeout)
|
||||
val networkSMPPingInterval = mkLongPreference(SHARED_PREFS_NETWORK_SMP_PING_INTERVAL, NetCfg.defaults.smpPingInterval)
|
||||
val networkSMPPingCount = mkIntPreference(SHARED_PREFS_NETWORK_SMP_PING_COUNT, NetCfg.defaults.smpPingCount)
|
||||
val networkEnableKeepAlive = mkBoolPreference(SHARED_PREFS_NETWORK_ENABLE_KEEP_ALIVE, NetCfg.defaults.enableKeepAlive)
|
||||
val networkTCPKeepIdle = mkIntPreference(SHARED_PREFS_NETWORK_TCP_KEEP_IDLE, KeepAliveOpts.defaults.keepIdle)
|
||||
val networkTCPKeepIntvl = mkIntPreference(SHARED_PREFS_NETWORK_TCP_KEEP_INTVL, KeepAliveOpts.defaults.keepIntvl)
|
||||
@@ -212,6 +213,7 @@ class AppPreferences(val context: Context) {
|
||||
private const val SHARED_PREFS_NETWORK_TCP_CONNECT_TIMEOUT = "NetworkTCPConnectTimeout"
|
||||
private const val SHARED_PREFS_NETWORK_TCP_TIMEOUT = "NetworkTCPTimeout"
|
||||
private const val SHARED_PREFS_NETWORK_SMP_PING_INTERVAL = "NetworkSMPPingInterval"
|
||||
private const val SHARED_PREFS_NETWORK_SMP_PING_COUNT = "NetworkSMPPingCount"
|
||||
private const val SHARED_PREFS_NETWORK_ENABLE_KEEP_ALIVE = "NetworkEnableKeepAlive"
|
||||
private const val SHARED_PREFS_NETWORK_TCP_KEEP_IDLE = "NetworkTCPKeepIdle"
|
||||
private const val SHARED_PREFS_NETWORK_TCP_KEEP_INTVL = "NetworkTCPKeepIntvl"
|
||||
@@ -993,6 +995,16 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun apiGetVersion(): CoreVersionInfo? {
|
||||
val r = sendCmd(CC.ShowVersion())
|
||||
return if (r is CR.VersionInfo) {
|
||||
r.versionInfo
|
||||
} else {
|
||||
Log.e(TAG, "apiGetVersion bad response: ${r.responseType} ${r.details}")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun networkErrorAlert(r: CR): Boolean {
|
||||
return when {
|
||||
r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent
|
||||
@@ -1023,7 +1035,7 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
|
||||
AlertManager.shared.showAlertMsg(title, errMsg)
|
||||
}
|
||||
|
||||
fun processReceivedMsg(r: CR) {
|
||||
suspend fun processReceivedMsg(r: CR) {
|
||||
lastMsgReceivedTimestamp = System.currentTimeMillis()
|
||||
chatModel.terminalItems.add(TerminalItem.resp(r))
|
||||
when (r) {
|
||||
@@ -1257,7 +1269,7 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
|
||||
}
|
||||
}
|
||||
|
||||
private fun chatItemSimpleUpdate(aChatItem: AChatItem) {
|
||||
private suspend fun chatItemSimpleUpdate(aChatItem: AChatItem) {
|
||||
val cInfo = aChatItem.chatInfo
|
||||
val cItem = aChatItem.chatItem
|
||||
if (chatModel.upsertChatItem(cInfo, cItem)) {
|
||||
@@ -1485,6 +1497,7 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
|
||||
val tcpConnectTimeout = appPrefs.networkTCPConnectTimeout.get()
|
||||
val tcpTimeout = appPrefs.networkTCPTimeout.get()
|
||||
val smpPingInterval = appPrefs.networkSMPPingInterval.get()
|
||||
val smpPingCount = appPrefs.networkSMPPingCount.get()
|
||||
val enableKeepAlive = appPrefs.networkEnableKeepAlive.get()
|
||||
val tcpKeepAlive = if (enableKeepAlive) {
|
||||
val keepIdle = appPrefs.networkTCPKeepIdle.get()
|
||||
@@ -1501,7 +1514,8 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
|
||||
tcpConnectTimeout = tcpConnectTimeout,
|
||||
tcpTimeout = tcpTimeout,
|
||||
tcpKeepAlive = tcpKeepAlive,
|
||||
smpPingInterval = smpPingInterval
|
||||
smpPingInterval = smpPingInterval,
|
||||
smpPingCount = smpPingCount
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1512,6 +1526,7 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
|
||||
appPrefs.networkTCPConnectTimeout.set(cfg.tcpConnectTimeout)
|
||||
appPrefs.networkTCPTimeout.set(cfg.tcpTimeout)
|
||||
appPrefs.networkSMPPingInterval.set(cfg.smpPingInterval)
|
||||
appPrefs.networkSMPPingCount.set(cfg.smpPingCount)
|
||||
if (cfg.tcpKeepAlive != null) {
|
||||
appPrefs.networkEnableKeepAlive.set(true)
|
||||
appPrefs.networkTCPKeepIdle.set(cfg.tcpKeepAlive.keepIdle)
|
||||
@@ -1607,6 +1622,7 @@ sealed class CC {
|
||||
class ApiChatRead(val type: ChatType, val id: Long, val range: ItemRange): CC()
|
||||
class ApiChatUnread(val type: ChatType, val id: Long, val unreadChat: Boolean): CC()
|
||||
class ReceiveFile(val fileId: Long, val inline: Boolean): CC()
|
||||
class ShowVersion(): CC()
|
||||
|
||||
val cmdString: String get() = when (this) {
|
||||
is Console -> cmd
|
||||
@@ -1678,6 +1694,7 @@ sealed class CC {
|
||||
is ApiChatRead -> "/_read chat ${chatRef(type, id)} from=${range.from} to=${range.to}"
|
||||
is ApiChatUnread -> "/_unread chat ${chatRef(type, id)} ${onOff(unreadChat)}"
|
||||
is ReceiveFile -> "/freceive $fileId inline=${onOff(inline)}"
|
||||
is ShowVersion -> "/version"
|
||||
}
|
||||
|
||||
val cmdType: String get() = when (this) {
|
||||
@@ -1750,6 +1767,7 @@ sealed class CC {
|
||||
is ApiChatRead -> "apiChatRead"
|
||||
is ApiChatUnread -> "apiChatUnread"
|
||||
is ReceiveFile -> "receiveFile"
|
||||
is ShowVersion -> "showVersion"
|
||||
}
|
||||
|
||||
class ItemRange(val from: Long, val to: Long)
|
||||
@@ -1953,6 +1971,7 @@ data class NetCfg(
|
||||
val tcpTimeout: Long, // microseconds
|
||||
val tcpKeepAlive: KeepAliveOpts?,
|
||||
val smpPingInterval: Long, // microseconds
|
||||
val smpPingCount: Int,
|
||||
val logTLSErrors: Boolean = false
|
||||
) {
|
||||
val useSocksProxy: Boolean get() = socksProxy != null
|
||||
@@ -1965,7 +1984,8 @@ data class NetCfg(
|
||||
tcpConnectTimeout = 10_000_000,
|
||||
tcpTimeout = 7_000_000,
|
||||
tcpKeepAlive = KeepAliveOpts.defaults,
|
||||
smpPingInterval = 600_000_000
|
||||
smpPingInterval = 1200_000_000,
|
||||
smpPingCount = 3
|
||||
)
|
||||
|
||||
val proxyDefaults: NetCfg =
|
||||
@@ -1974,7 +1994,8 @@ data class NetCfg(
|
||||
tcpConnectTimeout = 20_000_000,
|
||||
tcpTimeout = 15_000_000,
|
||||
tcpKeepAlive = KeepAliveOpts.defaults,
|
||||
smpPingInterval = 600_000_000
|
||||
smpPingInterval = 1200_000_000,
|
||||
smpPingCount = 3
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2725,6 +2746,7 @@ sealed class CR {
|
||||
@Serializable @SerialName("callEnded") class CallEnded(val contact: Contact): CR()
|
||||
@Serializable @SerialName("newContactConnection") class NewContactConnection(val connection: PendingContactConnection): CR()
|
||||
@Serializable @SerialName("contactConnectionDeleted") class ContactConnectionDeleted(val connection: PendingContactConnection): CR()
|
||||
@Serializable @SerialName("versionInfo") class VersionInfo(val versionInfo: CoreVersionInfo): CR()
|
||||
@Serializable @SerialName("cmdOk") class CmdOk: CR()
|
||||
@Serializable @SerialName("chatCmdError") class ChatCmdError(val chatError: ChatError): CR()
|
||||
@Serializable @SerialName("chatError") class ChatRespError(val chatError: ChatError): CR()
|
||||
@@ -2823,6 +2845,7 @@ sealed class CR {
|
||||
is CallEnded -> "callEnded"
|
||||
is NewContactConnection -> "newContactConnection"
|
||||
is ContactConnectionDeleted -> "contactConnectionDeleted"
|
||||
is VersionInfo -> "versionInfo"
|
||||
is CmdOk -> "cmdOk"
|
||||
is ChatCmdError -> "chatCmdError"
|
||||
is ChatRespError -> "chatError"
|
||||
@@ -2922,6 +2945,7 @@ sealed class CR {
|
||||
is CallEnded -> "contact: ${contact.id}"
|
||||
is NewContactConnection -> json.encodeToString(connection)
|
||||
is ContactConnectionDeleted -> json.encodeToString(connection)
|
||||
is VersionInfo -> json.encodeToString(versionInfo)
|
||||
is CmdOk -> noDetails()
|
||||
is ChatCmdError -> chatError.string
|
||||
is ChatRespError -> chatError.string
|
||||
@@ -2979,6 +3003,13 @@ class AutoAccept(val acceptIncognito: Boolean, val autoReply: MsgContent?) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class CoreVersionInfo(
|
||||
val version: String,
|
||||
val buildTimestamp: String,
|
||||
val simplexmqVersion: String,
|
||||
val simplexmqCommit: String
|
||||
)
|
||||
|
||||
@Serializable
|
||||
sealed class ChatError {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -174,7 +174,7 @@ fun TerminalLog(terminalItems: List<TerminalItem>) {
|
||||
DisposableEffect(Unit) {
|
||||
onDispose { lazyListState = listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset }
|
||||
}
|
||||
val reversedTerminalItems by remember { derivedStateOf { terminalItems.reversed() } }
|
||||
val reversedTerminalItems by remember { derivedStateOf { terminalItems.reversed().toList() } }
|
||||
LazyColumn(state = listState, reverseLayout = true) {
|
||||
items(reversedTerminalItems) { item ->
|
||||
Text(
|
||||
|
||||
@@ -529,7 +529,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
|
||||
}
|
||||
|
||||
Spacer(Modifier.size(8.dp))
|
||||
val reversedChatItems by remember { derivedStateOf { chatItems.reversed() } }
|
||||
val reversedChatItems by remember { derivedStateOf { chatItems.reversed().toList() } }
|
||||
val maxHeightRounded = with(LocalDensity.current) { maxHeight.roundToPx() }
|
||||
val scrollToItem: (Long) -> Unit = { itemId: Long ->
|
||||
val index = reversedChatItems.indexOfFirst { it.id == itemId }
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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? {
|
||||
@@ -430,7 +435,7 @@ fun ComposeView(
|
||||
if (cs.contextItem is ComposeContextItem.EditingItem) {
|
||||
val ei = cs.contextItem.chatItem
|
||||
sent = updateMessage(ei, cInfo, live)
|
||||
} else if (liveMessage != null) {
|
||||
} else if (liveMessage != null && liveMessage.sent) {
|
||||
sent = updateMessage(liveMessage.chatItem, cInfo, live)
|
||||
} else {
|
||||
val msgs: ArrayList<MsgContent> = ArrayList()
|
||||
@@ -569,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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -592,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))
|
||||
@@ -701,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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -723,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,
|
||||
@@ -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/*"))
|
||||
@@ -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)) {
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
@@ -519,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,
|
||||
@@ -547,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,
|
||||
@@ -575,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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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 -> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+18
-9
@@ -33,6 +33,7 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) {
|
||||
val networkTCPConnectTimeout = remember { mutableStateOf(currentCfgVal.tcpConnectTimeout) }
|
||||
val networkTCPTimeout = remember { mutableStateOf(currentCfgVal.tcpTimeout) }
|
||||
val networkSMPPingInterval = remember { mutableStateOf(currentCfgVal.smpPingInterval) }
|
||||
val networkSMPPingCount = remember { mutableStateOf(currentCfgVal.smpPingCount) }
|
||||
val networkEnableKeepAlive = remember { mutableStateOf(currentCfgVal.enableKeepAlive) }
|
||||
val networkTCPKeepIdle: MutableState<Int>
|
||||
val networkTCPKeepIntvl: MutableState<Int>
|
||||
@@ -48,10 +49,6 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) {
|
||||
}
|
||||
|
||||
fun buildCfg(): NetCfg {
|
||||
val socksProxy = currentCfg.value.socksProxy
|
||||
val tcpConnectTimeout = networkTCPConnectTimeout.value
|
||||
val tcpTimeout = networkTCPTimeout.value
|
||||
val smpPingInterval = networkSMPPingInterval.value
|
||||
val enableKeepAlive = networkEnableKeepAlive.value
|
||||
val tcpKeepAlive = if (enableKeepAlive) {
|
||||
val keepIdle = networkTCPKeepIdle.value
|
||||
@@ -62,11 +59,12 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) {
|
||||
null
|
||||
}
|
||||
return NetCfg(
|
||||
socksProxy = socksProxy,
|
||||
tcpConnectTimeout = tcpConnectTimeout,
|
||||
tcpTimeout = tcpTimeout,
|
||||
socksProxy = currentCfg.value.socksProxy,
|
||||
tcpConnectTimeout = networkTCPConnectTimeout.value,
|
||||
tcpTimeout = networkTCPTimeout.value,
|
||||
tcpKeepAlive = tcpKeepAlive,
|
||||
smpPingInterval = smpPingInterval
|
||||
smpPingInterval = networkSMPPingInterval.value,
|
||||
smpPingCount = networkSMPPingCount.value
|
||||
)
|
||||
}
|
||||
|
||||
@@ -74,6 +72,7 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) {
|
||||
networkTCPConnectTimeout.value = cfg.tcpConnectTimeout
|
||||
networkTCPTimeout.value = cfg.tcpTimeout
|
||||
networkSMPPingInterval.value = cfg.smpPingInterval
|
||||
networkSMPPingCount.value = cfg.smpPingCount
|
||||
networkEnableKeepAlive.value = cfg.enableKeepAlive
|
||||
if (cfg.tcpKeepAlive != null) {
|
||||
networkTCPKeepIdle.value = cfg.tcpKeepAlive.keepIdle
|
||||
@@ -113,6 +112,7 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) {
|
||||
networkTCPConnectTimeout,
|
||||
networkTCPTimeout,
|
||||
networkSMPPingInterval,
|
||||
networkSMPPingCount,
|
||||
networkEnableKeepAlive,
|
||||
networkTCPKeepIdle,
|
||||
networkTCPKeepIntvl,
|
||||
@@ -129,6 +129,7 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) {
|
||||
networkTCPConnectTimeout: MutableState<Long>,
|
||||
networkTCPTimeout: MutableState<Long>,
|
||||
networkSMPPingInterval: MutableState<Long>,
|
||||
networkSMPPingCount: MutableState<Int>,
|
||||
networkEnableKeepAlive: MutableState<Boolean>,
|
||||
networkTCPKeepIdle: MutableState<Int>,
|
||||
networkTCPKeepIntvl: MutableState<Int>,
|
||||
@@ -170,7 +171,14 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) {
|
||||
SectionItemView {
|
||||
TimeoutSettingRow(
|
||||
stringResource(R.string.network_option_ping_interval), networkSMPPingInterval,
|
||||
listOf(120_000000, 300_000000, 600_000000, 1200_000000, 2400_000000), secondsLabel
|
||||
listOf(120_000000, 300_000000, 600_000000, 1200_000000, 2400_000000, 3600_000000), secondsLabel
|
||||
)
|
||||
}
|
||||
SectionDivider()
|
||||
SectionItemView {
|
||||
IntSettingRow(
|
||||
stringResource(R.string.network_option_ping_count), networkSMPPingCount,
|
||||
listOf(1, 2, 3, 5, 8), ""
|
||||
)
|
||||
}
|
||||
SectionDivider()
|
||||
@@ -412,6 +420,7 @@ fun PreviewAdvancedNetworkSettingsLayout() {
|
||||
networkTCPConnectTimeout = remember { mutableStateOf(10_000000) },
|
||||
networkTCPTimeout = remember { mutableStateOf(10_000000) },
|
||||
networkSMPPingInterval = remember { mutableStateOf(10_000000) },
|
||||
networkSMPPingCount = remember { mutableStateOf(3) },
|
||||
networkEnableKeepAlive = remember { mutableStateOf(true) },
|
||||
networkTCPKeepIdle = remember { mutableStateOf(10) },
|
||||
networkTCPKeepIntvl = remember { mutableStateOf(10) },
|
||||
|
||||
+2
-4
@@ -123,10 +123,8 @@ fun NetworkAndServersView(
|
||||
}
|
||||
SectionDivider()
|
||||
UseOnionHosts(onionHosts, networkUseSocksProxy, showSettingsModal, useOnion)
|
||||
if (developerTools) {
|
||||
SectionDivider()
|
||||
SettingsActionItem(Icons.Outlined.Cable, stringResource(R.string.network_settings), showSettingsModal { AdvancedNetworkSettingsView(it) })
|
||||
}
|
||||
SectionDivider()
|
||||
SettingsActionItem(Icons.Outlined.Cable, stringResource(R.string.network_settings), showSettingsModal { AdvancedNetworkSettingsView(it) })
|
||||
}
|
||||
Spacer(Modifier.height(8.dp))
|
||||
SectionView(generalGetString(R.string.settings_section_title_calls)) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import SectionItemView
|
||||
import SectionSpacer
|
||||
import SectionView
|
||||
import android.content.res.Configuration
|
||||
import android.icu.util.VersionInfo
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
@@ -57,7 +58,14 @@ fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit) {
|
||||
showSettingsModal = { modalView -> { ModalManager.shared.showModal(true) { modalView(chatModel) } } },
|
||||
showCustomModal = { modalView -> { ModalManager.shared.showCustomModal { close -> modalView(chatModel, close) } } },
|
||||
showTerminal = { ModalManager.shared.showCustomModal { close -> TerminalView(chatModel, close) } },
|
||||
// showVideoChatPrototype = { ModalManager.shared.showCustomModal { close -> CallViewDebug(close) } },
|
||||
showVersion = {
|
||||
withApi {
|
||||
val info = chatModel.controller.apiGetVersion()
|
||||
if (info != null) {
|
||||
ModalManager.shared.showModal { VersionInfoView(info) }
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -89,7 +97,7 @@ fun SettingsLayout(
|
||||
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
||||
showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit),
|
||||
showTerminal: () -> Unit,
|
||||
// showVideoChatPrototype: () -> Unit
|
||||
showVersion: () -> Unit
|
||||
) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
Surface(Modifier.fillMaxSize().verticalScroll(rememberScrollState())) {
|
||||
@@ -170,7 +178,7 @@ fun SettingsLayout(
|
||||
}
|
||||
// SettingsActionItem(Icons.Outlined.Science, stringResource(R.string.settings_experimental_features), showSettingsModal { ExperimentalFeaturesView(it, enableCalls) })
|
||||
// SectionDivider()
|
||||
AppVersionItem()
|
||||
AppVersionItem(showVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -345,8 +353,8 @@ fun MaintainIncognitoState(chatModel: ChatModel) {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable private fun AppVersionItem() {
|
||||
SectionItemView() {
|
||||
@Composable private fun AppVersionItem(showVersion: () -> Unit) {
|
||||
SectionItemView(showVersion) {
|
||||
Text("v${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})")
|
||||
}
|
||||
}
|
||||
@@ -491,7 +499,7 @@ fun PreviewSettingsLayout() {
|
||||
showSettingsModal = { {} },
|
||||
showCustomModal = { {} },
|
||||
showTerminal = {},
|
||||
// showVideoChatPrototype = {}
|
||||
showVersion = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -44,8 +44,7 @@ fun UserProfileView(chatModel: ChatModel, close: () -> Unit) {
|
||||
close,
|
||||
saveProfile = { displayName, fullName, image ->
|
||||
withApi {
|
||||
val p = Profile(displayName, fullName, image)
|
||||
val newProfile = chatModel.controller.apiUpdateProfile(p)
|
||||
val newProfile = chatModel.controller.apiUpdateProfile(profile.copy(displayName = displayName, fullName = fullName, image = image))
|
||||
if (newProfile != null) {
|
||||
chatModel.currentUser.value?.profile?.profileId?.let {
|
||||
chatModel.updateUserProfile(newProfile.toLocalProfile(it))
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package chat.simplex.app.views.usersettings
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import chat.simplex.app.BuildConfig
|
||||
import chat.simplex.app.R
|
||||
import chat.simplex.app.model.CoreVersionInfo
|
||||
import chat.simplex.app.ui.theme.DEFAULT_PADDING
|
||||
import chat.simplex.app.views.helpers.AppBarTitle
|
||||
|
||||
@Composable
|
||||
fun VersionInfoView(info: CoreVersionInfo) {
|
||||
Column(
|
||||
Modifier.padding(horizontal = DEFAULT_PADDING),
|
||||
horizontalAlignment = Alignment.Start
|
||||
) {
|
||||
AppBarTitle(stringResource(R.string.app_version_title), false)
|
||||
Text(String.format(stringResource(R.string.app_version_name), BuildConfig.VERSION_NAME))
|
||||
Text(String.format(stringResource(R.string.app_version_code), BuildConfig.VERSION_CODE))
|
||||
Text(String.format(stringResource(R.string.core_version), info.version))
|
||||
Text(String.format(stringResource(R.string.core_build_timestamp), info.buildTimestamp))
|
||||
Text(String.format(stringResource(R.string.core_simplexmq_version), info.simplexmqVersion, info.simplexmqCommit.substring(startIndex = 0, endIndex = 7)))
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -90,10 +90,10 @@
|
||||
<string name="to_preserve_privacy_simplex_has_background_service_instead_of_push_notifications_it_uses_a_few_pc_battery">Um Ihre Privatsphäre zu schützen kann statt der Push-Benachrichtigung der <b><xliff:g id="appName">SimpleX</xliff:g> Hintergrunddienst genutzt werden</b> – dieser benötigt ein paar Prozent Akkuleistung am Tag.</string>
|
||||
<string name="it_can_disabled_via_settings_notifications_still_shown"><b>Diese können über die Einstellungen deaktiviert werden</b> – Solange die App abläuft werden Benachrichtigungen weiterhin angezeigt.</string>
|
||||
<string name="turn_off_battery_optimization">Um diese Funktion zu nutzen, ist es nötig, die Einstellung <b>Akkuoptimierung</b> für <xliff:g id="appName">SimpleX</xliff:g> im nächsten Dialog zu <b>deaktivieren</b>. Ansonsten werden die Benachrichtigungen deaktiviert.</string>
|
||||
<string name="turning_off_service_and_periodic">Die Akkuoptimierung ist aktiv, der Hintergrunddienst und die regelmäßige Nachfrage nach neuen Nachrichten ist abgeschaltet. Sie können diese Funktion in den Einstellungen wieder aktivieren.</string>
|
||||
<string name="periodic_notifications">Regelmäßige Benachrichtigungen</string>
|
||||
<string name="periodic_notifications_disabled">Regelmäßige Benachrichtigungen sind deaktiviert!</string>
|
||||
<string name="periodic_notifications_desc">Die App holt regelmäßig neue Nachrichten ab — dies benötigt ein paar Prozent Akkuleistung am Tag. Die App nutzt keine Push-Benachrichtigungen — es werden keine Daten von Ihrem Gerät an Server gesendet.</string>
|
||||
<string name="turning_off_service_and_periodic">Die Akkuoptimierung ist aktiv, der Hintergrunddienst und die periodische Nachfrage nach neuen Nachrichten ist abgeschaltet. Sie können diese Funktion in den Einstellungen wieder aktivieren.</string>
|
||||
<string name="periodic_notifications">Periodische Benachrichtigungen</string>
|
||||
<string name="periodic_notifications_disabled">Periodische Benachrichtigungen sind deaktiviert!</string>
|
||||
<string name="periodic_notifications_desc">Die App holt periodisch neue Nachrichten ab — dies benötigt ein paar Prozent Akkuleistung am Tag. Die App nutzt keine Push-Benachrichtigungen — es werden keine Daten von Ihrem Gerät an Server gesendet.</string>
|
||||
<string name="enter_passphrase_notification_title">Passwort wird benötigt</string>
|
||||
<string name="enter_passphrase_notification_desc">Geben Sie bitte das Datenbank-Passwort ein, um Benachrichtigungen zu erhalten.</string>
|
||||
<string name="database_initialization_error_title">Die Datenbank kann nicht initialisiert werden</string>
|
||||
@@ -110,7 +110,7 @@
|
||||
<string name="settings_notification_preview_mode_title">Vorschau anzeigen</string>
|
||||
<string name="settings_notification_preview_title">Benachrichtigungsvorschau</string>
|
||||
<string name="notifications_mode_off">Wird ausgeführt, wenn die App geöffnet ist</string>
|
||||
<string name="notifications_mode_periodic">Startet regelmäßig</string>
|
||||
<string name="notifications_mode_periodic">Startet periodisch</string>
|
||||
<string name="notifications_mode_service">Immer aktiv</string>
|
||||
<string name="notifications_mode_off_desc">Die App kann Benachrichtigungen nur empfangen, wenn sie ausgeführt wird, es wird kein Hintergrunddienst gestartet.</string>
|
||||
<string name="notifications_mode_periodic_desc">Überprüft alle 10 Minuten auf neue Nachrichten für bis zu einer Minute.</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>
|
||||
@@ -578,7 +578,7 @@
|
||||
<string name="settings_section_title_calls">CALLS</string>
|
||||
<string name="settings_section_title_incognito">Inkognito Modus</string>
|
||||
<!-- DatabaseView.kt -->
|
||||
<string name="your_chat_database">Meine Chat-Datenbank</string>
|
||||
<string name="your_chat_database">Chat-Datenbank</string>
|
||||
<string name="run_chat_section">CHAT STARTEN</string>
|
||||
<string name="chat_is_running">Der Chat läuft</string>
|
||||
<string name="chat_is_stopped">Der Chat ist beendet</string>
|
||||
@@ -882,37 +882,37 @@
|
||||
<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 erfolgt</string>
|
||||
<string name="is_verified">%s wurde erfolgreich ü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>
|
||||
@@ -960,7 +960,7 @@
|
||||
<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="is_not_verified">%s wurde noch 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>
|
||||
@@ -978,7 +978,7 @@
|
||||
<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_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>
|
||||
@@ -989,4 +989,8 @@
|
||||
<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>
|
||||
<string name="icon_descr_cancel_live_message">Livenachricht abbrechen</string>
|
||||
<string name="feature_offered_item">beginne %s</string>
|
||||
<string name="feature_offered_item_with_param">beginne %s: %2s</string>
|
||||
<string name="feature_cancelled_item">beende %s</string>
|
||||
</resources>
|
||||
@@ -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,7 +328,7 @@
|
||||
<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>
|
||||
@@ -381,7 +381,7 @@
|
||||
<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 – n\'importe qui peut heberger un serveur.</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="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>
|
||||
@@ -461,13 +461,13 @@
|
||||
<string name="privacy_redefined">La vie privée redéfinie</string>
|
||||
<string name="first_platform_without_user_ids">La 1ère plateforme sans aucun identifiant d\'utilisateur – privée par design.</string>
|
||||
<string name="immune_to_spam_and_abuse">Protégé du spam et des abus</string>
|
||||
<string name="people_can_connect_only_via_links_you_share">Les gens peuvent se connecter à vous uniquement via les liens que vous partagez.</string>
|
||||
<string name="people_can_connect_only_via_links_you_share">On ne peut se connecter à vous qu’avec les liens que vous partagez.</string>
|
||||
<string name="decentralized">Décentralisé</string>
|
||||
<string name="create_your_profile">Créez votre profil</string>
|
||||
<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>
|
||||
@@ -786,7 +786,7 @@
|
||||
<string name="prohibit_direct_messages">Interdire l\'envoi de messages directs aux membres.</string>
|
||||
<string name="group_members_can_delete">Les membres du groupe peuvent supprimer de manière irréversible les messages envoyés.</string>
|
||||
<string name="message_deletion_prohibited_in_chat">La suppression irréversible de messages est interdite dans ce groupe.</string>
|
||||
<string name="sending_via">Envoyé via</string>
|
||||
<string name="sending_via">Envoi via</string>
|
||||
<string name="network_status">État du réseau</string>
|
||||
<string name="switch_receiving_address">Changer d\'adresse de réception</string>
|
||||
<string name="create_secret_group_title">Créer un groupe secret</string>
|
||||
@@ -895,7 +895,7 @@
|
||||
<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">Nouveau en %s</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>
|
||||
@@ -918,4 +918,8 @@
|
||||
<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>
|
||||
<string name="icon_descr_cancel_live_message">Annuler le message dynamique</string>
|
||||
<string name="feature_offered_item">offert %s</string>
|
||||
<string name="feature_offered_item_with_param">offert %s: %2s</string>
|
||||
<string name="feature_cancelled_item">annulé %s</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,925 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="simplex_link_mode">Link di SimpleX</string>
|
||||
<string name="network_error_desc">Controlla la tua connessione di rete con <xliff:g id="serverHost" example="smp.simplex.im">%1$s</xliff:g> e riprova.</string>
|
||||
<string name="service_notifications_disabled">Le notifiche istantanee sono disattivate!</string>
|
||||
<string name="contact_connection_pending">in connessione…</string>
|
||||
<string name="attach">Allega</string>
|
||||
<string name="icon_descr_cancel_image_preview">Annulla anteprima immagine</string>
|
||||
<string name="images_limit_desc">Possono essere inviate solo 10 immagini alla volta</string>
|
||||
<string name="image_will_be_received_when_contact_is_online">L\'immagine verrà ricevuta quando il tuo contatto sarà in linea, aspetta o controlla più tardi!</string>
|
||||
<string name="waiting_for_image">In attesa dell\'immagine</string>
|
||||
<string name="app_name"><xliff:g id="appName">SimpleX</xliff:g></string>
|
||||
<string name="thousand_abbreviation">k</string>
|
||||
<string name="connect_via_invitation_link">Connettere via link di invito\?</string>
|
||||
<string name="connect_via_group_link">Connettere via link del gruppo\?</string>
|
||||
<string name="profile_will_be_sent_to_contact_sending_link">Il tuo profilo verrà inviato al contatto da cui hai ricevuto questo link.</string>
|
||||
<string name="connect_via_link_verb">Connetti</string>
|
||||
<string name="server_connected">connesso</string>
|
||||
<string name="server_error">errore</string>
|
||||
<string name="server_connecting">in connessione</string>
|
||||
<string name="connected_to_server_to_receive_messages_from_contact">Sei connesso al server usato per ricevere messaggi da questo contatto.</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages">Tentativo di connessione al server usato per ricevere messaggi da questo contatto.</string>
|
||||
<string name="deleted_description">eliminato</string>
|
||||
<string name="marked_deleted_description">contrassegnato eliminato</string>
|
||||
<string name="sending_files_not_yet_supported">l\'invio di file non è ancora supportato</string>
|
||||
<string name="receiving_files_not_yet_supported">la ricezione di file non è ancora supportata</string>
|
||||
<string name="sender_you_pronoun">tu</string>
|
||||
<string name="unknown_message_format">formato messaggio sconosciuto</string>
|
||||
<string name="invalid_message_format">formato messaggio non valido</string>
|
||||
<string name="live">IN DIRETTA</string>
|
||||
<string name="invalid_chat">conversazione non valida</string>
|
||||
<string name="invalid_data">dati non validi</string>
|
||||
<string name="display_name_connection_established">connessione stabilita</string>
|
||||
<string name="display_name_invited_to_connect">invitato a connettersi</string>
|
||||
<string name="display_name_connecting">in connessione…</string>
|
||||
<string name="description_you_shared_one_time_link">hai condiviso un link una tantum</string>
|
||||
<string name="description_you_shared_one_time_link_incognito">hai condiviso un link incognito una tantum</string>
|
||||
<string name="description_via_group_link">via link di gruppo</string>
|
||||
<string name="description_via_group_link_incognito">incognito via link di gruppo</string>
|
||||
<string name="description_via_contact_address_link">via link indirizzo del contatto</string>
|
||||
<string name="description_via_contact_address_link_incognito">incognito via link indirizzo del contatto</string>
|
||||
<string name="description_via_one_time_link">via link una tantum</string>
|
||||
<string name="description_via_one_time_link_incognito">incognito via link una tantum</string>
|
||||
<string name="simplex_link_contact">Indirizzo del contatto SimpleX</string>
|
||||
<string name="simplex_link_invitation">Invito SimpleX una tantum</string>
|
||||
<string name="simplex_link_group">Link gruppo SimpleX</string>
|
||||
<string name="simplex_link_mode_full">Link completo</string>
|
||||
<string name="simplex_link_mode_browser">Via browser</string>
|
||||
<string name="error_saving_smp_servers">Errore di salvataggio server SMP</string>
|
||||
<string name="ensure_smp_server_address_are_correct_format_and_unique">Assicurati che gli indirizzi dei server SMP siano nel formato giusto, uno per riga e non doppi.</string>
|
||||
<string name="error_setting_network_config">Errore di aggiornamento della configurazione di rete</string>
|
||||
<string name="failed_to_parse_chat_title">Caricamento conversazione fallito</string>
|
||||
<string name="failed_to_parse_chats_title">Caricamento delle chat fallito</string>
|
||||
<string name="contact_developers">Aggiorna l\'app e contatta gli sviluppatori.</string>
|
||||
<string name="connection_timeout">Connessione scaduta</string>
|
||||
<string name="connection_error">Errore di connessione</string>
|
||||
<string name="error_sending_message">Errore di invio del messaggio</string>
|
||||
<string name="error_adding_members">Errore di aggiunta del/i membro/i</string>
|
||||
<string name="error_joining_group">Errore di entrata nel gruppo</string>
|
||||
<string name="cannot_receive_file">Impossibile ricevere il file</string>
|
||||
<string name="sender_cancelled_file_transfer">Il mittente ha annullato il trasferimento del file.</string>
|
||||
<string name="error_receiving_file">Errore di ricezione del file</string>
|
||||
<string name="error_creating_address">Errore di creazione dell\'indirizzo</string>
|
||||
<string name="contact_already_exists">Il contatto esiste già</string>
|
||||
<string name="invalid_connection_link">Link di connessione non valido</string>
|
||||
<string name="please_check_correct_link_and_maybe_ask_for_a_new_one">Controlla di aver usato il link giusto o chiedi al tuo contatto di inviartene un altro.</string>
|
||||
<string name="connection_error_auth">Errore di connessione (AUTH)</string>
|
||||
<string name="error_accepting_contact_request">Errore di accettazione della richiesta del contatto</string>
|
||||
<string name="sender_may_have_deleted_the_connection_request">Il mittente potrebbe aver eliminato la richiesta di connessione.</string>
|
||||
<string name="error_deleting_contact">Errore di eliminazione del contatto</string>
|
||||
<string name="error_deleting_group">Errore di eliminazione del gruppo</string>
|
||||
<string name="error_deleting_contact_request">Errore di eliminazione della richiesta di contatto</string>
|
||||
<string name="error_deleting_pending_contact_connection">Errore di eliminazione della connessione del contatto in attesa</string>
|
||||
<string name="error_changing_address">Errore di modifica dell\'indirizzo</string>
|
||||
<string name="error_smp_test_failed_at_step">Test fallito al passo %s.</string>
|
||||
<string name="error_smp_test_server_auth">Il server richiede l\'autorizzazione di creare code, controlla la password</string>
|
||||
<string name="smp_server_test_connect">Connetti</string>
|
||||
<string name="smp_server_test_create_queue">Crea coda</string>
|
||||
<string name="smp_server_test_secure_queue">Coda sicura</string>
|
||||
<string name="smp_server_test_delete_queue">Elimina coda</string>
|
||||
<string name="smp_server_test_disconnect">Disconnetti</string>
|
||||
<string name="icon_descr_instant_notifications">Notifiche istantanee</string>
|
||||
<string name="service_notifications">Notifiche istantanee!</string>
|
||||
<string name="it_can_disabled_via_settings_notifications_still_shown"><b>Può essere disattivato nelle impostazioni</b>; le notifiche verranno comunque mostrate mentre l\'app è in uso.</string>
|
||||
<string name="turning_off_service_and_periodic">L\'ottimizzazione della batteria è attiva, spegnimento del servizio in secondo piano e delle richieste periodiche di messaggi nuovi. Puoi riattivarli nelle impostazioni.</string>
|
||||
<string name="periodic_notifications">Notifiche periodiche</string>
|
||||
<string name="periodic_notifications_disabled">Le notifiche periodiche sono disattivate!</string>
|
||||
<string name="periodic_notifications_desc">L\'app cerca nuovi messaggi periodicamente, utilizza una piccola percentuale di batteria al giorno. L\'app non usa notifiche push, non vengono inviati dati dal tuo dispositivo ai server.</string>
|
||||
<string name="enter_passphrase_notification_title">Password necessaria</string>
|
||||
<string name="enter_passphrase_notification_desc">Per ricevere notifiche, inserisci la password del database</string>
|
||||
<string name="database_initialization_error_title">Impossibile inizializzare il database</string>
|
||||
<string name="database_initialization_error_desc">Il database non funziona bene. Tocca per maggiori informazioni</string>
|
||||
<string name="simplex_service_notification_text">Ricezione messaggi…</string>
|
||||
<string name="hide_notification">Nascondi</string>
|
||||
<string name="ntf_channel_messages">Messaggi di SimpleX Chat</string>
|
||||
<string name="ntf_channel_calls">Chiamate di SimpleX Chat</string>
|
||||
<string name="settings_notifications_mode_title">Servizio di notifica</string>
|
||||
<string name="settings_notification_preview_mode_title">Mostra anteprima</string>
|
||||
<string name="settings_notification_preview_title">Anteprima notifica</string>
|
||||
<string name="notifications_mode_off">Quando l\'app è aperta</string>
|
||||
<string name="notifications_mode_periodic">Periodicamente</string>
|
||||
<string name="notifications_mode_service">Sempre attivo</string>
|
||||
<string name="notifications_mode_off_desc">L\'app può ricevere notifiche solo quando è attiva, non verrà avviato alcun servizio in secondo piano</string>
|
||||
<string name="notifications_mode_periodic_desc">Controlla messaggi nuovi ogni 10 minuti per massimo 1 minuto</string>
|
||||
<string name="notification_preview_mode_message">Testo del messaggio</string>
|
||||
<string name="notification_preview_mode_contact">Nome del contatto</string>
|
||||
<string name="notification_preview_mode_hidden">Nascosta</string>
|
||||
<string name="notification_preview_mode_message_desc">Mostra contatto e messaggio</string>
|
||||
<string name="notification_preview_mode_contact_desc">Mostra solo il contatto</string>
|
||||
<string name="notification_display_mode_hidden_desc">Nascondi contatto e messaggio</string>
|
||||
<string name="notification_preview_somebody">Contatto nascosto:</string>
|
||||
<string name="notification_preview_new_message">messaggio nuovo</string>
|
||||
<string name="notification_new_contact_request">Nuova richiesta di contatto</string>
|
||||
<string name="notification_contact_connected">Connesso</string>
|
||||
<string name="la_notice_turn_on">Attiva</string>
|
||||
<string name="auth_unlock">Sblocca</string>
|
||||
<string name="auth_log_in_using_credential">Accedi usando le tue credenziali</string>
|
||||
<string name="auth_enable_simplex_lock">Attiva SimpleX Lock</string>
|
||||
<string name="auth_disable_simplex_lock">Disattiva SimpleX Lock</string>
|
||||
<string name="auth_confirm_credential">Conferma le tue credenziali</string>
|
||||
<string name="auth_unavailable">Autenticazione non disponibile</string>
|
||||
<string name="auth_device_authentication_is_disabled_turning_off">L\'autenticazione del dispositivo è disattivata. Disattivazione di SimpleX Lock.</string>
|
||||
<string name="auth_stop_chat">Ferma la chat</string>
|
||||
<string name="auth_open_chat_console">Apri la console della chat</string>
|
||||
<string name="message_delivery_error_title">Errore di recapito del messaggio</string>
|
||||
<string name="message_delivery_error_desc">Probabilmente questo contatto ha eliminato la connessione con te.</string>
|
||||
<string name="reply_verb">Rispondi</string>
|
||||
<string name="share_verb">Condividi</string>
|
||||
<string name="copy_verb">Copia</string>
|
||||
<string name="save_verb">Salva</string>
|
||||
<string name="edit_verb">Modifica</string>
|
||||
<string name="delete_verb">Elimina</string>
|
||||
<string name="reveal_verb">Rivela</string>
|
||||
<string name="hide_verb">Nascondi</string>
|
||||
<string name="allow_verb">Consenti</string>
|
||||
<string name="delete_message__question">Eliminare il messaggio\?</string>
|
||||
<string name="delete_message_cannot_be_undone_warning">Il messaggio verrà eliminato, non è reversibile!</string>
|
||||
<string name="for_me_only">Elimina per me</string>
|
||||
<string name="for_everybody">Per tutti</string>
|
||||
<string name="icon_descr_edited">modificato</string>
|
||||
<string name="icon_descr_sent_msg_status_sent">inviato</string>
|
||||
<string name="icon_descr_sent_msg_status_unauthorized_send">invio non autorizzato</string>
|
||||
<string name="icon_descr_sent_msg_status_send_failed">invio fallito</string>
|
||||
<string name="icon_descr_received_msg_status_unread">non letto</string>
|
||||
<string name="personal_welcome">Benvenuto/a <xliff:g>%1$s</xliff:g>!</string>
|
||||
<string name="welcome">Benvenuto/a!</string>
|
||||
<string name="this_text_is_available_in_settings">Questo testo è disponibile nelle impostazioni</string>
|
||||
<string name="your_chats">Le tue chat</string>
|
||||
<string name="group_preview_you_are_invited">sei stato invitato in un gruppo</string>
|
||||
<string name="group_preview_join_as">entra come %s</string>
|
||||
<string name="group_connection_pending">in connessione…</string>
|
||||
<string name="tap_to_start_new_chat">Tocca per iniziare una conversazione</string>
|
||||
<string name="chat_with_developers">Scrivi agli sviluppatori</string>
|
||||
<string name="you_have_no_chats">Non hai chat</string>
|
||||
<string name="share_image">Condividi immagine…</string>
|
||||
<string name="share_file">Condividi file…</string>
|
||||
<string name="icon_descr_context">Icona contestuale</string>
|
||||
<string name="icon_descr_cancel_file_preview">Annulla anteprima file</string>
|
||||
<string name="images_limit_title">Troppe immagini!</string>
|
||||
<string name="image_decoding_exception_title">Errore di decodifica</string>
|
||||
<string name="image_decoding_exception_desc">L\'immagine non può essere decodificata. Prova con un\'altra o contatta gli sviluppatori.</string>
|
||||
<string name="image_descr">Immagine</string>
|
||||
<string name="icon_descr_waiting_for_image">In attesa dell\'immagine</string>
|
||||
<string name="icon_descr_asked_to_receive">Richiesta di ricezione immagine</string>
|
||||
<string name="icon_descr_image_snd_complete">Immagine inviata</string>
|
||||
<string name="image_saved">Immagine salvata nella Galleria</string>
|
||||
<string name="icon_descr_file">File</string>
|
||||
<string name="large_file">File grande!</string>
|
||||
<string name="maximum_supported_file_size">Attualmente la dimensione massima supportata è di <xliff:g id="maxFileSize">%1$s</xliff:g>.</string>
|
||||
<string name="waiting_for_file">In attesa del file</string>
|
||||
<string name="file_will_be_received_when_contact_is_online">Il file verrà ricevuto quando il tuo contatto sarà in linea, aspetta o controlla più tardi!</string>
|
||||
<string name="file_saved">File salvato</string>
|
||||
<string name="file_not_found">File non trovato</string>
|
||||
<string name="error_saving_file">Errore di salvataggio del file</string>
|
||||
<string name="voice_message">Messaggio vocale</string>
|
||||
<string name="voice_message_with_duration">Messaggio vocale (<xliff:g id="duration">%1$s</xliff:g>)</string>
|
||||
<string name="voice_message_send_text">Messaggio vocale…</string>
|
||||
<string name="notifications">Notifiche</string>
|
||||
<string name="delete_contact_question">Eliminare il contatto\?</string>
|
||||
<string name="button_delete_contact">Elimina contatto</string>
|
||||
<string name="text_field_set_contact_placeholder">Imposta nome del contatto…</string>
|
||||
<string name="icon_descr_server_status_connected">Connesso</string>
|
||||
<string name="icon_descr_server_status_disconnected">Disconnesso</string>
|
||||
<string name="icon_descr_server_status_error">Errore</string>
|
||||
<string name="icon_descr_server_status_pending">In attesa</string>
|
||||
<string name="switch_receiving_address_question">Cambiare l\'indirizzo di ricezione\?</string>
|
||||
<string name="view_security_code">Vedi codice di sicurezza</string>
|
||||
<string name="verify_security_code">Verifica codice di sicurezza</string>
|
||||
<string name="icon_descr_send_message">Invia messaggio</string>
|
||||
<string name="icon_descr_record_voice_message">Registra messaggio vocale</string>
|
||||
<string name="allow_voice_messages_question">Permettere i messaggi vocali\?</string>
|
||||
<string name="you_need_to_allow_to_send_voice">Devi consentire al tuo contatto di inviare messaggi vocali per poterli inviare anche tu.</string>
|
||||
<string name="voice_messages_prohibited">Messaggi vocali vietati!</string>
|
||||
<string name="ask_your_contact_to_enable_voice">Chiedi al tuo contatto di attivare l\'invio dei messaggi vocali.</string>
|
||||
<string name="send_live_message">Invia messaggio in diretta</string>
|
||||
<string name="live_message">Messaggio in diretta!</string>
|
||||
<string name="send_verb">Invia</string>
|
||||
<string name="back">Indietro</string>
|
||||
<string name="cancel_verb">Annulla</string>
|
||||
<string name="confirm_verb">Conferma</string>
|
||||
<string name="reset_verb">Ripristina</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="connect_via_contact_link">Connettere via link del contatto\?</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Tentativo di connessione al server usato per ricevere messaggi da questo contatto (errore: <xliff:g id="errorMsg">%1$s</xliff:g>).</string>
|
||||
<string name="you_will_join_group">Entrerai in un gruppo a cui si riferisce questo link e ti connetterai ai suoi membri.</string>
|
||||
<string name="connection_local_display_name">connessione <xliff:g id="connection ID" example="1">%1$d</xliff:g></string>
|
||||
<string name="simplex_link_mode_description">Descrizione</string>
|
||||
<string name="simplex_link_connection">via <xliff:g id="serverHost" example="smp.simplex.im">%1$s</xliff:g></string>
|
||||
<string name="simplex_link_mode_browser_warning">Aprire il link nel browser può ridurre la privacy e la sicurezza della connessione. I link SimpleX non fidati saranno in rosso.</string>
|
||||
<string name="you_are_already_connected_to_vName_via_this_link">Sei già connesso a <xliff:g id="contactName" example="Alice">%1$s!</xliff:g>.</string>
|
||||
<string name="connection_error_auth_desc">A meno che il tuo contatto non abbia eliminato la connessione o che questo link non sia già stato usato, potrebbe essere un errore; per favore segnalalo.
|
||||
\nPer connetterti, chiedi al tuo contatto di creare un altro link di connessione e controlla di avere una connessione di rete stabile.</string>
|
||||
<string name="error_smp_test_certificate">Probabilmente l\'impronta del certificato nell\'indirizzo del server è sbagliata</string>
|
||||
<string name="to_preserve_privacy_simplex_has_background_service_instead_of_push_notifications_it_uses_a_few_pc_battery">Per rispettare la tua privacy, invece delle notifiche push l\'app ha un <b>servizio <xliff:g id="appName">SimpleX</xliff:g> in secondo piano</b>; usa una piccola percentuale di batteria al giorno.</string>
|
||||
<string name="turn_off_battery_optimization">Per poterlo usare, <b>disattiva l\'ottimizzazione della batteria</b> per <xliff:g id="appName">SimpleX</xliff:g> nella prossima schermata. Altrimenti le notifiche saranno disattivate.</string>
|
||||
<string name="simplex_service_notification_title">Servizio <xliff:g id="appNameFull">SimpleX Chat</xliff:g></string>
|
||||
<string name="notifications_mode_service_desc">Servizio in secondo piano sempre attivo. Le notifiche verranno mostrate appena i messaggi saranno disponibili.</string>
|
||||
<string name="la_notice_title_simplex_lock">SimpleX Lock</string>
|
||||
<string name="la_notice_to_protect_your_information_turn_on_simplex_lock_you_will_be_prompted_to_complete_authentication_before_this_feature_is_enabled">Per proteggere le tue informazioni, attiva SimpleX Lock.
|
||||
\nTi verrà chiesto di completare l\'autenticazione prima di attivare questa funzionalità.</string>
|
||||
<string name="auth_simplex_lock_turned_on">SimpleX Lock attivo</string>
|
||||
<string name="auth_you_will_be_required_to_authenticate_when_you_start_or_resume">Dovrai autenticarti quando avvii o riapri l\'app dopo 30 secondi in secondo piano.</string>
|
||||
<string name="auth_device_authentication_is_not_enabled_you_can_turn_on_in_settings_once_enabled">L\'autenticazione del dispositivo non è attiva. Potrai attivare SimpleX Lock nelle impostazioni, quando avrai attivato l\'autenticazione del dispositivo.</string>
|
||||
<string name="delete_message_mark_deleted_warning">Il messaggio verrà contrassegnato per l\'eliminazione. I destinatari potranno rivelare questo messaggio.</string>
|
||||
<string name="share_message">Condividi messaggio…</string>
|
||||
<string name="contact_sent_large_file">Il tuo contatto ha inviato un file più grande della dimensione massima supportata (<xliff:g id="maxFileSize">%1$s</xliff:g>).</string>
|
||||
<string name="delete_contact_all_messages_deleted_cannot_undo_warning">Il contatto e tutti i messaggi verranno eliminati, non è reversibile!</string>
|
||||
<string name="switch_receiving_address_desc">Questa funzionalità è sperimentale! Funzionerà solo se l\'altro client ha la versione 4.2 installata. Dovresti vedere il messaggio nella conversazione una volta completato il cambio di indirizzo. Controlla di potere ancora ricevere messaggi da questo contatto (o membro del gruppo).</string>
|
||||
<string name="only_group_owners_can_enable_voice">Solo i proprietari del gruppo possono attivare i messaggi vocali.</string>
|
||||
<string name="send_live_message_desc">Invia un messaggio in diretta: si aggiornerà per i destinatari mentre lo digiti</string>
|
||||
<string name="chat_item_ttl_day">1 giorno</string>
|
||||
<string name="a_plus_b">a + b</string>
|
||||
<string name="about_simplex_chat">Riguardo <xliff:g id="appNameFull">SimpleX Chat</xliff:g></string>
|
||||
<string name="group_member_role_admin">amministratore</string>
|
||||
<string name="chat_item_ttl_week">1 settimana</string>
|
||||
<string name="smp_servers_add_to_another_device">Aggiungi ad un altro dispositivo</string>
|
||||
<string name="accept">Accetta</string>
|
||||
<string name="v4_2_group_links_desc">Gli amministratori possono creare i link per entrare nei gruppi.</string>
|
||||
<string name="allow_disappearing_messages_only_if">Consenti i messaggi a tempo solo se il tuo contatto li consente.</string>
|
||||
<string name="allow_to_delete_messages">Permetti di eliminare irreversibilmente i messaggi inviati.</string>
|
||||
<string name="allow_your_contacts_to_send_disappearing_messages">Permetti ai tuoi contatti di inviare messaggi a tempo.</string>
|
||||
<string name="accept_requests">Accetta le richieste</string>
|
||||
<string name="network_enable_socks_info">Accedere ai server via proxy SOCKS sulla porta 9050\? Il proxy deve essere avviato prima di attivare questa opzione.</string>
|
||||
<string name="v4_3_improved_server_configuration_desc">Aggiungi server scansionando codici QR.</string>
|
||||
<string name="all_group_members_will_remain_connected">Tutti i membri del gruppo resteranno connessi.</string>
|
||||
<string name="allow_irreversible_message_deletion_only_if">Consenti l\'eliminazione irreversibile dei messaggi solo se il contatto la consente a te.</string>
|
||||
<string name="above_then_preposition_continuation">sopra, quindi:</string>
|
||||
<string name="accept_contact_button">Accetta</string>
|
||||
<string name="accept_connection_request__question">Accettare la richiesta di connessione\?</string>
|
||||
<string name="accept_contact_incognito_button">Accetta in incognito</string>
|
||||
<string name="clear_chat_warning">Tutti i messaggi verranno eliminati, non è reversibile! I messaggi verranno eliminati SOLO per te.</string>
|
||||
<string name="smp_servers_preset_add">Aggiungi server preimpostati</string>
|
||||
<string name="smp_servers_add">Aggiungi server…</string>
|
||||
<string name="network_settings">Impostazioni di rete avanzate</string>
|
||||
<string name="about_simplex">Riguardo SimpleX</string>
|
||||
<string name="callstatus_accepted">chiamata accettata</string>
|
||||
<string name="accept_call_on_lock_screen">Accetta</string>
|
||||
<string name="color_primary">Principale</string>
|
||||
<string name="accept_feature">Accetta</string>
|
||||
<string name="allow_voice_messages_only_if">Consenti i messaggi vocali solo se il tuo contatto li consente.</string>
|
||||
<string name="allow_your_contacts_irreversibly_delete">Permetti ai tuoi contatti di eliminare irreversibilmente i messaggi inviati.</string>
|
||||
<string name="allow_direct_messages">Permetti l\'invio di messaggi diretti ai membri.</string>
|
||||
<string name="allow_to_send_disappearing">Permetti l\'invio di messaggi a tempo.</string>
|
||||
<string name="allow_to_send_voice">Permetti l\'invio di messaggi vocali.</string>
|
||||
<string name="chat_item_ttl_month">1 mese</string>
|
||||
<string name="error_importing_database">Errore nell\'importazione del database della chat</string>
|
||||
<string name="group_full_name_field">Nome completo del gruppo:</string>
|
||||
<string name="if_you_cannot_meet_in_person_scan_QR_in_video_call_or_ask_for_invitation_link">Se non potete incontrarvi di persona, puoi <b>scansionare il codice QR nella videochiamata</b>, oppure il tuo contatto può condividere un link di invito.</string>
|
||||
<string name="full_backup">Backup dei dati dell\'app</string>
|
||||
<string name="keychain_is_storing_securely">Android Keystore è usato per memorizzare in modo sicuro la password; permette il funzionamento del servizio di notifica.</string>
|
||||
<string name="allow_your_contacts_to_send_voice_messages">Permetti ai tuoi contatti di inviare messaggi vocali.</string>
|
||||
<string name="chat_database_deleted">Database della chat eliminato</string>
|
||||
<string name="settings_section_title_icon">ICONA APP</string>
|
||||
<string name="incognito_random_profile_from_contact_description">Verrà inviato un profilo casuale al contatto da cui hai ricevuto questo link</string>
|
||||
<string name="incognito_random_profile_description">Verrà inviato un profilo casuale al tuo contatto</string>
|
||||
<string name="onboarding_notifications_mode_off_desc"><b>Ideale per la batteria</b>. Riceverai notifiche solo quando l\'app è in esecuzione, il servizio in secondo piano NON verrà usato.</string>
|
||||
<string name="onboarding_notifications_mode_service_desc"><b>Consuma più batteria</b>! Il servizio in secondo piano è sempre attivo: le notifiche verranno mostrate non appena i messaggi saranno disponibili.</string>
|
||||
<string name="callstatus_calling">chiamata…</string>
|
||||
<string name="icon_descr_cancel_link_preview">annulla anteprima link</string>
|
||||
<string name="cannot_access_keychain">Impossibile accedere al Keystore per salvare la password del database</string>
|
||||
<string name="alert_title_cant_invite_contacts">Impossibile invitare i contatti!</string>
|
||||
<string name="change_role">Cambia ruolo</string>
|
||||
<string name="chat_archive_section">ARCHIVIO CHAT</string>
|
||||
<string name="snd_conn_event_switch_queue_phase_changing">cambio indirizzo…</string>
|
||||
<string name="chat_is_stopped">Chat fermata</string>
|
||||
<string name="group_member_status_introduced">connessione (presentato)</string>
|
||||
<string name="contact_requests">Richieste del contatto</string>
|
||||
<string name="connection_request_sent">Richiesta di connessione inviata!</string>
|
||||
<string name="delete_link_question">Eliminare il link\?</string>
|
||||
<string name="delete_link">Elimina link</string>
|
||||
<string name="create_address">Crea indirizzo</string>
|
||||
<string name="button_create_group_link">Crea link</string>
|
||||
<string name="data_section">DATI</string>
|
||||
<string name="database_encryption_will_be_updated">La password di crittografia del database verrà aggiornata e conservata nel Keystore.</string>
|
||||
<string name="encrypted_with_random_passphrase">Il database è crittografato con una password casuale, puoi cambiarla.</string>
|
||||
<string name="database_passphrase_is_required">La password del database è necessaria per aprire la chat.</string>
|
||||
<string name="delete_group_menu_action">Elimina</string>
|
||||
<string name="direct_messages_are_prohibited_in_chat">I messaggi diretti tra i membri sono vietati in questo gruppo.</string>
|
||||
<string name="display_name">Nome da mostrare</string>
|
||||
<string name="add_new_contact_to_create_one_time_QR_code"><b>Aggiungi un contatto</b>: per creare il tuo codice QR una tantum per il tuo contatto.</string>
|
||||
<string name="scan_QR_code_to_connect_to_contact_who_shows_QR_code"><b>Scansiona codice QR</b>: per connetterti al contatto che ti mostra il codice QR.</string>
|
||||
<string name="choose_file">Scegli file</string>
|
||||
<string name="clear_chat_button">Svuota chat</string>
|
||||
<string name="clear_chat_question">Svuotare la chat\?</string>
|
||||
<string name="clear_verb">Svuota</string>
|
||||
<string name="connect_via_link_or_qr">Connetti via link / codice QR</string>
|
||||
<string name="copied">Copiato negli appunti</string>
|
||||
<string name="share_one_time_link">Crea link di invito una tantum</string>
|
||||
<string name="create_group">Crea gruppo segreto</string>
|
||||
<string name="desktop_scan_QR_code_from_app_via_scan_QR_code">💻 desktop: scansiona dall\'app il codice QR mostrato, tramite <b>Scansiona codice QR</b>.</string>
|
||||
<string name="from_gallery_button">Dalla Galleria</string>
|
||||
<string name="if_you_choose_to_reject_the_sender_will_not_be_notified">Se scegli di rifiutare, il mittente NON verrà avvisato.</string>
|
||||
<string name="clear_chat_menu_action">Svuota</string>
|
||||
<string name="icon_descr_close_button">Pulsante di chiusura</string>
|
||||
<string name="alert_title_contact_connection_pending">Il contatto non è ancora connesso!</string>
|
||||
<string name="delete_contact_menu_action">Elimina</string>
|
||||
<string name="delete_pending_connection__question">Eliminare la connessione in attesa\?</string>
|
||||
<string name="icon_descr_email">Email</string>
|
||||
<string name="icon_descr_help">aiuto</string>
|
||||
<string name="if_you_cannot_meet_in_person_show_QR_in_video_call_or_via_another_channel">Se non potete incontrarvi di persona, <b>mostra il codice QR nella videochiamata</b>, oppure condividi il link.</string>
|
||||
<string name="chat_console">Console della chat</string>
|
||||
<string name="clear_verification">Annulla la verifica</string>
|
||||
<string name="connect_button">Connetti</string>
|
||||
<string name="connect_via_link">Connetti via link</string>
|
||||
<string name="create_one_time_link">Crea link di invito una tantum</string>
|
||||
<string name="database_passphrase_and_export">Password del database ed esportazione</string>
|
||||
<string name="smp_servers_enter_manually">Inserisci il server manualmente</string>
|
||||
<string name="how_to_use_simplex_chat">Come si usa</string>
|
||||
<string name="all_your_contacts_will_remain_connected">Tutti i tuoi contatti resteranno connessi.</string>
|
||||
<string name="appearance_settings">Aspetto</string>
|
||||
<string name="smp_servers_check_address">Controlla l\'indirizzo del server e riprova.</string>
|
||||
<string name="configure_ICE_servers">Configura server ICE</string>
|
||||
<string name="contribute">Contribuisci</string>
|
||||
<string name="delete_address">Elimina indirizzo</string>
|
||||
<string name="delete_address__question">Eliminare l\'indirizzo\?</string>
|
||||
<string name="smp_servers_delete_server">Elimina server</string>
|
||||
<string name="error_saving_ICE_servers">Errore nel salvataggio dei server ICE</string>
|
||||
<string name="how_to">Come si fa</string>
|
||||
<string name="how_to_use_your_servers">Come usare i tuoi server</string>
|
||||
<string name="enter_one_ICE_server_per_line">Server ICE (uno per riga)</string>
|
||||
<string name="accept_automatically">Automaticamente</string>
|
||||
<string name="bold">grassetto</string>
|
||||
<string name="callstatus_ended">chiamata terminata <xliff:g id="duration" example="01:15">%1$s</xliff:g></string>
|
||||
<string name="callstatus_error">errore di chiamata</string>
|
||||
<string name="callstatus_in_progress">chiamata in corso</string>
|
||||
<string name="colored">colorato</string>
|
||||
<string name="callstate_connected">connesso</string>
|
||||
<string name="callstate_connecting">connessione…</string>
|
||||
<string name="callstatus_connecting">connessione chiamata…</string>
|
||||
<string name="create_profile_button">Crea</string>
|
||||
<string name="create_profile">Crea profilo</string>
|
||||
<string name="delete_image">Elimina immagine</string>
|
||||
<string name="display_name__field">Nome da mostrare:</string>
|
||||
<string name="display_name_cannot_contain_whitespace">Il nome da mostrare non può contenere spazi.</string>
|
||||
<string name="edit_image">Modifica immagine</string>
|
||||
<string name="exit_without_saving">Esci senza salvare</string>
|
||||
<string name="full_name__field">Nome completo:</string>
|
||||
<string name="full_name_optional__prompt">Nome completo (facoltativo)</string>
|
||||
<string name="how_to_use_markdown">Come usare il markdown</string>
|
||||
<string name="icon_descr_audio_call">chiamata audio</string>
|
||||
<string name="audio_call_no_encryption">chiamata audio (non crittografata e2e)</string>
|
||||
<string name="onboarding_notifications_mode_periodic_desc"><b>Buono per la batteria</b>. Il servizio in secondo piano controlla nuovi messaggi ogni 10 minuti. Potresti perdere chiamate e messaggi urgenti.</string>
|
||||
<string name="call_already_ended">Chiamata già terminata!</string>
|
||||
<string name="create_your_profile">Crea il tuo profilo</string>
|
||||
<string name="decentralized">Decentralizzato</string>
|
||||
<string name="encrypted_audio_call">Chiamata crittografata e2e</string>
|
||||
<string name="encrypted_video_call">Videochiamata crittografata e2e</string>
|
||||
<string name="callstate_ended">terminata</string>
|
||||
<string name="how_it_works">Come funziona</string>
|
||||
<string name="how_simplex_works">Come funziona <xliff:g id="appName">SimpleX</xliff:g></string>
|
||||
<string name="answer_call">Rispondi alla chiamata</string>
|
||||
<string name="icon_descr_audio_off">Audio spento</string>
|
||||
<string name="icon_descr_audio_on">Audio acceso</string>
|
||||
<string name="settings_audio_video_calls">Chiamate audio e video</string>
|
||||
<string name="auto_accept_images">Auto-accetta immagini</string>
|
||||
<string name="integrity_msg_bad_hash">hash del messaggio errato</string>
|
||||
<string name="integrity_msg_bad_id">ID messaggio errato</string>
|
||||
<string name="icon_descr_call_ended">Chiamata terminata</string>
|
||||
<string name="icon_descr_call_progress">Chiamata in corso</string>
|
||||
<string name="call_on_lock_screen">Chiamate sulla schermata di blocco:</string>
|
||||
<string name="icon_descr_call_connecting">Connessione chiamata</string>
|
||||
<string name="connect_calls_via_relay">Connetti via relay</string>
|
||||
<string name="status_contact_has_e2e_encryption">il contatto ha la crittografia e2e</string>
|
||||
<string name="status_contact_has_no_e2e_encryption">il contatto non ha la crittografia e2e</string>
|
||||
<string name="no_call_on_lock_screen">Disattiva</string>
|
||||
<string name="integrity_msg_duplicate">messaggio duplicato</string>
|
||||
<string name="status_e2e_encrypted">crittografato e2e</string>
|
||||
<string name="allow_accepting_calls_from_lock_screen">Attiva le chiamate dalla schermata di blocco tramite le impostazioni.</string>
|
||||
<string name="icon_descr_flip_camera">Fotocamera frontale/posteriore</string>
|
||||
<string name="icon_descr_hang_up">Riaggancia</string>
|
||||
<string name="settings_section_title_calls">CHIAMATE</string>
|
||||
<string name="chat_database_section">DATABASE DELLA CHAT</string>
|
||||
<string name="chat_database_imported">Database della chat importato</string>
|
||||
<string name="chat_is_running">Chat in esecuzione</string>
|
||||
<string name="settings_section_title_chats">CHAT</string>
|
||||
<string name="set_password_to_export_desc">Il database è crittografato con una password casuale. Cambiala prima di esportare.</string>
|
||||
<string name="database_passphrase">Password del database</string>
|
||||
<string name="delete_chat_profile_question">Eliminare il profilo di chat\?</string>
|
||||
<string name="delete_database">Elimina database</string>
|
||||
<string name="settings_section_title_develop">SVILUPPA</string>
|
||||
<string name="settings_developer_tools">Strumenti di sviluppo</string>
|
||||
<string name="settings_section_title_device">DISPOSITIVO</string>
|
||||
<string name="error_deleting_database">Errore nell\'eliminazione del database della chat</string>
|
||||
<string name="error_exporting_chat_database">Errore nell\'esportazione del database della chat</string>
|
||||
<string name="error_starting_chat">Errore nell\'avvio della chat</string>
|
||||
<string name="error_stopping_chat">Errore nell\'interruzione della chat</string>
|
||||
<string name="settings_experimental_features">Funzionalità sperimentali</string>
|
||||
<string name="export_database">Esporta database</string>
|
||||
<string name="settings_section_title_help">AIUTO</string>
|
||||
<string name="chat_archive_header">Archivio chat</string>
|
||||
<string name="chat_is_stopped_indication">Chat fermata</string>
|
||||
<string name="archive_created_on_ts">Creato il <xliff:g id="archive_ts">%1$s</xliff:g></string>
|
||||
<string name="database_error">Errore del database</string>
|
||||
<string name="passphrase_is_different">La password del database è diversa da quella salvata nel Keystore.</string>
|
||||
<string name="delete_archive">Elimina archivio</string>
|
||||
<string name="delete_chat_archive_question">Eliminare l\'archivio della chat\?</string>
|
||||
<string name="encrypted_database">Database crittografato</string>
|
||||
<string name="enter_correct_passphrase">Inserisci la password giusta.</string>
|
||||
<string name="enter_passphrase">Inserisci la password…</string>
|
||||
<string name="error_with_info">Errore: %s</string>
|
||||
<string name="file_with_path">File: %s</string>
|
||||
<string name="icon_descr_group_inactive">Gruppo inattivo</string>
|
||||
<string name="rcv_conn_event_switch_queue_phase_completed">indirizzo cambiato per te</string>
|
||||
<string name="rcv_group_event_changed_member_role">cambiato il ruolo di %s in %s</string>
|
||||
<string name="rcv_group_event_changed_your_role">cambiato il tuo ruolo in %s</string>
|
||||
<string name="rcv_conn_event_switch_queue_phase_changing">cambio indirizzo…</string>
|
||||
<string name="snd_conn_event_switch_queue_phase_changing_for_member">cambio indirizzo per %s…</string>
|
||||
<string name="rcv_group_event_member_connected">connesso</string>
|
||||
<string name="group_member_status_connected">connesso</string>
|
||||
<string name="group_member_status_accepted">connessione (accettato)</string>
|
||||
<string name="group_member_status_announced">connessione (annunciato)</string>
|
||||
<string name="group_member_status_intro_invitation">connessione (invito di presentazione)</string>
|
||||
<string name="rcv_group_event_group_deleted">gruppo eliminato</string>
|
||||
<string name="group_member_status_group_deleted">gruppo eliminato</string>
|
||||
<string name="group_invitation_expired">Invito al gruppo scaduto</string>
|
||||
<string name="alert_message_group_invitation_expired">L\'invito al gruppo non è più valido, è stato rimosso dal mittente.</string>
|
||||
<string name="alert_title_no_group">Gruppo non trovato!</string>
|
||||
<string name="snd_group_event_group_profile_updated">profilo del gruppo aggiornato</string>
|
||||
<string name="invite_prohibited">Impossibile invitare il contatto!</string>
|
||||
<string name="change_verb">Cambia</string>
|
||||
<string name="change_member_role_question">Cambiare il ruolo del gruppo\?</string>
|
||||
<string name="clear_contacts_selection_button">Svuota</string>
|
||||
<string name="group_member_status_complete">completo</string>
|
||||
<string name="group_member_status_connecting">connessione</string>
|
||||
<string name="icon_descr_contact_checked">Contatto controllato</string>
|
||||
<string name="create_group_link">Crea link del gruppo</string>
|
||||
<string name="group_member_status_creator">creatore</string>
|
||||
<string name="info_row_database_id">ID database</string>
|
||||
<string name="button_delete_group">Elimina gruppo</string>
|
||||
<string name="delete_group_question">Eliminare il gruppo\?</string>
|
||||
<string name="button_edit_group_profile">Modifica il profilo del gruppo</string>
|
||||
<string name="error_creating_link_for_group">Errore nella creazione del link del gruppo</string>
|
||||
<string name="error_deleting_link_for_group">Errore nell\'eliminazione del link del gruppo</string>
|
||||
<string name="icon_descr_expand_role">Espandi la selezione dei ruoli</string>
|
||||
<string name="section_title_for_console">PER CONSOLE</string>
|
||||
<string name="group_link">Link del gruppo</string>
|
||||
<string name="delete_group_for_all_members_cannot_undo_warning">Il gruppo verrà eliminato per tutti i membri. Non è reversibile!</string>
|
||||
<string name="delete_group_for_self_cannot_undo_warning">Il gruppo verrà eliminato per te. Non è reversibile!</string>
|
||||
<string name="info_row_connection">Connessione</string>
|
||||
<string name="create_secret_group_title">Crea gruppo segreto</string>
|
||||
<string name="conn_level_desc_direct">diretta</string>
|
||||
<string name="network_option_enable_tcp_keep_alive">Attiva il keep-alive TCP</string>
|
||||
<string name="error_changing_role">Errore nel cambio di ruolo</string>
|
||||
<string name="error_removing_member">Errore nella rimozione del membro</string>
|
||||
<string name="error_saving_group_profile">Errore nel salvataggio del profilo del gruppo</string>
|
||||
<string name="info_row_group">Gruppo</string>
|
||||
<string name="group_display_name_field">Nome da mostrare del gruppo:</string>
|
||||
<string name="group_profile_is_stored_on_members_devices">Il profilo del gruppo è memorizzato sui dispositivi dei membri, non sui server.</string>
|
||||
<string name="chat_preferences_always">sempre</string>
|
||||
<string name="both_you_and_your_contacts_can_delete">Sia tu che il tuo contatto potete eliminare irreversibilmente i messaggi inviati.</string>
|
||||
<string name="both_you_and_your_contact_can_send_disappearing">Sia tu che il tuo contatto potete inviare messaggi a tempo.</string>
|
||||
<string name="both_you_and_your_contact_can_send_voice">Sia tu che il tuo contatto potete inviare messaggi vocali.</string>
|
||||
<string name="chat_preferences">Preferenze della chat</string>
|
||||
<string name="chat_preferences_contact_allows">Il contatto lo consente</string>
|
||||
<string name="contact_preferences">Preferenze del contatto</string>
|
||||
<string name="contacts_can_mark_messages_for_deletion">I contatti possono contrassegnare i messaggi per l\'eliminazione; potrai vederli.</string>
|
||||
<string name="theme_dark">Scuro</string>
|
||||
<string name="chat_preferences_default">predefinito (%s)</string>
|
||||
<string name="full_deletion">Elimina per tutti</string>
|
||||
<string name="direct_messages">Messaggi diretti</string>
|
||||
<string name="timed_messages">Messaggi a tempo</string>
|
||||
<string name="disappearing_prohibited_in_this_chat">I messaggi a tempo sono vietati in questa conversazione.</string>
|
||||
<string name="feature_enabled">attivato</string>
|
||||
<string name="feature_enabled_for_contact">attivato per il contatto</string>
|
||||
<string name="feature_enabled_for_you">attivato per te</string>
|
||||
<string name="group_preferences">Preferenze del gruppo</string>
|
||||
<string name="v4_2_auto_accept_contact_requests">Auto-accetta richieste di contatto</string>
|
||||
<string name="ttl_d">%dg</string>
|
||||
<string name="ttl_day">%d giorno</string>
|
||||
<string name="ttl_days">%d giorni</string>
|
||||
<string name="delete_after">Elimina dopo</string>
|
||||
<string name="ttl_h">%do</string>
|
||||
<string name="ttl_hour">%d ora</string>
|
||||
<string name="ttl_hours">%d ore</string>
|
||||
<string name="disappearing_messages_are_prohibited">I messaggi a tempo sono vietati in questo gruppo.</string>
|
||||
<string name="ttl_m">%dm</string>
|
||||
<string name="ttl_min">%d min</string>
|
||||
<string name="ttl_month">%d mese</string>
|
||||
<string name="ttl_months">%d mesi</string>
|
||||
<string name="ttl_mth">%dmese</string>
|
||||
<string name="ttl_s">%ds</string>
|
||||
<string name="ttl_sec">%d sec</string>
|
||||
<string name="ttl_w">%dset</string>
|
||||
<string name="ttl_week">%d settimana</string>
|
||||
<string name="ttl_weeks">%d settimane</string>
|
||||
<string name="v4_2_group_links">Link del gruppo</string>
|
||||
<string name="group_members_can_delete">I membri del gruppo possono eliminare irreversibilmente i messaggi inviati.</string>
|
||||
<string name="group_members_can_send_dms">I membri del gruppo possono inviare messaggi diretti.</string>
|
||||
<string name="group_members_can_send_disappearing">I membri del gruppo possono inviare messaggi a tempo.</string>
|
||||
<string name="group_members_can_send_voice">I membri del gruppo possono inviare messaggi vocali.</string>
|
||||
<string name="v4_4_verify_connection_security_desc">Confronta i codici di sicurezza con i tuoi contatti.</string>
|
||||
<string name="v4_4_disappearing_messages">Messaggi a tempo</string>
|
||||
<string name="v4_3_improved_privacy_and_security_desc">Nascondi la schermata dell\'app nelle app recenti.</string>
|
||||
<string name="keychain_allows_to_receive_ntfs">Android Keystore verrà usato per memorizzare in modo sicuro la password dopo il riavvio dell\'app o la modifica della password; consentirà di ricevere le notifiche.</string>
|
||||
<string name="impossible_to_recover_passphrase"><b>Nota bene</b>: NON potrai recuperare o cambiare la password se la perdi.</string>
|
||||
<string name="change_database_passphrase_question">Cambiare password del database\?</string>
|
||||
<string name="confirm_new_passphrase">Conferma password nuova…</string>
|
||||
<string name="current_passphrase">Password attuale…</string>
|
||||
<string name="database_encrypted">Database crittografato!</string>
|
||||
<string name="database_passphrase_will_be_updated">La password di crittografia del database verrà aggiornata.</string>
|
||||
<string name="database_will_be_encrypted">Il database verrà crittografato.</string>
|
||||
<string name="database_will_be_encrypted_and_passphrase_stored">Il database verrà crittografato e la password conservata nel Keystore.</string>
|
||||
<string name="delete_files_and_media_question">Eliminare i file e i multimediali\?</string>
|
||||
<string name="delete_files_and_media">"Elimina file e multimediali"</string>
|
||||
<string name="delete_messages">Elimina messaggi</string>
|
||||
<string name="delete_messages_after">Elimina messaggi dopo</string>
|
||||
<string name="total_files_count_and_size">%d file con dimensione totale di %s</string>
|
||||
<string name="enable_automatic_deletion_question">Attivare l\'eliminazione automatica dei messaggi\?</string>
|
||||
<string name="encrypt_database_question">Crittografare il database\?</string>
|
||||
<string name="encrypt_database">Crittografare</string>
|
||||
<string name="error_changing_message_deletion">Errore nella modifica dell\'impostazione</string>
|
||||
<string name="error_encrypting_database">Errore nella crittografia del database</string>
|
||||
<string name="your_settings">Le tue impostazioni</string>
|
||||
<string name="you_will_be_connected_when_group_host_device_is_online">Verrai connesso/a al gruppo quando il dispositivo dell\'host del gruppo sarà in linea, attendi o controlla più tardi!</string>
|
||||
<string name="if_you_received_simplex_invitation_link_you_can_open_in_browser">Se hai ricevuto il link di invito a <xliff:g id="appName">SimpleX Chat</xliff:g>, puoi aprirlo nel tuo browser:</string>
|
||||
<string name="mobile_tap_open_in_mobile_app_then_tap_connect_in_app">📱 mobile: tocca <b>Apri nell\'app mobile</b>, quindi <b>Connetti</b> nell\'app.</string>
|
||||
<string name="no_details">nessun dettaglio</string>
|
||||
<string name="add_contact">Link di invito una tantum</string>
|
||||
<string name="only_stored_on_members_devices">(memorizzato solo dai membri del gruppo)</string>
|
||||
<string name="toast_permission_denied">Autorizzazione negata!</string>
|
||||
<string name="reject_contact_button">Rifiuta</string>
|
||||
<string name="connect_via_link_or_qr_from_clipboard_or_in_person">(scansiona o incolla dagli appunti)</string>
|
||||
<string name="scan_QR_code">Scansiona codice QR</string>
|
||||
<string name="add_contact_or_create_group">Inizia una nuova conversazione</string>
|
||||
<string name="chat_help_tap_button">Tocca il pulsante</string>
|
||||
<string name="thank_you_for_installing_simplex">Grazie per aver installato <xliff:g id="appNameFull">SimpleX Chat</xliff:g>!</string>
|
||||
<string name="to_connect_via_link_title">Per connettersi via link</string>
|
||||
<string name="to_share_with_your_contact">(da condividere con il tuo contatto)</string>
|
||||
<string name="to_start_a_new_chat_help_header">Per iniziare una nuova chat</string>
|
||||
<string name="use_camera_button">Usa la fotocamera</string>
|
||||
<string name="you_can_connect_to_simplex_chat_founder">Puoi <font color="#0088ff">connetterti con gli sviluppatori di <xliff:g id="appNameFull">SimpleX Chat</xliff:g> per porre domande e ricevere aggiornamenti</font>.</string>
|
||||
<string name="invalid_contact_link">Link non valido!</string>
|
||||
<string name="invalid_QR_code">Codice QR non valido</string>
|
||||
<string name="image_descr_link_preview">immagine di anteprima link</string>
|
||||
<string name="mark_read">Segna come già letto</string>
|
||||
<string name="mark_unread">Segna come non letto</string>
|
||||
<string name="icon_descr_more_button">Altro</string>
|
||||
<string name="mute_chat">Silenzia</string>
|
||||
<string name="image_descr_profile_image">immagine del profilo</string>
|
||||
<string name="icon_descr_profile_image_placeholder">segnaposto immagine del profilo</string>
|
||||
<string name="image_descr_qr_code">Codice QR</string>
|
||||
<string name="set_contact_name">Imposta il nome del contatto</string>
|
||||
<string name="icon_descr_settings">Impostazioni</string>
|
||||
<string name="show_QR_code">Mostra codice QR</string>
|
||||
<string name="connection_you_accepted_will_be_cancelled">La connessione che hai accettato verrà annullata!</string>
|
||||
<string name="contact_you_shared_link_with_wont_be_able_to_connect">Il contatto con cui hai condiviso questo link NON sarà in grado di connettersi!</string>
|
||||
<string name="this_link_is_not_a_valid_connection_link">Questo non è un link di connessione valido!</string>
|
||||
<string name="this_QR_code_is_not_a_link">Questo codice QR non è un link!</string>
|
||||
<string name="unmute_chat">Riattiva audio</string>
|
||||
<string name="contact_wants_to_connect_with_you">vuole connettersi con te!</string>
|
||||
<string name="image_descr_simplex_logo">Logo di <xliff:g id="appName">SimpleX</xliff:g></string>
|
||||
<string name="icon_descr_address">Indirizzo di <xliff:g id="appName">SimpleX</xliff:g></string>
|
||||
<string name="icon_descr_simplex_team">Squadra di <xliff:g id="appName">SimpleX</xliff:g></string>
|
||||
<string name="you_accepted_connection">Hai accettato la connessione</string>
|
||||
<string name="you_invited_your_contact">Hai invitato il contatto</string>
|
||||
<string name="your_chat_profile_will_be_sent_to_your_contact">Il tuo profilo di chat verrà inviato
|
||||
\nal tuo contatto</string>
|
||||
<string name="show_QR_code_for_your_contact_to_scan_from_the_app__multiline">Il tuo contatto può scansionare il codice QR dall\'app.</string>
|
||||
<string name="alert_text_connection_pending_they_need_to_be_online_can_delete_and_retry">Il tuo contatto deve essere in linea per completare la connessione.
|
||||
\nPuoi annullare questa connessione e rimuovere il contatto (e riprovare più tardi con un link nuovo).</string>
|
||||
<string name="you_will_be_connected_when_your_connection_request_is_accepted">Verrai connesso/a quando la tua richiesta di connessione verrà accettata, attendi o controlla più tardi!</string>
|
||||
<string name="you_will_be_connected_when_your_contacts_device_is_online">Verrai connesso/a quando il dispositivo del tuo contatto sarà in linea, attendi o controlla più tardi!</string>
|
||||
<string name="incorrect_code">Codice di sicurezza sbagliato!</string>
|
||||
<string name="smp_servers_invalid_address">Indirizzo del server non valido!</string>
|
||||
<string name="markdown_help">Aiuto sul markdown</string>
|
||||
<string name="markdown_in_messages">Markdown nei messaggi</string>
|
||||
<string name="mark_code_verified">Segna come verificato/a</string>
|
||||
<string name="one_time_link">Link di invito una tantum</string>
|
||||
<string name="paste_button">Incolla</string>
|
||||
<string name="paste_connection_link_below_to_connect">Incolla il link che hai ricevuto nella casella sottostante per connetterti con il tuo contatto.</string>
|
||||
<string name="smp_servers_preset_server">Server preimpostato</string>
|
||||
<string name="smp_servers_preset_address">Indirizzo server preimpostato</string>
|
||||
<string name="smp_servers_save">Salva i server</string>
|
||||
<string name="scan_code">Scansiona codice</string>
|
||||
<string name="scan_code_from_contacts_app">Scansiona il codice di sicurezza dall\'app del tuo contatto.</string>
|
||||
<string name="smp_servers_scan_qr">Scansiona codice QR del server</string>
|
||||
<string name="security_code">Codice di sicurezza</string>
|
||||
<string name="chat_with_the_founder">Invia domande e idee</string>
|
||||
<string name="send_us_an_email">Inviaci un\'email</string>
|
||||
<string name="smp_servers_test_failed">Test del server fallito!</string>
|
||||
<string name="share_invitation_link">Condividi link di invito</string>
|
||||
<string name="chat_lock">SimpleX Lock</string>
|
||||
<string name="is_not_verified">%s non è verificato/a</string>
|
||||
<string name="is_verified">%s è verificato/a</string>
|
||||
<string name="smp_servers">Server SMP</string>
|
||||
<string name="smp_servers_test_some_failed">Alcuni server hanno fallito il test:</string>
|
||||
<string name="smp_servers_test_server">Testa server</string>
|
||||
<string name="smp_servers_test_servers">Testa i server</string>
|
||||
<string name="this_string_is_not_a_connection_link">Questa stringa non è un link di connessione!</string>
|
||||
<string name="to_verify_compare">Per verificare la crittografia end-to-end con il tuo contatto, confrontate (o scansionate) il codice sui vostri dispositivi.</string>
|
||||
<string name="smp_servers_use_server_for_new_conn">Usa per connessioni nuove</string>
|
||||
<string name="smp_servers_use_server">Usa il server</string>
|
||||
<string name="you_can_also_connect_by_clicking_the_link">Puoi anche connetterti cliccando il link. Se si apre nel browser, clicca il pulsante <b>Apri nell\'app mobile</b>.</string>
|
||||
<string name="your_profile_will_be_sent">Il tuo profilo di chat verrà inviato al tuo contatto</string>
|
||||
<string name="your_contact_address">Il tuo indirizzo di contatto</string>
|
||||
<string name="smp_servers_your_server">Il tuo server</string>
|
||||
<string name="smp_servers_your_server_address">L\'indirizzo del tuo server</string>
|
||||
<string name="your_simplex_contact_address">Il tuo indirizzo di contatto di <xliff:g id="appName">SimpleX</xliff:g>.</string>
|
||||
<string name="network_disable_socks_info">Se confermi, i server di messaggistica saranno in grado di vedere il tuo indirizzo IP e il tuo fornitore, a quali server ti stai connettendo.</string>
|
||||
<string name="install_simplex_chat_for_terminal">Installa <xliff:g id="appNameFull">SimpleX Chat</xliff:g> per terminale</string>
|
||||
<string name="ensure_ICE_server_address_are_correct_format_and_unique">Assicurati che gli indirizzi dei server WebRTC ICE siano nel formato corretto, uno per riga e non doppi.</string>
|
||||
<string name="network_and_servers">Rete e server</string>
|
||||
<string name="network_settings_title">Impostazioni di rete</string>
|
||||
<string name="network_use_onion_hosts_no">No</string>
|
||||
<string name="network_use_onion_hosts_required_desc">Gli host Onion saranno necessari per la connessione.</string>
|
||||
<string name="network_use_onion_hosts_required_desc_in_alert">Gli host Onion saranno necessari per la connessione.</string>
|
||||
<string name="network_use_onion_hosts_prefer_desc">Gli host Onion verranno usati quando disponibili.</string>
|
||||
<string name="network_use_onion_hosts_prefer_desc_in_alert">Gli host Onion verranno usati quando disponibili.</string>
|
||||
<string name="network_use_onion_hosts_no_desc">Gli host Onion non verranno usati.</string>
|
||||
<string name="network_use_onion_hosts_no_desc_in_alert">Gli host Onion non verranno usati.</string>
|
||||
<string name="rate_the_app">Valuta l\'app</string>
|
||||
<string name="network_use_onion_hosts_required">Obbligatorio</string>
|
||||
<string name="save_servers_button">Salva</string>
|
||||
<string name="saved_ICE_servers_will_be_removed">I server WebRTC ICE salvati verranno rimossi.</string>
|
||||
<string name="share_link">Condividi link</string>
|
||||
<string name="star_on_github">Stella su GitHub</string>
|
||||
<string name="update_onion_hosts_settings_question">Aggiornare l\'impostazione degli host .onion\?</string>
|
||||
<string name="network_disable_socks">Usare una connessione internet diretta\?</string>
|
||||
<string name="network_use_onion_hosts">Usa gli host .onion</string>
|
||||
<string name="network_enable_socks">Usare il proxy SOCKS\?</string>
|
||||
<string name="network_socks_toggle">Usa il proxy SOCKS (porta 9050)</string>
|
||||
<string name="use_simplex_chat_servers__question">Usare i server di <xliff:g id="appNameFull">SimpleX Chat</xliff:g>\?</string>
|
||||
<string name="using_simplex_chat_servers">Stai usando i server di <xliff:g id="appNameFull">SimpleX Chat</xliff:g>.</string>
|
||||
<string name="network_use_onion_hosts_prefer">Quando disponibili</string>
|
||||
<string name="you_can_share_your_address_anybody_will_be_able_to_connect">Puoi condividere il tuo indirizzo come link o come codice QR: chiunque potrà connettersi a te. Non perderai i tuoi contatti se in seguito lo elimini.</string>
|
||||
<string name="your_ICE_servers">I tuoi server ICE</string>
|
||||
<string name="your_SMP_servers">I tuoi server SMP</string>
|
||||
<string name="italic">corsivo</string>
|
||||
<string name="callstatus_missed">chiamata persa</string>
|
||||
<string name="callstate_received_answer">risposta ricevuta…</string>
|
||||
<string name="callstate_received_confirmation">conferma ricevuta…</string>
|
||||
<string name="callstatus_rejected">chiamata rifiutata</string>
|
||||
<string name="save_and_notify_contact">Salva e avvisa il contatto</string>
|
||||
<string name="save_and_notify_contacts">Salva e avvisa i contatti</string>
|
||||
<string name="save_and_notify_group_members">Salva e avvisa i membri del gruppo</string>
|
||||
<string name="save_preferences_question">Salvare le preferenze\?</string>
|
||||
<string name="secret">segreto</string>
|
||||
<string name="callstate_starting">avvio…</string>
|
||||
<string name="strikethrough">barrato</string>
|
||||
<string name="the_messaging_and_app_platform_protecting_your_privacy_and_security">La piattaforma di messaggistica che protegge la tua privacy e sicurezza.</string>
|
||||
<string name="profile_is_only_shared_with_your_contacts">Il profilo è condiviso solo con i tuoi contatti.</string>
|
||||
<string name="callstate_waiting_for_answer">in attesa di risposta…</string>
|
||||
<string name="callstate_waiting_for_confirmation">in attesa di conferma…</string>
|
||||
<string name="we_do_not_store_contacts_or_messages_on_servers">Non memorizziamo nessuno dei tuoi contatti o messaggi (una volta recapitati) sui server.</string>
|
||||
<string name="section_title_welcome_message">MESSAGGIO DI BENVENUTO</string>
|
||||
<string name="you_can_use_markdown_to_format_messages__prompt">Puoi usare il markdown per formattare i messaggi:</string>
|
||||
<string name="you_control_your_chat">Sei tu a controllare la tua chat!</string>
|
||||
<string name="your_chat_profile">Il tuo profilo di chat</string>
|
||||
<string name="your_profile_is_stored_on_your_device">Il tuo profilo, i contatti e i messaggi recapitati sono memorizzati sul tuo dispositivo.</string>
|
||||
<string name="your_profile_is_stored_on_device_and_shared_only_with_contacts_simplex_cannot_see_it">Il tuo profilo è memorizzato sul tuo dispositivo e condiviso solo con i tuoi contatti.
|
||||
\n
|
||||
\nI server di <xliff:g id="appName">SimpleX</xliff:g> non possono vedere il tuo profilo.</string>
|
||||
<string name="ignore">Ignora</string>
|
||||
<string name="immune_to_spam_and_abuse">Immune a spam e abusi</string>
|
||||
<string name="incoming_audio_call">Chiamata in arrivo</string>
|
||||
<string name="incoming_video_call">Videochiamata in arrivo</string>
|
||||
<string name="onboarding_notifications_mode_service">Istantaneo</string>
|
||||
<string name="onboarding_notifications_mode_subtitle">Può essere cambiato in seguito via impostazioni.</string>
|
||||
<string name="make_private_connection">Crea una connessione privata</string>
|
||||
<string name="many_people_asked_how_can_it_deliver">Molte persone hanno chiesto: <i>se <xliff:g id="appName">SimpleX</xliff:g> non ha identificatori utente, come può recapitare i messaggi\?</i></string>
|
||||
<string name="only_client_devices_store_contacts_groups_e2e_encrypted_messages">Solo i dispositivi client memorizzano i profili utente, i contatti, i gruppi e i messaggi inviati con <b>crittografia end-to-end a 2 livelli</b>.</string>
|
||||
<string name="opensource_protocol_and_code_anybody_can_run_servers">Protocollo e codice open source: chiunque può gestire i server.</string>
|
||||
<string name="paste_the_link_you_received">Incolla il link ricevuto</string>
|
||||
<string name="people_can_connect_only_via_links_you_share">Le persone possono connettersi a te solo tramite i link che condividi.</string>
|
||||
<string name="onboarding_notifications_mode_periodic">Periodico</string>
|
||||
<string name="privacy_redefined">Privacy ridefinita</string>
|
||||
<string name="onboarding_notifications_mode_title">Notifiche private</string>
|
||||
<string name="read_more_in_github_with_link">Maggiori informazioni nel nostro <font color="#0088ff">repository GitHub</font>.</string>
|
||||
<string name="read_more_in_github">Maggiori informazioni nel nostro repository GitHub.</string>
|
||||
<string name="reject">Rifiuta</string>
|
||||
<string name="first_platform_without_user_ids">La prima piattaforma senza alcun identificatore utente – privata by design.</string>
|
||||
<string name="next_generation_of_private_messaging">La nuova generazione di messaggistica privata</string>
|
||||
<string name="to_protect_privacy_simplex_has_ids_for_queues">Per proteggere la privacy, invece degli ID utente usati da tutte le altre piattaforme, <xliff:g id="appName">SimpleX</xliff:g> dispone di identificatori per le code dei messaggi, separati per ciascuno dei tuoi contatti.</string>
|
||||
<string name="use_chat">Usa la chat</string>
|
||||
<string name="icon_descr_video_call">videochiamata</string>
|
||||
<string name="video_call_no_encryption">videochiamata (non crittografata e2e)</string>
|
||||
<string name="onboarding_notifications_mode_off">Quando l\'app è in esecuzione</string>
|
||||
<string name="contact_wants_to_connect_via_call"><xliff:g id="contactName" example="Alice">%1$s</xliff:g> vuole connettersi con te via</string>
|
||||
<string name="you_control_servers_to_receive_your_contacts_to_send">Puoi controllare attraverso quale/i server <b>ricevere</b> i messaggi, i tuoi contatti – i server che usi per inviare loro i messaggi.</string>
|
||||
<string name="alert_text_skipped_messages_it_can_happen_when">Può accadere quando:
|
||||
\n1. I messaggi scadono sul server se non sono stati ricevuti per 30 giorni,
|
||||
\n2. Il server usato per ricevere i messaggi da questo contatto è stato aggiornato e riavviato.
|
||||
\n3. La connessione è compromessa.
|
||||
\nConnettiti agli sviluppatori tramite Impostazioni per ricevere aggiornamenti riguardo i server.
|
||||
\nAggiungeremo la ridondanza del server per prevenire la perdita di messaggi.</string>
|
||||
<string name="icon_descr_call_rejected">Chiamata rifiutata</string>
|
||||
<string name="icon_descr_call_missed">Chiamata persa</string>
|
||||
<string name="status_no_e2e_encryption">nessuna crittografia e2e</string>
|
||||
<string name="open_verb">Apri</string>
|
||||
<string name="open_simplex_chat_to_accept_call">Apri <xliff:g id="appNameFull">SimpleX Chat</xliff:g> per accettare la chiamata</string>
|
||||
<string name="call_connection_peer_to_peer">peer-to-peer</string>
|
||||
<string name="icon_descr_call_pending_sent">Chiamata in sospeso</string>
|
||||
<string name="privacy_and_security">Privacy e sicurezza</string>
|
||||
<string name="protect_app_screen">Proteggi la schermata dell\'app</string>
|
||||
<string name="show_call_on_lock_screen">Mostra</string>
|
||||
<string name="alert_title_skipped_messages">Messaggi saltati</string>
|
||||
<string name="icon_descr_speaker_off">Altoparlante spento</string>
|
||||
<string name="icon_descr_speaker_on">Altoparlante acceso</string>
|
||||
<string name="call_connection_via_relay">via relay</string>
|
||||
<string name="icon_descr_video_off">Video off</string>
|
||||
<string name="icon_descr_video_on">Video on</string>
|
||||
<string name="webrtc_ice_servers">Server WebRTC ICE</string>
|
||||
<string name="integrity_msg_skipped"><xliff:g id="connection ID" example="1">%1$d</xliff:g> messaggio/i saltato/i</string>
|
||||
<string name="your_calls">Le tue chiamate</string>
|
||||
<string name="your_ice_servers">I tuoi server ICE</string>
|
||||
<string name="your_privacy">La tua privacy</string>
|
||||
<string name="import_database_confirmation">Importa</string>
|
||||
<string name="import_database_question">Importare il database della chat\?</string>
|
||||
<string name="import_database">Importa database</string>
|
||||
<string name="settings_section_title_incognito">Modalità incognito</string>
|
||||
<string name="settings_section_title_messages">MESSAGGI</string>
|
||||
<string name="new_database_archive">Nuovo archivio database</string>
|
||||
<string name="old_database_archive">Vecchio archivio del database</string>
|
||||
<string name="restart_the_app_to_create_a_new_chat_profile">Riavvia l\'app per creare un profilo di chat nuovo.</string>
|
||||
<string name="restart_the_app_to_use_imported_chat_database">Riavvia l\'app per usare il database della chat importato.</string>
|
||||
<string name="run_chat_section">AVVIA CHAT</string>
|
||||
<string name="send_link_previews">Invia anteprime dei link</string>
|
||||
<string name="set_password_to_export">Imposta la password per esportare</string>
|
||||
<string name="settings_section_title_settings">IMPOSTAZIONI</string>
|
||||
<string name="settings_section_title_socks">PROXY SOCKS</string>
|
||||
<string name="stop_chat_confirmation">Ferma</string>
|
||||
<string name="stop_chat_question">Fermare la chat\?</string>
|
||||
<string name="stop_chat_to_export_import_or_delete_chat_database">Ferma la chat per esportare, importare o eliminare il database della chat. Non potrai ricevere e inviare messaggi mentre la chat è ferma.</string>
|
||||
<string name="settings_section_title_support">SUPPORTA SIMPLEX CHAT</string>
|
||||
<string name="settings_section_title_themes">TEMI</string>
|
||||
<string name="delete_chat_profile_action_cannot_be_undone_warning">Questa azione non può essere annullata: il tuo profilo, i contatti, i messaggi e i file andranno persi in modo irreversibile.</string>
|
||||
<string name="transfer_images_faster">Trasferisci immagini più velocemente</string>
|
||||
<string name="settings_section_title_you">TU</string>
|
||||
<string name="your_chat_database">Il tuo database della chat</string>
|
||||
<string name="your_current_chat_database_will_be_deleted_and_replaced_with_the_imported_one">Il tuo attuale database di chat verrà ELIMINATO e SOSTITUITO con quello importato.
|
||||
\nQuesta azione non può essere annullata: il tuo profilo, i contatti, i messaggi e i file andranno persi in modo irreversibile.</string>
|
||||
<string name="alert_title_group_invitation_expired">Invito scaduto!</string>
|
||||
<string name="group_invitation_item_description">invito al gruppo <xliff:g id="group_name">%1$s</xliff:g></string>
|
||||
<string name="icon_descr_add_members">Invita membri</string>
|
||||
<string name="join_group_button">Entra</string>
|
||||
<string name="join_group_question">Entrare nel gruppo\?</string>
|
||||
<string name="join_group_incognito_button">Entra in incognito</string>
|
||||
<string name="joining_group">Ingresso nel gruppo</string>
|
||||
<string name="keychain_error">Errore del portachiavi</string>
|
||||
<string name="leave_group_button">Esci</string>
|
||||
<string name="leave_group_question">Uscire dal gruppo\?</string>
|
||||
<string name="open_chat">Apri chat</string>
|
||||
<string name="restore_passphrase_not_found_desc">Password non trovata nel Keystore, inseriscila a mano. Potrebbe essere successo se hai ripristinato i dati dell\'app usando uno strumento di backup. In caso contrario, contatta gli sviluppatori.</string>
|
||||
<string name="restore_database_alert_desc">Inserisci la password precedente dopo aver ripristinato il backup del database. Questa azione non può essere annullata.</string>
|
||||
<string name="store_passphrase_securely_without_recover">Conserva la password in modo sicuro, NON potrai accedere alla chat se la perdi.</string>
|
||||
<string name="restore_database_alert_confirm">Ripristina</string>
|
||||
<string name="restore_database">Ripristina backup del database</string>
|
||||
<string name="restore_database_alert_title">Ripristinare il backup del database\?</string>
|
||||
<string name="database_restore_error">Errore di ripristino del database</string>
|
||||
<string name="save_archive">Salva archivio</string>
|
||||
<string name="save_passphrase_and_open_chat">Salva la password e apri la chat</string>
|
||||
<string name="database_backup_can_be_restored">Il tentativo di cambiare la password del database non è stato completato.</string>
|
||||
<string name="unknown_database_error_with_info">Errore del database sconosciuto: %s</string>
|
||||
<string name="unknown_error">Errore sconosciuto</string>
|
||||
<string name="wrong_passphrase">Password del database sbagliata</string>
|
||||
<string name="wrong_passphrase_title">Password sbagliata!</string>
|
||||
<string name="you_are_invited_to_group_join_to_connect_with_group_members">Sei stato/a invitato/a al gruppo. Entra per connetterti con i suoi membri.</string>
|
||||
<string name="you_can_start_chat_via_setting_or_by_restarting_the_app">Puoi avviare la chat tramite Impostazioni -> Database o riavviando l\'app.</string>
|
||||
<string name="youve_accepted_group_invitation_connecting_to_inviting_group_member">Sei entrato/a in questo gruppo. Connessione al membro del gruppo invitante.</string>
|
||||
<string name="you_will_stop_receiving_messages_from_this_group_chat_history_will_be_preserved">Non riceverai più messaggi da questo gruppo. La cronologia della chat verrà conservata.</string>
|
||||
<string name="group_member_status_invited">invitato</string>
|
||||
<string name="rcv_group_event_invited_via_your_group_link">invitato via link del tuo gruppo</string>
|
||||
<string name="rcv_group_event_member_added">invitato <xliff:g id="member profile" example="alice (Alice)">%1$s</xliff:g></string>
|
||||
<string name="rcv_group_event_member_left">uscito/a</string>
|
||||
<string name="group_member_status_left">uscito/a</string>
|
||||
<string name="group_member_role_member">membro</string>
|
||||
<string name="group_member_role_owner">proprietario</string>
|
||||
<string name="group_member_status_removed">rimosso</string>
|
||||
<string name="rcv_group_event_member_deleted">rimosso <xliff:g id="member profile" example="alice (Alice)">%1$s</xliff:g></string>
|
||||
<string name="rcv_group_event_user_deleted">sei stato/a rimosso/a</string>
|
||||
<string name="group_invitation_tap_to_join">Tocca per entrare</string>
|
||||
<string name="group_invitation_tap_to_join_incognito">Toccare per entrare in incognito</string>
|
||||
<string name="alert_message_no_group">Questo gruppo non esiste più.</string>
|
||||
<string name="rcv_group_event_updated_group_profile">profilo del gruppo aggiornato</string>
|
||||
<string name="you_are_invited_to_group">Sei stato/a invitato/a al gruppo</string>
|
||||
<string name="snd_conn_event_switch_queue_phase_completed">hai cambiato indirizzo</string>
|
||||
<string name="snd_conn_event_switch_queue_phase_completed_for_member">hai cambiato l\'indirizzo per %s</string>
|
||||
<string name="snd_group_event_changed_role_for_yourself">hai cambiato il tuo ruolo in %s</string>
|
||||
<string name="snd_group_event_changed_member_role">hai cambiato il ruolo di %s in %s</string>
|
||||
<string name="you_joined_this_group">Sei entrato/a in questo gruppo</string>
|
||||
<string name="snd_group_event_user_left">sei uscito/a</string>
|
||||
<string name="you_rejected_group_invitation">Hai rifiutato l\'invito al gruppo</string>
|
||||
<string name="snd_group_event_member_deleted">hai rimosso <xliff:g id="member profile" example="alice (Alice)">%1$s</xliff:g></string>
|
||||
<string name="alert_title_cant_invite_contacts_descr">Stai usando un profilo in incognito per questo gruppo: per impedire la condivisione del tuo profilo principale non è consentito invitare contatti</string>
|
||||
<string name="you_sent_group_invitation">Hai inviato un invito al gruppo</string>
|
||||
<string name="button_add_members">Invita membri</string>
|
||||
<string name="invite_to_group_button">Invita al gruppo</string>
|
||||
<string name="button_leave_group">Esci dal gruppo</string>
|
||||
<string name="info_row_local_name">Nome locale</string>
|
||||
<string name="member_info_section_title_member">MEMBRO</string>
|
||||
<string name="member_will_be_removed_from_group_cannot_be_undone">Il membro verrà rimosso dal gruppo, non è reversibile!</string>
|
||||
<string name="new_member_role">Nuovo ruolo del membro</string>
|
||||
<string name="no_contacts_selected">Nessun contatto selezionato</string>
|
||||
<string name="no_contacts_to_add">Nessun contatto da aggiungere</string>
|
||||
<string name="only_group_owners_can_change_prefs">Solo i proprietari del gruppo possono modificarne le preferenze.</string>
|
||||
<string name="remove_member_confirmation">Rimuovi</string>
|
||||
<string name="button_remove_member">Rimuovi membro</string>
|
||||
<string name="role_in_group">Ruolo</string>
|
||||
<string name="select_contacts">Seleziona i contatti</string>
|
||||
<string name="button_send_direct_message">Invia messaggio diretto</string>
|
||||
<string name="skip_inviting_button">Salta l\'invito di membri</string>
|
||||
<string name="switch_verb">Cambia</string>
|
||||
<string name="num_contacts_selected"><xliff:g id="num_contacts">%1$s</xliff:g> contatto/i selezionato/i</string>
|
||||
<string name="group_info_section_title_num_members"><xliff:g id="num_members">%1$s</xliff:g> MEMBRI</string>
|
||||
<string name="you_can_share_group_link_anybody_will_be_able_to_connect">Puoi condividere un link o un codice QR: chiunque potrà unirsi al gruppo. Non perderai i membri del gruppo se in seguito lo elimini.</string>
|
||||
<string name="invite_prohibited_description">Stai tentando di invitare un contatto con cui hai condiviso un profilo in incognito nel gruppo in cui stai usando il tuo profilo principale</string>
|
||||
<string name="group_info_member_you">tu: <xliff:g id="group_info_you">%1$s</xliff:g></string>
|
||||
<string name="incognito">Incognito</string>
|
||||
<string name="group_unsupported_incognito_main_profile_sent">La modalità in incognito non è supportata qui: il tuo profilo principale verrà inviato ai membri del gruppo</string>
|
||||
<string name="incognito_info_protects">La modalità in incognito protegge la privacy del nome e dell\'immagine del tuo profilo principale: per ogni nuovo contatto viene creato un nuovo profilo casuale.</string>
|
||||
<string name="conn_level_desc_indirect">indiretta (<xliff:g id="conn_level">%1$s</xliff:g>)</string>
|
||||
<string name="incognito_info_allows">Permette di avere molte connessioni anonime senza dati condivisi tra di loro in un unico profilo di chat.</string>
|
||||
<string name="theme_light">Chiaro</string>
|
||||
<string name="network_status">Stato della rete</string>
|
||||
<string name="network_option_ping_interval">Intervallo PING</string>
|
||||
<string name="network_option_protocol_timeout">Scadenza del protocollo</string>
|
||||
<string name="receiving_via">Ricezione via</string>
|
||||
<string name="network_options_reset_to_defaults">Ripristina i predefiniti</string>
|
||||
<string name="network_options_revert">Annulla</string>
|
||||
<string name="network_options_save">Salva</string>
|
||||
<string name="save_group_profile">Salva il profilo del gruppo</string>
|
||||
<string name="network_option_seconds_label">sec</string>
|
||||
<string name="sending_via">Invio tramite</string>
|
||||
<string name="conn_stats_section_title_servers">SERVER</string>
|
||||
<string name="switch_receiving_address">Cambia indirizzo di ricezione</string>
|
||||
<string name="theme_system">Sistema</string>
|
||||
<string name="network_option_tcp_connection_timeout">Scadenza connessione TCP</string>
|
||||
<string name="group_is_decentralized">Il gruppo è completamente decentralizzato: è visibile solo ai membri.</string>
|
||||
<string name="member_role_will_be_changed_with_notification">Il ruolo verrà cambiato in \"%s\". Tutti i membri del gruppo riceveranno una notifica.</string>
|
||||
<string name="member_role_will_be_changed_with_invitation">Il ruolo verrà cambiato in \"%s\". Il membro riceverà un nuovo invito.</string>
|
||||
<string name="incognito_info_find">Per trovare il profilo usato per una connessione in incognito, tocca il nome del contatto o del gruppo in cima alla chat.</string>
|
||||
<string name="update_network_settings_confirmation">Aggiorna</string>
|
||||
<string name="update_network_settings_question">Aggiornare le impostazioni di rete\?</string>
|
||||
<string name="updating_settings_will_reconnect_client_to_all_servers">L\'aggiornamento delle impostazioni riconnetterà il client a tutti i server.</string>
|
||||
<string name="incognito_info_share">Quando condividi un profilo in incognito con qualcuno, questo profilo verrà utilizzato per i gruppi a cui ti invitano.</string>
|
||||
<string name="group_main_profile_sent">Il tuo profilo di chat verrà inviato ai membri del gruppo</string>
|
||||
<string name="incognito_random_profile">Il tuo profilo casuale</string>
|
||||
<string name="message_deletion_prohibited">L\'eliminazione irreversibile dei messaggi è vietata in questa chat.</string>
|
||||
<string name="chat_preferences_no">no</string>
|
||||
<string name="chat_preferences_off">off</string>
|
||||
<string name="feature_off">off</string>
|
||||
<string name="chat_preferences_on">on</string>
|
||||
<string name="only_you_can_delete_messages">Solo tu puoi eliminare irreversibilmente i messaggi (il tuo contatto può contrassegnarli per l\'eliminazione).</string>
|
||||
<string name="only_you_can_send_disappearing">Solo tu puoi inviare messaggi a tempo.</string>
|
||||
<string name="only_your_contact_can_delete">Solo il tuo contatto può eliminare irreversibilmente i messaggi (tu puoi contrassegnarli per l\'eliminazione).</string>
|
||||
<string name="only_your_contact_can_send_disappearing">Solo il tuo contatto può inviare messaggi a tempo.</string>
|
||||
<string name="prohibit_sending_disappearing_messages">Proibisci l\'invio di messaggi a tempo.</string>
|
||||
<string name="prohibit_sending_voice_messages">Proibisci l\'invio di messaggi vocali.</string>
|
||||
<string name="feature_received_prohibited">ricevuto, vietato</string>
|
||||
<string name="reset_color">Ripristina i colori</string>
|
||||
<string name="save_color">Salva colore</string>
|
||||
<string name="accept_feature_set_1_day">Imposta 1 giorno</string>
|
||||
<string name="set_group_preferences">Imposta le preferenze del gruppo</string>
|
||||
<string name="theme">Tema</string>
|
||||
<string name="voice_messages">Messaggi vocali</string>
|
||||
<string name="chat_preferences_yes">sì</string>
|
||||
<string name="chat_preferences_you_allow">Lo consenti</string>
|
||||
<string name="your_preferences">Le tue preferenze</string>
|
||||
<string name="v4_3_improved_server_configuration">Configurazione del server migliorata</string>
|
||||
<string name="v4_3_irreversible_message_deletion">Eliminazione irreversibile del messaggio</string>
|
||||
<string name="message_deletion_prohibited_in_chat">L\'eliminazione irreversibile dei messaggi è vietata in questo gruppo.</string>
|
||||
<string name="v4_3_voice_messages_desc">Max 40 secondi, ricevuto istantaneamente.</string>
|
||||
<string name="new_in_version">Novità nella %s</string>
|
||||
<string name="only_you_can_send_voice">Solo tu puoi inviare messaggi vocali.</string>
|
||||
<string name="only_your_contact_can_send_voice">Solo il tuo contatto può inviare messaggi vocali.</string>
|
||||
<string name="prohibit_message_deletion">Proibisci l\'eliminazione irreversibile dei messaggi.</string>
|
||||
<string name="prohibit_direct_messages">Proibisci l\'invio di messaggi diretti ai membri.</string>
|
||||
<string name="prohibit_sending_disappearing">Proibisci l\'invio di messaggi a tempo.</string>
|
||||
<string name="prohibit_sending_voice">Proibisci l\'invio di messaggi vocali.</string>
|
||||
<string name="v4_2_security_assessment">Valutazione della sicurezza</string>
|
||||
<string name="v4_2_security_assessment_desc">La sicurezza di SimpleX Chat è stata verificata da Trail of Bits.</string>
|
||||
<string name="v4_3_voice_messages">Messaggi vocali</string>
|
||||
<string name="voice_prohibited_in_this_chat">I messaggi vocali sono vietati in questa chat.</string>
|
||||
<string name="voice_messages_are_prohibited">I messaggi vocali sono vietati in questo gruppo.</string>
|
||||
<string name="whats_new">Novità</string>
|
||||
<string name="v4_2_auto_accept_contact_requests_desc">Con messaggio di benvenuto facoltativo.</string>
|
||||
<string name="v4_3_irreversible_message_deletion_desc">I tuoi contatti possono consentire l\'eliminazione completa dei messaggi.</string>
|
||||
<string name="v4_3_improved_privacy_and_security">Privacy e sicurezza migliorate</string>
|
||||
<string name="v4_4_live_messages">Messaggi in diretta</string>
|
||||
<string name="v4_4_live_messages_desc">I destinatari vedono gli aggiornamenti mentre li digiti.</string>
|
||||
<string name="v4_4_disappearing_messages_desc">I messaggi inviati verranno eliminati dopo il tempo impostato.</string>
|
||||
<string name="v4_4_verify_connection_security">Verifica la sicurezza della connessione</string>
|
||||
<string name="chat_item_ttl_none">mai</string>
|
||||
<string name="new_passphrase">Nuova password…</string>
|
||||
<string name="no_received_app_files">Nessun file ricevuto o inviato</string>
|
||||
<string name="notifications_will_be_hidden">Le notifiche verranno mostrate solo fino all\'arresto dell\'app!</string>
|
||||
<string name="enter_correct_current_passphrase">Inserisci la password attuale corretta.</string>
|
||||
<string name="store_passphrase_securely">Conserva la password in modo sicuro, NON potrai cambiarla se la perdi.</string>
|
||||
<string name="remove_passphrase">Rimuovi</string>
|
||||
<string name="remove_passphrase_from_keychain">Rimuovere la password dal Keystore\?</string>
|
||||
<string name="save_passphrase_in_keychain">Salva la password nel Keystore</string>
|
||||
<string name="chat_item_ttl_seconds">%s secondo/i</string>
|
||||
<string name="stop_chat_to_enable_database_actions">Ferma la chat per attivare le azioni del database.</string>
|
||||
<string name="delete_files_and_media_desc">Questa azione non può essere annullata: tutti i file e i media ricevuti e inviati verranno eliminati. Rimarranno le immagini a bassa risoluzione.</string>
|
||||
<string name="enable_automatic_deletion_message">Questa azione non può essere annullata: i messaggi inviati e ricevuti prima di quanto selezionato verranno eliminati. Potrebbe richiedere diversi minuti.</string>
|
||||
<string name="update_database">Aggiorna</string>
|
||||
<string name="update_database_passphrase">Aggiorna la password del database</string>
|
||||
<string name="you_have_to_enter_passphrase_every_time">Devi inserire la password ogni volta che si avvia l\'app: non viene memorizzata sul dispositivo.</string>
|
||||
<string name="you_must_use_the_most_recent_version_of_database">Devi usare la versione più recente del tuo database della chat SOLO su un dispositivo, altrimenti potresti non ricevere più i messaggi da alcuni contatti.</string>
|
||||
<string name="database_is_not_encrypted">Il database della chat non è crittografato: imposta la password per proteggerlo.</string>
|
||||
<string name="icon_descr_cancel_live_message">Annulla messaggio in diretta</string>
|
||||
<string name="feature_offered_item">offerto %s</string>
|
||||
<string name="feature_offered_item_with_param">offerto %s: %2s</string>
|
||||
<string name="feature_cancelled_item">annullato %s</string>
|
||||
</resources>
|
||||
@@ -990,4 +990,7 @@
|
||||
<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>
|
||||
<string name="feature_offered_item">предложил(a) %s</string>
|
||||
<string name="feature_offered_item_with_param">предложил(a) %s: %2s</string>
|
||||
<string name="feature_cancelled_item">отменил(a) %s</string>
|
||||
</resources>
|
||||
@@ -271,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>
|
||||
@@ -478,6 +479,12 @@
|
||||
<string name="network_use_onion_hosts_no_desc_in_alert">Onion hosts will not be used.</string>
|
||||
<string name="network_use_onion_hosts_required_desc_in_alert">Onion hosts will be required for connection.</string>
|
||||
<string name="appearance_settings">Appearance</string>
|
||||
<string name="app_version_title">App version</string>
|
||||
<string name="app_version_name">App version: v%s</string>
|
||||
<string name="app_version_code">App build: %s</string>
|
||||
<string name="core_version">Core version: v%s</string>
|
||||
<string name="core_build_timestamp">Core built at: %s</string>
|
||||
<string name="core_simplexmq_version">simplexmq: v%s (%2s)</string>
|
||||
|
||||
<!-- Address Items - UserAddressView.kt -->
|
||||
<string name="create_address">Create address</string>
|
||||
@@ -951,6 +958,7 @@
|
||||
<string name="network_option_tcp_connection_timeout">TCP connection timeout</string>
|
||||
<string name="network_option_protocol_timeout">Protocol timeout</string>
|
||||
<string name="network_option_ping_interval">PING interval</string>
|
||||
<string name="network_option_ping_count">PING count</string>
|
||||
<string name="network_option_enable_tcp_keep_alive">Enable TCP keep-alive</string>
|
||||
<string name="network_options_revert">Revert</string>
|
||||
<string name="network_options_save">Save</string>
|
||||
@@ -1059,6 +1067,9 @@
|
||||
<string name="ttl_week">%d week</string>
|
||||
<string name="ttl_weeks">%d weeks</string>
|
||||
<string name="ttl_w">%dw</string>
|
||||
<string name="feature_offered_item">offered %s</string>
|
||||
<string name="feature_offered_item_with_param">offered %s: %2s</string>
|
||||
<string name="feature_cancelled_item">cancelled %s</string>
|
||||
|
||||
<!-- WhatsNewView.kt -->
|
||||
<string name="whats_new">What\'s new</string>
|
||||
|
||||
@@ -17,7 +17,7 @@ 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
|
||||
|
||||
|
||||
@@ -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? {
|
||||
|
||||
@@ -851,6 +851,12 @@ func apiGetGroupLink(_ groupId: Int64) throws -> String? {
|
||||
}
|
||||
}
|
||||
|
||||
func apiGetVersion() throws -> CoreVersionInfo {
|
||||
let r = chatSendCmdSync(.showVersion)
|
||||
if case let .versionInfo(info) = r { return info }
|
||||
throw r
|
||||
}
|
||||
|
||||
func initializeChat(start: Bool, dbKey: String? = nil) throws {
|
||||
logger.debug("initializeChat")
|
||||
let m = ChatModel.shared
|
||||
|
||||
@@ -15,6 +15,7 @@ private let memberImageSize: CGFloat = 34
|
||||
struct ChatView: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@State @ObservedObject var chat: Chat
|
||||
@State private var showChatInfoSheet: Bool = false
|
||||
@State private var showAddMembersSheet: Bool = false
|
||||
@@ -70,6 +71,9 @@ struct ChatView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: chatModel.chatId) { _ in
|
||||
if chatModel.chatId == nil { dismiss() }
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button {
|
||||
@@ -442,9 +446,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)
|
||||
@@ -459,10 +467,10 @@ 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())
|
||||
@@ -478,13 +486,15 @@ struct ChatView: View {
|
||||
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 {
|
||||
@@ -512,7 +534,7 @@ struct ComposeView: View {
|
||||
}
|
||||
if case let .editingItem(ci) = composeState.contextItem {
|
||||
sent = await updateMessage(ci, live: live)
|
||||
} else if let liveMessage = liveMessage {
|
||||
} else if let liveMessage = liveMessage, liveMessage.sentMsg != nil {
|
||||
sent = await updateMessage(liveMessage.chatItem, live: live)
|
||||
} else {
|
||||
var quoted: Int64? = nil
|
||||
@@ -609,6 +631,7 @@ struct ComposeView: View {
|
||||
live: live
|
||||
) {
|
||||
await MainActor.run {
|
||||
chatModel.removeLiveDummy(animated: false)
|
||||
chatModel.addChatItem(chat.chatInfo, chatItem)
|
||||
}
|
||||
return chatItem
|
||||
|
||||
@@ -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 {
|
||||
@@ -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 {
|
||||
@@ -271,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,13 @@ struct ChatListView: View {
|
||||
@State private var showAddChat = false
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
NavStackCompat(
|
||||
isActive: Binding(
|
||||
get: { ChatModel.shared.chatId != nil },
|
||||
set: { _ in }
|
||||
),
|
||||
destination: chatView
|
||||
) {
|
||||
VStack {
|
||||
if chatModel.chats.isEmpty {
|
||||
onboardingButtons()
|
||||
@@ -29,7 +35,6 @@ struct ChatListView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationViewStyle(.stack)
|
||||
}
|
||||
|
||||
var chatList: some View {
|
||||
@@ -75,15 +80,6 @@ struct ChatListView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.background(
|
||||
NavigationLink(
|
||||
destination: chatView(),
|
||||
isActive: Binding(
|
||||
get: { chatModel.chatId != nil },
|
||||
set: { _, _ in chatModel.chatId = nil }
|
||||
)
|
||||
) { EmptyView() }
|
||||
)
|
||||
}
|
||||
|
||||
private func onboardingButtons() -> some View {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// NavStackCompat.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by Evgeny on 23/01/2023.
|
||||
// Copyright © 2023 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct NavStackCompat <C: View, D: View>: View {
|
||||
let isActive: Binding<Bool>
|
||||
let destination: () -> D
|
||||
let content: () -> C
|
||||
|
||||
var body: some View {
|
||||
if #available(iOS 16, *) {
|
||||
NavigationStack(path: Binding(
|
||||
get: { isActive.wrappedValue ? [true] : [] },
|
||||
set: { _ in }
|
||||
)) {
|
||||
ZStack {
|
||||
NavigationLink(value: true) { EmptyView() }
|
||||
content()
|
||||
}
|
||||
.navigationDestination(for: Bool.self) { show in
|
||||
if show { destination() }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
NavigationView {
|
||||
ZStack {
|
||||
NavigationLink(
|
||||
destination: destination(),
|
||||
isActive: isActive
|
||||
) { EmptyView() }
|
||||
content()
|
||||
}
|
||||
}
|
||||
.navigationViewStyle(.stack)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,8 @@ struct AdvancedNetworkSettings: View {
|
||||
|
||||
timeoutSettingPicker("TCP connection timeout", selection: $netCfg.tcpConnectTimeout, values: [2_500000, 5_000000, 7_500000, 10_000000, 15_000000, 20_000000], label: secondsLabel)
|
||||
timeoutSettingPicker("Protocol timeout", selection: $netCfg.tcpTimeout, values: [1_500000, 3_000000, 5_000000, 7_000000, 10_000000, 15_000000], label: secondsLabel)
|
||||
timeoutSettingPicker("PING interval", selection: $netCfg.smpPingInterval, values: [120_000000, 300_000000, 600_000000, 1200_000000, 2400_000000], label: secondsLabel)
|
||||
timeoutSettingPicker("PING interval", selection: $netCfg.smpPingInterval, values: [120_000000, 300_000000, 600_000000, 1200_000000, 2400_000000, 3600_000000], label: secondsLabel)
|
||||
intSettingPicker("PING count", selection: $netCfg.smpPingCount, values: [1, 2, 3, 5, 8], label: "")
|
||||
Toggle("Enable TCP keep-alive", isOn: $enableKeepAlive)
|
||||
|
||||
if enableKeepAlive {
|
||||
|
||||
@@ -45,13 +45,11 @@ struct NetworkAndServers: View {
|
||||
}
|
||||
.frame(height: 36)
|
||||
|
||||
if developerTools {
|
||||
NavigationLink {
|
||||
AdvancedNetworkSettings()
|
||||
.navigationTitle("Network settings")
|
||||
} label: {
|
||||
Text("Advanced network settings")
|
||||
}
|
||||
NavigationLink {
|
||||
AdvancedNetworkSettings()
|
||||
.navigationTitle("Network settings")
|
||||
} label: {
|
||||
Text("Advanced network settings")
|
||||
}
|
||||
} header: {
|
||||
Text("Messages")
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -271,7 +271,12 @@ struct SettingsView: View {
|
||||
// } label: {
|
||||
// settingsRow("gauge") { Text("Experimental features") }
|
||||
// }
|
||||
Text("v\(appVersion ?? "?") (\(appBuild ?? "?"))")
|
||||
NavigationLink {
|
||||
VersionView()
|
||||
.navigationBarTitle("App version")
|
||||
} label: {
|
||||
Text("v\(appVersion ?? "?") (\(appBuild ?? "?"))")
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Your settings")
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// VersionView.swift
|
||||
// SimpleXChat
|
||||
//
|
||||
// Created by Evgeny on 22/01/2023.
|
||||
// Copyright © 2023 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct VersionView: View {
|
||||
@State var versionInfo: CoreVersionInfo?
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Text("App version: v\(appVersion ?? "?")")
|
||||
Text("App build: \(appBuild ?? "?")")
|
||||
if let info = versionInfo {
|
||||
Text("Core version: v\(info.version)")
|
||||
Text("Core built at: \(info.buildTimestamp)")
|
||||
if let v = try? AttributedString(markdown: "simplexmq: v\(info.simplexmqVersion) ([\(info.simplexmqCommit.prefix(7))](https://github.com/simplex-chat/simplexmq/commit/\(info.simplexmqCommit)))") {
|
||||
Text(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
||||
.padding()
|
||||
.onAppear {
|
||||
do {
|
||||
versionInfo = try apiGetVersion()
|
||||
} catch let error {
|
||||
logger.error("apiGetVersion error: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct VersionView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VersionView()
|
||||
}
|
||||
}
|
||||
@@ -69,12 +69,12 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ is not verified" xml:space="preserve">
|
||||
<source>%@ is not verified</source>
|
||||
<target>%@ wurde nicht überprüft</target>
|
||||
<target>%@ wurde noch 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>
|
||||
<target>%@ wurde erfolgreich überprüft</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ wants to connect!" xml:space="preserve">
|
||||
@@ -144,12 +144,12 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="%lldd" xml:space="preserve">
|
||||
<source>%lldd</source>
|
||||
<target>%lldd</target>
|
||||
<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>
|
||||
<target>%lldH</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lldk" xml:space="preserve">
|
||||
@@ -159,22 +159,22 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="%lldm" xml:space="preserve">
|
||||
<source>%lldm</source>
|
||||
<target>%lldm</target>
|
||||
<target>%lldmin</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lldmth" xml:space="preserve">
|
||||
<source>%lldmth</source>
|
||||
<target>%lldmth</target>
|
||||
<target>%lldMon</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%llds" xml:space="preserve">
|
||||
<source>%llds</source>
|
||||
<target>%llds</target>
|
||||
<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>
|
||||
<target>%lldW</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="(" xml:space="preserve">
|
||||
@@ -204,7 +204,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." xml:space="preserve">
|
||||
<source>**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app).</source>
|
||||
<target>**Beste Privatsphäre**: Es wird kein SimpleX-Chat-Benachrichtigungs-Server genutzt, Nachrichten werden in regelmäßigen Abständen im Hintergrund geprüft (dies hängt davon ab, wie häufig Sie die App nutzen).</target>
|
||||
<target>**Beste Privatsphäre**: Es wird kein SimpleX-Chat-Benachrichtigungs-Server genutzt, Nachrichten werden in periodischen Abständen im Hintergrund geprüft (dies hängt davon ab, wie häufig Sie die App nutzen).</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Paste received link** or open it in the browser and tap **Open in mobile app**." xml:space="preserve">
|
||||
@@ -214,7 +214,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="**Please note**: you will NOT be able to recover or change passphrase if you lose it." xml:space="preserve">
|
||||
<source>**Please note**: you will NOT be able to recover or change passphrase if you lose it.</source>
|
||||
<target>**Bitte beachten Sie**: Sie können das Passwort NICHT wiederherstellen oder ändern, wenn Sie es vergessen haben oder verlieren.</target>
|
||||
<target>**Bitte beachten Sie**: Das Passwort kann NICHT wiederhergestellt oder geändert werden, wenn Sie es vergessen haben oder verlieren.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." xml:space="preserve">
|
||||
@@ -299,12 +299,12 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="A random profile will be sent to the contact that you received this link from" xml:space="preserve">
|
||||
<source>A random profile will be sent to the contact that you received this link from</source>
|
||||
<target>Ein zufälliges Profil wird an den Kontakt gesendet, von dem Sie diesen Link erhalten haben.</target>
|
||||
<target>Ein zufälliges Profil wird an den Kontakt gesendet, von dem Sie diesen Link erhalten haben</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="A random profile will be sent to your contact" xml:space="preserve">
|
||||
<source>A random profile will be sent to your contact</source>
|
||||
<target>Ein zufälliges Profil wird an Ihren Kontakt gesendet.</target>
|
||||
<target>Ein zufälliges Profil wird an Ihren Kontakt gesendet</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="About SimpleX" xml:space="preserve">
|
||||
@@ -390,7 +390,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="All your contacts will remain connected" xml:space="preserve">
|
||||
<source>All your contacts will remain connected</source>
|
||||
<target>Alle Ihre Kontakte bleiben verbunden.</target>
|
||||
<target>Alle Ihre Kontakte bleiben verbunden</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow" xml:space="preserve">
|
||||
@@ -405,12 +405,12 @@
|
||||
</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">
|
||||
@@ -420,12 +420,12 @@
|
||||
</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">
|
||||
@@ -463,11 +463,23 @@
|
||||
<target>Anruf annehmen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App build: %@" xml:space="preserve">
|
||||
<source>App build: %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App icon" xml:space="preserve">
|
||||
<source>App icon</source>
|
||||
<target>App Icon</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App version" xml:space="preserve">
|
||||
<source>App version</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App version: v%@" xml:space="preserve">
|
||||
<source>App version: v%@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Appearance" xml:space="preserve">
|
||||
<source>Appearance</source>
|
||||
<target>Design</target>
|
||||
@@ -555,7 +567,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Cannot access keychain to save database password" xml:space="preserve">
|
||||
<source>Cannot access keychain to save database password</source>
|
||||
<target>Die App kann nicht auf den Schlüsselbund zugreifen, um das Datenbank-Passwort zu speichern.</target>
|
||||
<target>Die App kann nicht auf den Schlüsselbund zugreifen, um das Datenbank-Passwort zu speichern</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Cannot receive file" xml:space="preserve">
|
||||
@@ -670,7 +682,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Clear verification" xml:space="preserve">
|
||||
<source>Clear verification</source>
|
||||
<target>Überprüfung erfolgt</target>
|
||||
<target>Überprüfung zurücknehmen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Colors" xml:space="preserve">
|
||||
@@ -828,6 +840,14 @@
|
||||
<target>Kopieren</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Core built at: %@" xml:space="preserve">
|
||||
<source>Core built at: %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Core version: v%@" xml:space="preserve">
|
||||
<source>Core version: v%@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create" xml:space="preserve">
|
||||
<source>Create</source>
|
||||
<target>Erstellen</target>
|
||||
@@ -1158,7 +1178,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">
|
||||
@@ -1168,7 +1188,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Disappearing messages" xml:space="preserve">
|
||||
<source>Disappearing messages</source>
|
||||
<target>Verschwindende Nachrichten</target>
|
||||
<target>verschwindende Nachrichten</target>
|
||||
<note>chat feature</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disappearing messages are prohibited in this chat." xml:space="preserve">
|
||||
@@ -1248,7 +1268,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Enable periodic notifications?" xml:space="preserve">
|
||||
<source>Enable periodic notifications?</source>
|
||||
<target>Regelmäßige Benachrichtigungen aktivieren?</target>
|
||||
<target>Periodische Benachrichtigungen aktivieren?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Encrypt" xml:space="preserve">
|
||||
@@ -1433,7 +1453,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">
|
||||
@@ -1613,7 +1633,7 @@
|
||||
</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">
|
||||
@@ -1633,7 +1653,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">
|
||||
@@ -1703,12 +1723,12 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="If you can't meet in person, **show QR code in the video call**, or share the link." xml:space="preserve">
|
||||
<source>If you can't meet in person, **show QR code in the video call**, or share the link.</source>
|
||||
<target>Wenn Sie sich nicht persönlich treffen können, können Sie den **QR-Code während eines Videoanrufs anzeigen** oder den Einladungslink über einen anderen Kanal mit Ihrem Kontakt teilen.</target>
|
||||
<target>Wenn Sie sich nicht persönlich treffen können, kann der **QR-Code während eines Videoanrufs angezeigt werden**, oder der Einladungslink über einen anderen Kanal mit Ihrem Kontakt geteilt werden.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link." xml:space="preserve">
|
||||
<source>If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link.</source>
|
||||
<target>Wenn Sie sich nicht persönlich treffen können, können Sie den **QR-Code während eines Videoanrufs scannen** oder Ihr Kontakt kann den Einladungslink über einen anderen Kanal mit Ihnen teilen.</target>
|
||||
<target>Wenn Sie sich nicht persönlich treffen können, kann der **QR-Code während eines Videoanrufs gescannt werden**, oder Ihr Kontakt kann den Einladungslink über einen anderen Kanal mit Ihnen teilen.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="If you need to use the chat now tap **Do it later** below (you will be offered to migrate the database when you restart the app)." xml:space="preserve">
|
||||
@@ -1840,17 +1860,17 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Irreversible message deletion" xml:space="preserve">
|
||||
<source>Irreversible message deletion</source>
|
||||
<target>Unwiederbringliches Löschen einer Nachricht</target>
|
||||
<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">
|
||||
@@ -1965,7 +1985,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">
|
||||
@@ -2230,7 +2250,7 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v
|
||||
</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">
|
||||
@@ -2245,7 +2265,7 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v
|
||||
</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">
|
||||
@@ -2273,6 +2293,10 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v
|
||||
<target>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.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="PING count" xml:space="preserve">
|
||||
<source>PING count</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="PING interval" xml:space="preserve">
|
||||
<source>PING interval</source>
|
||||
<target>PING-Intervall</target>
|
||||
@@ -2305,7 +2329,7 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v
|
||||
</trans-unit>
|
||||
<trans-unit id="Periodically" xml:space="preserve">
|
||||
<source>Periodically</source>
|
||||
<target>Regelmäßig</target>
|
||||
<target>Periodisch</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please ask your contact to enable sending voice messages." xml:space="preserve">
|
||||
@@ -2335,7 +2359,7 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v
|
||||
</trans-unit>
|
||||
<trans-unit id="Please enter the previous password after restoring database backup. This action can not be undone." xml:space="preserve">
|
||||
<source>Please enter the previous password after restoring database backup. This action can not be undone.</source>
|
||||
<target>Bitte geben Sie das vorherige Passwort ein, nachdem Sie die Datenbanksicherung wiederhergestellt haben. Diese Aktion kann nicht rückgängig gemacht werden!</target>
|
||||
<target>Bitte geben Sie das vorherige Passwort ein, nachdem Sie die Datenbanksicherung wiederhergestellt haben. Diese Aktion kann nicht rückgängig gemacht werden.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please restart the app and migrate the database to enable push notifications." xml:space="preserve">
|
||||
@@ -2385,12 +2409,12 @@ 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">
|
||||
@@ -2400,7 +2424,7 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v
|
||||
</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">
|
||||
@@ -2525,7 +2549,7 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v
|
||||
</trans-unit>
|
||||
<trans-unit id="Restart the app to create a new chat profile" xml:space="preserve">
|
||||
<source>Restart the app to create a new chat profile</source>
|
||||
<target>Um ein neues Chat-Profil zu erstellen, starten Sie die App neu.</target>
|
||||
<target>Um ein neues Chat-Profil zu erstellen, starten Sie die App neu</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Restart the app to use imported chat database" xml:space="preserve">
|
||||
@@ -2625,7 +2649,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">
|
||||
@@ -2745,7 +2769,7 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v
|
||||
</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">
|
||||
@@ -2995,7 +3019,7 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v
|
||||
</trans-unit>
|
||||
<trans-unit id="The 1st platform without any user identifiers – private by design." xml:space="preserve">
|
||||
<source>The 1st platform without any user identifiers – private by design.</source>
|
||||
<target>Die erste Plattform ohne Benutzerkennungen – Privat per Design</target>
|
||||
<target>Die erste Plattform ohne Benutzerkennungen – Privat per Design.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The app can notify you when you receive messages or contact requests - please open settings to enable." xml:space="preserve">
|
||||
@@ -3259,7 +3283,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">
|
||||
@@ -3304,17 +3328,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">
|
||||
@@ -3434,7 +3458,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
|
||||
</trans-unit>
|
||||
<trans-unit id="You can start chat via app Settings / Database or by restarting the app" xml:space="preserve">
|
||||
<source>You can start chat via app Settings / Database or by restarting the app</source>
|
||||
<target>Sie können den Chat über die App-Einstellungen / Datenbank oder durch Neustart der App starten.</target>
|
||||
<target>Sie können den Chat über die App-Einstellungen / Datenbank oder durch Neustart der App starten</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can use markdown to format messages:" xml:space="preserve">
|
||||
@@ -3529,12 +3553,12 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
|
||||
</trans-unit>
|
||||
<trans-unit id="You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile" xml:space="preserve">
|
||||
<source>You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile</source>
|
||||
<target>Sie versuchen, einen Kontakt, mit dem Sie ein Inkognito-Profil geteilt haben, in die Gruppe einzuladen, in der Sie Ihr Hauptprofil verwenden.</target>
|
||||
<target>Sie versuchen, einen Kontakt, mit dem Sie ein Inkognito-Profil geteilt haben, in die Gruppe einzuladen, in der Sie Ihr Hauptprofil verwenden</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" xml:space="preserve">
|
||||
<source>You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed</source>
|
||||
<target>Sie verwenden ein Inkognito-Profil für diese Gruppe. Um zu verhindern, dass Sie Ihr Hauptprofil teilen, ist in diesem Fall das Einladen von Kontakten nicht erlaubt.</target>
|
||||
<target>Sie verwenden ein Inkognito-Profil für diese Gruppe. Um zu verhindern, dass Sie Ihr Hauptprofil teilen, ist in diesem Fall das Einladen von Kontakten nicht erlaubt</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your ICE servers" xml:space="preserve">
|
||||
@@ -3559,7 +3583,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
|
||||
</trans-unit>
|
||||
<trans-unit id="Your chat database" xml:space="preserve">
|
||||
<source>Your chat database</source>
|
||||
<target>Meine Chat-Datenbank</target>
|
||||
<target>Chat-Datenbank</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your chat database is not encrypted - set passphrase to encrypt it." xml:space="preserve">
|
||||
@@ -3621,7 +3645,7 @@ Sie können diese Verbindung abbrechen und den Kontakt entfernen (und es später
|
||||
</trans-unit>
|
||||
<trans-unit id="Your preferences" xml:space="preserve">
|
||||
<source>Your preferences</source>
|
||||
<target>Ihre Präferenzen</target>
|
||||
<target>Meine Präferenzen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your privacy" xml:space="preserve">
|
||||
@@ -3638,7 +3662,7 @@ SimpleX-Server können Ihr Profil nicht einsehen.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your profile will be sent to the contact that you received this link from" xml:space="preserve">
|
||||
<source>Your profile will be sent to the contact that you received this link from</source>
|
||||
<target>Ihr Profil wird an den Kontakt gesendet, von dem Sie diesen Link erhalten haben.</target>
|
||||
<target>Ihr Profil wird an den Kontakt gesendet, von dem Sie diesen Link erhalten haben</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your profile, contacts and delivered messages are stored on your device." xml:space="preserve">
|
||||
@@ -3746,6 +3770,11 @@ 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>
|
||||
<target>Beende %@</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>wechselte die Adresse für Sie</target>
|
||||
@@ -3978,7 +4007,7 @@ SimpleX-Server können Ihr Profil nicht einsehen.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="invited %@" xml:space="preserve">
|
||||
<source>invited %@</source>
|
||||
<target>hat %@ eingeladen.</target>
|
||||
<target>hat %@ eingeladen</target>
|
||||
<note>rcv group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="invited to connect" xml:space="preserve">
|
||||
@@ -4057,6 +4086,16 @@ 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>
|
||||
<target>Beginne %@</target>
|
||||
<note>feature offered item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="offered %@: %@" xml:space="preserve">
|
||||
<source>offered %1$@: %2$@</source>
|
||||
<target>Beginne %1$@: %2$@</target>
|
||||
<note>feature offered item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="on" xml:space="preserve">
|
||||
<source>on</source>
|
||||
<target>Ein</target>
|
||||
|
||||
@@ -463,11 +463,26 @@
|
||||
<target>Answer call</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App build: %@" xml:space="preserve">
|
||||
<source>App build: %@</source>
|
||||
<target>App build: %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App icon" xml:space="preserve">
|
||||
<source>App icon</source>
|
||||
<target>App icon</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App version" xml:space="preserve">
|
||||
<source>App version</source>
|
||||
<target>App version</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App version: v%@" xml:space="preserve">
|
||||
<source>App version: v%@</source>
|
||||
<target>App version: v%@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Appearance" xml:space="preserve">
|
||||
<source>Appearance</source>
|
||||
<target>Appearance</target>
|
||||
@@ -828,6 +843,16 @@
|
||||
<target>Copy</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Core built at: %@" xml:space="preserve">
|
||||
<source>Core built at: %@</source>
|
||||
<target>Core built at: %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Core version: v%@" xml:space="preserve">
|
||||
<source>Core version: v%@</source>
|
||||
<target>Core version: v%@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create" xml:space="preserve">
|
||||
<source>Create</source>
|
||||
<target>Create</target>
|
||||
@@ -2273,6 +2298,11 @@ We will be adding server redundancy to prevent lost messages.</target>
|
||||
<target>Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="PING count" xml:space="preserve">
|
||||
<source>PING count</source>
|
||||
<target>PING count</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="PING interval" xml:space="preserve">
|
||||
<source>PING interval</source>
|
||||
<target>PING interval</target>
|
||||
@@ -3746,6 +3776,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>
|
||||
@@ -4057,6 +4092,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>
|
||||
|
||||
@@ -463,11 +463,23 @@
|
||||
<target>Répondre à l'appel</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App build: %@" xml:space="preserve">
|
||||
<source>App build: %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App icon" xml:space="preserve">
|
||||
<source>App icon</source>
|
||||
<target>Icône de l'app</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App version" xml:space="preserve">
|
||||
<source>App version</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App version: v%@" xml:space="preserve">
|
||||
<source>App version: v%@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Appearance" xml:space="preserve">
|
||||
<source>Appearance</source>
|
||||
<target>Apparence</target>
|
||||
@@ -828,6 +840,14 @@
|
||||
<target>Copier</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Core built at: %@" xml:space="preserve">
|
||||
<source>Core built at: %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Core version: v%@" xml:space="preserve">
|
||||
<source>Core version: v%@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create" xml:space="preserve">
|
||||
<source>Create</source>
|
||||
<target>Créer</target>
|
||||
@@ -1810,7 +1830,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Instantly" xml:space="preserve">
|
||||
<source>Instantly</source>
|
||||
<target>Instantanément</target>
|
||||
<target>Instantané</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid connection link" xml:space="preserve">
|
||||
@@ -1970,7 +1990,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">
|
||||
@@ -2100,7 +2120,7 @@ Nous allons ajouter une redondance des serveurs pour éviter la perte de message
|
||||
</trans-unit>
|
||||
<trans-unit id="New in %@" xml:space="preserve">
|
||||
<source>New in %@</source>
|
||||
<target>Nouveau en %@</target>
|
||||
<target>Nouveautés de la %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="New member role" xml:space="preserve">
|
||||
@@ -2273,6 +2293,10 @@ Nous allons ajouter une redondance des serveurs pour éviter la perte de message
|
||||
<target>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.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="PING count" xml:space="preserve">
|
||||
<source>PING count</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="PING interval" xml:space="preserve">
|
||||
<source>PING interval</source>
|
||||
<target>Intervalle de PING</target>
|
||||
@@ -2300,12 +2324,12 @@ Nous allons ajouter une redondance des serveurs pour éviter la perte de message
|
||||
</trans-unit>
|
||||
<trans-unit id="People can connect to you only via the links you share." xml:space="preserve">
|
||||
<source>People can connect to you only via the links you share.</source>
|
||||
<target>Les gens peuvent se connecter à vous uniquement via les liens que vous partagez.</target>
|
||||
<target>On ne peut se connecter à vous qu’avec les liens que vous partagez.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Periodically" xml:space="preserve">
|
||||
<source>Periodically</source>
|
||||
<target>Périodiquement</target>
|
||||
<target>Périodique</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please ask your contact to enable sending voice messages." xml:space="preserve">
|
||||
@@ -2730,7 +2754,7 @@ Nous allons ajouter une redondance des serveurs pour éviter la perte de message
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending via" xml:space="preserve">
|
||||
<source>Sending via</source>
|
||||
<target>Envoyé via</target>
|
||||
<target>Envoi via</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sent file event" xml:space="preserve">
|
||||
@@ -2805,7 +2829,7 @@ Nous allons ajouter une redondance des serveurs pour éviter la perte de message
|
||||
</trans-unit>
|
||||
<trans-unit id="Share one-time invitation link" xml:space="preserve">
|
||||
<source>Share one-time invitation link</source>
|
||||
<target>Partager le lien d'invitation unique</target>
|
||||
<target>Partager un lien d'invitation unique</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Show QR code" xml:space="preserve">
|
||||
@@ -3085,7 +3109,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">
|
||||
@@ -3105,7 +3129,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">
|
||||
@@ -3444,7 +3468,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">
|
||||
@@ -3746,6 +3770,11 @@ 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>
|
||||
<target>annulé %@</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>adresse modifiée pour vous</target>
|
||||
@@ -4057,6 +4086,16 @@ 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>
|
||||
<target>offert %@</target>
|
||||
<note>feature offered item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="offered %@: %@" xml:space="preserve">
|
||||
<source>offered %1$@: %2$@</source>
|
||||
<target>offert %1$@ : %2$@</target>
|
||||
<note>feature offered item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="on" xml:space="preserve">
|
||||
<source>on</source>
|
||||
<target>on</target>
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"locale" : "it"
|
||||
}
|
||||
],
|
||||
"properties" : {
|
||||
"localizable" : true
|
||||
},
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"red" : "0.000",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "1.000",
|
||||
"green" : "0.533"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"properties" : {
|
||||
"localizable" : true
|
||||
},
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
/* Bundle display name */
|
||||
"CFBundleDisplayName" = "SimpleX NSE";
|
||||
/* Bundle name */
|
||||
"CFBundleName" = "SimpleX NSE";
|
||||
/* Copyright (human-readable) */
|
||||
"NSHumanReadableCopyright" = "Copyright © 2022 SimpleX Chat. All rights reserved.";
|
||||
+1
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/* No comment provided by engineer. */
|
||||
"_italic_" = "\\_italic_";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"*bold*" = "\\*bold*";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"`a + b`" = "\\`a + b`";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"~strike~" = "\\~strike~";
|
||||
|
||||
/* call status */
|
||||
"connecting call" = "connecting call…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connecting server…" = "Connecting to server…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connecting server… (error: %@)" = "Connecting to server… (error: %@)";
|
||||
|
||||
/* rcv group event chat item */
|
||||
"member connected" = "connected";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No group!" = "Group not found!";
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/* Bundle name */
|
||||
"CFBundleName" = "SimpleX";
|
||||
/* Privacy - Camera Usage Description */
|
||||
"NSCameraUsageDescription" = "SimpleX needs camera access to scan QR codes to connect to other users and for video calls.";
|
||||
/* Privacy - Face ID Usage Description */
|
||||
"NSFaceIDUsageDescription" = "SimpleX uses Face ID for local authentication";
|
||||
/* Privacy - Microphone Usage Description */
|
||||
"NSMicrophoneUsageDescription" = "SimpleX needs microphone access for audio and video calls, and to record voice messages.";
|
||||
/* Privacy - Photo Library Additions Usage Description */
|
||||
"NSPhotoLibraryAddUsageDescription" = "SimpleX needs access to Photo Library for saving captured and received media";
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"developmentRegion" : "en",
|
||||
"project" : "SimpleX.xcodeproj",
|
||||
"targetLocale" : "it",
|
||||
"toolInfo" : {
|
||||
"toolBuildNumber" : "14A309",
|
||||
"toolID" : "com.apple.dt.xcode",
|
||||
"toolName" : "Xcode",
|
||||
"toolVersion" : "14.0"
|
||||
},
|
||||
"version" : "1.0"
|
||||
}
|
||||
@@ -463,11 +463,23 @@
|
||||
<target>Принять звонок</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App build: %@" xml:space="preserve">
|
||||
<source>App build: %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App icon" xml:space="preserve">
|
||||
<source>App icon</source>
|
||||
<target>Иконка</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App version" xml:space="preserve">
|
||||
<source>App version</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App version: v%@" xml:space="preserve">
|
||||
<source>App version: v%@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Appearance" xml:space="preserve">
|
||||
<source>Appearance</source>
|
||||
<target>Интерфейс</target>
|
||||
@@ -828,6 +840,14 @@
|
||||
<target>Скопировать</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Core built at: %@" xml:space="preserve">
|
||||
<source>Core built at: %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Core version: v%@" xml:space="preserve">
|
||||
<source>Core version: v%@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create" xml:space="preserve">
|
||||
<source>Create</source>
|
||||
<target>Создать</target>
|
||||
@@ -1198,7 +1218,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Do NOT use SimpleX for emergency calls." xml:space="preserve">
|
||||
<source>Do NOT use SimpleX for emergency calls.</source>
|
||||
<target>Не используйте SimpleX для экстренных звонков</target>
|
||||
<target>Не используйте SimpleX для экстренных звонков.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Do it later" xml:space="preserve">
|
||||
@@ -2205,7 +2225,7 @@ We will be adding server redundancy to prevent lost messages.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." xml:space="preserve">
|
||||
<source>Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**.</source>
|
||||
<target>Только пользовательские устройства хранят контакты, группы и сообщения, которые отправляются **с двухуровневым end-to-end шифрованием**</target>
|
||||
<target>Только пользовательские устройства хранят контакты, группы и сообщения, которые отправляются **с двухуровневым end-to-end шифрованием**.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Only group owners can change group preferences." xml:space="preserve">
|
||||
@@ -2273,6 +2293,10 @@ 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="PING count" xml:space="preserve">
|
||||
<source>PING count</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="PING interval" xml:space="preserve">
|
||||
<source>PING interval</source>
|
||||
<target>Интервал PING</target>
|
||||
@@ -2950,7 +2974,7 @@ We will be adding server redundancy to prevent lost messages.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap button " xml:space="preserve">
|
||||
<source>Tap button </source>
|
||||
<target>Нажмите кнопку</target>
|
||||
<target>Нажмите кнопку </target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap to join" xml:space="preserve">
|
||||
@@ -3594,7 +3618,7 @@ To connect, please ask your contact to create another connection link and check
|
||||
</trans-unit>
|
||||
<trans-unit id="Your contact can scan it from the app." xml:space="preserve">
|
||||
<source>Your contact can scan it from the app.</source>
|
||||
<target>Ваш контакт может сосканировать QR код в приложении</target>
|
||||
<target>Ваш контакт может сосканировать QR код в приложении.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your contact needs to be online for the connection to complete. You can cancel this connection and remove the contact (and try later with a new link)." xml:space="preserve">
|
||||
@@ -3698,7 +3722,7 @@ SimpleX серверы не могут получить доступ к ваше
|
||||
</trans-unit>
|
||||
<trans-unit id="accepted call" xml:space="preserve">
|
||||
<source>accepted call</source>
|
||||
<target> принятый звонок</target>
|
||||
<target>принятый звонок</target>
|
||||
<note>call status</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="admin" xml:space="preserve">
|
||||
@@ -3746,6 +3770,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>
|
||||
@@ -4057,6 +4086,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>
|
||||
|
||||
@@ -154,6 +154,7 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
}
|
||||
|
||||
var chatStarted = false
|
||||
var networkConfig: NetCfg = getNetCfg()
|
||||
|
||||
func startChat() -> DBMigrationResult? {
|
||||
hs_init(0, nil)
|
||||
@@ -166,7 +167,7 @@ func startChat() -> DBMigrationResult? {
|
||||
if let user = apiGetActiveUser() {
|
||||
logger.debug("active user \(String(describing: user))")
|
||||
do {
|
||||
try setNetworkConfig(getNetCfg())
|
||||
try setNetworkConfig(networkConfig)
|
||||
let justStarted = try apiStartChat()
|
||||
chatStarted = true
|
||||
if justStarted {
|
||||
@@ -188,6 +189,7 @@ func startChat() -> DBMigrationResult? {
|
||||
func receiveMessages() async {
|
||||
logger.debug("NotificationService receiveMessages")
|
||||
while true {
|
||||
updateNetCfg()
|
||||
if let msg = await chatRecvMsg() {
|
||||
if let (id, ntf) = await receivedMsgNtf(msg) {
|
||||
await PendingNtfs.shared.createStream(id)
|
||||
@@ -241,6 +243,19 @@ func receivedMsgNtf(_ res: ChatResponse) async -> (String, UNMutableNotification
|
||||
}
|
||||
}
|
||||
|
||||
func updateNetCfg() {
|
||||
let newNetConfig = getNetCfg()
|
||||
if newNetConfig != networkConfig {
|
||||
logger.debug("NotificationService applying changed network config")
|
||||
do {
|
||||
try setNetworkConfig(networkConfig)
|
||||
networkConfig = newNetConfig
|
||||
} catch {
|
||||
logger.error("NotificationService apply changed network config error: \(responseError(error), privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func apiGetActiveUser() -> User? {
|
||||
let r = sendSimpleXCmd(.showActiveUser)
|
||||
logger.debug("apiGetActiveUser sendSimpleXCmd responce: \(String(describing: r))")
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
/* Bundle display name */
|
||||
"CFBundleDisplayName" = "SimpleX NSE";
|
||||
|
||||
/* Bundle name */
|
||||
"CFBundleName" = "SimpleX NSE";
|
||||
|
||||
/* Copyright (human-readable) */
|
||||
"NSHumanReadableCopyright" = "Copyright © 2022 SimpleX Chat. Tutti i diritti riservati.";
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -48,14 +48,15 @@
|
||||
5C5E5D3B2824468B00B0488A /* ActiveCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5E5D3A2824468B00B0488A /* ActiveCallView.swift */; };
|
||||
5C5F2B6D27EBC3FE006A9D5F /* ImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5F2B6C27EBC3FE006A9D5F /* ImagePicker.swift */; };
|
||||
5C5F2B7027EBC704006A9D5F /* ProfileImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5F2B6F27EBC704006A9D5F /* ProfileImage.swift */; };
|
||||
5C65F33C297D3D9700B67AF3 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C65F337297D3D9700B67AF3 /* libgmpxx.a */; };
|
||||
5C65F33D297D3D9700B67AF3 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C65F338297D3D9700B67AF3 /* libgmp.a */; };
|
||||
5C65F33E297D3D9700B67AF3 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C65F339297D3D9700B67AF3 /* libffi.a */; };
|
||||
5C65F33F297D3D9700B67AF3 /* libHSsimplex-chat-4.4.4-696z0wvJHN7Hvtog9MKb6r-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C65F33A297D3D9700B67AF3 /* libHSsimplex-chat-4.4.4-696z0wvJHN7Hvtog9MKb6r-ghc8.10.7.a */; };
|
||||
5C65F340297D3D9700B67AF3 /* libHSsimplex-chat-4.4.4-696z0wvJHN7Hvtog9MKb6r.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C65F33B297D3D9700B67AF3 /* libHSsimplex-chat-4.4.4-696z0wvJHN7Hvtog9MKb6r.a */; };
|
||||
5C65F343297D45E100B67AF3 /* VersionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C65F341297D3F3600B67AF3 /* VersionView.swift */; };
|
||||
5C6AD81327A834E300348BD7 /* NewChatButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6AD81227A834E300348BD7 /* NewChatButton.swift */; };
|
||||
5C6BA667289BD954009B8ECC /* DismissSheets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6BA666289BD954009B8ECC /* DismissSheets.swift */; };
|
||||
5C7031162953C97F00150A12 /* CIFeaturePreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7031152953C97F00150A12 /* CIFeaturePreferenceView.swift */; };
|
||||
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 */; };
|
||||
@@ -109,6 +110,7 @@
|
||||
5CC2C0FC2809BF11000C35E3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CC2C0FA2809BF11000C35E3 /* Localizable.strings */; };
|
||||
5CC2C0FF2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CC2C0FD2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings */; };
|
||||
5CCA7DF32905735700C8FEBA /* AcceptRequestsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCA7DF22905735700C8FEBA /* AcceptRequestsView.swift */; };
|
||||
5CCB939C297EFCB100399E78 /* NavStackCompat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCB939B297EFCB100399E78 /* NavStackCompat.swift */; };
|
||||
5CCD403427A5F6DF00368C90 /* AddContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403327A5F6DF00368C90 /* AddContactView.swift */; };
|
||||
5CCD403727A5F9A200368C90 /* ScanToConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */; };
|
||||
5CDCAD482818589900503DA2 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CDCAD472818589900503DA2 /* NotificationService.swift */; };
|
||||
@@ -265,14 +267,15 @@
|
||||
5C5E5D3C282447AB00B0488A /* CallTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallTypes.swift; sourceTree = "<group>"; };
|
||||
5C5F2B6C27EBC3FE006A9D5F /* ImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePicker.swift; sourceTree = "<group>"; };
|
||||
5C5F2B6F27EBC704006A9D5F /* ProfileImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileImage.swift; sourceTree = "<group>"; };
|
||||
5C65F337297D3D9700B67AF3 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5C65F338297D3D9700B67AF3 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5C65F339297D3D9700B67AF3 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5C65F33A297D3D9700B67AF3 /* libHSsimplex-chat-4.4.4-696z0wvJHN7Hvtog9MKb6r-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-4.4.4-696z0wvJHN7Hvtog9MKb6r-ghc8.10.7.a"; sourceTree = "<group>"; };
|
||||
5C65F33B297D3D9700B67AF3 /* libHSsimplex-chat-4.4.4-696z0wvJHN7Hvtog9MKb6r.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-4.4.4-696z0wvJHN7Hvtog9MKb6r.a"; sourceTree = "<group>"; };
|
||||
5C65F341297D3F3600B67AF3 /* VersionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionView.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
@@ -301,6 +304,10 @@
|
||||
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>"; };
|
||||
5CA85D0A297218AA0095AF72 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
5CA85D0B297218AA0095AF72 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
5CA85D0C297219EF0095AF72 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = "it.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
5CA85D0D297219EF0095AF72 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; 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>"; };
|
||||
@@ -335,6 +342,7 @@
|
||||
5CC2C0FB2809BF11000C35E3 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
5CC2C0FE2809BF11000C35E3 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = "ru.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
5CCA7DF22905735700C8FEBA /* AcceptRequestsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcceptRequestsView.swift; sourceTree = "<group>"; };
|
||||
5CCB939B297EFCB100399E78 /* NavStackCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavStackCompat.swift; sourceTree = "<group>"; };
|
||||
5CCD403327A5F6DF00368C90 /* AddContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactView.swift; sourceTree = "<group>"; };
|
||||
5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanToConnectView.swift; sourceTree = "<group>"; };
|
||||
5CDCAD452818589900503DA2 /* SimpleX NSE.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "SimpleX NSE.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -420,13 +428,13 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5C70311F2955080A00150A12 /* libHSsimplex-chat-4.4.0-AHPp9UIBWT5C2IlT3cD6QO.a in Frameworks */,
|
||||
5C65F33C297D3D9700B67AF3 /* libgmpxx.a in Frameworks */,
|
||||
5C65F340297D3D9700B67AF3 /* libHSsimplex-chat-4.4.4-696z0wvJHN7Hvtog9MKb6r.a in Frameworks */,
|
||||
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
||||
5C65F33D297D3D9700B67AF3 /* libgmp.a in Frameworks */,
|
||||
5C65F33F297D3D9700B67AF3 /* libHSsimplex-chat-4.4.4-696z0wvJHN7Hvtog9MKb6r-ghc8.10.7.a in Frameworks */,
|
||||
5C65F33E297D3D9700B67AF3 /* libffi.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 */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -484,11 +492,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 */,
|
||||
5C65F339297D3D9700B67AF3 /* libffi.a */,
|
||||
5C65F338297D3D9700B67AF3 /* libgmp.a */,
|
||||
5C65F337297D3D9700B67AF3 /* libgmpxx.a */,
|
||||
5C65F33A297D3D9700B67AF3 /* libHSsimplex-chat-4.4.4-696z0wvJHN7Hvtog9MKb6r-ghc8.10.7.a */,
|
||||
5C65F33B297D3D9700B67AF3 /* libHSsimplex-chat-4.4.4-696z0wvJHN7Hvtog9MKb6r.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
@@ -532,6 +540,7 @@
|
||||
5C6BA666289BD954009B8ECC /* DismissSheets.swift */,
|
||||
5C00164328A26FBC0094D739 /* ContextMenu.swift */,
|
||||
5CA7DFC229302AF000F7FDDE /* AppSheet.swift */,
|
||||
5CCB939B297EFCB100399E78 /* NavStackCompat.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
@@ -638,6 +647,7 @@
|
||||
5CB2084E28DA4B4800D024EC /* RTCServers.swift */,
|
||||
5C3F1D592844B4DE00EC8A82 /* ExperimentalFeaturesView.swift */,
|
||||
64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */,
|
||||
5C65F341297D3F3600B67AF3 /* VersionView.swift */,
|
||||
);
|
||||
path = UserSettings;
|
||||
sourceTree = "<group>";
|
||||
@@ -890,6 +900,7 @@
|
||||
Base,
|
||||
de,
|
||||
fr,
|
||||
it,
|
||||
);
|
||||
mainGroup = 5CA059BD279559F40002BEB4;
|
||||
packageReferences = (
|
||||
@@ -988,6 +999,7 @@
|
||||
5C00164428A26FBC0094D739 /* ContextMenu.swift in Sources */,
|
||||
5C3A88D127DF57800060F1C2 /* FramedItemView.swift in Sources */,
|
||||
5CB924E427A8683A00ACCCDD /* UserAddress.swift in Sources */,
|
||||
5C65F343297D45E100B67AF3 /* VersionView.swift in Sources */,
|
||||
64F1CC3B28B39D8600CD1FB1 /* IncognitoHelp.swift in Sources */,
|
||||
5CB0BA90282713D900B3292C /* SimpleXInfo.swift in Sources */,
|
||||
5C063D2727A4564100AEC577 /* ChatPreviewView.swift in Sources */,
|
||||
@@ -1042,6 +1054,7 @@
|
||||
5CB346E52868AA7F001FD2EF /* SuspendChat.swift in Sources */,
|
||||
5C9C2DA52894777E00CC63B1 /* GroupProfileView.swift in Sources */,
|
||||
5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */,
|
||||
5CCB939C297EFCB100399E78 /* NavStackCompat.swift in Sources */,
|
||||
5C764E89279CBCB3000C6508 /* ChatModel.swift in Sources */,
|
||||
5C971E1D27AEBEF600C8A3CE /* ChatInfoView.swift in Sources */,
|
||||
5CBD285C29575B8E00EC2CF4 /* WhatsNewView.swift in Sources */,
|
||||
@@ -1140,6 +1153,7 @@
|
||||
5CB0BA872826CB3A00B3292C /* ru */,
|
||||
5CE1330428E118CC00FFFD8C /* de */,
|
||||
5CBD285829565D2600EC2CF4 /* fr */,
|
||||
5CA85D0D297219EF0095AF72 /* it */,
|
||||
);
|
||||
name = InfoPlist.strings;
|
||||
sourceTree = "<group>";
|
||||
@@ -1151,6 +1165,7 @@
|
||||
5C9CC7B128D1F8F400BEF955 /* en */,
|
||||
5CB2085528DE647400D024EC /* de */,
|
||||
5CBD285629565CAE00EC2CF4 /* fr */,
|
||||
5CA85D0B297218AA0095AF72 /* it */,
|
||||
);
|
||||
name = Localizable.strings;
|
||||
sourceTree = "<group>";
|
||||
@@ -1162,6 +1177,7 @@
|
||||
6493D667280ED77F007A76FB /* en */,
|
||||
5CB2085428DE647400D024EC /* de */,
|
||||
5CBD285529565CAE00EC2CF4 /* fr */,
|
||||
5CA85D0A297218AA0095AF72 /* it */,
|
||||
);
|
||||
name = Localizable.strings;
|
||||
sourceTree = "<group>";
|
||||
@@ -1172,6 +1188,7 @@
|
||||
5CC2C0FE2809BF11000C35E3 /* ru */,
|
||||
5CE1330328E118CC00FFFD8C /* de */,
|
||||
5CBD285729565D2600EC2CF4 /* fr */,
|
||||
5CA85D0C297219EF0095AF72 /* it */,
|
||||
);
|
||||
name = "SimpleX--iOS--InfoPlist.strings";
|
||||
sourceTree = "<group>";
|
||||
@@ -1305,7 +1322,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 107;
|
||||
CURRENT_PROJECT_VERSION = 118;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -1326,7 +1343,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 4.4;
|
||||
MARKETING_VERSION = 4.4.4;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = SimpleX;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1347,7 +1364,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 107;
|
||||
CURRENT_PROJECT_VERSION = 118;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -1368,7 +1385,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 4.4;
|
||||
MARKETING_VERSION = 4.4.4;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = SimpleX;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1426,7 +1443,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 107;
|
||||
CURRENT_PROJECT_VERSION = 118;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -1439,7 +1456,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 4.4;
|
||||
MARKETING_VERSION = 4.4.4;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1456,7 +1473,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 107;
|
||||
CURRENT_PROJECT_VERSION = 118;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -1469,7 +1486,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 4.4;
|
||||
MARKETING_VERSION = 4.4.4;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
enableAddressSanitizer = "YES"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
||||
@@ -89,6 +89,7 @@ public enum ChatCommand {
|
||||
case apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64))
|
||||
case apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool)
|
||||
case receiveFile(fileId: Int64, inline: Bool)
|
||||
case showVersion
|
||||
case string(String)
|
||||
|
||||
public var cmdString: String {
|
||||
@@ -174,6 +175,7 @@ public enum ChatCommand {
|
||||
case let .apiChatRead(type, id, itemRange: (from, to)): return "/_read chat \(ref(type, id)) from=\(from) to=\(to)"
|
||||
case let .apiChatUnread(type, id, unreadChat): return "/_unread chat \(ref(type, id)) \(onOff(unreadChat))"
|
||||
case let .receiveFile(fileId, inline): return "/freceive \(fileId) inline=\(onOff(inline))"
|
||||
case .showVersion: return "/version"
|
||||
case let .string(str): return str
|
||||
}
|
||||
}
|
||||
@@ -257,6 +259,7 @@ public enum ChatCommand {
|
||||
case .apiChatRead: return "apiChatRead"
|
||||
case .apiChatUnread: return "apiChatUnread"
|
||||
case .receiveFile: return "receiveFile"
|
||||
case .showVersion: return "showVersion"
|
||||
case .string: return "console command"
|
||||
}
|
||||
}
|
||||
@@ -399,6 +402,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
case ntfMessages(connEntity: ConnectionEntity?, msgTs: Date?, ntfMessages: [NtfMsgInfo])
|
||||
case newContactConnection(connection: PendingContactConnection)
|
||||
case contactConnectionDeleted(connection: PendingContactConnection)
|
||||
case versionInfo(versionInfo: CoreVersionInfo)
|
||||
case cmdOk
|
||||
case chatCmdError(chatError: ChatError)
|
||||
case chatError(chatError: ChatError)
|
||||
@@ -502,6 +506,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
case .ntfMessages: return "ntfMessages"
|
||||
case .newContactConnection: return "newContactConnection"
|
||||
case .contactConnectionDeleted: return "contactConnectionDeleted"
|
||||
case .versionInfo: return "versionInfo"
|
||||
case .cmdOk: return "cmdOk"
|
||||
case .chatCmdError: return "chatCmdError"
|
||||
case .chatError: return "chatError"
|
||||
@@ -608,6 +613,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
case let .ntfMessages(connEntity, msgTs, ntfMessages): return "connEntity: \(String(describing: connEntity))\nmsgTs: \(String(describing: msgTs))\nntfMessages: \(String(describing: ntfMessages))"
|
||||
case let .newContactConnection(connection): return String(describing: connection)
|
||||
case let .contactConnectionDeleted(connection): return String(describing: connection)
|
||||
case let .versionInfo(versionInfo): return String(describing: versionInfo)
|
||||
case .cmdOk: return noDetails
|
||||
case let .chatCmdError(chatError): return String(describing: chatError)
|
||||
case let .chatError(chatError): return String(describing: chatError)
|
||||
@@ -809,6 +815,7 @@ public struct NetCfg: Codable, Equatable {
|
||||
public var tcpTimeout: Int // microseconds
|
||||
public var tcpKeepAlive: KeepAliveOpts?
|
||||
public var smpPingInterval: Int // microseconds
|
||||
public var smpPingCount: Int // times
|
||||
public var logTLSErrors: Bool
|
||||
|
||||
public static let defaults: NetCfg = NetCfg(
|
||||
@@ -816,7 +823,8 @@ public struct NetCfg: Codable, Equatable {
|
||||
tcpConnectTimeout: 10_000_000,
|
||||
tcpTimeout: 7_000_000,
|
||||
tcpKeepAlive: KeepAliveOpts.defaults,
|
||||
smpPingInterval: 600_000_000,
|
||||
smpPingInterval: 1200_000_000,
|
||||
smpPingCount: 3,
|
||||
logTLSErrors: false
|
||||
)
|
||||
|
||||
@@ -825,7 +833,8 @@ public struct NetCfg: Codable, Equatable {
|
||||
tcpConnectTimeout: 20_000_000,
|
||||
tcpTimeout: 15_000_000,
|
||||
tcpKeepAlive: KeepAliveOpts.defaults,
|
||||
smpPingInterval: 600_000_000,
|
||||
smpPingInterval: 1200_000_000,
|
||||
smpPingCount: 3,
|
||||
logTLSErrors: false
|
||||
)
|
||||
|
||||
@@ -998,6 +1007,13 @@ public enum NotificationPreviewMode: String, SelectableItem {
|
||||
public static var values: [NotificationPreviewMode] = [.message, .contact, .hidden]
|
||||
}
|
||||
|
||||
public struct CoreVersionInfo: Decodable {
|
||||
public var version: String
|
||||
public var buildTimestamp: String
|
||||
public var simplexmqVersion: String
|
||||
public var simplexmqCommit: String
|
||||
}
|
||||
|
||||
public func decodeJSON<T: Decodable>(_ json: String) -> T? {
|
||||
if let data = json.data(using: .utf8) {
|
||||
return try? jsonDecoder.decode(T.self, from: data)
|
||||
|
||||
@@ -20,6 +20,7 @@ let GROUP_DEFAULT_NETWORK_USE_ONION_HOSTS = "networkUseOnionHosts"
|
||||
let GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT = "networkTCPConnectTimeout"
|
||||
let GROUP_DEFAULT_NETWORK_TCP_TIMEOUT = "networkTCPTimeout"
|
||||
let GROUP_DEFAULT_NETWORK_SMP_PING_INTERVAL = "networkSMPPingInterval"
|
||||
let GROUP_DEFAULT_NETWORK_SMP_PING_COUNT = "networkSMPPingCount"
|
||||
let GROUP_DEFAULT_NETWORK_ENABLE_KEEP_ALIVE = "networkEnableKeepAlive"
|
||||
let GROUP_DEFAULT_NETWORK_TCP_KEEP_IDLE = "networkTCPKeepIdle"
|
||||
let GROUP_DEFAULT_NETWORK_TCP_KEEP_INTVL = "networkTCPKeepIntvl"
|
||||
@@ -38,13 +39,16 @@ public func registerGroupDefaults() {
|
||||
GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT: NetCfg.defaults.tcpConnectTimeout,
|
||||
GROUP_DEFAULT_NETWORK_TCP_TIMEOUT: NetCfg.defaults.tcpTimeout,
|
||||
GROUP_DEFAULT_NETWORK_SMP_PING_INTERVAL: NetCfg.defaults.smpPingInterval,
|
||||
GROUP_DEFAULT_NETWORK_SMP_PING_COUNT: NetCfg.defaults.smpPingCount,
|
||||
GROUP_DEFAULT_NETWORK_ENABLE_KEEP_ALIVE: NetCfg.defaults.enableKeepAlive,
|
||||
GROUP_DEFAULT_NETWORK_TCP_KEEP_IDLE: KeepAliveOpts.defaults.keepIdle,
|
||||
GROUP_DEFAULT_NETWORK_TCP_KEEP_INTVL: KeepAliveOpts.defaults.keepIntvl,
|
||||
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
|
||||
])
|
||||
}
|
||||
|
||||
@@ -185,6 +189,7 @@ public func getNetCfg() -> NetCfg {
|
||||
let tcpConnectTimeout = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT)
|
||||
let tcpTimeout = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT)
|
||||
let smpPingInterval = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_SMP_PING_INTERVAL)
|
||||
let smpPingCount = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_SMP_PING_COUNT)
|
||||
let enableKeepAlive = groupDefaults.bool(forKey: GROUP_DEFAULT_NETWORK_ENABLE_KEEP_ALIVE)
|
||||
var tcpKeepAlive: KeepAliveOpts?
|
||||
if enableKeepAlive {
|
||||
@@ -202,6 +207,7 @@ public func getNetCfg() -> NetCfg {
|
||||
tcpTimeout: tcpTimeout,
|
||||
tcpKeepAlive: tcpKeepAlive,
|
||||
smpPingInterval: smpPingInterval,
|
||||
smpPingCount: smpPingCount,
|
||||
logTLSErrors: false
|
||||
)
|
||||
}
|
||||
@@ -211,6 +217,7 @@ public func setNetCfg(_ cfg: NetCfg) {
|
||||
groupDefaults.set(cfg.tcpConnectTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT)
|
||||
groupDefaults.set(cfg.tcpTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT)
|
||||
groupDefaults.set(cfg.smpPingInterval, forKey: GROUP_DEFAULT_NETWORK_SMP_PING_INTERVAL)
|
||||
groupDefaults.set(cfg.smpPingCount, forKey: GROUP_DEFAULT_NETWORK_SMP_PING_COUNT)
|
||||
if let tcpKeepAlive = cfg.tcpKeepAlive {
|
||||
groupDefaults.set(true, forKey: GROUP_DEFAULT_NETWORK_ENABLE_KEEP_ALIVE)
|
||||
groupDefaults.set(tcpKeepAlive.keepIdle, forKey: GROUP_DEFAULT_NETWORK_TCP_KEEP_IDLE)
|
||||
|
||||
@@ -93,11 +93,11 @@ public struct LocalProfile: Codable, NamedChat {
|
||||
}
|
||||
|
||||
public func toLocalProfile (_ profileId: Int64, _ profile: Profile, _ localAlias: String) -> LocalProfile {
|
||||
LocalProfile(profileId: profileId, displayName: profile.displayName, fullName: profile.fullName, image: profile.image, localAlias: localAlias)
|
||||
LocalProfile(profileId: profileId, displayName: profile.displayName, fullName: profile.fullName, image: profile.image, preferences: profile.preferences, localAlias: localAlias)
|
||||
}
|
||||
|
||||
public func fromLocalProfile (_ profile: LocalProfile) -> Profile {
|
||||
Profile(displayName: profile.displayName, fullName: profile.fullName, image: profile.image)
|
||||
Profile(displayName: profile.displayName, fullName: profile.fullName, image: profile.image, preferences: profile.preferences)
|
||||
}
|
||||
|
||||
public enum ChatType: String {
|
||||
@@ -1670,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
|
||||
@@ -1862,6 +1863,29 @@ public struct ChatItem: Identifiable, Decodable {
|
||||
)
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -2060,10 +2084,10 @@ public enum CIContent: Decodable, ItemContent {
|
||||
|
||||
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? {
|
||||
|
||||
@@ -62,13 +62,13 @@
|
||||
"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**Mehr Privatsphäre**: Es wird alle 20 Minuten auf neue Nachrichten geprüft. Nur Ihr Geräte-Token wird dem SimpleX-Chat-Server mitgeteilt, aber nicht wie viele Kontakte Sie haben oder welche Nachrichten Sie empfangen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Beste Privatsphäre**: Es wird kein SimpleX-Chat-Benachrichtigungs-Server genutzt, Nachrichten werden in regelmäßigen Abständen im Hintergrund geprüft (dies hängt davon ab, wie häufig Sie die App nutzen).";
|
||||
"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Beste Privatsphäre**: Es wird kein SimpleX-Chat-Benachrichtigungs-Server genutzt, Nachrichten werden in periodischen Abständen im Hintergrund geprüft (dies hängt davon ab, wie häufig Sie die App nutzen).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Paste received link** or open it in the browser and tap **Open in mobile app**." = "**Fügen Sie den von Ihrem Kontakt erhaltenen Link ein** oder öffnen Sie ihn im Browser und tippen Sie auf **In mobiler App öffnen**.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Bitte beachten Sie**: Sie können das Passwort NICHT wiederherstellen oder ändern, wenn Sie es vergessen haben oder verlieren.";
|
||||
"**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Bitte beachten Sie**: Das Passwort kann NICHT wiederhergestellt oder geändert werden, wenn Sie es vergessen haben oder verlieren.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Empfohlen**: Nur Ihr Geräte-Token und ihre Benachrichtigungen werden an den SimpleX-Chat-Benachrichtigungs-Server gesendet, aber weder der Nachrichteninhalt noch deren Größe oder von wem sie gesendet wurde.";
|
||||
@@ -98,10 +98,10 @@
|
||||
"%@ is connected!" = "%@ ist mit Ihnen verbunden!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%@ is not verified" = "%@ wurde nicht überprüft";
|
||||
"%@ is not verified" = "%@ wurde noch nicht überprüft";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%@ is verified" = "%@ wurde überprüft";
|
||||
"%@ is verified" = "%@ wurde erfolgreich überprüft";
|
||||
|
||||
/* notification title */
|
||||
"%@ wants to connect!" = "%@ will sich mit Ihnen verbinden!";
|
||||
@@ -143,25 +143,25 @@
|
||||
"%lld second(s)" = "%lld Sekunde(n)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%lldd" = "%lldd";
|
||||
"%lldd" = "%lldT";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%lldh" = "%lldh";
|
||||
"%lldh" = "%lldH";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%lldk" = "%lldk";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%lldm" = "%lldm";
|
||||
"%lldm" = "%lldmin";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%lldmth" = "%lldmth";
|
||||
"%lldmth" = "%lldMon";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%llds" = "%llds";
|
||||
"%llds" = "%lldsek";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%lldw" = "%lldw";
|
||||
"%lldw" = "%lldW";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"`a + b`" = "\\`a + b`";
|
||||
@@ -191,10 +191,10 @@
|
||||
"A new contact" = "Ein neuer Kontakt";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"A random profile will be sent to the contact that you received this link from" = "Ein zufälliges Profil wird an den Kontakt gesendet, von dem Sie diesen Link erhalten haben.";
|
||||
"A random profile will be sent to the contact that you received this link from" = "Ein zufälliges Profil wird an den Kontakt gesendet, von dem Sie diesen Link erhalten haben";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"A random profile will be sent to your contact" = "Ein zufälliges Profil wird an Ihren Kontakt gesendet.";
|
||||
"A random profile will be sent to your contact" = "Ein zufälliges Profil wird an Ihren Kontakt gesendet";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"About SimpleX" = "Über SimpleX";
|
||||
@@ -255,7 +255,7 @@
|
||||
"All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." = "Alle Nachrichten werden gelöscht - dies kann nicht rückgängig gemacht werden! Die Nachrichten werden NUR bei Ihnen gelöscht.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"All your contacts will remain connected" = "Alle Ihre Kontakte bleiben verbunden.";
|
||||
"All your contacts will remain connected" = "Alle Ihre Kontakte bleiben verbunden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow" = "Erlauben";
|
||||
@@ -264,19 +264,19 @@
|
||||
"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 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 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 sending direct messages to members." = "Erlauben Sie das Senden von Direktnachrichten an Mitglieder";
|
||||
"Allow sending direct messages to members." = "Das Senden von Direktnachrichten an Gruppenmitglieder erlauben.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"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.";
|
||||
"Allow to irreversibly delete sent messages." = "Unwiederbringliches löschen von gesendeten Nachrichten erlauben.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow to send voice messages." = "Senden von Sprachnachrichten erlauben.";
|
||||
"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.";
|
||||
@@ -377,8 +377,11 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Cancel" = "Abbrechen";
|
||||
|
||||
/* feature offered item */
|
||||
"cancelled %@" = "Beende %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cannot access keychain to save database password" = "Die App kann nicht auf den Schlüsselbund zugreifen, um das Datenbank-Passwort zu speichern.";
|
||||
"Cannot access keychain to save database password" = "Die App kann nicht auf den Schlüsselbund zugreifen, um das Datenbank-Passwort zu speichern";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cannot receive file" = "Datei kann nicht empfangen werden";
|
||||
@@ -462,7 +465,7 @@
|
||||
"Clear conversation?" = "Unterhaltung löschen?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Clear verification" = "Überprüfung erfolgt";
|
||||
"Clear verification" = "Überprüfung zurücknehmen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"colored" = "farbig";
|
||||
@@ -810,13 +813,13 @@
|
||||
"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";
|
||||
"Disappearing messages" = "verschwindende Nachrichten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Disappearing messages are prohibited in this chat." = "In diesem Chat sind verschwindende Nachrichten nicht erlaubt.";
|
||||
@@ -864,7 +867,7 @@
|
||||
"Enable notifications" = "Benachrichtigungen aktivieren";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable periodic notifications?" = "Regelmäßige Benachrichtigungen aktivieren?";
|
||||
"Enable periodic notifications?" = "Periodische Benachrichtigungen aktivieren?";
|
||||
|
||||
/* authentication reason */
|
||||
"Enable SimpleX Lock" = "SimpleX Sperre aktivieren";
|
||||
@@ -1005,7 +1008,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";
|
||||
@@ -1110,7 +1113,7 @@
|
||||
"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 senden.";
|
||||
"Group members can send voice messages." = "Gruppenmitglieder können Sprachnachrichten versenden.";
|
||||
|
||||
/* notification */
|
||||
"Group message:" = "Grppennachricht:";
|
||||
@@ -1122,7 +1125,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";
|
||||
@@ -1167,10 +1170,10 @@
|
||||
"If the video fails to connect, flip the camera to resolve it." = "Wenn der Videoanruf nicht funktioniert, wechseln Sie die Kamera, um das Problem zu lösen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"If you can't meet in person, **show QR code in the video call**, or share the link." = "Wenn Sie sich nicht persönlich treffen können, können Sie den **QR-Code während eines Videoanrufs anzeigen** oder den Einladungslink über einen anderen Kanal mit Ihrem Kontakt teilen.";
|
||||
"If you can't meet in person, **show QR code in the video call**, or share the link." = "Wenn Sie sich nicht persönlich treffen können, kann der **QR-Code während eines Videoanrufs angezeigt werden**, oder der Einladungslink über einen anderen Kanal mit Ihrem Kontakt geteilt werden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link." = "Wenn Sie sich nicht persönlich treffen können, können Sie den **QR-Code während eines Videoanrufs scannen** oder Ihr Kontakt kann den Einladungslink über einen anderen Kanal mit Ihnen teilen.";
|
||||
"If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link." = "Wenn Sie sich nicht persönlich treffen können, kann der **QR-Code während eines Videoanrufs gescannt werden**, oder Ihr Kontakt kann den Einladungslink über einen anderen Kanal mit Ihnen teilen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"If you need to use the chat now tap **Do it later** below (you will be offered to migrate the database when you restart the app)." = "Tippen Sie unten auf **Später wiederholen**, wenn Sie den Chat jetzt benötigen (es wird Ihnen angeboten, die Datenbank bei einem Neustart der App zu migrieren).";
|
||||
@@ -1275,7 +1278,7 @@
|
||||
"invited" = "eingeladen";
|
||||
|
||||
/* rcv group event chat item */
|
||||
"invited %@" = "hat %@ eingeladen.";
|
||||
"invited %@" = "hat %@ eingeladen";
|
||||
|
||||
/* chat list item title */
|
||||
"invited to connect" = "Für eine Verbindung eingeladen";
|
||||
@@ -1290,13 +1293,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" = "Unwiederbringliches Löschen einer Nachricht";
|
||||
"Irreversible message deletion" = "Unwiederbringliches löschen einer Nachricht";
|
||||
|
||||
/* 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 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 verboten.";
|
||||
"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.";
|
||||
@@ -1368,7 +1371,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?*";
|
||||
@@ -1521,6 +1524,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Off (Local)" = "Aus (Lokal)";
|
||||
|
||||
/* feature offered item */
|
||||
"offered %@" = "Beginne %@";
|
||||
|
||||
/* feature offered item */
|
||||
"offered %@: %@" = "Beginne %1$@: %2$@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Ok" = "Ok";
|
||||
|
||||
@@ -1561,7 +1570,7 @@
|
||||
"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 senden.";
|
||||
"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).";
|
||||
@@ -1570,7 +1579,7 @@
|
||||
"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 senden.";
|
||||
"Only your contact can send voice messages." = "Nur Ihr Kontakt kann Sprachnachrichten versenden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open chat" = "Chat öffnen";
|
||||
@@ -1612,7 +1621,7 @@
|
||||
"People can connect to you only via the links you share." = "Verbindungen mit Kontakten sind nur über Links möglich, die Sie oder Ihre Kontakte untereinander teilen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Periodically" = "Regelmäßig";
|
||||
"Periodically" = "Periodisch";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"PING interval" = "PING-Intervall";
|
||||
@@ -1633,7 +1642,7 @@
|
||||
"Please enter correct current passphrase." = "Bitte geben Sie das korrekte, aktuelle Passwort ein.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Please enter the previous password after restoring database backup. This action can not be undone." = "Bitte geben Sie das vorherige Passwort ein, nachdem Sie die Datenbanksicherung wiederhergestellt haben. Diese Aktion kann nicht rückgängig gemacht werden!";
|
||||
"Please enter the previous password after restoring database backup. This action can not be undone." = "Bitte geben Sie das vorherige Passwort ein, nachdem Sie die Datenbanksicherung wiederhergestellt haben. Diese Aktion kann nicht rückgängig gemacht werden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Please restart the app and migrate the database to enable push notifications." = "Bitte führen Sie einen Neustart der App durch und migrieren Sie die Datenbank, um Benachrichtigungen zu aktivieren.";
|
||||
@@ -1663,16 +1672,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 disappearing messages." = "Das Senden von verschwindenden Nachrichten verbieten.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Prohibit sending voice messages." = "Senden von Sprachnachrichten untersagen.";
|
||||
"Prohibit sending voice messages." = "Das Senden von Sprachnachrichten nicht erlauben.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Protect app screen" = "App-Bildschirm schützen";
|
||||
@@ -1765,7 +1774,7 @@
|
||||
"Reset to defaults" = "Auf Standardwerte zurücksetzen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Restart the app to create a new chat profile" = "Um ein neues Chat-Profil zu erstellen, starten Sie die App neu.";
|
||||
"Restart the app to create a new chat profile" = "Um ein neues Chat-Profil zu erstellen, starten Sie die App neu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Restart the app to use imported chat database" = "Um die importierte Chat-Datenbank zu verwenden, starten Sie die App neu";
|
||||
@@ -1822,7 +1831,7 @@
|
||||
"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";
|
||||
@@ -1900,7 +1909,7 @@
|
||||
"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!";
|
||||
@@ -2059,7 +2068,7 @@
|
||||
"Thank you for installing SimpleX Chat!" = "Vielen Dank, dass Sie SimpleX Chat installiert haben!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The 1st platform without any user identifiers – private by design." = "Die erste Plattform ohne Benutzerkennungen – Privat per Design";
|
||||
"The 1st platform without any user identifiers – private by design." = "Die erste Plattform ohne Benutzerkennungen – Privat per Design.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The app can notify you when you receive messages or contact requests - please open settings to enable." = "Wenn sie Nachrichten oder Kontaktanfragen empfangen, kann Sie die App benachrichtigen - Um dies zu aktivieren, öffnen Sie bitte die Einstellungen.";
|
||||
@@ -2221,7 +2230,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?";
|
||||
@@ -2272,13 +2281,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…";
|
||||
@@ -2362,7 +2371,7 @@
|
||||
"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." = "Sie können Ihre Adresse als Link oder als QR-Code teilen – Jede Person kann sich darüber mit Ihnen verbinden. Sie werden Ihre mit dieser Adresse verbundenen Kontakte nicht verlieren, wenn Sie diese Adresse später löschen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can start chat via app Settings / Database or by restarting the app" = "Sie können den Chat über die App-Einstellungen / Datenbank oder durch Neustart der App starten.";
|
||||
"You can start chat via app Settings / Database or by restarting the app" = "Sie können den Chat über die App-Einstellungen / Datenbank oder durch Neustart der App starten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can use markdown to format messages:" = "Um Nachrichteninhalte zu formatieren, können Sie Markdowns verwenden:";
|
||||
@@ -2446,16 +2455,16 @@
|
||||
"you: " = "Sie: ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile" = "Sie versuchen, einen Kontakt, mit dem Sie ein Inkognito-Profil geteilt haben, in die Gruppe einzuladen, in der Sie Ihr Hauptprofil verwenden.";
|
||||
"You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile" = "Sie versuchen, einen Kontakt, mit dem Sie ein Inkognito-Profil geteilt haben, in die Gruppe einzuladen, in der Sie Ihr Hauptprofil verwenden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Sie verwenden ein Inkognito-Profil für diese Gruppe. Um zu verhindern, dass Sie Ihr Hauptprofil teilen, ist in diesem Fall das Einladen von Kontakten nicht erlaubt.";
|
||||
"You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Sie verwenden ein Inkognito-Profil für diese Gruppe. Um zu verhindern, dass Sie Ihr Hauptprofil teilen, ist in diesem Fall das Einladen von Kontakten nicht erlaubt";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your calls" = "Anrufe";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat database" = "Meine Chat-Datenbank";
|
||||
"Your chat database" = "Chat-Datenbank";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat database is not encrypted - set passphrase to encrypt it." = "Ihre Chat-Datenbank ist nicht verschlüsselt. Bitte legen Sie ein Passwort fest, um sie zu schützen.";
|
||||
@@ -2494,7 +2503,7 @@
|
||||
"Your ICE servers" = "Ihre ICE-Server";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your preferences" = "Ihre Präferenzen";
|
||||
"Your preferences" = "Meine Präferenzen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your privacy" = "Meine Privatsphäre";
|
||||
@@ -2503,7 +2512,7 @@
|
||||
"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Ihr Profil wird auf Ihrem Gerät gespeichert und nur mit Ihren Kontakten geteilt.\nSimpleX-Server können Ihr Profil nicht einsehen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your profile will be sent to the contact that you received this link from" = "Ihr Profil wird an den Kontakt gesendet, von dem Sie diesen Link erhalten haben.";
|
||||
"Your profile will be sent to the contact that you received this link from" = "Ihr Profil wird an den Kontakt gesendet, von dem Sie diesen Link erhalten haben";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your profile, contacts and delivered messages are stored on your device." = "Ihr Profil, Ihre Kontakte und zugestellten Nachrichten werden auf Ihrem Gerät gespeichert.";
|
||||
|
||||
@@ -377,6 +377,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Cancel" = "Annuler";
|
||||
|
||||
/* feature offered item */
|
||||
"cancelled %@" = "annulé %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cannot access keychain to save database password" = "Impossible d'accéder à la keychain pour enregistrer le mot de passe de la base de données";
|
||||
|
||||
@@ -1242,7 +1245,7 @@
|
||||
"Instant push notifications will be hidden!\n" = "Les notifications push instantanées vont être cachées !\n";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Instantly" = "Instantanément";
|
||||
"Instantly" = "Instantané";
|
||||
|
||||
/* invalid chat data */
|
||||
"invalid chat" = "chat invalide";
|
||||
@@ -1371,7 +1374,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";
|
||||
@@ -1467,7 +1470,7 @@
|
||||
"New database archive" = "Nouvelle archive de base de données";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"New in %@" = "Nouveau en %@";
|
||||
"New in %@" = "Nouveautés de la %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"New member role" = "Nouveau rôle";
|
||||
@@ -1521,6 +1524,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Off (Local)" = "Off (Local)";
|
||||
|
||||
/* feature offered item */
|
||||
"offered %@" = "offert %@";
|
||||
|
||||
/* feature offered item */
|
||||
"offered %@: %@" = "offert %1$@ : %2$@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Ok" = "Ok";
|
||||
|
||||
@@ -1609,10 +1618,10 @@
|
||||
"peer-to-peer" = "pair-à-pair";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"People can connect to you only via the links you share." = "Les gens peuvent se connecter à vous uniquement via les liens que vous partagez.";
|
||||
"People can connect to you only via the links you share." = "On ne peut se connecter à vous qu’avec les liens que vous partagez.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Periodically" = "Périodiquement";
|
||||
"Periodically" = "Périodique";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"PING interval" = "Intervalle de PING";
|
||||
@@ -1891,7 +1900,7 @@
|
||||
"Sender may have deleted the connection request." = "L'expéditeur a peut-être supprimé la demande de connexion.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending via" = "Envoyé via";
|
||||
"Sending via" = "Envoi via";
|
||||
|
||||
/* notification */
|
||||
"Sent file event" = "Événement de fichier envoyé";
|
||||
@@ -1936,7 +1945,7 @@
|
||||
"Share link" = "Partager le lien";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share one-time invitation link" = "Partager le lien d'invitation unique";
|
||||
"Share one-time invitation link" = "Partager un lien d'invitation unique";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Show preview" = "Montrer l'aperçu";
|
||||
@@ -2116,7 +2125,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.";
|
||||
@@ -2128,7 +2137,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.";
|
||||
@@ -2380,7 +2389,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.";
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,15 @@
|
||||
/* Bundle name */
|
||||
"CFBundleName" = "SimpleX";
|
||||
|
||||
/* Privacy - Camera Usage Description */
|
||||
"NSCameraUsageDescription" = "SimpleX ha bisogno dell'accesso alla fotocamera per scansionare i codici QR per connettersi ad altri utenti e per le videochiamate.";
|
||||
|
||||
/* Privacy - Face ID Usage Description */
|
||||
"NSFaceIDUsageDescription" = "SimpleX usa Face ID per l'autenticazione locale";
|
||||
|
||||
/* Privacy - Microphone Usage Description */
|
||||
"NSMicrophoneUsageDescription" = "SimpleX ha bisogno dell'accesso al microfono per le chiamate audio e video e per registrare messaggi vocali.";
|
||||
|
||||
/* Privacy - Photo Library Additions Usage Description */
|
||||
"NSPhotoLibraryAddUsageDescription" = "SimpleX ha bisogno di accedere alla libreria di foto per salvare i contenuti multimediali acquisiti e ricevuti";
|
||||
|
||||
@@ -225,7 +225,7 @@
|
||||
"Accept requests" = "Принимать запросы";
|
||||
|
||||
/* call status */
|
||||
"accepted call" = " принятый звонок";
|
||||
"accepted call" = "принятый звонок";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add preset servers" = "Добавить серверы по умолчанию";
|
||||
@@ -377,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 при сохранении пароля";
|
||||
|
||||
@@ -837,7 +840,7 @@
|
||||
"Do it later" = "Отложить";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Do NOT use SimpleX for emergency calls." = "Не используйте SimpleX для экстренных звонков";
|
||||
"Do NOT use SimpleX for emergency calls." = "Не используйте SimpleX для экстренных звонков.";
|
||||
|
||||
/* integrity error chat item */
|
||||
"duplicate message" = "повторное сообщение";
|
||||
@@ -1521,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" = "Ок";
|
||||
|
||||
@@ -1546,7 +1555,7 @@
|
||||
"Onion hosts will not be used." = "Onion хосты не используются.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Только пользовательские устройства хранят контакты, группы и сообщения, которые отправляются **с двухуровневым end-to-end шифрованием**";
|
||||
"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Только пользовательские устройства хранят контакты, группы и сообщения, которые отправляются **с двухуровневым end-to-end шифрованием**.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only group owners can change group preferences." = "Только владельцы группы могут изменять предпочтения группы.";
|
||||
@@ -2020,7 +2029,7 @@
|
||||
"Take picture" = "Сделать фото";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap button " = "Нажмите кнопку";
|
||||
"Tap button " = "Нажмите кнопку ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to join" = "Нажмите, чтобы вступить";
|
||||
@@ -2476,7 +2485,7 @@
|
||||
"Your contact address" = "Ваш SimpleX адрес";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contact can scan it from the app." = "Ваш контакт может сосканировать QR код в приложении";
|
||||
"Your contact can scan it from the app." = "Ваш контакт может сосканировать QR код в приложении.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contact needs to be online for the connection to complete.\nYou can cancel this connection and remove the contact (and try later with a new link)." = "Ваш контакт должен быть в сети чтобы установить соединение.\nВы можете отменить соединение и удалить контакт (и попробовать позже с другой ссылкой).";
|
||||
|
||||
@@ -5,7 +5,7 @@ module Main where
|
||||
import Control.Concurrent (threadDelay)
|
||||
import Data.Time.Clock (getCurrentTime)
|
||||
import Server
|
||||
import Simplex.Chat.Controller (versionNumber)
|
||||
import Simplex.Chat.Controller (versionNumber, versionString)
|
||||
import Simplex.Chat.Core
|
||||
import Simplex.Chat.Options
|
||||
import Simplex.Chat.Terminal
|
||||
@@ -36,7 +36,7 @@ welcome :: ChatOpts -> IO ()
|
||||
welcome ChatOpts {dbFilePrefix, networkConfig} =
|
||||
mapM_
|
||||
putStrLn
|
||||
[ "SimpleX Chat v" ++ versionNumber,
|
||||
[ versionString versionNumber,
|
||||
"db: " <> dbFilePrefix <> "_chat.db, " <> dbFilePrefix <> "_agent.db",
|
||||
maybe
|
||||
"direct network connection - use `/network` command or `-x` CLI option to connect via SOCKS5 at :9050"
|
||||
|
||||
@@ -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
|
||||
+10
-1
@@ -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:
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 353 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 145 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 141 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 343 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 480 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 229 KiB |
+1
-1
@@ -7,7 +7,7 @@ constraints: zip +disable-bzip2 +disable-zstd
|
||||
source-repository-package
|
||||
type: git
|
||||
location: https://github.com/simplex-chat/simplexmq.git
|
||||
tag: 874f9f5cd67310ddfbe5540d6abfa5943a534f37
|
||||
tag: 56eea29ec36ba723fda6ae27a18e03a4e5aad6e8
|
||||
|
||||
source-repository-package
|
||||
type: git
|
||||
|
||||
Generated
+6
-6
@@ -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": {
|
||||
|
||||
+13
-1
@@ -1,5 +1,5 @@
|
||||
name: simplex-chat
|
||||
version: 4.4.0
|
||||
version: 4.4.4
|
||||
#synopsis:
|
||||
#description:
|
||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||
@@ -39,6 +39,7 @@ dependencies:
|
||||
- socks == 0.6.*
|
||||
- sqlcipher-simple == 0.4.*
|
||||
- stm == 2.5.*
|
||||
- template-haskell == 2.16.*
|
||||
- terminal == 0.2.*
|
||||
- text == 1.2.*
|
||||
- time == 1.9.*
|
||||
@@ -46,6 +47,17 @@ dependencies:
|
||||
- unliftio-core == 0.2.*
|
||||
- zip == 1.7.*
|
||||
|
||||
flags:
|
||||
swift:
|
||||
description: Enable swift JSON format
|
||||
manual: True
|
||||
default: False
|
||||
|
||||
when:
|
||||
- condition: flag(swift)
|
||||
cpp-options:
|
||||
- -DswiftJSON
|
||||
|
||||
library:
|
||||
source-dirs: src
|
||||
|
||||
|
||||
@@ -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"."874f9f5cd67310ddfbe5540d6abfa5943a534f37" = "0nyhsrg9nb205x57h1dwrhwi9j7bwh3j6hpp7p1kp9pqdsxhcvdl";
|
||||
"https://github.com/simplex-chat/simplexmq.git"."56eea29ec36ba723fda6ae27a18e03a4e5aad6e8" = "107vy6147vf282w41kmm6jhhiykcg4jz9napav5m0kcfinzbkqpg";
|
||||
"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";
|
||||
|
||||
+22
-1
@@ -5,7 +5,7 @@ cabal-version: 1.12
|
||||
-- see: https://github.com/sol/hpack
|
||||
|
||||
name: simplex-chat
|
||||
version: 4.4.0
|
||||
version: 4.4.4
|
||||
category: Web, System, Services, Cryptography
|
||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||
author: simplex.chat
|
||||
@@ -17,6 +17,11 @@ build-type: Simple
|
||||
extra-source-files:
|
||||
README.md
|
||||
|
||||
flag swift
|
||||
description: Enable swift JSON format
|
||||
manual: True
|
||||
default: False
|
||||
|
||||
library
|
||||
exposed-modules:
|
||||
Simplex.Chat
|
||||
@@ -73,6 +78,7 @@ library
|
||||
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
|
||||
@@ -119,6 +125,7 @@ library
|
||||
, socks ==0.6.*
|
||||
, sqlcipher-simple ==0.4.*
|
||||
, stm ==2.5.*
|
||||
, template-haskell ==2.16.*
|
||||
, terminal ==0.2.*
|
||||
, text ==1.2.*
|
||||
, time ==1.9.*
|
||||
@@ -126,6 +133,8 @@ library
|
||||
, unliftio-core ==0.2.*
|
||||
, zip ==1.7.*
|
||||
default-language: Haskell2010
|
||||
if flag(swift)
|
||||
cpp-options: -DswiftJSON
|
||||
|
||||
executable simplex-bot
|
||||
main-is: Main.hs
|
||||
@@ -163,6 +172,7 @@ executable simplex-bot
|
||||
, socks ==0.6.*
|
||||
, sqlcipher-simple ==0.4.*
|
||||
, stm ==2.5.*
|
||||
, template-haskell ==2.16.*
|
||||
, terminal ==0.2.*
|
||||
, text ==1.2.*
|
||||
, time ==1.9.*
|
||||
@@ -170,6 +180,8 @@ executable simplex-bot
|
||||
, unliftio-core ==0.2.*
|
||||
, zip ==1.7.*
|
||||
default-language: Haskell2010
|
||||
if flag(swift)
|
||||
cpp-options: -DswiftJSON
|
||||
|
||||
executable simplex-bot-advanced
|
||||
main-is: Main.hs
|
||||
@@ -207,6 +219,7 @@ executable simplex-bot-advanced
|
||||
, socks ==0.6.*
|
||||
, sqlcipher-simple ==0.4.*
|
||||
, stm ==2.5.*
|
||||
, template-haskell ==2.16.*
|
||||
, terminal ==0.2.*
|
||||
, text ==1.2.*
|
||||
, time ==1.9.*
|
||||
@@ -214,6 +227,8 @@ executable simplex-bot-advanced
|
||||
, unliftio-core ==0.2.*
|
||||
, zip ==1.7.*
|
||||
default-language: Haskell2010
|
||||
if flag(swift)
|
||||
cpp-options: -DswiftJSON
|
||||
|
||||
executable simplex-chat
|
||||
main-is: Main.hs
|
||||
@@ -252,6 +267,7 @@ executable simplex-chat
|
||||
, socks ==0.6.*
|
||||
, sqlcipher-simple ==0.4.*
|
||||
, stm ==2.5.*
|
||||
, template-haskell ==2.16.*
|
||||
, terminal ==0.2.*
|
||||
, text ==1.2.*
|
||||
, time ==1.9.*
|
||||
@@ -260,6 +276,8 @@ executable simplex-chat
|
||||
, websockets ==0.12.*
|
||||
, zip ==1.7.*
|
||||
default-language: Haskell2010
|
||||
if flag(swift)
|
||||
cpp-options: -DswiftJSON
|
||||
|
||||
test-suite simplex-chat-test
|
||||
type: exitcode-stdio-1.0
|
||||
@@ -306,6 +324,7 @@ test-suite simplex-chat-test
|
||||
, socks ==0.6.*
|
||||
, sqlcipher-simple ==0.4.*
|
||||
, stm ==2.5.*
|
||||
, template-haskell ==2.16.*
|
||||
, terminal ==0.2.*
|
||||
, text ==1.2.*
|
||||
, time ==1.9.*
|
||||
@@ -313,3 +332,5 @@ test-suite simplex-chat-test
|
||||
, unliftio-core ==0.2.*
|
||||
, zip ==1.7.*
|
||||
default-language: Haskell2010
|
||||
if flag(swift)
|
||||
cpp-options: -DswiftJSON
|
||||
|
||||
+165
-89
@@ -8,6 +8,7 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE RankNTypes #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# LANGUAGE TemplateHaskell #-}
|
||||
{-# LANGUAGE TupleSections #-}
|
||||
{-# LANGUAGE TypeApplications #-}
|
||||
|
||||
@@ -89,6 +90,7 @@ defaultChatConfig =
|
||||
{ agentConfig =
|
||||
defaultAgentConfig
|
||||
{ tcpPort = undefined, -- agent does not listen to TCP
|
||||
tbqSize = 1024,
|
||||
database = AgentDBFile {dbFile = "simplex_v1_agent", dbKey = ""},
|
||||
yesToMigrations = False
|
||||
},
|
||||
@@ -99,9 +101,10 @@ defaultChatConfig =
|
||||
ntf = _defaultNtfServers,
|
||||
netCfg = defaultNetworkConfig
|
||||
},
|
||||
tbqSize = 64,
|
||||
tbqSize = 1024,
|
||||
fileChunkSize = 15780, -- do not change
|
||||
inlineFiles = defaultInlineFilesConfig,
|
||||
logLevel = CLLImportant,
|
||||
subscriptionConcurrency = 16,
|
||||
subscriptionEvents = False,
|
||||
hostEvents = False,
|
||||
@@ -135,14 +138,14 @@ 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, optFilesFolder, allowInstantFiles} sendToast = do
|
||||
newChatController ChatDatabase {chatStore, agentStore} user cfg@ChatConfig {agentConfig = aCfg, defaultServers, inlineFiles} ChatOpts {smpServers, networkConfig, logLevel, logConnections, logServerHosts, tbqSize, 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'}
|
||||
config = cfg {logLevel, tbqSize, subscriptionEvents = logConnections, hostEvents = logServerHosts, defaultServers = configServers, inlineFiles = inlineFiles'}
|
||||
sendNotification = fromMaybe (const $ pure ()) sendToast
|
||||
firstTime = dbNew chatStore
|
||||
activeTo <- newTVarIO ActiveNone
|
||||
currentUser <- newTVarIO user
|
||||
smpAgent <- getSMPAgentClient aCfg {database = AgentDB agentStore} =<< agentServers config
|
||||
smpAgent <- getSMPAgentClient aCfg {tbqSize, database = AgentDB agentStore} =<< agentServers config
|
||||
agentAsync <- newTVarIO Nothing
|
||||
idsDrg <- newTVarIO =<< drgNew
|
||||
inputQ <- newTBQueueIO tbqSize
|
||||
@@ -227,13 +230,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
|
||||
@@ -383,12 +394,14 @@ processChatCommand = \case
|
||||
pure (fileInvitation, ciFile, ft)
|
||||
sendGroupFileInline :: [GroupMember] -> SharedMsgId -> FileTransferMeta -> m ()
|
||||
sendGroupFileInline ms sharedMsgId ft@FileTransferMeta {fileInline} =
|
||||
when (fileInline == Just IFMSent) . forM_ ms $ \case
|
||||
m@GroupMember {activeConn = Just conn@Connection {connStatus}} ->
|
||||
when (fileInline == Just IFMSent) . forM_ ms $ \m ->
|
||||
processMember m `catchError` (toView . CRChatError)
|
||||
where
|
||||
processMember m@GroupMember {activeConn = Just conn@Connection {connStatus}} =
|
||||
when (connStatus == ConnReady || connStatus == ConnSndReady) $ do
|
||||
void . withStore' $ \db -> createSndGroupInlineFT db m conn ft
|
||||
sendMemberFileInline m conn ft sharedMsgId
|
||||
_ -> pure ()
|
||||
processMember _ = pure ()
|
||||
prepareMsg :: Maybe FileInvitation -> Maybe CITimed -> GroupMember -> m (MsgContainer, Maybe (CIQuote 'CTGroup))
|
||||
prepareMsg fileInvitation_ timed_ membership = case quotedItemId_ of
|
||||
Nothing -> pure (MCSimple (ExtMsgContent mc fileInvitation_ (ttl' <$> timed_) (justTrue live)), Nothing)
|
||||
@@ -832,6 +845,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
|
||||
@@ -846,6 +870,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
|
||||
@@ -1100,6 +1126,9 @@ processChatCommand = \case
|
||||
quotedItemId <- withStore $ \db -> getGroupChatItemIdByText db user groupId cName (safeDecodeUtf8 quotedMsg)
|
||||
let mc = MCText $ safeDecodeUtf8 msg
|
||||
processChatCommand . APISendMessage (ChatRef CTGroup groupId) False $ ComposedMessage Nothing (Just quotedItemId) mc
|
||||
LastChats count_ -> withUser' $ \user -> do
|
||||
chats <- withStore' $ \db -> getChatPreviews db user False
|
||||
pure $ CRChats $ maybe id take count_ chats
|
||||
LastMessages (Just chatName) count search -> withUser $ \user -> do
|
||||
chatRef <- getChatRef user chatName
|
||||
CRChatItems . aChatItems . chat <$> processChatCommand (APIGetChat chatRef (CPLast count) search)
|
||||
@@ -1136,8 +1165,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 $
|
||||
@@ -1194,7 +1223,7 @@ processChatCommand = \case
|
||||
updateGroupProfileByName gName $ \p ->
|
||||
p {groupPreferences = Just . setGroupPreference' SGFTimedMessages pref $ groupPreferences p}
|
||||
QuitChat -> liftIO exitSuccess
|
||||
ShowVersion -> pure $ CRVersionInfo versionNumber
|
||||
ShowVersion -> pure $ CRVersionInfo $ coreVersionInfo $(buildTimestampQ) $(simplexmqCommitQ)
|
||||
DebugLocks -> do
|
||||
chatLockName <- atomically . tryReadTMVar =<< asks chatLock
|
||||
agentLocks <- withAgent debugAgentLocks
|
||||
@@ -1306,13 +1335,16 @@ processChatCommand = \case
|
||||
asks currentUser >>= atomically . (`writeTVar` Just user')
|
||||
withChatLock "updateProfile" . procCmd $ do
|
||||
forM_ contacts $ \ct -> do
|
||||
let mergedProfile = userProfileToSend user Nothing $ Just ct
|
||||
ct' = updateMergedPreferences user' ct
|
||||
mergedProfile' = userProfileToSend user' Nothing $ Just ct'
|
||||
when (mergedProfile' /= mergedProfile) $ do
|
||||
void (sendDirectContactMessage ct' $ XInfo mergedProfile') `catchError` (toView . CRChatError)
|
||||
when (directOrUsed ct') $ createSndFeatureItems user' ct ct'
|
||||
processContact user' ct `catchError` (toView . CRChatError)
|
||||
pure $ CRUserProfileUpdated (fromLocalProfile p) p'
|
||||
where
|
||||
processContact user' ct = do
|
||||
let mergedProfile = userProfileToSend user Nothing $ Just ct
|
||||
ct' = updateMergedPreferences user' ct
|
||||
mergedProfile' = userProfileToSend user' Nothing $ Just ct'
|
||||
when (mergedProfile' /= mergedProfile) $ do
|
||||
void $ sendDirectContactMessage ct' (XInfo mergedProfile')
|
||||
when (directOrUsed ct') $ createSndFeatureItems user' ct ct'
|
||||
updateContactPrefs :: User -> Contact -> Preferences -> m ChatResponse
|
||||
updateContactPrefs user@User {userId} ct@Contact {activeConn = Connection {customUserProfileId}, userPreferences = contactUserPrefs} contactUserPrefs'
|
||||
| contactUserPrefs == contactUserPrefs' = pure $ CRContactPrefsUpdated ct ct
|
||||
@@ -1724,7 +1756,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)]
|
||||
@@ -1734,7 +1766,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
|
||||
|
||||
@@ -1867,18 +1899,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
|
||||
@@ -1899,8 +1932,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
|
||||
@@ -1916,15 +1949,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 ()
|
||||
@@ -2037,14 +2071,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
|
||||
@@ -2114,7 +2150,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
|
||||
@@ -2132,9 +2169,12 @@ processAgentMessage (Just user@User {userId}) corrId agentConnId agentMessage =
|
||||
showToast ("#" <> gName) $ "member " <> localDisplayName (m :: GroupMember) <> " is connected"
|
||||
intros <- withStore' $ \db -> createIntroductions db members m
|
||||
void . sendGroupMessage gInfo members . XGrpMemNew $ memberInfo m
|
||||
forM_ intros $ \intro@GroupMemberIntro {introId} -> do
|
||||
void $ sendDirectMessage conn (XGrpMemIntro . memberInfo $ reMember intro) (GroupId groupId)
|
||||
withStore' $ \db -> updateIntroStatus db introId GMIntroSent
|
||||
forM_ intros $ \intro ->
|
||||
processIntro intro `catchError` (toView . CRChatError)
|
||||
where
|
||||
processIntro intro@GroupMemberIntro {introId} = do
|
||||
void $ sendDirectMessage conn (XGrpMemIntro . memberInfo $ reMember intro) (GroupId groupId)
|
||||
withStore' $ \db -> updateIntroStatus db introId GMIntroSent
|
||||
_ -> do
|
||||
-- TODO send probe and decide whether to use existing contact connection or the new contact connection
|
||||
-- TODO notify member who forwarded introduction - question - where it is stored? There is via_contact but probably there should be via_member in group_members table
|
||||
@@ -2183,15 +2223,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
|
||||
@@ -2229,13 +2271,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} ->
|
||||
@@ -2274,9 +2316,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 ()
|
||||
@@ -2323,8 +2367,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
|
||||
@@ -2332,9 +2376,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 ()
|
||||
@@ -2363,6 +2409,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
|
||||
@@ -2398,11 +2453,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
|
||||
@@ -2486,7 +2545,7 @@ processAgentMessage (Just user@User {userId}) corrId agentConnId agentMessage =
|
||||
(filePath, fileStatus) <- case inline of
|
||||
Just IFMSent -> do
|
||||
fPath <- getRcvFilePath fileId Nothing fileName
|
||||
withStore' $ \db -> startRcvInlineFT db user ft fPath
|
||||
withStore' $ \db -> startRcvInlineFT db user ft fPath inline
|
||||
pure (Just fPath, CIFSRcvAccepted)
|
||||
_ -> pure (Nothing, CIFSRcvInvitation)
|
||||
pure CIFile {fileId, fileName, fileSize, filePath, fileStatus}
|
||||
@@ -3215,8 +3274,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
|
||||
|
||||
@@ -3233,9 +3291,9 @@ cancelRcvFileTransfer user ft@RcvFileTransfer {fileId, fileStatus, rcvFileInline
|
||||
updateRcvFileStatus db ft FSCancelled
|
||||
deleteRcvFileChunks db ft
|
||||
when (isNothing rcvFileInline) $ case fileStatus of
|
||||
RFSAccepted RcvFileInfo {connId, agentConnId} ->
|
||||
RFSAccepted RcvFileInfo {connId = Just connId, agentConnId = Just agentConnId} ->
|
||||
deleteAgentConnectionAsync' user connId agentConnId
|
||||
RFSConnected RcvFileInfo {connId, agentConnId} ->
|
||||
RFSConnected RcvFileInfo {connId = Just connId, agentConnId = Just agentConnId} ->
|
||||
deleteAgentConnectionAsync' user connId agentConnId
|
||||
_ -> pure ()
|
||||
|
||||
@@ -3245,13 +3303,13 @@ cancelSndFile user FileTransferMeta {fileId} fts = do
|
||||
forM_ fts $ \ft' -> cancelSndFileTransfer user ft'
|
||||
|
||||
cancelSndFileTransfer :: ChatMonad m => User -> SndFileTransfer -> m ()
|
||||
cancelSndFileTransfer user ft@SndFileTransfer {connId, agentConnId = agentConnId@(AgentConnId acId), fileStatus} =
|
||||
cancelSndFileTransfer user ft@SndFileTransfer {connId, agentConnId = agentConnId@(AgentConnId acId), fileStatus, fileInline} =
|
||||
unless (fileStatus == FSCancelled || fileStatus == FSComplete) $ do
|
||||
withStore' $ \db -> do
|
||||
updateSndFileStatus db ft FSCancelled
|
||||
deleteSndFileChunks db ft
|
||||
withAgent $ \a -> void (sendMessage a acId SMP.noMsgFlags $ smpEncode FileChunkCancel) `catchError` \_ -> pure ()
|
||||
deleteAgentConnectionAsync' user connId agentConnId
|
||||
when (isNothing fileInline) $ deleteAgentConnectionAsync' user connId agentConnId
|
||||
|
||||
closeFileHandle :: ChatMonad m => Int64 -> (ChatController -> TVar (Map Int64 Handle)) -> m ()
|
||||
closeFileHandle fileId files = do
|
||||
@@ -3276,13 +3334,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
|
||||
|
||||
@@ -3301,7 +3360,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,31 +3370,36 @@ sendGroupMessage GroupInfo {groupId} members chatMsgEvent =
|
||||
|
||||
sendGroupMessage' :: (MsgEncodingI e, ChatMonad m) => [GroupMember] -> ChatMsgEvent e -> Int64 -> Maybe Int64 -> m () -> m SndMessage
|
||||
sendGroupMessage' members chatMsgEvent groupId introId_ postDeliver = do
|
||||
msg@SndMessage {msgId, msgBody} <- createSndMessage chatMsgEvent (GroupId groupId)
|
||||
msg <- createSndMessage chatMsgEvent (GroupId groupId)
|
||||
-- TODO collect failed deliveries into a single error
|
||||
forM_ (filter memberCurrent members) $ \m@GroupMember {groupMemberId} ->
|
||||
case memberConn m of
|
||||
forM_ (filter memberCurrent members) $ \m ->
|
||||
messageMember m msg `catchError` (toView . CRChatError)
|
||||
pure msg
|
||||
where
|
||||
messageMember m@GroupMember {groupMemberId} SndMessage {msgId, msgBody} = 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 ()
|
||||
deliverMessage conn tag msgBody msgId >> postDeliver
|
||||
| otherwise -> withStore' $ \db -> createPendingGroupMessage db groupMemberId msgId introId_
|
||||
pure msg
|
||||
|
||||
sendPendingGroupMessages :: ChatMonad m => GroupMember -> Connection -> m ()
|
||||
sendPendingGroupMessages GroupMember {groupMemberId, localDisplayName} conn = do
|
||||
pendingMessages <- withStore' $ \db -> getPendingGroupMessages db groupMemberId
|
||||
-- TODO ensure order - pending messages interleave with user input messages
|
||||
forM_ pendingMessages $ \PendingGroupMessage {msgId, cmEventTag = ACMEventTag _ tag, msgBody, introId_} -> do
|
||||
void $ deliverMessage conn tag msgBody msgId
|
||||
withStore' $ \db -> deletePendingGroupMessage db groupMemberId msgId
|
||||
case tag of
|
||||
XGrpMemFwd_ -> case introId_ of
|
||||
Just introId -> withStore' $ \db -> updateIntroStatus db introId GMIntroInvForwarded
|
||||
_ -> throwChatError $ CEGroupMemberIntroNotFound localDisplayName
|
||||
_ -> pure ()
|
||||
forM_ pendingMessages $ \pgm ->
|
||||
processPendingMessage pgm `catchError` (toView . CRChatError)
|
||||
where
|
||||
processPendingMessage PendingGroupMessage {msgId, cmEventTag = ACMEventTag _ tag, msgBody, introId_} = do
|
||||
void $ deliverMessage conn tag msgBody msgId
|
||||
withStore' $ \db -> deletePendingGroupMessage db groupMemberId msgId
|
||||
case tag of
|
||||
XGrpMemFwd_ -> case introId_ of
|
||||
Just introId -> withStore' $ \db -> updateIntroStatus db introId GMIntroInvForwarded
|
||||
_ -> throwChatError $ CEGroupMemberIntroNotFound localDisplayName
|
||||
_ -> pure ()
|
||||
|
||||
saveRcvMSG :: ChatMonad m => Connection -> ConnOrGroupId -> MsgMeta -> MsgBody -> CommandId -> m RcvMessage
|
||||
saveRcvMSG Connection {connId} connOrGroupId agentMsgMeta msgBody agentAckCmdId = do
|
||||
@@ -3341,7 +3407,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
|
||||
@@ -3604,22 +3672,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 =
|
||||
@@ -3709,10 +3780,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,
|
||||
@@ -3744,7 +3819,7 @@ chatCommandP =
|
||||
"/show link #" *> (ShowGroupLink <$> displayName),
|
||||
(">#" <|> "> #") *> (SendGroupMessageQuote <$> displayName <* A.space <*> pure Nothing <*> quotedMsg <*> A.takeByteString),
|
||||
(">#" <|> "> #") *> (SendGroupMessageQuote <$> displayName <* A.space <* char_ '@' <*> (Just <$> displayName) <* A.space <*> quotedMsg <*> A.takeByteString),
|
||||
("/contacts" <|> "/cs") $> ListContacts,
|
||||
"/contacts" $> ListContacts,
|
||||
("/connect " <|> "/c ") *> (Connect <$> ((Just <$> strP) <|> A.takeByteString $> Nothing)),
|
||||
("/connect" <|> "/c") $> AddContact,
|
||||
SendMessage <$> chatNameP <* A.space <*> A.takeByteString,
|
||||
@@ -3754,6 +3829,7 @@ chatCommandP =
|
||||
("\\ " <|> "\\") *> (DeleteMessage <$> chatNameP <* A.space <*> A.takeByteString),
|
||||
("! " <|> "!") *> (EditMessage <$> chatNameP <* A.space <*> (quotedMsg <|> pure "") <*> A.takeByteString),
|
||||
"/feed " *> (SendMessageBroadcast <$> A.takeByteString),
|
||||
("/chats" <|> "/cs") *> (LastChats <$> (" all" $> Nothing <|> Just <$> (A.space *> A.decimal <|> pure 20))),
|
||||
("/tail" <|> "/t") *> (LastMessages <$> optional (A.space *> chatNameP) <*> msgCountP <*> pure Nothing),
|
||||
("/search" <|> "/?") *> (LastMessages <$> optional (A.space *> chatNameP) <*> msgCountP <*> (Just <$> (A.space *> stringP))),
|
||||
"/last_item_id" *> (LastChatItemId <$> optional (A.space *> chatNameP) <*> (A.space *> A.decimal <|> pure 0)),
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
{-# LANGUAGE DeriveGeneric #-}
|
||||
{-# LANGUAGE DuplicateRecordFields #-}
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE StrictData #-}
|
||||
{-# LANGUAGE TemplateHaskell #-}
|
||||
|
||||
module Simplex.Chat.Controller where
|
||||
|
||||
@@ -30,9 +30,11 @@ import Data.Map.Strict (Map)
|
||||
import Data.String
|
||||
import Data.Text (Text)
|
||||
import Data.Time (ZonedTime)
|
||||
import Data.Time.Clock (UTCTime)
|
||||
import Data.Time.Clock (UTCTime, getCurrentTime)
|
||||
import Data.Time.Format (defaultTimeLocale, formatTime, iso8601DateFormat)
|
||||
import Data.Version (showVersion)
|
||||
import GHC.Generics (Generic)
|
||||
import Language.Haskell.TH (Exp, Q, runIO)
|
||||
import Numeric.Natural
|
||||
import qualified Paths_simplex_chat as SC
|
||||
import Simplex.Chat.Call
|
||||
@@ -53,6 +55,7 @@ import Simplex.Messaging.Notifications.Protocol (DeviceToken (..), NtfTknStatus)
|
||||
import Simplex.Messaging.Parsers (dropPrefix, enumJSON, parseAll, parseString, sumTypeJSON)
|
||||
import Simplex.Messaging.Protocol (AProtocolType, CorrId, MsgFlags)
|
||||
import Simplex.Messaging.TMap (TMap)
|
||||
import Simplex.Messaging.Transport (simplexMQVersion)
|
||||
import Simplex.Messaging.Transport.Client (TransportHost)
|
||||
import System.IO (Handle)
|
||||
import System.Mem.Weak (Weak)
|
||||
@@ -61,12 +64,40 @@ import UnliftIO.STM
|
||||
versionNumber :: String
|
||||
versionNumber = showVersion SC.version
|
||||
|
||||
versionStr :: String
|
||||
versionStr = "SimpleX Chat v" <> versionNumber
|
||||
versionString :: String -> String
|
||||
versionString version = "SimpleX Chat v" <> version
|
||||
|
||||
updateStr :: String
|
||||
updateStr = "To update run: curl -o- https://raw.githubusercontent.com/simplex-chat/simplex-chat/master/install.sh | bash"
|
||||
|
||||
buildTimestampQ :: Q Exp
|
||||
buildTimestampQ = do
|
||||
s <- formatTime defaultTimeLocale (iso8601DateFormat $ Just "%H:%M:%S") <$> runIO getCurrentTime
|
||||
[|fromString s|]
|
||||
|
||||
simplexmqCommitQ :: Q Exp
|
||||
simplexmqCommitQ = do
|
||||
s <- either error B.unpack . A.parseOnly commitHashP <$> runIO (B.readFile "./cabal.project")
|
||||
[|fromString s|]
|
||||
where
|
||||
commitHashP :: A.Parser ByteString
|
||||
commitHashP =
|
||||
A.manyTill' A.anyChar "location: https://github.com/simplex-chat/simplexmq.git"
|
||||
*> A.takeWhile (== ' ')
|
||||
*> A.endOfLine
|
||||
*> A.takeWhile (== ' ')
|
||||
*> "tag: "
|
||||
*> A.takeWhile (A.notInClass " \r\n")
|
||||
|
||||
coreVersionInfo :: String -> String -> CoreVersionInfo
|
||||
coreVersionInfo buildTimestamp simplexmqCommit =
|
||||
CoreVersionInfo
|
||||
{ version = versionNumber,
|
||||
buildTimestamp,
|
||||
simplexmqVersion = simplexMQVersion,
|
||||
simplexmqCommit
|
||||
}
|
||||
|
||||
data ChatConfig = ChatConfig
|
||||
{ agentConfig :: AgentConfig,
|
||||
yesToMigrations :: Bool,
|
||||
@@ -77,6 +108,7 @@ data ChatConfig = ChatConfig
|
||||
subscriptionConcurrency :: Int,
|
||||
subscriptionEvents :: Bool,
|
||||
hostEvents :: Bool,
|
||||
logLevel :: ChatLogLevel,
|
||||
testView :: Bool
|
||||
}
|
||||
|
||||
@@ -210,6 +242,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 +253,8 @@ data ChatCommand
|
||||
| GetGroupMemberCode GroupName ContactName
|
||||
| VerifyContact ContactName (Maybe Text)
|
||||
| VerifyGroupMember GroupName ContactName (Maybe Text)
|
||||
| EnableContact ContactName
|
||||
| EnableGroupMember GroupName ContactName
|
||||
| ChatHelp HelpSection
|
||||
| Welcome
|
||||
| AddContact
|
||||
@@ -257,6 +293,7 @@ data ChatCommand
|
||||
| DeleteGroupLink GroupName
|
||||
| ShowGroupLink GroupName
|
||||
| SendGroupMessageQuote {groupName :: GroupName, contactName_ :: Maybe ContactName, quotedMsg :: ByteString, message :: ByteString}
|
||||
| LastChats (Maybe Int)
|
||||
| LastMessages (Maybe ChatName) Int (Maybe String)
|
||||
| LastChatItemId (Maybe ChatName) Int
|
||||
| ShowChatItem (Maybe ChatItemId)
|
||||
@@ -291,6 +328,7 @@ data ChatResponse
|
||||
| CRChatStopped
|
||||
| CRChatSuspended
|
||||
| CRApiChats {chats :: [AChat]}
|
||||
| CRChats {chats :: [AChat]}
|
||||
| CRApiChat {chat :: AChat}
|
||||
| CRChatItems {chatItems :: [AChatItem]}
|
||||
| CRChatItemId (Maybe ChatItemId)
|
||||
@@ -330,7 +368,7 @@ data ChatResponse
|
||||
| CRFileTransferStatus (FileTransfer, [Integer]) -- TODO refactor this type to FileTransferStatus
|
||||
| CRUserProfile {profile :: Profile}
|
||||
| CRUserProfileNoChange
|
||||
| CRVersionInfo {version :: String}
|
||||
| CRVersionInfo {versionInfo :: CoreVersionInfo}
|
||||
| CRInvitation {connReqInvitation :: ConnReqInvitation}
|
||||
| CRSentConfirmation
|
||||
| CRSentInvitation {customUserProfile :: Maybe Profile}
|
||||
@@ -414,6 +452,7 @@ data ChatResponse
|
||||
| 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}
|
||||
@@ -538,9 +577,22 @@ tmeToPref currentTTL tme = uncurry TimedMessagesPreference $ case tme of
|
||||
TMEEnableKeepTTL -> (FAYes, currentTTL)
|
||||
TMEDisableKeepTTL -> (FANo, currentTTL)
|
||||
|
||||
data ChatLogLevel = CLLDebug | CLLInfo | CLLWarning | CLLError | CLLImportant
|
||||
deriving (Eq, Ord, Show)
|
||||
|
||||
data CoreVersionInfo = CoreVersionInfo
|
||||
{ version :: String,
|
||||
buildTimestamp :: String,
|
||||
simplexmqVersion :: String,
|
||||
simplexmqCommit :: String
|
||||
}
|
||||
deriving (Show, Generic)
|
||||
|
||||
instance ToJSON CoreVersionInfo where toEncoding = J.genericToEncoding J.defaultOptions
|
||||
|
||||
data ChatError
|
||||
= ChatError {errorType :: ChatErrorType}
|
||||
| ChatErrorAgent {agentError :: AgentErrorType}
|
||||
| ChatErrorAgent {agentError :: AgentErrorType, connectionEntity_ :: Maybe ConnectionEntity}
|
||||
| ChatErrorStore {storeError :: StoreError}
|
||||
| ChatErrorDatabase {databaseError :: DatabaseError}
|
||||
deriving (Show, Exception, Generic)
|
||||
@@ -558,6 +610,8 @@ data ChatErrorType
|
||||
| CEInvalidConnReq
|
||||
| CEInvalidChatMessage {message :: String}
|
||||
| CEContactNotReady {contact :: Contact}
|
||||
| CEContactDisabled {contact :: Contact}
|
||||
| CEConnectionDisabled {connection :: Connection}
|
||||
| CEGroupUserRole
|
||||
| CEContactIncognitoCantInvite
|
||||
| CEGroupIncognitoCantInvite
|
||||
|
||||
@@ -87,7 +87,7 @@ chatHelpInfo =
|
||||
indent <> highlight "/help <topic> " <> " - help on: " <> listHighlight ["messages", "files", "groups", "address", "settings"],
|
||||
indent <> highlight "/profile " <> " - show / update user profile",
|
||||
indent <> highlight "/delete <contact>" <> " - delete contact and all messages with them",
|
||||
indent <> highlight "/contacts " <> " - list contacts",
|
||||
indent <> highlight "/chats " <> " - most recent chats",
|
||||
indent <> highlight "/markdown " <> " - supported markdown syntax",
|
||||
indent <> highlight "/version " <> " - SimpleX Chat version",
|
||||
indent <> highlight "/quit " <> " - quit chat",
|
||||
@@ -153,7 +153,11 @@ messagesHelpInfo :: [StyledString]
|
||||
messagesHelpInfo =
|
||||
map
|
||||
styleMarkdown
|
||||
[ green "Show recent messages",
|
||||
[ green "Show recent chats",
|
||||
indent <> highlight "/chats [N] " <> " - the most recent N conversations (20 by default)",
|
||||
indent <> highlight "/chats all " <> " - all conversations",
|
||||
"",
|
||||
green "Show recent messages",
|
||||
indent <> highlight "/tail @alice [N]" <> " - the last N messages with alice (10 by default)",
|
||||
indent <> highlight "/tail #team [N] " <> " - the last N messages in the group team",
|
||||
indent <> highlight "/tail [N] " <> " - the last N messages in all chats",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -10,10 +10,10 @@ m20221012_inline_files =
|
||||
[sql|
|
||||
DROP INDEX idx_messages_direct_shared_msg_id;
|
||||
|
||||
ALTER TABLE files ADD COLUMN file_inline TEXT;
|
||||
ALTER TABLE rcv_files ADD COLUMN rcv_file_inline TEXT;
|
||||
ALTER TABLE rcv_files ADD COLUMN file_inline TEXT;
|
||||
ALTER TABLE snd_files ADD COLUMN file_inline TEXT;
|
||||
ALTER TABLE files ADD COLUMN file_inline TEXT; -- based on offer, determined by file sender for both sides
|
||||
ALTER TABLE rcv_files ADD COLUMN rcv_file_inline TEXT; -- actual mode when receiving file, determined when invitation is accepted
|
||||
ALTER TABLE rcv_files ADD COLUMN file_inline TEXT; -- based on offer, determined when invitation is processed
|
||||
ALTER TABLE snd_files ADD COLUMN file_inline TEXT; -- actual mode when sending file, determined when invitation is accepted
|
||||
ALTER TABLE snd_files ADD COLUMN last_inline_msg_delivery_id INTEGER;
|
||||
|
||||
CREATE UNIQUE INDEX idx_snd_files_last_inline_msg_delivery_id ON snd_files(last_inline_msg_delivery_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
|
||||
|
||||
@@ -123,9 +123,11 @@ mobileChatOpts =
|
||||
dbKey = "",
|
||||
smpServers = [],
|
||||
networkConfig = defaultNetworkConfig,
|
||||
logLevel = CLLImportant,
|
||||
logConnections = False,
|
||||
logServerHosts = True,
|
||||
logAgent = False,
|
||||
tbqSize = 64,
|
||||
chatCmd = "",
|
||||
chatCmdDelay = 3,
|
||||
chatServerPort = Nothing,
|
||||
|
||||
+45
-11
@@ -1,5 +1,6 @@
|
||||
{-# LANGUAGE ApplicativeDo #-}
|
||||
{-# LANGUAGE DuplicateRecordFields #-}
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
@@ -14,8 +15,9 @@ where
|
||||
|
||||
import qualified Data.Attoparsec.ByteString.Char8 as A
|
||||
import qualified Data.ByteString.Char8 as B
|
||||
import Numeric.Natural (Natural)
|
||||
import Options.Applicative
|
||||
import Simplex.Chat.Controller (updateStr, versionStr)
|
||||
import Simplex.Chat.Controller (ChatLogLevel (..), updateStr, versionNumber, versionString)
|
||||
import Simplex.Messaging.Client (NetworkConfig (..), defaultNetworkConfig)
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Parsers (parseAll)
|
||||
@@ -28,9 +30,11 @@ data ChatOpts = ChatOpts
|
||||
dbKey :: String,
|
||||
smpServers :: [SMPServerWithAuth],
|
||||
networkConfig :: NetworkConfig,
|
||||
logLevel :: ChatLogLevel,
|
||||
logConnections :: Bool,
|
||||
logServerHosts :: Bool,
|
||||
logAgent :: Bool,
|
||||
tbqSize :: Natural,
|
||||
chatCmd :: String,
|
||||
chatCmdDelay :: Int,
|
||||
chatServerPort :: Maybe String,
|
||||
@@ -84,27 +88,45 @@ chatOpts appDir defaultDbFileName = do
|
||||
<> help "TCP timeout, seconds (default: 5/10 without/with SOCKS5 proxy)"
|
||||
<> value 0
|
||||
)
|
||||
logLevel <-
|
||||
option
|
||||
parseLogLevel
|
||||
( long "log-level"
|
||||
<> short 'l'
|
||||
<> metavar "LEVEL"
|
||||
<> help "Log level: debug, info, warn, error, important (default)"
|
||||
<> value CLLImportant
|
||||
)
|
||||
logTLSErrors <-
|
||||
switch
|
||||
( long "log-tls-errors"
|
||||
<> help "Log TLS errors"
|
||||
<> help "Log TLS errors (also enabled with `-l debug`)"
|
||||
)
|
||||
logConnections <-
|
||||
switch
|
||||
( long "connections"
|
||||
<> short 'c'
|
||||
<> help "Log every contact and group connection on start"
|
||||
<> help "Log every contact and group connection on start (also with `-l info`)"
|
||||
)
|
||||
logServerHosts <-
|
||||
switch
|
||||
( long "log-hosts"
|
||||
<> short 'l'
|
||||
<> help "Log connections to servers"
|
||||
<> help "Log connections to servers (also with `-l info`)"
|
||||
)
|
||||
logAgent <-
|
||||
switch
|
||||
( long "log-agent"
|
||||
<> help "Enable logs from SMP agent"
|
||||
<> help "Enable logs from SMP agent (also with `-l debug`)"
|
||||
)
|
||||
tbqSize <-
|
||||
option
|
||||
auto
|
||||
( long "queue-size"
|
||||
<> short 'q'
|
||||
<> metavar "SIZE"
|
||||
<> help "Internal queue size"
|
||||
<> value 64
|
||||
<> showDefault
|
||||
)
|
||||
chatCmd <-
|
||||
strOption
|
||||
@@ -139,7 +161,7 @@ chatOpts appDir defaultDbFileName = do
|
||||
( long "files-folder"
|
||||
<> metavar "FOLDER"
|
||||
<> help "Folder to use for sent and received files"
|
||||
)
|
||||
)
|
||||
allowInstantFiles <-
|
||||
switch
|
||||
( long "allow-instant-files"
|
||||
@@ -157,10 +179,12 @@ chatOpts appDir defaultDbFileName = do
|
||||
{ dbFilePrefix,
|
||||
dbKey,
|
||||
smpServers,
|
||||
networkConfig = fullNetworkConfig socksProxy (useTcpTimeout socksProxy t) logTLSErrors,
|
||||
logConnections,
|
||||
logServerHosts,
|
||||
logAgent,
|
||||
networkConfig = fullNetworkConfig socksProxy (useTcpTimeout socksProxy t) (logTLSErrors || logLevel == CLLDebug),
|
||||
logLevel,
|
||||
logConnections = logConnections || logLevel <= CLLInfo,
|
||||
logServerHosts = logServerHosts || logLevel <= CLLInfo,
|
||||
logAgent = logAgent || logLevel == CLLDebug,
|
||||
tbqSize,
|
||||
chatCmd,
|
||||
chatCmdDelay,
|
||||
chatServerPort,
|
||||
@@ -192,6 +216,15 @@ serverPortP = Just . B.unpack <$> A.takeWhile A.isDigit
|
||||
smpServersP :: A.Parser [SMPServerWithAuth]
|
||||
smpServersP = strP `A.sepBy1` A.char ';'
|
||||
|
||||
parseLogLevel :: ReadM ChatLogLevel
|
||||
parseLogLevel = eitherReader $ \case
|
||||
"debug" -> Right CLLDebug
|
||||
"info" -> Right CLLInfo
|
||||
"warn" -> Right CLLWarning
|
||||
"error" -> Right CLLError
|
||||
"important" -> Right CLLImportant
|
||||
_ -> Left "Invalid log level"
|
||||
|
||||
getChatOpts :: FilePath -> FilePath -> IO ChatOpts
|
||||
getChatOpts appDir defaultDbFileName =
|
||||
execParser $
|
||||
@@ -199,5 +232,6 @@ getChatOpts appDir defaultDbFileName =
|
||||
(helper <*> versionOption <*> chatOpts appDir defaultDbFileName)
|
||||
(header versionStr <> fullDesc <> progDesc "Start chat with DB_FILE file and use SERVER as SMP server")
|
||||
where
|
||||
versionStr = versionString versionNumber
|
||||
versionOption = infoOption versionAndUpdate (long "version" <> short 'v' <> help "Show version")
|
||||
versionAndUpdate = versionStr <> "\n" <> updateStr
|
||||
|
||||
+57
-48
@@ -48,6 +48,8 @@ module Simplex.Chat.Store
|
||||
updateContactUnreadChat,
|
||||
updateGroupUnreadChat,
|
||||
setConnectionVerified,
|
||||
incConnectionAuthErrCounter,
|
||||
setConnectionAuthErrCounter,
|
||||
getUserContacts,
|
||||
getUserContactProfiles,
|
||||
createUserContactLink,
|
||||
@@ -324,6 +326,7 @@ 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)
|
||||
@@ -383,7 +386,8 @@ schemaMigrations =
|
||||
("20221214_live_message", m20221214_live_message),
|
||||
("20221222_chat_ts", m20221222_chat_ts),
|
||||
("20221223_idx_chat_items_item_status", m20221223_idx_chat_items_item_status),
|
||||
("20221230_idxs", m20221230_idxs)
|
||||
("20221230_idxs", m20221230_idxs),
|
||||
("20230107_connections_auth_err_counter", m20230107_connections_auth_err_counter)
|
||||
]
|
||||
|
||||
-- | The list of migrations in ascending order by date
|
||||
@@ -495,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
|
||||
@@ -570,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
|
||||
|
||||
@@ -764,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
|
||||
@@ -861,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
|
||||
@@ -875,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
|
||||
@@ -1008,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 = ?
|
||||
@@ -1112,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
|
||||
@@ -1250,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
|
||||
@@ -1270,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
|
||||
@@ -1311,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 = ?
|
||||
@@ -1322,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
|
||||
@@ -1340,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]
|
||||
@@ -1512,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 = ?
|
||||
|]
|
||||
@@ -1612,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 = ?
|
||||
|]
|
||||
@@ -1657,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
|
||||
@@ -1937,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 = (
|
||||
@@ -2097,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 = (
|
||||
@@ -2402,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)
|
||||
@@ -2435,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 = (
|
||||
@@ -2726,14 +2743,14 @@ createRcvGroupFileTransfer db userId GroupMember {groupId, groupMemberId, localD
|
||||
pure RcvFileTransfer {fileId, fileInvitation = f, fileStatus = RFSNew, rcvFileInline, senderDisplayName = c, chunkSize, cancelled = False, grpMemberId = Just groupMemberId}
|
||||
|
||||
getRcvFileTransfer :: DB.Connection -> User -> Int64 -> ExceptT StoreError IO RcvFileTransfer
|
||||
getRcvFileTransfer db user@User {userId} fileId = do
|
||||
getRcvFileTransfer db User {userId} fileId = do
|
||||
rftRow <-
|
||||
ExceptT . firstRow id (SERcvFileNotFound fileId) $
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT r.file_status, r.file_queue_info, r.group_member_id, f.file_name,
|
||||
f.file_size, f.chunk_size, f.cancelled, cs.contact_id, cs.local_display_name, m.group_id, m.group_member_id, m.local_display_name,
|
||||
f.file_size, f.chunk_size, f.cancelled, cs.local_display_name, m.local_display_name,
|
||||
f.file_path, r.file_inline, r.rcv_file_inline, c.connection_id, c.agent_conn_id
|
||||
FROM rcv_files r
|
||||
JOIN files f USING (file_id)
|
||||
@@ -2746,11 +2763,11 @@ getRcvFileTransfer db user@User {userId} fileId = do
|
||||
rcvFileTransfer rftRow
|
||||
where
|
||||
rcvFileTransfer ::
|
||||
(FileStatus, Maybe ConnReqInvitation, Maybe Int64, String, Integer, Integer, Maybe Bool) :. (Maybe Int64, Maybe ContactName, Maybe Int64, Maybe Int64, Maybe ContactName, Maybe FilePath, Maybe InlineFileMode, Maybe InlineFileMode) :. (Maybe Int64, Maybe AgentConnId) ->
|
||||
(FileStatus, Maybe ConnReqInvitation, Maybe Int64, String, Integer, Integer, Maybe Bool) :. (Maybe ContactName, Maybe ContactName, Maybe FilePath, Maybe InlineFileMode, Maybe InlineFileMode) :. (Maybe Int64, Maybe AgentConnId) ->
|
||||
ExceptT StoreError IO RcvFileTransfer
|
||||
rcvFileTransfer ((fileStatus', fileConnReq, grpMemberId, fileName, fileSize, chunkSize, cancelled_) :. (contactId_, contactName_, groupId_, groupMemberId_, memberName_, filePath_, fileInline, rcvFileInline) :. (connId_, agentConnId_)) = do
|
||||
rcvFileTransfer ((fileStatus', fileConnReq, grpMemberId, fileName, fileSize, chunkSize, cancelled_) :. (contactName_, memberName_, filePath_, fileInline, rcvFileInline) :. (connId_, agentConnId_)) = do
|
||||
let fileInv = FileInvitation {fileName, fileSize, fileConnReq, fileInline}
|
||||
fileInfo = (filePath_, connId_, agentConnId_, contactId_, groupId_, groupMemberId_, isJust fileInline)
|
||||
fileInfo = (filePath_, connId_, agentConnId_)
|
||||
case contactName_ <|> memberName_ of
|
||||
Nothing -> throwError $ SERcvFileInvalid fileId
|
||||
Just name -> do
|
||||
@@ -2765,22 +2782,14 @@ getRcvFileTransfer db user@User {userId} fileId = do
|
||||
RcvFileTransfer {fileId, fileInvitation, fileStatus, rcvFileInline, senderDisplayName, chunkSize, cancelled, grpMemberId}
|
||||
rfi fileInfo = maybe (throwError $ SERcvFileInvalid fileId) pure =<< rfi_ fileInfo
|
||||
rfi_ = \case
|
||||
(Just filePath, Just connId, Just agentConnId, _, _, _, _) -> pure $ Just RcvFileInfo {filePath, connId, agentConnId}
|
||||
(Just filePath, Nothing, Nothing, Just contactId, _, _, True) -> do
|
||||
Contact {activeConn = Connection {connId, agentConnId}} <- getContact db user contactId
|
||||
pure $ Just RcvFileInfo {filePath, connId, agentConnId}
|
||||
(Just filePath, Nothing, Nothing, _, Just groupId, Just groupMemberId, True) -> do
|
||||
getGroupMember db user groupId groupMemberId >>= \case
|
||||
GroupMember {activeConn = Just Connection {connId, agentConnId}} ->
|
||||
pure $ Just RcvFileInfo {filePath, connId, agentConnId}
|
||||
_ -> pure Nothing
|
||||
(Just filePath, connId, agentConnId) -> pure $ Just RcvFileInfo {filePath, connId, agentConnId}
|
||||
_ -> pure Nothing
|
||||
cancelled = fromMaybe False cancelled_
|
||||
|
||||
acceptRcvFileTransfer :: DB.Connection -> User -> Int64 -> (CommandId, ConnId) -> ConnStatus -> FilePath -> ExceptT StoreError IO AChatItem
|
||||
acceptRcvFileTransfer db user@User {userId} fileId (cmdId, acId) connStatus filePath = ExceptT $ do
|
||||
currentTs <- getCurrentTime
|
||||
acceptRcvFT_ db user fileId filePath currentTs
|
||||
acceptRcvFT_ db user fileId filePath Nothing currentTs
|
||||
DB.execute
|
||||
db
|
||||
"INSERT INTO connections (agent_conn_id, conn_status, conn_type, rcv_file_id, user_id, created_at, updated_at) VALUES (?,?,?,?,?,?,?)"
|
||||
@@ -2800,23 +2809,23 @@ getContactByFileId db user@User {userId} fileId = do
|
||||
|
||||
acceptRcvInlineFT :: DB.Connection -> User -> Int64 -> FilePath -> ExceptT StoreError IO AChatItem
|
||||
acceptRcvInlineFT db user fileId filePath = do
|
||||
liftIO $ acceptRcvFT_ db user fileId filePath =<< getCurrentTime
|
||||
liftIO $ acceptRcvFT_ db user fileId filePath (Just IFMOffer) =<< getCurrentTime
|
||||
getChatItemByFileId db user fileId
|
||||
|
||||
startRcvInlineFT :: DB.Connection -> User -> RcvFileTransfer -> FilePath -> IO ()
|
||||
startRcvInlineFT db user RcvFileTransfer {fileId} filePath =
|
||||
acceptRcvFT_ db user fileId filePath =<< getCurrentTime
|
||||
startRcvInlineFT :: DB.Connection -> User -> RcvFileTransfer -> FilePath -> Maybe InlineFileMode -> IO ()
|
||||
startRcvInlineFT db user RcvFileTransfer {fileId} filePath rcvFileInline =
|
||||
acceptRcvFT_ db user fileId filePath rcvFileInline =<< getCurrentTime
|
||||
|
||||
acceptRcvFT_ :: DB.Connection -> User -> Int64 -> FilePath -> UTCTime -> IO ()
|
||||
acceptRcvFT_ db User {userId} fileId filePath currentTs = do
|
||||
acceptRcvFT_ :: DB.Connection -> User -> Int64 -> FilePath -> Maybe InlineFileMode -> UTCTime -> IO ()
|
||||
acceptRcvFT_ db User {userId} fileId filePath rcvFileInline currentTs = do
|
||||
DB.execute
|
||||
db
|
||||
"UPDATE files SET file_path = ?, ci_file_status = ?, updated_at = ? WHERE user_id = ? AND file_id = ?"
|
||||
(filePath, CIFSRcvAccepted, currentTs, userId, fileId)
|
||||
DB.execute
|
||||
db
|
||||
"UPDATE rcv_files SET file_status = ?, updated_at = ? WHERE file_id = ?"
|
||||
(FSAccepted, currentTs, fileId)
|
||||
"UPDATE rcv_files SET rcv_file_inline = ?, file_status = ?, updated_at = ? WHERE file_id = ?"
|
||||
(rcvFileInline, FSAccepted, currentTs, fileId)
|
||||
|
||||
updateRcvFileStatus :: DB.Connection -> RcvFileTransfer -> FileStatus -> IO ()
|
||||
updateRcvFileStatus db RcvFileTransfer {fileId} status = do
|
||||
@@ -3350,7 +3359,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
|
||||
@@ -3673,7 +3682,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
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{-# LANGUAGE FlexibleInstances #-}
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module Simplex.Chat.Styled
|
||||
( StyledString (..),
|
||||
@@ -9,6 +10,7 @@ module Simplex.Chat.Styled
|
||||
unStyle,
|
||||
sLength,
|
||||
sShow,
|
||||
sTake,
|
||||
)
|
||||
where
|
||||
|
||||
@@ -25,7 +27,7 @@ data StyledString = Styled [SGR] String | StyledString :<>: StyledString
|
||||
|
||||
instance Semigroup StyledString where (<>) = (:<>:)
|
||||
|
||||
instance Monoid StyledString where mempty = plain ""
|
||||
instance Monoid StyledString where mempty = ""
|
||||
|
||||
instance IsString StyledString where fromString = plain
|
||||
|
||||
@@ -34,7 +36,7 @@ styleMarkdown (s1 :|: s2) = styleMarkdown s1 <> styleMarkdown s2
|
||||
styleMarkdown (Markdown f s) = styleFormat f s
|
||||
|
||||
styleMarkdownList :: MarkdownList -> StyledString
|
||||
styleMarkdownList [] = plain ""
|
||||
styleMarkdownList [] = ""
|
||||
styleMarkdownList [FormattedText f s] = styleFormat f s
|
||||
styleMarkdownList (FormattedText f s : ts) = styleFormat f s <> styleMarkdownList ts
|
||||
|
||||
@@ -82,3 +84,15 @@ unStyle (s1 :<>: s2) = unStyle s1 <> unStyle s2
|
||||
sLength :: StyledString -> Int
|
||||
sLength (Styled _ s) = length s
|
||||
sLength (s1 :<>: s2) = sLength s1 + sLength s2
|
||||
|
||||
sTake :: Int -> StyledString -> StyledString
|
||||
sTake n = go Nothing 0
|
||||
where
|
||||
go res len = \case
|
||||
Styled f s ->
|
||||
let s' = Styled f $ take (n - len) s
|
||||
in maybe id (<>) res s'
|
||||
s1 :<>: s2 ->
|
||||
let s1' = go res len s1
|
||||
len' = sLength s1'
|
||||
in if len' >= n then s1' else go (Just s1') len' s2
|
||||
|
||||
@@ -112,10 +112,9 @@ runTerminalOutput ct cc@ChatController {outputQ, showLiveItems} = do
|
||||
|
||||
printRespToTerminal :: ChatTerminal -> ChatController -> Bool -> ChatResponse -> IO ()
|
||||
printRespToTerminal ct cc liveItems r = do
|
||||
let testV = testView $ config cc
|
||||
user <- readTVarIO $ currentUser cc
|
||||
ts <- getCurrentTime
|
||||
printToTerminal ct $ responseToView user testV liveItems ts r
|
||||
printToTerminal ct $ responseToView user (config cc) liveItems ts r
|
||||
|
||||
printToTerminal :: ChatTerminal -> [StyledString] -> IO ()
|
||||
printToTerminal ct s =
|
||||
|
||||
@@ -1481,8 +1481,8 @@ instance ToJSON RcvFileStatus where
|
||||
|
||||
data RcvFileInfo = RcvFileInfo
|
||||
{ filePath :: FilePath,
|
||||
connId :: Int64,
|
||||
agentConnId :: AgentConnId
|
||||
connId :: Maybe Int64,
|
||||
agentConnId :: Maybe AgentConnId
|
||||
}
|
||||
deriving (Eq, Show, Generic)
|
||||
|
||||
@@ -1494,7 +1494,8 @@ liveRcvFileTransferConnId RcvFileTransfer {fileStatus} = case fileStatus of
|
||||
RFSConnected fi -> acId fi
|
||||
_ -> Nothing
|
||||
where
|
||||
acId RcvFileInfo {agentConnId = AgentConnId cId} = Just cId
|
||||
acId RcvFileInfo {agentConnId = Just (AgentConnId cId)} = Just cId
|
||||
acId _ = Nothing
|
||||
|
||||
newtype AgentConnId = AgentConnId ConnId
|
||||
deriving (Eq, Show)
|
||||
@@ -1603,10 +1604,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)
|
||||
|
||||
|
||||
+81
-19
@@ -28,7 +28,7 @@ import Data.Time.LocalTime (ZonedTime (..), localDay, localTimeOfDay, timeOfDayT
|
||||
import GHC.Generics (Generic)
|
||||
import qualified Network.HTTP.Types as Q
|
||||
import Numeric (showFFloat)
|
||||
import Simplex.Chat (maxImageSize)
|
||||
import Simplex.Chat (defaultChatConfig, maxImageSize)
|
||||
import Simplex.Chat.Call
|
||||
import Simplex.Chat.Controller
|
||||
import Simplex.Chat.Help
|
||||
@@ -48,22 +48,23 @@ import Simplex.Messaging.Parsers (dropPrefix, taggedObjectJSON)
|
||||
import Simplex.Messaging.Protocol (AProtocolType, ProtocolServer (..))
|
||||
import qualified Simplex.Messaging.Protocol as SMP
|
||||
import Simplex.Messaging.Transport.Client (TransportHost (..))
|
||||
import Simplex.Messaging.Util (bshow)
|
||||
import Simplex.Messaging.Util (bshow, tshow)
|
||||
import System.Console.ANSI.Types
|
||||
|
||||
type CurrentTime = UTCTime
|
||||
|
||||
serializeChatResponse :: Maybe User -> CurrentTime -> ChatResponse -> String
|
||||
serializeChatResponse user_ ts = unlines . map unStyle . responseToView user_ False False ts
|
||||
serializeChatResponse user_ ts = unlines . map unStyle . responseToView user_ defaultChatConfig False ts
|
||||
|
||||
responseToView :: Maybe User -> Bool -> Bool -> CurrentTime -> ChatResponse -> [StyledString]
|
||||
responseToView user_ testView liveItems ts = \case
|
||||
responseToView :: Maybe User -> ChatConfig -> Bool -> CurrentTime -> ChatResponse -> [StyledString]
|
||||
responseToView user_ ChatConfig {logLevel, testView} liveItems ts = \case
|
||||
CRActiveUser User {profile} -> viewUserProfile $ fromLocalProfile profile
|
||||
CRChatStarted -> ["chat started"]
|
||||
CRChatRunning -> ["chat is running"]
|
||||
CRChatStopped -> ["chat stopped"]
|
||||
CRChatSuspended -> ["chat suspended"]
|
||||
CRApiChats chats -> if testView then testViewChats chats else [plain . bshow $ J.encode chats]
|
||||
CRChats chats -> viewChats ts chats
|
||||
CRApiChat chat -> if testView then testViewChat chat else [plain . bshow $ J.encode chat]
|
||||
CRApiParsedMarkdown ft -> [plain . bshow $ J.encode ft]
|
||||
CRUserSMPServers smpServers _ -> viewSMPServers (L.toList smpServers) testView
|
||||
@@ -111,8 +112,8 @@ responseToView user_ testView liveItems ts = \case
|
||||
CRFileTransferStatus ftStatus -> viewFileTransferStatus ftStatus
|
||||
CRUserProfile p -> viewUserProfile p
|
||||
CRUserProfileNoChange -> ["user profile did not change"]
|
||||
CRVersionInfo _ -> [plain versionStr, plain updateStr]
|
||||
CRChatCmdError e -> viewChatError e
|
||||
CRVersionInfo info -> viewVersionInfo logLevel info
|
||||
CRChatCmdError e -> viewChatError logLevel e
|
||||
CRInvitation cReq -> viewConnReqInvitation cReq
|
||||
CRSentConfirmation -> ["confirmation sent!"]
|
||||
CRSentInvitation customUserProfile -> viewSentInvitation customUserProfile testView
|
||||
@@ -217,8 +218,9 @@ responseToView user_ testView liveItems ts = \case
|
||||
plain $ "agent locks: " <> LB.unpack (J.encode agentLocks)
|
||||
]
|
||||
CRAgentStats stats -> map (plain . intercalate ",") stats
|
||||
CRMessageError prefix err -> [plain prefix <> ": " <> plain err]
|
||||
CRChatError e -> viewChatError e
|
||||
CRConnectionDisabled entity -> viewConnectionEntityDisabled entity
|
||||
CRMessageError prefix err -> [plain prefix <> ": " <> plain err | prefix == "error" || logLevel <= CLLWarning]
|
||||
CRChatError e -> viewChatError logLevel e
|
||||
where
|
||||
testViewChats :: [AChat] -> [StyledString]
|
||||
testViewChats chats = [sShow $ map toChatView chats]
|
||||
@@ -271,6 +273,20 @@ showSMPServer = B.unpack . strEncode . host
|
||||
viewHostEvent :: AProtocolType -> TransportHost -> String
|
||||
viewHostEvent p h = map toUpper (B.unpack $ strEncode p) <> " host " <> B.unpack (strEncode h)
|
||||
|
||||
viewChats :: CurrentTime -> [AChat] -> [StyledString]
|
||||
viewChats ts = concatMap chatPreview . reverse
|
||||
where
|
||||
chatPreview (AChat _ (Chat chat items _)) = case items of
|
||||
CChatItem _ ci : _ -> case viewChatItem chat ci True ts of
|
||||
s : _ -> [let s' = sTake 120 s in if sLength s' < sLength s then s' <> "..." else s']
|
||||
_ -> chatName
|
||||
_ -> chatName
|
||||
where
|
||||
chatName = case chat of
|
||||
DirectChat ct -> [" " <> ttyToContact' ct]
|
||||
GroupChat g -> [" " <> ttyToGroup g]
|
||||
_ -> []
|
||||
|
||||
viewChatItem :: forall c d. MsgDirectionI d => ChatInfo c -> ChatItem c d -> Bool -> CurrentTime -> [StyledString]
|
||||
viewChatItem chat ChatItem {chatDir, meta = meta@CIMeta {itemDeleted}, content, quotedItem, file} doShow ts =
|
||||
withItemDeleted <$> case chat of
|
||||
@@ -1126,8 +1142,17 @@ instance ToJSON WCallCommand where
|
||||
toEncoding = J.genericToEncoding . taggedObjectJSON $ dropPrefix "WCCall"
|
||||
toJSON = J.genericToJSON . taggedObjectJSON $ dropPrefix "WCCall"
|
||||
|
||||
viewChatError :: ChatError -> [StyledString]
|
||||
viewChatError = \case
|
||||
viewVersionInfo :: ChatLogLevel -> CoreVersionInfo -> [StyledString]
|
||||
viewVersionInfo logLevel CoreVersionInfo {version, buildTimestamp, simplexmqVersion, simplexmqCommit} =
|
||||
map plain $
|
||||
if logLevel <= CLLInfo
|
||||
then [versionString version <> parens buildTimestamp, updateStr, "simplexmq: " <> simplexmqVersion <> parens simplexmqCommit]
|
||||
else [versionString version, updateStr]
|
||||
where
|
||||
parens s = " (" <> s <> ")"
|
||||
|
||||
viewChatError :: ChatLogLevel -> ChatError -> [StyledString]
|
||||
viewChatError logLevel = \case
|
||||
ChatError err -> case err of
|
||||
CENoActiveUser -> ["error: active user is required"]
|
||||
CEActiveUserExists -> ["error: active user already exists"]
|
||||
@@ -1137,6 +1162,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 Connection {connId, connType} -> [plain $ "connection " <> textEncode connType <> " (" <> tshow connId <> ") is disabled" | logLevel <= CLLWarning]
|
||||
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"]
|
||||
@@ -1190,7 +1217,7 @@ viewChatError = \case
|
||||
SEUserContactLinkNotFound -> ["no chat address, to create: " <> highlight' "/ad"]
|
||||
SEContactRequestNotFoundByName c -> ["no contact request from " <> ttyContact c]
|
||||
SEFileIdNotFoundBySharedMsgId _ -> [] -- recipient tried to accept cancelled file
|
||||
SEConnectionNotFound _ -> [] -- TODO mutes delete group error, but also mutes any error from getConnectionEntity
|
||||
SEConnectionNotFound agentConnId -> ["event connection not found, agent ID: " <> sShow agentConnId | logLevel <= CLLWarning] -- mutes delete group error
|
||||
SEQuotedChatItemNotFound -> ["message not found - reply is not sent"]
|
||||
SEDuplicateGroupLink g -> ["you already have link for this group, to show: " <> highlight ("/show link #" <> groupName' g)]
|
||||
SEGroupLinkNotFound g -> ["no group link, to create: " <> highlight ("/create link #" <> groupName' g)]
|
||||
@@ -1201,21 +1228,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]
|
||||
AGENT A_DUPLICATE -> [withConnEntity <> "error: AGENT A_DUPLICATE" | logLevel == CLLDebug]
|
||||
AGENT A_PROHIBITED -> [withConnEntity <> "error: AGENT A_PROHIBITED" | logLevel <= CLLWarning]
|
||||
CONN NOT_FOUND -> [withConnEntity <> "error: CONN NOT_FOUND" | logLevel <= CLLWarning]
|
||||
e -> [withConnEntity <> "smp agent error: " <> sShow e | logLevel <= CLLWarning]
|
||||
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
|
||||
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ extra-deps:
|
||||
# - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561
|
||||
# - ../simplexmq
|
||||
- github: simplex-chat/simplexmq
|
||||
commit: 874f9f5cd67310ddfbe5540d6abfa5943a534f37
|
||||
commit: 56eea29ec36ba723fda6ae27a18e03a4e5aad6e8
|
||||
# - ../direct-sqlcipher
|
||||
- github: simplex-chat/direct-sqlcipher
|
||||
commit: 34309410eb2069b029b8fc1872deb1e0db123294
|
||||
|
||||
+3
-1
@@ -19,7 +19,7 @@ import Data.Maybe (fromJust, isNothing)
|
||||
import qualified Data.Text as T
|
||||
import Network.Socket
|
||||
import Simplex.Chat
|
||||
import Simplex.Chat.Controller (ChatConfig (..), ChatController (..), ChatDatabase (..))
|
||||
import Simplex.Chat.Controller (ChatConfig (..), ChatController (..), ChatDatabase (..), ChatLogLevel (..))
|
||||
import Simplex.Chat.Core
|
||||
import Simplex.Chat.Options
|
||||
import Simplex.Chat.Store
|
||||
@@ -53,9 +53,11 @@ testOpts =
|
||||
-- dbKey = "this is a pass-phrase to encrypt the database",
|
||||
smpServers = ["smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:5001"],
|
||||
networkConfig = defaultNetworkConfig,
|
||||
logLevel = CLLImportant,
|
||||
logConnections = False,
|
||||
logServerHosts = False,
|
||||
logAgent = False,
|
||||
tbqSize = 64,
|
||||
chatCmd = "",
|
||||
chatCmdDelay = 3,
|
||||
chatServerPort = Nothing,
|
||||
|
||||
+167
-33
@@ -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
|
||||
@@ -85,6 +88,7 @@ chatTests = do
|
||||
describe "sending and receiving files" $ do
|
||||
describe "send and receive file" $ fileTestMatrix2 runTestFileTransfer
|
||||
it "send and receive file inline (without accepting)" testInlineFileTransfer
|
||||
it "accept inline file transfer, sender cancels during transfer" testAcceptInlineFileSndCancelDuringTransfer
|
||||
it "send and receive small file inline (default config)" testSmallInlineFileTransfer
|
||||
it "small file sent without acceptance is ignored in terminal by default" testSmallInlineFileIgnored
|
||||
it "receive file inline with inline=on option" testReceiveInline
|
||||
@@ -156,10 +160,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
|
||||
@@ -324,13 +330,13 @@ testDeleteContactDeletesProfile =
|
||||
-- alice deletes contact, profile is deleted
|
||||
alice ##> "/d bob"
|
||||
alice <## "bob: contact is deleted"
|
||||
alice ##> "/cs"
|
||||
alice ##> "/contacts"
|
||||
(alice </)
|
||||
alice `hasContactProfiles` ["alice"]
|
||||
-- bob deletes contact, profile is deleted
|
||||
bob ##> "/d alice"
|
||||
bob <## "alice: contact is deleted"
|
||||
bob ##> "/cs"
|
||||
bob ##> "/contacts"
|
||||
(bob </)
|
||||
bob `hasContactProfiles` ["bob"]
|
||||
|
||||
@@ -495,6 +501,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
|
||||
@@ -571,7 +613,7 @@ testGroupShared alice bob cath checkMessages = do
|
||||
"cath (Catherine): admin, invited, connected"
|
||||
]
|
||||
-- list contacts
|
||||
alice ##> "/cs"
|
||||
alice ##> "/contacts"
|
||||
alice <## "bob (Bob)"
|
||||
alice <## "cath (Catherine)"
|
||||
-- remove member
|
||||
@@ -1004,6 +1046,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 +1098,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 +1415,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 $
|
||||
@@ -1487,28 +1555,28 @@ testGroupDeleteUnusedContacts =
|
||||
bob <## "use @cath <message> to send messages"
|
||||
]
|
||||
-- list contacts
|
||||
bob ##> "/cs"
|
||||
bob ##> "/contacts"
|
||||
bob <## "alice (Alice)"
|
||||
bob <## "cath (Catherine)"
|
||||
cath ##> "/cs"
|
||||
cath ##> "/contacts"
|
||||
cath <## "alice (Alice)"
|
||||
cath <## "bob (Bob)"
|
||||
-- delete group 1, contacts and profiles are kept
|
||||
deleteGroup alice bob cath "team"
|
||||
bob ##> "/cs"
|
||||
bob ##> "/contacts"
|
||||
bob <## "alice (Alice)"
|
||||
bob <## "cath (Catherine)"
|
||||
bob `hasContactProfiles` ["alice", "bob", "cath"]
|
||||
cath ##> "/cs"
|
||||
cath ##> "/contacts"
|
||||
cath <## "alice (Alice)"
|
||||
cath <## "bob (Bob)"
|
||||
cath `hasContactProfiles` ["alice", "bob", "cath"]
|
||||
-- delete group 2, unused contacts and profiles are deleted
|
||||
deleteGroup alice bob cath "club"
|
||||
bob ##> "/cs"
|
||||
bob ##> "/contacts"
|
||||
bob <## "alice (Alice)"
|
||||
bob `hasContactProfiles` ["alice", "bob"]
|
||||
cath ##> "/cs"
|
||||
cath ##> "/contacts"
|
||||
cath <## "alice (Alice)"
|
||||
cath `hasContactProfiles` ["alice", "cath"]
|
||||
where
|
||||
@@ -1839,6 +1907,37 @@ testInlineFileTransfer =
|
||||
where
|
||||
cfg = testCfg {inlineFiles = defaultInlineFilesConfig {offerChunks = 100, sendChunks = 100, receiveChunks = 100}}
|
||||
|
||||
testAcceptInlineFileSndCancelDuringTransfer :: IO ()
|
||||
testAcceptInlineFileSndCancelDuringTransfer =
|
||||
testChatCfg2 cfg aliceProfile bobProfile $ \alice bob -> do
|
||||
connectUsers alice bob
|
||||
bob ##> "/_files_folder ./tests/tmp/"
|
||||
bob <## "ok"
|
||||
alice #> "/f @bob ./tests/fixtures/test_1MB.pdf"
|
||||
alice <## "use /fc 1 to cancel sending"
|
||||
bob <# "alice> sends file test_1MB.pdf (1017.7 KiB / 1042157 bytes)"
|
||||
bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
|
||||
bob ##> "/fr 1 inline=on"
|
||||
bob <## "saving file 1 from alice to test_1MB.pdf"
|
||||
alice <## "started sending file 1 (test_1MB.pdf) to bob"
|
||||
bob <## "started receiving file 1 (test_1MB.pdf) from alice"
|
||||
alice ##> "/fc 1" -- test that inline file cancel doesn't delete contact connection
|
||||
concurrentlyN_
|
||||
[ do
|
||||
alice <##. "cancelled sending file 1 (test_1MB.pdf)"
|
||||
alice <## "completed sending file 1 (test_1MB.pdf) to bob",
|
||||
bob <## "completed receiving file 1 (test_1MB.pdf) from alice"
|
||||
]
|
||||
_ <- getTermLine alice
|
||||
alice #> "@bob hi"
|
||||
bob #> "@alice hey"
|
||||
_ <- getTermLine bob
|
||||
bob <## "alice cancelled sending file 1 (test_1MB.pdf)"
|
||||
bob <# "alice> hi"
|
||||
alice <# "bob> hey"
|
||||
where
|
||||
cfg = testCfg {inlineFiles = defaultInlineFilesConfig {offerChunks = 100, receiveChunks = 50}}
|
||||
|
||||
testSmallInlineFileTransfer :: IO ()
|
||||
testSmallInlineFileTransfer =
|
||||
testChat2 aliceProfile bobProfile $ \alice bob -> do
|
||||
@@ -2846,25 +2945,25 @@ testConnectIncognitoInvitationLink = testChat3 aliceProfile bobProfile cathProfi
|
||||
bob <## (aliceIncognito <> " updated preferences for you:")
|
||||
bob <## "Full deletion: off (you allow: no, contact allows: no)"
|
||||
-- list contacts
|
||||
alice ##> "/cs"
|
||||
alice ##> "/contacts"
|
||||
alice
|
||||
<### [ ConsoleString $ "i " <> bobIncognito,
|
||||
"cath (Catherine)"
|
||||
]
|
||||
alice `hasContactProfiles` ["alice", T.pack aliceIncognito, T.pack bobIncognito, "cath"]
|
||||
bob ##> "/cs"
|
||||
bob ##> "/contacts"
|
||||
bob <## ("i " <> aliceIncognito)
|
||||
bob `hasContactProfiles` ["bob", T.pack aliceIncognito, T.pack bobIncognito]
|
||||
-- alice deletes contact, incognito profile is deleted
|
||||
alice ##> ("/d " <> bobIncognito)
|
||||
alice <## (bobIncognito <> ": contact is deleted")
|
||||
alice ##> "/cs"
|
||||
alice ##> "/contacts"
|
||||
alice <## "cath (Catherine)"
|
||||
alice `hasContactProfiles` ["alice", "cath"]
|
||||
-- bob deletes contact, incognito profile is deleted
|
||||
bob ##> ("/d " <> aliceIncognito)
|
||||
bob <## (aliceIncognito <> ": contact is deleted")
|
||||
bob ##> "/cs"
|
||||
bob ##> "/contacts"
|
||||
(bob </)
|
||||
bob `hasContactProfiles` ["bob"]
|
||||
|
||||
@@ -2897,13 +2996,13 @@ testConnectIncognitoContactAddress = testChat2 aliceProfile bobProfile $
|
||||
bob ?#> "@alice I'm Batman"
|
||||
alice <# (bobIncognito <> "> I'm Batman")
|
||||
-- list contacts
|
||||
bob ##> "/cs"
|
||||
bob ##> "/contacts"
|
||||
bob <## "i alice (Alice)"
|
||||
bob `hasContactProfiles` ["alice", "bob", T.pack bobIncognito]
|
||||
-- delete contact, incognito profile is deleted
|
||||
bob ##> "/d alice"
|
||||
bob <## "alice: contact is deleted"
|
||||
bob ##> "/cs"
|
||||
bob ##> "/contacts"
|
||||
(bob </)
|
||||
bob `hasContactProfiles` ["bob"]
|
||||
|
||||
@@ -2932,13 +3031,13 @@ testAcceptContactRequestIncognito = testChat2 aliceProfile bobProfile $
|
||||
bob #> ("@" <> aliceIncognito <> " I know!")
|
||||
alice ?<# "bob> I know!"
|
||||
-- list contacts
|
||||
alice ##> "/cs"
|
||||
alice ##> "/contacts"
|
||||
alice <## "i bob (Bob)"
|
||||
alice `hasContactProfiles` ["alice", "bob", T.pack aliceIncognito]
|
||||
-- delete contact, incognito profile is deleted
|
||||
alice ##> "/d bob"
|
||||
alice <## "bob: contact is deleted"
|
||||
alice ##> "/cs"
|
||||
alice ##> "/contacts"
|
||||
(alice </)
|
||||
alice `hasContactProfiles` ["alice"]
|
||||
|
||||
@@ -3243,13 +3342,13 @@ testDeleteContactThenGroupDeletesIncognitoProfile = testChat2 aliceProfile bobPr
|
||||
concurrently_
|
||||
(alice <## ("#team: " <> bobIncognito <> " joined the group"))
|
||||
(bob <## ("#team: you joined the group incognito as " <> bobIncognito))
|
||||
bob ##> "/cs"
|
||||
bob ##> "/contacts"
|
||||
bob <## "i alice (Alice)"
|
||||
bob `hasContactProfiles` ["alice", "bob", T.pack bobIncognito]
|
||||
-- delete contact
|
||||
bob ##> "/d alice"
|
||||
bob <## "alice: contact is deleted"
|
||||
bob ##> "/cs"
|
||||
bob ##> "/contacts"
|
||||
(bob </)
|
||||
bob `hasContactProfiles` ["alice", "bob", T.pack bobIncognito]
|
||||
-- delete group
|
||||
@@ -3295,7 +3394,7 @@ testDeleteGroupThenContactDeletesIncognitoProfile = testChat2 aliceProfile bobPr
|
||||
concurrently_
|
||||
(alice <## ("#team: " <> bobIncognito <> " joined the group"))
|
||||
(bob <## ("#team: you joined the group incognito as " <> bobIncognito))
|
||||
bob ##> "/cs"
|
||||
bob ##> "/contacts"
|
||||
bob <## "i alice (Alice)"
|
||||
bob `hasContactProfiles` ["alice", "bob", T.pack bobIncognito]
|
||||
-- delete group
|
||||
@@ -3312,7 +3411,7 @@ testDeleteGroupThenContactDeletesIncognitoProfile = testChat2 aliceProfile bobPr
|
||||
-- delete contact
|
||||
bob ##> "/d alice"
|
||||
bob <## "alice: contact is deleted"
|
||||
bob ##> "/cs"
|
||||
bob ##> "/contacts"
|
||||
(bob </)
|
||||
bob `hasContactProfiles` ["bob"]
|
||||
|
||||
@@ -3321,10 +3420,10 @@ testSetAlias = testChat2 aliceProfile bobProfile $
|
||||
\alice bob -> do
|
||||
connectUsers alice bob
|
||||
alice #$> ("/_set alias @2 my friend bob", id, "contact bob alias updated: my friend bob")
|
||||
alice ##> "/cs"
|
||||
alice ##> "/contacts"
|
||||
alice <## "bob (Bob) (alias: my friend bob)"
|
||||
alice #$> ("/_set alias @2", id, "contact bob alias removed")
|
||||
alice ##> "/cs"
|
||||
alice ##> "/contacts"
|
||||
alice <## "bob (Bob)"
|
||||
|
||||
testSetConnectionAlias :: IO ()
|
||||
@@ -3341,7 +3440,7 @@ testSetConnectionAlias = testChat2 aliceProfile bobProfile $
|
||||
(alice <## "bob (Bob): contact is connected")
|
||||
(bob <## "alice (Alice): contact is connected")
|
||||
alice @@@ [("@bob", "Voice messages: enabled")]
|
||||
alice ##> "/cs"
|
||||
alice ##> "/contacts"
|
||||
alice <## "bob (Bob) (alias: friend)"
|
||||
|
||||
testSetContactPrefs :: IO ()
|
||||
@@ -3945,6 +4044,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 ->
|
||||
@@ -4247,11 +4374,11 @@ testMuteContact =
|
||||
bob <## "ok"
|
||||
alice #> "@bob hi"
|
||||
(bob </)
|
||||
bob ##> "/cs"
|
||||
bob ##> "/contacts"
|
||||
bob <## "alice (Alice) (muted, you can /unmute @alice)"
|
||||
bob ##> "/unmute alice"
|
||||
bob <## "ok"
|
||||
bob ##> "/cs"
|
||||
bob ##> "/contacts"
|
||||
bob <## "alice (Alice)"
|
||||
alice #> "@bob hi again"
|
||||
bob <# "alice> hi again"
|
||||
@@ -4669,16 +4796,16 @@ testGroupLinkUnusedHostContactDeleted =
|
||||
]
|
||||
]
|
||||
-- list contacts
|
||||
bob ##> "/cs"
|
||||
bob ##> "/contacts"
|
||||
bob <## "alice (Alice)"
|
||||
-- delete group 1, host contact and profile are kept
|
||||
bobLeaveDeleteGroup alice bob "team"
|
||||
bob ##> "/cs"
|
||||
bob ##> "/contacts"
|
||||
bob <## "alice (Alice)"
|
||||
bob `hasContactProfiles` ["alice", "bob"]
|
||||
-- delete group 2, unused host contact and profile are deleted
|
||||
bobLeaveDeleteGroup alice bob "club"
|
||||
bob ##> "/cs"
|
||||
bob ##> "/contacts"
|
||||
(bob </)
|
||||
bob `hasContactProfiles` ["bob"]
|
||||
where
|
||||
@@ -4702,18 +4829,18 @@ testGroupLinkIncognitoUnusedHostContactsDeleted =
|
||||
bobIncognitoClub <- createGroupBobIncognito alice bob "club" "alice_1"
|
||||
bobIncognitoTeam `shouldNotBe` bobIncognitoClub
|
||||
-- list contacts
|
||||
bob ##> "/cs"
|
||||
bob ##> "/contacts"
|
||||
bob <## "i alice (Alice)"
|
||||
bob <## "i alice_1 (Alice)"
|
||||
bob `hasContactProfiles` ["alice", "alice", "bob", T.pack bobIncognitoTeam, T.pack bobIncognitoClub]
|
||||
-- delete group 1, unused host contact and profile are deleted
|
||||
bobLeaveDeleteGroup alice bob "team" bobIncognitoTeam
|
||||
bob ##> "/cs"
|
||||
bob ##> "/contacts"
|
||||
bob <## "i alice_1 (Alice)"
|
||||
bob `hasContactProfiles` ["alice", "bob", T.pack bobIncognitoClub]
|
||||
-- delete group 2, unused host contact and profile are deleted
|
||||
bobLeaveDeleteGroup alice bob "club" bobIncognitoClub
|
||||
bob ##> "/cs"
|
||||
bob ##> "/contacts"
|
||||
(bob </)
|
||||
bob `hasContactProfiles` ["bob"]
|
||||
where
|
||||
@@ -5062,6 +5189,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
|
||||
|
||||
@@ -32,7 +32,7 @@ activeUserExists = "{\"resp\":{\"type\":\"chatCmdError\",\"chatError\":{\"type\"
|
||||
|
||||
activeUser :: String
|
||||
#if defined(darwin_HOST_OS) && defined(swiftJSON)
|
||||
activeUser = "{\"resp\":{\"activeUser\":{\"user\":{\"userId\":1,\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"no\"},\"fullDelete\":{\"allow\":\"no\"},\"voice\":{\"allow\":\"yes\"}},\"activeUser\":true}}}"
|
||||
activeUser = "{\"resp\":{\"activeUser\":{\"user\":{\"userId\":1,\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"no\"},\"fullDelete\":{\"allow\":\"no\"},\"voice\":{\"allow\":\"yes\"}},\"activeUser\":true}}}}"
|
||||
#else
|
||||
activeUser = "{\"resp\":{\"type\":\"activeUser\",\"user\":{\"userId\":1,\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"no\"},\"fullDelete\":{\"allow\":\"no\"},\"voice\":{\"allow\":\"yes\"}},\"activeUser\":true}}}"
|
||||
#endif
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user