Compare commits

...

165 Commits

Author SHA1 Message Date
Evgeny Poberezkin 51bf2f413c 5.4.2: ios 186, android 166, desktop 20 2023-12-27 22:24:21 +00:00
Evgeny Poberezkin e3a69b12ba core: 5.4.2.1 (simplexmq 5.5.0.2) 2023-12-27 21:00:19 +00:00
Evgeny Poberezkin b220b5f6ec core: fix contact subscriptions (#3611) 2023-12-27 18:55:50 +00:00
Stanislav Dmitrenko 0b8346701c android, desktop: fix terminal items crash (#3607)
* android, desktop: fix terminal items crash

* test

* Revert "test"

This reverts commit 3f1e5c93ca.

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-12-27 14:59:38 +00:00
Evgeny Poberezkin efc873b09b core: min version for remote connection 5.4.2.0 (#3594) 2023-12-27 14:48:28 +00:00
Stanislav Dmitrenko 9f71502b51 desktop (windows): fix script that generates localization (#3606)
* desktop (windows): fix script that generates localization

* firstOrNull
2023-12-27 14:47:04 +00:00
Evgeny Poberezkin bbde6d81ee core: update simplexmq 2023-12-27 13:57:02 +00:00
Stanislav Dmitrenko 58906e1a60 desktop (windows): fixed handling non-utf8 Windows profile names (#3605) 2023-12-27 13:56:48 +00:00
Stanislav Dmitrenko ed3d234826 android, desktop: limit text length in terminal view (#3604) 2023-12-27 11:27:34 +00:00
Stanislav Dmitrenko dded56d8b8 ios: converting video to mp4 and making quality lower (#3597)
* ios: converting video to mp4 and making quality lower

Lower quality allows to play that videos on Android as well

* update export settings

---------

Co-authored-by: Avently <avently@local>
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-12-27 11:23:46 +00:00
spaced4ndy 4d5aefa82c ui: don't share address at onboarding by default (#3603)
* ios: don't share address at onboarding by default

* android
2023-12-27 10:54:44 +00:00
Evgeny Poberezkin 9ac99ec2d9 core: update simplexmq (mark failed work items to continue processing) (#3600)
* core: update simplexmq (mark failed work items to continue processing) WIP

* simplexmq
2023-12-26 19:53:58 +00:00
spaced4ndy 37d033c7a5 core: test group members connect in group when they were previously connected as contacts (#3595) 2023-12-25 11:03:02 +04:00
spaced4ndy 5798efcf71 code: modify test 2023-12-24 20:55:03 +04:00
spaced4ndy e086719f27 core: add to tests 2023-12-24 20:44:30 +04:00
spaced4ndy bb4293eb5e fix tests 2023-12-24 20:23:09 +04:00
Evgeny Poberezkin c3c66182f2 Merge branch 'stable' 2023-12-24 14:20:58 +00:00
Evgeny Poberezkin 5b90d92ca2 core: add group tests 2023-12-24 14:20:07 +00:00
Evgeny Poberezkin af22348bf8 core: use version from config, add tests (#3588)
* core: use version from config, add tests

* comment

* refactor
2023-12-24 13:27:51 +00:00
Stanislav Dmitrenko 5a6670998c android, desktop: loading prev messages better (#3585)
* android, desktop: loading prev messages better

* better

* better

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-12-24 12:22:00 +00:00
spaced4ndy 700f6fa663 core, docs: drop message views if they exist, remove mentions in docs (#3589)
* core, docs: drop message views if they exist, remove mentions in docs

* fix migration
2023-12-24 11:13:34 +00:00
Stanislav Dmitrenko d474cae705 ios: saving and sharing items menu item (#3581)
* ios: saving and sharing items menu item

* refactor

---------

Co-authored-by: Avently <avently@local>
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-12-23 23:15:31 +00:00
Evgeny Poberezkin 2834b192ce core: do not create group history item (#3586)
* core: do not create group history item

* fix tests
2023-12-23 19:56:01 +00:00
Evgeny Poberezkin b8cb954882 ios: update library 2023-12-23 17:24:30 +00:00
Evgeny Poberezkin 6aeef6f132 5.4.2.0: fix migration in simplexmq 2023-12-23 16:09:08 +00:00
Evgeny Poberezkin fa1702a566 5.4.2.0: update .cabal 2023-12-23 14:13:38 +00:00
Evgeny Poberezkin 95d6df926c 5.4.2.0 2023-12-23 13:46:11 +00:00
Evgeny Poberezkin cccd517277 core: fix simplexmq commit 2023-12-23 13:08:40 +00:00
spaced4ndy 12d1ada25e core: support batch sending in groups, batch introductions; send recent message history to new members (#3519)
* core: batch send stubs, comments

* multiple events in ChatMessage and supporting types

* Revert "multiple events in ChatMessage and supporting types"

This reverts commit 9b239b26ba.

* schema, refactor group processing for batched messages

* encoding, refactor processing

* refactor code to work with updated schema

* encoding, remove instances

* wip

* implement batching

* batch introductions

* wip

* collect and send message history

* missing new line

* rename

* test

* rework to build history via chat items

* refactor, tests

* correctly set member version range, dont include deleted items

* tests

* fix disappearing messages

* check number of errors

* comment

* check size in encodeChatMessage

* fix - don't check msg size for binary

* use builder

* rename

* rename

* rework batching

* lazy msg body

* use withStoreBatch

* refactor

* reverse batches

* comment

* possibly fix builder for single msg

* refactor batcher

* refactor

* dont repopulate msg_deliveries on down migration

* EncodedChatMessage type

* remove type

* batcher tests

* add tests

* group history preference

* test group link

* fix tests

* fix for random update

* add test testImageFitsSingleBatch

* refactor

* rename function

* refactor

* mconcat

* rename feature

* catch error on each batch

* refactor file inv retrieval

* refactor gathering item forward events

* refactor message batching

* unite migrations

* move files

* refactor

* Revert "unite migrations"

This reverts commit 0be7a3117a.

* refactor splitFileDescr

* improve tests

* Revert "dont repopulate msg_deliveries on down migration"

This reverts commit 2944c1cc28.

* fix down migration

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-12-23 13:07:23 +00:00
Evgeny Poberezkin f93f68e425 core: agent background mode for iOS NSE (#3574)
* core: agent background mode for iOS NSE

* change parameter for APIActivateChat

* fix

* update lib

* update lib

* simplexmq

* simplify
2023-12-23 13:06:59 +00:00
Andor Kesselman 23989aca57 Update README.md (#3553) 2023-12-22 08:48:26 +00:00
Andor Kesselman 57a6e85668 docs: fix typo (#3552) 2023-12-22 08:47:48 +00:00
Stanislav Dmitrenko 67590f3258 Revert "ios: making thumbnails faster" (#3571)
This reverts commit cd9cb8e064.
2023-12-22 08:46:55 +00:00
Stanislav Dmitrenko c83238c35a desktop: enable sending images and files with enter (#3582) 2023-12-21 22:48:32 +00:00
Stanislav Dmitrenko 8b0d2dede7 android, desktop: saving and sharing files menu item (#3580) 2023-12-21 15:19:36 +00:00
Stanislav Dmitrenko c4855313b6 android: splash screen with background color on Android 12+ (#3579) 2023-12-21 13:49:49 +00:00
Evgeny Poberezkin 2bff3b9c97 desktop, android: update api to pass controller when encrypting files (use ChaChaDRG as source of randomness) (#3578) 2023-12-21 12:49:18 +00:00
Evgeny Poberezkin aa037c0662 ios: update core library (uses GHC 9.6.3) 2023-12-21 10:05:43 +00:00
Evgeny Poberezkin d198d6a8db core: build iOS library with ghc 9.6.3 with iPhone7 etc. support (#3577)
* bump haskell.nix

* bump flake.lock

* Try openssl fix

* CFLAGS. not CCFLAGS

* Fix iOS build issues and improve static library handling

---------

Co-authored-by: Moritz Angermann <moritz.angermann@gmail.com>
2023-12-21 09:57:43 +00:00
Evgeny Poberezkin 7bcda7e54b core: use ChaChaDRG as the source of randomness (#3551)
* core: use ChaChaDRG as the source of randomness

* do not use entropy directly

* dont use RNG from agent

* simplexmq

* update iOS
2023-12-21 00:42:40 +00:00
Stanislav Dmitrenko 4a4d470859 android, desktop: try-catch composables (#3575)
* android, desktop: try-catch composables

* test

* better catching on Android

* more try-catch'es

* Revert "test"

This reverts commit adaf92b116.

* more try-catch'es

* unneeded imports
2023-12-20 18:00:44 +00:00
Evgeny Poberezkin 6ba3100d34 core: batch sending messages (#3566)
* core: batch sending messages

* batch without iorefs (#3573)

* one-pass

* simplexmq

* simplexmq

* simplexmq

* simplexmq

* revert change to ios project file

* refactor

* simplify

---------

Co-authored-by: Alexander Bondarenko <486682+dpwiz@users.noreply.github.com>
2023-12-20 10:38:39 +04:00
Evgeny Poberezkin 7b073ba9f8 core: allow deleting last user (#3567)
* core: allow deleting last user (tests fail)

* tests, allow activating the hidden user when there is no active user

* hide logs

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>

* comment

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>

* comment

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2023-12-19 10:26:01 +00:00
Stanislav Dmitrenko 5e042d222e desktop: saving qr code as an image (#3572) 2023-12-19 10:24:13 +00:00
Stanislav Dmitrenko 26a189917b sctipt: check string formatting (#3570)
* sctipt: check string formatting

* all
2023-12-18 21:37:10 +00:00
spaced4ndy ce9218b186 ios: rework authentication (#3556) 2023-12-18 22:04:49 +04:00
Evgeny Poberezkin f0338a03d1 directory: better search, allow both simplex:/ and simplex.chat links in description (#3546)
* directory: new commands

* better search

* search test

* return group links in simplex.chat domain, allow both simplex:/ and simplex.chat links in group description
2023-12-18 10:41:08 +00:00
Evgeny Poberezkin 6fa0001ea7 ios: delay suspendChat in NSE, background schedule depends on notifications mode (#3561)
* ios: delay suspendChat in NSE

* different background refresh interval depending on the settings

* simplify

* comment

* reduce NSE suspend interval

* space
2023-12-18 10:36:25 +00:00
Stanislav Dmitrenko 974fa448b4 android, desktop: some alerts became privacy sensitive (#3554)
* android, desktop: some alerts became privacy sensitive

* changes
2023-12-14 13:11:19 +00:00
spaced4ndy 8cec5428ee core: save CIContent tag in chat_items table (#3555) 2023-12-14 17:08:40 +04:00
Evgeny Poberezkin 73130bf321 ios: update core library 2023-12-13 21:48:25 +00:00
spaced4ndy 67241ff65c ios: fix code scanners only attempting to scan once (#3548) 2023-12-13 16:13:05 +04:00
spaced4ndy b6b041490f core: improve chat list pagination performance, simplify logic by always reading chat stats and last item id for previews (#3541)
* core: improve chat list pagination performance

* fix query

* core: improve chat list pagination performance, simplify logic by always reading chat stats (#3543)

* microseconds

* fix

* update simplexmq

* simplify queries

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-12-13 15:32:23 +04:00
Evgeny Poberezkin 7f9f9a674c ios: fix member view freezing on iOS 15, closes #3487 (#3547) 2023-12-13 11:27:28 +00:00
Evgeny Poberezkin ae94bb6f87 core: use crypton instead of cryptonite (#3542)
* update hackage

* use crypton instead of cryptonite

* remove http2 from cabal.project

* simplexmq
2023-12-13 11:20:03 +00:00
Evgeny Poberezkin 7ec39d1ffa all: increase default TCP timeouts, update simplexmq (#3540) 2023-12-12 13:13:36 +00:00
Evgeny Poberezkin ca6dfb5ea1 docs: update latest version 2023-12-12 10:25:02 +00:00
Evgeny Poberezkin a5048db6fa ios: improve media picker for multiple images/videos (#3538)
* ios: improve media picker to work with multiple images reliably

* MainActor
2023-12-12 09:04:48 +00:00
Evgeny Poberezkin aca3a71b38 ios: update library 2023-12-11 18:57:42 +00:00
Evgeny Poberezkin d358390e3d Merge pull request #3532 from simplex-chat/ios-notifications
improve iOS notifications
2023-12-11 14:55:03 +00:00
Evgeny Poberezkin f9a125bc32 Merge branch 'master' into ios-notifications 2023-12-11 14:11:00 +00:00
Alexander Bondarenko 35c1975d66 core: chat list pagination (#3505)
* add pagination args to APIGetChats

* add search to chat list API

* rename arg to paginationTs_ to match type

* lift another condition to ids query

* collect all chat refs before sorting, then get details

* split remaining preview functions

* roll back to collecting ids first with query cleanup

* add connection join back to filter out groups

* extract and expand tests

* add fav/unread args

* WIP

* lay out the queries with favs

* tweak tests

* add fav tests

* fix order by in the before case

* build query footer wholly from pagination

* add migration for direct contacts

* fix setting contact_used

* fix setting contact_used for group link contacts

* align search x filters space with UI, support filter by either favorite or unread, optimize queries, indexes

* always set chat_ts, fix tests

* refactor tests

* fix pagination logic, more tests

* refactor, rename

* increase default pagination count

* comments

* refactor

* comment

* report errors

* refactor

* remove unused type

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2023-12-11 17:50:32 +04:00
Evgeny Poberezkin 0bfe37137c core: update simplexmq (message notification markers) 2023-12-11 13:11:35 +00:00
Evgeny Poberezkin 666903ae76 Merge branch 'master' into ios-notifications 2023-12-11 13:06:32 +00:00
Evgeny Poberezkin 8a41a4c214 ios: do not start chat if it was stopped, deliver "app stopped" notifications (#3535)
* add stopped notifications, remove full off mode

* core: allow initializing chat data without starting chat

* ios: ask before starting chat if it was stopped

* correct text

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>

* fix comment

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2023-12-11 12:59:49 +00:00
Evgeny Poberezkin 79a954336c ios: communication between NSE and app via files (#3533)
* ios: communication between NSE and app via files

* clean up

* better concurrency
2023-12-11 12:34:56 +00:00
Evgeny Poberezkin f65b8a9e78 core: mark all user messages read (#3530) 2023-12-11 12:26:45 +00:00
Evgeny Poberezkin e8016adfdc simplexmq 2023-12-10 17:47:44 +00:00
Evgeny Poberezkin d3059afc99 ios, core: better notifications processing to avoid contention for database (#3485)
* core: forward notifications about message processing (for iOS notifications)

* simplexmq

* the option to keep database key, to allow re-opening the database

* export new init with keepKey and reopen DB api

* stop remote ctrl when suspending chat

* ios: close/re-open db on suspend/activate

* allow activating chat without restoring (for NSE)

* update NSE to suspend/activate (does not work)

* simplexmq

* suspend chat and close database when last notification in the process is processed

* stop reading notifications on message markers

* replace async stream with cancellable concurrent queue

* better synchronization of app and NSE

* remove outside of task

* remove unused var

* whitespace

* more debug logging, handle cancelled read after dequeue

* comments

* more comments
2023-12-09 21:59:40 +00:00
Evgeny Poberezkin 2f7632a70f 5.4.1: ios 185, android 164, desktop 19 2023-12-07 21:01:14 +00:00
Evgeny Poberezkin 27c14f32f1 core: 5.4.0.7 2023-12-07 14:30:42 +00:00
Stanislav Dmitrenko 13a32f7864 android: made minimum supported version of Android as 9 (#3525) 2023-12-07 10:49:16 +00:00
Stanislav Dmitrenko b1652b8930 desktop: fix toasts theme (#3524) 2023-12-06 21:19:30 +00:00
Stanislav Dmitrenko a9b36e8e39 desktop: fix onboarding when choosing random password (#3523) 2023-12-06 20:33:53 +00:00
Evgeny Poberezkin ee163a6540 core: add missing field 2023-12-06 12:34:10 +00:00
Evgeny Poberezkin 4fd6405113 core: update simplexmq (better suspend agent) 2023-12-06 00:19:24 +00:00
Stanislav Dmitrenko ccc62274ee android, desktop: crash handler addition (#3517)
* android, desktop: crash handler addition

* added
2023-12-05 22:50:25 +00:00
Stanislav Dmitrenko 4c6d52ba75 android, desktop: crash handler (#3516)
* android, desktop: crash handler

* test

* rename

* string

* Revert "test"

This reverts commit 530faf39c1.

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-12-05 21:31:49 +00:00
Evgeny Poberezkin 9df63160e5 ios: fix simplex address view (#3515)
* ios: fix simplex address view

* fix lib paths

* fix call
2023-12-05 09:48:04 +00:00
Stanislav Dmitrenko c8e9788c29 desktop: enhancements to remote desktop connect UI (#3513)
* desktop: enhancements to remote desktop connect UI

* changes

* more changes

This reverts commit e8323e8bfa.

* color

* random port
2023-12-04 21:04:58 +00:00
spaced4ndy 7099776357 docs: fix typo 2023-12-04 19:17:24 +04:00
Evgeny Poberezkin 3481d379c6 core: compatibility with GHC 8.10.7, narrow dependency ranges (#3503)
* Revert "raise lower bound on mtl to a real version (#3499)"

This reverts commit f94c0311c1.

* Revert "core: expand ranges to fit ghc 8.10 & 9.6 (#3496)"

This reverts commit 9a1c7f41f7.

* update simplexmq

* remove netword-transport fork

* simplexmq

* fix test

* fix index-state in cabal.project

* simplexmq

* simplexmq

* bytestring,simplexmq

* template-haskell, simplexmq

* simplexmq

* simplexmq

* mtl

* simplexmq
2023-12-04 10:01:37 +00:00
Evgeny Poberezkin 85c1e871dc Merge branch 'stable' 2023-12-04 00:06:53 +00:00
Evgeny Poberezkin fec5ff3f15 docs: SimpleX address (#3508)
* docs: SimpleX address

* table

* header
2023-12-03 22:21:13 +00:00
Evgeny Poberezkin acaa597c90 desktop, android: fix image not appearing in view when received (#3504)
* desktop, android: fix image not appearing in view when received

* change to KeyChangeEffect
2023-12-03 15:42:43 +00:00
Evgeny Poberezkin 6a9a67db14 cli: option to mark shown messages as read (off by default) (#3506)
* cli: option to mark shown messages as read (off by default)

* fix tests

* fix tests
2023-12-03 15:42:26 +00:00
Alexander Bondarenko f94c0311c1 raise lower bound on mtl to a real version (#3499)
* raise lower bound on mtl to a real version

* simplexmq

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-12-02 12:24:29 +00:00
Stanislav Dmitrenko e1ff7c88d7 desktop: allow changing listening ip and port of remote (#3498)
* desktop: allow changing listening ip and port of remote

* remove empty lines

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-12-01 20:41:08 +00:00
Alexander Bondarenko 9a1c7f41f7 core: expand ranges to fit ghc 8.10 & 9.6 (#3496)
* expand ranges to fit ghc 8.10 & 9.6

* update nix

* use hashes from mq master

* fix more deps

* use network-transport from hackage
2023-12-01 16:52:47 +00:00
Stanislav Dmitrenko 40e69ae713 desktop: enable database operations (#3495)
* desktop: enable database operations

* disconnect hosts button

* not relaying on dev tools

* different logic

* different logic 2

* toggle placement
2023-12-01 15:04:00 +00:00
spaced4ndy b74e33b958 docs: inactive group members rfc (#3419) 2023-12-01 19:02:50 +04:00
Stanislav Dmitrenko 540c8883a0 android: do not show alert too early in obboarding (#3493) 2023-11-30 19:39:16 +00:00
Stanislav Dmitrenko 0e18b13bea desktop: adapting onboarding process to linking devices (#3490)
* desktop: adapting onboarding process to linking devices

* show progress on long operations

* changes

* clearing chat cache logic

* lines

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-11-30 19:38:21 +00:00
spaced4ndy a4b44254bc core: update simplexmq (ghc 8.10.7 compatibility) (#3492) 2023-11-30 21:09:07 +04:00
spaced4ndy 5819e42305 core: remove CRNewContactConnection response; mobile, desktop: create pending connections based on api responses (CRNewContactConnection was being used as "event" in UI) (#3489) 2023-11-30 20:31:32 +04:00
Jesse Horne 9580b4110d desktop: remember window position and size (#3465)
* initial work on storing desktop window position and size

* removed useless imports

* updated to use app preferences

* vars to vals

* defensive programming

* fixed default

* removed default json

* do nothing if encoding to json while storing fails

* names, clean up

* move comment

* changes

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
2023-11-30 12:43:01 +00:00
Stanislav Dmitrenko 05a64c99a2 ios: moving webrtc commands processing to another mechanism (#3480)
* ios: moving webrtc commands processing to another mechanism

* async

* decide

* handle errors

* error alert

* await

---------

Co-authored-by: Avently <avently@local>
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-11-28 17:36:05 +00:00
Alexander Bondarenko 6a21d5c7f1 add remote host bindings (#3471)
* add remote host bindings

* group iface/address together

* rename migration

* add implementation

* update view and api

* bump upstream

* add schema

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-11-28 16:32:33 +00:00
Stanislav Dmitrenko 950bbe19da ios: fix calls connecting state (#3475)
* ios: fix calls connecting state

* optimization

* changes

* removed relay protocol

* simplify

* use actor

* fix loop, better onChange, some questions

* remove extra iteration

---------

Co-authored-by: Avently <avently@local>
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-11-27 22:20:51 +00:00
Stanislav Dmitrenko f31054de4f desktop (windows): fix action (#3479) 2023-11-27 22:11:53 +00:00
Evgeny Poberezkin 05278e5a06 core: allow remote host commands without user (#3478) 2023-11-27 18:34:15 +00:00
spaced4ndy 7a54d74517 Revert "ios: update libraries (#3474)"
This reverts commit bfcb2ac230.
2023-11-27 19:16:53 +04:00
spaced4ndy bfcb2ac230 ios: update libraries (#3474) 2023-11-27 19:02:44 +04:00
spaced4ndy 3073c4a1d5 core: fix chat previews showing not the latest message, fix message ordering in direct chats; mobile: update group previews only on timestamp increase (#3473) 2023-11-27 17:14:12 +04:00
Evgeny Poberezkin d4ac1c0cf2 core, ui: add remote host/controller stop reasons to events (#3472) 2023-11-26 23:23:37 +00:00
Evgeny Poberezkin d29f1bb0cf core: use fourmolu styles (#3470) 2023-11-26 18:16:37 +00:00
Jesse Horne 75c2de8a12 desktop: closing console window no longer closes entire application (#3466)
* closing the console now doesn't close all windows

* simplify

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-11-26 13:40:51 +00:00
Alexander Bondarenko f20ac33e67 cli: remove clashing short option for device-name (#3468) 2023-11-26 13:16:32 +00:00
Evgeny Poberezkin 8cc0954430 Merge branch 'stable' 2023-11-26 11:50:30 +00:00
Evgeny Poberezkin 1e6891e222 blog: v5.4 announcement (#3457)
* blog: v5.4 announcement

* update

* corrections

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>

* add images, CLI section

* images

* preview, readme

* correction

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2023-11-25 18:27:41 +00:00
Evgeny Poberezkin 962964a73d docs: update downloads page 2023-11-25 14:15:41 +00:00
Evgeny Poberezkin b9dd2f45c9 rfc: post-quantum resistant augmented double ratchet algorithm (#3463)
* rfc: post-quantum resistant augmented double ratchet algorithm

* update doc

* replace Kyber with "some KEM"
2023-11-25 12:51:05 +00:00
Evgeny Poberezkin de1c885501 ios: 5.4 build 184: switch to GHC 8.10.7 (9.6.3 crashes on older iPhone models), fix Connect to desktop closing when switching to QR code scan 2023-11-25 11:22:02 +00:00
Evgeny Poberezkin 1902b692f5 5.4: ios 183, android 162, desktop 18 2023-11-25 00:13:31 +00:00
Evgeny Poberezkin 6c05eb0ff3 directory: support group names with spaces (#3458) 2023-11-24 23:21:38 +00:00
Evgeny Poberezkin d148ce4cbb ui: translations (#3459)
* Translated using Weblate (Russian)

Currently translated at 100.0% (1500 of 1500 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/

* Translated using Weblate (Russian)

Currently translated at 100.0% (1346 of 1346 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ru/

* Translated using Weblate (Russian)

Currently translated at 100.0% (1500 of 1500 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/

* Translated using Weblate (Russian)

Currently translated at 100.0% (1346 of 1346 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ru/

* Translated using Weblate (Hungarian)

Currently translated at 22.1% (332 of 1500 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/

* Translated using Weblate (German)

Currently translated at 100.0% (1346 of 1346 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/

* Translated using Weblate (Hungarian)

Currently translated at 22.3% (335 of 1500 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/

* Translated using Weblate (Polish)

Currently translated at 96.9% (1454 of 1500 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pl/

* Translated using Weblate (French)

Currently translated at 100.0% (1500 of 1500 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fr/

* Translated using Weblate (French)

Currently translated at 100.0% (1346 of 1346 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1346 of 1346 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/

* Translated using Weblate (Polish)

Currently translated at 100.0% (1500 of 1500 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pl/

* Translated using Weblate (Polish)

Currently translated at 100.0% (1346 of 1346 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/pl/

* Translated using Weblate (Hungarian)

Currently translated at 28.6% (429 of 1500 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/

* import/export/update

---------

Co-authored-by: Istvan Novak <easthvan@gmail.com>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: M1K4 <oomikaoo@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
2023-11-24 23:20:28 +00:00
Evgeny Poberezkin 3d09073bfc ios: update core lib to 5.4.0.6 2023-11-24 20:46:00 +00:00
Evgeny Poberezkin da64b2e3cd android, desktop: fix translation 2023-11-24 20:37:32 +00:00
Stanislav Dmitrenko 4572fec61d desktop (windows): fix lib build (#3456) 2023-11-24 20:05:41 +00:00
Alexander Bondarenko fe9953fc49 desktop: remove GC flag when building on windows (#3455)
* desktop: remove GC flag when building on windows

* add correct define
2023-11-24 20:00:20 +00:00
Stanislav Dmitrenko e91a1f151d desktop: hide profiles screen on remote host change (#3454) 2023-11-24 19:24:16 +00:00
Evgeny Poberezkin 34d7fe3744 core: 5.4.0.6 2023-11-24 18:59:41 +00:00
Alexander Bondarenko 4327b023ed terminal: add remote user information (#3448)
* terminal: add remote user information

* rename

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-11-24 18:44:12 +00:00
Stanislav Dmitrenko 50bada24af desktop: better status check of loaded remote files (#3453) 2023-11-24 18:43:28 +00:00
spaced4ndy 97934c8289 android, desktop: fix alert text for deleting received message 2023-11-24 21:10:03 +04:00
Evgeny Poberezkin 4a254560c0 desktop: fix user address changes on connected mobile (#3452)
* desktop: fix user address changes on connected mobile

* close user-specific views when remote host changes
2023-11-24 16:51:31 +00:00
Stanislav Dmitrenko f9b5c673c5 android, desktop: better handling of URI's (#3450) 2023-11-24 16:19:31 +00:00
spaced4ndy 8ce9dd7ab6 android: don't show lock notice on first start (#3451) 2023-11-24 20:05:37 +04:00
spaced4ndy 9fb4b3cf40 desktop: don't show device specific network and database settings when connected to remote host (#3449) 2023-11-24 19:38:19 +04:00
Evgeny Poberezkin 7f9a490edb website: translations (#3447)
* Translated using Weblate (Arabic)

Currently translated at 100.0% (252 of 252 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/ar/

* Translated using Weblate (Arabic)

Currently translated at 100.0% (252 of 252 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/ar/

---------

Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
2023-11-24 14:21:13 +00:00
Evgeny Poberezkin bfd13f059a ui: translations (#3446)
* Translated using Weblate (Dutch)

Currently translated at 100.0% (1502 of 1502 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1341 of 1341 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1503 of 1503 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1503 of 1503 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/

* Translated using Weblate (Russian)

Currently translated at 97.7% (1459 of 1493 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/

* Translated using Weblate (Russian)

Currently translated at 100.0% (1341 of 1341 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ru/

* Translated using Weblate (Italian)

Currently translated at 100.0% (1493 of 1493 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/

* Translated using Weblate (Italian)

Currently translated at 100.0% (1341 of 1341 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1502 of 1502 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1341 of 1341 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1503 of 1503 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1503 of 1503 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/

* Translated using Weblate (Russian)

Currently translated at 97.7% (1459 of 1493 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/

* Translated using Weblate (Russian)

Currently translated at 100.0% (1341 of 1341 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ru/

* Translated using Weblate (Italian)

Currently translated at 100.0% (1493 of 1493 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/

* Translated using Weblate (Italian)

Currently translated at 100.0% (1341 of 1341 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/

* Translated using Weblate (Russian)

Currently translated at 100.0% (1493 of 1493 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/

* Translated using Weblate (Russian)

Currently translated at 99.8% (1339 of 1341 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ru/

* Translated using Weblate (Arabic)

Currently translated at 92.4% (1380 of 1493 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1494 of 1494 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/

* Added translation using Weblate (Hungarian)

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1495 of 1495 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/

* Translated using Weblate (Hungarian)

Currently translated at 0.5% (8 of 1495 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/

* Translated using Weblate (Hungarian)

Currently translated at 1.4% (21 of 1495 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/

* Translated using Weblate (Hungarian)

Currently translated at 2.0% (30 of 1495 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/

* Translated using Weblate (Hungarian)

Currently translated at 2.1% (32 of 1495 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/

* Translated using Weblate (Hungarian)

Currently translated at 10.9% (163 of 1495 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1495 of 1495 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/

* Translated using Weblate (Hungarian)

Currently translated at 12.3% (184 of 1495 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/

* Translated using Weblate (Italian)

Currently translated at 100.0% (1495 of 1495 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/

* Translated using Weblate (Hungarian)

Currently translated at 14.8% (222 of 1495 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1495 of 1495 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/

* Translated using Weblate (Hungarian)

Currently translated at 21.6% (323 of 1495 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/

* Translated using Weblate (German)

Currently translated at 97.7% (1461 of 1495 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/

* Translated using Weblate (French)

Currently translated at 100.0% (1495 of 1495 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fr/

* Translated using Weblate (French)

Currently translated at 100.0% (1341 of 1341 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/

* Translated using Weblate (Hungarian)

Currently translated at 21.9% (328 of 1495 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/

* Translated using Weblate (German)

Currently translated at 100.0% (1495 of 1495 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/

* Translated using Weblate (German)

Currently translated at 100.0% (1341 of 1341 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/

* Translated using Weblate (German)

Currently translated at 100.0% (1500 of 1500 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/

* Translated using Weblate (German)

Currently translated at 100.0% (1341 of 1341 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1500 of 1500 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1500 of 1500 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/

* Translated using Weblate (Turkish)

Currently translated at 70.0% (1051 of 1500 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/tr/

* ru: corrections

* export/import

* ios: export

* ru: corrections

---------

Co-authored-by: M1K4 <oomikaoo@gmail.com>
Co-authored-by: Float <float.hu+@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: J R <jr@simplex.chat>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Co-authored-by: Istvan Novak <easthvan@gmail.com>
Co-authored-by: Eric <zxmegaxqug@hldrive.com>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: xe1st <dnzkckali@gmail.com>
2023-11-24 14:18:31 +00:00
Evgeny Poberezkin c9aec88c39 desktop: fix sending videos via connected mobile 2023-11-24 13:14:52 +00:00
Evgeny Poberezkin b1cf1656a0 core: cli remote control help section (#3445) 2023-11-24 10:48:14 +00:00
Alexander Bondarenko 74e80eb348 core: add remote stop reason and state (#3444)
* add remote stop reason and state

* rename

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-11-23 22:00:20 +00:00
Evgeny Poberezkin 6f3174d0a1 android, desktop: remove unnecessary serialization 2023-11-23 21:25:32 +00:00
Evgeny Poberezkin 8f0a9cd609 ios: connect remote desktop via multicast (#3436)
* ios: connect remote desktop via multicast

* works

* fix camera freeze when leaving linked devices view

* label

* fix linked devices

* fix compatible

* string
2023-11-23 21:22:29 +00:00
Stanislav Dmitrenko b2dbb558f9 android, desktop: connect remote desktop via multicast (#3442)
* android, desktop: connect remote desktop via multicast

* changes

* string

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-11-23 21:00:11 +00:00
Stanislav Dmitrenko f7903c5c83 desktop: close remote host connecting screen on stop of host (#3440) 2023-11-23 19:49:53 +00:00
Evgeny Poberezkin 4d3529a3e0 desktop: fix incorrect remote host for active user (#3441) 2023-11-23 17:00:13 +00:00
Alexander Bondarenko d837f87f09 fix circular cancel at rcDiscoverCtrl (#3438) 2023-11-23 11:00:57 +00:00
Evgeny Poberezkin d3f9616f9b core: report controller info when found via multicast (#3437)
* core: report controller info when found via multicast

* handle parse error
2023-11-23 10:07:26 +00:00
Evgeny Poberezkin 1b7baa244a core: track network statuses in CLI (#3434) 2023-11-23 08:39:08 +00:00
Alexander Bondarenko 954b7150af android, desktop: set RTS options for core (#3418)
* desktop: allow settings RTS options

* set initial heap and arena sizes

* add non-moving GC for even more productivity/less reallocs

* add RTS options for android too

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-11-22 22:37:33 +00:00
Evgeny Poberezkin d0419df396 android: close Use from desktop when disconnecting 2023-11-22 22:34:30 +00:00
Evgeny Poberezkin 01f351e65a ios: haskell RTS options (#3433) 2023-11-22 22:12:42 +00:00
Evgeny Poberezkin 2d4e99d610 cli: set device name for remote control via CLI option (#3427)
* cli: set device name for remote control via CLI option

* fix

* add property in tests
2023-11-22 22:11:32 +00:00
Evgeny Poberezkin 4af4fbae2b ios: close sheet when disconnecting from desktop (#3435) 2023-11-22 22:10:41 +00:00
spaced4ndy 15fdab597b core: shuffle members when sending messages and introductions; send to admins and owners first (#3431)
* core: shuffle members when sending messages and introductions; send to admins and owners first

* refactor

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-11-22 21:36:52 +00:00
Stanislav Dmitrenko 0c1d78ab08 desktop: specifying port in connect remote host page (#3432)
* desktop: specifying port in connect remote host page

* shorter string

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-11-22 20:17:05 +00:00
Evgeny Poberezkin 324f614e00 core: return remote controller port to UI (#3430) 2023-11-22 17:40:10 +00:00
spaced4ndy cec0fe2702 ios, android: add author group member role to fix decoding (hidden from UI) (#3429) 2023-11-22 18:47:46 +04:00
Stanislav Dmitrenko 48d7afc959 desktop: enhancements to remote hosts experience (#3428) 2023-11-22 14:35:32 +00:00
Evgeny Poberezkin 9442121efa desktop: do not switch remote host when inactive host is disconnected (#3426) 2023-11-22 12:14:49 +00:00
spaced4ndy 69acac5331 android: remove unused strings (#3424) 2023-11-22 10:19:39 +04:00
Evgeny Poberezkin aade3d359f v5.4-beta.4: ios 182, android 161, desktop 17 2023-11-21 23:32:33 +00:00
Evgeny Poberezkin c1d89f2c0f ios: move hs_init to background thread (#3411) 2023-11-21 22:21:01 +00:00
Stanislav Dmitrenko c40bfb0f43 android, desktop: show remote host info (#3423)
* android, desktop: show remote host info

* hide alerts too
2023-11-21 21:49:39 +00:00
Evgeny Poberezkin 64520a4cf4 core: 5.4.0.5, update simplexmq 2023-11-21 21:12:43 +00:00
Evgeny Poberezkin d0f43628ef ui: translations (#3421)
* Translated using Weblate (Italian)

Currently translated at 100.0% (1483 of 1483 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/

* Translated using Weblate (Italian)

Currently translated at 100.0% (1332 of 1332 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1483 of 1483 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/

* Translated using Weblate (Spanish)

Currently translated at 97.2% (1442 of 1483 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1483 of 1483 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1332 of 1332 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/

* android: fix formatted strings

* ios: imp/exp localizations

---------

Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: Eric <zxmegaxqug@hldrive.com>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: M1K4 <oomikaoo@gmail.com>
2023-11-21 20:30:21 +00:00
spaced4ndy febf3e0a45 ui: v5.4 what's new (#3413)
* ios: v5.4 what's new

* android

* export localizations

* update

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-11-21 19:38:05 +00:00
Jesse Horne 097242e7a8 desktop: add image pasting from clipboard (#3378)
* first pass at desktop image pasting for multiplatform

* removed debug println

* fixed bug with pasting text

* temp files are deleted now following simplex conventions

* optimizations

---------

Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
2023-11-21 19:37:15 +00:00
spaced4ndy a8576c2340 core: test forwarded message deduplication, mute terminal error (#3414) 2023-11-21 19:25:50 +04:00
Alexander Bondarenko 5a08a26c9a desktop: add exception handlers to startReceiver loop (#3417)
* desktop: add exception handlers to startReceiver loop

* simplify

* more exceptions

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
2023-11-21 14:43:52 +00:00
Alexander Bondarenko da8789ef4c desktop: fix RCP tag in AgentErrorType (#3416) 2023-11-21 12:20:04 +00:00
qvsojBJGiEnR 8f6a31ca07 Update app-settings.md (#3379) 2023-11-17 23:29:25 +00:00
321 changed files with 13364 additions and 4548 deletions
+1 -1
View File
@@ -262,7 +262,7 @@ jobs:
# rm -rf dist-newstyle/src/direct-sq* is here because of the bug in cabal's dependency which prevents second build from finishing
- name: 'Setup MSYS2'
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest'
if: matrix.os == 'windows-latest'
uses: msys2/setup-msys2@v2
with:
msystem: ucrt64
+6 -4
View File
@@ -232,6 +232,8 @@ You can use SimpleX with your own servers and still communicate with people usin
Recent and important updates:
[Nov 25, 2023. SimpleX Chat v5.4 released: link mobile and desktop apps via quantum resistant protocol, and much better groups](./blog/20231125-simplex-chat-v5-4-link-mobile-desktop-quantum-resistant-better-groups.md).
[Sep 25, 2023. SimpleX Chat v5.3 released: desktop app, local file encryption, improved groups and directory service](./blog/20230925-simplex-chat-v5-3-desktop-app-local-file-encryption-directory-service.md).
[Jul 22, 2023. SimpleX Chat: v5.2 released with message delivery receipts](./blog/20230722-simplex-chat-v5-2-message-delivery-receipts.md).
@@ -366,13 +368,13 @@ Please also join [#simplex-devs](https://simplex.chat/contact#/?v=1-2&smp=smp%3A
- ✅ Message delivery confirmation (with sender opt-out per contact).
- ✅ Desktop client.
- ✅ Encryption of local files stored in the app.
- 🏗 Using mobile profiles from the desktop app.
- Using mobile profiles from the desktop app.
- 🏗 Improve experience for the new users.
- 🏗 Post-quantum resistant key exchange in double ratchet protocol.
- 🏗 Large groups, communities and public channels.
- Message delivery relay for senders (to conceal IP address from the recipients' servers and to reduce the traffic).
- Post-quantum resistant key exchange in double ratchet protocol.
- Large groups, communities and public channels.
- Privacy & security slider - a simple way to set all settings at once.
- Improve sending videos (including encryption of locally stored videos).
- Improve experience for the new users.
- SMP queue redundancy and rotation (manual is supported).
- Include optional message into connection request sent via contact address.
- Improved navigation and search in the conversation (expand and scroll to quoted message, scroll to search results, etc.).
+2 -1
View File
@@ -42,6 +42,7 @@ class AppDelegate: NSObject, UIApplicationDelegate {
let m = ChatModel.shared
let deviceToken = DeviceToken(pushProvider: PushProvider(env: pushEnvironment), token: token)
m.deviceToken = deviceToken
// savedToken is set in startChat, when it is started before this method is called
if m.savedToken != nil {
registerToken(token: deviceToken)
}
@@ -80,7 +81,7 @@ class AppDelegate: NSObject, UIApplicationDelegate {
}
} else if let checkMessages = ntfData["checkMessages"] as? Bool, checkMessages {
logger.debug("AppDelegate: didReceiveRemoteNotification: checkMessages")
if appStateGroupDefault.get().inactive && m.ntfEnablePeriodic {
if m.ntfEnablePeriodic && allowBackgroundRefresh() && BGManager.shared.lastRanLongAgo {
receiveMessages(completionHandler)
} else {
completionHandler(.noData)
+96 -61
View File
@@ -14,11 +14,14 @@ struct ContentView: View {
@ObservedObject var alertManager = AlertManager.shared
@ObservedObject var callController = CallController.shared
@Environment(\.colorScheme) var colorScheme
@Binding var doAuthenticate: Bool
@Binding var userAuthorized: Bool?
@Binding var canConnectCall: Bool
@Binding var lastSuccessfulUnlock: TimeInterval?
@Binding var showInitializationView: Bool
var contentAccessAuthenticationExtended: Bool
@Environment(\.scenePhase) var scenePhase
@State private var automaticAuthenticationAttempted = false
@State private var canConnectViewCall = false
@State private var lastSuccessfulUnlock: TimeInterval? = nil
@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
@@ -40,9 +43,19 @@ struct ContentView: View {
}
}
private var accessAuthenticated: Bool {
chatModel.contentViewAccessAuthenticated || contentAccessAuthenticationExtended
}
var body: some View {
ZStack {
contentView()
// contentView() has to be in a single branch, so that enabling authentication doesn't trigger re-rendering and close settings.
// i.e. with separate branches like this settings are closed: `if prefPerformLA { ... contentView() ... } else { contentView() }
if !prefPerformLA || accessAuthenticated {
contentView()
} else {
lockButton()
}
if chatModel.showCallView, let call = chatModel.activeCall {
callView(call)
}
@@ -50,6 +63,7 @@ struct ContentView: View {
LocalAuthView(authRequest: la)
} else if showSetPasscode {
SetAppPasscodeView {
chatModel.contentViewAccessAuthenticated = true
prefPerformLA = true
showSetPasscode = false
privacyLocalAuthModeDefault.set(.passcode)
@@ -60,13 +74,9 @@ struct ContentView: View {
alertManager.showAlert(laPasscodeNotSetAlert())
}
}
}
.onAppear {
if prefPerformLA { requestNtfAuthorization() }
initAuthenticate()
}
.onChange(of: doAuthenticate) { _ in
initAuthenticate()
if chatModel.chatDbStatus == nil {
initializationView()
}
}
.alert(isPresented: $alertManager.presentAlert) { alertManager.alertView! }
.sheet(isPresented: $showSettings) {
@@ -76,14 +86,44 @@ struct ContentView: View {
Button("System authentication") { initialEnableLA() }
Button("Passcode entry") { showSetPasscode = true }
}
.onChange(of: scenePhase) { phase in
logger.debug("scenePhase was \(String(describing: scenePhase)), now \(String(describing: phase))")
switch (phase) {
case .background:
// also see .onChange(of: scenePhase) in SimpleXApp: on entering background
// it remembers enteredBackgroundAuthenticated and sets chatModel.contentViewAccessAuthenticated to false
automaticAuthenticationAttempted = false
canConnectViewCall = false
case .active:
canConnectViewCall = !prefPerformLA || contentAccessAuthenticationExtended || unlockedRecently()
// condition `!chatModel.contentViewAccessAuthenticated` is required for when authentication is enabled in settings or on initial notice
if prefPerformLA && !chatModel.contentViewAccessAuthenticated {
if AppChatState.shared.value != .stopped {
if contentAccessAuthenticationExtended {
chatModel.contentViewAccessAuthenticated = true
} else {
if !automaticAuthenticationAttempted {
automaticAuthenticationAttempted = true
// authenticate if call kit call is not in progress
if !(CallController.useCallKit() && chatModel.showCallView && chatModel.activeCall != nil) {
authenticateContentViewAccess()
}
}
}
} else {
// when app is stopped automatic authentication is not attempted
chatModel.contentViewAccessAuthenticated = contentAccessAuthenticationExtended
}
}
default:
break
}
}
}
@ViewBuilder private func contentView() -> some View {
if prefPerformLA && userAuthorized != true {
lockButton()
} else if chatModel.chatDbStatus == nil && showInitializationView {
initializationView()
} else if let status = chatModel.chatDbStatus, status != .ok {
if let status = chatModel.chatDbStatus, status != .ok {
DatabaseErrorView(status: status)
} else if !chatModel.v3DBMigration.startChat {
MigrateToAppGroupView()
@@ -106,11 +146,11 @@ struct ContentView: View {
if CallController.useCallKit() {
ActiveCallView(call: call, canConnectCall: Binding.constant(true))
.onDisappear {
if userAuthorized == false && doAuthenticate { runAuthenticate() }
if prefPerformLA && !accessAuthenticated { authenticateContentViewAccess() }
}
} else {
ActiveCallView(call: call, canConnectCall: $canConnectCall)
if prefPerformLA && userAuthorized != true {
ActiveCallView(call: call, canConnectCall: $canConnectViewCall)
if prefPerformLA && !accessAuthenticated {
Rectangle()
.fill(colorScheme == .dark ? .black : .white)
.frame(maxWidth: .infinity, maxHeight: .infinity)
@@ -120,22 +160,27 @@ struct ContentView: View {
}
private func lockButton() -> some View {
Button(action: runAuthenticate) { Label("Unlock", systemImage: "lock") }
Button(action: authenticateContentViewAccess) { Label("Unlock", systemImage: "lock") }
}
private func initializationView() -> some View {
VStack {
ProgressView().scaleEffect(2)
Text("Opening database")
Text("Opening app")
.padding()
}
.frame(maxWidth: .infinity, maxHeight: .infinity )
.background(
Rectangle()
.fill(.background)
)
}
private func mainView() -> some View {
ZStack(alignment: .top) {
ChatListView(showSettings: $showSettings).privacySensitive(protectScreen)
.onAppear {
if !prefPerformLA { requestNtfAuthorization() }
requestNtfAuthorization()
// Local Authentication notice is to be shown on next start after onboarding is complete
if (!prefLANoticeShown && prefShowLANotice && !chatModel.chats.isEmpty) {
prefLANoticeShown = true
@@ -187,48 +232,37 @@ struct ContentView: View {
}
}
private func initAuthenticate() {
logger.debug("initAuthenticate")
if CallController.useCallKit() && chatModel.showCallView && chatModel.activeCall != nil {
userAuthorized = false
} else if doAuthenticate {
runAuthenticate()
}
}
private func runAuthenticate() {
logger.debug("DEBUGGING: runAuthenticate")
if !prefPerformLA {
userAuthorized = true
private func unlockedRecently() -> Bool {
if let lastSuccessfulUnlock = lastSuccessfulUnlock {
return ProcessInfo.processInfo.systemUptime - lastSuccessfulUnlock < 2
} else {
logger.debug("DEBUGGING: before dismissAllSheets")
dismissAllSheets(animated: false) {
logger.debug("DEBUGGING: in dismissAllSheets callback")
chatModel.chatId = nil
justAuthenticate()
}
return false
}
}
private func justAuthenticate() {
userAuthorized = false
let laMode = privacyLocalAuthModeDefault.get()
authenticate(reason: NSLocalizedString("Unlock app", comment: "authentication reason"), selfDestruct: true) { laResult in
logger.debug("DEBUGGING: authenticate callback: \(String(describing: laResult))")
switch (laResult) {
case .success:
userAuthorized = true
canConnectCall = true
lastSuccessfulUnlock = ProcessInfo.processInfo.systemUptime
case .failed:
if laMode == .passcode {
AlertManager.shared.showAlert(laFailedAlert())
private func authenticateContentViewAccess() {
logger.debug("DEBUGGING: authenticateContentViewAccess")
dismissAllSheets(animated: false) {
logger.debug("DEBUGGING: authenticateContentViewAccess, in dismissAllSheets callback")
chatModel.chatId = nil
authenticate(reason: NSLocalizedString("Unlock app", comment: "authentication reason"), selfDestruct: true) { laResult in
logger.debug("DEBUGGING: authenticate callback: \(String(describing: laResult))")
switch (laResult) {
case .success:
chatModel.contentViewAccessAuthenticated = true
canConnectViewCall = true
lastSuccessfulUnlock = ProcessInfo.processInfo.systemUptime
case .failed:
chatModel.contentViewAccessAuthenticated = false
if privacyLocalAuthModeDefault.get() == .passcode {
AlertManager.shared.showAlert(laFailedAlert())
}
case .unavailable:
prefPerformLA = false
canConnectViewCall = true
AlertManager.shared.showAlert(laUnavailableTurningOffAlert())
}
case .unavailable:
userAuthorized = true
prefPerformLA = false
canConnectCall = true
AlertManager.shared.showAlert(laUnavailableTurningOffAlert())
}
}
}
@@ -259,6 +293,7 @@ struct ContentView: View {
authenticate(reason: NSLocalizedString("Enable SimpleX Lock", comment: "authentication reason")) { laResult in
switch laResult {
case .success:
chatModel.contentViewAccessAuthenticated = true
prefPerformLA = true
alertManager.showAlert(laTurnedOnAlert())
case .failed:
+28 -6
View File
@@ -15,7 +15,13 @@ private let receiveTaskId = "chat.simplex.app.receive"
// TCP timeout + 2 sec
private let waitForMessages: TimeInterval = 6
private let bgRefreshInterval: TimeInterval = 450
// This is the smallest interval between refreshes, and also target interval in "off" mode
private let bgRefreshInterval: TimeInterval = 600 // 10 minutes
// This intervals are used for background refresh in instant and periodic modes
private let periodicBgRefreshInterval: TimeInterval = 1200 // 20 minutes
private let maxBgRefreshInterval: TimeInterval = 2400 // 40 minutes
private let maxTimerCount = 9
@@ -33,14 +39,14 @@ class BGManager {
}
}
func schedule() {
func schedule(interval: TimeInterval? = nil) {
if !ChatModel.shared.ntfEnableLocal {
logger.debug("BGManager.schedule: disabled")
return
}
logger.debug("BGManager.schedule")
let request = BGAppRefreshTaskRequest(identifier: receiveTaskId)
request.earliestBeginDate = Date(timeIntervalSinceNow: bgRefreshInterval)
request.earliestBeginDate = Date(timeIntervalSinceNow: interval ?? runInterval)
do {
try BGTaskScheduler.shared.submit(request)
} catch {
@@ -48,20 +54,34 @@ class BGManager {
}
}
var runInterval: TimeInterval {
switch ChatModel.shared.notificationMode {
case .instant: maxBgRefreshInterval
case .periodic: periodicBgRefreshInterval
case .off: bgRefreshInterval
}
}
var lastRanLongAgo: Bool {
Date.now.timeIntervalSince(chatLastBackgroundRunGroupDefault.get()) > runInterval
}
private func handleRefresh(_ task: BGAppRefreshTask) {
if !ChatModel.shared.ntfEnableLocal {
logger.debug("BGManager.handleRefresh: disabled")
return
}
logger.debug("BGManager.handleRefresh")
schedule()
if appStateGroupDefault.get().inactive {
let shouldRun_ = lastRanLongAgo
if allowBackgroundRefresh() && shouldRun_ {
schedule()
let completeRefresh = completionHandler {
task.setTaskCompleted(success: true)
}
task.expirationHandler = { completeRefresh("expirationHandler") }
receiveMessages(completeRefresh)
} else {
schedule(interval: shouldRun_ ? bgRefreshInterval : runInterval)
logger.debug("BGManager.completionHandler: already active, not started")
task.setTaskCompleted(success: true)
}
@@ -90,20 +110,22 @@ class BGManager {
}
self.completed = false
DispatchQueue.main.async {
chatLastBackgroundRunGroupDefault.set(Date.now)
let m = ChatModel.shared
if (!m.chatInitialized) {
setAppState(.bgRefresh)
do {
try initializeChat(start: true)
} catch let error {
fatalError("Failed to start or load chats: \(responseError(error))")
}
}
activateChat(appState: .bgRefresh)
if m.currentUser == nil {
completeReceiving("no current user")
return
}
logger.debug("BGManager.receiveMessages: starting chat")
activateChat(appState: .bgRefresh)
let cr = ChatReceiver()
self.chatReceiver = cr
cr.start()
+26 -7
View File
@@ -54,6 +54,8 @@ final class ChatModel: ObservableObject {
@Published var chatDbChanged = false
@Published var chatDbEncrypted: Bool?
@Published var chatDbStatus: DBMigrationResult?
// local authentication
@Published var contentViewAccessAuthenticated: Bool = false
@Published var laRequest: LocalAuthRequest?
// list of chat "previews"
@Published var chats: [Chat] = []
@@ -83,7 +85,7 @@ final class ChatModel: ObservableObject {
// current WebRTC call
@Published var callInvitations: Dictionary<ChatId, RcvCallInvitation> = [:]
@Published var activeCall: Call?
@Published var callCommand: WCallCommand?
let callCommand: WebRTCCommandProcessor = WebRTCCommandProcessor()
@Published var showCallView = false
// remote desktop
@Published var remoteCtrlSession: RemoteCtrlSession?
@@ -104,12 +106,10 @@ final class ChatModel: ObservableObject {
static var ok: Bool { ChatModel.shared.chatDbStatus == .ok }
var ntfEnableLocal: Bool {
notificationMode == .off || ntfEnableLocalGroupDefault.get()
}
let ntfEnableLocal = true
var ntfEnablePeriodic: Bool {
notificationMode == .periodic || ntfEnablePeriodicGroupDefault.get()
notificationMode != .off
}
var activeRemoteCtrl: Bool {
@@ -267,7 +267,20 @@ final class ChatModel: ObservableObject {
func addChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) {
// update previews
if let i = getChatIndex(cInfo.id) {
chats[i].chatItems = [cItem]
chats[i].chatItems = switch cInfo {
case .group:
if let currentPreviewItem = chats[i].chatItems.first {
if cItem.meta.itemTs >= currentPreviewItem.meta.itemTs {
[cItem]
} else {
[currentPreviewItem]
}
} else {
[cItem]
}
default:
[cItem]
}
if case .rcvNew = cItem.meta.itemStatus {
chats[i].chatStats.unreadCount = chats[i].chatStats.unreadCount + 1
increaseUnreadCounter(user: currentUser!)
@@ -770,7 +783,7 @@ final class GMember: ObservableObject, Identifiable {
}
struct RemoteCtrlSession {
var ctrlAppInfo: CtrlAppInfo
var ctrlAppInfo: CtrlAppInfo?
var appVersion: String
var sessionState: UIRemoteCtrlSessionState
@@ -782,6 +795,10 @@ struct RemoteCtrlSession {
if case .connected = sessionState { true } else { false }
}
var discovery: Bool {
if case .searching = sessionState { true } else { false }
}
var sessionCode: String? {
switch sessionState {
case let .pendingConfirmation(_, sessionCode): sessionCode
@@ -793,6 +810,8 @@ struct RemoteCtrlSession {
enum UIRemoteCtrlSessionState {
case starting
case searching
case found(remoteCtrl: RemoteCtrlInfo, compatible: Bool)
case connecting(remoteCtrl_: RemoteCtrlInfo?)
case pendingConfirmation(remoteCtrl_: RemoteCtrlInfo?, sessionCode: String)
case connected(remoteCtrl: RemoteCtrlInfo, sessionCode: String)
+4 -4
View File
@@ -195,18 +195,18 @@ func moveTempFileFromURL(_ url: URL) -> CryptoFile? {
}
}
func generateNewFileName(_ prefix: String, _ ext: String) -> String {
uniqueCombine("\(prefix)_\(getTimestamp()).\(ext)")
func generateNewFileName(_ prefix: String, _ ext: String, fullPath: Bool = false) -> String {
uniqueCombine("\(prefix)_\(getTimestamp()).\(ext)", fullPath: fullPath)
}
private func uniqueCombine(_ fileName: String) -> String {
private func uniqueCombine(_ fileName: String, fullPath: Bool = false) -> String {
func tryCombine(_ fileName: String, _ n: Int) -> String {
let ns = fileName as NSString
let name = ns.deletingPathExtension
let ext = ns.pathExtension
let suffix = (n == 0) ? "" : "_\(n)"
let f = "\(name)\(suffix).\(ext)"
return (FileManager.default.fileExists(atPath: getAppFilePath(f).path)) ? tryCombine(fileName, n + 1) : f
return (FileManager.default.fileExists(atPath: fullPath ? f : getAppFilePath(f).path)) ? tryCombine(fileName, n + 1) : f
}
return tryCombine(fileName, 0)
}
+83
View File
@@ -0,0 +1,83 @@
//
// NSESubscriber.swift
// SimpleXChat
//
// Created by Evgeny on 09/12/2023.
// Copyright © 2023 SimpleX Chat. All rights reserved.
//
import Foundation
import SimpleXChat
private var nseSubscribers: [UUID:NSESubscriber] = [:]
// timeout for active notification service extension going into "suspending" state.
// If in two seconds the state does not change, we assume that it was not running and proceed with app activation/answering call.
private let SUSPENDING_TIMEOUT: TimeInterval = 2
// timeout should be larger than SUSPENDING_TIMEOUT
func waitNSESuspended(timeout: TimeInterval, dispatchQueue: DispatchQueue = DispatchQueue.main, suspended: @escaping (Bool) -> Void) {
if timeout <= SUSPENDING_TIMEOUT {
logger.warning("waitNSESuspended: small timeout \(timeout), using \(SUSPENDING_TIMEOUT + 1)")
}
var state = nseStateGroupDefault.get()
if case .suspended = state {
dispatchQueue.async { suspended(true) }
return
}
let id = UUID()
var suspendedCalled = false
checkTimeout()
nseSubscribers[id] = nseMessageSubscriber { msg in
if case let .state(newState) = msg {
state = newState
logger.debug("waitNSESuspended state: \(state.rawValue)")
if case .suspended = newState {
notifySuspended(true)
}
}
}
return
func notifySuspended(_ ok: Bool) {
logger.debug("waitNSESuspended notifySuspended: \(ok)")
if !suspendedCalled {
logger.debug("waitNSESuspended notifySuspended: calling suspended(\(ok))")
suspendedCalled = true
nseSubscribers.removeValue(forKey: id)
dispatchQueue.async { suspended(ok) }
}
}
func checkTimeout() {
if !suspending() {
checkSuspendingTimeout()
} else if state == .suspending {
checkSuspendedTimeout()
}
}
func suspending() -> Bool {
suspendedCalled || state == .suspended || state == .suspending
}
func checkSuspendingTimeout() {
DispatchQueue.global().asyncAfter(deadline: .now() + SUSPENDING_TIMEOUT) {
logger.debug("waitNSESuspended check suspending timeout")
if !suspending() {
notifySuspended(false)
} else if state != .suspended {
checkSuspendedTimeout()
}
}
}
func checkSuspendedTimeout() {
DispatchQueue.global().asyncAfter(deadline: .now() + min(timeout - SUSPENDING_TIMEOUT, 1)) {
logger.debug("waitNSESuspended check suspended timeout")
if state != .suspended {
notifySuspended(false)
}
}
}
}
+55 -38
View File
@@ -228,7 +228,8 @@ func apiStopChat() async throws {
}
func apiActivateChat() {
let r = chatSendCmdSync(.apiActivateChat)
chatReopenStore()
let r = chatSendCmdSync(.apiActivateChat(restoreChat: true))
if case .cmdOk = r { return }
logger.error("apiActivateChat error: \(String(describing: r))")
}
@@ -605,27 +606,29 @@ func apiConnectPlan(connReq: String) async throws -> ConnectionPlan {
throw r
}
func apiConnect(incognito: Bool, connReq: String) async -> ConnReqType? {
let (connReqType, alert) = await apiConnect_(incognito: incognito, connReq: connReq)
func apiConnect(incognito: Bool, connReq: String) async -> (ConnReqType, PendingContactConnection)? {
let (r, alert) = await apiConnect_(incognito: incognito, connReq: connReq)
if let alert = alert {
AlertManager.shared.showAlert(alert)
return nil
} else {
return connReqType
return r
}
}
func apiConnect_(incognito: Bool, connReq: String) async -> (ConnReqType?, Alert?) {
func apiConnect_(incognito: Bool, connReq: String) async -> ((ConnReqType, PendingContactConnection)?, Alert?) {
guard let userId = ChatModel.shared.currentUser?.userId else {
logger.error("apiConnect: no current user")
return (nil, nil)
}
let r = await chatSendCmd(.apiConnect(userId: userId, incognito: incognito, connReq: connReq))
let m = ChatModel.shared
switch r {
case .sentConfirmation: return (.invitation, nil)
case .sentInvitation: return (.contact, nil)
case let .sentConfirmation(_, connection):
return ((.invitation, connection), nil)
case let .sentInvitation(_, connection):
return ((.contact, connection), nil)
case let .contactAlreadyExists(_, contact):
let m = ChatModel.shared
if let c = m.getContactChat(contact.contactId) {
await MainActor.run { m.chatId = c.id }
}
@@ -919,8 +922,10 @@ func findKnownRemoteCtrl() async throws {
try await sendCommandOkResp(.findKnownRemoteCtrl)
}
func confirmRemoteCtrl(_ rcId: Int64) async throws {
try await sendCommandOkResp(.confirmRemoteCtrl(remoteCtrlId: rcId))
func confirmRemoteCtrl(_ rcId: Int64) async throws -> (RemoteCtrlInfo?, CtrlAppInfo, String) {
let r = await chatSendCmd(.confirmRemoteCtrl(remoteCtrlId: rcId))
if case let .remoteCtrlConnecting(rc_, ctrlAppInfo, v) = r { return (rc_, ctrlAppInfo, v) }
throw r
}
func verifyRemoteCtrlSession(_ sessCode: String) async throws -> RemoteCtrlInfo {
@@ -1230,6 +1235,9 @@ func initializeChat(start: Bool, dbKey: String? = nil, refreshInvitations: Bool
try startChat(refreshInvitations: refreshInvitations)
} else {
m.chatRunning = false
try getUserChatData()
NtfManager.shared.setNtfBadgeCount(m.totalUnreadCountForAllUsers())
m.onboardingStage = onboardingStageDefault.get()
}
}
@@ -1246,6 +1254,8 @@ func startChat(refreshInvitations: Bool = true) throws {
try refreshCallInvitations()
}
(m.savedToken, m.tokenStatus, m.notificationMode) = apiGetNtfToken()
// deviceToken is set when AppDelegate.application(didRegisterForRemoteNotificationsWithDeviceToken:) is called,
// when it is called before startChat
if let token = m.deviceToken {
registerToken(token: token)
}
@@ -1360,18 +1370,6 @@ func processReceivedMsg(_ res: ChatResponse) async {
let m = ChatModel.shared
logger.debug("processReceivedMsg: \(res.responseType)")
switch res {
case let .newContactConnection(user, connection):
if active(user) {
await MainActor.run {
m.updateContactConnection(connection)
}
}
case let .contactConnectionDeleted(user, connection):
if active(user) {
await MainActor.run {
m.removeChat(connection.id)
}
}
case let .contactDeletedByContact(user, contact):
if active(user) && contact.directOrUsed {
await MainActor.run {
@@ -1664,36 +1662,40 @@ func processReceivedMsg(_ res: ChatResponse) async {
activateCall(invitation)
case let .callOffer(_, contact, callType, offer, sharedKey, _):
await withCall(contact) { call in
call.callState = .offerReceived
call.peerMedia = callType.media
call.sharedKey = sharedKey
await MainActor.run {
call.callState = .offerReceived
call.peerMedia = callType.media
call.sharedKey = sharedKey
}
let useRelay = UserDefaults.standard.bool(forKey: DEFAULT_WEBRTC_POLICY_RELAY)
let iceServers = getIceServers()
logger.debug(".callOffer useRelay \(useRelay)")
logger.debug(".callOffer iceServers \(String(describing: iceServers))")
m.callCommand = .offer(
await m.callCommand.processCommand(.offer(
offer: offer.rtcSession,
iceCandidates: offer.rtcIceCandidates,
media: callType.media, aesKey: sharedKey,
iceServers: iceServers,
relay: useRelay
)
))
}
case let .callAnswer(_, contact, answer):
await withCall(contact) { call in
call.callState = .answerReceived
m.callCommand = .answer(answer: answer.rtcSession, iceCandidates: answer.rtcIceCandidates)
await MainActor.run {
call.callState = .answerReceived
}
await m.callCommand.processCommand(.answer(answer: answer.rtcSession, iceCandidates: answer.rtcIceCandidates))
}
case let .callExtraInfo(_, contact, extraInfo):
await withCall(contact) { _ in
m.callCommand = .ice(iceCandidates: extraInfo.rtcIceCandidates)
await m.callCommand.processCommand(.ice(iceCandidates: extraInfo.rtcIceCandidates))
}
case let .callEnded(_, contact):
if let invitation = await MainActor.run(body: { m.callInvitations.removeValue(forKey: contact.id) }) {
CallController.shared.reportCallRemoteEnded(invitation: invitation)
}
await withCall(contact) { call in
m.callCommand = .end
await m.callCommand.processCommand(.end)
CallController.shared.reportCallRemoteEnded(call: call)
}
case .chatSuspended:
@@ -1714,9 +1716,17 @@ func processReceivedMsg(_ res: ChatResponse) async {
await MainActor.run {
m.updateGroupMemberConnectionStats(groupInfo, member, ratchetSyncProgress.connectionStats)
}
case let .remoteCtrlFound(remoteCtrl):
// TODO multicast
logger.debug("\(String(describing: remoteCtrl))")
case let .remoteCtrlFound(remoteCtrl, ctrlAppInfo_, appVersion, compatible):
await MainActor.run {
if let sess = m.remoteCtrlSession, case .searching = sess.sessionState {
let state = UIRemoteCtrlSessionState.found(remoteCtrl: remoteCtrl, compatible: compatible)
m.remoteCtrlSession = RemoteCtrlSession(
ctrlAppInfo: ctrlAppInfo_,
appVersion: appVersion,
sessionState: state
)
}
}
case let .remoteCtrlSessionCode(remoteCtrl_, sessionCode):
await MainActor.run {
let state = UIRemoteCtrlSessionState.pendingConfirmation(remoteCtrl_: remoteCtrl_, sessionCode: sessionCode)
@@ -1731,16 +1741,23 @@ func processReceivedMsg(_ res: ChatResponse) async {
case .remoteCtrlStopped:
// This delay is needed to cancel the session that fails on network failure,
// e.g. when user did not grant permission to access local network yet.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
switchToLocalSession()
if let sess = m.remoteCtrlSession {
await MainActor.run {
m.remoteCtrlSession = nil
}
if case .connected = sess.sessionState {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
switchToLocalSession()
}
}
}
default:
logger.debug("unsupported event: \(res.responseType)")
}
func withCall(_ contact: Contact, _ perform: (Call) -> Void) async {
func withCall(_ contact: Contact, _ perform: (Call) async -> Void) async {
if let call = m.activeCall, call.contact.apiId == contact.apiId {
await MainActor.run { perform(call) }
await perform(call)
} else {
logger.debug("processReceivedMsg: ignoring \(res.responseType), not in call with the contact \(contact.id)")
}
+81 -23
View File
@@ -9,27 +9,28 @@
import Foundation
import UIKit
import SimpleXChat
import SwiftUI
private let suspendLockQueue = DispatchQueue(label: "chat.simplex.app.suspend.lock")
let appSuspendTimeout: Int = 15 // seconds
let bgSuspendTimeout: Int = 5 // seconds
let terminationTimeout: Int = 3 // seconds
let activationDelay: TimeInterval = 1.5
private func _suspendChat(timeout: Int) {
// this is a redundant check to prevent logical errors, like the one fixed in this PR
let state = appStateGroupDefault.get()
let state = AppChatState.shared.value
if !state.canSuspend {
logger.error("_suspendChat called, current state: \(state.rawValue, privacy: .public)")
} else if ChatModel.ok {
appStateGroupDefault.set(.suspending)
AppChatState.shared.set(.suspending)
apiSuspendChat(timeoutMicroseconds: timeout * 1000000)
let endTask = beginBGTask(chatSuspended)
DispatchQueue.global().asyncAfter(deadline: .now() + Double(timeout) + 1, execute: endTask)
} else {
appStateGroupDefault.set(.suspended)
AppChatState.shared.set(.suspended)
}
}
@@ -41,18 +42,16 @@ func suspendChat() {
func suspendBgRefresh() {
suspendLockQueue.sync {
if case .bgRefresh = appStateGroupDefault.get() {
if case .bgRefresh = AppChatState.shared.value {
_suspendChat(timeout: bgSuspendTimeout)
}
}
}
private var terminating = false
func terminateChat() {
logger.debug("terminateChat")
suspendLockQueue.sync {
switch appStateGroupDefault.get() {
switch AppChatState.shared.value {
case .suspending:
// suspend instantly if already suspending
_chatSuspended()
@@ -64,7 +63,6 @@ func terminateChat() {
case .stopped:
chatCloseStore()
default:
terminating = true
// the store will be closed in _chatSuspended when event is received
_suspendChat(timeout: terminationTimeout)
}
@@ -73,7 +71,7 @@ func terminateChat() {
func chatSuspended() {
suspendLockQueue.sync {
if case .suspending = appStateGroupDefault.get() {
if case .suspending = AppChatState.shared.value {
_chatSuspended()
}
}
@@ -81,48 +79,108 @@ func chatSuspended() {
private func _chatSuspended() {
logger.debug("_chatSuspended")
appStateGroupDefault.set(.suspended)
AppChatState.shared.set(.suspended)
if ChatModel.shared.chatRunning == true {
ChatReceiver.shared.stop()
}
if terminating {
chatCloseStore()
chatCloseStore()
}
func setAppState(_ appState: AppState) {
suspendLockQueue.sync {
AppChatState.shared.set(appState)
}
}
func activateChat(appState: AppState = .active) {
logger.debug("DEBUGGING: activateChat")
terminating = false
suspendLockQueue.sync {
appStateGroupDefault.set(appState)
AppChatState.shared.set(appState)
if ChatModel.ok { apiActivateChat() }
logger.debug("DEBUGGING: activateChat: after apiActivateChat")
}
}
func initChatAndMigrate(refreshInvitations: Bool = true) {
terminating = false
let m = ChatModel.shared
if (!m.chatInitialized) {
m.v3DBMigration = v3DBMigrationDefault.get()
if AppChatState.shared.value == .stopped {
AlertManager.shared.showAlert(Alert(
title: Text("Start chat?"),
message: Text("Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat."),
primaryButton: .default(Text("Ok")) {
AppChatState.shared.set(.active)
initialize(start: true)
},
secondaryButton: .cancel {
initialize(start: false)
}
))
} else {
initialize(start: true)
}
}
func initialize(start: Bool) {
do {
m.v3DBMigration = v3DBMigrationDefault.get()
try initializeChat(start: m.v3DBMigration.startChat, refreshInvitations: refreshInvitations)
try initializeChat(start: m.v3DBMigration.startChat && start, refreshInvitations: refreshInvitations)
} catch let error {
fatalError("Failed to start or load chats: \(responseError(error))")
AlertManager.shared.showAlertMsg(
title: start ? "Error starting chat" : "Error opening chat",
message: "Please contact developers.\nError: \(responseError(error))"
)
}
}
}
func startChatAndActivate() {
terminating = false
func startChatAndActivate(dispatchQueue: DispatchQueue = DispatchQueue.main, _ completion: @escaping () -> Void) {
logger.debug("DEBUGGING: startChatAndActivate")
if ChatModel.shared.chatRunning == true {
ChatReceiver.shared.start()
logger.debug("DEBUGGING: startChatAndActivate: after ChatReceiver.shared.start")
}
if .active != appStateGroupDefault.get() {
if .active == AppChatState.shared.value {
completion()
} else if nseStateGroupDefault.get().inactive {
activate()
} else {
// setting app state to "activating" to notify NSE that it should suspend
setAppState(.activating)
waitNSESuspended(timeout: 10, dispatchQueue: dispatchQueue) { ok in
if !ok {
// if for some reason NSE failed to suspend,
// e.g., it crashed previously without setting its state to "suspended",
// set it to "suspended" state anyway, so that next time app
// does not have to wait when activating.
nseStateGroupDefault.set(.suspended)
}
if AppChatState.shared.value == .activating {
activate()
}
}
}
func activate() {
logger.debug("DEBUGGING: startChatAndActivate: before activateChat")
activateChat()
completion()
logger.debug("DEBUGGING: startChatAndActivate: after activateChat")
}
}
// appStateGroupDefault must not be used in the app directly, only via this singleton
class AppChatState {
static let shared = AppChatState()
private var value_ = appStateGroupDefault.get()
var value: AppState {
value_
}
func set(_ state: AppState) {
appStateGroupDefault.set(state)
sendAppState(state)
value_ = state
}
}
+31 -41
View File
@@ -16,17 +16,15 @@ struct SimpleXApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
@StateObject private var chatModel = ChatModel.shared
@ObservedObject var alertManager = AlertManager.shared
@Environment(\.scenePhase) var scenePhase
@AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false
@State private var userAuthorized: Bool?
@State private var doAuthenticate = false
@State private var enteredBackground: TimeInterval? = nil
@State private var canConnectCall = false
@State private var lastSuccessfulUnlock: TimeInterval? = nil
@State private var showInitializationView = false
@State private var enteredBackgroundAuthenticated: TimeInterval? = nil
init() {
hs_init(0, nil)
DispatchQueue.global(qos: .background).sync {
haskell_init()
// hs_init(0, nil)
}
UserDefaults.standard.register(defaults: appDefaults)
setGroupDefaults()
registerGroupDefaults()
@@ -36,22 +34,17 @@ struct SimpleXApp: App {
}
var body: some Scene {
return WindowGroup {
ContentView(
doAuthenticate: $doAuthenticate,
userAuthorized: $userAuthorized,
canConnectCall: $canConnectCall,
lastSuccessfulUnlock: $lastSuccessfulUnlock,
showInitializationView: $showInitializationView
)
WindowGroup {
// contentAccessAuthenticationExtended has to be passed to ContentView on view initialization,
// so that it's computed by the time view renders, and not on event after rendering
ContentView(contentAccessAuthenticationExtended: !authenticationExpired())
.environmentObject(chatModel)
.onOpenURL { url in
logger.debug("ContentView.onOpenURL: \(url)")
chatModel.appOpenUrl = url
}
.onAppear() {
showInitializationView = true
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
initChatAndMigrate()
}
}
@@ -59,30 +52,35 @@ struct SimpleXApp: App {
logger.debug("scenePhase was \(String(describing: scenePhase)), now \(String(describing: phase))")
switch (phase) {
case .background:
// --- authentication
// see ContentView .onChange(of: scenePhase) for remaining authentication logic
if chatModel.contentViewAccessAuthenticated {
enteredBackgroundAuthenticated = ProcessInfo.processInfo.systemUptime
}
chatModel.contentViewAccessAuthenticated = false
// authentication ---
if CallController.useCallKit() && chatModel.activeCall != nil {
CallController.shared.shouldSuspendChat = true
} else {
suspendChat()
BGManager.shared.schedule()
}
if userAuthorized == true {
enteredBackground = ProcessInfo.processInfo.systemUptime
}
doAuthenticate = false
canConnectCall = false
NtfManager.shared.setNtfBadgeCount(chatModel.totalUnreadCountForAllUsers())
case .active:
CallController.shared.shouldSuspendChat = false
let appState = appStateGroupDefault.get()
startChatAndActivate()
if appState.inactive && chatModel.chatRunning == true {
updateChats()
if !chatModel.showCallView && !CallController.shared.hasActiveCalls() {
updateCallInvitations()
let appState = AppChatState.shared.value
if appState != .stopped {
startChatAndActivate {
if appState.inactive && chatModel.chatRunning == true {
updateChats()
if !chatModel.showCallView && !CallController.shared.hasActiveCalls() {
updateCallInvitations()
}
}
}
}
doAuthenticate = authenticationExpired()
canConnectCall = !(doAuthenticate && prefPerformLA) || unlockedRecently()
default:
break
}
@@ -115,22 +113,14 @@ struct SimpleXApp: App {
}
private func authenticationExpired() -> Bool {
if let enteredBackground = enteredBackground {
if let enteredBackgroundAuthenticated = enteredBackgroundAuthenticated {
let delay = Double(UserDefaults.standard.integer(forKey: DEFAULT_LA_LOCK_DELAY))
return ProcessInfo.processInfo.systemUptime - enteredBackground >= delay
return ProcessInfo.processInfo.systemUptime - enteredBackgroundAuthenticated >= delay
} else {
return true
}
}
private func unlockedRecently() -> Bool {
if let lastSuccessfulUnlock = lastSuccessfulUnlock {
return ProcessInfo.processInfo.systemUptime - lastSuccessfulUnlock < 2
} else {
return false
}
}
private func updateChats() {
do {
let chats = try apiGetChats()
@@ -49,10 +49,10 @@ struct ActiveCallView: View {
}
.onDisappear {
logger.debug("ActiveCallView: disappear")
Task { await m.callCommand.setClient(nil) }
AppDelegate.keepScreenOn(false)
client?.endCall()
}
.onChange(of: m.callCommand) { _ in sendCommandToClient()}
.background(.black)
.preferredColorScheme(.dark)
}
@@ -60,19 +60,8 @@ struct ActiveCallView: View {
private func createWebRTCClient() {
if client == nil && canConnectCall {
client = WebRTCClient($activeCall, { msg in await MainActor.run { processRtcMessage(msg: msg) } }, $localRendererAspectRatio)
sendCommandToClient()
}
}
private func sendCommandToClient() {
if call == m.activeCall,
m.activeCall != nil,
let client = client,
let cmd = m.callCommand {
m.callCommand = nil
logger.debug("sendCallCommand: \(cmd.cmdType)")
Task {
await client.sendCallCommand(command: cmd)
await m.callCommand.setClient(client)
}
}
}
@@ -168,8 +157,10 @@ struct ActiveCallView: View {
}
case let .error(message):
logger.debug("ActiveCallView: command error: \(message)")
AlertManager.shared.showAlert(Alert(title: Text("Error"), message: Text(message)))
case let .invalid(type):
logger.debug("ActiveCallView: invalid response: \(type)")
AlertManager.shared.showAlert(Alert(title: Text("Invalid response"), message: Text(type)))
}
}
}
@@ -255,7 +246,6 @@ struct ActiveCallOverlay: View {
HStack {
Text(call.encryptionStatus)
if let connInfo = call.connectionInfo {
// Text("(") + Text(connInfo.text) + Text(", \(connInfo.protocolText))")
Text("(") + Text(connInfo.text) + Text(")")
}
}
+22 -21
View File
@@ -155,31 +155,32 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
if (!ChatModel.shared.chatInitialized) {
initChatAndMigrate(refreshInvitations: false)
}
startChatAndActivate()
shouldSuspendChat = true
// There are no invitations in the model, as it was processed by NSE
_ = try? justRefreshCallInvitations()
// logger.debug("CallController justRefreshCallInvitations: \(String(describing: m.callInvitations))")
// Extract the call information from the push notification payload
let m = ChatModel.shared
if let contactId = payload.dictionaryPayload["contactId"] as? String,
let invitation = m.callInvitations[contactId] {
let update = cxCallUpdate(invitation: invitation)
if let uuid = invitation.callkitUUID {
logger.debug("CallController: report pushkit call via CallKit")
let update = cxCallUpdate(invitation: invitation)
provider.reportNewIncomingCall(with: uuid, update: update) { error in
if error != nil {
m.callInvitations.removeValue(forKey: contactId)
startChatAndActivate(dispatchQueue: DispatchQueue.global()) {
self.shouldSuspendChat = true
// There are no invitations in the model, as it was processed by NSE
_ = try? justRefreshCallInvitations()
// logger.debug("CallController justRefreshCallInvitations: \(String(describing: m.callInvitations))")
// Extract the call information from the push notification payload
let m = ChatModel.shared
if let contactId = payload.dictionaryPayload["contactId"] as? String,
let invitation = m.callInvitations[contactId] {
let update = self.cxCallUpdate(invitation: invitation)
if let uuid = invitation.callkitUUID {
logger.debug("CallController: report pushkit call via CallKit")
let update = self.cxCallUpdate(invitation: invitation)
self.provider.reportNewIncomingCall(with: uuid, update: update) { error in
if error != nil {
m.callInvitations.removeValue(forKey: contactId)
}
// Tell PushKit that the notification is handled.
completion()
}
// Tell PushKit that the notification is handled.
completion()
} else {
self.reportExpiredCall(update: update, completion)
}
} else {
reportExpiredCall(update: update, completion)
self.reportExpiredCall(payload: payload, completion)
}
} else {
reportExpiredCall(payload: payload, completion)
}
}
+12 -8
View File
@@ -22,7 +22,7 @@ class CallManager {
let m = ChatModel.shared
if let call = m.activeCall, call.callkitUUID == callUUID {
m.showCallView = true
m.callCommand = .capabilities(media: call.localMedia)
Task { await m.callCommand.processCommand(.capabilities(media: call.localMedia)) }
return true
}
return false
@@ -57,19 +57,21 @@ class CallManager {
m.activeCall = call
m.showCallView = true
m.callCommand = .start(
Task {
await m.callCommand.processCommand(.start(
media: invitation.callType.media,
aesKey: invitation.sharedKey,
iceServers: iceServers,
relay: useRelay
)
))
}
}
}
func enableMedia(media: CallMediaType, enable: Bool, callUUID: UUID) -> Bool {
if let call = ChatModel.shared.activeCall, call.callkitUUID == callUUID {
let m = ChatModel.shared
m.callCommand = .media(media: media, enable: enable)
Task { await m.callCommand.processCommand(.media(media: media, enable: enable)) }
return true
}
return false
@@ -94,11 +96,13 @@ class CallManager {
completed()
} else {
logger.debug("CallManager.endCall: ending call...")
m.callCommand = .end
m.activeCall = nil
m.showCallView = false
completed()
Task {
await m.callCommand.processCommand(.end)
await MainActor.run {
m.activeCall = nil
m.showCallView = false
completed()
}
do {
try await apiEndCall(call.contact)
} catch {
+44 -14
View File
@@ -335,6 +335,50 @@ extension WCallResponse: Encodable {
}
}
actor WebRTCCommandProcessor {
private var client: WebRTCClient? = nil
private var commands: [WCallCommand] = []
private var running: Bool = false
func setClient(_ client: WebRTCClient?) async {
logger.debug("WebRTC: setClient, commands count \(self.commands.count)")
self.client = client
if client != nil {
await processAllCommands()
} else {
commands.removeAll()
}
}
func processCommand(_ c: WCallCommand) async {
// logger.debug("WebRTC: process command \(c.cmdType)")
commands.append(c)
if !running && client != nil {
await processAllCommands()
}
}
func processAllCommands() async {
logger.debug("WebRTC: process all commands, commands count \(self.commands.count), client == nil \(self.client == nil)")
if let client = client {
running = true
while let c = commands.first, shouldRunCommand(client, c) {
commands.remove(at: 0)
await client.sendCallCommand(command: c)
logger.debug("WebRTC: processed cmd \(c.cmdType)")
}
running = false
}
}
func shouldRunCommand(_ client: WebRTCClient, _ c: WCallCommand) -> Bool {
switch c {
case .capabilities, .start, .offer, .end: true
default: client.activeCall.wrappedValue != nil
}
}
}
struct ConnectionState: Codable, Equatable {
var connectionState: String
var iceConnectionState: String
@@ -358,26 +402,12 @@ struct ConnectionInfo: Codable, Equatable {
return "\(local?.rawValue ?? unknown) / \(remote?.rawValue ?? unknown)"
}
}
var protocolText: String {
let unknown = NSLocalizedString("unknown", comment: "connection info")
let local = localCandidate?.protocol?.uppercased() ?? unknown
let localRelay = localCandidate?.relayProtocol?.uppercased() ?? unknown
let remote = remoteCandidate?.protocol?.uppercased() ?? unknown
let localText = localRelay == local || localCandidate?.relayProtocol == nil
? local
: "\(local) (\(localRelay))"
return local == remote
? localText
: "\(localText) / \(remote)"
}
}
// https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidate
struct RTCIceCandidate: Codable, Equatable {
var candidateType: RTCIceCandidateType?
var `protocol`: String?
var relayProtocol: String?
var sdpMid: String?
var sdpMLineIndex: Int?
var candidate: String
+115 -74
View File
@@ -18,10 +18,11 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
}()
private static let ivTagBytes: Int = 28
private static let enableEncryption: Bool = true
private var chat_ctrl = getChatCtrl()
struct Call {
var connection: RTCPeerConnection
var iceCandidates: [RTCIceCandidate]
var iceCandidates: IceCandidates
var localMedia: CallMediaType
var localCamera: RTCVideoCapturer?
var localVideoSource: RTCVideoSource?
@@ -33,10 +34,24 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
var frameDecryptor: RTCFrameDecryptor?
}
actor IceCandidates {
private var candidates: [RTCIceCandidate] = []
func getAndClear() async -> [RTCIceCandidate] {
let cs = candidates
candidates = []
return cs
}
func append(_ c: RTCIceCandidate) async {
candidates.append(c)
}
}
private let rtcAudioSession = RTCAudioSession.sharedInstance()
private let audioQueue = DispatchQueue(label: "audio")
private var sendCallResponse: (WVAPIMessage) async -> Void
private var activeCall: Binding<Call?>
var activeCall: Binding<Call?>
private var localRendererAspectRatio: Binding<CGFloat?>
@available(*, unavailable)
@@ -60,7 +75,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
WebRTC.RTCIceServer(urlStrings: ["turn:turn.simplex.im:443?transport=tcp"], username: "private", credential: "yleob6AVkiNI87hpR94Z"),
]
func initializeCall(_ iceServers: [WebRTC.RTCIceServer]?, _ remoteIceCandidates: [RTCIceCandidate], _ mediaType: CallMediaType, _ aesKey: String?, _ relay: Bool?) -> Call {
func initializeCall(_ iceServers: [WebRTC.RTCIceServer]?, _ mediaType: CallMediaType, _ aesKey: String?, _ relay: Bool?) -> Call {
let connection = createPeerConnection(iceServers ?? getWebRTCIceServers() ?? defaultIceServers, relay)
connection.delegate = self
createAudioSender(connection)
@@ -87,7 +102,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
}
return Call(
connection: connection,
iceCandidates: remoteIceCandidates,
iceCandidates: IceCandidates(),
localMedia: mediaType,
localCamera: localCamera,
localVideoSource: localVideoSource,
@@ -144,26 +159,18 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
logger.debug("starting incoming call - create webrtc session")
if activeCall.wrappedValue != nil { endCall() }
let encryption = WebRTCClient.enableEncryption
let call = initializeCall(iceServers?.toWebRTCIceServers(), [], media, encryption ? aesKey : nil, relay)
let call = initializeCall(iceServers?.toWebRTCIceServers(), media, encryption ? aesKey : nil, relay)
activeCall.wrappedValue = call
call.connection.offer { answer in
Task {
let gotCandidates = await self.waitWithTimeout(10_000, stepMs: 1000, until: { self.activeCall.wrappedValue?.iceCandidates.count ?? 0 > 0 })
if gotCandidates {
await self.sendCallResponse(.init(
corrId: nil,
resp: .offer(
offer: compressToBase64(input: encodeJSON(CustomRTCSessionDescription(type: answer.type.toSdpType(), sdp: answer.sdp))),
iceCandidates: compressToBase64(input: encodeJSON(self.activeCall.wrappedValue?.iceCandidates ?? [])),
capabilities: CallCapabilities(encryption: encryption)
),
command: command)
)
} else {
self.endCall()
}
}
let (offer, error) = await call.connection.offer()
if let offer = offer {
resp = .offer(
offer: compressToBase64(input: encodeJSON(CustomRTCSessionDescription(type: offer.type.toSdpType(), sdp: offer.sdp))),
iceCandidates: compressToBase64(input: encodeJSON(await self.getInitialIceCandidates())),
capabilities: CallCapabilities(encryption: encryption)
)
self.waitForMoreIceCandidates()
} else {
resp = .error(message: "offer error: \(error?.localizedDescription ?? "unknown error")")
}
case let .offer(offer, iceCandidates, media, aesKey, iceServers, relay):
if activeCall.wrappedValue != nil {
@@ -172,26 +179,21 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
resp = .error(message: "accept: encryption is not supported")
} else if let offer: CustomRTCSessionDescription = decodeJSON(decompressFromBase64(input: offer)),
let remoteIceCandidates: [RTCIceCandidate] = decodeJSON(decompressFromBase64(input: iceCandidates)) {
let call = initializeCall(iceServers?.toWebRTCIceServers(), remoteIceCandidates, media, WebRTCClient.enableEncryption ? aesKey : nil, relay)
let call = initializeCall(iceServers?.toWebRTCIceServers(), media, WebRTCClient.enableEncryption ? aesKey : nil, relay)
activeCall.wrappedValue = call
let pc = call.connection
if let type = offer.type, let sdp = offer.sdp {
if (try? await pc.setRemoteDescription(RTCSessionDescription(type: type.toWebRTCSdpType(), sdp: sdp))) != nil {
pc.answer { answer in
let (answer, error) = await pc.answer()
if let answer = answer {
self.addIceCandidates(pc, remoteIceCandidates)
// Task {
// try? await Task.sleep(nanoseconds: 32_000 * 1000000)
Task {
await self.sendCallResponse(.init(
corrId: nil,
resp: .answer(
answer: compressToBase64(input: encodeJSON(CustomRTCSessionDescription(type: answer.type.toSdpType(), sdp: answer.sdp))),
iceCandidates: compressToBase64(input: encodeJSON(call.iceCandidates))
),
command: command)
)
}
// }
resp = .answer(
answer: compressToBase64(input: encodeJSON(CustomRTCSessionDescription(type: answer.type.toSdpType(), sdp: answer.sdp))),
iceCandidates: compressToBase64(input: encodeJSON(await self.getInitialIceCandidates()))
)
self.waitForMoreIceCandidates()
} else {
resp = .error(message: "answer error: \(error?.localizedDescription ?? "unknown error")")
}
} else {
resp = .error(message: "accept: remote description is not set")
@@ -234,6 +236,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
resp = .ok
}
case .end:
// TODO possibly, endCall should be called before returning .ok
await sendCallResponse(.init(corrId: nil, resp: .ok, command: command))
endCall()
}
@@ -242,6 +245,33 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
}
}
func getInitialIceCandidates() async -> [RTCIceCandidate] {
await untilIceComplete(timeoutMs: 750, stepMs: 150) {}
let candidates = await activeCall.wrappedValue?.iceCandidates.getAndClear() ?? []
logger.debug("WebRTCClient: sending initial ice candidates: \(candidates.count)")
return candidates
}
func waitForMoreIceCandidates() {
Task {
await untilIceComplete(timeoutMs: 12000, stepMs: 1500) {
let candidates = await self.activeCall.wrappedValue?.iceCandidates.getAndClear() ?? []
if candidates.count > 0 {
logger.debug("WebRTCClient: sending more ice candidates: \(candidates.count)")
await self.sendIceCandidates(candidates)
}
}
}
}
func sendIceCandidates(_ candidates: [RTCIceCandidate]) async {
await self.sendCallResponse(.init(
corrId: nil,
resp: .ice(iceCandidates: compressToBase64(input: encodeJSON(candidates))),
command: nil)
)
}
func enableMedia(_ media: CallMediaType, _ enable: Bool) {
logger.debug("WebRTCClient: enabling media \(media.rawValue) \(enable)")
media == .video ? setVideoEnabled(enable) : setAudioEnabled(enable)
@@ -279,7 +309,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
memcpy(pointer, (unencrypted as NSData).bytes, unencrypted.count)
let isKeyFrame = unencrypted[0] & 1 == 0
let clearTextBytesSize = mediaType.rawValue == 0 ? 1 : isKeyFrame ? 10 : 3
logCrypto("encrypt", chat_encrypt_media(&key, pointer.advanced(by: clearTextBytesSize), Int32(unencrypted.count + WebRTCClient.ivTagBytes - clearTextBytesSize)))
logCrypto("encrypt", chat_encrypt_media(chat_ctrl, &key, pointer.advanced(by: clearTextBytesSize), Int32(unencrypted.count + WebRTCClient.ivTagBytes - clearTextBytesSize)))
return Data(bytes: pointer, count: unencrypted.count + WebRTCClient.ivTagBytes)
} else {
return nil
@@ -387,12 +417,13 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
audioSessionToDefaults()
}
func waitWithTimeout(_ timeoutMs: UInt64, stepMs: UInt64, until success: () -> Bool) async -> Bool {
let startedAt = DispatchTime.now()
while !success() && startedAt.uptimeNanoseconds + timeoutMs * 1000000 > DispatchTime.now().uptimeNanoseconds {
guard let _ = try? await Task.sleep(nanoseconds: stepMs * 1000000) else { break }
}
return success()
func untilIceComplete(timeoutMs: UInt64, stepMs: UInt64, action: @escaping () async -> Void) async {
var t: UInt64 = 0
repeat {
_ = try? await Task.sleep(nanoseconds: stepMs * 1000000)
t += stepMs
await action()
} while t < timeoutMs && activeCall.wrappedValue?.connection.iceGatheringState != .complete
}
}
@@ -405,25 +436,33 @@ extension WebRTC.RTCPeerConnection {
optionalConstraints: nil)
}
func offer(_ completion: @escaping (_ sdp: RTCSessionDescription) -> Void) {
offer(for: mediaConstraints()) { (sdp, error) in
guard let sdp = sdp else {
return
func offer() async -> (RTCSessionDescription?, Error?) {
await withCheckedContinuation { cont in
offer(for: mediaConstraints()) { (sdp, error) in
self.processSDP(cont, sdp, error)
}
self.setLocalDescription(sdp, completionHandler: { (error) in
completion(sdp)
})
}
}
func answer(_ completion: @escaping (_ sdp: RTCSessionDescription) -> Void) {
answer(for: mediaConstraints()) { (sdp, error) in
guard let sdp = sdp else {
return
func answer() async -> (RTCSessionDescription?, Error?) {
await withCheckedContinuation { cont in
answer(for: mediaConstraints()) { (sdp, error) in
self.processSDP(cont, sdp, error)
}
}
}
private func processSDP(_ cont: CheckedContinuation<(RTCSessionDescription?, Error?), Never>, _ sdp: RTCSessionDescription?, _ error: Error?) {
if let sdp = sdp {
self.setLocalDescription(sdp, completionHandler: { (error) in
completion(sdp)
if let error = error {
cont.resume(returning: (nil, error))
} else {
cont.resume(returning: (sdp, nil))
}
})
} else {
cont.resume(returning: (nil, error))
}
}
}
@@ -479,6 +518,7 @@ extension WebRTCClient: RTCPeerConnectionDelegate {
default: enableSpeaker = false
}
setSpeakerEnabledAndConfigureSession(enableSpeaker)
case .connected: sendConnectedEvent(connection)
case .disconnected, .failed: endCall()
default: do {}
}
@@ -491,7 +531,9 @@ extension WebRTCClient: RTCPeerConnectionDelegate {
func peerConnection(_ connection: RTCPeerConnection, didGenerate candidate: WebRTC.RTCIceCandidate) {
// logger.debug("Connection generated candidate \(candidate.debugDescription)")
activeCall.wrappedValue?.iceCandidates.append(candidate.toCandidate(nil, nil, nil))
Task {
await self.activeCall.wrappedValue?.iceCandidates.append(candidate.toCandidate(nil, nil))
}
}
func peerConnection(_ connection: RTCPeerConnection, didRemove candidates: [WebRTC.RTCIceCandidate]) {
@@ -506,10 +548,9 @@ extension WebRTCClient: RTCPeerConnectionDelegate {
lastReceivedMs lastDataReceivedMs: Int32,
changeReason reason: String) {
// logger.debug("Connection changed candidate \(reason) \(remote.debugDescription) \(remote.description)")
sendConnectedEvent(connection, local: local, remote: remote)
}
func sendConnectedEvent(_ connection: WebRTC.RTCPeerConnection, local: WebRTC.RTCIceCandidate, remote: WebRTC.RTCIceCandidate) {
func sendConnectedEvent(_ connection: WebRTC.RTCPeerConnection) {
connection.statistics { (stats: RTCStatisticsReport) in
stats.statistics.values.forEach { stat in
// logger.debug("Stat \(stat.debugDescription)")
@@ -517,24 +558,25 @@ extension WebRTCClient: RTCPeerConnectionDelegate {
let localId = stat.values["localCandidateId"] as? String,
let remoteId = stat.values["remoteCandidateId"] as? String,
let localStats = stats.statistics[localId],
let remoteStats = stats.statistics[remoteId],
local.sdp.contains("\((localStats.values["ip"] as? String ?? "--")) \((localStats.values["port"] as? String ?? "--"))") &&
remote.sdp.contains("\((remoteStats.values["ip"] as? String ?? "--")) \((remoteStats.values["port"] as? String ?? "--"))")
let remoteStats = stats.statistics[remoteId]
{
Task {
await self.sendCallResponse(.init(
corrId: nil,
resp: .connected(connectionInfo: ConnectionInfo(
localCandidate: local.toCandidate(
RTCIceCandidateType.init(rawValue: localStats.values["candidateType"] as! String),
localStats.values["protocol"] as? String,
localStats.values["relayProtocol"] as? String
localCandidate: RTCIceCandidate(
candidateType: RTCIceCandidateType.init(rawValue: localStats.values["candidateType"] as! String),
protocol: localStats.values["protocol"] as? String,
sdpMid: nil,
sdpMLineIndex: nil,
candidate: ""
),
remoteCandidate: remote.toCandidate(
RTCIceCandidateType.init(rawValue: remoteStats.values["candidateType"] as! String),
remoteStats.values["protocol"] as? String,
remoteStats.values["relayProtocol"] as? String
))),
remoteCandidate: RTCIceCandidate(
candidateType: RTCIceCandidateType.init(rawValue: remoteStats.values["candidateType"] as! String),
protocol: remoteStats.values["protocol"] as? String,
sdpMid: nil,
sdpMLineIndex: nil,
candidate: ""))),
command: nil)
)
}
@@ -634,11 +676,10 @@ extension RTCIceCandidate {
}
extension WebRTC.RTCIceCandidate {
func toCandidate(_ candidateType: RTCIceCandidateType?, _ protocol: String?, _ relayProtocol: String?) -> RTCIceCandidate {
func toCandidate(_ candidateType: RTCIceCandidateType?, _ protocol: String?) -> RTCIceCandidate {
RTCIceCandidate(
candidateType: candidateType,
protocol: `protocol`,
relayProtocol: relayProtocol,
sdpMid: sdpMid,
sdpMLineIndex: Int(sdpMLineIndex),
candidate: sdp
+8 -3
View File
@@ -723,9 +723,14 @@ struct ChatView: View {
if ci.meta.itemDeleted == nil && !ci.isLiveDummy && !live {
menu.append(replyUIAction(ci))
}
menu.append(shareUIAction(ci))
menu.append(copyUIAction(ci))
if let fileSource = getLoadedFileSource(ci.file) {
let fileSource = getLoadedFileSource(ci.file)
let fileExists = if let fs = fileSource, FileManager.default.fileExists(atPath: getAppFilePath(fs.filePath).path) { true } else { false }
let copyAndShareAllowed = !ci.content.text.isEmpty || (ci.content.msgContent?.isImage == true && fileExists)
if copyAndShareAllowed {
menu.append(shareUIAction(ci))
menu.append(copyUIAction(ci))
}
if let fileSource = fileSource, fileExists {
if case .image = ci.content.msgContent, let image = getLoadedImage(ci.file) {
if image.imageData != nil {
menu.append(saveFileAction(fileSource))
@@ -104,7 +104,7 @@ struct ComposeState {
var sendEnabled: Bool {
switch preview {
case .mediaPreviews: return true
case let .mediaPreviews(media): return !media.isEmpty
case .voicePreview: return voiceMessageRecordingState == .finished
case .filePreview: return true
default: return !message.isEmpty || liveMessage != nil
@@ -384,10 +384,10 @@ struct ComposeView: View {
}
}
.sheet(isPresented: $showMediaPicker) {
LibraryMediaListPicker(media: $chosenMedia, selectionLimit: 10) { itemsSelected in
showMediaPicker = false
if itemsSelected {
DispatchQueue.main.async {
LibraryMediaListPicker(addMedia: addMediaContent, selectionLimit: 10, finishedPreprocessing: finishedPreprocessingMediaContent) { itemsSelected in
await MainActor.run {
showMediaPicker = false
if itemsSelected {
composeState = composeState.copy(preview: .mediaPreviews(mediaPreviews: []))
}
}
@@ -488,6 +488,30 @@ struct ComposeView: View {
}
}
private func addMediaContent(_ content: UploadContent) async {
if let img = resizeImageToStrSize(content.uiImage, maxDataSize: 14000) {
var newMedia: [(String, UploadContent?)] = []
if case var .mediaPreviews(media) = composeState.preview {
media.append((img, content))
newMedia = media
} else {
newMedia = [(img, content)]
}
await MainActor.run {
composeState = composeState.copy(preview: .mediaPreviews(mediaPreviews: newMedia))
}
}
}
// When error occurs while converting video, remove media preview
private func finishedPreprocessingMediaContent() {
if case let .mediaPreviews(media) = composeState.preview, media.isEmpty {
DispatchQueue.main.async {
composeState = composeState.copy(preview: .noPreview)
}
}
}
private var maxFileSize: Int64 {
getMaxFileSize(.xftp)
}
@@ -157,7 +157,7 @@ struct AddGroupMembersViewCommon: View {
private func rolePicker() -> some View {
Picker("New member role", selection: $selectedRole) {
ForEach(GroupMemberRole.allCases) { role in
if role <= groupInfo.membership.memberRole {
if role <= groupInfo.membership.memberRole && role != .author {
Text(role.text)
}
}
@@ -16,7 +16,6 @@ struct GroupChatInfoView: View {
@Environment(\.dismiss) var dismiss: DismissAction
@ObservedObject var chat: Chat
@Binding var groupInfo: GroupInfo
@ObservedObject private var alertManager = AlertManager.shared
@State private var alert: GroupChatInfoViewAlert? = nil
@State private var groupLink: String?
@State private var groupLinkMemberRole: GroupMemberRole = .member
@@ -188,17 +188,19 @@ struct GroupMemberInfoView: View {
// this condition prevents re-setting picker
if !justOpened { return }
}
newRole = member.memberRole
do {
let (_, stats) = try apiGroupMemberInfo(groupInfo.apiId, member.groupMemberId)
let (mem, code) = member.memberActive ? try apiGetGroupMemberCode(groupInfo.apiId, member.groupMemberId) : (member, nil)
_ = chatModel.upsertGroupMember(groupInfo, mem)
connectionStats = stats
connectionCode = code
} catch let error {
logger.error("apiGroupMemberInfo or apiGetGroupMemberCode error: \(responseError(error))")
}
justOpened = false
DispatchQueue.main.async {
newRole = member.memberRole
do {
let (_, stats) = try apiGroupMemberInfo(groupInfo.apiId, member.groupMemberId)
let (mem, code) = member.memberActive ? try apiGetGroupMemberCode(groupInfo.apiId, member.groupMemberId) : (member, nil)
_ = chatModel.upsertGroupMember(groupInfo, mem)
connectionStats = stats
connectionCode = code
} catch let error {
logger.error("apiGroupMemberInfo or apiGetGroupMemberCode error: \(responseError(error))")
}
}
}
.onChange(of: newRole) { newRole in
if newRole != member.memberRole {
@@ -103,8 +103,10 @@ struct GroupProfileView: View {
}
}
.sheet(isPresented: $showImagePicker) {
LibraryImagePicker(image: $chosenImage) {
didSelectItem in showImagePicker = false
LibraryImagePicker(image: $chosenImage) { _ in
await MainActor.run {
showImagePicker = false
}
}
}
.onChange(of: chosenImage) { image in
@@ -17,7 +17,7 @@ struct ScanCodeView: View {
var body: some View {
VStack(alignment: .leading) {
CodeScannerView(codeTypes: [.qr], completion: processQRCode)
CodeScannerView(codeTypes: [.qr], scanMode: .oncePerCode, completion: processQRCode)
.aspectRatio(1, contentMode: .fit)
.cornerRadius(12)
Text("Scan security code from your contact's app.")
@@ -415,7 +415,7 @@ struct DatabaseView: View {
do {
try initializeChat(start: true)
m.chatDbChanged = false
appStateGroupDefault.set(.active)
AppChatState.shared.set(.active)
} catch let error {
fatalError("Error starting chat \(responseError(error))")
}
@@ -427,7 +427,7 @@ struct DatabaseView: View {
m.chatRunning = true
ChatReceiver.shared.start()
chatLastStartGroupDefault.set(Date.now)
appStateGroupDefault.set(.active)
AppChatState.shared.set(.active)
} catch let error {
runChat = false
alert = .error(title: "Error starting chat", error: responseError(error))
@@ -477,7 +477,7 @@ func stopChatAsync() async throws {
try await apiStopChat()
ChatReceiver.shared.stop()
await MainActor.run { ChatModel.shared.chatRunning = false }
appStateGroupDefault.set(.stopped)
AppChatState.shared.set(.stopped)
}
func deleteChatAsync() async throws {
+100 -82
View File
@@ -13,112 +13,130 @@ import SimpleXChat
struct LibraryImagePicker: View {
@Binding var image: UIImage?
var didFinishPicking: (_ didSelectItems: Bool) -> Void
@State var images: [UploadContent] = []
var didFinishPicking: (_ didSelectImage: Bool) async -> Void
@State var mediaAdded = false
var body: some View {
LibraryMediaListPicker(media: $images, selectionLimit: 1, didFinishPicking: didFinishPicking)
.onChange(of: images) { _ in
if let img = images.first {
image = img.uiImage
}
}
LibraryMediaListPicker(addMedia: addMedia, selectionLimit: 1, didFinishPicking: didFinishPicking)
}
private func addMedia(_ content: UploadContent) async {
if mediaAdded { return }
await MainActor.run {
mediaAdded = true
image = content.uiImage
}
}
}
struct LibraryMediaListPicker: UIViewControllerRepresentable {
typealias UIViewControllerType = PHPickerViewController
@Binding var media: [UploadContent]
var addMedia: (_ content: UploadContent) async -> Void
var selectionLimit: Int
var didFinishPicking: (_ didSelectItems: Bool) -> Void
var finishedPreprocessing: () -> Void = {}
var didFinishPicking: (_ didSelectItems: Bool) async -> Void
class Coordinator: PHPickerViewControllerDelegate {
let parent: LibraryMediaListPicker
let dispatchQueue = DispatchQueue(label: "chat.simplex.app.LibraryMediaListPicker")
var media: [UploadContent] = []
var mediaCount: Int = 0
init(_ parent: LibraryMediaListPicker) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
parent.didFinishPicking(!results.isEmpty)
guard !results.isEmpty else {
return
Task {
await parent.didFinishPicking(!results.isEmpty)
if results.isEmpty { return }
for r in results {
await loadItem(r.itemProvider)
}
parent.finishedPreprocessing()
}
}
parent.media = []
media = []
mediaCount = results.count
for result in results {
logger.log("LibraryMediaListPicker result")
let p = result.itemProvider
if p.hasItemConformingToTypeIdentifier(UTType.movie.identifier) {
p.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) { url, error in
if let url = url {
let tempUrl = URL(fileURLWithPath: getTempFilesDirectory().path + "/" + generateNewFileName("video", url.pathExtension))
if ((try? FileManager.default.copyItem(at: url, to: tempUrl)) != nil) {
ChatModel.shared.filesToDelete.insert(tempUrl)
self.loadVideo(url: tempUrl, error: error)
private func loadItem(_ p: NSItemProvider) async {
logger.debug("LibraryMediaListPicker result")
if p.hasItemConformingToTypeIdentifier(UTType.movie.identifier) {
if let video = await loadVideo(p) {
await self.parent.addMedia(video)
logger.debug("LibraryMediaListPicker: added video")
}
} else if p.hasItemConformingToTypeIdentifier(UTType.data.identifier) {
if let img = await loadImageData(p) {
await self.parent.addMedia(img)
logger.debug("LibraryMediaListPicker: added image")
}
} else if p.canLoadObject(ofClass: UIImage.self) {
if let img = await loadImage(p) {
await self.parent.addMedia(.simpleImage(image: img))
logger.debug("LibraryMediaListPicker: added image")
}
}
}
private func loadImageData(_ p: NSItemProvider) async -> UploadContent? {
await withCheckedContinuation { cont in
loadFileURL(p, type: UTType.data) { url in
if let url = url {
let img = UploadContent.loadFromURL(url: url)
cont.resume(returning: img)
} else {
cont.resume(returning: nil)
}
}
}
}
private func loadImage(_ p: NSItemProvider) async -> UIImage? {
await withCheckedContinuation { cont in
p.loadObject(ofClass: UIImage.self) { obj, err in
if let err = err {
logger.error("LibraryMediaListPicker result image error: \(err.localizedDescription)")
cont.resume(returning: nil)
} else {
cont.resume(returning: obj as? UIImage)
}
}
}
}
private func loadVideo(_ p: NSItemProvider) async -> UploadContent? {
await withCheckedContinuation { cont in
loadFileURL(p, type: UTType.movie) { url in
if let url = url {
let tempUrl = URL(fileURLWithPath: generateNewFileName(getTempFilesDirectory().path + "/" + "rawvideo", url.pathExtension, fullPath: true))
let convertedVideoUrl = URL(fileURLWithPath: generateNewFileName(getTempFilesDirectory().path + "/" + "video", "mp4", fullPath: true))
do {
// logger.debug("LibraryMediaListPicker copyItem \(url) to \(tempUrl)")
try FileManager.default.copyItem(at: url, to: tempUrl)
} catch let err {
logger.error("LibraryMediaListPicker copyItem error: \(err.localizedDescription)")
return cont.resume(returning: nil)
}
Task {
let success = await makeVideoQualityLower(tempUrl, outputUrl: convertedVideoUrl)
try? FileManager.default.removeItem(at: tempUrl)
if success {
_ = ChatModel.shared.filesToDelete.insert(convertedVideoUrl)
let video = UploadContent.loadVideoFromURL(url: convertedVideoUrl)
return cont.resume(returning: video)
}
try? FileManager.default.removeItem(at: convertedVideoUrl)
cont.resume(returning: nil)
}
}
} else if p.hasItemConformingToTypeIdentifier(UTType.data.identifier) {
p.loadFileRepresentation(forTypeIdentifier: UTType.data.identifier) { url, error in
self.loadImage(object: url, error: error)
}
} else if p.canLoadObject(ofClass: UIImage.self) {
p.loadObject(ofClass: UIImage.self) { image, error in
DispatchQueue.main.async {
self.loadImage(object: image, error: error)
}
}
}
}
}
private func loadFileURL(_ p: NSItemProvider, type: UTType, completion: @escaping (URL?) -> Void) {
p.loadFileRepresentation(forTypeIdentifier: type.identifier) { url, err in
if let err = err {
logger.error("LibraryMediaListPicker loadFileURL error: \(err.localizedDescription)")
completion(nil)
} else {
dispatchQueue.sync { self.mediaCount -= 1}
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
self.dispatchQueue.sync {
if self.parent.media.count == 0 {
logger.log("LibraryMediaListPicker: added \(self.media.count) images out of \(results.count)")
self.parent.media = self.media
}
}
}
}
func loadImage(object: Any?, error: Error? = nil) {
if let error = error {
logger.error("LibraryMediaListPicker: couldn't load image with error: \(error.localizedDescription)")
} else if let image = object as? UIImage {
media.append(.simpleImage(image: image))
logger.log("LibraryMediaListPicker: added image")
} else if let url = object as? URL, let image = UploadContent.loadFromURL(url: url) {
media.append(image)
}
dispatchQueue.sync {
self.mediaCount -= 1
if self.mediaCount == 0 && self.parent.media.count == 0 {
logger.log("LibraryMediaListPicker: added all media")
self.parent.media = self.media
self.media = []
}
}
}
func loadVideo(url: URL?, error: Error? = nil) {
if let error = error {
logger.error("LibraryMediaListPicker: couldn't load video with error: \(error.localizedDescription)")
} else if let url = url as URL?, let video = UploadContent.loadVideoFromURL(url: url) {
media.append(video)
}
dispatchQueue.sync {
self.mediaCount -= 1
if self.mediaCount == 0 && self.parent.media.count == 0 {
logger.log("LibraryMediaListPicker: added all media")
self.parent.media = self.media
self.media = []
completion(url)
}
}
}
@@ -0,0 +1,26 @@
//
// VideoUtils.swift
// SimpleX (iOS)
//
// Created by Avently on 25.12.2023.
// Copyright © 2023 SimpleX Chat. All rights reserved.
//
import AVFoundation
import Foundation
import SimpleXChat
func makeVideoQualityLower(_ input: URL, outputUrl: URL) async -> Bool {
let asset: AVURLAsset = AVURLAsset(url: input, options: nil)
if let s = AVAssetExportSession(asset: asset, presetName: AVAssetExportPreset640x480) {
s.outputURL = outputUrl
s.outputFileType = .mp4
s.metadataItemFilter = AVMetadataItemFilter.forSharing()
await s.export()
if let err = s.error {
logger.error("Failed to export video with error: \(err)")
}
return s.status == .completed
}
return false
}
@@ -52,7 +52,7 @@ struct LocalAuthView: View {
resetChatCtrl()
try initializeChat(start: true)
m.chatDbChanged = false
appStateGroupDefault.set(.active)
AppChatState.shared.set(.active)
if m.currentUser != nil { return }
var profile: Profile? = nil
if let displayName = displayName, displayName != "" {
@@ -130,8 +130,10 @@ struct AddGroupView: View {
}
}
.sheet(isPresented: $showImagePicker) {
LibraryImagePicker(image: $chosenImage) {
didSelectItem in showImagePicker = false
LibraryImagePicker(image: $chosenImage) { _ in
await MainActor.run {
showImagePicker = false
}
}
}
.alert(isPresented: $showInvalidNameAlert) {
@@ -73,6 +73,7 @@ struct CreateLinkView: View {
Task {
if let (connReq, pcc) = await apiAddContact(incognito: incognitoGroupDefault.get()) {
await MainActor.run {
m.updateContactConnection(pcc)
connReqInvitation = connReq
contactConnection = pcc
m.connReqInv = connReq
@@ -52,6 +52,9 @@ struct NewChatButton: View {
func addContactAction() {
Task {
if let (connReq, pcc) = await apiAddContact(incognito: incognitoGroupDefault.get()) {
await MainActor.run {
ChatModel.shared.updateContactConnection(pcc)
}
actionSheet = .createLink(link: connReq, connection: pcc)
}
}
@@ -346,7 +349,10 @@ private func connectContactViaAddress_(_ contact: Contact, dismiss: Bool, incogn
private func connectViaLink(_ connectionLink: String, connectionPlan: ConnectionPlan?, dismiss: Bool, incognito: Bool) {
Task {
if let connReqType = await apiConnect(incognito: incognito, connReq: connectionLink) {
if let (connReqType, pcc) = await apiConnect(incognito: incognito, connReq: connectionLink) {
await MainActor.run {
ChatModel.shared.updateContactConnection(pcc)
}
let crt: ConnReqType
if let plan = connectionPlan {
crt = planToConnReqType(plan)
+13 -20
View File
@@ -11,20 +11,12 @@ import CoreImage.CIFilterBuiltins
struct MutableQRCode: View {
@Binding var uri: String
@State private var image: UIImage?
var withLogo: Bool = true
var tintColor = UIColor(red: 0.023, green: 0.176, blue: 0.337, alpha: 1)
var body: some View {
ZStack {
if let image = image {
qrCodeImage(image)
}
}
.onAppear {
image = generateImage(uri)
}
.onChange(of: uri) { _ in
image = generateImage(uri)
}
QRCode(uri: uri, withLogo: withLogo, tintColor: tintColor)
.id("simplex-qrcode-view-for-\(uri)")
}
}
@@ -49,7 +41,7 @@ struct QRCode: View {
var withLogo: Bool = true
var tintColor = UIColor(red: 0.023, green: 0.176, blue: 0.337, alpha: 1)
@State private var image: UIImage? = nil
@State private var makeScreenshotBinding: () -> Void = {}
@State private var makeScreenshotFunc: () -> Void = {}
var body: some View {
ZStack {
@@ -70,18 +62,19 @@ struct QRCode: View {
}
}
.onAppear {
makeScreenshotBinding = {
makeScreenshotFunc = {
let size = CGSizeMake(1024 / UIScreen.main.scale, 1024 / UIScreen.main.scale)
showShareSheet(items: [makeScreenshot(geo.frame(in: .local).origin, size)])}
showShareSheet(items: [makeScreenshot(geo.frame(in: .local).origin, size)])
}
}
.frame(width: geo.size.width, height: geo.size.height)
}
}
.onTapGesture(perform: makeScreenshotBinding)
.onTapGesture(perform: makeScreenshotFunc)
.onAppear {
image = image ?? generateImage(uri)?.replaceColor(UIColor.black, tintColor)
image = image ?? generateImage(uri, tintColor: tintColor)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
@@ -93,13 +86,13 @@ private func qrCodeImage(_ image: UIImage) -> some View {
.textSelection(.enabled)
}
private func generateImage(_ uri: String) -> UIImage? {
private func generateImage(_ uri: String, tintColor: UIColor) -> UIImage? {
let context = CIContext()
let filter = CIFilter.qrCodeGenerator()
filter.message = Data(uri.utf8)
if let outputImage = filter.outputImage,
let cgImage = context.createCGImage(outputImage, from: outputImage.extent) {
return UIImage(cgImage: cgImage)
return UIImage(cgImage: cgImage).replaceColor(UIColor.black, tintColor)
}
return nil
}
@@ -25,7 +25,7 @@ struct ScanToConnectView: View {
.fixedSize(horizontal: false, vertical: true)
.padding(.vertical)
CodeScannerView(codeTypes: [.qr], completion: processQRCode)
CodeScannerView(codeTypes: [.qr], scanMode: .continuous, completion: processQRCode)
.aspectRatio(1, contentMode: .fit)
.cornerRadius(12)
@@ -81,11 +81,6 @@ struct CreateSimpleXAddress: View {
DispatchQueue.main.async {
m.userAddress = UserContactLink(connReqContact: connReqContact)
}
if let u = try await apiSetProfileAddress(on: true) {
DispatchQueue.main.async {
m.updateUser(u)
}
}
await MainActor.run { progressIndicator = false }
} catch let error {
logger.error("CreateSimpleXAddress create address: \(responseError(error))")
@@ -100,7 +95,7 @@ struct CreateSimpleXAddress: View {
} label: {
Text("Create SimpleX address").font(.title)
}
Text("Your contacts in SimpleX will see it.\nYou can change it in Settings.")
Text("You can make it visible to your SimpleX contacts via Settings.")
.multilineTextAlignment(.center)
.font(.footnote)
.padding(.horizontal, 32)
@@ -283,6 +283,37 @@ private let versionDescriptions: [VersionDescription] = [
),
]
),
VersionDescription(
version: "v5.4",
post: URL(string: "https://simplex.chat/blog/20231125-simplex-chat-v5-4-link-mobile-desktop-quantum-resistant-better-groups.html"),
features: [
FeatureDescription(
icon: "desktopcomputer",
title: "Link mobile and desktop apps! 🔗",
description: "Via secure quantum resistant protocol."
),
FeatureDescription(
icon: "person.2",
title: "Better groups",
description: "Faster joining and more reliable messages."
),
FeatureDescription(
icon: "theatermasks",
title: "Incognito groups",
description: "Create a group using a random profile."
),
FeatureDescription(
icon: "hand.raised",
title: "Block group members",
description: "To hide unwanted messages."
),
FeatureDescription(
icon: "gift",
title: "A few more things",
description: "- optionally notify deleted contacts.\n- profile names with spaces.\n- and more!"
),
]
),
]
private let lastVersion = versionDescriptions.last!.version
@@ -16,12 +16,19 @@ struct ConnectDesktopView: View {
var viaSettings = false
@AppStorage(DEFAULT_DEVICE_NAME_FOR_REMOTE_ACCESS) private var deviceName = UIDevice.current.name
@AppStorage(DEFAULT_CONFIRM_REMOTE_SESSIONS) private var confirmRemoteSessions = false
@AppStorage(DEFAULT_CONNECT_REMOTE_VIA_MULTICAST) private var connectRemoteViaMulticast = false
@AppStorage(DEFAULT_OFFER_REMOTE_MULTICAST) private var offerRemoteMulticast = true
@AppStorage(DEFAULT_CONNECT_REMOTE_VIA_MULTICAST) private var connectRemoteViaMulticast = true
@AppStorage(DEFAULT_CONNECT_REMOTE_VIA_MULTICAST_AUTO) private var connectRemoteViaMulticastAuto = true
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
@State private var sessionAddress: String = ""
@State private var remoteCtrls: [RemoteCtrlInfo] = []
@State private var alert: ConnectDesktopAlert?
@State private var showConnectScreen = true
@State private var showQRCodeScanner = true
@State private var firstAppearance = true
private var useMulticast: Bool {
connectRemoteViaMulticast && !remoteCtrls.isEmpty
}
private enum ConnectDesktopAlert: Identifiable {
case unlinkDesktop(rc: RemoteCtrlInfo)
@@ -67,9 +74,14 @@ struct ConnectDesktopView: View {
var viewBody: some View {
Group {
if let session = m.remoteCtrlSession {
let discovery = m.remoteCtrlSession?.discovery
if discovery == true || (discovery == nil && !showConnectScreen) {
searchingDesktopView()
} else if let session = m.remoteCtrlSession {
switch session.sessionState {
case .starting: connectingDesktopView(session, nil)
case .searching: searchingDesktopView()
case let .found(rc, compatible): foundDesktopView(session, rc, compatible)
case let .connecting(rc_): connectingDesktopView(session, rc_)
case let .pendingConfirmation(rc_, sessCode):
if confirmRemoteSessions || rc_ == nil {
@@ -81,16 +93,35 @@ struct ConnectDesktopView: View {
}
case let .connected(rc, _): activeSessionView(session, rc)
}
} else {
// The hack below prevents camera freezing when exiting linked devices view.
// Using showQRCodeScanner inside connectDesktopView or passing it as parameter still results in freezing.
} else if showQRCodeScanner || firstAppearance {
connectDesktopView()
} else {
connectDesktopView(showScanner: false)
}
}
.onAppear {
setDeviceName(deviceName)
updateRemoteCtrls()
showConnectScreen = !useMulticast
if m.remoteCtrlSession != nil {
disconnectDesktop()
} else if useMulticast {
findKnownDesktop()
}
// The hack below prevents camera freezing when exiting linked devices view.
// `firstAppearance` prevents camera flicker when the view first opens.
// moving `showQRCodeScanner = false` to `onDisappear` (to avoid `firstAppearance`) does not prevent freeze.
showQRCodeScanner = false
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
firstAppearance = false
showQRCodeScanner = true
}
}
.onDisappear {
if m.remoteCtrlSession != nil {
showConnectScreen = false
disconnectDesktop()
}
}
@@ -134,12 +165,14 @@ struct ConnectDesktopView: View {
.interactiveDismissDisabled(m.activeRemoteCtrl)
}
private func connectDesktopView() -> some View {
private func connectDesktopView(showScanner: Bool = true) -> some View {
List {
Section("This device name") {
devicesView()
}
scanDesctopAddressView()
if showScanner {
scanDesctopAddressView()
}
if developerTools {
desktopAddressView()
}
@@ -167,6 +200,56 @@ struct ConnectDesktopView: View {
.navigationTitle("Connecting to desktop")
}
private func searchingDesktopView() -> some View {
List {
Section("This device name") {
devicesView()
}
Section("Found desktop") {
Text("Waiting for desktop...").italic()
Button {
disconnectDesktop()
} label: {
Label("Scan QR code", systemImage: "qrcode")
}
}
}
.navigationTitle("Connecting to desktop")
}
@ViewBuilder private func foundDesktopView(_ session: RemoteCtrlSession, _ rc: RemoteCtrlInfo, _ compatible: Bool) -> some View {
let v = List {
Section("This device name") {
devicesView()
}
Section("Found desktop") {
ctrlDeviceNameText(session, rc)
ctrlDeviceVersionText(session)
if !compatible {
Text("Not compatible!").foregroundColor(.red)
} else if !connectRemoteViaMulticastAuto {
Button {
confirmKnownDesktop(rc)
} label: {
Label("Connect", systemImage: "checkmark")
}
}
}
if !compatible && !connectRemoteViaMulticastAuto {
Section {
disconnectButton("Cancel")
}
}
}
.navigationTitle("Found desktop")
if compatible && connectRemoteViaMulticastAuto {
v.onAppear { confirmKnownDesktop(rc) }
} else {
v
}
}
private func verifySessionView(_ session: RemoteCtrlSession, _ rc: RemoteCtrlInfo?, _ sessCode: String) -> some View {
List {
Section("Connected to desktop") {
@@ -191,7 +274,7 @@ struct ConnectDesktopView: View {
}
private func ctrlDeviceNameText(_ session: RemoteCtrlSession, _ rc: RemoteCtrlInfo?) -> Text {
var t = Text(rc?.deviceViewName ?? session.ctrlAppInfo.deviceName)
var t = Text(rc?.deviceViewName ?? session.ctrlAppInfo?.deviceName ?? "")
if (rc == nil) {
t = t + Text(" ") + Text("(new)").italic()
}
@@ -199,8 +282,8 @@ struct ConnectDesktopView: View {
}
private func ctrlDeviceVersionText(_ session: RemoteCtrlSession) -> Text {
let v = session.ctrlAppInfo.appVersionRange.maxVersion
var t = Text("v\(v)")
let v = session.ctrlAppInfo?.appVersionRange.maxVersion
var t = Text("v\(v ?? "")")
if v != session.appVersion {
t = t + Text(" ") + Text("(this device v\(session.appVersion))").italic()
}
@@ -249,7 +332,7 @@ struct ConnectDesktopView: View {
private func scanDesctopAddressView() -> some View {
Section("Scan QR code from desktop") {
CodeScannerView(codeTypes: [.qr], completion: processDesktopQRCode)
CodeScannerView(codeTypes: [.qr], scanMode: .oncePerCode, completion: processDesktopQRCode)
.aspectRatio(1, contentMode: .fit)
.cornerRadius(12)
.listRowBackground(Color.clear)
@@ -301,7 +384,10 @@ struct ConnectDesktopView: View {
Section("Linked desktop options") {
Toggle("Verify connections", isOn: $confirmRemoteSessions)
Toggle("Discover on network", isOn: $connectRemoteViaMulticast).disabled(true)
Toggle("Discover via local network", isOn: $connectRemoteViaMulticast)
if connectRemoteViaMulticast {
Toggle("Connect automatically", isOn: $connectRemoteViaMulticastAuto)
}
}
}
.navigationTitle("Linked desktops")
@@ -335,10 +421,42 @@ struct ConnectDesktopView: View {
}
}
private func connectDesktopAddress(_ addr: String) {
private func findKnownDesktop() {
Task {
do {
let (rc_, ctrlAppInfo, v) = try await connectRemoteCtrl(desktopAddress: addr)
try await findKnownRemoteCtrl()
await MainActor.run {
m.remoteCtrlSession = RemoteCtrlSession(
ctrlAppInfo: nil,
appVersion: "",
sessionState: .searching
)
showConnectScreen = true
}
} catch let e {
await MainActor.run {
errorAlert(e)
}
}
}
}
private func confirmKnownDesktop(_ rc: RemoteCtrlInfo) {
connectDesktop_ {
try await confirmRemoteCtrl(rc.remoteCtrlId)
}
}
private func connectDesktopAddress(_ addr: String) {
connectDesktop_ {
try await connectRemoteCtrl(desktopAddress: addr)
}
}
private func connectDesktop_(_ connect: @escaping () async throws -> (RemoteCtrlInfo?, CtrlAppInfo, String)) {
Task {
do {
let (rc_, ctrlAppInfo, v) = try await connect()
await MainActor.run {
sessionAddress = ""
m.remoteCtrlSession = RemoteCtrlSession(
@@ -380,11 +498,11 @@ struct ConnectDesktopView: View {
}
}
private func disconnectButton() -> some View {
private func disconnectButton(_ label: LocalizedStringKey = "Disconnect") -> some View {
Button {
disconnectDesktop()
disconnectDesktop(.dismiss)
} label: {
Label("Disconnect", systemImage: "multiply")
Label(label, systemImage: "multiply")
}
}
@@ -393,7 +511,11 @@ struct ConnectDesktopView: View {
do {
try await stopRemoteCtrl()
await MainActor.run {
switchToLocalSession()
if case .connected = m.remoteCtrlSession?.sessionState {
switchToLocalSession()
} else {
m.remoteCtrlSession = nil
}
switch action {
case .back: dismiss()
case .dismiss: dismiss()
@@ -51,9 +51,9 @@ struct AdvancedNetworkSettings: View {
}
.disabled(currentNetCfg == NetCfg.proxyDefaults)
timeoutSettingPicker("TCP connection timeout", selection: $netCfg.tcpConnectTimeout, values: [5_000000, 7_500000, 10_000000, 15_000000, 20_000000, 30_000000, 45_000000], label: secondsLabel)
timeoutSettingPicker("Protocol timeout", selection: $netCfg.tcpTimeout, values: [3_000000, 5_000000, 7_000000, 10_000000, 15_000000, 20_000000, 30_000000], label: secondsLabel)
timeoutSettingPicker("Protocol timeout per KB", selection: $netCfg.tcpTimeoutPerKb, values: [15_000, 30_000, 60_000, 90_000, 120_000], label: secondsLabel)
timeoutSettingPicker("TCP connection timeout", selection: $netCfg.tcpConnectTimeout, values: [7_500000, 10_000000, 15_000000, 20_000000, 30_000000, 45_000000], label: secondsLabel)
timeoutSettingPicker("Protocol timeout", selection: $netCfg.tcpTimeout, values: [5_000000, 7_000000, 10_000000, 15_000000, 20_000000, 30_000000], label: secondsLabel)
timeoutSettingPicker("Protocol timeout per KB", selection: $netCfg.tcpTimeoutPerKb, values: [15_000, 30_000, 45_000, 60_000, 90_000, 120_000], 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)
@@ -14,9 +14,6 @@ struct NotificationsView: View {
@State private var notificationMode: NotificationsMode = ChatModel.shared.notificationMode
@State private var showAlert: NotificationAlert?
@State private var legacyDatabase = dbContainerGroupDefault.get() == .documents
// @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
// @AppStorage(GROUP_DEFAULT_NTF_ENABLE_LOCAL, store: groupDefaults) private var ntfEnableLocal = false
// @AppStorage(GROUP_DEFAULT_NTF_ENABLE_PERIODIC, store: groupDefaults) private var ntfEnablePeriodic = false
var body: some View {
List {
@@ -88,13 +85,6 @@ struct NotificationsView: View {
.padding(.top, 1)
}
}
// if developerTools {
// Section(String("Experimental")) {
// Toggle(String("Always enable local"), isOn: $ntfEnableLocal)
// Toggle(String("Always enable periodic"), isOn: $ntfEnablePeriodic)
// }
// }
}
.disabled(legacyDatabase)
}
@@ -119,7 +109,7 @@ struct NotificationsView: View {
private func ntfModeAlertTitle(_ mode: NotificationsMode) -> LocalizedStringKey {
switch mode {
case .off: return "Turn off notifications?"
case .off: return "Use only local notifications?"
case .periodic: return "Enable periodic notifications?"
case .instant: return "Enable instant notifications?"
}
@@ -467,6 +467,7 @@ struct SimplexLockView: View {
switch a {
case .enableAuth:
SetAppPasscodeView {
m.contentViewAccessAuthenticated = true
laLockDelay = 30
prefPerformLA = true
showChangePassword = true
@@ -619,6 +620,7 @@ struct SimplexLockView: View {
authenticate(reason: NSLocalizedString("Enable SimpleX Lock", comment: "authentication reason")) { laResult in
switch laResult {
case .success:
m.contentViewAccessAuthenticated = true
prefPerformLA = true
laAlert = .laTurnedOnAlert
case .failed:
@@ -21,7 +21,7 @@ struct ScanProtocolServer: View {
.font(.largeTitle)
.bold()
.padding(.vertical)
CodeScannerView(codeTypes: [.qr], completion: processQRCode)
CodeScannerView(codeTypes: [.qr], scanMode: .oncePerCode, completion: processQRCode)
.aspectRatio(1, contentMode: .fit)
.cornerRadius(12)
.padding(.top)
@@ -56,7 +56,7 @@ let DEFAULT_SHOW_UNREAD_AND_FAVORITES = "showUnreadAndFavorites"
let DEFAULT_DEVICE_NAME_FOR_REMOTE_ACCESS = "deviceNameForRemoteAccess"
let DEFAULT_CONFIRM_REMOTE_SESSIONS = "confirmRemoteSessions"
let DEFAULT_CONNECT_REMOTE_VIA_MULTICAST = "connectRemoteViaMulticast"
let DEFAULT_OFFER_REMOTE_MULTICAST = "offerRemoteMulticast"
let DEFAULT_CONNECT_REMOTE_VIA_MULTICAST_AUTO = "connectRemoteViaMulticastAuto"
let appDefaults: [String: Any] = [
DEFAULT_SHOW_LA_NOTICE: false,
@@ -91,8 +91,8 @@ let appDefaults: [String: Any] = [
DEFAULT_CUSTOM_DISAPPEARING_MESSAGE_TIME: 300,
DEFAULT_SHOW_UNREAD_AND_FAVORITES: false,
DEFAULT_CONFIRM_REMOTE_SESSIONS: false,
DEFAULT_CONNECT_REMOTE_VIA_MULTICAST: false,
DEFAULT_OFFER_REMOTE_MULTICAST: true
DEFAULT_CONNECT_REMOTE_VIA_MULTICAST: true,
DEFAULT_CONNECT_REMOTE_VIA_MULTICAST_AUTO: true,
]
enum SimpleXLinkMode: String, Identifiable {
@@ -190,7 +190,8 @@ struct UserAddressView: View {
@ViewBuilder private func existingAddressView(_ userAddress: UserContactLink) -> some View {
Section {
MutableQRCode(uri: Binding.constant(simplexChatLink(userAddress.connReqContact)))
SimpleXLinkQRCode(uri: userAddress.connReqContact)
.id("simplex-contact-address-qrcode-\(userAddress.connReqContact)")
shareQRCodeButton(userAddress)
if MFMailComposeViewController.canSendMail() {
shareViaEmailButton(userAddress)
@@ -120,8 +120,10 @@ struct UserProfile: View {
}
}
.sheet(isPresented: $showImagePicker) {
LibraryImagePicker(image: $chosenImage) {
didSelectItem in showImagePicker = false
LibraryImagePicker(image: $chosenImage) { _ in
await MainActor.run {
showImagePicker = false
}
}
}
.onChange(of: chosenImage) { image in
@@ -386,6 +386,12 @@
- и още!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- optionally notify deleted contacts.&#10;- profile names with spaces.&#10;- and more!" xml:space="preserve">
<source>- optionally notify deleted contacts.
- profile names with spaces.
- and more!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- voice messages up to 5 minutes.&#10;- custom time to disappear.&#10;- editing history." xml:space="preserve">
<source>- voice messages up to 5 minutes.
- custom time to disappear.
@@ -880,6 +886,10 @@
<target>Лош хеш на съобщението</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better groups" xml:space="preserve">
<source>Better groups</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better messages" xml:space="preserve">
<source>Better messages</source>
<target>По-добри съобщения</target>
@@ -889,6 +899,10 @@
<source>Block</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block group members" xml:space="preserve">
<source>Block group members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block member" xml:space="preserve">
<source>Block member</source>
<note>No comment provided by engineer.</note>
@@ -1158,6 +1172,10 @@
<target>Свързване</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Connect automatically" xml:space="preserve">
<source>Connect automatically</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect incognito" xml:space="preserve">
<source>Connect incognito</source>
<target>Свързване инкогнито</target>
@@ -1329,6 +1347,10 @@ This is your own one-time link!</source>
<target>Създай SimpleX адрес</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create a group using a random profile." xml:space="preserve">
<source>Create a group using a random profile.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create an address to let people connect with you." xml:space="preserve">
<source>Create an address to let people connect with you.</source>
<target>Създайте адрес, за да позволите на хората да се свързват с вас.</target>
@@ -1844,8 +1866,8 @@ This cannot be undone!</source>
<target>Открийте и се присъединете към групи</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Discover on network" xml:space="preserve">
<source>Discover on network</source>
<trans-unit id="Discover via local network" xml:space="preserve">
<source>Discover via local network</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do NOT use SimpleX for emergency calls." xml:space="preserve">
@@ -2382,6 +2404,10 @@ This cannot be undone!</source>
<target>Бързо и без чакане, докато подателят е онлайн!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Faster joining and more reliable messages." xml:space="preserve">
<source>Faster joining and more reliable messages.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Favorite" xml:space="preserve">
<source>Favorite</source>
<target>Любим</target>
@@ -2477,6 +2503,10 @@ This cannot be undone!</source>
<target>За конзолата</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Found desktop" xml:space="preserve">
<source>Found desktop</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="French interface" xml:space="preserve">
<source>French interface</source>
<target>Френски интерфейс</target>
@@ -2799,6 +2829,10 @@ This cannot be undone!</source>
<target>Инкогнито</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito groups" xml:space="preserve">
<source>Incognito groups</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito mode" xml:space="preserve">
<source>Incognito mode</source>
<target>Режим инкогнито</target>
@@ -3062,6 +3096,10 @@ This is your link for group %@!</source>
<target>Ограничения</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Link mobile and desktop apps! 🔗" xml:space="preserve">
<source>Link mobile and desktop apps! 🔗</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Linked desktop options" xml:space="preserve">
<source>Linked desktop options</source>
<note>No comment provided by engineer.</note>
@@ -3419,6 +3457,10 @@ This is your link for group %@!</source>
<target>Няма получени или изпратени файлове</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Not compatible!" xml:space="preserve">
<source>Not compatible!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Notifications" xml:space="preserve">
<source>Notifications</source>
<target>Известия</target>
@@ -4993,6 +5035,10 @@ It can happen because of some bug or when the connection is compromised.</source
<target>За да се свърже, вашият контакт може да сканира QR код или да използва линка в приложението.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To hide unwanted messages." xml:space="preserve">
<source>To hide unwanted messages.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To make a new connection" xml:space="preserve">
<source>To make a new connection</source>
<target>За да направите нова връзка</target>
@@ -5308,6 +5354,10 @@ To connect, please ask your contact to create another connection link and check
<target>Чрез браузър</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Via secure quantum resistant protocol." xml:space="preserve">
<source>Via secure quantum resistant protocol.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Video call" xml:space="preserve">
<source>Video call</source>
<target>Видео разговор</target>
@@ -5358,6 +5408,10 @@ To connect, please ask your contact to create another connection link and check
<target>Гласово съобщение…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for desktop..." xml:space="preserve">
<source>Waiting for desktop...</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for file" xml:space="preserve">
<source>Waiting for file</source>
<target>Изчаква се получаването на файла</target>
@@ -5888,6 +5942,10 @@ SimpleX сървърите не могат да видят вашия профи
<target>аудио разговор (не е e2e криптиран)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="author" xml:space="preserve">
<source>author</source>
<note>member role</note>
</trans-unit>
<trans-unit id="bad message ID" xml:space="preserve">
<source>bad message ID</source>
<target>лошо ID на съобщението</target>
@@ -386,6 +386,12 @@
- a více!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- optionally notify deleted contacts.&#10;- profile names with spaces.&#10;- and more!" xml:space="preserve">
<source>- optionally notify deleted contacts.
- profile names with spaces.
- and more!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- voice messages up to 5 minutes.&#10;- custom time to disappear.&#10;- editing history." xml:space="preserve">
<source>- voice messages up to 5 minutes.
- custom time to disappear.
@@ -880,6 +886,10 @@
<target>Špatný hash zprávy</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better groups" xml:space="preserve">
<source>Better groups</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better messages" xml:space="preserve">
<source>Better messages</source>
<target>Lepší zprávy</target>
@@ -889,6 +899,10 @@
<source>Block</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block group members" xml:space="preserve">
<source>Block group members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block member" xml:space="preserve">
<source>Block member</source>
<note>No comment provided by engineer.</note>
@@ -1158,6 +1172,10 @@
<target>Připojit</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Connect automatically" xml:space="preserve">
<source>Connect automatically</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect incognito" xml:space="preserve">
<source>Connect incognito</source>
<target>Spojit se inkognito</target>
@@ -1329,6 +1347,10 @@ This is your own one-time link!</source>
<target>Vytvořit SimpleX adresu</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create a group using a random profile." xml:space="preserve">
<source>Create a group using a random profile.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create an address to let people connect with you." xml:space="preserve">
<source>Create an address to let people connect with you.</source>
<target>Vytvořit adresu, aby se s vámi lidé mohli spojit.</target>
@@ -1844,8 +1866,8 @@ This cannot be undone!</source>
<target>Objevte a připojte skupiny</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Discover on network" xml:space="preserve">
<source>Discover on network</source>
<trans-unit id="Discover via local network" xml:space="preserve">
<source>Discover via local network</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do NOT use SimpleX for emergency calls." xml:space="preserve">
@@ -2382,6 +2404,10 @@ This cannot be undone!</source>
<target>Rychle a bez čekání, než bude odesílatel online!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Faster joining and more reliable messages." xml:space="preserve">
<source>Faster joining and more reliable messages.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Favorite" xml:space="preserve">
<source>Favorite</source>
<target>Oblíbené</target>
@@ -2477,6 +2503,10 @@ This cannot be undone!</source>
<target>Pro konzoli</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Found desktop" xml:space="preserve">
<source>Found desktop</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="French interface" xml:space="preserve">
<source>French interface</source>
<target>Francouzské rozhraní</target>
@@ -2799,6 +2829,10 @@ This cannot be undone!</source>
<target>Inkognito</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito groups" xml:space="preserve">
<source>Incognito groups</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito mode" xml:space="preserve">
<source>Incognito mode</source>
<target>Režim inkognito</target>
@@ -3062,6 +3096,10 @@ This is your link for group %@!</source>
<target>Omezení</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Link mobile and desktop apps! 🔗" xml:space="preserve">
<source>Link mobile and desktop apps! 🔗</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Linked desktop options" xml:space="preserve">
<source>Linked desktop options</source>
<note>No comment provided by engineer.</note>
@@ -3419,6 +3457,10 @@ This is your link for group %@!</source>
<target>Žádné přijaté ani odeslané soubory</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Not compatible!" xml:space="preserve">
<source>Not compatible!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Notifications" xml:space="preserve">
<source>Notifications</source>
<target>Oznámení</target>
@@ -4993,6 +5035,10 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován
<target>Pro připojení může váš kontakt naskenovat QR kód, nebo použít odkaz v aplikaci.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To hide unwanted messages." xml:space="preserve">
<source>To hide unwanted messages.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To make a new connection" xml:space="preserve">
<source>To make a new connection</source>
<target>Vytvoření nového připojení</target>
@@ -5308,6 +5354,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu
<target>Prostřednictvím prohlížeče</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Via secure quantum resistant protocol." xml:space="preserve">
<source>Via secure quantum resistant protocol.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Video call" xml:space="preserve">
<source>Video call</source>
<target>Videohovor</target>
@@ -5358,6 +5408,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu
<target>Hlasová zpráva…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for desktop..." xml:space="preserve">
<source>Waiting for desktop...</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for file" xml:space="preserve">
<source>Waiting for file</source>
<target>Čekání na soubor</target>
@@ -5888,6 +5942,10 @@ Servery SimpleX nevidí váš profil.</target>
<target>zvukový hovor (nešifrovaný e2e)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="author" xml:space="preserve">
<source>author</source>
<note>member role</note>
</trans-unit>
<trans-unit id="bad message ID" xml:space="preserve">
<source>bad message ID</source>
<target>špatné ID zprávy</target>
@@ -299,10 +299,12 @@
</trans-unit>
<trans-unit id="(new)" xml:space="preserve">
<source>(new)</source>
<target>(Neu)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="(this device v%@)" xml:space="preserve">
<source>(this device v%@)</source>
<target>(Dieses Gerät hat v%@)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id=")" xml:space="preserve">
@@ -393,6 +395,15 @@
- und mehr!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- optionally notify deleted contacts.&#10;- profile names with spaces.&#10;- and more!" xml:space="preserve">
<source>- optionally notify deleted contacts.
- profile names with spaces.
- and more!</source>
<target>- Optionale Benachrichtigung von gelöschten Kontakten.
- Profilnamen mit Leerzeichen.
- Und mehr!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- voice messages up to 5 minutes.&#10;- custom time to disappear.&#10;- editing history." xml:space="preserve">
<source>- voice messages up to 5 minutes.
- custom time to disappear.
@@ -879,6 +890,7 @@
</trans-unit>
<trans-unit id="Bad desktop address" xml:space="preserve">
<source>Bad desktop address</source>
<target>Falsche Desktop-Adresse</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Bad message ID" xml:space="preserve">
@@ -891,6 +903,11 @@
<target>Ungültiger Nachrichten-Hash</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better groups" xml:space="preserve">
<source>Better groups</source>
<target>Bessere Gruppen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better messages" xml:space="preserve">
<source>Better messages</source>
<target>Verbesserungen bei Nachrichten</target>
@@ -901,6 +918,11 @@
<target>Blockieren</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block group members" xml:space="preserve">
<source>Block group members</source>
<target>Gruppenmitglieder blockieren</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block member" xml:space="preserve">
<source>Block member</source>
<target>Mitglied blockieren</target>
@@ -1172,6 +1194,11 @@
<target>Verbinden</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Connect automatically" xml:space="preserve">
<source>Connect automatically</source>
<target>Automatisch verbinden</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect incognito" xml:space="preserve">
<source>Connect incognito</source>
<target>Inkognito verbinden</target>
@@ -1179,6 +1206,7 @@
</trans-unit>
<trans-unit id="Connect to desktop" xml:space="preserve">
<source>Connect to desktop</source>
<target>Mit dem Desktop verbinden</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect to yourself?" xml:space="preserve">
@@ -1227,10 +1255,12 @@ Das ist Ihr eigener Einmal-Link!</target>
</trans-unit>
<trans-unit id="Connected desktop" xml:space="preserve">
<source>Connected desktop</source>
<target>Verbundener Desktop</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connected to desktop" xml:space="preserve">
<source>Connected to desktop</source>
<target>Mit dem Desktop verbunden</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connecting server…" xml:space="preserve">
@@ -1245,6 +1275,7 @@ Das ist Ihr eigener Einmal-Link!</target>
</trans-unit>
<trans-unit id="Connecting to desktop" xml:space="preserve">
<source>Connecting to desktop</source>
<target>Mit dem Desktop verbinden</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connection" xml:space="preserve">
@@ -1269,6 +1300,7 @@ Das ist Ihr eigener Einmal-Link!</target>
</trans-unit>
<trans-unit id="Connection terminated" xml:space="preserve">
<source>Connection terminated</source>
<target>Verbindung beendet</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connection timeout" xml:space="preserve">
@@ -1351,6 +1383,11 @@ Das ist Ihr eigener Einmal-Link!</target>
<target>SimpleX-Adresse erstellen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create a group using a random profile." xml:space="preserve">
<source>Create a group using a random profile.</source>
<target>Erstellen Sie eine Gruppe mit einem zufälligen Profil.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create an address to let people connect with you." xml:space="preserve">
<source>Create an address to let people connect with you.</source>
<target>Erstellen Sie eine Adresse, damit sich Personen mit Ihnen verbinden können.</target>
@@ -1763,14 +1800,17 @@ Das kann nicht rückgängig gemacht werden!</target>
</trans-unit>
<trans-unit id="Desktop address" xml:space="preserve">
<source>Desktop address</source>
<target>Desktop-Adresse</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Desktop app version %@ is not compatible with this app." xml:space="preserve">
<source>Desktop app version %@ is not compatible with this app.</source>
<target>Desktop App-Version %@ ist mit dieser App nicht kompatibel.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Desktop devices" xml:space="preserve">
<source>Desktop devices</source>
<target>Desktop-Geräte</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Develop" xml:space="preserve">
@@ -1865,6 +1905,7 @@ Das kann nicht rückgängig gemacht werden!</target>
</trans-unit>
<trans-unit id="Disconnect desktop?" xml:space="preserve">
<source>Disconnect desktop?</source>
<target>Desktop-Verbindung trennen?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Discover and join groups" xml:space="preserve">
@@ -1872,8 +1913,9 @@ Das kann nicht rückgängig gemacht werden!</target>
<target>Gruppen entdecken und ihnen beitreten</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Discover on network" xml:space="preserve">
<source>Discover on network</source>
<trans-unit id="Discover via local network" xml:space="preserve">
<source>Discover via local network</source>
<target>Lokales Netzwerk durchsuchen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do NOT use SimpleX for emergency calls." xml:space="preserve">
@@ -2048,10 +2090,12 @@ Das kann nicht rückgängig gemacht werden!</target>
</trans-unit>
<trans-unit id="Encryption re-negotiation error" xml:space="preserve">
<source>Encryption re-negotiation error</source>
<target>Fehler bei der Neuverhandlung der Verschlüsselung</target>
<note>message decrypt error item</note>
</trans-unit>
<trans-unit id="Encryption re-negotiation failed." xml:space="preserve">
<source>Encryption re-negotiation failed.</source>
<target>Neuverhandlung der Verschlüsselung fehlgeschlagen.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enter Passcode" xml:space="preserve">
@@ -2086,6 +2130,7 @@ Das kann nicht rückgängig gemacht werden!</target>
</trans-unit>
<trans-unit id="Enter this device name…" xml:space="preserve">
<source>Enter this device name…</source>
<target>Geben Sie diesen Gerätenamen ein…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enter welcome message…" xml:space="preserve">
@@ -2413,6 +2458,11 @@ Das kann nicht rückgängig gemacht werden!</target>
<target>Schnell und ohne warten auf den Absender, bis er online ist!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Faster joining and more reliable messages." xml:space="preserve">
<source>Faster joining and more reliable messages.</source>
<target>Schnellerer Gruppenbeitritt und zuverlässigere Nachrichtenzustellung.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Favorite" xml:space="preserve">
<source>Favorite</source>
<target>Favorit</target>
@@ -2508,6 +2558,11 @@ Das kann nicht rückgängig gemacht werden!</target>
<target>Für Konsole</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Found desktop" xml:space="preserve">
<source>Found desktop</source>
<target>Gefundener Desktop</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="French interface" xml:space="preserve">
<source>French interface</source>
<target>Französische Bedienoberfläche</target>
@@ -2833,6 +2888,11 @@ Das kann nicht rückgängig gemacht werden!</target>
<target>Inkognito</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito groups" xml:space="preserve">
<source>Incognito groups</source>
<target>Inkognito-Gruppen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito mode" xml:space="preserve">
<source>Incognito mode</source>
<target>Inkognito-Modus</target>
@@ -2865,6 +2925,7 @@ Das kann nicht rückgängig gemacht werden!</target>
</trans-unit>
<trans-unit id="Incompatible version" xml:space="preserve">
<source>Incompatible version</source>
<target>Inkompatible Version</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incorrect passcode" xml:space="preserve">
@@ -3039,6 +3100,7 @@ Das ist Ihr Link für die Gruppe %@!</target>
</trans-unit>
<trans-unit id="Keep the app open to use it from desktop" xml:space="preserve">
<source>Keep the app open to use it from desktop</source>
<target>Die App muss geöffnet bleiben, um sie vom Desktop aus nutzen zu können</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Keep your connections" xml:space="preserve">
@@ -3101,12 +3163,19 @@ Das ist Ihr Link für die Gruppe %@!</target>
<target>Einschränkungen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Link mobile and desktop apps! 🔗" xml:space="preserve">
<source>Link mobile and desktop apps! 🔗</source>
<target>Verknüpfe Mobiltelefon- und Desktop-Apps! 🔗</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Linked desktop options" xml:space="preserve">
<source>Linked desktop options</source>
<target>Verknüpfte Desktop-Optionen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Linked desktops" xml:space="preserve">
<source>Linked desktops</source>
<target>Verknüpfte Desktops</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Live message!" xml:space="preserve">
@@ -3459,6 +3528,11 @@ Das ist Ihr Link für die Gruppe %@!</target>
<target>Keine empfangenen oder gesendeten Dateien</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Not compatible!" xml:space="preserve">
<source>Not compatible!</source>
<target>Nicht kompatibel!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Notifications" xml:space="preserve">
<source>Notifications</source>
<target>Benachrichtigungen</target>
@@ -3680,6 +3754,7 @@ Das ist Ihr Link für die Gruppe %@!</target>
</trans-unit>
<trans-unit id="Paste desktop address" xml:space="preserve">
<source>Paste desktop address</source>
<target>Desktop-Adresse einfügen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Paste image" xml:space="preserve">
@@ -4279,6 +4354,7 @@ Das ist Ihr Link für die Gruppe %@!</target>
</trans-unit>
<trans-unit id="Scan QR code from desktop" xml:space="preserve">
<source>Scan QR code from desktop</source>
<target>Den QR-Code vom Desktop scannen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Scan code" xml:space="preserve">
@@ -4503,6 +4579,7 @@ Das ist Ihr Link für die Gruppe %@!</target>
</trans-unit>
<trans-unit id="Session code" xml:space="preserve">
<source>Session code</source>
<target>Sitzungscode</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set 1 day" xml:space="preserve">
@@ -5004,6 +5081,7 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro
</trans-unit>
<trans-unit id="This device name" xml:space="preserve">
<source>This device name</source>
<target>Dieser Gerätename</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="This group has over %lld members, delivery receipts are not sent." xml:space="preserve">
@@ -5041,6 +5119,11 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro
<target>Um eine Verbindung herzustellen, kann Ihr Kontakt den QR-Code scannen oder den Link in der App verwenden.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To hide unwanted messages." xml:space="preserve">
<source>To hide unwanted messages.</source>
<target>Um unerwünschte Nachrichten zu verbergen.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To make a new connection" xml:space="preserve">
<source>To make a new connection</source>
<target>Um eine Verbindung mit einem neuen Kontakt zu erstellen</target>
@@ -5202,10 +5285,12 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
</trans-unit>
<trans-unit id="Unlink" xml:space="preserve">
<source>Unlink</source>
<target>Entkoppeln</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unlink desktop?" xml:space="preserve">
<source>Unlink desktop?</source>
<target>Desktop entkoppeln?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unlock" xml:space="preserve">
@@ -5300,6 +5385,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
</trans-unit>
<trans-unit id="Use from desktop" xml:space="preserve">
<source>Use from desktop</source>
<target>Vom Desktop aus nutzen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Use iOS call interface" xml:space="preserve">
@@ -5334,10 +5420,12 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
</trans-unit>
<trans-unit id="Verify code with desktop" xml:space="preserve">
<source>Verify code with desktop</source>
<target>Code mit dem Desktop überprüfen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Verify connection" xml:space="preserve">
<source>Verify connection</source>
<target>Verbindung überprüfen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Verify connection security" xml:space="preserve">
@@ -5347,6 +5435,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
</trans-unit>
<trans-unit id="Verify connections" xml:space="preserve">
<source>Verify connections</source>
<target>Verbindungen überprüfen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Verify security code" xml:space="preserve">
@@ -5359,6 +5448,11 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
<target>Über den Browser</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Via secure quantum resistant protocol." xml:space="preserve">
<source>Via secure quantum resistant protocol.</source>
<target>Über ein sicheres quantenbeständiges Protokoll.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Video call" xml:space="preserve">
<source>Video call</source>
<target>Videoanruf</target>
@@ -5409,6 +5503,11 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
<target>Sprachnachrichten…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for desktop..." xml:space="preserve">
<source>Waiting for desktop...</source>
<target>Es wird auf den Desktop gewartet...</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for file" xml:space="preserve">
<source>Waiting for file</source>
<target>Warte auf Datei</target>
@@ -5954,6 +6053,11 @@ SimpleX-Server können Ihr Profil nicht einsehen.</target>
<target>Audioanruf (nicht E2E verschlüsselt)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="author" xml:space="preserve">
<source>author</source>
<target>Autor</target>
<note>member role</note>
</trans-unit>
<trans-unit id="bad message ID" xml:space="preserve">
<source>bad message ID</source>
<target>Ungültige Nachrichten-ID</target>
@@ -6538,6 +6642,7 @@ SimpleX-Server können Ihr Profil nicht einsehen.</target>
</trans-unit>
<trans-unit id="v%@" xml:space="preserve">
<source>v%@</source>
<target>v%@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="v%@ (%@)" xml:space="preserve">
@@ -6679,6 +6784,7 @@ SimpleX-Server können Ihr Profil nicht einsehen.</target>
</trans-unit>
<trans-unit id="NSLocalNetworkUsageDescription" xml:space="preserve">
<source>SimpleX uses local network access to allow using user chat profile via desktop app on the same network.</source>
<target>SimpleX nutzt den lokalen Netzwerkzugriff, um die Nutzung von Benutzer-Chatprofilen über eine Desktop-App im gleichen Netzwerk zu erlauben.</target>
<note>Privacy - Local Network Usage Description</note>
</trans-unit>
<trans-unit id="NSMicrophoneUsageDescription" xml:space="preserve">
@@ -392,6 +392,15 @@
- and more!</source>
<target>- more stable message delivery.
- a bit better groups.
- and more!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- optionally notify deleted contacts.&#10;- profile names with spaces.&#10;- and more!" xml:space="preserve">
<source>- optionally notify deleted contacts.
- profile names with spaces.
- and more!</source>
<target>- optionally notify deleted contacts.
- profile names with spaces.
- and more!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -894,6 +903,11 @@
<target>Bad message hash</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better groups" xml:space="preserve">
<source>Better groups</source>
<target>Better groups</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better messages" xml:space="preserve">
<source>Better messages</source>
<target>Better messages</target>
@@ -904,6 +918,11 @@
<target>Block</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block group members" xml:space="preserve">
<source>Block group members</source>
<target>Block group members</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block member" xml:space="preserve">
<source>Block member</source>
<target>Block member</target>
@@ -1175,6 +1194,11 @@
<target>Connect</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Connect automatically" xml:space="preserve">
<source>Connect automatically</source>
<target>Connect automatically</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect incognito" xml:space="preserve">
<source>Connect incognito</source>
<target>Connect incognito</target>
@@ -1359,6 +1383,11 @@ This is your own one-time link!</target>
<target>Create SimpleX address</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create a group using a random profile." xml:space="preserve">
<source>Create a group using a random profile.</source>
<target>Create a group using a random profile.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create an address to let people connect with you." xml:space="preserve">
<source>Create an address to let people connect with you.</source>
<target>Create an address to let people connect with you.</target>
@@ -1884,9 +1913,9 @@ This cannot be undone!</target>
<target>Discover and join groups</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Discover on network" xml:space="preserve">
<source>Discover on network</source>
<target>Discover on network</target>
<trans-unit id="Discover via local network" xml:space="preserve">
<source>Discover via local network</source>
<target>Discover via local network</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do NOT use SimpleX for emergency calls." xml:space="preserve">
@@ -2429,6 +2458,11 @@ This cannot be undone!</target>
<target>Fast and no wait until the sender is online!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Faster joining and more reliable messages." xml:space="preserve">
<source>Faster joining and more reliable messages.</source>
<target>Faster joining and more reliable messages.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Favorite" xml:space="preserve">
<source>Favorite</source>
<target>Favorite</target>
@@ -2524,6 +2558,11 @@ This cannot be undone!</target>
<target>For console</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Found desktop" xml:space="preserve">
<source>Found desktop</source>
<target>Found desktop</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="French interface" xml:space="preserve">
<source>French interface</source>
<target>French interface</target>
@@ -2849,6 +2888,11 @@ This cannot be undone!</target>
<target>Incognito</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito groups" xml:space="preserve">
<source>Incognito groups</source>
<target>Incognito groups</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito mode" xml:space="preserve">
<source>Incognito mode</source>
<target>Incognito mode</target>
@@ -3119,6 +3163,11 @@ This is your link for group %@!</target>
<target>Limitations</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Link mobile and desktop apps! 🔗" xml:space="preserve">
<source>Link mobile and desktop apps! 🔗</source>
<target>Link mobile and desktop apps! 🔗</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Linked desktop options" xml:space="preserve">
<source>Linked desktop options</source>
<target>Linked desktop options</target>
@@ -3479,6 +3528,11 @@ This is your link for group %@!</target>
<target>No received or sent files</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Not compatible!" xml:space="preserve">
<source>Not compatible!</source>
<target>Not compatible!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Notifications" xml:space="preserve">
<source>Notifications</source>
<target>Notifications</target>
@@ -5065,6 +5119,11 @@ It can happen because of some bug or when the connection is compromised.</target
<target>To connect, your contact can scan QR code or use the link in the app.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To hide unwanted messages." xml:space="preserve">
<source>To hide unwanted messages.</source>
<target>To hide unwanted messages.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To make a new connection" xml:space="preserve">
<source>To make a new connection</source>
<target>To make a new connection</target>
@@ -5389,6 +5448,11 @@ To connect, please ask your contact to create another connection link and check
<target>Via browser</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Via secure quantum resistant protocol." xml:space="preserve">
<source>Via secure quantum resistant protocol.</source>
<target>Via secure quantum resistant protocol.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Video call" xml:space="preserve">
<source>Video call</source>
<target>Video call</target>
@@ -5439,6 +5503,11 @@ To connect, please ask your contact to create another connection link and check
<target>Voice message…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for desktop..." xml:space="preserve">
<source>Waiting for desktop...</source>
<target>Waiting for desktop...</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for file" xml:space="preserve">
<source>Waiting for file</source>
<target>Waiting for file</target>
@@ -5984,6 +6053,11 @@ SimpleX servers cannot see your profile.</target>
<target>audio call (not e2e encrypted)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="author" xml:space="preserve">
<source>author</source>
<target>author</target>
<note>member role</note>
</trans-unit>
<trans-unit id="bad message ID" xml:space="preserve">
<source>bad message ID</source>
<target>bad message ID</target>
@@ -386,6 +386,12 @@
- ¡y más!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- optionally notify deleted contacts.&#10;- profile names with spaces.&#10;- and more!" xml:space="preserve">
<source>- optionally notify deleted contacts.
- profile names with spaces.
- and more!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- voice messages up to 5 minutes.&#10;- custom time to disappear.&#10;- editing history." xml:space="preserve">
<source>- voice messages up to 5 minutes.
- custom time to disappear.
@@ -880,6 +886,10 @@
<target>Hash de mensaje incorrecto</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better groups" xml:space="preserve">
<source>Better groups</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better messages" xml:space="preserve">
<source>Better messages</source>
<target>Mensajes mejorados</target>
@@ -889,6 +899,10 @@
<source>Block</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block group members" xml:space="preserve">
<source>Block group members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block member" xml:space="preserve">
<source>Block member</source>
<note>No comment provided by engineer.</note>
@@ -1158,6 +1172,10 @@
<target>Conectar</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Connect automatically" xml:space="preserve">
<source>Connect automatically</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect incognito" xml:space="preserve">
<source>Connect incognito</source>
<target>Conectar incognito</target>
@@ -1329,6 +1347,10 @@ This is your own one-time link!</source>
<target>Crear tu dirección SimpleX</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create a group using a random profile." xml:space="preserve">
<source>Create a group using a random profile.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create an address to let people connect with you." xml:space="preserve">
<source>Create an address to let people connect with you.</source>
<target>Crea una dirección para que otras personas puedan conectar contigo.</target>
@@ -1844,8 +1866,8 @@ This cannot be undone!</source>
<target>Descubre y únete a grupos</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Discover on network" xml:space="preserve">
<source>Discover on network</source>
<trans-unit id="Discover via local network" xml:space="preserve">
<source>Discover via local network</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do NOT use SimpleX for emergency calls." xml:space="preserve">
@@ -2382,6 +2404,10 @@ This cannot be undone!</source>
<target>¡Rápido y sin necesidad de esperar a que el remitente esté en línea!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Faster joining and more reliable messages." xml:space="preserve">
<source>Faster joining and more reliable messages.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Favorite" xml:space="preserve">
<source>Favorite</source>
<target>Favoritos</target>
@@ -2477,6 +2503,10 @@ This cannot be undone!</source>
<target>Para consola</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Found desktop" xml:space="preserve">
<source>Found desktop</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="French interface" xml:space="preserve">
<source>French interface</source>
<target>Interfaz en francés</target>
@@ -2799,6 +2829,10 @@ This cannot be undone!</source>
<target>Incógnito</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito groups" xml:space="preserve">
<source>Incognito groups</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito mode" xml:space="preserve">
<source>Incognito mode</source>
<target>Modo incógnito</target>
@@ -3062,6 +3096,10 @@ This is your link for group %@!</source>
<target>Limitaciones</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Link mobile and desktop apps! 🔗" xml:space="preserve">
<source>Link mobile and desktop apps! 🔗</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Linked desktop options" xml:space="preserve">
<source>Linked desktop options</source>
<note>No comment provided by engineer.</note>
@@ -3419,6 +3457,10 @@ This is your link for group %@!</source>
<target>Sin archivos recibidos o enviados</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Not compatible!" xml:space="preserve">
<source>Not compatible!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Notifications" xml:space="preserve">
<source>Notifications</source>
<target>Notificaciones</target>
@@ -4993,6 +5035,10 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida.</target>
<target>Para conectarse, tu contacto puede escanear el código QR o usar el enlace en la aplicación.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To hide unwanted messages." xml:space="preserve">
<source>To hide unwanted messages.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To make a new connection" xml:space="preserve">
<source>To make a new connection</source>
<target>Para hacer una conexión nueva</target>
@@ -5309,6 +5355,10 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb
<target>Mediante navegador</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Via secure quantum resistant protocol." xml:space="preserve">
<source>Via secure quantum resistant protocol.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Video call" xml:space="preserve">
<source>Video call</source>
<target>Videollamada</target>
@@ -5359,6 +5409,10 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb
<target>Mensaje de voz…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for desktop..." xml:space="preserve">
<source>Waiting for desktop...</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for file" xml:space="preserve">
<source>Waiting for file</source>
<target>Esperando archivo</target>
@@ -5889,6 +5943,10 @@ Los servidores de SimpleX no pueden ver tu perfil.</target>
<target>llamada (sin cifrar)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="author" xml:space="preserve">
<source>author</source>
<note>member role</note>
</trans-unit>
<trans-unit id="bad message ID" xml:space="preserve">
<source>bad message ID</source>
<target>ID de mensaje erróneo</target>
@@ -383,6 +383,12 @@
- ja paljon muuta!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- optionally notify deleted contacts.&#10;- profile names with spaces.&#10;- and more!" xml:space="preserve">
<source>- optionally notify deleted contacts.
- profile names with spaces.
- and more!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- voice messages up to 5 minutes.&#10;- custom time to disappear.&#10;- editing history." xml:space="preserve">
<source>- voice messages up to 5 minutes.
- custom time to disappear.
@@ -876,6 +882,10 @@
<target>Virheellinen viestin tarkiste</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better groups" xml:space="preserve">
<source>Better groups</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better messages" xml:space="preserve">
<source>Better messages</source>
<target>Parempia viestejä</target>
@@ -885,6 +895,10 @@
<source>Block</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block group members" xml:space="preserve">
<source>Block group members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block member" xml:space="preserve">
<source>Block member</source>
<note>No comment provided by engineer.</note>
@@ -1153,6 +1167,10 @@
<target>Yhdistä</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Connect automatically" xml:space="preserve">
<source>Connect automatically</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect incognito" xml:space="preserve">
<source>Connect incognito</source>
<target>Yhdistä Incognito</target>
@@ -1324,6 +1342,10 @@ This is your own one-time link!</source>
<target>Luo SimpleX-osoite</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create a group using a random profile." xml:space="preserve">
<source>Create a group using a random profile.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create an address to let people connect with you." xml:space="preserve">
<source>Create an address to let people connect with you.</source>
<target>Luo osoite, jolla ihmiset voivat ottaa sinuun yhteyttä.</target>
@@ -1839,8 +1861,8 @@ This cannot be undone!</source>
<target>Löydä ryhmiä ja liity niihin</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Discover on network" xml:space="preserve">
<source>Discover on network</source>
<trans-unit id="Discover via local network" xml:space="preserve">
<source>Discover via local network</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do NOT use SimpleX for emergency calls." xml:space="preserve">
@@ -2374,6 +2396,10 @@ This cannot be undone!</source>
<target>Nopea ja ei odotusta, kunnes lähettäjä on online-tilassa!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Faster joining and more reliable messages." xml:space="preserve">
<source>Faster joining and more reliable messages.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Favorite" xml:space="preserve">
<source>Favorite</source>
<target>Suosikki</target>
@@ -2469,6 +2495,10 @@ This cannot be undone!</source>
<target>Konsoliin</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Found desktop" xml:space="preserve">
<source>Found desktop</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="French interface" xml:space="preserve">
<source>French interface</source>
<target>Ranskalainen käyttöliittymä</target>
@@ -2791,6 +2821,10 @@ This cannot be undone!</source>
<target>Incognito</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito groups" xml:space="preserve">
<source>Incognito groups</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito mode" xml:space="preserve">
<source>Incognito mode</source>
<target>Incognito-tila</target>
@@ -3054,6 +3088,10 @@ This is your link for group %@!</source>
<target>Rajoitukset</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Link mobile and desktop apps! 🔗" xml:space="preserve">
<source>Link mobile and desktop apps! 🔗</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Linked desktop options" xml:space="preserve">
<source>Linked desktop options</source>
<note>No comment provided by engineer.</note>
@@ -3410,6 +3448,10 @@ This is your link for group %@!</source>
<target>Ei vastaanotettuja tai lähetettyjä tiedostoja</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Not compatible!" xml:space="preserve">
<source>Not compatible!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Notifications" xml:space="preserve">
<source>Notifications</source>
<target>Ilmoitukset</target>
@@ -4981,6 +5023,10 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.</t
<target>Kontaktisi voi muodostaa yhteyden skannaamalla QR-koodin tai käyttämällä sovelluksessa olevaa linkkiä.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To hide unwanted messages." xml:space="preserve">
<source>To hide unwanted messages.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To make a new connection" xml:space="preserve">
<source>To make a new connection</source>
<target>Uuden yhteyden luominen</target>
@@ -5295,6 +5341,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja
<target>Selaimella</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Via secure quantum resistant protocol." xml:space="preserve">
<source>Via secure quantum resistant protocol.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Video call" xml:space="preserve">
<source>Video call</source>
<target>Videopuhelu</target>
@@ -5345,6 +5395,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja
<target>Ääniviesti…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for desktop..." xml:space="preserve">
<source>Waiting for desktop...</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for file" xml:space="preserve">
<source>Waiting for file</source>
<target>Odottaa tiedostoa</target>
@@ -5875,6 +5929,10 @@ SimpleX-palvelimet eivät näe profiiliasi.</target>
<target>äänipuhelu (ei e2e-salattu)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="author" xml:space="preserve">
<source>author</source>
<note>member role</note>
</trans-unit>
<trans-unit id="bad message ID" xml:space="preserve">
<source>bad message ID</source>
<target>virheellinen viestin tunniste</target>
@@ -299,10 +299,12 @@
</trans-unit>
<trans-unit id="(new)" xml:space="preserve">
<source>(new)</source>
<target>(nouveau)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="(this device v%@)" xml:space="preserve">
<source>(this device v%@)</source>
<target>(cet appareil v%@)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id=")" xml:space="preserve">
@@ -393,6 +395,15 @@
- et bien d'autres choses encore !</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- optionally notify deleted contacts.&#10;- profile names with spaces.&#10;- and more!" xml:space="preserve">
<source>- optionally notify deleted contacts.
- profile names with spaces.
- and more!</source>
<target>- option pour notifier les contacts supprimés.
- noms de profil avec espaces.
- et plus encore !</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- voice messages up to 5 minutes.&#10;- custom time to disappear.&#10;- editing history." xml:space="preserve">
<source>- voice messages up to 5 minutes.
- custom time to disappear.
@@ -879,6 +890,7 @@
</trans-unit>
<trans-unit id="Bad desktop address" xml:space="preserve">
<source>Bad desktop address</source>
<target>Mauvaise adresse de bureau</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Bad message ID" xml:space="preserve">
@@ -891,6 +903,11 @@
<target>Mauvais hash de message</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better groups" xml:space="preserve">
<source>Better groups</source>
<target>Des groupes plus performants</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better messages" xml:space="preserve">
<source>Better messages</source>
<target>Meilleurs messages</target>
@@ -901,6 +918,11 @@
<target>Bloquer</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block group members" xml:space="preserve">
<source>Block group members</source>
<target>Bloquer des membres d'un groupe</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block member" xml:space="preserve">
<source>Block member</source>
<target>Bloquer ce membre</target>
@@ -1172,6 +1194,11 @@
<target>Se connecter</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Connect automatically" xml:space="preserve">
<source>Connect automatically</source>
<target>Connexion automatique</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect incognito" xml:space="preserve">
<source>Connect incognito</source>
<target>Se connecter incognito</target>
@@ -1179,6 +1206,7 @@
</trans-unit>
<trans-unit id="Connect to desktop" xml:space="preserve">
<source>Connect to desktop</source>
<target>Se connecter au bureau</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect to yourself?" xml:space="preserve">
@@ -1227,10 +1255,12 @@ Il s'agit de votre propre lien unique !</target>
</trans-unit>
<trans-unit id="Connected desktop" xml:space="preserve">
<source>Connected desktop</source>
<target>Bureau connecté</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connected to desktop" xml:space="preserve">
<source>Connected to desktop</source>
<target>Connecté au bureau</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connecting server…" xml:space="preserve">
@@ -1245,6 +1275,7 @@ Il s'agit de votre propre lien unique !</target>
</trans-unit>
<trans-unit id="Connecting to desktop" xml:space="preserve">
<source>Connecting to desktop</source>
<target>Connexion au bureau</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connection" xml:space="preserve">
@@ -1269,6 +1300,7 @@ Il s'agit de votre propre lien unique !</target>
</trans-unit>
<trans-unit id="Connection terminated" xml:space="preserve">
<source>Connection terminated</source>
<target>Connexion terminée</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connection timeout" xml:space="preserve">
@@ -1351,6 +1383,11 @@ Il s'agit de votre propre lien unique !</target>
<target>Créer une adresse SimpleX</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create a group using a random profile." xml:space="preserve">
<source>Create a group using a random profile.</source>
<target>Création de groupes via un profil aléatoire.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create an address to let people connect with you." xml:space="preserve">
<source>Create an address to let people connect with you.</source>
<target>Créez une adresse pour permettre aux gens de vous contacter.</target>
@@ -1763,14 +1800,17 @@ Cette opération ne peut être annulée !</target>
</trans-unit>
<trans-unit id="Desktop address" xml:space="preserve">
<source>Desktop address</source>
<target>Adresse de bureau</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Desktop app version %@ is not compatible with this app." xml:space="preserve">
<source>Desktop app version %@ is not compatible with this app.</source>
<target>La version de l'application de bureau %@ n'est pas compatible avec cette application.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Desktop devices" xml:space="preserve">
<source>Desktop devices</source>
<target>Appareils de bureau</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Develop" xml:space="preserve">
@@ -1865,6 +1905,7 @@ Cette opération ne peut être annulée !</target>
</trans-unit>
<trans-unit id="Disconnect desktop?" xml:space="preserve">
<source>Disconnect desktop?</source>
<target>Déconnecter le bureau ?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Discover and join groups" xml:space="preserve">
@@ -1872,8 +1913,9 @@ Cette opération ne peut être annulée !</target>
<target>Découvrir et rejoindre des groupes</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Discover on network" xml:space="preserve">
<source>Discover on network</source>
<trans-unit id="Discover via local network" xml:space="preserve">
<source>Discover via local network</source>
<target>Rechercher sur le réseau</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do NOT use SimpleX for emergency calls." xml:space="preserve">
@@ -2048,10 +2090,12 @@ Cette opération ne peut être annulée !</target>
</trans-unit>
<trans-unit id="Encryption re-negotiation error" xml:space="preserve">
<source>Encryption re-negotiation error</source>
<target>Erreur lors de la renégociation du chiffrement</target>
<note>message decrypt error item</note>
</trans-unit>
<trans-unit id="Encryption re-negotiation failed." xml:space="preserve">
<source>Encryption re-negotiation failed.</source>
<target>La renégociation du chiffrement a échoué.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enter Passcode" xml:space="preserve">
@@ -2086,6 +2130,7 @@ Cette opération ne peut être annulée !</target>
</trans-unit>
<trans-unit id="Enter this device name…" xml:space="preserve">
<source>Enter this device name…</source>
<target>Entrez le nom de l'appareil…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enter welcome message…" xml:space="preserve">
@@ -2413,6 +2458,11 @@ Cette opération ne peut être annulée !</target>
<target>Rapide et ne nécessitant pas d'attendre que l'expéditeur soit en ligne !</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Faster joining and more reliable messages." xml:space="preserve">
<source>Faster joining and more reliable messages.</source>
<target>Connexion plus rapide et messages plus fiables.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Favorite" xml:space="preserve">
<source>Favorite</source>
<target>Favoris</target>
@@ -2508,6 +2558,11 @@ Cette opération ne peut être annulée !</target>
<target>Pour la console</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Found desktop" xml:space="preserve">
<source>Found desktop</source>
<target>Bureau trouvé</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="French interface" xml:space="preserve">
<source>French interface</source>
<target>Interface en français</target>
@@ -2833,6 +2888,11 @@ Cette opération ne peut être annulée !</target>
<target>Incognito</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito groups" xml:space="preserve">
<source>Incognito groups</source>
<target>Groupes incognito</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito mode" xml:space="preserve">
<source>Incognito mode</source>
<target>Mode Incognito</target>
@@ -2865,6 +2925,7 @@ Cette opération ne peut être annulée !</target>
</trans-unit>
<trans-unit id="Incompatible version" xml:space="preserve">
<source>Incompatible version</source>
<target>Version incompatible</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incorrect passcode" xml:space="preserve">
@@ -3039,6 +3100,7 @@ Voici votre lien pour le groupe %@ !</target>
</trans-unit>
<trans-unit id="Keep the app open to use it from desktop" xml:space="preserve">
<source>Keep the app open to use it from desktop</source>
<target>Garder l'application ouverte pour l'utiliser depuis le bureau</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Keep your connections" xml:space="preserve">
@@ -3101,12 +3163,19 @@ Voici votre lien pour le groupe %@ !</target>
<target>Limitations</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Link mobile and desktop apps! 🔗" xml:space="preserve">
<source>Link mobile and desktop apps! 🔗</source>
<target>Liez vos applications mobiles et de bureau ! 🔗</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Linked desktop options" xml:space="preserve">
<source>Linked desktop options</source>
<target>Options de bureau lié</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Linked desktops" xml:space="preserve">
<source>Linked desktops</source>
<target>Bureaux liés</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Live message!" xml:space="preserve">
@@ -3459,6 +3528,11 @@ Voici votre lien pour le groupe %@ !</target>
<target>Aucun fichier reçu ou envoyé</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Not compatible!" xml:space="preserve">
<source>Not compatible!</source>
<target>Non compatible !</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Notifications" xml:space="preserve">
<source>Notifications</source>
<target>Notifications</target>
@@ -3680,6 +3754,7 @@ Voici votre lien pour le groupe %@ !</target>
</trans-unit>
<trans-unit id="Paste desktop address" xml:space="preserve">
<source>Paste desktop address</source>
<target>Coller l'adresse du bureau</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Paste image" xml:space="preserve">
@@ -4279,6 +4354,7 @@ Voici votre lien pour le groupe %@ !</target>
</trans-unit>
<trans-unit id="Scan QR code from desktop" xml:space="preserve">
<source>Scan QR code from desktop</source>
<target>Scanner le code QR du bureau</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Scan code" xml:space="preserve">
@@ -4503,6 +4579,7 @@ Voici votre lien pour le groupe %@ !</target>
</trans-unit>
<trans-unit id="Session code" xml:space="preserve">
<source>Session code</source>
<target>Code de session</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set 1 day" xml:space="preserve">
@@ -5004,6 +5081,7 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise.
</trans-unit>
<trans-unit id="This device name" xml:space="preserve">
<source>This device name</source>
<target>Ce nom d'appareil</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="This group has over %lld members, delivery receipts are not sent." xml:space="preserve">
@@ -5041,6 +5119,11 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise.
<target>Pour se connecter, votre contact peut scanner le code QR ou utiliser le lien dans l'application.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To hide unwanted messages." xml:space="preserve">
<source>To hide unwanted messages.</source>
<target>Pour cacher les messages indésirables.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To make a new connection" xml:space="preserve">
<source>To make a new connection</source>
<target>Pour établir une nouvelle connexion</target>
@@ -5202,10 +5285,12 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien
</trans-unit>
<trans-unit id="Unlink" xml:space="preserve">
<source>Unlink</source>
<target>Délier</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unlink desktop?" xml:space="preserve">
<source>Unlink desktop?</source>
<target>Délier le bureau ?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unlock" xml:space="preserve">
@@ -5300,6 +5385,7 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien
</trans-unit>
<trans-unit id="Use from desktop" xml:space="preserve">
<source>Use from desktop</source>
<target>Utilisation depuis le bureau</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Use iOS call interface" xml:space="preserve">
@@ -5334,10 +5420,12 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien
</trans-unit>
<trans-unit id="Verify code with desktop" xml:space="preserve">
<source>Verify code with desktop</source>
<target>Vérifier le code avec le bureau</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Verify connection" xml:space="preserve">
<source>Verify connection</source>
<target>Vérifier la connexion</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Verify connection security" xml:space="preserve">
@@ -5347,6 +5435,7 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien
</trans-unit>
<trans-unit id="Verify connections" xml:space="preserve">
<source>Verify connections</source>
<target>Vérifier les connexions</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Verify security code" xml:space="preserve">
@@ -5359,6 +5448,11 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien
<target>Via navigateur</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Via secure quantum resistant protocol." xml:space="preserve">
<source>Via secure quantum resistant protocol.</source>
<target>Via un protocole sécurisé de cryptographie post-quantique.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Video call" xml:space="preserve">
<source>Video call</source>
<target>Appel vidéo</target>
@@ -5409,6 +5503,11 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien
<target>Message vocal…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for desktop..." xml:space="preserve">
<source>Waiting for desktop...</source>
<target>En attente du bureau...</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for file" xml:space="preserve">
<source>Waiting for file</source>
<target>En attente du fichier</target>
@@ -5954,6 +6053,11 @@ Les serveurs SimpleX ne peuvent pas voir votre profil.</target>
<target>appel audio (sans chiffrement)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="author" xml:space="preserve">
<source>author</source>
<target>auteur</target>
<note>member role</note>
</trans-unit>
<trans-unit id="bad message ID" xml:space="preserve">
<source>bad message ID</source>
<target>ID de message incorrecte</target>
@@ -6538,6 +6642,7 @@ Les serveurs SimpleX ne peuvent pas voir votre profil.</target>
</trans-unit>
<trans-unit id="v%@" xml:space="preserve">
<source>v%@</source>
<target>v%@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="v%@ (%@)" xml:space="preserve">
@@ -6679,6 +6784,7 @@ Les serveurs SimpleX ne peuvent pas voir votre profil.</target>
</trans-unit>
<trans-unit id="NSLocalNetworkUsageDescription" xml:space="preserve">
<source>SimpleX uses local network access to allow using user chat profile via desktop app on the same network.</source>
<target>SimpleX utilise un accès au réseau local pour permettre l'utilisation du profil de chat de l'utilisateur via l'application de bureau au sein de ce même réseau.</target>
<note>Privacy - Local Network Usage Description</note>
</trans-unit>
<trans-unit id="NSMicrophoneUsageDescription" xml:space="preserve">
@@ -299,10 +299,12 @@
</trans-unit>
<trans-unit id="(new)" xml:space="preserve">
<source>(new)</source>
<target>(nuovo)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="(this device v%@)" xml:space="preserve">
<source>(this device v%@)</source>
<target>(questo dispositivo v%@)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id=")" xml:space="preserve">
@@ -393,6 +395,15 @@
- e altro ancora!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- optionally notify deleted contacts.&#10;- profile names with spaces.&#10;- and more!" xml:space="preserve">
<source>- optionally notify deleted contacts.
- profile names with spaces.
- and more!</source>
<target>- avvisa facoltativamente i contatti eliminati.
- nomi del profilo con spazi.
- e molto altro!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- voice messages up to 5 minutes.&#10;- custom time to disappear.&#10;- editing history." xml:space="preserve">
<source>- voice messages up to 5 minutes.
- custom time to disappear.
@@ -879,6 +890,7 @@
</trans-unit>
<trans-unit id="Bad desktop address" xml:space="preserve">
<source>Bad desktop address</source>
<target>Indirizzo desktop errato</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Bad message ID" xml:space="preserve">
@@ -891,6 +903,11 @@
<target>Hash del messaggio errato</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better groups" xml:space="preserve">
<source>Better groups</source>
<target>Gruppi migliorati</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better messages" xml:space="preserve">
<source>Better messages</source>
<target>Messaggi migliorati</target>
@@ -901,6 +918,11 @@
<target>Blocca</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block group members" xml:space="preserve">
<source>Block group members</source>
<target>Blocca i membri dei gruppi</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block member" xml:space="preserve">
<source>Block member</source>
<target>Blocca membro</target>
@@ -1172,6 +1194,10 @@
<target>Connetti</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Connect automatically" xml:space="preserve">
<source>Connect automatically</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect incognito" xml:space="preserve">
<source>Connect incognito</source>
<target>Connetti in incognito</target>
@@ -1179,6 +1205,7 @@
</trans-unit>
<trans-unit id="Connect to desktop" xml:space="preserve">
<source>Connect to desktop</source>
<target>Connetti al desktop</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect to yourself?" xml:space="preserve">
@@ -1227,10 +1254,12 @@ Questo è il tuo link una tantum!</target>
</trans-unit>
<trans-unit id="Connected desktop" xml:space="preserve">
<source>Connected desktop</source>
<target>Desktop connesso</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connected to desktop" xml:space="preserve">
<source>Connected to desktop</source>
<target>Connesso al desktop</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connecting server…" xml:space="preserve">
@@ -1245,6 +1274,7 @@ Questo è il tuo link una tantum!</target>
</trans-unit>
<trans-unit id="Connecting to desktop" xml:space="preserve">
<source>Connecting to desktop</source>
<target>Connessione al desktop</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connection" xml:space="preserve">
@@ -1269,6 +1299,7 @@ Questo è il tuo link una tantum!</target>
</trans-unit>
<trans-unit id="Connection terminated" xml:space="preserve">
<source>Connection terminated</source>
<target>Connessione terminata</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connection timeout" xml:space="preserve">
@@ -1351,6 +1382,11 @@ Questo è il tuo link una tantum!</target>
<target>Crea indirizzo SimpleX</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create a group using a random profile." xml:space="preserve">
<source>Create a group using a random profile.</source>
<target>Crea un gruppo usando un profilo casuale.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create an address to let people connect with you." xml:space="preserve">
<source>Create an address to let people connect with you.</source>
<target>Crea un indirizzo per consentire alle persone di connettersi con te.</target>
@@ -1763,14 +1799,17 @@ Non è reversibile!</target>
</trans-unit>
<trans-unit id="Desktop address" xml:space="preserve">
<source>Desktop address</source>
<target>Indirizzo desktop</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Desktop app version %@ is not compatible with this app." xml:space="preserve">
<source>Desktop app version %@ is not compatible with this app.</source>
<target>La versione dell'app desktop %@ non è compatibile con questa app.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Desktop devices" xml:space="preserve">
<source>Desktop devices</source>
<target>Dispositivi desktop</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Develop" xml:space="preserve">
@@ -1865,6 +1904,7 @@ Non è reversibile!</target>
</trans-unit>
<trans-unit id="Disconnect desktop?" xml:space="preserve">
<source>Disconnect desktop?</source>
<target>Disconnettere il desktop?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Discover and join groups" xml:space="preserve">
@@ -1872,8 +1912,8 @@ Non è reversibile!</target>
<target>Scopri ed unisciti ai gruppi</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Discover on network" xml:space="preserve">
<source>Discover on network</source>
<trans-unit id="Discover via local network" xml:space="preserve">
<source>Discover via local network</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do NOT use SimpleX for emergency calls." xml:space="preserve">
@@ -2048,10 +2088,12 @@ Non è reversibile!</target>
</trans-unit>
<trans-unit id="Encryption re-negotiation error" xml:space="preserve">
<source>Encryption re-negotiation error</source>
<target>Errore di rinegoziazione crittografia</target>
<note>message decrypt error item</note>
</trans-unit>
<trans-unit id="Encryption re-negotiation failed." xml:space="preserve">
<source>Encryption re-negotiation failed.</source>
<target>Rinegoziazione crittografia fallita.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enter Passcode" xml:space="preserve">
@@ -2086,6 +2128,7 @@ Non è reversibile!</target>
</trans-unit>
<trans-unit id="Enter this device name…" xml:space="preserve">
<source>Enter this device name…</source>
<target>Inserisci il nome di questo dispositivo…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enter welcome message…" xml:space="preserve">
@@ -2413,6 +2456,11 @@ Non è reversibile!</target>
<target>Veloce e senza aspettare che il mittente sia in linea!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Faster joining and more reliable messages." xml:space="preserve">
<source>Faster joining and more reliable messages.</source>
<target>Ingresso più veloce e messaggi più affidabili.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Favorite" xml:space="preserve">
<source>Favorite</source>
<target>Preferito</target>
@@ -2508,6 +2556,10 @@ Non è reversibile!</target>
<target>Per console</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Found desktop" xml:space="preserve">
<source>Found desktop</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="French interface" xml:space="preserve">
<source>French interface</source>
<target>Interfaccia francese</target>
@@ -2833,6 +2885,11 @@ Non è reversibile!</target>
<target>Incognito</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito groups" xml:space="preserve">
<source>Incognito groups</source>
<target>Gruppi in incognito</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito mode" xml:space="preserve">
<source>Incognito mode</source>
<target>Modalità incognito</target>
@@ -2865,6 +2922,7 @@ Non è reversibile!</target>
</trans-unit>
<trans-unit id="Incompatible version" xml:space="preserve">
<source>Incompatible version</source>
<target>Versione incompatibile</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incorrect passcode" xml:space="preserve">
@@ -3039,6 +3097,7 @@ Questo è il tuo link per il gruppo %@!</target>
</trans-unit>
<trans-unit id="Keep the app open to use it from desktop" xml:space="preserve">
<source>Keep the app open to use it from desktop</source>
<target>Tieni aperta l'app per usarla dal desktop</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Keep your connections" xml:space="preserve">
@@ -3101,12 +3160,19 @@ Questo è il tuo link per il gruppo %@!</target>
<target>Limitazioni</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Link mobile and desktop apps! 🔗" xml:space="preserve">
<source>Link mobile and desktop apps! 🔗</source>
<target>Collega le app mobile e desktop! 🔗</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Linked desktop options" xml:space="preserve">
<source>Linked desktop options</source>
<target>Opzioni del desktop collegato</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Linked desktops" xml:space="preserve">
<source>Linked desktops</source>
<target>Desktop collegati</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Live message!" xml:space="preserve">
@@ -3459,6 +3525,10 @@ Questo è il tuo link per il gruppo %@!</target>
<target>Nessun file ricevuto o inviato</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Not compatible!" xml:space="preserve">
<source>Not compatible!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Notifications" xml:space="preserve">
<source>Notifications</source>
<target>Notifiche</target>
@@ -3680,6 +3750,7 @@ Questo è il tuo link per il gruppo %@!</target>
</trans-unit>
<trans-unit id="Paste desktop address" xml:space="preserve">
<source>Paste desktop address</source>
<target>Incolla l'indirizzo desktop</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Paste image" xml:space="preserve">
@@ -4279,6 +4350,7 @@ Questo è il tuo link per il gruppo %@!</target>
</trans-unit>
<trans-unit id="Scan QR code from desktop" xml:space="preserve">
<source>Scan QR code from desktop</source>
<target>Scansiona codice QR dal desktop</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Scan code" xml:space="preserve">
@@ -4503,6 +4575,7 @@ Questo è il tuo link per il gruppo %@!</target>
</trans-unit>
<trans-unit id="Session code" xml:space="preserve">
<source>Session code</source>
<target>Codice di sessione</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set 1 day" xml:space="preserve">
@@ -5004,6 +5077,7 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.</ta
</trans-unit>
<trans-unit id="This device name" xml:space="preserve">
<source>This device name</source>
<target>Il nome di questo dispositivo</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="This group has over %lld members, delivery receipts are not sent." xml:space="preserve">
@@ -5041,6 +5115,11 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.</ta
<target>Per connettervi, il tuo contatto può scansionare il codice QR o usare il link nell'app.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To hide unwanted messages." xml:space="preserve">
<source>To hide unwanted messages.</source>
<target>Per nascondere messaggi indesiderati.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To make a new connection" xml:space="preserve">
<source>To make a new connection</source>
<target>Per creare una nuova connessione</target>
@@ -5202,10 +5281,12 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e
</trans-unit>
<trans-unit id="Unlink" xml:space="preserve">
<source>Unlink</source>
<target>Scollega</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unlink desktop?" xml:space="preserve">
<source>Unlink desktop?</source>
<target>Scollegare il desktop?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unlock" xml:space="preserve">
@@ -5300,6 +5381,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e
</trans-unit>
<trans-unit id="Use from desktop" xml:space="preserve">
<source>Use from desktop</source>
<target>Usa dal desktop</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Use iOS call interface" xml:space="preserve">
@@ -5334,10 +5416,12 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e
</trans-unit>
<trans-unit id="Verify code with desktop" xml:space="preserve">
<source>Verify code with desktop</source>
<target>Verifica il codice con il desktop</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Verify connection" xml:space="preserve">
<source>Verify connection</source>
<target>Verifica la connessione</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Verify connection security" xml:space="preserve">
@@ -5347,6 +5431,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e
</trans-unit>
<trans-unit id="Verify connections" xml:space="preserve">
<source>Verify connections</source>
<target>Verifica le connessioni</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Verify security code" xml:space="preserve">
@@ -5359,6 +5444,11 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e
<target>Via browser</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Via secure quantum resistant protocol." xml:space="preserve">
<source>Via secure quantum resistant protocol.</source>
<target>Tramite protocollo sicuro resistente alla quantistica.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Video call" xml:space="preserve">
<source>Video call</source>
<target>Videochiamata</target>
@@ -5409,6 +5499,10 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e
<target>Messaggio vocale…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for desktop..." xml:space="preserve">
<source>Waiting for desktop...</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for file" xml:space="preserve">
<source>Waiting for file</source>
<target>In attesa del file</target>
@@ -5954,6 +6048,10 @@ I server di SimpleX non possono vedere il tuo profilo.</target>
<target>chiamata audio (non crittografata e2e)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="author" xml:space="preserve">
<source>author</source>
<note>member role</note>
</trans-unit>
<trans-unit id="bad message ID" xml:space="preserve">
<source>bad message ID</source>
<target>ID messaggio errato</target>
@@ -6538,6 +6636,7 @@ I server di SimpleX non possono vedere il tuo profilo.</target>
</trans-unit>
<trans-unit id="v%@" xml:space="preserve">
<source>v%@</source>
<target>v%@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="v%@ (%@)" xml:space="preserve">
@@ -6679,6 +6778,7 @@ I server di SimpleX non possono vedere il tuo profilo.</target>
</trans-unit>
<trans-unit id="NSLocalNetworkUsageDescription" xml:space="preserve">
<source>SimpleX uses local network access to allow using user chat profile via desktop app on the same network.</source>
<target>SimpleX usa l'accesso alla rete locale per consentire di usare il profilo di chat tramite l'app desktop sulla stessa rete.</target>
<note>Privacy - Local Network Usage Description</note>
</trans-unit>
<trans-unit id="NSMicrophoneUsageDescription" xml:space="preserve">
@@ -383,6 +383,12 @@
- などなど!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- optionally notify deleted contacts.&#10;- profile names with spaces.&#10;- and more!" xml:space="preserve">
<source>- optionally notify deleted contacts.
- profile names with spaces.
- and more!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- voice messages up to 5 minutes.&#10;- custom time to disappear.&#10;- editing history." xml:space="preserve">
<source>- voice messages up to 5 minutes.
- custom time to disappear.
@@ -877,6 +883,10 @@
<target>メッセージのハッシュ値問題</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better groups" xml:space="preserve">
<source>Better groups</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better messages" xml:space="preserve">
<source>Better messages</source>
<target>より良いメッセージ</target>
@@ -886,6 +896,10 @@
<source>Block</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block group members" xml:space="preserve">
<source>Block group members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block member" xml:space="preserve">
<source>Block member</source>
<note>No comment provided by engineer.</note>
@@ -1155,6 +1169,10 @@
<target>接続</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Connect automatically" xml:space="preserve">
<source>Connect automatically</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect incognito" xml:space="preserve">
<source>Connect incognito</source>
<target>シークレットモードで接続</target>
@@ -1326,6 +1344,10 @@ This is your own one-time link!</source>
<target>SimpleXアドレスの作成</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create a group using a random profile." xml:space="preserve">
<source>Create a group using a random profile.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create an address to let people connect with you." xml:space="preserve">
<source>Create an address to let people connect with you.</source>
<target>人とつながるためのアドレスを作成する。</target>
@@ -1841,8 +1863,8 @@ This cannot be undone!</source>
<target>グループを見つけて参加する</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Discover on network" xml:space="preserve">
<source>Discover on network</source>
<trans-unit id="Discover via local network" xml:space="preserve">
<source>Discover via local network</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do NOT use SimpleX for emergency calls." xml:space="preserve">
@@ -2377,6 +2399,10 @@ This cannot be undone!</source>
<target>送信者がオンラインになるまでの待ち時間がなく、速い!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Faster joining and more reliable messages." xml:space="preserve">
<source>Faster joining and more reliable messages.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Favorite" xml:space="preserve">
<source>Favorite</source>
<target>お気に入り</target>
@@ -2472,6 +2498,10 @@ This cannot be undone!</source>
<target>コンソール</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Found desktop" xml:space="preserve">
<source>Found desktop</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="French interface" xml:space="preserve">
<source>French interface</source>
<target>フランス語UI</target>
@@ -2794,6 +2824,10 @@ This cannot be undone!</source>
<target>シークレットモード</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito groups" xml:space="preserve">
<source>Incognito groups</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito mode" xml:space="preserve">
<source>Incognito mode</source>
<target>シークレットモード</target>
@@ -3057,6 +3091,10 @@ This is your link for group %@!</source>
<target>制限事項</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Link mobile and desktop apps! 🔗" xml:space="preserve">
<source>Link mobile and desktop apps! 🔗</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Linked desktop options" xml:space="preserve">
<source>Linked desktop options</source>
<note>No comment provided by engineer.</note>
@@ -3413,6 +3451,10 @@ This is your link for group %@!</source>
<target>送受信済みのファイルがありません</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Not compatible!" xml:space="preserve">
<source>Not compatible!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Notifications" xml:space="preserve">
<source>Notifications</source>
<target>通知</target>
@@ -4977,6 +5019,10 @@ It can happen because of some bug or when the connection is compromised.</source
<target>接続するにはQRコードを読み込むか、アプリ内のリンクを使用する必要があります。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To hide unwanted messages." xml:space="preserve">
<source>To hide unwanted messages.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To make a new connection" xml:space="preserve">
<source>To make a new connection</source>
<target>新規に接続する場合</target>
@@ -5291,6 +5337,10 @@ To connect, please ask your contact to create another connection link and check
<target>ブラウザ経由</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Via secure quantum resistant protocol." xml:space="preserve">
<source>Via secure quantum resistant protocol.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Video call" xml:space="preserve">
<source>Video call</source>
<target>ビデオ通話</target>
@@ -5341,6 +5391,10 @@ To connect, please ask your contact to create another connection link and check
<target>音声メッセージ…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for desktop..." xml:space="preserve">
<source>Waiting for desktop...</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for file" xml:space="preserve">
<source>Waiting for file</source>
<target>ファイル待ち</target>
@@ -5871,6 +5925,10 @@ SimpleX サーバーはあなたのプロファイルを参照できません。
<target>音声通話 (エンドツーエンド暗号化なし)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="author" xml:space="preserve">
<source>author</source>
<note>member role</note>
</trans-unit>
<trans-unit id="bad message ID" xml:space="preserve">
<source>bad message ID</source>
<target>メッセージ ID が正しくありません</target>
@@ -299,10 +299,12 @@
</trans-unit>
<trans-unit id="(new)" xml:space="preserve">
<source>(new)</source>
<target>(nieuw)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="(this device v%@)" xml:space="preserve">
<source>(this device v%@)</source>
<target>(dit apparaat v%@)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id=")" xml:space="preserve">
@@ -390,6 +392,15 @@
- and more!</source>
<target>- stabielere berichtbezorging.
- een beetje betere groepen.
- en meer!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- optionally notify deleted contacts.&#10;- profile names with spaces.&#10;- and more!" xml:space="preserve">
<source>- optionally notify deleted contacts.
- profile names with spaces.
- and more!</source>
<target>- optioneel verwijderde contacten op de hoogte stellen.
- profielnamen met spaties.
- en meer!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -879,6 +890,7 @@
</trans-unit>
<trans-unit id="Bad desktop address" xml:space="preserve">
<source>Bad desktop address</source>
<target>Onjuist desktopadres</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Bad message ID" xml:space="preserve">
@@ -891,6 +903,11 @@
<target>Onjuiste bericht hash</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better groups" xml:space="preserve">
<source>Better groups</source>
<target>Betere groepen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better messages" xml:space="preserve">
<source>Better messages</source>
<target>Betere berichten</target>
@@ -901,6 +918,11 @@
<target>Blokkeren</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block group members" xml:space="preserve">
<source>Block group members</source>
<target>Groepsleden blokkeren</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block member" xml:space="preserve">
<source>Block member</source>
<target>Lid blokkeren</target>
@@ -1172,6 +1194,11 @@
<target>Verbind</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Connect automatically" xml:space="preserve">
<source>Connect automatically</source>
<target>Automatisch verbinden</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect incognito" xml:space="preserve">
<source>Connect incognito</source>
<target>Verbind incognito</target>
@@ -1179,6 +1206,7 @@
</trans-unit>
<trans-unit id="Connect to desktop" xml:space="preserve">
<source>Connect to desktop</source>
<target>Verbinden met desktop</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect to yourself?" xml:space="preserve">
@@ -1227,10 +1255,12 @@ Dit is uw eigen eenmalige link!</target>
</trans-unit>
<trans-unit id="Connected desktop" xml:space="preserve">
<source>Connected desktop</source>
<target>Verbonden desktop</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connected to desktop" xml:space="preserve">
<source>Connected to desktop</source>
<target>Verbonden met desktop</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connecting server…" xml:space="preserve">
@@ -1245,6 +1275,7 @@ Dit is uw eigen eenmalige link!</target>
</trans-unit>
<trans-unit id="Connecting to desktop" xml:space="preserve">
<source>Connecting to desktop</source>
<target>Verbinding maken met desktop</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connection" xml:space="preserve">
@@ -1269,6 +1300,7 @@ Dit is uw eigen eenmalige link!</target>
</trans-unit>
<trans-unit id="Connection terminated" xml:space="preserve">
<source>Connection terminated</source>
<target>Verbinding beëindigd</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connection timeout" xml:space="preserve">
@@ -1351,6 +1383,11 @@ Dit is uw eigen eenmalige link!</target>
<target>Maak een SimpleX adres aan</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create a group using a random profile." xml:space="preserve">
<source>Create a group using a random profile.</source>
<target>Maak een groep met een willekeurig profiel.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create an address to let people connect with you." xml:space="preserve">
<source>Create an address to let people connect with you.</source>
<target>Maak een adres aan zodat mensen contact met je kunnen opnemen.</target>
@@ -1763,14 +1800,17 @@ Dit kan niet ongedaan gemaakt worden!</target>
</trans-unit>
<trans-unit id="Desktop address" xml:space="preserve">
<source>Desktop address</source>
<target>Desktop adres</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Desktop app version %@ is not compatible with this app." xml:space="preserve">
<source>Desktop app version %@ is not compatible with this app.</source>
<target>Desktop-app-versie %@ is niet compatibel met deze app.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Desktop devices" xml:space="preserve">
<source>Desktop devices</source>
<target>Desktop apparaten</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Develop" xml:space="preserve">
@@ -1865,6 +1905,7 @@ Dit kan niet ongedaan gemaakt worden!</target>
</trans-unit>
<trans-unit id="Disconnect desktop?" xml:space="preserve">
<source>Disconnect desktop?</source>
<target>Desktop loskoppelen?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Discover and join groups" xml:space="preserve">
@@ -1872,8 +1913,9 @@ Dit kan niet ongedaan gemaakt worden!</target>
<target>Ontdek en sluit je aan bij groepen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Discover on network" xml:space="preserve">
<source>Discover on network</source>
<trans-unit id="Discover via local network" xml:space="preserve">
<source>Discover via local network</source>
<target>Ontdek via het lokale netwerk</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do NOT use SimpleX for emergency calls." xml:space="preserve">
@@ -2048,10 +2090,12 @@ Dit kan niet ongedaan gemaakt worden!</target>
</trans-unit>
<trans-unit id="Encryption re-negotiation error" xml:space="preserve">
<source>Encryption re-negotiation error</source>
<target>Fout bij heronderhandeling van codering</target>
<note>message decrypt error item</note>
</trans-unit>
<trans-unit id="Encryption re-negotiation failed." xml:space="preserve">
<source>Encryption re-negotiation failed.</source>
<target>Opnieuw onderhandelen over de codering is mislukt.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enter Passcode" xml:space="preserve">
@@ -2086,6 +2130,7 @@ Dit kan niet ongedaan gemaakt worden!</target>
</trans-unit>
<trans-unit id="Enter this device name…" xml:space="preserve">
<source>Enter this device name…</source>
<target>Voer deze apparaatnaam in…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enter welcome message…" xml:space="preserve">
@@ -2413,6 +2458,11 @@ Dit kan niet ongedaan gemaakt worden!</target>
<target>Snel en niet wachten tot de afzender online is!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Faster joining and more reliable messages." xml:space="preserve">
<source>Faster joining and more reliable messages.</source>
<target>Snellere deelname en betrouwbaardere berichten.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Favorite" xml:space="preserve">
<source>Favorite</source>
<target>Favoriet</target>
@@ -2508,6 +2558,11 @@ Dit kan niet ongedaan gemaakt worden!</target>
<target>Voor console</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Found desktop" xml:space="preserve">
<source>Found desktop</source>
<target>Desktop gevonden</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="French interface" xml:space="preserve">
<source>French interface</source>
<target>Franse interface</target>
@@ -2833,6 +2888,11 @@ Dit kan niet ongedaan gemaakt worden!</target>
<target>Incognito</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito groups" xml:space="preserve">
<source>Incognito groups</source>
<target>Incognitogroepen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito mode" xml:space="preserve">
<source>Incognito mode</source>
<target>Incognito modus</target>
@@ -2865,6 +2925,7 @@ Dit kan niet ongedaan gemaakt worden!</target>
</trans-unit>
<trans-unit id="Incompatible version" xml:space="preserve">
<source>Incompatible version</source>
<target>Incompatibele versie</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incorrect passcode" xml:space="preserve">
@@ -3039,6 +3100,7 @@ Dit is jouw link voor groep %@!</target>
</trans-unit>
<trans-unit id="Keep the app open to use it from desktop" xml:space="preserve">
<source>Keep the app open to use it from desktop</source>
<target>Houd de app geopend om deze vanaf de desktop te gebruiken</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Keep your connections" xml:space="preserve">
@@ -3101,12 +3163,19 @@ Dit is jouw link voor groep %@!</target>
<target>Beperkingen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Link mobile and desktop apps! 🔗" xml:space="preserve">
<source>Link mobile and desktop apps! 🔗</source>
<target>Koppel mobiele en desktop-apps! 🔗</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Linked desktop options" xml:space="preserve">
<source>Linked desktop options</source>
<target>Gekoppelde desktop opties</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Linked desktops" xml:space="preserve">
<source>Linked desktops</source>
<target>Gelinkte desktops</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Live message!" xml:space="preserve">
@@ -3459,6 +3528,11 @@ Dit is jouw link voor groep %@!</target>
<target>Geen ontvangen of verzonden bestanden</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Not compatible!" xml:space="preserve">
<source>Not compatible!</source>
<target>Niet compatibel!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Notifications" xml:space="preserve">
<source>Notifications</source>
<target>Meldingen</target>
@@ -3680,6 +3754,7 @@ Dit is jouw link voor groep %@!</target>
</trans-unit>
<trans-unit id="Paste desktop address" xml:space="preserve">
<source>Paste desktop address</source>
<target>Desktopadres plakken</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Paste image" xml:space="preserve">
@@ -4279,6 +4354,7 @@ Dit is jouw link voor groep %@!</target>
</trans-unit>
<trans-unit id="Scan QR code from desktop" xml:space="preserve">
<source>Scan QR code from desktop</source>
<target>Scan QR-code vanaf uw desktop</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Scan code" xml:space="preserve">
@@ -4503,6 +4579,7 @@ Dit is jouw link voor groep %@!</target>
</trans-unit>
<trans-unit id="Session code" xml:space="preserve">
<source>Session code</source>
<target>Sessie code</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set 1 day" xml:space="preserve">
@@ -5004,6 +5081,7 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast.</target>
</trans-unit>
<trans-unit id="This device name" xml:space="preserve">
<source>This device name</source>
<target>Deze apparaatnaam</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="This group has over %lld members, delivery receipts are not sent." xml:space="preserve">
@@ -5041,6 +5119,11 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast.</target>
<target>Om verbinding te maken, kan uw contact de QR-code scannen of de link in de app gebruiken.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To hide unwanted messages." xml:space="preserve">
<source>To hide unwanted messages.</source>
<target>Om ongewenste berichten te verbergen.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To make a new connection" xml:space="preserve">
<source>To make a new connection</source>
<target>Om een nieuwe verbinding te maken</target>
@@ -5202,10 +5285,12 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak
</trans-unit>
<trans-unit id="Unlink" xml:space="preserve">
<source>Unlink</source>
<target>Ontkoppelen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unlink desktop?" xml:space="preserve">
<source>Unlink desktop?</source>
<target>Desktop ontkoppelen?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unlock" xml:space="preserve">
@@ -5300,6 +5385,7 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak
</trans-unit>
<trans-unit id="Use from desktop" xml:space="preserve">
<source>Use from desktop</source>
<target>Gebruik vanaf desktop</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Use iOS call interface" xml:space="preserve">
@@ -5334,10 +5420,12 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak
</trans-unit>
<trans-unit id="Verify code with desktop" xml:space="preserve">
<source>Verify code with desktop</source>
<target>Code verifiëren met desktop</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Verify connection" xml:space="preserve">
<source>Verify connection</source>
<target>Controleer de verbinding</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Verify connection security" xml:space="preserve">
@@ -5347,6 +5435,7 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak
</trans-unit>
<trans-unit id="Verify connections" xml:space="preserve">
<source>Verify connections</source>
<target>Controleer verbindingen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Verify security code" xml:space="preserve">
@@ -5359,6 +5448,11 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak
<target>Via browser</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Via secure quantum resistant protocol." xml:space="preserve">
<source>Via secure quantum resistant protocol.</source>
<target>Via een beveiligd kwantumbestendig protocol.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Video call" xml:space="preserve">
<source>Video call</source>
<target>video oproep</target>
@@ -5409,6 +5503,11 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak
<target>Spraakbericht…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for desktop..." xml:space="preserve">
<source>Waiting for desktop...</source>
<target>Wachten op desktop...</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for file" xml:space="preserve">
<source>Waiting for file</source>
<target>Wachten op bestand</target>
@@ -5954,6 +6053,11 @@ SimpleX servers kunnen uw profiel niet zien.</target>
<target>audio oproep (niet e2e versleuteld)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="author" xml:space="preserve">
<source>author</source>
<target>auteur</target>
<note>member role</note>
</trans-unit>
<trans-unit id="bad message ID" xml:space="preserve">
<source>bad message ID</source>
<target>Onjuiste bericht-ID</target>
@@ -6538,6 +6642,7 @@ SimpleX servers kunnen uw profiel niet zien.</target>
</trans-unit>
<trans-unit id="v%@" xml:space="preserve">
<source>v%@</source>
<target>v%@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="v%@ (%@)" xml:space="preserve">
@@ -6679,6 +6784,7 @@ SimpleX servers kunnen uw profiel niet zien.</target>
</trans-unit>
<trans-unit id="NSLocalNetworkUsageDescription" xml:space="preserve">
<source>SimpleX uses local network access to allow using user chat profile via desktop app on the same network.</source>
<target>SimpleX maakt gebruik van lokale netwerktoegang om het gebruik van een gebruikerschatprofiel via de desktop-app op hetzelfde netwerk mogelijk te maken.</target>
<note>Privacy - Local Network Usage Description</note>
</trans-unit>
<trans-unit id="NSMicrophoneUsageDescription" xml:space="preserve">
@@ -299,10 +299,12 @@
</trans-unit>
<trans-unit id="(new)" xml:space="preserve">
<source>(new)</source>
<target>(nowy)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="(this device v%@)" xml:space="preserve">
<source>(this device v%@)</source>
<target>(to urządzenie v%@)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id=")" xml:space="preserve">
@@ -393,6 +395,15 @@
- i więcej!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- optionally notify deleted contacts.&#10;- profile names with spaces.&#10;- and more!" xml:space="preserve">
<source>- optionally notify deleted contacts.
- profile names with spaces.
- and more!</source>
<target>- opcjonalnie powiadamiaj usunięte kontakty.
- nazwy profili ze spacją.
- i wiele więcej!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- voice messages up to 5 minutes.&#10;- custom time to disappear.&#10;- editing history." xml:space="preserve">
<source>- voice messages up to 5 minutes.
- custom time to disappear.
@@ -879,6 +890,7 @@
</trans-unit>
<trans-unit id="Bad desktop address" xml:space="preserve">
<source>Bad desktop address</source>
<target>Zły adres komputera</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Bad message ID" xml:space="preserve">
@@ -891,6 +903,11 @@
<target>Zły hash wiadomości</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better groups" xml:space="preserve">
<source>Better groups</source>
<target>Lepsze grupy</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better messages" xml:space="preserve">
<source>Better messages</source>
<target>Lepsze wiadomości</target>
@@ -901,6 +918,11 @@
<target>Zablokuj</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block group members" xml:space="preserve">
<source>Block group members</source>
<target>Blokuj członków grupy</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block member" xml:space="preserve">
<source>Block member</source>
<target>Zablokuj członka</target>
@@ -1172,6 +1194,11 @@
<target>Połącz</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Connect automatically" xml:space="preserve">
<source>Connect automatically</source>
<target>Łącz automatycznie</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect incognito" xml:space="preserve">
<source>Connect incognito</source>
<target>Połącz incognito</target>
@@ -1179,6 +1206,7 @@
</trans-unit>
<trans-unit id="Connect to desktop" xml:space="preserve">
<source>Connect to desktop</source>
<target>Połącz do komputera</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect to yourself?" xml:space="preserve">
@@ -1227,10 +1255,12 @@ To jest twój jednorazowy link!</target>
</trans-unit>
<trans-unit id="Connected desktop" xml:space="preserve">
<source>Connected desktop</source>
<target>Połączony komputer</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connected to desktop" xml:space="preserve">
<source>Connected to desktop</source>
<target>Połączony do komputera</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connecting server…" xml:space="preserve">
@@ -1245,6 +1275,7 @@ To jest twój jednorazowy link!</target>
</trans-unit>
<trans-unit id="Connecting to desktop" xml:space="preserve">
<source>Connecting to desktop</source>
<target>Łączenie z komputerem</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connection" xml:space="preserve">
@@ -1269,6 +1300,7 @@ To jest twój jednorazowy link!</target>
</trans-unit>
<trans-unit id="Connection terminated" xml:space="preserve">
<source>Connection terminated</source>
<target>Połączenie zakończone</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connection timeout" xml:space="preserve">
@@ -1351,6 +1383,11 @@ To jest twój jednorazowy link!</target>
<target>Utwórz adres SimpleX</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create a group using a random profile." xml:space="preserve">
<source>Create a group using a random profile.</source>
<target>Utwórz grupę używając losowego profilu.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create an address to let people connect with you." xml:space="preserve">
<source>Create an address to let people connect with you.</source>
<target>Utwórz adres, aby ludzie mogli się z Tobą połączyć.</target>
@@ -1763,14 +1800,17 @@ To nie może być cofnięte!</target>
</trans-unit>
<trans-unit id="Desktop address" xml:space="preserve">
<source>Desktop address</source>
<target>Adres komputera</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Desktop app version %@ is not compatible with this app." xml:space="preserve">
<source>Desktop app version %@ is not compatible with this app.</source>
<target>Wersja aplikacji komputerowej %@ nie jest kompatybilna z tą aplikacją.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Desktop devices" xml:space="preserve">
<source>Desktop devices</source>
<target>Urządzenia komputerowe</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Develop" xml:space="preserve">
@@ -1865,6 +1905,7 @@ To nie może być cofnięte!</target>
</trans-unit>
<trans-unit id="Disconnect desktop?" xml:space="preserve">
<source>Disconnect desktop?</source>
<target>Rozłączyć komputer?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Discover and join groups" xml:space="preserve">
@@ -1872,8 +1913,9 @@ To nie może być cofnięte!</target>
<target>Odkrywaj i dołączaj do grup</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Discover on network" xml:space="preserve">
<source>Discover on network</source>
<trans-unit id="Discover via local network" xml:space="preserve">
<source>Discover via local network</source>
<target>Odkryj przez sieć lokalną</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do NOT use SimpleX for emergency calls." xml:space="preserve">
@@ -2048,10 +2090,12 @@ To nie może być cofnięte!</target>
</trans-unit>
<trans-unit id="Encryption re-negotiation error" xml:space="preserve">
<source>Encryption re-negotiation error</source>
<target>Błąd renegocjacji szyfrowania</target>
<note>message decrypt error item</note>
</trans-unit>
<trans-unit id="Encryption re-negotiation failed." xml:space="preserve">
<source>Encryption re-negotiation failed.</source>
<target>Renegocjacja szyfrowania nie powiodła się.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enter Passcode" xml:space="preserve">
@@ -2086,6 +2130,7 @@ To nie może być cofnięte!</target>
</trans-unit>
<trans-unit id="Enter this device name…" xml:space="preserve">
<source>Enter this device name…</source>
<target>Podaj nazwę urządzenia…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enter welcome message…" xml:space="preserve">
@@ -2413,6 +2458,11 @@ To nie może być cofnięte!</target>
<target>Szybko i bez czekania aż nadawca będzie online!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Faster joining and more reliable messages." xml:space="preserve">
<source>Faster joining and more reliable messages.</source>
<target>Szybsze dołączenie i bardziej niezawodne wiadomości.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Favorite" xml:space="preserve">
<source>Favorite</source>
<target>Ulubione</target>
@@ -2508,6 +2558,11 @@ To nie może być cofnięte!</target>
<target>Dla konsoli</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Found desktop" xml:space="preserve">
<source>Found desktop</source>
<target>Znaleziono komputer</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="French interface" xml:space="preserve">
<source>French interface</source>
<target>Francuski interfejs</target>
@@ -2833,6 +2888,11 @@ To nie może być cofnięte!</target>
<target>Incognito</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito groups" xml:space="preserve">
<source>Incognito groups</source>
<target>Grupy incognito</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito mode" xml:space="preserve">
<source>Incognito mode</source>
<target>Tryb incognito</target>
@@ -2865,6 +2925,7 @@ To nie może być cofnięte!</target>
</trans-unit>
<trans-unit id="Incompatible version" xml:space="preserve">
<source>Incompatible version</source>
<target>Niekompatybilna wersja</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incorrect passcode" xml:space="preserve">
@@ -3039,6 +3100,7 @@ To jest twój link do grupy %@!</target>
</trans-unit>
<trans-unit id="Keep the app open to use it from desktop" xml:space="preserve">
<source>Keep the app open to use it from desktop</source>
<target>Zostaw aplikację otwartą i używaj ją z komputera</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Keep your connections" xml:space="preserve">
@@ -3101,12 +3163,19 @@ To jest twój link do grupy %@!</target>
<target>Ograniczenia</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Link mobile and desktop apps! 🔗" xml:space="preserve">
<source>Link mobile and desktop apps! 🔗</source>
<target>Połącz mobile i komputerowe aplikacje! 🔗</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Linked desktop options" xml:space="preserve">
<source>Linked desktop options</source>
<target>Połączone opcje komputera</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Linked desktops" xml:space="preserve">
<source>Linked desktops</source>
<target>Połączone komputery</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Live message!" xml:space="preserve">
@@ -3459,6 +3528,11 @@ To jest twój link do grupy %@!</target>
<target>Brak odebranych lub wysłanych plików</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Not compatible!" xml:space="preserve">
<source>Not compatible!</source>
<target>Nie kompatybilny!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Notifications" xml:space="preserve">
<source>Notifications</source>
<target>Powiadomienia</target>
@@ -3680,6 +3754,7 @@ To jest twój link do grupy %@!</target>
</trans-unit>
<trans-unit id="Paste desktop address" xml:space="preserve">
<source>Paste desktop address</source>
<target>Wklej adres komputera</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Paste image" xml:space="preserve">
@@ -4279,6 +4354,7 @@ To jest twój link do grupy %@!</target>
</trans-unit>
<trans-unit id="Scan QR code from desktop" xml:space="preserve">
<source>Scan QR code from desktop</source>
<target>Zeskanuj kod QR z komputera</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Scan code" xml:space="preserve">
@@ -4503,6 +4579,7 @@ To jest twój link do grupy %@!</target>
</trans-unit>
<trans-unit id="Session code" xml:space="preserve">
<source>Session code</source>
<target>Kod sesji</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set 1 day" xml:space="preserve">
@@ -5004,6 +5081,7 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom
</trans-unit>
<trans-unit id="This device name" xml:space="preserve">
<source>This device name</source>
<target>Nazwa tego urządzenia</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="This group has over %lld members, delivery receipts are not sent." xml:space="preserve">
@@ -5041,6 +5119,11 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom
<target>Aby się połączyć, Twój kontakt może zeskanować kod QR lub skorzystać z linku w aplikacji.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To hide unwanted messages." xml:space="preserve">
<source>To hide unwanted messages.</source>
<target>Aby ukryć niechciane wiadomości.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To make a new connection" xml:space="preserve">
<source>To make a new connection</source>
<target>Aby nawiązać nowe połączenie</target>
@@ -5202,10 +5285,12 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc
</trans-unit>
<trans-unit id="Unlink" xml:space="preserve">
<source>Unlink</source>
<target>Odłącz</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unlink desktop?" xml:space="preserve">
<source>Unlink desktop?</source>
<target>Odłączyć komputer?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unlock" xml:space="preserve">
@@ -5300,6 +5385,7 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc
</trans-unit>
<trans-unit id="Use from desktop" xml:space="preserve">
<source>Use from desktop</source>
<target>Użyj z komputera</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Use iOS call interface" xml:space="preserve">
@@ -5334,10 +5420,12 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc
</trans-unit>
<trans-unit id="Verify code with desktop" xml:space="preserve">
<source>Verify code with desktop</source>
<target>Zweryfikuj kod z komputera</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Verify connection" xml:space="preserve">
<source>Verify connection</source>
<target>Zweryfikuj połączenie</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Verify connection security" xml:space="preserve">
@@ -5347,6 +5435,7 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc
</trans-unit>
<trans-unit id="Verify connections" xml:space="preserve">
<source>Verify connections</source>
<target>Zweryfikuj połączenia</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Verify security code" xml:space="preserve">
@@ -5359,6 +5448,11 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc
<target>Przez przeglądarkę</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Via secure quantum resistant protocol." xml:space="preserve">
<source>Via secure quantum resistant protocol.</source>
<target>Dzięki bezpiecznemu protokołowi odpornego kwantowo.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Video call" xml:space="preserve">
<source>Video call</source>
<target>Połączenie wideo</target>
@@ -5409,6 +5503,11 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc
<target>Wiadomość głosowa…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for desktop..." xml:space="preserve">
<source>Waiting for desktop...</source>
<target>Oczekiwanie na komputer...</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for file" xml:space="preserve">
<source>Waiting for file</source>
<target>Oczekiwanie na plik</target>
@@ -5954,6 +6053,11 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu.</target>
<target>połączenie audio (nie szyfrowane e2e)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="author" xml:space="preserve">
<source>author</source>
<target>autor</target>
<note>member role</note>
</trans-unit>
<trans-unit id="bad message ID" xml:space="preserve">
<source>bad message ID</source>
<target>zły identyfikator wiadomości</target>
@@ -6538,6 +6642,7 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu.</target>
</trans-unit>
<trans-unit id="v%@" xml:space="preserve">
<source>v%@</source>
<target>v%@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="v%@ (%@)" xml:space="preserve">
@@ -6679,6 +6784,7 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu.</target>
</trans-unit>
<trans-unit id="NSLocalNetworkUsageDescription" xml:space="preserve">
<source>SimpleX uses local network access to allow using user chat profile via desktop app on the same network.</source>
<target>SimpleX używa sieci lokalnej aby pozwolić na dostęp profilom czatu użytkownika przez aplikację komputerową na tej samej sieci.</target>
<note>Privacy - Local Network Usage Description</note>
</trans-unit>
<trans-unit id="NSMicrophoneUsageDescription" xml:space="preserve">
@@ -89,6 +89,7 @@
</trans-unit>
<trans-unit id="%@ and %@" xml:space="preserve">
<source>%@ and %@</source>
<target>%@ и %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@ and %@ connected" xml:space="preserve">
@@ -103,6 +104,7 @@
</trans-unit>
<trans-unit id="%@ connected" xml:space="preserve">
<source>%@ connected</source>
<target>%@ соединен(а)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@ is connected!" xml:space="preserve">
@@ -132,6 +134,7 @@
</trans-unit>
<trans-unit id="%@, %@ and %lld members" xml:space="preserve">
<source>%@, %@ and %lld members</source>
<target>%@, %@ и %lld членов группы</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@, %@ and %lld other members connected" xml:space="preserve">
@@ -201,6 +204,7 @@
</trans-unit>
<trans-unit id="%lld group events" xml:space="preserve">
<source>%lld group events</source>
<target>%lld событий</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld members" xml:space="preserve">
@@ -210,14 +214,17 @@
</trans-unit>
<trans-unit id="%lld messages blocked" xml:space="preserve">
<source>%lld messages blocked</source>
<target>%lld сообщений заблокировано</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld messages marked deleted" xml:space="preserve">
<source>%lld messages marked deleted</source>
<target>%lld сообщений помечено удалёнными</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld messages moderated by %@" xml:space="preserve">
<source>%lld messages moderated by %@</source>
<target>%lld сообщений модерировано членом %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld minutes" xml:space="preserve">
@@ -292,10 +299,12 @@
</trans-unit>
<trans-unit id="(new)" xml:space="preserve">
<source>(new)</source>
<target>(новое)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="(this device v%@)" xml:space="preserve">
<source>(this device v%@)</source>
<target>(это устройство v%@)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id=")" xml:space="preserve">
@@ -383,6 +392,15 @@
- and more!</source>
<target>- более стабильная доставка сообщений.
- немного улучшенные группы.
- и прочее!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- optionally notify deleted contacts.&#10;- profile names with spaces.&#10;- and more!" xml:space="preserve">
<source>- optionally notify deleted contacts.
- profile names with spaces.
- and more!</source>
<target>- опционально уведомляйте удалённые контакты.
- имена профилей с пробелами.
- и прочее!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -402,6 +420,7 @@
</trans-unit>
<trans-unit id="0 sec" xml:space="preserve">
<source>0 sec</source>
<target>0 сек</target>
<note>time to disappear</note>
</trans-unit>
<trans-unit id="0s" xml:space="preserve">
@@ -631,6 +650,7 @@
</trans-unit>
<trans-unit id="All new messages from %@ will be hidden!" xml:space="preserve">
<source>All new messages from %@ will be hidden!</source>
<target>Все новые сообщения от %@ будут скрыты!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="All your contacts will remain connected." xml:space="preserve">
@@ -740,10 +760,12 @@
</trans-unit>
<trans-unit id="Already connecting!" xml:space="preserve">
<source>Already connecting!</source>
<target>Уже соединяется!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Already joining the group!" xml:space="preserve">
<source>Already joining the group!</source>
<target>Вступление в группу уже начато!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Always use relay" xml:space="preserve">
@@ -868,6 +890,7 @@
</trans-unit>
<trans-unit id="Bad desktop address" xml:space="preserve">
<source>Bad desktop address</source>
<target>Неверный адрес компьютера</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Bad message ID" xml:space="preserve">
@@ -880,6 +903,11 @@
<target>Ошибка хэш сообщения</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better groups" xml:space="preserve">
<source>Better groups</source>
<target>Улучшенные группы</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better messages" xml:space="preserve">
<source>Better messages</source>
<target>Улучшенные сообщения</target>
@@ -887,14 +915,22 @@
</trans-unit>
<trans-unit id="Block" xml:space="preserve">
<source>Block</source>
<target>Заблокировать</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block group members" xml:space="preserve">
<source>Block group members</source>
<target>Блокируйте членов группы</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block member" xml:space="preserve">
<source>Block member</source>
<target>Заблокировать члена группы</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block member?" xml:space="preserve">
<source>Block member?</source>
<target>Заблокировать члена группы?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Both you and your contact can add message reactions." xml:space="preserve">
@@ -1158,6 +1194,11 @@
<target>Соединиться</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Connect automatically" xml:space="preserve">
<source>Connect automatically</source>
<target>Соединяться автоматически</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect incognito" xml:space="preserve">
<source>Connect incognito</source>
<target>Соединиться Инкогнито</target>
@@ -1165,24 +1206,31 @@
</trans-unit>
<trans-unit id="Connect to desktop" xml:space="preserve">
<source>Connect to desktop</source>
<target>Подключиться к компьютеру</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect to yourself?" xml:space="preserve">
<source>Connect to yourself?</source>
<target>Соединиться с самим собой?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect to yourself?&#10;This is your own SimpleX address!" xml:space="preserve">
<source>Connect to yourself?
This is your own SimpleX address!</source>
<target>Соединиться с самим собой?
Это ваш собственный адрес SimpleX!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect to yourself?&#10;This is your own one-time link!" xml:space="preserve">
<source>Connect to yourself?
This is your own one-time link!</source>
<target>Соединиться с самим собой?
Это ваша собственная одноразовая ссылка!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect via contact address" xml:space="preserve">
<source>Connect via contact address</source>
<target>Соединиться через адрес</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect via link" xml:space="preserve">
@@ -1202,14 +1250,17 @@ This is your own one-time link!</source>
</trans-unit>
<trans-unit id="Connect with %@" xml:space="preserve">
<source>Connect with %@</source>
<target>Соединиться с %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connected desktop" xml:space="preserve">
<source>Connected desktop</source>
<target>Подключенный компьютер</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connected to desktop" xml:space="preserve">
<source>Connected to desktop</source>
<target>Компьютер подключен</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connecting server…" xml:space="preserve">
@@ -1224,6 +1275,7 @@ This is your own one-time link!</source>
</trans-unit>
<trans-unit id="Connecting to desktop" xml:space="preserve">
<source>Connecting to desktop</source>
<target>Подключение к компьютеру</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connection" xml:space="preserve">
@@ -1248,6 +1300,7 @@ This is your own one-time link!</source>
</trans-unit>
<trans-unit id="Connection terminated" xml:space="preserve">
<source>Connection terminated</source>
<target>Подключение прервано</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connection timeout" xml:space="preserve">
@@ -1317,6 +1370,7 @@ This is your own one-time link!</source>
</trans-unit>
<trans-unit id="Correct name to %@?" xml:space="preserve">
<source>Correct name to %@?</source>
<target>Исправить имя на %@?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create" xml:space="preserve">
@@ -1329,6 +1383,11 @@ This is your own one-time link!</source>
<target>Создать адрес SimpleX</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create a group using a random profile." xml:space="preserve">
<source>Create a group using a random profile.</source>
<target>Создайте группу, используя случайный профиль.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create an address to let people connect with you." xml:space="preserve">
<source>Create an address to let people connect with you.</source>
<target>Создайте адрес, чтобы можно было соединиться с вами.</target>
@@ -1341,6 +1400,7 @@ This is your own one-time link!</source>
</trans-unit>
<trans-unit id="Create group" xml:space="preserve">
<source>Create group</source>
<target>Создать группу</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create group link" xml:space="preserve">
@@ -1365,6 +1425,7 @@ This is your own one-time link!</source>
</trans-unit>
<trans-unit id="Create profile" xml:space="preserve">
<source>Create profile</source>
<target>Создать профиль</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create queue" xml:space="preserve">
@@ -1527,6 +1588,7 @@ This is your own one-time link!</source>
</trans-unit>
<trans-unit id="Delete %lld messages?" xml:space="preserve">
<source>Delete %lld messages?</source>
<target>Удалить %lld сообщений?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete Contact" xml:space="preserve">
@@ -1556,6 +1618,7 @@ This is your own one-time link!</source>
</trans-unit>
<trans-unit id="Delete and notify contact" xml:space="preserve">
<source>Delete and notify contact</source>
<target>Удалить и уведомить контакт</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete archive" xml:space="preserve">
@@ -1591,6 +1654,8 @@ This is your own one-time link!</source>
<trans-unit id="Delete contact?&#10;This cannot be undone!" xml:space="preserve">
<source>Delete contact?
This cannot be undone!</source>
<target>Удалить контакт?
Это не может быть отменено!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete database" xml:space="preserve">
@@ -1735,14 +1800,17 @@ This cannot be undone!</source>
</trans-unit>
<trans-unit id="Desktop address" xml:space="preserve">
<source>Desktop address</source>
<target>Адрес компьютера</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Desktop app version %@ is not compatible with this app." xml:space="preserve">
<source>Desktop app version %@ is not compatible with this app.</source>
<target>Версия настольного приложения %@ несовместима с этим приложением.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Desktop devices" xml:space="preserve">
<source>Desktop devices</source>
<target>Компьютеры</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Develop" xml:space="preserve">
@@ -1837,6 +1905,7 @@ This cannot be undone!</source>
</trans-unit>
<trans-unit id="Disconnect desktop?" xml:space="preserve">
<source>Disconnect desktop?</source>
<target>Отключить компьютер?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Discover and join groups" xml:space="preserve">
@@ -1844,8 +1913,9 @@ This cannot be undone!</source>
<target>Найдите и вступите в группы</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Discover on network" xml:space="preserve">
<source>Discover on network</source>
<trans-unit id="Discover via local network" xml:space="preserve">
<source>Discover via local network</source>
<target>Обнаружение по локальной сети</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do NOT use SimpleX for emergency calls." xml:space="preserve">
@@ -2020,10 +2090,12 @@ This cannot be undone!</source>
</trans-unit>
<trans-unit id="Encryption re-negotiation error" xml:space="preserve">
<source>Encryption re-negotiation error</source>
<target>Ошибка нового соглашения о шифровании</target>
<note>message decrypt error item</note>
</trans-unit>
<trans-unit id="Encryption re-negotiation failed." xml:space="preserve">
<source>Encryption re-negotiation failed.</source>
<target>Ошибка нового соглашения о шифровании.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enter Passcode" xml:space="preserve">
@@ -2038,6 +2110,7 @@ This cannot be undone!</source>
</trans-unit>
<trans-unit id="Enter group name…" xml:space="preserve">
<source>Enter group name…</source>
<target>Введите имя группы…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enter passphrase…" xml:space="preserve">
@@ -2057,6 +2130,7 @@ This cannot be undone!</source>
</trans-unit>
<trans-unit id="Enter this device name…" xml:space="preserve">
<source>Enter this device name…</source>
<target>Введите имя этого устройства…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enter welcome message…" xml:space="preserve">
@@ -2071,6 +2145,7 @@ This cannot be undone!</source>
</trans-unit>
<trans-unit id="Enter your name…" xml:space="preserve">
<source>Enter your name…</source>
<target>Введите ваше имя…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error" xml:space="preserve">
@@ -2130,6 +2205,7 @@ This cannot be undone!</source>
</trans-unit>
<trans-unit id="Error creating member contact" xml:space="preserve">
<source>Error creating member contact</source>
<target>Ошибка создания контакта с членом группы</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error creating profile!" xml:space="preserve">
@@ -2264,6 +2340,7 @@ This cannot be undone!</source>
</trans-unit>
<trans-unit id="Error sending member contact invitation" xml:space="preserve">
<source>Error sending member contact invitation</source>
<target>Ошибка отправки приглашения члену группы</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error sending message" xml:space="preserve">
@@ -2348,6 +2425,7 @@ This cannot be undone!</source>
</trans-unit>
<trans-unit id="Expand" xml:space="preserve">
<source>Expand</source>
<target>Раскрыть</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Export database" xml:space="preserve">
@@ -2380,6 +2458,11 @@ This cannot be undone!</source>
<target>Быстрые и не нужно ждать, когда отправитель онлайн!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Faster joining and more reliable messages." xml:space="preserve">
<source>Faster joining and more reliable messages.</source>
<target>Быстрое вступление и надежная доставка сообщений.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Favorite" xml:space="preserve">
<source>Favorite</source>
<target>Избранный</target>
@@ -2475,6 +2558,11 @@ This cannot be undone!</source>
<target>Для консоли</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Found desktop" xml:space="preserve">
<source>Found desktop</source>
<target>Компьютер найден</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="French interface" xml:space="preserve">
<source>French interface</source>
<target>Французский интерфейс</target>
@@ -2497,6 +2585,7 @@ This cannot be undone!</source>
</trans-unit>
<trans-unit id="Fully decentralized visible only to members." xml:space="preserve">
<source>Fully decentralized visible only to members.</source>
<target>Группа полностью децентрализована – она видна только членам.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Fully re-implemented - work in background!" xml:space="preserve">
@@ -2521,10 +2610,12 @@ This cannot be undone!</source>
</trans-unit>
<trans-unit id="Group already exists" xml:space="preserve">
<source>Group already exists</source>
<target>Группа уже существует</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Group already exists!" xml:space="preserve">
<source>Group already exists!</source>
<target>Группа уже существует!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Group display name" xml:space="preserve">
@@ -2797,6 +2888,11 @@ This cannot be undone!</source>
<target>Инкогнито</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito groups" xml:space="preserve">
<source>Incognito groups</source>
<target>Инкогнито группы</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito mode" xml:space="preserve">
<source>Incognito mode</source>
<target>Режим Инкогнито</target>
@@ -2829,6 +2925,7 @@ This cannot be undone!</source>
</trans-unit>
<trans-unit id="Incompatible version" xml:space="preserve">
<source>Incompatible version</source>
<target>Несовместимая версия</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incorrect passcode" xml:space="preserve">
@@ -2880,6 +2977,7 @@ This cannot be undone!</source>
</trans-unit>
<trans-unit id="Invalid name!" xml:space="preserve">
<source>Invalid name!</source>
<target>Неверное имя!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Invalid server address!" xml:space="preserve">
@@ -2975,6 +3073,7 @@ This cannot be undone!</source>
</trans-unit>
<trans-unit id="Join group?" xml:space="preserve">
<source>Join group?</source>
<target>Вступить в группу?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Join incognito" xml:space="preserve">
@@ -2984,11 +3083,14 @@ This cannot be undone!</source>
</trans-unit>
<trans-unit id="Join with current profile" xml:space="preserve">
<source>Join with current profile</source>
<target>Вступить с активным профилем</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Join your group?&#10;This is your link for group %@!" xml:space="preserve">
<source>Join your group?
This is your link for group %@!</source>
<target>Вступить в вашу группу?
Это ваша ссылка на группу %@!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Joining group" xml:space="preserve">
@@ -2998,6 +3100,7 @@ This is your link for group %@!</source>
</trans-unit>
<trans-unit id="Keep the app open to use it from desktop" xml:space="preserve">
<source>Keep the app open to use it from desktop</source>
<target>Оставьте приложение открытым, чтобы использовать его с компьютера</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Keep your connections" xml:space="preserve">
@@ -3060,12 +3163,19 @@ This is your link for group %@!</source>
<target>Ограничения</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Link mobile and desktop apps! 🔗" xml:space="preserve">
<source>Link mobile and desktop apps! 🔗</source>
<target>Свяжите мобильное и настольное приложения! 🔗</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Linked desktop options" xml:space="preserve">
<source>Linked desktop options</source>
<target>Опции связанных компьютеров</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Linked desktops" xml:space="preserve">
<source>Linked desktops</source>
<target>Связанные компьютеры</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Live message!" xml:space="preserve">
@@ -3220,6 +3330,7 @@ This is your link for group %@!</source>
</trans-unit>
<trans-unit id="Messages from %@ will be shown!" xml:space="preserve">
<source>Messages from %@ will be shown!</source>
<target>Сообщения от %@ будут показаны!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Migrating database archive…" xml:space="preserve">
@@ -3417,6 +3528,11 @@ This is your link for group %@!</source>
<target>Нет полученных или отправленных файлов</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Not compatible!" xml:space="preserve">
<source>Not compatible!</source>
<target>Несовместимая версия!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Notifications" xml:space="preserve">
<source>Notifications</source>
<target>Уведомления</target>
@@ -3553,6 +3669,7 @@ This is your link for group %@!</source>
</trans-unit>
<trans-unit id="Open" xml:space="preserve">
<source>Open</source>
<target>Открыть</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open Settings" xml:space="preserve">
@@ -3572,6 +3689,7 @@ This is your link for group %@!</source>
</trans-unit>
<trans-unit id="Open group" xml:space="preserve">
<source>Open group</source>
<target>Открыть группу</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open user profiles" xml:space="preserve">
@@ -3636,6 +3754,7 @@ This is your link for group %@!</source>
</trans-unit>
<trans-unit id="Paste desktop address" xml:space="preserve">
<source>Paste desktop address</source>
<target>Вставить адрес компьютера</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Paste image" xml:space="preserve">
@@ -3785,10 +3904,12 @@ This is your link for group %@!</source>
</trans-unit>
<trans-unit id="Profile name" xml:space="preserve">
<source>Profile name</source>
<target>Имя профиля</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Profile name:" xml:space="preserve">
<source>Profile name:</source>
<target>Имя профиля:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Profile password" xml:space="preserve">
@@ -4038,10 +4159,12 @@ This is your link for group %@!</source>
</trans-unit>
<trans-unit id="Repeat connection request?" xml:space="preserve">
<source>Repeat connection request?</source>
<target>Повторить запрос на соединение?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Repeat join request?" xml:space="preserve">
<source>Repeat join request?</source>
<target>Повторить запрос на вступление?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Reply" xml:space="preserve">
@@ -4231,6 +4354,7 @@ This is your link for group %@!</source>
</trans-unit>
<trans-unit id="Scan QR code from desktop" xml:space="preserve">
<source>Scan QR code from desktop</source>
<target>Сканировать QR код с компьютера</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Scan code" xml:space="preserve">
@@ -4315,6 +4439,7 @@ This is your link for group %@!</source>
</trans-unit>
<trans-unit id="Send direct message to connect" xml:space="preserve">
<source>Send direct message to connect</source>
<target>Отправьте сообщение чтобы соединиться</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Send disappearing message" xml:space="preserve">
@@ -4454,6 +4579,7 @@ This is your link for group %@!</source>
</trans-unit>
<trans-unit id="Session code" xml:space="preserve">
<source>Session code</source>
<target>Код сессии</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set 1 day" xml:space="preserve">
@@ -4768,6 +4894,7 @@ This is your link for group %@!</source>
</trans-unit>
<trans-unit id="Tap to Connect" xml:space="preserve">
<source>Tap to Connect</source>
<target>Нажмите чтобы соединиться</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Tap to activate profile." xml:space="preserve">
@@ -4954,6 +5081,7 @@ It can happen because of some bug or when the connection is compromised.</source
</trans-unit>
<trans-unit id="This device name" xml:space="preserve">
<source>This device name</source>
<target>Имя этого устройства</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="This group has over %lld members, delivery receipts are not sent." xml:space="preserve">
@@ -4968,10 +5096,12 @@ It can happen because of some bug or when the connection is compromised.</source
</trans-unit>
<trans-unit id="This is your own SimpleX address!" xml:space="preserve">
<source>This is your own SimpleX address!</source>
<target>Это ваш собственный адрес SimpleX!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="This is your own one-time link!" xml:space="preserve">
<source>This is your own one-time link!</source>
<target>Это ваша собственная одноразовая ссылка!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="This setting applies to messages in your current chat profile **%@**." xml:space="preserve">
@@ -4989,6 +5119,11 @@ It can happen because of some bug or when the connection is compromised.</source
<target>Чтобы соединиться с Вами, Ваш контакт может отсканировать QR-код или использовать ссылку в приложении.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To hide unwanted messages." xml:space="preserve">
<source>To hide unwanted messages.</source>
<target>Чтобы скрыть нежелательные сообщения.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To make a new connection" xml:space="preserve">
<source>To make a new connection</source>
<target>Чтобы соединиться</target>
@@ -5073,14 +5208,17 @@ You will be prompted to complete authentication before this feature is enabled.<
</trans-unit>
<trans-unit id="Unblock" xml:space="preserve">
<source>Unblock</source>
<target>Разблокировать</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unblock member" xml:space="preserve">
<source>Unblock member</source>
<target>Разблокировать члена группы</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unblock member?" xml:space="preserve">
<source>Unblock member?</source>
<target>Разблокировать члена группы?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unexpected error: %@" xml:space="preserve">
@@ -5147,10 +5285,12 @@ To connect, please ask your contact to create another connection link and check
</trans-unit>
<trans-unit id="Unlink" xml:space="preserve">
<source>Unlink</source>
<target>Забыть</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unlink desktop?" xml:space="preserve">
<source>Unlink desktop?</source>
<target>Забыть компьютер?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unlock" xml:space="preserve">
@@ -5245,6 +5385,7 @@ To connect, please ask your contact to create another connection link and check
</trans-unit>
<trans-unit id="Use from desktop" xml:space="preserve">
<source>Use from desktop</source>
<target>Использовать с компьютера</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Use iOS call interface" xml:space="preserve">
@@ -5279,10 +5420,12 @@ To connect, please ask your contact to create another connection link and check
</trans-unit>
<trans-unit id="Verify code with desktop" xml:space="preserve">
<source>Verify code with desktop</source>
<target>Сверьте код с компьютером</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Verify connection" xml:space="preserve">
<source>Verify connection</source>
<target>Проверить соединение</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Verify connection security" xml:space="preserve">
@@ -5292,6 +5435,7 @@ To connect, please ask your contact to create another connection link and check
</trans-unit>
<trans-unit id="Verify connections" xml:space="preserve">
<source>Verify connections</source>
<target>Проверять соединения</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Verify security code" xml:space="preserve">
@@ -5304,6 +5448,11 @@ To connect, please ask your contact to create another connection link and check
<target>В браузере</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Via secure quantum resistant protocol." xml:space="preserve">
<source>Via secure quantum resistant protocol.</source>
<target>Через безопасный квантово-устойчивый протокол.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Video call" xml:space="preserve">
<source>Video call</source>
<target>Видеозвонок</target>
@@ -5354,6 +5503,11 @@ To connect, please ask your contact to create another connection link and check
<target>Голосовое сообщение…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for desktop..." xml:space="preserve">
<source>Waiting for desktop...</source>
<target>Ожидается подключение компьютера...</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for file" xml:space="preserve">
<source>Waiting for file</source>
<target>Ожидается прием файла</target>
@@ -5456,31 +5610,39 @@ To connect, please ask your contact to create another connection link and check
</trans-unit>
<trans-unit id="You are already connecting to %@." xml:space="preserve">
<source>You are already connecting to %@.</source>
<target>Вы уже соединяетесь с %@.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You are already connecting via this one-time link!" xml:space="preserve">
<source>You are already connecting via this one-time link!</source>
<target>Вы уже соединяетесь по этой одноразовой ссылке!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You are already in group %@." xml:space="preserve">
<source>You are already in group %@.</source>
<target>Вы уже состоите в группе %@.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You are already joining the group %@." xml:space="preserve">
<source>You are already joining the group %@.</source>
<target>Вы уже вступаете в группу %@.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You are already joining the group via this link!" xml:space="preserve">
<source>You are already joining the group via this link!</source>
<target>Вы уже вступаете в группу по этой ссылке!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You are already joining the group via this link." xml:space="preserve">
<source>You are already joining the group via this link.</source>
<target>Вы уже вступаете в группу по этой ссылке.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You are already joining the group!&#10;Repeat join request?" xml:space="preserve">
<source>You are already joining the group!
Repeat join request?</source>
<target>Вы уже вступаете в группу!
Повторить запрос на вступление?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You are connected to the server used to receive messages from this contact." xml:space="preserve">
@@ -5580,11 +5742,14 @@ Repeat join request?</source>
</trans-unit>
<trans-unit id="You have already requested connection via this address!" xml:space="preserve">
<source>You have already requested connection via this address!</source>
<target>Вы уже запросили соединение через этот адрес!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You have already requested connection!&#10;Repeat connection request?" xml:space="preserve">
<source>You have already requested connection!
Repeat connection request?</source>
<target>Вы уже запросили соединение!
Повторить запрос?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You have no chats" xml:space="preserve">
@@ -5639,6 +5804,7 @@ Repeat connection request?</source>
</trans-unit>
<trans-unit id="You will be connected when group link host's device is online, please wait or check later!" xml:space="preserve">
<source>You will be connected when group link host's device is online, please wait or check later!</source>
<target>Соединение будет установлено, когда владелец ссылки группы будет онлайн. Пожалуйста, подождите или проверьте позже!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You will be connected when your connection request is accepted, please wait or check later!" xml:space="preserve">
@@ -5658,6 +5824,7 @@ Repeat connection request?</source>
</trans-unit>
<trans-unit id="You will connect to all group members." xml:space="preserve">
<source>You will connect to all group members.</source>
<target>Вы соединитесь со всеми членами группы.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You will still receive calls and notifications from muted profiles when they are active." xml:space="preserve">
@@ -5781,6 +5948,7 @@ You can change it in Settings.</source>
</trans-unit>
<trans-unit id="Your profile" xml:space="preserve">
<source>Your profile</source>
<target>Ваш профиль</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your profile **%@** will be shared." xml:space="preserve">
@@ -5877,6 +6045,7 @@ SimpleX серверы не могут получить доступ к Ваше
</trans-unit>
<trans-unit id="and %lld other events" xml:space="preserve">
<source>and %lld other events</source>
<target>и %lld других событий</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="audio call (not e2e encrypted)" xml:space="preserve">
@@ -5884,6 +6053,11 @@ SimpleX серверы не могут получить доступ к Ваше
<target>аудиозвонок (не e2e зашифрованный)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="author" xml:space="preserve">
<source>author</source>
<target>автор</target>
<note>member role</note>
</trans-unit>
<trans-unit id="bad message ID" xml:space="preserve">
<source>bad message ID</source>
<target>ошибка ID сообщения</target>
@@ -5896,6 +6070,7 @@ SimpleX серверы не могут получить доступ к Ваше
</trans-unit>
<trans-unit id="blocked" xml:space="preserve">
<source>blocked</source>
<target>заблокировано</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="bold" xml:space="preserve">
@@ -5970,6 +6145,7 @@ SimpleX серверы не могут получить доступ к Ваше
</trans-unit>
<trans-unit id="connected directly" xml:space="preserve">
<source>connected directly</source>
<target>соединен(а) напрямую</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="connecting" xml:space="preserve">
@@ -6069,6 +6245,7 @@ SimpleX серверы не могут получить доступ к Ваше
</trans-unit>
<trans-unit id="deleted contact" xml:space="preserve">
<source>deleted contact</source>
<target>удалил(а) контакт</target>
<note>rcv direct event chat item</note>
</trans-unit>
<trans-unit id="deleted group" xml:space="preserve">
@@ -6435,6 +6612,7 @@ SimpleX серверы не могут получить доступ к Ваше
</trans-unit>
<trans-unit id="send direct message" xml:space="preserve">
<source>send direct message</source>
<target>отправьте сообщение</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="starting…" xml:space="preserve">
@@ -6464,6 +6642,7 @@ SimpleX серверы не могут получить доступ к Ваше
</trans-unit>
<trans-unit id="v%@" xml:space="preserve">
<source>v%@</source>
<target>v%@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="v%@ (%@)" xml:space="preserve">
@@ -6605,6 +6784,7 @@ SimpleX серверы не могут получить доступ к Ваше
</trans-unit>
<trans-unit id="NSLocalNetworkUsageDescription" xml:space="preserve">
<source>SimpleX uses local network access to allow using user chat profile via desktop app on the same network.</source>
<target>SimpleX использует доступ к локальной сети, чтобы разрешить использование профиля чата через компьютер в той же сети.</target>
<note>Privacy - Local Network Usage Description</note>
</trans-unit>
<trans-unit id="NSMicrophoneUsageDescription" xml:space="preserve">
@@ -377,6 +377,12 @@
- และอื่น ๆ!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- optionally notify deleted contacts.&#10;- profile names with spaces.&#10;- and more!" xml:space="preserve">
<source>- optionally notify deleted contacts.
- profile names with spaces.
- and more!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- voice messages up to 5 minutes.&#10;- custom time to disappear.&#10;- editing history." xml:space="preserve">
<source>- voice messages up to 5 minutes.
- custom time to disappear.
@@ -868,6 +874,10 @@
<target>แฮชข้อความไม่ดี</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better groups" xml:space="preserve">
<source>Better groups</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better messages" xml:space="preserve">
<source>Better messages</source>
<target>ข้อความที่ดีขึ้น</target>
@@ -877,6 +887,10 @@
<source>Block</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block group members" xml:space="preserve">
<source>Block group members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block member" xml:space="preserve">
<source>Block member</source>
<note>No comment provided by engineer.</note>
@@ -1145,6 +1159,10 @@
<target>เชื่อมต่อ</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Connect automatically" xml:space="preserve">
<source>Connect automatically</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect incognito" xml:space="preserve">
<source>Connect incognito</source>
<note>No comment provided by engineer.</note>
@@ -1314,6 +1332,10 @@ This is your own one-time link!</source>
<target>สร้างที่อยู่ SimpleX</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create a group using a random profile." xml:space="preserve">
<source>Create a group using a random profile.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create an address to let people connect with you." xml:space="preserve">
<source>Create an address to let people connect with you.</source>
<target>สร้างที่อยู่เพื่อให้ผู้อื่นเชื่อมต่อกับคุณ</target>
@@ -1826,8 +1848,8 @@ This cannot be undone!</source>
<source>Discover and join groups</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Discover on network" xml:space="preserve">
<source>Discover on network</source>
<trans-unit id="Discover via local network" xml:space="preserve">
<source>Discover via local network</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do NOT use SimpleX for emergency calls." xml:space="preserve">
@@ -2359,6 +2381,10 @@ This cannot be undone!</source>
<target>รวดเร็วและไม่ต้องรอจนกว่าผู้ส่งจะออนไลน์!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Faster joining and more reliable messages." xml:space="preserve">
<source>Faster joining and more reliable messages.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Favorite" xml:space="preserve">
<source>Favorite</source>
<target>ที่ชอบ</target>
@@ -2454,6 +2480,10 @@ This cannot be undone!</source>
<target>สำหรับคอนโซล</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Found desktop" xml:space="preserve">
<source>Found desktop</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="French interface" xml:space="preserve">
<source>French interface</source>
<target>อินเทอร์เฟซภาษาฝรั่งเศส</target>
@@ -2776,6 +2806,10 @@ This cannot be undone!</source>
<target>ไม่ระบุตัวตน</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito groups" xml:space="preserve">
<source>Incognito groups</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito mode" xml:space="preserve">
<source>Incognito mode</source>
<target>โหมดไม่ระบุตัวตน</target>
@@ -3037,6 +3071,10 @@ This is your link for group %@!</source>
<target>ข้อจำกัด</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Link mobile and desktop apps! 🔗" xml:space="preserve">
<source>Link mobile and desktop apps! 🔗</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Linked desktop options" xml:space="preserve">
<source>Linked desktop options</source>
<note>No comment provided by engineer.</note>
@@ -3391,6 +3429,10 @@ This is your link for group %@!</source>
<target>ไม่มีไฟล์ที่ได้รับหรือส่ง</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Not compatible!" xml:space="preserve">
<source>Not compatible!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Notifications" xml:space="preserve">
<source>Notifications</source>
<target>การแจ้งเตือน</target>
@@ -4954,6 +4996,10 @@ It can happen because of some bug or when the connection is compromised.</source
<target>เพื่อการเชื่อมต่อ ผู้ติดต่อของคุณสามารถสแกนคิวอาร์โค้ดหรือใช้ลิงก์ในแอป</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To hide unwanted messages." xml:space="preserve">
<source>To hide unwanted messages.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To make a new connection" xml:space="preserve">
<source>To make a new connection</source>
<target>เพื่อสร้างการเชื่อมต่อใหม่</target>
@@ -5266,6 +5312,10 @@ To connect, please ask your contact to create another connection link and check
<target>ผ่านเบราว์เซอร์</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Via secure quantum resistant protocol." xml:space="preserve">
<source>Via secure quantum resistant protocol.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Video call" xml:space="preserve">
<source>Video call</source>
<target>การสนทนาทางวิดีโอ</target>
@@ -5316,6 +5366,10 @@ To connect, please ask your contact to create another connection link and check
<target>ข้อความเสียง…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for desktop..." xml:space="preserve">
<source>Waiting for desktop...</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for file" xml:space="preserve">
<source>Waiting for file</source>
<target>กำลังรอไฟล์</target>
@@ -5844,6 +5898,10 @@ SimpleX servers cannot see your profile.</source>
<target>การโทรด้วยเสียง (ไม่ได้ encrypt จากต้นจนจบ)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="author" xml:space="preserve">
<source>author</source>
<note>member role</note>
</trans-unit>
<trans-unit id="bad message ID" xml:space="preserve">
<source>bad message ID</source>
<target>ID ข้อความที่ไม่ดี</target>
@@ -382,6 +382,12 @@
- і багато іншого!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- optionally notify deleted contacts.&#10;- profile names with spaces.&#10;- and more!" xml:space="preserve">
<source>- optionally notify deleted contacts.
- profile names with spaces.
- and more!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- voice messages up to 5 minutes.&#10;- custom time to disappear.&#10;- editing history." xml:space="preserve">
<source>- voice messages up to 5 minutes.
- custom time to disappear.
@@ -875,6 +881,10 @@
<target>Поганий хеш повідомлення</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better groups" xml:space="preserve">
<source>Better groups</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better messages" xml:space="preserve">
<source>Better messages</source>
<target>Кращі повідомлення</target>
@@ -884,6 +894,10 @@
<source>Block</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block group members" xml:space="preserve">
<source>Block group members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block member" xml:space="preserve">
<source>Block member</source>
<note>No comment provided by engineer.</note>
@@ -1152,6 +1166,10 @@
<target>Підключіться</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Connect automatically" xml:space="preserve">
<source>Connect automatically</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect incognito" xml:space="preserve">
<source>Connect incognito</source>
<target>Підключайтеся інкогніто</target>
@@ -1323,6 +1341,10 @@ This is your own one-time link!</source>
<target>Створіть адресу SimpleX</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create a group using a random profile." xml:space="preserve">
<source>Create a group using a random profile.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create an address to let people connect with you." xml:space="preserve">
<source>Create an address to let people connect with you.</source>
<target>Створіть адресу, щоб люди могли з вами зв'язатися.</target>
@@ -1836,8 +1858,8 @@ This cannot be undone!</source>
<source>Discover and join groups</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Discover on network" xml:space="preserve">
<source>Discover on network</source>
<trans-unit id="Discover via local network" xml:space="preserve">
<source>Discover via local network</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do NOT use SimpleX for emergency calls." xml:space="preserve">
@@ -2369,6 +2391,10 @@ This cannot be undone!</source>
<target>Швидко і без очікування, поки відправник буде онлайн!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Faster joining and more reliable messages." xml:space="preserve">
<source>Faster joining and more reliable messages.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Favorite" xml:space="preserve">
<source>Favorite</source>
<target>Улюблений</target>
@@ -2464,6 +2490,10 @@ This cannot be undone!</source>
<target>Для консолі</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Found desktop" xml:space="preserve">
<source>Found desktop</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="French interface" xml:space="preserve">
<source>French interface</source>
<target>Французький інтерфейс</target>
@@ -2786,6 +2816,10 @@ This cannot be undone!</source>
<target>Інкогніто</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito groups" xml:space="preserve">
<source>Incognito groups</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito mode" xml:space="preserve">
<source>Incognito mode</source>
<target>Режим інкогніто</target>
@@ -3049,6 +3083,10 @@ This is your link for group %@!</source>
<target>Обмеження</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Link mobile and desktop apps! 🔗" xml:space="preserve">
<source>Link mobile and desktop apps! 🔗</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Linked desktop options" xml:space="preserve">
<source>Linked desktop options</source>
<note>No comment provided by engineer.</note>
@@ -3405,6 +3443,10 @@ This is your link for group %@!</source>
<target>Немає отриманих або відправлених файлів</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Not compatible!" xml:space="preserve">
<source>Not compatible!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Notifications" xml:space="preserve">
<source>Notifications</source>
<target>Сповіщення</target>
@@ -4976,6 +5018,10 @@ It can happen because of some bug or when the connection is compromised.</source
<target>Щоб підключитися, ваш контакт може відсканувати QR-код або скористатися посиланням у додатку.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To hide unwanted messages." xml:space="preserve">
<source>To hide unwanted messages.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To make a new connection" xml:space="preserve">
<source>To make a new connection</source>
<target>Щоб створити нове з'єднання</target>
@@ -5290,6 +5336,10 @@ To connect, please ask your contact to create another connection link and check
<target>Через браузер</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Via secure quantum resistant protocol." xml:space="preserve">
<source>Via secure quantum resistant protocol.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Video call" xml:space="preserve">
<source>Video call</source>
<target>Відеодзвінок</target>
@@ -5340,6 +5390,10 @@ To connect, please ask your contact to create another connection link and check
<target>Голосове повідомлення…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for desktop..." xml:space="preserve">
<source>Waiting for desktop...</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for file" xml:space="preserve">
<source>Waiting for file</source>
<target>Очікування файлу</target>
@@ -5870,6 +5924,10 @@ SimpleX servers cannot see your profile.</source>
<target>аудіовиклик (без шифрування e2e)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="author" xml:space="preserve">
<source>author</source>
<note>member role</note>
</trans-unit>
<trans-unit id="bad message ID" xml:space="preserve">
<source>bad message ID</source>
<target>невірний ідентифікатор повідомлення</target>
@@ -386,6 +386,12 @@
- 以及更多!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- optionally notify deleted contacts.&#10;- profile names with spaces.&#10;- and more!" xml:space="preserve">
<source>- optionally notify deleted contacts.
- profile names with spaces.
- and more!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- voice messages up to 5 minutes.&#10;- custom time to disappear.&#10;- editing history." xml:space="preserve">
<source>- voice messages up to 5 minutes.
- custom time to disappear.
@@ -880,6 +886,10 @@
<target>错误消息散列</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better groups" xml:space="preserve">
<source>Better groups</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Better messages" xml:space="preserve">
<source>Better messages</source>
<target>更好的消息</target>
@@ -889,6 +899,10 @@
<source>Block</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block group members" xml:space="preserve">
<source>Block group members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block member" xml:space="preserve">
<source>Block member</source>
<note>No comment provided by engineer.</note>
@@ -1158,6 +1172,10 @@
<target>连接</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Connect automatically" xml:space="preserve">
<source>Connect automatically</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect incognito" xml:space="preserve">
<source>Connect incognito</source>
<target>在隐身状态下连接</target>
@@ -1329,6 +1347,10 @@ This is your own one-time link!</source>
<target>创建 SimpleX 地址</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create a group using a random profile." xml:space="preserve">
<source>Create a group using a random profile.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create an address to let people connect with you." xml:space="preserve">
<source>Create an address to let people connect with you.</source>
<target>创建一个地址,让人们与您联系。</target>
@@ -1844,8 +1866,8 @@ This cannot be undone!</source>
<target>发现和加入群组</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Discover on network" xml:space="preserve">
<source>Discover on network</source>
<trans-unit id="Discover via local network" xml:space="preserve">
<source>Discover via local network</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do NOT use SimpleX for emergency calls." xml:space="preserve">
@@ -2382,6 +2404,10 @@ This cannot be undone!</source>
<target>快速且无需等待发件人在线!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Faster joining and more reliable messages." xml:space="preserve">
<source>Faster joining and more reliable messages.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Favorite" xml:space="preserve">
<source>Favorite</source>
<target>最喜欢</target>
@@ -2477,6 +2503,10 @@ This cannot be undone!</source>
<target>用于控制台</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Found desktop" xml:space="preserve">
<source>Found desktop</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="French interface" xml:space="preserve">
<source>French interface</source>
<target>法语界面</target>
@@ -2799,6 +2829,10 @@ This cannot be undone!</source>
<target>隐身聊天</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito groups" xml:space="preserve">
<source>Incognito groups</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Incognito mode" xml:space="preserve">
<source>Incognito mode</source>
<target>隐身模式</target>
@@ -3062,6 +3096,10 @@ This is your link for group %@!</source>
<target>限制</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Link mobile and desktop apps! 🔗" xml:space="preserve">
<source>Link mobile and desktop apps! 🔗</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Linked desktop options" xml:space="preserve">
<source>Linked desktop options</source>
<note>No comment provided by engineer.</note>
@@ -3419,6 +3457,10 @@ This is your link for group %@!</source>
<target>未收到或发送文件</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Not compatible!" xml:space="preserve">
<source>Not compatible!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Notifications" xml:space="preserve">
<source>Notifications</source>
<target>通知</target>
@@ -4993,6 +5035,10 @@ It can happen because of some bug or when the connection is compromised.</source
<target>您的联系人可以扫描二维码或使用应用程序中的链接来建立连接。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To hide unwanted messages." xml:space="preserve">
<source>To hide unwanted messages.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To make a new connection" xml:space="preserve">
<source>To make a new connection</source>
<target>建立新连接</target>
@@ -5308,6 +5354,10 @@ To connect, please ask your contact to create another connection link and check
<target>通过浏览器</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Via secure quantum resistant protocol." xml:space="preserve">
<source>Via secure quantum resistant protocol.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Video call" xml:space="preserve">
<source>Video call</source>
<target>视频通话</target>
@@ -5358,6 +5408,10 @@ To connect, please ask your contact to create another connection link and check
<target>语音消息……</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for desktop..." xml:space="preserve">
<source>Waiting for desktop...</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Waiting for file" xml:space="preserve">
<source>Waiting for file</source>
<target>等待文件中</target>
@@ -5888,6 +5942,10 @@ SimpleX 服务器无法看到您的资料。</target>
<target>语音通话(非端到端加密)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="author" xml:space="preserve">
<source>author</source>
<note>member role</note>
</trans-unit>
<trans-unit id="bad message ID" xml:space="preserve">
<source>bad message ID</source>
<target>错误消息 ID</target>
@@ -0,0 +1,64 @@
//
// ConcurrentQueue.swift
// SimpleX NSE
//
// Created by Evgeny on 08/12/2023.
// Copyright © 2023 SimpleX Chat. All rights reserved.
//
import Foundation
struct DequeueElement<T> {
var elementId: UUID?
var task: Task<T?, Never>
}
class ConcurrentQueue<T> {
private var queue: [T] = []
private var queueLock = DispatchQueue(label: "chat.simplex.app.SimpleX-NSE.concurrent-queue.lock.\(UUID())")
private var continuations = [(elementId: UUID, continuation: CheckedContinuation<T?, Never>)]()
func enqueue(_ el: T) {
resumeContinuation(el) { self.queue.append(el) }
}
func frontEnqueue(_ el: T) {
resumeContinuation(el) { self.queue.insert(el, at: 0) }
}
private func resumeContinuation(_ el: T, add: @escaping () -> Void) {
queueLock.sync {
if let (_, cont) = continuations.first {
continuations.remove(at: 0)
cont.resume(returning: el)
} else {
add()
}
}
}
func dequeue() -> DequeueElement<T> {
queueLock.sync {
if queue.isEmpty {
let elementId = UUID()
let task = Task {
await withCheckedContinuation { cont in
continuations.append((elementId, cont))
}
}
return DequeueElement(elementId: elementId, task: task)
} else {
let el = queue.remove(at: 0)
return DequeueElement(task: Task { el })
}
}
}
func cancelDequeue(_ elementId: UUID) {
queueLock.sync {
let cancelled = continuations.filter { $0.elementId == elementId }
continuations.removeAll { $0.elementId == elementId }
cancelled.forEach { $0.continuation.resume(returning: nil) }
}
}
}
+421 -87
View File
@@ -14,91 +14,231 @@ import SimpleXChat
let logger = Logger()
let suspendingDelay: UInt64 = 2_000_000_000
let appSuspendingDelay: UInt64 = 2_500_000_000
typealias NtfStream = AsyncStream<NSENotification>
let nseSuspendDelay: TimeInterval = 2
let nseSuspendTimeout: Int = 5
typealias NtfStream = ConcurrentQueue<NSENotification>
// Notifications are delivered via concurrent queues, as they are all received from chat controller in a single loop that
// writes to ConcurrentQueue and when notification is processed, the instance of Notification service extension reads from the queue.
// One queue per connection (entity) is used.
// The concurrent queues allow read cancellation, to ensure that notifications are not lost in case the current thread completes
// before expected notification is read (multiple notifications can be expected, because one notification can be delivered for several messages).
actor PendingNtfs {
static let shared = PendingNtfs()
private var ntfStreams: [String: NtfStream] = [:]
private var ntfConts: [String: NtfStream.Continuation] = [:]
func createStream(_ id: String) {
logger.debug("PendingNtfs.createStream: \(id, privacy: .public)")
if ntfStreams.index(forKey: id) == nil {
ntfStreams[id] = AsyncStream { cont in
ntfConts[id] = cont
logger.debug("PendingNtfs.createStream: store continuation")
}
func createStream(_ id: String) async {
logger.debug("NotificationService PendingNtfs.createStream: \(id, privacy: .public)")
if ntfStreams[id] == nil {
ntfStreams[id] = ConcurrentQueue()
logger.debug("NotificationService PendingNtfs.createStream: created ConcurrentQueue")
}
}
func readStream(_ id: String, for nse: NotificationService, msgCount: Int = 1, showNotifications: Bool) async {
logger.debug("PendingNtfs.readStream: \(id, privacy: .public) \(msgCount, privacy: .public)")
func readStream(_ id: String, for nse: NotificationService, ntfInfo: NtfMessages) async {
logger.debug("NotificationService PendingNtfs.readStream: \(id, privacy: .public) \(ntfInfo.ntfMessages.count, privacy: .public)")
if !ntfInfo.user.showNotifications {
nse.setBestAttemptNtf(.empty)
}
if let s = ntfStreams[id] {
logger.debug("PendingNtfs.readStream: has stream")
var rcvCount = max(1, msgCount)
for await ntf in s {
nse.setBestAttemptNtf(showNotifications ? ntf : .empty)
rcvCount -= 1
if rcvCount == 0 || ntf.categoryIdentifier == ntfCategoryCallInvitation { break }
logger.debug("NotificationService PendingNtfs.readStream: has stream")
var expected = Set(ntfInfo.ntfMessages.map { $0.msgId })
logger.debug("NotificationService PendingNtfs.readStream: expecting: \(expected, privacy: .public)")
var readCancelled = false
var dequeued: DequeueElement<NSENotification>?
nse.cancelRead = {
readCancelled = true
if let elementId = dequeued?.elementId {
s.cancelDequeue(elementId)
}
}
logger.debug("PendingNtfs.readStream: exiting")
while !readCancelled {
dequeued = s.dequeue()
if let ntf = await dequeued?.task.value {
if readCancelled {
logger.debug("NotificationService PendingNtfs.readStream: read cancelled, put ntf to queue front")
s.frontEnqueue(ntf)
break
} else if case let .msgInfo(info) = ntf {
let found = expected.remove(info.msgId)
if found != nil {
logger.debug("NotificationService PendingNtfs.readStream: msgInfo, last: \(expected.isEmpty, privacy: .public)")
if expected.isEmpty { break }
} else if let msgTs = ntfInfo.msgTs, info.msgTs > msgTs {
logger.debug("NotificationService PendingNtfs.readStream: unexpected msgInfo")
s.frontEnqueue(ntf)
break
}
} else if ntfInfo.user.showNotifications {
logger.debug("NotificationService PendingNtfs.readStream: setting best attempt")
nse.setBestAttemptNtf(ntf)
if ntf.isCallInvitation { break }
}
} else {
break
}
}
nse.cancelRead = nil
logger.debug("NotificationService PendingNtfs.readStream: exiting")
}
}
func writeStream(_ id: String, _ ntf: NSENotification) {
logger.debug("PendingNtfs.writeStream: \(id, privacy: .public)")
if let cont = ntfConts[id] {
logger.debug("PendingNtfs.writeStream: writing ntf")
cont.yield(ntf)
func writeStream(_ id: String, _ ntf: NSENotification) async {
logger.debug("NotificationService PendingNtfs.writeStream: \(id, privacy: .public)")
if let s = ntfStreams[id] {
logger.debug("NotificationService PendingNtfs.writeStream: writing ntf")
s.enqueue(ntf)
}
}
}
// The current implementation assumes concurrent notification delivery and uses semaphores
// to process only one notification per connection (entity) at a time.
class NtfStreamSemaphores {
static let shared = NtfStreamSemaphores()
private static let queue = DispatchQueue(label: "chat.simplex.app.SimpleX-NSE.notification-semaphores.lock")
private var semaphores: [String: DispatchSemaphore] = [:]
func waitForStream(_ id: String) {
streamSemaphore(id, value: 0)?.wait()
}
func signalStreamReady(_ id: String) {
streamSemaphore(id, value: 1)?.signal()
}
// this function returns nil if semaphore is just created, so passed value shoud be coordinated with the desired end value of the semaphore
private func streamSemaphore(_ id: String, value: Int) -> DispatchSemaphore? {
NtfStreamSemaphores.queue.sync {
if let s = semaphores[id] {
return s
} else {
semaphores[id] = DispatchSemaphore(value: value)
return nil
}
}
}
}
enum NSENotification {
case nse(notification: UNMutableNotificationContent)
case callkit(invitation: RcvCallInvitation)
case nse(UNMutableNotificationContent)
case callkit(RcvCallInvitation)
case empty
case msgInfo(NtfMsgInfo)
var categoryIdentifier: String? {
var isCallInvitation: Bool {
switch self {
case let .nse(ntf): return ntf.categoryIdentifier
case .callkit: return ntfCategoryCallInvitation
case .empty: return nil
case let .nse(ntf): ntf.categoryIdentifier == ntfCategoryCallInvitation
case .callkit: true
case .empty: false
case .msgInfo: false
}
}
}
// Once the last thread in the process completes processing chat controller is suspended, and the database is closed, to avoid
// background crashes and contention for database with the application (both UI and background fetch triggered either on schedule
// or when background notification is received.
class NSEThreads {
static let shared = NSEThreads()
private static let queue = DispatchQueue(label: "chat.simplex.app.SimpleX-NSE.notification-threads.lock")
private var allThreads: Set<UUID> = []
private var activeThreads: Set<UUID> = []
func newThread() -> UUID {
NSEThreads.queue.sync {
let (_, t) = allThreads.insert(UUID())
return t
}
}
func startThread(_ t: UUID) {
NSEThreads.queue.sync {
if allThreads.contains(t) {
_ = activeThreads.insert(t)
} else {
logger.warning("NotificationService startThread: thread \(t) was removed before it started")
}
}
}
func endThread(_ t: UUID) -> Bool {
NSEThreads.queue.sync {
let tActive = activeThreads.remove(t)
let t = allThreads.remove(t)
if tActive != nil && activeThreads.isEmpty {
return true
}
if t != nil && allThreads.isEmpty {
NSEChatState.shared.set(.suspended)
}
return false
}
}
var noThreads: Bool {
allThreads.isEmpty
}
}
// Notification service extension creates a new instance of the class and calls didReceive for each notification.
// Each didReceive is called in its own thread, but multiple calls can be made in one process, and, empirically, there is never
// more than one process of notification service extension exists at a time.
// Soon after notification service delivers the last notification it is either suspended or terminated.
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptNtf: NSENotification?
var badgeCount: Int = 0
// thread is added to allThreads here - if thread did not start chat,
// chat does not need to be suspended but NSE state still needs to be set to "suspended".
var threadId: UUID? = NSEThreads.shared.newThread()
var receiveEntityId: String?
var cancelRead: (() -> Void)?
var appSubscriber: AppSubscriber?
var returnedSuspension = false
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
logger.debug("DEBUGGING: NotificationService.didReceive")
if let ntf = request.content.mutableCopy() as? UNMutableNotificationContent {
setBestAttemptNtf(ntf)
}
let ntf = if let ntf_ = request.content.mutableCopy() as? UNMutableNotificationContent { ntf_ } else { UNMutableNotificationContent() }
setBestAttemptNtf(ntf)
self.contentHandler = contentHandler
registerGroupDefaults()
let appState = appStateGroupDefault.get()
logger.debug("NotificationService: app is \(appState.rawValue, privacy: .public)")
switch appState {
case .stopped:
setBadgeCount()
setBestAttemptNtf(createAppStoppedNtf())
deliverBestAttemptNtf()
case .suspended:
logger.debug("NotificationService: app is suspended")
setBadgeCount()
receiveNtfMessages(request, contentHandler)
case .suspending:
logger.debug("NotificationService: app is suspending")
setBadgeCount()
Task {
var state = appState
for _ in 1...5 {
_ = try await Task.sleep(nanoseconds: suspendingDelay)
state = appStateGroupDefault.get()
if state == .suspended || state != .suspending { break }
let state: AppState = await withCheckedContinuation { cont in
appSubscriber = appStateSubscriber { s in
if s == .suspended { appSuspension(s) }
}
DispatchQueue.global().asyncAfter(deadline: .now() + Double(appSuspendTimeout) + 1) {
logger.debug("NotificationService: appSuspension timeout")
appSuspension(appStateGroupDefault.get())
}
@Sendable
func appSuspension(_ s: AppState) {
if !self.returnedSuspension {
self.returnedSuspension = true
self.appSubscriber = nil // this disposes of appStateSubscriber
cont.resume(returning: s)
}
}
}
logger.debug("NotificationService: app state is \(state.rawValue, privacy: .public)")
logger.debug("NotificationService: app state is now \(state.rawValue, privacy: .public)")
if state.inactive {
receiveNtfMessages(request, contentHandler)
} else {
@@ -106,7 +246,6 @@ class NotificationService: UNNotificationServiceExtension {
}
}
default:
logger.debug("NotificationService: app state is \(appState.rawValue, privacy: .public)")
deliverBestAttemptNtf()
}
}
@@ -121,27 +260,35 @@ class NotificationService: UNNotificationServiceExtension {
if let ntfData = userInfo["notificationData"] as? [AnyHashable : Any],
let nonce = ntfData["nonce"] as? String,
let encNtfInfo = ntfData["message"] as? String,
let dbStatus = startChat() {
// check it here again
appStateGroupDefault.get().inactive {
// thread is added to activeThreads tracking set here - if thread started chat it needs to be suspended
if let t = threadId { NSEThreads.shared.startThread(t) }
let dbStatus = startChat()
if case .ok = dbStatus,
let ntfMsgInfo = apiGetNtfMessage(nonce: nonce, encNtfInfo: encNtfInfo) {
logger.debug("NotificationService: receiveNtfMessages: apiGetNtfMessage \(String(describing: ntfMsgInfo), privacy: .public)")
if let connEntity = ntfMsgInfo.connEntity {
let ntfInfo = apiGetNtfMessage(nonce: nonce, encNtfInfo: encNtfInfo) {
logger.debug("NotificationService: receiveNtfMessages: apiGetNtfMessage \(String(describing: ntfInfo.ntfMessages.count), privacy: .public)")
if let connEntity = ntfInfo.connEntity_ {
setBestAttemptNtf(
ntfMsgInfo.ntfsEnabled
? .nse(notification: createConnectionEventNtf(ntfMsgInfo.user, connEntity))
ntfInfo.ntfsEnabled
? .nse(createConnectionEventNtf(ntfInfo.user, connEntity))
: .empty
)
if let id = connEntity.id {
Task {
logger.debug("NotificationService: receiveNtfMessages: in Task, connEntity id \(id, privacy: .public)")
await PendingNtfs.shared.createStream(id)
await PendingNtfs.shared.readStream(id, for: self, msgCount: ntfMsgInfo.ntfMessages.count, showNotifications: ntfMsgInfo.user.showNotifications)
deliverBestAttemptNtf()
receiveEntityId = id
NtfStreamSemaphores.shared.waitForStream(id)
if receiveEntityId != nil {
Task {
logger.debug("NotificationService: receiveNtfMessages: in Task, connEntity id \(id, privacy: .public)")
await PendingNtfs.shared.createStream(id)
await PendingNtfs.shared.readStream(id, for: self, ntfInfo: ntfInfo)
deliverBestAttemptNtf()
}
}
return
}
}
return
} else {
} else if let dbStatus = dbStatus {
setBestAttemptNtf(createErrorNtf(dbStatus))
}
}
@@ -159,14 +306,14 @@ class NotificationService: UNNotificationServiceExtension {
}
func setBestAttemptNtf(_ ntf: UNMutableNotificationContent) {
setBestAttemptNtf(.nse(notification: ntf))
setBestAttemptNtf(.nse(ntf))
}
func setBestAttemptNtf(_ ntf: NSENotification) {
logger.debug("NotificationService.setBestAttemptNtf")
if case let .nse(notification) = ntf {
notification.badge = badgeCount as NSNumber
bestAttemptNtf = .nse(notification: notification)
bestAttemptNtf = .nse(notification)
} else {
bestAttemptNtf = ntf
}
@@ -174,9 +321,41 @@ class NotificationService: UNNotificationServiceExtension {
private func deliverBestAttemptNtf() {
logger.debug("NotificationService.deliverBestAttemptNtf")
if let cancel = cancelRead {
cancelRead = nil
cancel()
}
if let id = receiveEntityId {
receiveEntityId = nil
NtfStreamSemaphores.shared.signalStreamReady(id)
}
if let t = threadId {
threadId = nil
if NSEThreads.shared.endThread(t) {
logger.debug("NotificationService.deliverBestAttemptNtf: will suspend")
// suspension is delayed to allow chat core finalise any processing
// (e.g., send delivery receipts)
DispatchQueue.global().asyncAfter(deadline: .now() + nseSuspendDelay) {
if NSEThreads.shared.noThreads {
logger.debug("NotificationService.deliverBestAttemptNtf: suspending...")
suspendChat(nseSuspendTimeout)
}
}
}
}
if let handler = contentHandler, let ntf = bestAttemptNtf {
contentHandler = nil
bestAttemptNtf = nil
let deliver: (UNMutableNotificationContent?) -> Void = { ntf in
let useNtf = if let ntf = ntf {
appStateGroupDefault.get().running ? UNMutableNotificationContent() : ntf
} else {
UNMutableNotificationContent()
}
handler(useNtf)
}
switch ntf {
case let .nse(content): handler(content)
case let .nse(content): deliver(content)
case let .callkit(invitation):
CXProvider.reportNewIncomingVoIPPushPayload([
"displayName": invitation.contact.displayName,
@@ -184,66 +363,201 @@ class NotificationService: UNNotificationServiceExtension {
"media": invitation.callType.media.rawValue
]) { error in
if error == nil {
handler(UNMutableNotificationContent())
deliver(nil)
} else {
logger.debug("reportNewIncomingVoIPPushPayload success to CallController for \(invitation.contact.id)")
handler(createCallInvitationNtf(invitation))
logger.debug("NotificationService reportNewIncomingVoIPPushPayload success to CallController for \(invitation.contact.id)")
deliver(createCallInvitationNtf(invitation))
}
}
case .empty: handler(UNMutableNotificationContent())
case .empty: deliver(nil) // used to mute notifications that did not unsubscribe yet
case .msgInfo: deliver(nil) // unreachable, the best attempt is never set to msgInfo
}
bestAttemptNtf = nil
}
}
}
var chatStarted = false
var networkConfig: NetCfg = getNetCfg()
var xftpConfig: XFTPFileConfig? = getXFTPCfg()
// nseStateGroupDefault must not be used in NSE directly, only via this singleton
class NSEChatState {
static let shared = NSEChatState()
private var value_ = NSEState.created
var value: NSEState {
value_
}
func set(_ state: NSEState) {
nseStateGroupDefault.set(state)
sendNSEState(state)
value_ = state
}
init() {
// This is always set to .created state, as in case previous start of NSE crashed in .active state, it is stored correctly.
// Otherwise the app will be activating slower
set(.created)
}
}
var appSubscriber: AppSubscriber = appStateSubscriber { state in
logger.debug("NotificationService: appSubscriber")
if state.running && NSEChatState.shared.value.canSuspend {
logger.debug("NotificationService: appSubscriber app state \(state.rawValue), suspending")
suspendChat(nseSuspendTimeout)
}
}
func appStateSubscriber(onState: @escaping (AppState) -> Void) -> AppSubscriber {
appMessageSubscriber { msg in
if case let .state(state) = msg {
logger.debug("NotificationService: appStateSubscriber \(state.rawValue, privacy: .public)")
onState(state)
}
}
}
var receiverStarted = false
let startLock = DispatchSemaphore(value: 1)
let suspendLock = DispatchSemaphore(value: 1)
var networkConfig: NetCfg = getNetCfg()
let xftpConfig: XFTPFileConfig? = getXFTPCfg()
// startChat uses semaphore startLock to ensure that only one didReceive thread can start chat controller
// Subsequent calls to didReceive will be waiting on semaphore and won't start chat again, as it will be .active
func startChat() -> DBMigrationResult? {
logger.debug("NotificationService: startChat")
if case .active = NSEChatState.shared.value { return .ok }
startLock.wait()
defer { startLock.signal() }
return switch NSEChatState.shared.value {
case .created: doStartChat()
case .starting: .ok // it should never get to this branch, as it would be waiting for start on startLock
case .active: .ok
case .suspending: activateChat()
case .suspended: activateChat()
}
}
func doStartChat() -> DBMigrationResult? {
logger.debug("NotificationService: doStartChat")
hs_init(0, nil)
if chatStarted { return .ok }
let (_, dbStatus) = chatMigrateInit(confirmMigrations: defaultMigrationConfirmation())
let (_, dbStatus) = chatMigrateInit(confirmMigrations: defaultMigrationConfirmation(), backgroundMode: true)
if dbStatus != .ok {
resetChatCtrl()
NSEChatState.shared.set(.created)
return dbStatus
}
let state = NSEChatState.shared.value
NSEChatState.shared.set(.starting)
if let user = apiGetActiveUser() {
logger.debug("active user \(String(describing: user))")
logger.debug("NotificationService active user \(String(describing: user))")
do {
try setNetworkConfig(networkConfig)
try apiSetTempFolder(tempFolder: getTempFilesDirectory().path)
try apiSetFilesFolder(filesFolder: getAppFilesDirectory().path)
try setXFTPConfig(xftpConfig)
try apiSetEncryptLocalFiles(privacyEncryptLocalFilesGroupDefault.get())
let justStarted = try apiStartChat()
chatStarted = true
if justStarted {
chatLastStartGroupDefault.set(Date.now)
Task { await receiveMessages() }
// prevent suspension while starting chat
suspendLock.wait()
defer { suspendLock.signal() }
if NSEChatState.shared.value == .starting {
updateNetCfg()
let justStarted = try apiStartChat()
NSEChatState.shared.set(.active)
if justStarted {
chatLastStartGroupDefault.set(Date.now)
Task {
if !receiverStarted {
receiverStarted = true
await receiveMessages()
}
}
}
return .ok
}
return .ok
} catch {
logger.error("NotificationService startChat error: \(responseError(error), privacy: .public)")
}
} else {
logger.debug("no active user")
logger.debug("NotificationService: no active user")
}
if NSEChatState.shared.value == .starting { NSEChatState.shared.set(state) }
return nil
}
func activateChat() -> DBMigrationResult? {
logger.debug("NotificationService: activateChat")
let state = NSEChatState.shared.value
NSEChatState.shared.set(.active)
if apiActivateChat() {
logger.debug("NotificationService: activateChat: after apiActivateChat")
return .ok
} else {
NSEChatState.shared.set(state)
return nil
}
}
// suspendChat uses semaphore suspendLock to ensure that only one suspension can happen.
func suspendChat(_ timeout: Int) {
logger.debug("NotificationService: suspendChat")
let state = NSEChatState.shared.value
if !state.canSuspend {
logger.error("NotificationService suspendChat called, current state: \(state.rawValue, privacy: .public)")
} else {
suspendLock.wait()
defer { suspendLock.signal() }
NSEChatState.shared.set(.suspending)
if apiSuspendChat(timeoutMicroseconds: timeout * 1000000) {
logger.debug("NotificationService: suspendChat: after apiSuspendChat")
DispatchQueue.global().asyncAfter(deadline: .now() + Double(timeout) + 1, execute: chatSuspended)
} else {
NSEChatState.shared.set(state)
}
}
}
func chatSuspended() {
logger.debug("NotificationService chatSuspended")
if case .suspending = NSEChatState.shared.value {
NSEChatState.shared.set(.suspended)
chatCloseStore()
logger.debug("NotificationService chatSuspended: suspended")
}
}
// A single loop is used per Notification service extension process to receive and process all messages depending on the NSE state
// If the extension is not active yet, or suspended/suspending, or the app is running, the notifications will no be received.
func receiveMessages() async {
logger.debug("NotificationService receiveMessages")
while true {
updateNetCfg()
switch NSEChatState.shared.value {
// it should never get to "created" and "starting" branches, as NSE state is set to .active before the loop start
case .created: await delayWhenInactive()
case .starting: await delayWhenInactive()
case .active: await receiveMsg()
case .suspending: await receiveMsg()
case .suspended: await delayWhenInactive()
}
}
func receiveMsg() async {
if let msg = await chatRecvMsg() {
logger.debug("NotificationService receiveMsg: message")
if let (id, ntf) = await receivedMsgNtf(msg) {
logger.debug("NotificationService receiveMsg: notification")
await PendingNtfs.shared.createStream(id)
await PendingNtfs.shared.writeStream(id, ntf)
}
}
}
func delayWhenInactive() async {
logger.debug("NotificationService delayWhenInactive")
_ = try? await Task.sleep(nanoseconds: 1000_000000)
}
}
func chatRecvMsg() async -> ChatResponse? {
@@ -257,14 +571,14 @@ private let isInChina = SKStorefront().countryCode == "CHN"
private func useCallKit() -> Bool { !isInChina && callKitEnabledGroupDefault.get() }
func receivedMsgNtf(_ res: ChatResponse) async -> (String, NSENotification)? {
logger.debug("NotificationService processReceivedMsg: \(res.responseType)")
logger.debug("NotificationService receivedMsgNtf: \(res.responseType, privacy: .public)")
switch res {
case let .contactConnected(user, contact, _):
return (contact.id, .nse(notification: createContactConnectedNtf(user, contact)))
return (contact.id, .nse(createContactConnectedNtf(user, contact)))
// case let .contactConnecting(contact):
// TODO profile update
case let .receivedContactRequest(user, contactRequest):
return (UserContact(contactRequest: contactRequest).id, .nse(notification: createContactRequestNtf(user, contactRequest)))
return (UserContact(contactRequest: contactRequest).id, .nse(createContactRequestNtf(user, contactRequest)))
case let .newChatItem(user, aChatItem):
let cInfo = aChatItem.chatInfo
var cItem = aChatItem.chatItem
@@ -274,7 +588,7 @@ func receivedMsgNtf(_ res: ChatResponse) async -> (String, NSENotification)? {
if let file = cItem.autoReceiveFile() {
cItem = autoReceiveFile(file, encrypted: cItem.encryptLocalFile) ?? cItem
}
let ntf: NSENotification = cInfo.ntfsEnabled ? .nse(notification: createMessageReceivedNtf(user, cInfo, cItem)) : .empty
let ntf: NSENotification = cInfo.ntfsEnabled ? .nse(createMessageReceivedNtf(user, cInfo, cItem)) : .empty
return cItem.showNotification ? (aChatItem.chatId, ntf) : nil
case let .rcvFileSndCancelled(_, aChatItem, _):
cleanupFile(aChatItem)
@@ -292,10 +606,15 @@ func receivedMsgNtf(_ res: ChatResponse) async -> (String, NSENotification)? {
// Do not post it without CallKit support, iOS will stop launching the app without showing CallKit
return (
invitation.contact.id,
useCallKit() ? .callkit(invitation: invitation) : .nse(notification: createCallInvitationNtf(invitation))
useCallKit() ? .callkit(invitation) : .nse(createCallInvitationNtf(invitation))
)
case let .ntfMessage(_, connEntity, ntfMessage):
return if let id = connEntity.id { (id, .msgInfo(ntfMessage)) } else { nil }
case .chatSuspended:
chatSuspended()
return nil
default:
logger.debug("NotificationService processReceivedMsg ignored event: \(res.responseType)")
logger.debug("NotificationService receivedMsgNtf ignored event: \(res.responseType)")
return nil
}
}
@@ -334,6 +653,21 @@ func apiStartChat() throws -> Bool {
}
}
func apiActivateChat() -> Bool {
chatReopenStore()
let r = sendSimpleXCmd(.apiActivateChat(restoreChat: false))
if case .cmdOk = r { return true }
logger.error("NotificationService apiActivateChat error: \(String(describing: r))")
return false
}
func apiSuspendChat(timeoutMicroseconds: Int) -> Bool {
let r = sendSimpleXCmd(.apiSuspendChat(timeoutMicroseconds: timeoutMicroseconds))
if case .cmdOk = r { return true }
logger.error("NotificationService apiSuspendChat error: \(String(describing: r))")
return false
}
func apiSetTempFolder(tempFolder: String) throws {
let r = sendSimpleXCmd(.setTempFolder(tempFolder: tempFolder))
if case .cmdOk = r { return }
@@ -364,8 +698,8 @@ func apiGetNtfMessage(nonce: String, encNtfInfo: String) -> NtfMessages? {
return nil
}
let r = sendSimpleXCmd(.apiGetNtfMessage(nonce: nonce, encNtfInfo: encNtfInfo))
if case let .ntfMessages(user, connEntity, msgTs, ntfMessages) = r, let user = user {
return NtfMessages(user: user, connEntity: connEntity, msgTs: msgTs, ntfMessages: ntfMessages)
if case let .ntfMessages(user, connEntity_, msgTs, ntfMessages) = r, let user = user {
return NtfMessages(user: user, connEntity_: connEntity_, msgTs: msgTs, ntfMessages: ntfMessages)
} else if case let .chatCmdError(_, error) = r {
logger.debug("apiGetNtfMessage error response: \(String.init(describing: error))")
} else {
@@ -405,11 +739,11 @@ func setNetworkConfig(_ cfg: NetCfg) throws {
struct NtfMessages {
var user: User
var connEntity: ConnectionEntity?
var connEntity_: ConnectionEntity?
var msgTs: Date?
var ntfMessages: [NtfMsgInfo]
var ntfsEnabled: Bool {
user.showNotifications && (connEntity?.ntfsEnabled ?? false)
user.showNotifications && (connEntity_?.ntfsEnabled ?? false)
}
}
+56 -32
View File
@@ -43,6 +43,11 @@
5C3F1D562842B68D00EC8A82 /* IntegrityErrorItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3F1D552842B68D00EC8A82 /* IntegrityErrorItemView.swift */; };
5C3F1D58284363C400EC8A82 /* PrivacySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3F1D57284363C400EC8A82 /* PrivacySettings.swift */; };
5C4B3B0A285FB130003915F2 /* DatabaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B3B09285FB130003915F2 /* DatabaseView.swift */; };
5C4E80DA2B3CCD090080FAE2 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80D52B3CCD090080FAE2 /* libgmp.a */; };
5C4E80DB2B3CCD090080FAE2 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80D62B3CCD090080FAE2 /* libffi.a */; };
5C4E80DC2B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80D72B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a */; };
5C4E80DD2B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80D82B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a */; };
5C4E80DE2B3CCD090080FAE2 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80D92B3CCD090080FAE2 /* libgmpxx.a */; };
5C5346A827B59A6A004DF848 /* ChatHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5346A727B59A6A004DF848 /* ChatHelp.swift */; };
5C55A91F283AD0E400C4E99E /* CallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C55A91E283AD0E400C4E99E /* CallManager.swift */; };
5C55A921283CCCB700C4E99E /* IncomingCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C55A920283CCCB700C4E99E /* IncomingCallView.swift */; };
@@ -118,11 +123,8 @@
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 */; };
5CDA5A2D2B04FE2D00A71D61 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CDA5A282B04FE2D00A71D61 /* libgmp.a */; };
5CDA5A2E2B04FE2D00A71D61 /* libHSsimplex-chat-5.4.0.3-rODxCBVsb2BkD1fnTAqXL-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CDA5A292B04FE2D00A71D61 /* libHSsimplex-chat-5.4.0.3-rODxCBVsb2BkD1fnTAqXL-ghc9.6.3.a */; };
5CDA5A2F2B04FE2D00A71D61 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CDA5A2A2B04FE2D00A71D61 /* libffi.a */; };
5CDA5A302B04FE2D00A71D61 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CDA5A2B2B04FE2D00A71D61 /* libgmpxx.a */; };
5CDA5A312B04FE2D00A71D61 /* libHSsimplex-chat-5.4.0.3-rODxCBVsb2BkD1fnTAqXL.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CDA5A2C2B04FE2D00A71D61 /* libHSsimplex-chat-5.4.0.3-rODxCBVsb2BkD1fnTAqXL.a */; };
5CD67B8F2B0E858A00C510B1 /* hs_init.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CD67B8D2B0E858A00C510B1 /* hs_init.h */; settings = {ATTRIBUTES = (Public, ); }; };
5CD67B902B0E858A00C510B1 /* hs_init.c in Sources */ = {isa = PBXBuildFile; fileRef = 5CD67B8E2B0E858A00C510B1 /* hs_init.c */; };
5CDCAD482818589900503DA2 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CDCAD472818589900503DA2 /* NotificationService.swift */; };
5CE2BA702845308900EC33A6 /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; };
5CE2BA712845308900EC33A6 /* SimpleXChat.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@@ -148,6 +150,9 @@
5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEACCEC27DEA495000BD591 /* MsgContentView.swift */; };
5CEBD7462A5C0A8F00665FE2 /* KeyboardPadding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */; };
5CEBD7482A5F115D00665FE2 /* SetDeliveryReceiptsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBD7472A5F115D00665FE2 /* SetDeliveryReceiptsView.swift */; };
5CF9371E2B23429500E1D781 /* ConcurrentQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF9371D2B23429500E1D781 /* ConcurrentQueue.swift */; };
5CF937202B24DE8C00E1D781 /* SharedFileSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */; };
5CF937232B2503D000E1D781 /* NSESubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF937212B25034A00E1D781 /* NSESubscriber.swift */; };
5CFA59C42860BC6200863A68 /* MigrateToAppGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */; };
5CFA59D12864782E00863A68 /* ChatArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFA59CF286477B400863A68 /* ChatArchiveView.swift */; };
5CFE0921282EEAF60002594B /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */; };
@@ -183,6 +188,7 @@
64D0C2C629FAC1EC00B38D5F /* AddContactLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C529FAC1EC00B38D5F /* AddContactLearnMore.swift */; };
64E972072881BB22008DBC02 /* CIGroupInvitationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E972062881BB22008DBC02 /* CIGroupInvitationView.swift */; };
64F1CC3B28B39D8600CD1FB1 /* IncognitoHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */; };
8C05382E2B39887E006436DC /* VideoUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C05382D2B39887E006436DC /* VideoUtils.swift */; };
D7197A1829AE89660055C05A /* WebRTC in Frameworks */ = {isa = PBXBuildFile; productRef = D7197A1729AE89660055C05A /* WebRTC */; };
D72A9088294BD7A70047C86D /* NativeTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A9087294BD7A70047C86D /* NativeTextEditor.swift */; };
D741547829AF89AF0022400A /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D741547729AF89AF0022400A /* StoreKit.framework */; };
@@ -288,6 +294,11 @@
5C3F1D57284363C400EC8A82 /* PrivacySettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacySettings.swift; sourceTree = "<group>"; };
5C422A7C27A9A6FA0097A1E1 /* SimpleX (iOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "SimpleX (iOS).entitlements"; sourceTree = "<group>"; };
5C4B3B09285FB130003915F2 /* DatabaseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseView.swift; sourceTree = "<group>"; };
5C4E80D52B3CCD090080FAE2 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
5C4E80D62B3CCD090080FAE2 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
5C4E80D72B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a"; sourceTree = "<group>"; };
5C4E80D82B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a"; sourceTree = "<group>"; };
5C4E80D92B3CCD090080FAE2 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
5C5346A727B59A6A004DF848 /* ChatHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatHelp.swift; sourceTree = "<group>"; };
5C55A91E283AD0E400C4E99E /* CallManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallManager.swift; sourceTree = "<group>"; };
5C55A920283CCCB700C4E99E /* IncomingCallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncomingCallView.swift; sourceTree = "<group>"; };
@@ -399,11 +410,8 @@
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>"; };
5CDA5A282B04FE2D00A71D61 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
5CDA5A292B04FE2D00A71D61 /* libHSsimplex-chat-5.4.0.3-rODxCBVsb2BkD1fnTAqXL-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.0.3-rODxCBVsb2BkD1fnTAqXL-ghc9.6.3.a"; sourceTree = "<group>"; };
5CDA5A2A2B04FE2D00A71D61 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
5CDA5A2B2B04FE2D00A71D61 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
5CDA5A2C2B04FE2D00A71D61 /* libHSsimplex-chat-5.4.0.3-rODxCBVsb2BkD1fnTAqXL.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.0.3-rODxCBVsb2BkD1fnTAqXL.a"; sourceTree = "<group>"; };
5CD67B8D2B0E858A00C510B1 /* hs_init.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = hs_init.h; sourceTree = "<group>"; };
5CD67B8E2B0E858A00C510B1 /* hs_init.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = hs_init.c; sourceTree = "<group>"; };
5CDCAD452818589900503DA2 /* SimpleX NSE.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "SimpleX NSE.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
5CDCAD472818589900503DA2 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
5CDCAD492818589900503DA2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -430,6 +438,9 @@
5CEACCEC27DEA495000BD591 /* MsgContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MsgContentView.swift; sourceTree = "<group>"; };
5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardPadding.swift; sourceTree = "<group>"; };
5CEBD7472A5F115D00665FE2 /* SetDeliveryReceiptsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetDeliveryReceiptsView.swift; sourceTree = "<group>"; };
5CF9371D2B23429500E1D781 /* ConcurrentQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConcurrentQueue.swift; sourceTree = "<group>"; };
5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedFileSubscriber.swift; sourceTree = "<group>"; };
5CF937212B25034A00E1D781 /* NSESubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSESubscriber.swift; sourceTree = "<group>"; };
5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateToAppGroupView.swift; sourceTree = "<group>"; };
5CFA59CF286477B400863A68 /* ChatArchiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatArchiveView.swift; sourceTree = "<group>"; };
5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ZoomableScrollView.swift; path = Shared/Views/ZoomableScrollView.swift; sourceTree = SOURCE_ROOT; };
@@ -466,6 +477,7 @@
64DAE1502809D9F5000DA960 /* FileUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = "<group>"; };
64E972062881BB22008DBC02 /* CIGroupInvitationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIGroupInvitationView.swift; sourceTree = "<group>"; };
64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncognitoHelp.swift; sourceTree = "<group>"; };
8C05382D2B39887E006436DC /* VideoUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoUtils.swift; sourceTree = "<group>"; };
D72A9087294BD7A70047C86D /* NativeTextEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeTextEditor.swift; sourceTree = "<group>"; };
D741547729AF89AF0022400A /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; };
D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; };
@@ -507,13 +519,13 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
5CDA5A302B04FE2D00A71D61 /* libgmpxx.a in Frameworks */,
5C4E80DD2B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a in Frameworks */,
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
5CDA5A2D2B04FE2D00A71D61 /* libgmp.a in Frameworks */,
5CDA5A2E2B04FE2D00A71D61 /* libHSsimplex-chat-5.4.0.3-rODxCBVsb2BkD1fnTAqXL-ghc9.6.3.a in Frameworks */,
5CDA5A2F2B04FE2D00A71D61 /* libffi.a in Frameworks */,
5C4E80DA2B3CCD090080FAE2 /* libgmp.a in Frameworks */,
5C4E80DC2B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a in Frameworks */,
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
5CDA5A312B04FE2D00A71D61 /* libHSsimplex-chat-5.4.0.3-rODxCBVsb2BkD1fnTAqXL.a in Frameworks */,
5C4E80DB2B3CCD090080FAE2 /* libffi.a in Frameworks */,
5C4E80DE2B3CCD090080FAE2 /* libgmpxx.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -575,11 +587,11 @@
5C764E5C279C70B7000C6508 /* Libraries */ = {
isa = PBXGroup;
children = (
5CDA5A2A2B04FE2D00A71D61 /* libffi.a */,
5CDA5A282B04FE2D00A71D61 /* libgmp.a */,
5CDA5A2B2B04FE2D00A71D61 /* libgmpxx.a */,
5CDA5A292B04FE2D00A71D61 /* libHSsimplex-chat-5.4.0.3-rODxCBVsb2BkD1fnTAqXL-ghc9.6.3.a */,
5CDA5A2C2B04FE2D00A71D61 /* libHSsimplex-chat-5.4.0.3-rODxCBVsb2BkD1fnTAqXL.a */,
5C4E80D62B3CCD090080FAE2 /* libffi.a */,
5C4E80D52B3CCD090080FAE2 /* libgmp.a */,
5C4E80D92B3CCD090080FAE2 /* libgmpxx.a */,
5C4E80D82B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a */,
5C4E80D72B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a */,
);
path = Libraries;
sourceTree = "<group>";
@@ -604,6 +616,7 @@
5C35CFC727B2782E00FB6C6D /* BGManager.swift */,
5C35CFCA27B2E91D00FB6C6D /* NtfManager.swift */,
5CB346E42868AA7F001FD2EF /* SuspendChat.swift */,
5CF937212B25034A00E1D781 /* NSESubscriber.swift */,
5CB346E82869E8BA001FD2EF /* PushEnvironment.swift */,
5C93293E2928E0FD0090FFF9 /* AudioRecPlay.swift */,
5CBD2859295711D700EC2CF4 /* ImageUtils.swift */,
@@ -631,6 +644,7 @@
64466DCB29FFE3E800E3D48D /* MailView.swift */,
64C3B0202A0D359700E19930 /* CustomTimePicker.swift */,
5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */,
8C05382D2B39887E006436DC /* VideoUtils.swift */,
);
path = Helpers;
sourceTree = "<group>";
@@ -784,6 +798,7 @@
isa = PBXGroup;
children = (
5CDCAD5128186DE400503DA2 /* SimpleX NSE.entitlements */,
5CF9371D2B23429500E1D781 /* ConcurrentQueue.swift */,
5CDCAD472818589900503DA2 /* NotificationService.swift */,
5CDCAD492818589900503DA2 /* Info.plist */,
5CB0BA862826CB3A00B3292C /* InfoPlist.strings */,
@@ -804,10 +819,13 @@
64DAE1502809D9F5000DA960 /* FileUtils.swift */,
5C9D81182AA7A4F1001D49FD /* CryptoFile.swift */,
5C00168028C4FE760094D739 /* KeyChain.swift */,
5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */,
5CE2BA76284530BF00EC33A6 /* SimpleXChat.h */,
5CE2BA8A2845332200EC33A6 /* SimpleX.h */,
5CE2BA78284530CC00EC33A6 /* SimpleXChat.docc */,
5CE2BA96284537A800EC33A6 /* dummy.m */,
5CD67B8D2B0E858A00C510B1 /* hs_init.h */,
5CD67B8E2B0E858A00C510B1 /* hs_init.c */,
);
path = SimpleXChat;
sourceTree = "<group>";
@@ -892,6 +910,7 @@
buildActionMask = 2147483647;
files = (
5CE2BA77284530BF00EC33A6 /* SimpleXChat.h in Headers */,
5CD67B8F2B0E858A00C510B1 /* hs_init.h in Headers */,
5CE2BA952845354B00EC33A6 /* SimpleX.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -1173,6 +1192,7 @@
5C2E260F27A30FDC00F70299 /* ChatView.swift in Sources */,
5C2E260B27A30CFA00F70299 /* ChatListView.swift in Sources */,
6442E0BA287F169300CEC0F9 /* AddGroupView.swift in Sources */,
5CF937232B2503D000E1D781 /* NSESubscriber.swift in Sources */,
6419EC582AB97507004A607A /* CIMemberCreatedContactView.swift in Sources */,
64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */,
64466DCC29FFE3E800E3D48D /* MailView.swift in Sources */,
@@ -1195,6 +1215,7 @@
5CCD403727A5F9A200368C90 /* ScanToConnectView.swift in Sources */,
5CFA59D12864782E00863A68 /* ChatArchiveView.swift in Sources */,
649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */,
8C05382E2B39887E006436DC /* VideoUtils.swift in Sources */,
5CADE79C292131E900072E13 /* ContactPreferencesView.swift in Sources */,
5CB346E52868AA7F001FD2EF /* SuspendChat.swift in Sources */,
5C9C2DA52894777E00CC63B1 /* GroupProfileView.swift in Sources */,
@@ -1252,6 +1273,7 @@
files = (
5CDCAD482818589900503DA2 /* NotificationService.swift in Sources */,
5CFE0922282EEAF60002594B /* ZoomableScrollView.swift in Sources */,
5CF9371E2B23429500E1D781 /* ConcurrentQueue.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1259,9 +1281,11 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5CF937202B24DE8C00E1D781 /* SharedFileSubscriber.swift in Sources */,
5C00168128C4FE760094D739 /* KeyChain.swift in Sources */,
5CE2BA97284537A800EC33A6 /* dummy.m in Sources */,
5CE2BA922845340900EC33A6 /* FileUtils.swift in Sources */,
5CD67B902B0E858A00C510B1 /* hs_init.c in Sources */,
5CE2BA91284533A300EC33A6 /* Notifications.swift in Sources */,
5CE2BA79284530CC00EC33A6 /* SimpleXChat.docc in Sources */,
5CE2BA90284533A300EC33A6 /* JSON.swift in Sources */,
@@ -1494,7 +1518,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 181;
CURRENT_PROJECT_VERSION = 186;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
ENABLE_PREVIEWS = YES;
@@ -1516,7 +1540,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 5.4;
MARKETING_VERSION = 5.4.2;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
PRODUCT_NAME = SimpleX;
SDKROOT = iphoneos;
@@ -1537,7 +1561,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 181;
CURRENT_PROJECT_VERSION = 186;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
ENABLE_PREVIEWS = YES;
@@ -1559,7 +1583,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 5.4;
MARKETING_VERSION = 5.4.2;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
PRODUCT_NAME = SimpleX;
SDKROOT = iphoneos;
@@ -1618,7 +1642,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 181;
CURRENT_PROJECT_VERSION = 186;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
GENERATE_INFOPLIST_FILE = YES;
@@ -1631,7 +1655,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.4;
MARKETING_VERSION = 5.4.2;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -1650,7 +1674,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 181;
CURRENT_PROJECT_VERSION = 186;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
GENERATE_INFOPLIST_FILE = YES;
@@ -1663,7 +1687,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.4;
MARKETING_VERSION = 5.4.2;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -1682,7 +1706,7 @@
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 181;
CURRENT_PROJECT_VERSION = 186;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -1706,7 +1730,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Libraries/sim",
);
MARKETING_VERSION = 5.4;
MARKETING_VERSION = 5.4.2;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SDKROOT = iphoneos;
@@ -1728,7 +1752,7 @@
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 181;
CURRENT_PROJECT_VERSION = 186;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -1752,7 +1776,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Libraries/sim",
);
MARKETING_VERSION = 5.4;
MARKETING_VERSION = 5.4.2;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SDKROOT = iphoneos;
+9 -2
View File
@@ -17,7 +17,7 @@ public func getChatCtrl(_ useKey: String? = nil) -> chat_ctrl {
fatalError("chat controller not initialized")
}
public func chatMigrateInit(_ useKey: String? = nil, confirmMigrations: MigrationConfirmation? = nil) -> (Bool, DBMigrationResult) {
public func chatMigrateInit(_ useKey: String? = nil, confirmMigrations: MigrationConfirmation? = nil, backgroundMode: Bool = false) -> (Bool, DBMigrationResult) {
if let res = migrationResult { return res }
let dbPath = getAppDatabasePath().path
var dbKey = ""
@@ -41,7 +41,7 @@ public func chatMigrateInit(_ useKey: String? = nil, confirmMigrations: Migratio
var cKey = dbKey.cString(using: .utf8)!
var cConfirm = confirm.rawValue.cString(using: .utf8)!
// the last parameter of chat_migrate_init is used to return the pointer to chat controller
let cjson = chat_migrate_init(&cPath, &cKey, &cConfirm, &chatController)!
let cjson = chat_migrate_init_key(&cPath, &cKey, 1, &cConfirm, backgroundMode ? 1 : 0, &chatController)!
let dbRes = dbMigrationResult(fromCString(cjson))
let encrypted = dbKey != ""
let keychainErr = dbRes == .ok && useKeychain && encrypted && !kcDatabasePassword.set(dbKey)
@@ -57,6 +57,13 @@ public func chatCloseStore() {
}
}
public func chatReopenStore() {
let err = fromCString(chat_reopen_store(getChatCtrl()))
if err != "" {
logger.error("chatReopenStore error: \(err)")
}
}
public func resetChatCtrl() {
chatController = nil
migrationResult = nil
+30 -20
View File
@@ -27,7 +27,7 @@ public enum ChatCommand {
case apiDeleteUser(userId: Int64, delSMPQueues: Bool, viewPwd: String?)
case startChat(subscribe: Bool, expire: Bool, xftp: Bool)
case apiStopChat
case apiActivateChat
case apiActivateChat(restoreChat: Bool)
case apiSuspendChat(timeoutMicroseconds: Int)
case setTempFolder(tempFolder: String)
case setFilesFolder(filesFolder: String)
@@ -156,7 +156,7 @@ public enum ChatCommand {
case let .apiDeleteUser(userId, delSMPQueues, viewPwd): return "/_delete user \(userId) del_smp=\(onOff(delSMPQueues))\(maybePwd(viewPwd))"
case let .startChat(subscribe, expire, xftp): return "/_start subscribe=\(onOff(subscribe)) expire=\(onOff(expire)) xftp=\(onOff(xftp))"
case .apiStopChat: return "/_stop"
case .apiActivateChat: return "/_app activate"
case let .apiActivateChat(restore): return "/_app activate restore=\(onOff(restore))"
case let .apiSuspendChat(timeoutMicroseconds): return "/_app suspend \(timeoutMicroseconds)"
case let .setTempFolder(tempFolder): return "/_temp_folder \(tempFolder)"
case let .setFilesFolder(filesFolder): return "/_files_folder \(filesFolder)"
@@ -505,8 +505,8 @@ public enum ChatResponse: Decodable, Error {
case invitation(user: UserRef, connReqInvitation: String, connection: PendingContactConnection)
case connectionIncognitoUpdated(user: UserRef, toConnection: PendingContactConnection)
case connectionPlan(user: UserRef, connectionPlan: ConnectionPlan)
case sentConfirmation(user: UserRef)
case sentInvitation(user: UserRef)
case sentConfirmation(user: UserRef, connection: PendingContactConnection)
case sentInvitation(user: UserRef, connection: PendingContactConnection)
case sentInvitationToContact(user: UserRef, contact: Contact, customUserProfile: Profile?)
case contactAlreadyExists(user: UserRef, contact: Contact)
case contactRequestAlreadyAccepted(user: UserRef, contact: Contact)
@@ -604,16 +604,16 @@ public enum ChatResponse: Decodable, Error {
case callInvitations(callInvitations: [RcvCallInvitation])
case ntfTokenStatus(status: NtfTknStatus)
case ntfToken(token: DeviceToken, status: NtfTknStatus, ntfMode: NotificationsMode)
case ntfMessages(user_: User?, connEntity: ConnectionEntity?, msgTs: Date?, ntfMessages: [NtfMsgInfo])
case newContactConnection(user: UserRef, connection: PendingContactConnection)
case ntfMessages(user_: User?, connEntity_: ConnectionEntity?, msgTs: Date?, ntfMessages: [NtfMsgInfo])
case ntfMessage(user: UserRef, connEntity: ConnectionEntity, ntfMessage: NtfMsgInfo)
case contactConnectionDeleted(user: UserRef, connection: PendingContactConnection)
// remote desktop responses/events
case remoteCtrlList(remoteCtrls: [RemoteCtrlInfo])
case remoteCtrlFound(remoteCtrl: RemoteCtrlInfo)
case remoteCtrlFound(remoteCtrl: RemoteCtrlInfo, ctrlAppInfo_: CtrlAppInfo?, appVersion: String, compatible: Bool)
case remoteCtrlConnecting(remoteCtrl_: RemoteCtrlInfo?, ctrlAppInfo: CtrlAppInfo, appVersion: String)
case remoteCtrlSessionCode(remoteCtrl_: RemoteCtrlInfo?, sessionCode: String)
case remoteCtrlConnected(remoteCtrl: RemoteCtrlInfo)
case remoteCtrlStopped
case remoteCtrlStopped(rcsState: RemoteCtrlSessionState, rcStopReason: RemoteCtrlStopReason)
// misc
case versionInfo(versionInfo: CoreVersionInfo, chatMigrations: [UpMigration], agentMigrations: [UpMigration])
case cmdOk(user: UserRef?)
@@ -752,7 +752,7 @@ public enum ChatResponse: Decodable, Error {
case .ntfTokenStatus: return "ntfTokenStatus"
case .ntfToken: return "ntfToken"
case .ntfMessages: return "ntfMessages"
case .newContactConnection: return "newContactConnection"
case .ntfMessage: return "ntfMessage"
case .contactConnectionDeleted: return "contactConnectionDeleted"
case .remoteCtrlList: return "remoteCtrlList"
case .remoteCtrlFound: return "remoteCtrlFound"
@@ -803,11 +803,11 @@ public enum ChatResponse: Decodable, Error {
case let .contactCode(u, contact, connectionCode): return withUser(u, "contact: \(String(describing: contact))\nconnectionCode: \(connectionCode)")
case let .groupMemberCode(u, groupInfo, member, connectionCode): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionCode: \(connectionCode)")
case let .connectionVerified(u, verified, expectedCode): return withUser(u, "verified: \(verified)\nconnectionCode: \(expectedCode)")
case let .invitation(u, connReqInvitation, _): return withUser(u, connReqInvitation)
case let .invitation(u, connReqInvitation, connection): return withUser(u, "connReqInvitation: \(connReqInvitation)\nconnection: \(connection)")
case let .connectionIncognitoUpdated(u, toConnection): return withUser(u, String(describing: toConnection))
case let .connectionPlan(u, connectionPlan): return withUser(u, String(describing: connectionPlan))
case .sentConfirmation: return noDetails
case .sentInvitation: return noDetails
case let .sentConfirmation(u, connection): return withUser(u, String(describing: connection))
case let .sentInvitation(u, connection): return withUser(u, String(describing: connection))
case let .sentInvitationToContact(u, contact, _): return withUser(u, String(describing: contact))
case let .contactAlreadyExists(u, contact): return withUser(u, String(describing: contact))
case let .contactRequestAlreadyAccepted(u, contact): return withUser(u, String(describing: contact))
@@ -900,10 +900,10 @@ public enum ChatResponse: Decodable, Error {
case let .ntfTokenStatus(status): return String(describing: status)
case let .ntfToken(token, status, ntfMode): return "token: \(token)\nstatus: \(status.rawValue)\nntfMode: \(ntfMode.rawValue)"
case let .ntfMessages(u, connEntity, msgTs, ntfMessages): return withUser(u, "connEntity: \(String(describing: connEntity))\nmsgTs: \(String(describing: msgTs))\nntfMessages: \(String(describing: ntfMessages))")
case let .newContactConnection(u, connection): return withUser(u, String(describing: connection))
case let .ntfMessage(u, connEntity, ntfMessage): return withUser(u, "connEntity: \(String(describing: connEntity))\nntfMessage: \(String(describing: ntfMessage))")
case let .contactConnectionDeleted(u, connection): return withUser(u, String(describing: connection))
case let .remoteCtrlList(remoteCtrls): return String(describing: remoteCtrls)
case let .remoteCtrlFound(remoteCtrl): return String(describing: remoteCtrl)
case let .remoteCtrlFound(remoteCtrl, ctrlAppInfo_, appVersion, compatible): return "remoteCtrl:\n\(String(describing: remoteCtrl))\nctrlAppInfo_:\n\(String(describing: ctrlAppInfo_))\nappVersion: \(appVersion)\ncompatible: \(compatible)"
case let .remoteCtrlConnecting(remoteCtrl_, ctrlAppInfo, appVersion): return "remoteCtrl_:\n\(String(describing: remoteCtrl_))\nctrlAppInfo:\n\(String(describing: ctrlAppInfo))\nappVersion: \(appVersion)"
case let .remoteCtrlSessionCode(remoteCtrl_, sessionCode): return "remoteCtrl_:\n\(String(describing: remoteCtrl_))\nsessionCode: \(sessionCode)"
case let .remoteCtrlConnected(remoteCtrl): return String(describing: remoteCtrl)
@@ -1207,9 +1207,9 @@ public struct NetCfg: Codable, Equatable {
public static let defaults: NetCfg = NetCfg(
socksProxy: nil,
sessionMode: TransportSessionMode.user,
tcpConnectTimeout: 15_000_000,
tcpTimeout: 10_000_000,
tcpTimeoutPerKb: 30_000,
tcpConnectTimeout: 20_000_000,
tcpTimeout: 15_000_000,
tcpTimeoutPerKb: 45_000,
tcpKeepAlive: KeepAliveOpts.defaults,
smpPingInterval: 1200_000_000,
smpPingCount: 3,
@@ -1498,6 +1498,8 @@ public enum PushProvider: String, Decodable {
}
}
// This notification mode is for app core, UI uses AppNotificationsMode.off to mean completely disable,
// and .local for periodic background checks
public enum NotificationsMode: String, Decodable, SelectableItem {
case off = "OFF"
case periodic = "PERIODIC"
@@ -1505,9 +1507,9 @@ public enum NotificationsMode: String, Decodable, SelectableItem {
public var label: LocalizedStringKey {
switch self {
case .off: return "Off (Local)"
case .periodic: return "Periodically"
case .instant: return "Instantly"
case .off: "Local"
case .periodic: "Periodically"
case .instant: "Instantly"
}
}
@@ -1546,11 +1548,19 @@ public struct RemoteCtrlInfo: Decodable {
public enum RemoteCtrlSessionState: Decodable {
case starting
case searching
case connecting
case pendingConfirmation(sessionCode: String)
case connected(sessionCode: String)
}
public enum RemoteCtrlStopReason: Decodable {
case discoveryFailed(chatError: ChatError)
case connectionFailed(chatError: ChatError)
case setupFailed(chatError: ChatError)
case disconnected
}
public struct CtrlAppInfo: Decodable {
public var appVersionRange: AppVersionRange
public var deviceName: String
+53 -7
View File
@@ -9,12 +9,16 @@
import Foundation
import SwiftUI
public let appSuspendTimeout: Int = 15 // seconds
let GROUP_DEFAULT_APP_STATE = "appState"
let GROUP_DEFAULT_NSE_STATE = "nseState"
let GROUP_DEFAULT_DB_CONTAINER = "dbContainer"
public let GROUP_DEFAULT_CHAT_LAST_START = "chatLastStart"
public let GROUP_DEFAULT_CHAT_LAST_BACKGROUND_RUN = "chatLastBackgroundRun"
let GROUP_DEFAULT_NTF_PREVIEW_MODE = "ntfPreviewMode"
public let GROUP_DEFAULT_NTF_ENABLE_LOCAL = "ntfEnableLocal"
public let GROUP_DEFAULT_NTF_ENABLE_PERIODIC = "ntfEnablePeriodic"
public let GROUP_DEFAULT_NTF_ENABLE_LOCAL = "ntfEnableLocal" // no longer used
public let GROUP_DEFAULT_NTF_ENABLE_PERIODIC = "ntfEnablePeriodic" // no longer used
let GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES = "privacyAcceptImages"
public let GROUP_DEFAULT_PRIVACY_TRANSFER_IMAGES_INLINE = "privacyTransferImagesInline" // no longer used
public let GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES = "privacyEncryptLocalFiles"
@@ -66,13 +70,23 @@ public func registerGroupDefaults() {
])
}
public enum AppState: String {
public enum AppState: String, Codable {
case active
case activating
case bgRefresh
case suspending
case suspended
case stopped
public var running: Bool {
switch self {
case .active: return true
case .activating: return true
case .bgRefresh: return true
default: return false
}
}
public var inactive: Bool {
switch self {
case .suspending: return true
@@ -84,23 +98,57 @@ public enum AppState: String {
public var canSuspend: Bool {
switch self {
case .active: return true
case .activating: return true
case .bgRefresh: return true
default: return false
}
}
}
public enum NSEState: String, Codable {
case created
case starting
case active
case suspending
case suspended
public var inactive: Bool {
switch self {
case .created: true
case .suspended: true
default: false
}
}
public var canSuspend: Bool {
if case .active = self { true } else { false }
}
}
public enum DBContainer: String {
case documents
case group
}
// appStateGroupDefault must not be used in the app directly, only via AppChatState singleton
public let appStateGroupDefault = EnumDefault<AppState>(
defaults: groupDefaults,
forKey: GROUP_DEFAULT_APP_STATE,
withDefault: .active
)
// nseStateGroupDefault must not be used in NSE directly, only via NSEChatState singleton
public let nseStateGroupDefault = EnumDefault<NSEState>(
defaults: groupDefaults,
forKey: GROUP_DEFAULT_NSE_STATE,
withDefault: .suspended // so that NSE that was never launched does not delay the app from resuming
)
// inactive app states do not include "stopped" state
public func allowBackgroundRefresh() -> Bool {
appStateGroupDefault.get().inactive && nseStateGroupDefault.get().inactive
}
public let dbContainerGroupDefault = EnumDefault<DBContainer>(
defaults: groupDefaults,
forKey: GROUP_DEFAULT_DB_CONTAINER,
@@ -109,6 +157,8 @@ public let dbContainerGroupDefault = EnumDefault<DBContainer>(
public let chatLastStartGroupDefault = DateDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_CHAT_LAST_START)
public let chatLastBackgroundRunGroupDefault = DateDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_CHAT_LAST_BACKGROUND_RUN)
public let ntfPreviewModeGroupDefault = EnumDefault<NotificationPreviewMode>(
defaults: groupDefaults,
forKey: GROUP_DEFAULT_NTF_PREVIEW_MODE,
@@ -117,10 +167,6 @@ public let ntfPreviewModeGroupDefault = EnumDefault<NotificationPreviewMode>(
public let incognitoGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_INCOGNITO)
public let ntfEnableLocalGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_NTF_ENABLE_LOCAL)
public let ntfEnablePeriodicGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_NTF_ENABLE_PERIODIC)
public let privacyAcceptImagesGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES)
public let privacyEncryptLocalFilesGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES)
+9 -5
View File
@@ -1847,7 +1847,7 @@ public struct GroupMember: Identifiable, Decodable {
public func canChangeRoleTo(groupInfo: GroupInfo) -> [GroupMemberRole]? {
if !canBeRemoved(groupInfo: groupInfo) { return nil }
let userRole = groupInfo.membership.memberRole
return GroupMemberRole.allCases.filter { $0 <= userRole }
return GroupMemberRole.allCases.filter { $0 <= userRole && $0 != .author }
}
public var memberIncognito: Bool {
@@ -1887,6 +1887,7 @@ public struct GroupMemberIds: Decodable {
public enum GroupMemberRole: String, Identifiable, CaseIterable, Comparable, Decodable {
case observer = "observer"
case author = "author"
case member = "member"
case admin = "admin"
case owner = "owner"
@@ -1896,6 +1897,7 @@ public enum GroupMemberRole: String, Identifiable, CaseIterable, Comparable, Dec
public var text: String {
switch self {
case .observer: return NSLocalizedString("observer", comment: "member role")
case .author: return NSLocalizedString("author", comment: "member role")
case .member: return NSLocalizedString("member", comment: "member role")
case .admin: return NSLocalizedString("admin", comment: "member role")
case .owner: return NSLocalizedString("owner", comment: "member role")
@@ -1905,9 +1907,10 @@ public enum GroupMemberRole: String, Identifiable, CaseIterable, Comparable, Dec
private var comparisonValue: Int {
switch self {
case .observer: return 0
case .member: return 1
case .admin: return 2
case .owner: return 3
case .author: return 1
case .member: return 2
case .admin: return 3
case .owner: return 4
}
}
@@ -2013,7 +2016,8 @@ public enum ConnectionEntity: Decodable {
}
public struct NtfMsgInfo: Decodable {
public var msgId: String
public var msgTs: Date
}
public struct AChatItem: Decodable {
+2 -2
View File
@@ -17,7 +17,7 @@ public func writeCryptoFile(path: String, data: Data) throws -> CryptoFileArgs {
let ptr: UnsafeMutableRawPointer = malloc(data.count)
memcpy(ptr, (data as NSData).bytes, data.count)
var cPath = path.cString(using: .utf8)!
let cjson = chat_write_file(&cPath, ptr, Int32(data.count))!
let cjson = chat_write_file(getChatCtrl(), &cPath, ptr, Int32(data.count))!
let d = fromCString(cjson).data(using: .utf8)!
switch try jsonDecoder.decode(WriteFileResult.self, from: d) {
case let .result(cfArgs): return cfArgs
@@ -50,7 +50,7 @@ public func readCryptoFile(path: String, cryptoArgs: CryptoFileArgs) throws -> D
public func encryptCryptoFile(fromPath: String, toPath: String) throws -> CryptoFileArgs {
var cFromPath = fromPath.cString(using: .utf8)!
var cToPath = toPath.cString(using: .utf8)!
let cjson = chat_encrypt_file(&cFromPath, &cToPath)!
let cjson = chat_encrypt_file(getChatCtrl(), &cFromPath, &cToPath)!
let d = fromCString(cjson).data(using: .utf8)!
switch try jsonDecoder.decode(WriteFileResult.self, from: d) {
case let .result(cfArgs): return cfArgs
+7
View File
@@ -146,6 +146,13 @@ public func createErrorNtf(_ dbStatus: DBMigrationResult) -> UNMutableNotificati
)
}
public func createAppStoppedNtf() -> UNMutableNotificationContent {
return createNotification(
categoryIdentifier: ntfCategoryConnectionEvent,
title: NSLocalizedString("Encrypted message: app is stopped", comment: "notification")
)
}
private func groupMsgNtfTitle(_ groupInfo: GroupInfo, _ groupMember: GroupMember, hideContent: Bool) -> String {
hideContent
? NSLocalizedString("Group message:", comment: "notification")
@@ -0,0 +1,99 @@
//
// SharedFileSubscriber.swift
// SimpleXChat
//
// Created by Evgeny on 09/12/2023.
// Copyright © 2023 SimpleX Chat. All rights reserved.
//
import Foundation
public typealias AppSubscriber = SharedFileSubscriber<ProcessMessage<AppProcessMessage>>
public typealias NSESubscriber = SharedFileSubscriber<ProcessMessage<NSEProcessMessage>>
public class SharedFileSubscriber<Message: Codable>: NSObject, NSFilePresenter {
var fileURL: URL
public var presentedItemURL: URL?
public var presentedItemOperationQueue: OperationQueue = .main
var subscriber: (Message) -> Void
init(fileURL: URL, onMessage: @escaping (Message) -> Void) {
self.fileURL = fileURL
presentedItemURL = fileURL
subscriber = onMessage
super.init()
NSFileCoordinator.addFilePresenter(self)
}
public func presentedItemDidChange() {
do {
let data = try Data(contentsOf: fileURL)
let msg = try jsonDecoder.decode(Message.self, from: data)
subscriber(msg)
} catch let error {
logger.error("presentedItemDidChange error: \(error)")
}
}
static func notify(url: URL, message: Message) {
let fc = NSFileCoordinator(filePresenter: nil)
fc.coordinate(writingItemAt: url, options: [], error: nil) { newURL in
do {
let data = try jsonEncoder.encode(message)
try data.write(to: newURL, options: [.atomic])
} catch {
logger.error("notifyViaSharedFile error: \(error)")
}
}
}
deinit {
NSFileCoordinator.removeFilePresenter(self)
}
}
let appMessagesSharedFile = getGroupContainerDirectory().appendingPathComponent("chat.simplex.app.messages", isDirectory: false)
let nseMessagesSharedFile = getGroupContainerDirectory().appendingPathComponent("chat.simplex.app.SimpleX-NSE.messages", isDirectory: false)
public struct ProcessMessage<Message: Codable>: Codable {
var createdAt: Date = Date.now
var message: Message
}
public enum AppProcessMessage: Codable {
case state(state: AppState)
}
public enum NSEProcessMessage: Codable {
case state(state: NSEState)
}
public func sendAppProcessMessage(_ message: AppProcessMessage) {
SharedFileSubscriber.notify(url: appMessagesSharedFile, message: ProcessMessage(message: message))
}
public func sendNSEProcessMessage(_ message: NSEProcessMessage) {
SharedFileSubscriber.notify(url: nseMessagesSharedFile, message: ProcessMessage(message: message))
}
public func appMessageSubscriber(onMessage: @escaping (AppProcessMessage) -> Void) -> AppSubscriber {
SharedFileSubscriber(fileURL: appMessagesSharedFile) { (msg: ProcessMessage<AppProcessMessage>) in
onMessage(msg.message)
}
}
public func nseMessageSubscriber(onMessage: @escaping (NSEProcessMessage) -> Void) -> NSESubscriber {
SharedFileSubscriber(fileURL: nseMessagesSharedFile) { (msg: ProcessMessage<NSEProcessMessage>) in
onMessage(msg.message)
}
}
public func sendAppState(_ state: AppState) {
sendAppProcessMessage(.state(state: state))
}
public func sendNSEState(_ state: NSEState) {
sendNSEProcessMessage(.state(state: state))
}
+8 -6
View File
@@ -9,27 +9,27 @@
#ifndef SimpleX_h
#define SimpleX_h
#endif /* SimpleX_h */
#include "hs_init.h"
extern void hs_init(int argc, char **argv[]);
typedef void* chat_ctrl;
// the last parameter is used to return the pointer to chat controller
extern char *chat_migrate_init(char *path, char *key, char *confirm, chat_ctrl *ctrl);
extern char *chat_migrate_init_key(char *path, char *key, int keepKey, char *confirm, int backgroundMode, chat_ctrl *ctrl);
extern char *chat_close_store(chat_ctrl ctl);
extern char *chat_reopen_store(chat_ctrl ctl);
extern char *chat_send_cmd(chat_ctrl ctl, char *cmd);
extern char *chat_recv_msg(chat_ctrl ctl);
extern char *chat_recv_msg_wait(chat_ctrl ctl, int wait);
extern char *chat_parse_markdown(char *str);
extern char *chat_parse_server(char *str);
extern char *chat_password_hash(char *pwd, char *salt);
extern char *chat_valid_name(char *name);
extern char *chat_encrypt_media(char *key, char *frame, int len);
extern char *chat_encrypt_media(chat_ctrl ctl, char *key, char *frame, int len);
extern char *chat_decrypt_media(char *key, char *frame, int len);
// chat_write_file returns null-terminated string with JSON of WriteFileResult
extern char *chat_write_file(char *path, char *data, int len);
extern char *chat_write_file(chat_ctrl ctl, char *path, char *data, int len);
// chat_read_file returns a buffer with:
// result status (1 byte), then if
@@ -38,7 +38,9 @@ extern char *chat_write_file(char *path, char *data, int len);
extern char *chat_read_file(char *path, char *key, char *nonce);
// chat_encrypt_file returns null-terminated string with JSON of WriteFileResult
extern char *chat_encrypt_file(char *fromPath, char *toPath);
extern char *chat_encrypt_file(chat_ctrl ctl, char *fromPath, char *toPath);
// chat_decrypt_file returns null-terminated string with the error message
extern char *chat_decrypt_file(char *fromPath, char *key, char *nonce, char *toPath);
#endif /* SimpleX_h */
+25
View File
@@ -0,0 +1,25 @@
//
// hs_init.c
// SimpleXChat
//
// Created by Evgeny on 22/11/2023.
// Copyright © 2023 SimpleX Chat. All rights reserved.
//
#include "hs_init.h"
extern void hs_init_with_rtsopts(int * argc, char **argv[]);
void haskell_init(void) {
int argc = 5;
char *argv[] = {
"simplex",
"+RTS", // requires `hs_init_with_rtsopts`
"-A16m", // chunk size for new allocations
"-H64m", // initial heap size
"-xn", // non-moving GC
0
};
char **pargv = argv;
hs_init_with_rtsopts(&argc, &pargv);
}
+14
View File
@@ -0,0 +1,14 @@
//
// hs_init.h
// SimpleXChat
//
// Created by Evgeny on 22/11/2023.
// Copyright © 2023 SimpleX Chat. All rights reserved.
//
#ifndef hs_init_h
#define hs_init_h
void haskell_init(void);
#endif /* hs_init_h */
+135
View File
@@ -25,6 +25,9 @@
/* No comment provided by engineer. */
"- more stable message delivery.\n- a bit better groups.\n- and more!" = "- stabilere Zustellung von Nachrichten.\n- ein bisschen verbesserte Gruppen.\n- und mehr!";
/* No comment provided by engineer. */
"- optionally notify deleted contacts.\n- profile names with spaces.\n- and more!" = "- Optionale Benachrichtigung von gelöschten Kontakten.\n- Profilnamen mit Leerzeichen.\n- Und mehr!";
/* No comment provided by engineer. */
"- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- Bis zu 5 Minuten lange Sprachnachrichten.\n- Zeitdauer für verschwindende Nachrichten anpassen.\n- Nachrichten-Historie bearbeiten.";
@@ -43,6 +46,12 @@
/* No comment provided by engineer. */
"(" = "(";
/* No comment provided by engineer. */
"(new)" = "(Neu)";
/* No comment provided by engineer. */
"(this device v%@)" = "(Dieses Gerät hat v%@)";
/* No comment provided by engineer. */
")" = ")";
@@ -536,6 +545,9 @@
/* No comment provided by engineer. */
"Authentication unavailable" = "Authentifizierung nicht verfügbar";
/* member role */
"author" = "Autor";
/* No comment provided by engineer. */
"Auto-accept" = "Automatisch akzeptieren";
@@ -548,6 +560,9 @@
/* No comment provided by engineer. */
"Back" = "Zurück";
/* No comment provided by engineer. */
"Bad desktop address" = "Falsche Desktop-Adresse";
/* integrity error chat item */
"bad message hash" = "Ungültiger Nachrichten-Hash";
@@ -560,12 +575,18 @@
/* No comment provided by engineer. */
"Bad message ID" = "Falsche Nachrichten-ID";
/* No comment provided by engineer. */
"Better groups" = "Bessere Gruppen";
/* No comment provided by engineer. */
"Better messages" = "Verbesserungen bei Nachrichten";
/* No comment provided by engineer. */
"Block" = "Blockieren";
/* No comment provided by engineer. */
"Block group members" = "Gruppenmitglieder blockieren";
/* No comment provided by engineer. */
"Block member" = "Mitglied blockieren";
@@ -768,9 +789,15 @@
/* server test step */
"Connect" = "Verbinden";
/* No comment provided by engineer. */
"Connect automatically" = "Automatisch verbinden";
/* No comment provided by engineer. */
"Connect incognito" = "Inkognito verbinden";
/* No comment provided by engineer. */
"Connect to desktop" = "Mit dem Desktop verbinden";
/* No comment provided by engineer. */
"connect to SimpleX Chat developers." = "Mit den SimpleX Chat-Entwicklern verbinden.";
@@ -801,9 +828,15 @@
/* No comment provided by engineer. */
"connected" = "Verbunden";
/* No comment provided by engineer. */
"Connected desktop" = "Verbundener Desktop";
/* rcv group event chat item */
"connected directly" = "Direkt miteinander verbunden";
/* No comment provided by engineer. */
"Connected to desktop" = "Mit dem Desktop verbunden";
/* No comment provided by engineer. */
"connecting" = "verbinde";
@@ -828,6 +861,9 @@
/* No comment provided by engineer. */
"Connecting server… (error: %@)" = "Mit dem Server verbinden… (Fehler: %@)";
/* No comment provided by engineer. */
"Connecting to desktop" = "Mit dem Desktop verbinden";
/* chat list item title */
"connecting…" = "Verbinde…";
@@ -846,6 +882,9 @@
/* No comment provided by engineer. */
"Connection request sent!" = "Verbindungsanfrage wurde gesendet!";
/* No comment provided by engineer. */
"Connection terminated" = "Verbindung beendet";
/* No comment provided by engineer. */
"Connection timeout" = "Verbindungszeitüberschreitung";
@@ -900,6 +939,9 @@
/* No comment provided by engineer. */
"Create" = "Erstellen";
/* No comment provided by engineer. */
"Create a group using a random profile." = "Erstellen Sie eine Gruppe mit einem zufälligen Profil.";
/* No comment provided by engineer. */
"Create an address to let people connect with you." = "Erstellen Sie eine Adresse, damit sich Personen mit Ihnen verbinden können.";
@@ -1173,6 +1215,15 @@
/* No comment provided by engineer. */
"Description" = "Beschreibung";
/* No comment provided by engineer. */
"Desktop address" = "Desktop-Adresse";
/* No comment provided by engineer. */
"Desktop app version %@ is not compatible with this app." = "Desktop App-Version %@ ist mit dieser App nicht kompatibel.";
/* No comment provided by engineer. */
"Desktop devices" = "Desktop-Geräte";
/* No comment provided by engineer. */
"Develop" = "Entwicklung";
@@ -1236,9 +1287,15 @@
/* server test step */
"Disconnect" = "Trennen";
/* No comment provided by engineer. */
"Disconnect desktop?" = "Desktop-Verbindung trennen?";
/* No comment provided by engineer. */
"Discover and join groups" = "Gruppen entdecken und ihnen beitreten";
/* No comment provided by engineer. */
"Discover via local network" = "Lokales Netzwerk durchsuchen";
/* No comment provided by engineer. */
"Do it later" = "Später wiederholen";
@@ -1374,6 +1431,12 @@
/* chat item text */
"encryption re-negotiation allowed for %@" = "Neuaushandlung der Verschlüsselung von %@ erlaubt";
/* message decrypt error item */
"Encryption re-negotiation error" = "Fehler bei der Neuverhandlung der Verschlüsselung";
/* No comment provided by engineer. */
"Encryption re-negotiation failed." = "Neuverhandlung der Verschlüsselung fehlgeschlagen.";
/* chat item text */
"encryption re-negotiation required" = "Neuaushandlung der Verschlüsselung notwendig";
@@ -1404,6 +1467,9 @@
/* No comment provided by engineer. */
"Enter server manually" = "Geben Sie den Server manuell ein";
/* No comment provided by engineer. */
"Enter this device name…" = "Geben Sie diesen Gerätenamen ein…";
/* placeholder */
"Enter welcome message…" = "Geben Sie eine Begrüßungsmeldung ein …";
@@ -1605,6 +1671,9 @@
/* No comment provided by engineer. */
"Fast and no wait until the sender is online!" = "Schnell und ohne warten auf den Absender, bis er online ist!";
/* No comment provided by engineer. */
"Faster joining and more reliable messages." = "Schnellerer Gruppenbeitritt und zuverlässigere Nachrichtenzustellung.";
/* No comment provided by engineer. */
"Favorite" = "Favorit";
@@ -1662,6 +1731,9 @@
/* No comment provided by engineer. */
"For console" = "Für Konsole";
/* No comment provided by engineer. */
"Found desktop" = "Gefundener Desktop";
/* No comment provided by engineer. */
"French interface" = "Französische Bedienoberfläche";
@@ -1866,6 +1938,9 @@
/* No comment provided by engineer. */
"Incognito" = "Inkognito";
/* No comment provided by engineer. */
"Incognito groups" = "Inkognito-Gruppen";
/* No comment provided by engineer. */
"Incognito mode" = "Inkognito-Modus";
@@ -1893,6 +1968,9 @@
/* No comment provided by engineer. */
"Incompatible database version" = "Inkompatible Datenbank-Version";
/* No comment provided by engineer. */
"Incompatible version" = "Inkompatible Version";
/* PIN entry */
"Incorrect passcode" = "Zugangscode ist falsch";
@@ -2028,6 +2106,9 @@
/* No comment provided by engineer. */
"Joining group" = "Der Gruppe beitreten";
/* No comment provided by engineer. */
"Keep the app open to use it from desktop" = "Die App muss geöffnet bleiben, um sie vom Desktop aus nutzen zu können";
/* No comment provided by engineer. */
"Keep your connections" = "Ihre Verbindungen beibehalten";
@@ -2064,6 +2145,15 @@
/* No comment provided by engineer. */
"Limitations" = "Einschränkungen";
/* No comment provided by engineer. */
"Link mobile and desktop apps! 🔗" = "Verknüpfe Mobiltelefon- und Desktop-Apps! 🔗";
/* No comment provided by engineer. */
"Linked desktop options" = "Verknüpfte Desktop-Optionen";
/* No comment provided by engineer. */
"Linked desktops" = "Verknüpfte Desktops";
/* No comment provided by engineer. */
"LIVE" = "LIVE";
@@ -2319,6 +2409,9 @@
/* copied message info in history */
"no text" = "Kein Text";
/* No comment provided by engineer. */
"Not compatible!" = "Nicht kompatibel!";
/* No comment provided by engineer. */
"Notifications" = "Benachrichtigungen";
@@ -2462,6 +2555,9 @@
/* No comment provided by engineer. */
"Paste" = "Einfügen";
/* No comment provided by engineer. */
"Paste desktop address" = "Desktop-Adresse einfügen";
/* No comment provided by engineer. */
"Paste image" = "Bild einfügen";
@@ -2846,6 +2942,9 @@
/* No comment provided by engineer. */
"Scan QR code" = "QR-Code scannen";
/* No comment provided by engineer. */
"Scan QR code from desktop" = "Den QR-Code vom Desktop scannen";
/* No comment provided by engineer. */
"Scan security code from your contact's app." = "Scannen Sie den Sicherheitscode von der App Ihres Kontakts.";
@@ -2990,6 +3089,9 @@
/* No comment provided by engineer. */
"Servers" = "Server";
/* No comment provided by engineer. */
"Session code" = "Sitzungscode";
/* No comment provided by engineer. */
"Set 1 day" = "Einen Tag festlegen";
@@ -3299,6 +3401,9 @@
/* notification title */
"this contact" = "Dieser Kontakt";
/* No comment provided by engineer. */
"This device name" = "Dieser Gerätename";
/* No comment provided by engineer. */
"This group has over %lld members, delivery receipts are not sent." = "Es werden keine Empfangsbestätigungen gesendet, da diese Gruppe über %lld Mitglieder hat.";
@@ -3320,6 +3425,9 @@
/* No comment provided by engineer. */
"To connect, your contact can scan QR code or use the link in the app." = "Um eine Verbindung herzustellen, kann Ihr Kontakt den QR-Code scannen oder den Link in der App verwenden.";
/* No comment provided by engineer. */
"To hide unwanted messages." = "Um unerwünschte Nachrichten zu verbergen.";
/* No comment provided by engineer. */
"To make a new connection" = "Um eine Verbindung mit einem neuen Kontakt zu erstellen";
@@ -3416,6 +3524,12 @@
/* No comment provided by engineer. */
"Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection." = "Entweder hat Ihr Kontakt die Verbindung gelöscht, oder dieser Link wurde bereits verwendet, es könnte sich um einen Fehler handeln - Bitte melden Sie es uns.\nBitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um sich neu verbinden zu können und stellen Sie sicher, dass Sie eine stabile Netzwerk-Verbindung haben.";
/* No comment provided by engineer. */
"Unlink" = "Entkoppeln";
/* No comment provided by engineer. */
"Unlink desktop?" = "Desktop entkoppeln?";
/* No comment provided by engineer. */
"Unlock" = "Entsperren";
@@ -3470,6 +3584,9 @@
/* No comment provided by engineer. */
"Use for new connections" = "Für neue Verbindungen nutzen";
/* No comment provided by engineer. */
"Use from desktop" = "Vom Desktop aus nutzen";
/* No comment provided by engineer. */
"Use iOS call interface" = "iOS Anrufschnittstelle nutzen";
@@ -3491,12 +3608,24 @@
/* No comment provided by engineer. */
"Using SimpleX Chat servers." = "Verwendung von SimpleX-Chat-Servern.";
/* No comment provided by engineer. */
"v%@" = "v%@";
/* No comment provided by engineer. */
"v%@ (%@)" = "v%@ (%@)";
/* No comment provided by engineer. */
"Verify code with desktop" = "Code mit dem Desktop überprüfen";
/* No comment provided by engineer. */
"Verify connection" = "Verbindung überprüfen";
/* No comment provided by engineer. */
"Verify connection security" = "Sicherheit der Verbindung überprüfen";
/* No comment provided by engineer. */
"Verify connections" = "Verbindungen überprüfen";
/* No comment provided by engineer. */
"Verify security code" = "Sicherheitscode überprüfen";
@@ -3515,6 +3644,9 @@
/* No comment provided by engineer. */
"via relay" = "über Relais";
/* No comment provided by engineer. */
"Via secure quantum resistant protocol." = "Über ein sicheres quantenbeständiges Protokoll.";
/* No comment provided by engineer. */
"Video call" = "Videoanruf";
@@ -3554,6 +3686,9 @@
/* No comment provided by engineer. */
"waiting for confirmation…" = "Warten auf Bestätigung…";
/* No comment provided by engineer. */
"Waiting for desktop..." = "Es wird auf den Desktop gewartet...";
/* No comment provided by engineer. */
"Waiting for file" = "Warte auf Datei";
@@ -7,6 +7,9 @@
/* Privacy - Face ID Usage Description */
"NSFaceIDUsageDescription" = "Face ID wird von SimpleX für die lokale Authentifizierung genutzt";
/* Privacy - Local Network Usage Description */
"NSLocalNetworkUsageDescription" = "SimpleX nutzt den lokalen Netzwerkzugriff, um die Nutzung von Benutzer-Chatprofilen über eine Desktop-App im gleichen Netzwerk zu erlauben.";
/* Privacy - Microphone Usage Description */
"NSMicrophoneUsageDescription" = "SimpleX benötigt Zugriff auf das Mikrofon, um Audio- und Videoanrufe und die Aufnahme von Sprachnachrichten zu ermöglichen.";
+135
View File
@@ -25,6 +25,9 @@
/* No comment provided by engineer. */
"- more stable message delivery.\n- a bit better groups.\n- and more!" = "- une diffusion plus stable des messages.\n- des groupes un peu plus performants.\n- et bien d'autres choses encore !";
/* No comment provided by engineer. */
"- optionally notify deleted contacts.\n- profile names with spaces.\n- and more!" = "- option pour notifier les contacts supprimés.\n- noms de profil avec espaces.\n- et plus encore !";
/* No comment provided by engineer. */
"- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- messages vocaux pouvant durer jusqu'à 5 minutes.\n- délai personnalisé de disparition.\n- l'historique de modification.";
@@ -43,6 +46,12 @@
/* No comment provided by engineer. */
"(" = "(";
/* No comment provided by engineer. */
"(new)" = "(nouveau)";
/* No comment provided by engineer. */
"(this device v%@)" = "(cet appareil v%@)";
/* No comment provided by engineer. */
")" = ")";
@@ -536,6 +545,9 @@
/* No comment provided by engineer. */
"Authentication unavailable" = "Authentification indisponible";
/* member role */
"author" = "auteur";
/* No comment provided by engineer. */
"Auto-accept" = "Auto-accepter";
@@ -548,6 +560,9 @@
/* No comment provided by engineer. */
"Back" = "Retour";
/* No comment provided by engineer. */
"Bad desktop address" = "Mauvaise adresse de bureau";
/* integrity error chat item */
"bad message hash" = "hash de message incorrect";
@@ -560,12 +575,18 @@
/* No comment provided by engineer. */
"Bad message ID" = "Mauvais ID de message";
/* No comment provided by engineer. */
"Better groups" = "Des groupes plus performants";
/* No comment provided by engineer. */
"Better messages" = "Meilleurs messages";
/* No comment provided by engineer. */
"Block" = "Bloquer";
/* No comment provided by engineer. */
"Block group members" = "Bloquer des membres d'un groupe";
/* No comment provided by engineer. */
"Block member" = "Bloquer ce membre";
@@ -768,9 +789,15 @@
/* server test step */
"Connect" = "Se connecter";
/* No comment provided by engineer. */
"Connect automatically" = "Connexion automatique";
/* No comment provided by engineer. */
"Connect incognito" = "Se connecter incognito";
/* No comment provided by engineer. */
"Connect to desktop" = "Se connecter au bureau";
/* No comment provided by engineer. */
"connect to SimpleX Chat developers." = "se connecter aux developpeurs de SimpleX Chat.";
@@ -801,9 +828,15 @@
/* No comment provided by engineer. */
"connected" = "connecté";
/* No comment provided by engineer. */
"Connected desktop" = "Bureau connecté";
/* rcv group event chat item */
"connected directly" = "s'est connecté.e de manière directe";
/* No comment provided by engineer. */
"Connected to desktop" = "Connecté au bureau";
/* No comment provided by engineer. */
"connecting" = "connexion";
@@ -828,6 +861,9 @@
/* No comment provided by engineer. */
"Connecting server… (error: %@)" = "Connexion au serveur… (erreur : %@)";
/* No comment provided by engineer. */
"Connecting to desktop" = "Connexion au bureau";
/* chat list item title */
"connecting…" = "connexion…";
@@ -846,6 +882,9 @@
/* No comment provided by engineer. */
"Connection request sent!" = "Demande de connexion envoyée !";
/* No comment provided by engineer. */
"Connection terminated" = "Connexion terminée";
/* No comment provided by engineer. */
"Connection timeout" = "Délai de connexion";
@@ -900,6 +939,9 @@
/* No comment provided by engineer. */
"Create" = "Créer";
/* No comment provided by engineer. */
"Create a group using a random profile." = "Création de groupes via un profil aléatoire.";
/* No comment provided by engineer. */
"Create an address to let people connect with you." = "Créez une adresse pour permettre aux gens de vous contacter.";
@@ -1173,6 +1215,15 @@
/* No comment provided by engineer. */
"Description" = "Description";
/* No comment provided by engineer. */
"Desktop address" = "Adresse de bureau";
/* No comment provided by engineer. */
"Desktop app version %@ is not compatible with this app." = "La version de l'application de bureau %@ n'est pas compatible avec cette application.";
/* No comment provided by engineer. */
"Desktop devices" = "Appareils de bureau";
/* No comment provided by engineer. */
"Develop" = "Développer";
@@ -1236,9 +1287,15 @@
/* server test step */
"Disconnect" = "Se déconnecter";
/* No comment provided by engineer. */
"Disconnect desktop?" = "Déconnecter le bureau ?";
/* No comment provided by engineer. */
"Discover and join groups" = "Découvrir et rejoindre des groupes";
/* No comment provided by engineer. */
"Discover via local network" = "Rechercher sur le réseau";
/* No comment provided by engineer. */
"Do it later" = "Faites-le plus tard";
@@ -1374,6 +1431,12 @@
/* chat item text */
"encryption re-negotiation allowed for %@" = "renégociation de chiffrement autorisée pour %@";
/* message decrypt error item */
"Encryption re-negotiation error" = "Erreur lors de la renégociation du chiffrement";
/* No comment provided by engineer. */
"Encryption re-negotiation failed." = "La renégociation du chiffrement a échoué.";
/* chat item text */
"encryption re-negotiation required" = "renégociation de chiffrement requise";
@@ -1404,6 +1467,9 @@
/* No comment provided by engineer. */
"Enter server manually" = "Entrer un serveur manuellement";
/* No comment provided by engineer. */
"Enter this device name…" = "Entrez le nom de l'appareil…";
/* placeholder */
"Enter welcome message…" = "Entrez un message de bienvenue…";
@@ -1605,6 +1671,9 @@
/* No comment provided by engineer. */
"Fast and no wait until the sender is online!" = "Rapide et ne nécessitant pas d'attendre que l'expéditeur soit en ligne !";
/* No comment provided by engineer. */
"Faster joining and more reliable messages." = "Connexion plus rapide et messages plus fiables.";
/* No comment provided by engineer. */
"Favorite" = "Favoris";
@@ -1662,6 +1731,9 @@
/* No comment provided by engineer. */
"For console" = "Pour la console";
/* No comment provided by engineer. */
"Found desktop" = "Bureau trouvé";
/* No comment provided by engineer. */
"French interface" = "Interface en français";
@@ -1866,6 +1938,9 @@
/* No comment provided by engineer. */
"Incognito" = "Incognito";
/* No comment provided by engineer. */
"Incognito groups" = "Groupes incognito";
/* No comment provided by engineer. */
"Incognito mode" = "Mode Incognito";
@@ -1893,6 +1968,9 @@
/* No comment provided by engineer. */
"Incompatible database version" = "Version de la base de données incompatible";
/* No comment provided by engineer. */
"Incompatible version" = "Version incompatible";
/* PIN entry */
"Incorrect passcode" = "Code d'accès erroné";
@@ -2028,6 +2106,9 @@
/* No comment provided by engineer. */
"Joining group" = "Entrain de rejoindre le groupe";
/* No comment provided by engineer. */
"Keep the app open to use it from desktop" = "Garder l'application ouverte pour l'utiliser depuis le bureau";
/* No comment provided by engineer. */
"Keep your connections" = "Conserver vos connexions";
@@ -2064,6 +2145,15 @@
/* No comment provided by engineer. */
"Limitations" = "Limitations";
/* No comment provided by engineer. */
"Link mobile and desktop apps! 🔗" = "Liez vos applications mobiles et de bureau ! 🔗";
/* No comment provided by engineer. */
"Linked desktop options" = "Options de bureau lié";
/* No comment provided by engineer. */
"Linked desktops" = "Bureaux liés";
/* No comment provided by engineer. */
"LIVE" = "LIVE";
@@ -2319,6 +2409,9 @@
/* copied message info in history */
"no text" = "aucun texte";
/* No comment provided by engineer. */
"Not compatible!" = "Non compatible !";
/* No comment provided by engineer. */
"Notifications" = "Notifications";
@@ -2462,6 +2555,9 @@
/* No comment provided by engineer. */
"Paste" = "Coller";
/* No comment provided by engineer. */
"Paste desktop address" = "Coller l'adresse du bureau";
/* No comment provided by engineer. */
"Paste image" = "Coller l'image";
@@ -2846,6 +2942,9 @@
/* No comment provided by engineer. */
"Scan QR code" = "Scanner un code QR";
/* No comment provided by engineer. */
"Scan QR code from desktop" = "Scanner le code QR du bureau";
/* No comment provided by engineer. */
"Scan security code from your contact's app." = "Scannez le code de sécurité depuis l'application de votre contact.";
@@ -2990,6 +3089,9 @@
/* No comment provided by engineer. */
"Servers" = "Serveurs";
/* No comment provided by engineer. */
"Session code" = "Code de session";
/* No comment provided by engineer. */
"Set 1 day" = "Définir 1 jour";
@@ -3299,6 +3401,9 @@
/* notification title */
"this contact" = "ce contact";
/* No comment provided by engineer. */
"This device name" = "Ce nom d'appareil";
/* No comment provided by engineer. */
"This group has over %lld members, delivery receipts are not sent." = "Ce groupe compte plus de %lld membres, les accusés de réception ne sont pas envoyés.";
@@ -3320,6 +3425,9 @@
/* No comment provided by engineer. */
"To connect, your contact can scan QR code or use the link in the app." = "Pour se connecter, votre contact peut scanner le code QR ou utiliser le lien dans l'application.";
/* No comment provided by engineer. */
"To hide unwanted messages." = "Pour cacher les messages indésirables.";
/* No comment provided by engineer. */
"To make a new connection" = "Pour établir une nouvelle connexion";
@@ -3416,6 +3524,12 @@
/* No comment provided by engineer. */
"Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection." = "A moins que votre contact ait supprimé la connexion ou que ce lien ait déjà été utilisé, il peut s'agir d'un bug - veuillez le signaler.\nPour vous connecter, veuillez demander à votre contact de créer un autre lien de connexion et vérifiez que vous disposez d'une connexion réseau stable.";
/* No comment provided by engineer. */
"Unlink" = "Délier";
/* No comment provided by engineer. */
"Unlink desktop?" = "Délier le bureau ?";
/* No comment provided by engineer. */
"Unlock" = "Déverrouiller";
@@ -3470,6 +3584,9 @@
/* No comment provided by engineer. */
"Use for new connections" = "Utiliser pour les nouvelles connexions";
/* No comment provided by engineer. */
"Use from desktop" = "Utilisation depuis le bureau";
/* No comment provided by engineer. */
"Use iOS call interface" = "Utiliser l'interface d'appel d'iOS";
@@ -3491,12 +3608,24 @@
/* No comment provided by engineer. */
"Using SimpleX Chat servers." = "Utilisation des serveurs SimpleX Chat.";
/* No comment provided by engineer. */
"v%@" = "v%@";
/* No comment provided by engineer. */
"v%@ (%@)" = "v%@ (%@)";
/* No comment provided by engineer. */
"Verify code with desktop" = "Vérifier le code avec le bureau";
/* No comment provided by engineer. */
"Verify connection" = "Vérifier la connexion";
/* No comment provided by engineer. */
"Verify connection security" = "Vérifier la sécurité de la connexion";
/* No comment provided by engineer. */
"Verify connections" = "Vérifier les connexions";
/* No comment provided by engineer. */
"Verify security code" = "Vérifier le code de sécurité";
@@ -3515,6 +3644,9 @@
/* No comment provided by engineer. */
"via relay" = "via relais";
/* No comment provided by engineer. */
"Via secure quantum resistant protocol." = "Via un protocole sécurisé de cryptographie post-quantique.";
/* No comment provided by engineer. */
"Video call" = "Appel vidéo";
@@ -3554,6 +3686,9 @@
/* No comment provided by engineer. */
"waiting for confirmation…" = "en attente de confirmation…";
/* No comment provided by engineer. */
"Waiting for desktop..." = "En attente du bureau...";
/* No comment provided by engineer. */
"Waiting for file" = "En attente du fichier";
@@ -7,6 +7,9 @@
/* Privacy - Face ID Usage Description */
"NSFaceIDUsageDescription" = "SimpleGroup not found!X utilise Face ID pour l'authentification locale";
/* Privacy - Local Network Usage Description */
"NSLocalNetworkUsageDescription" = "SimpleX utilise un accès au réseau local pour permettre l'utilisation du profil de chat de l'utilisateur via l'application de bureau au sein de ce même réseau.";
/* Privacy - Microphone Usage Description */
"NSMicrophoneUsageDescription" = "SimpleX a besoin d'un accès au microphone pour les appels audio et vidéo ainsi que pour enregistrer des messages vocaux.";
+117
View File
@@ -25,6 +25,9 @@
/* No comment provided by engineer. */
"- more stable message delivery.\n- a bit better groups.\n- and more!" = "- recapito dei messaggi più stabile.\n- gruppi un po' migliorati.\n- e altro ancora!";
/* No comment provided by engineer. */
"- optionally notify deleted contacts.\n- profile names with spaces.\n- and more!" = "- avvisa facoltativamente i contatti eliminati.\n- nomi del profilo con spazi.\n- e molto altro!";
/* No comment provided by engineer. */
"- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- messaggi vocali fino a 5 minuti.\n- tempo di scomparsa personalizzato.\n- cronologia delle modifiche.";
@@ -43,6 +46,12 @@
/* No comment provided by engineer. */
"(" = "(";
/* No comment provided by engineer. */
"(new)" = "(nuovo)";
/* No comment provided by engineer. */
"(this device v%@)" = "(questo dispositivo v%@)";
/* No comment provided by engineer. */
")" = ")";
@@ -548,6 +557,9 @@
/* No comment provided by engineer. */
"Back" = "Indietro";
/* No comment provided by engineer. */
"Bad desktop address" = "Indirizzo desktop errato";
/* integrity error chat item */
"bad message hash" = "hash del messaggio errato";
@@ -560,12 +572,18 @@
/* No comment provided by engineer. */
"Bad message ID" = "ID del messaggio errato";
/* No comment provided by engineer. */
"Better groups" = "Gruppi migliorati";
/* No comment provided by engineer. */
"Better messages" = "Messaggi migliorati";
/* No comment provided by engineer. */
"Block" = "Blocca";
/* No comment provided by engineer. */
"Block group members" = "Blocca i membri dei gruppi";
/* No comment provided by engineer. */
"Block member" = "Blocca membro";
@@ -771,6 +789,9 @@
/* No comment provided by engineer. */
"Connect incognito" = "Connetti in incognito";
/* No comment provided by engineer. */
"Connect to desktop" = "Connetti al desktop";
/* No comment provided by engineer. */
"connect to SimpleX Chat developers." = "connettiti agli sviluppatori di SimpleX Chat.";
@@ -801,9 +822,15 @@
/* No comment provided by engineer. */
"connected" = "connesso/a";
/* No comment provided by engineer. */
"Connected desktop" = "Desktop connesso";
/* rcv group event chat item */
"connected directly" = "si è connesso/a direttamente";
/* No comment provided by engineer. */
"Connected to desktop" = "Connesso al desktop";
/* No comment provided by engineer. */
"connecting" = "in connessione";
@@ -828,6 +855,9 @@
/* No comment provided by engineer. */
"Connecting server… (error: %@)" = "Connessione al server… (errore: %@)";
/* No comment provided by engineer. */
"Connecting to desktop" = "Connessione al desktop";
/* chat list item title */
"connecting…" = "in connessione…";
@@ -846,6 +876,9 @@
/* No comment provided by engineer. */
"Connection request sent!" = "Richiesta di connessione inviata!";
/* No comment provided by engineer. */
"Connection terminated" = "Connessione terminata";
/* No comment provided by engineer. */
"Connection timeout" = "Connessione scaduta";
@@ -900,6 +933,9 @@
/* No comment provided by engineer. */
"Create" = "Crea";
/* No comment provided by engineer. */
"Create a group using a random profile." = "Crea un gruppo usando un profilo casuale.";
/* No comment provided by engineer. */
"Create an address to let people connect with you." = "Crea un indirizzo per consentire alle persone di connettersi con te.";
@@ -1173,6 +1209,15 @@
/* No comment provided by engineer. */
"Description" = "Descrizione";
/* No comment provided by engineer. */
"Desktop address" = "Indirizzo desktop";
/* No comment provided by engineer. */
"Desktop app version %@ is not compatible with this app." = "La versione dell'app desktop %@ non è compatibile con questa app.";
/* No comment provided by engineer. */
"Desktop devices" = "Dispositivi desktop";
/* No comment provided by engineer. */
"Develop" = "Sviluppa";
@@ -1236,6 +1281,9 @@
/* server test step */
"Disconnect" = "Disconnetti";
/* No comment provided by engineer. */
"Disconnect desktop?" = "Disconnettere il desktop?";
/* No comment provided by engineer. */
"Discover and join groups" = "Scopri ed unisciti ai gruppi";
@@ -1374,6 +1422,12 @@
/* chat item text */
"encryption re-negotiation allowed for %@" = "rinegoziazione della crittografia consentita per %@";
/* message decrypt error item */
"Encryption re-negotiation error" = "Errore di rinegoziazione crittografia";
/* No comment provided by engineer. */
"Encryption re-negotiation failed." = "Rinegoziazione crittografia fallita.";
/* chat item text */
"encryption re-negotiation required" = "richiesta rinegoziazione della crittografia";
@@ -1404,6 +1458,9 @@
/* No comment provided by engineer. */
"Enter server manually" = "Inserisci il server a mano";
/* No comment provided by engineer. */
"Enter this device name…" = "Inserisci il nome di questo dispositivo…";
/* placeholder */
"Enter welcome message…" = "Inserisci il messaggio di benvenuto…";
@@ -1605,6 +1662,9 @@
/* No comment provided by engineer. */
"Fast and no wait until the sender is online!" = "Veloce e senza aspettare che il mittente sia in linea!";
/* No comment provided by engineer. */
"Faster joining and more reliable messages." = "Ingresso più veloce e messaggi più affidabili.";
/* No comment provided by engineer. */
"Favorite" = "Preferito";
@@ -1866,6 +1926,9 @@
/* No comment provided by engineer. */
"Incognito" = "Incognito";
/* No comment provided by engineer. */
"Incognito groups" = "Gruppi in incognito";
/* No comment provided by engineer. */
"Incognito mode" = "Modalità incognito";
@@ -1893,6 +1956,9 @@
/* No comment provided by engineer. */
"Incompatible database version" = "Versione del database incompatibile";
/* No comment provided by engineer. */
"Incompatible version" = "Versione incompatibile";
/* PIN entry */
"Incorrect passcode" = "Codice di accesso errato";
@@ -2028,6 +2094,9 @@
/* No comment provided by engineer. */
"Joining group" = "Ingresso nel gruppo";
/* No comment provided by engineer. */
"Keep the app open to use it from desktop" = "Tieni aperta l'app per usarla dal desktop";
/* No comment provided by engineer. */
"Keep your connections" = "Mantieni le tue connessioni";
@@ -2064,6 +2133,15 @@
/* No comment provided by engineer. */
"Limitations" = "Limitazioni";
/* No comment provided by engineer. */
"Link mobile and desktop apps! 🔗" = "Collega le app mobile e desktop! 🔗";
/* No comment provided by engineer. */
"Linked desktop options" = "Opzioni del desktop collegato";
/* No comment provided by engineer. */
"Linked desktops" = "Desktop collegati";
/* No comment provided by engineer. */
"LIVE" = "IN DIRETTA";
@@ -2462,6 +2540,9 @@
/* No comment provided by engineer. */
"Paste" = "Incolla";
/* No comment provided by engineer. */
"Paste desktop address" = "Incolla l'indirizzo desktop";
/* No comment provided by engineer. */
"Paste image" = "Incolla immagine";
@@ -2846,6 +2927,9 @@
/* No comment provided by engineer. */
"Scan QR code" = "Scansiona codice QR";
/* No comment provided by engineer. */
"Scan QR code from desktop" = "Scansiona codice QR dal desktop";
/* No comment provided by engineer. */
"Scan security code from your contact's app." = "Scansiona il codice di sicurezza dall'app del tuo contatto.";
@@ -2990,6 +3074,9 @@
/* No comment provided by engineer. */
"Servers" = "Server";
/* No comment provided by engineer. */
"Session code" = "Codice di sessione";
/* No comment provided by engineer. */
"Set 1 day" = "Imposta 1 giorno";
@@ -3299,6 +3386,9 @@
/* notification title */
"this contact" = "questo contatto";
/* No comment provided by engineer. */
"This device name" = "Il nome di questo dispositivo";
/* No comment provided by engineer. */
"This group has over %lld members, delivery receipts are not sent." = "Questo gruppo ha più di %lld membri, le ricevute di consegna non vengono inviate.";
@@ -3320,6 +3410,9 @@
/* No comment provided by engineer. */
"To connect, your contact can scan QR code or use the link in the app." = "Per connettervi, il tuo contatto può scansionare il codice QR o usare il link nell'app.";
/* No comment provided by engineer. */
"To hide unwanted messages." = "Per nascondere messaggi indesiderati.";
/* No comment provided by engineer. */
"To make a new connection" = "Per creare una nuova connessione";
@@ -3416,6 +3509,12 @@
/* No comment provided by engineer. */
"Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection." = "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.";
/* No comment provided by engineer. */
"Unlink" = "Scollega";
/* No comment provided by engineer. */
"Unlink desktop?" = "Scollegare il desktop?";
/* No comment provided by engineer. */
"Unlock" = "Sblocca";
@@ -3470,6 +3569,9 @@
/* No comment provided by engineer. */
"Use for new connections" = "Usa per connessioni nuove";
/* No comment provided by engineer. */
"Use from desktop" = "Usa dal desktop";
/* No comment provided by engineer. */
"Use iOS call interface" = "Usa interfaccia di chiamata iOS";
@@ -3491,12 +3593,24 @@
/* No comment provided by engineer. */
"Using SimpleX Chat servers." = "Utilizzo dei server SimpleX Chat.";
/* No comment provided by engineer. */
"v%@" = "v%@";
/* No comment provided by engineer. */
"v%@ (%@)" = "v%@ (%@)";
/* No comment provided by engineer. */
"Verify code with desktop" = "Verifica il codice con il desktop";
/* No comment provided by engineer. */
"Verify connection" = "Verifica la connessione";
/* No comment provided by engineer. */
"Verify connection security" = "Verifica la sicurezza della connessione";
/* No comment provided by engineer. */
"Verify connections" = "Verifica le connessioni";
/* No comment provided by engineer. */
"Verify security code" = "Verifica codice di sicurezza";
@@ -3515,6 +3629,9 @@
/* No comment provided by engineer. */
"via relay" = "via relay";
/* No comment provided by engineer. */
"Via secure quantum resistant protocol." = "Tramite protocollo sicuro resistente alla quantistica.";
/* No comment provided by engineer. */
"Video call" = "Videochiamata";
@@ -7,6 +7,9 @@
/* Privacy - Face ID Usage Description */
"NSFaceIDUsageDescription" = "SimpleX usa Face ID per l'autenticazione locale";
/* Privacy - Local Network Usage Description */
"NSLocalNetworkUsageDescription" = "SimpleX usa l'accesso alla rete locale per consentire di usare il profilo di chat tramite l'app desktop sulla stessa rete.";
/* Privacy - Microphone Usage Description */
"NSMicrophoneUsageDescription" = "SimpleX ha bisogno dell'accesso al microfono per le chiamate audio e video e per registrare messaggi vocali.";
+135
View File
@@ -25,6 +25,9 @@
/* No comment provided by engineer. */
"- more stable message delivery.\n- a bit better groups.\n- and more!" = "- stabielere berichtbezorging.\n- een beetje betere groepen.\n- en meer!";
/* No comment provided by engineer. */
"- optionally notify deleted contacts.\n- profile names with spaces.\n- and more!" = "- optioneel verwijderde contacten op de hoogte stellen.\n- profielnamen met spaties.\n- en meer!";
/* No comment provided by engineer. */
"- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- spraakberichten tot 5 minuten.\n- aangepaste tijd om te verdwijnen.\n- bewerkingsgeschiedenis.";
@@ -43,6 +46,12 @@
/* No comment provided by engineer. */
"(" = "(";
/* No comment provided by engineer. */
"(new)" = "(nieuw)";
/* No comment provided by engineer. */
"(this device v%@)" = "(dit apparaat v%@)";
/* No comment provided by engineer. */
")" = ")";
@@ -536,6 +545,9 @@
/* No comment provided by engineer. */
"Authentication unavailable" = "Verificatie niet beschikbaar";
/* member role */
"author" = "auteur";
/* No comment provided by engineer. */
"Auto-accept" = "Automatisch accepteren";
@@ -548,6 +560,9 @@
/* No comment provided by engineer. */
"Back" = "Terug";
/* No comment provided by engineer. */
"Bad desktop address" = "Onjuist desktopadres";
/* integrity error chat item */
"bad message hash" = "Onjuiste bericht hash";
@@ -560,12 +575,18 @@
/* No comment provided by engineer. */
"Bad message ID" = "Onjuiste bericht-ID";
/* No comment provided by engineer. */
"Better groups" = "Betere groepen";
/* No comment provided by engineer. */
"Better messages" = "Betere berichten";
/* No comment provided by engineer. */
"Block" = "Blokkeren";
/* No comment provided by engineer. */
"Block group members" = "Groepsleden blokkeren";
/* No comment provided by engineer. */
"Block member" = "Lid blokkeren";
@@ -768,9 +789,15 @@
/* server test step */
"Connect" = "Verbind";
/* No comment provided by engineer. */
"Connect automatically" = "Automatisch verbinden";
/* No comment provided by engineer. */
"Connect incognito" = "Verbind incognito";
/* No comment provided by engineer. */
"Connect to desktop" = "Verbinden met desktop";
/* No comment provided by engineer. */
"connect to SimpleX Chat developers." = "maak verbinding met SimpleX Chat-ontwikkelaars.";
@@ -801,9 +828,15 @@
/* No comment provided by engineer. */
"connected" = "verbonden";
/* No comment provided by engineer. */
"Connected desktop" = "Verbonden desktop";
/* rcv group event chat item */
"connected directly" = "direct verbonden";
/* No comment provided by engineer. */
"Connected to desktop" = "Verbonden met desktop";
/* No comment provided by engineer. */
"connecting" = "Verbinden";
@@ -828,6 +861,9 @@
/* No comment provided by engineer. */
"Connecting server… (error: %@)" = "Verbinden met server... (fout: %@)";
/* No comment provided by engineer. */
"Connecting to desktop" = "Verbinding maken met desktop";
/* chat list item title */
"connecting…" = "Verbinden…";
@@ -846,6 +882,9 @@
/* No comment provided by engineer. */
"Connection request sent!" = "Verbindingsverzoek verzonden!";
/* No comment provided by engineer. */
"Connection terminated" = "Verbinding beëindigd";
/* No comment provided by engineer. */
"Connection timeout" = "Timeout verbinding";
@@ -900,6 +939,9 @@
/* No comment provided by engineer. */
"Create" = "Maak";
/* No comment provided by engineer. */
"Create a group using a random profile." = "Maak een groep met een willekeurig profiel.";
/* No comment provided by engineer. */
"Create an address to let people connect with you." = "Maak een adres aan zodat mensen contact met je kunnen opnemen.";
@@ -1173,6 +1215,15 @@
/* No comment provided by engineer. */
"Description" = "Beschrijving";
/* No comment provided by engineer. */
"Desktop address" = "Desktop adres";
/* No comment provided by engineer. */
"Desktop app version %@ is not compatible with this app." = "Desktop-app-versie %@ is niet compatibel met deze app.";
/* No comment provided by engineer. */
"Desktop devices" = "Desktop apparaten";
/* No comment provided by engineer. */
"Develop" = "Ontwikkelen";
@@ -1236,9 +1287,15 @@
/* server test step */
"Disconnect" = "verbinding verbreken";
/* No comment provided by engineer. */
"Disconnect desktop?" = "Desktop loskoppelen?";
/* No comment provided by engineer. */
"Discover and join groups" = "Ontdek en sluit je aan bij groepen";
/* No comment provided by engineer. */
"Discover via local network" = "Ontdek via het lokale netwerk";
/* No comment provided by engineer. */
"Do it later" = "Doe het later";
@@ -1374,6 +1431,12 @@
/* chat item text */
"encryption re-negotiation allowed for %@" = "versleuteling heronderhandeling toegestaan voor % @";
/* message decrypt error item */
"Encryption re-negotiation error" = "Fout bij heronderhandeling van codering";
/* No comment provided by engineer. */
"Encryption re-negotiation failed." = "Opnieuw onderhandelen over de codering is mislukt.";
/* chat item text */
"encryption re-negotiation required" = "heronderhandeling van versleuteling vereist";
@@ -1404,6 +1467,9 @@
/* No comment provided by engineer. */
"Enter server manually" = "Voer de server handmatig in";
/* No comment provided by engineer. */
"Enter this device name…" = "Voer deze apparaatnaam in…";
/* placeholder */
"Enter welcome message…" = "Welkomst bericht invoeren…";
@@ -1605,6 +1671,9 @@
/* No comment provided by engineer. */
"Fast and no wait until the sender is online!" = "Snel en niet wachten tot de afzender online is!";
/* No comment provided by engineer. */
"Faster joining and more reliable messages." = "Snellere deelname en betrouwbaardere berichten.";
/* No comment provided by engineer. */
"Favorite" = "Favoriet";
@@ -1662,6 +1731,9 @@
/* No comment provided by engineer. */
"For console" = "Voor console";
/* No comment provided by engineer. */
"Found desktop" = "Desktop gevonden";
/* No comment provided by engineer. */
"French interface" = "Franse interface";
@@ -1866,6 +1938,9 @@
/* No comment provided by engineer. */
"Incognito" = "Incognito";
/* No comment provided by engineer. */
"Incognito groups" = "Incognitogroepen";
/* No comment provided by engineer. */
"Incognito mode" = "Incognito modus";
@@ -1893,6 +1968,9 @@
/* No comment provided by engineer. */
"Incompatible database version" = "Incompatibele database versie";
/* No comment provided by engineer. */
"Incompatible version" = "Incompatibele versie";
/* PIN entry */
"Incorrect passcode" = "Onjuiste toegangscode";
@@ -2028,6 +2106,9 @@
/* No comment provided by engineer. */
"Joining group" = "Deel nemen aan groep";
/* No comment provided by engineer. */
"Keep the app open to use it from desktop" = "Houd de app geopend om deze vanaf de desktop te gebruiken";
/* No comment provided by engineer. */
"Keep your connections" = "Behoud uw verbindingen";
@@ -2064,6 +2145,15 @@
/* No comment provided by engineer. */
"Limitations" = "Beperkingen";
/* No comment provided by engineer. */
"Link mobile and desktop apps! 🔗" = "Koppel mobiele en desktop-apps! 🔗";
/* No comment provided by engineer. */
"Linked desktop options" = "Gekoppelde desktop opties";
/* No comment provided by engineer. */
"Linked desktops" = "Gelinkte desktops";
/* No comment provided by engineer. */
"LIVE" = "LIVE";
@@ -2319,6 +2409,9 @@
/* copied message info in history */
"no text" = "geen tekst";
/* No comment provided by engineer. */
"Not compatible!" = "Niet compatibel!";
/* No comment provided by engineer. */
"Notifications" = "Meldingen";
@@ -2462,6 +2555,9 @@
/* No comment provided by engineer. */
"Paste" = "Plakken";
/* No comment provided by engineer. */
"Paste desktop address" = "Desktopadres plakken";
/* No comment provided by engineer. */
"Paste image" = "Afbeelding plakken";
@@ -2846,6 +2942,9 @@
/* No comment provided by engineer. */
"Scan QR code" = "Scan QR-code";
/* No comment provided by engineer. */
"Scan QR code from desktop" = "Scan QR-code vanaf uw desktop";
/* No comment provided by engineer. */
"Scan security code from your contact's app." = "Scan de beveiligingscode van de app van uw contact.";
@@ -2990,6 +3089,9 @@
/* No comment provided by engineer. */
"Servers" = "Servers";
/* No comment provided by engineer. */
"Session code" = "Sessie code";
/* No comment provided by engineer. */
"Set 1 day" = "Stel 1 dag in";
@@ -3299,6 +3401,9 @@
/* notification title */
"this contact" = "dit contact";
/* No comment provided by engineer. */
"This device name" = "Deze apparaatnaam";
/* No comment provided by engineer. */
"This group has over %lld members, delivery receipts are not sent." = "Deze groep heeft meer dan %lld -leden, ontvangstbevestigingen worden niet verzonden.";
@@ -3320,6 +3425,9 @@
/* No comment provided by engineer. */
"To connect, your contact can scan QR code or use the link in the app." = "Om verbinding te maken, kan uw contact de QR-code scannen of de link in de app gebruiken.";
/* No comment provided by engineer. */
"To hide unwanted messages." = "Om ongewenste berichten te verbergen.";
/* No comment provided by engineer. */
"To make a new connection" = "Om een nieuwe verbinding te maken";
@@ -3416,6 +3524,12 @@
/* No comment provided by engineer. */
"Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection." = "Tenzij uw contact de verbinding heeft verwijderd of deze link al is gebruikt, kan het een bug zijn. Meld het alstublieft.\nOm verbinding te maken, vraagt u uw contact om een andere verbinding link te maken en te controleren of u een stabiele netwerkverbinding heeft.";
/* No comment provided by engineer. */
"Unlink" = "Ontkoppelen";
/* No comment provided by engineer. */
"Unlink desktop?" = "Desktop ontkoppelen?";
/* No comment provided by engineer. */
"Unlock" = "Ontgrendelen";
@@ -3470,6 +3584,9 @@
/* No comment provided by engineer. */
"Use for new connections" = "Gebruik voor nieuwe verbindingen";
/* No comment provided by engineer. */
"Use from desktop" = "Gebruik vanaf desktop";
/* No comment provided by engineer. */
"Use iOS call interface" = "De iOS-oproepinterface gebruiken";
@@ -3491,12 +3608,24 @@
/* No comment provided by engineer. */
"Using SimpleX Chat servers." = "SimpleX Chat servers gebruiken.";
/* No comment provided by engineer. */
"v%@" = "v%@";
/* No comment provided by engineer. */
"v%@ (%@)" = "v%@ (%@)";
/* No comment provided by engineer. */
"Verify code with desktop" = "Code verifiëren met desktop";
/* No comment provided by engineer. */
"Verify connection" = "Controleer de verbinding";
/* No comment provided by engineer. */
"Verify connection security" = "Controleer de verbindingsbeveiliging";
/* No comment provided by engineer. */
"Verify connections" = "Controleer verbindingen";
/* No comment provided by engineer. */
"Verify security code" = "Controleer de beveiligingscode";
@@ -3515,6 +3644,9 @@
/* No comment provided by engineer. */
"via relay" = "via relais";
/* No comment provided by engineer. */
"Via secure quantum resistant protocol." = "Via een beveiligd kwantumbestendig protocol.";
/* No comment provided by engineer. */
"Video call" = "video oproep";
@@ -3554,6 +3686,9 @@
/* No comment provided by engineer. */
"waiting for confirmation…" = "Wachten op bevestiging…";
/* No comment provided by engineer. */
"Waiting for desktop..." = "Wachten op desktop...";
/* No comment provided by engineer. */
"Waiting for file" = "Wachten op bestand";
@@ -7,6 +7,9 @@
/* Privacy - Face ID Usage Description */
"NSFaceIDUsageDescription" = "SimpleX gebruikt Face-ID voor lokale authenticatie";
/* Privacy - Local Network Usage Description */
"NSLocalNetworkUsageDescription" = "SimpleX maakt gebruik van lokale netwerktoegang om het gebruik van een gebruikerschatprofiel via de desktop-app op hetzelfde netwerk mogelijk te maken.";
/* Privacy - Microphone Usage Description */
"NSMicrophoneUsageDescription" = "SimpleX heeft microfoon toegang nodig voor audio en video oproepen en om spraak berichten op te nemen.";
+135
View File
@@ -25,6 +25,9 @@
/* No comment provided by engineer. */
"- more stable message delivery.\n- a bit better groups.\n- and more!" = "- bardziej stabilne dostarczanie wiadomości.\n- nieco lepsze grupy.\n- i więcej!";
/* No comment provided by engineer. */
"- optionally notify deleted contacts.\n- profile names with spaces.\n- and more!" = "- opcjonalnie powiadamiaj usunięte kontakty.\n- nazwy profili ze spacją.\n- i wiele więcej!";
/* No comment provided by engineer. */
"- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- wiadomości głosowe do 5 minut.\n- niestandardowy czas zniknięcia.\n- historia edycji.";
@@ -43,6 +46,12 @@
/* No comment provided by engineer. */
"(" = "(";
/* No comment provided by engineer. */
"(new)" = "(nowy)";
/* No comment provided by engineer. */
"(this device v%@)" = "(to urządzenie v%@)";
/* No comment provided by engineer. */
")" = ")";
@@ -536,6 +545,9 @@
/* No comment provided by engineer. */
"Authentication unavailable" = "Uwierzytelnianie niedostępne";
/* member role */
"author" = "autor";
/* No comment provided by engineer. */
"Auto-accept" = "Automatycznie akceptuj";
@@ -548,6 +560,9 @@
/* No comment provided by engineer. */
"Back" = "Wstecz";
/* No comment provided by engineer. */
"Bad desktop address" = "Zły adres komputera";
/* integrity error chat item */
"bad message hash" = "zły hash wiadomości";
@@ -560,12 +575,18 @@
/* No comment provided by engineer. */
"Bad message ID" = "Zły identyfikator wiadomości";
/* No comment provided by engineer. */
"Better groups" = "Lepsze grupy";
/* No comment provided by engineer. */
"Better messages" = "Lepsze wiadomości";
/* No comment provided by engineer. */
"Block" = "Zablokuj";
/* No comment provided by engineer. */
"Block group members" = "Blokuj członków grupy";
/* No comment provided by engineer. */
"Block member" = "Zablokuj członka";
@@ -768,9 +789,15 @@
/* server test step */
"Connect" = "Połącz";
/* No comment provided by engineer. */
"Connect automatically" = "Łącz automatycznie";
/* No comment provided by engineer. */
"Connect incognito" = "Połącz incognito";
/* No comment provided by engineer. */
"Connect to desktop" = "Połącz do komputera";
/* No comment provided by engineer. */
"connect to SimpleX Chat developers." = "połącz się z deweloperami SimpleX Chat.";
@@ -801,9 +828,15 @@
/* No comment provided by engineer. */
"connected" = "połączony";
/* No comment provided by engineer. */
"Connected desktop" = "Połączony komputer";
/* rcv group event chat item */
"connected directly" = "połącz bezpośrednio";
/* No comment provided by engineer. */
"Connected to desktop" = "Połączony do komputera";
/* No comment provided by engineer. */
"connecting" = "łączenie";
@@ -828,6 +861,9 @@
/* No comment provided by engineer. */
"Connecting server… (error: %@)" = "Łączenie z serwerem... (błąd: %@)";
/* No comment provided by engineer. */
"Connecting to desktop" = "Łączenie z komputerem";
/* chat list item title */
"connecting…" = "łączenie…";
@@ -846,6 +882,9 @@
/* No comment provided by engineer. */
"Connection request sent!" = "Prośba o połączenie wysłana!";
/* No comment provided by engineer. */
"Connection terminated" = "Połączenie zakończone";
/* No comment provided by engineer. */
"Connection timeout" = "Czas połączenia minął";
@@ -900,6 +939,9 @@
/* No comment provided by engineer. */
"Create" = "Utwórz";
/* No comment provided by engineer. */
"Create a group using a random profile." = "Utwórz grupę używając losowego profilu.";
/* No comment provided by engineer. */
"Create an address to let people connect with you." = "Utwórz adres, aby ludzie mogli się z Tobą połączyć.";
@@ -1173,6 +1215,15 @@
/* No comment provided by engineer. */
"Description" = "Opis";
/* No comment provided by engineer. */
"Desktop address" = "Adres komputera";
/* No comment provided by engineer. */
"Desktop app version %@ is not compatible with this app." = "Wersja aplikacji komputerowej %@ nie jest kompatybilna z tą aplikacją.";
/* No comment provided by engineer. */
"Desktop devices" = "Urządzenia komputerowe";
/* No comment provided by engineer. */
"Develop" = "Deweloperskie";
@@ -1236,9 +1287,15 @@
/* server test step */
"Disconnect" = "Rozłącz";
/* No comment provided by engineer. */
"Disconnect desktop?" = "Rozłączyć komputer?";
/* No comment provided by engineer. */
"Discover and join groups" = "Odkrywaj i dołączaj do grup";
/* No comment provided by engineer. */
"Discover via local network" = "Odkryj przez sieć lokalną";
/* No comment provided by engineer. */
"Do it later" = "Zrób to później";
@@ -1374,6 +1431,12 @@
/* chat item text */
"encryption re-negotiation allowed for %@" = "renegocjacja szyfrowania dozwolona dla %@";
/* message decrypt error item */
"Encryption re-negotiation error" = "Błąd renegocjacji szyfrowania";
/* No comment provided by engineer. */
"Encryption re-negotiation failed." = "Renegocjacja szyfrowania nie powiodła się.";
/* chat item text */
"encryption re-negotiation required" = "renegocjacja szyfrowania wymagana";
@@ -1404,6 +1467,9 @@
/* No comment provided by engineer. */
"Enter server manually" = "Wprowadź serwer ręcznie";
/* No comment provided by engineer. */
"Enter this device name…" = "Podaj nazwę urządzenia…";
/* placeholder */
"Enter welcome message…" = "Wpisz wiadomość powitalną…";
@@ -1605,6 +1671,9 @@
/* No comment provided by engineer. */
"Fast and no wait until the sender is online!" = "Szybko i bez czekania aż nadawca będzie online!";
/* No comment provided by engineer. */
"Faster joining and more reliable messages." = "Szybsze dołączenie i bardziej niezawodne wiadomości.";
/* No comment provided by engineer. */
"Favorite" = "Ulubione";
@@ -1662,6 +1731,9 @@
/* No comment provided by engineer. */
"For console" = "Dla konsoli";
/* No comment provided by engineer. */
"Found desktop" = "Znaleziono komputer";
/* No comment provided by engineer. */
"French interface" = "Francuski interfejs";
@@ -1866,6 +1938,9 @@
/* No comment provided by engineer. */
"Incognito" = "Incognito";
/* No comment provided by engineer. */
"Incognito groups" = "Grupy incognito";
/* No comment provided by engineer. */
"Incognito mode" = "Tryb incognito";
@@ -1893,6 +1968,9 @@
/* No comment provided by engineer. */
"Incompatible database version" = "Niekompatybilna wersja bazy danych";
/* No comment provided by engineer. */
"Incompatible version" = "Niekompatybilna wersja";
/* PIN entry */
"Incorrect passcode" = "Nieprawidłowy pin";
@@ -2028,6 +2106,9 @@
/* No comment provided by engineer. */
"Joining group" = "Dołączanie do grupy";
/* No comment provided by engineer. */
"Keep the app open to use it from desktop" = "Zostaw aplikację otwartą i używaj ją z komputera";
/* No comment provided by engineer. */
"Keep your connections" = "Zachowaj swoje połączenia";
@@ -2064,6 +2145,15 @@
/* No comment provided by engineer. */
"Limitations" = "Ograniczenia";
/* No comment provided by engineer. */
"Link mobile and desktop apps! 🔗" = "Połącz mobile i komputerowe aplikacje! 🔗";
/* No comment provided by engineer. */
"Linked desktop options" = "Połączone opcje komputera";
/* No comment provided by engineer. */
"Linked desktops" = "Połączone komputery";
/* No comment provided by engineer. */
"LIVE" = "NA ŻYWO";
@@ -2319,6 +2409,9 @@
/* copied message info in history */
"no text" = "brak tekstu";
/* No comment provided by engineer. */
"Not compatible!" = "Nie kompatybilny!";
/* No comment provided by engineer. */
"Notifications" = "Powiadomienia";
@@ -2462,6 +2555,9 @@
/* No comment provided by engineer. */
"Paste" = "Wklej";
/* No comment provided by engineer. */
"Paste desktop address" = "Wklej adres komputera";
/* No comment provided by engineer. */
"Paste image" = "Wklej obraz";
@@ -2846,6 +2942,9 @@
/* No comment provided by engineer. */
"Scan QR code" = "Zeskanuj kod QR";
/* No comment provided by engineer. */
"Scan QR code from desktop" = "Zeskanuj kod QR z komputera";
/* No comment provided by engineer. */
"Scan security code from your contact's app." = "Zeskanuj kod bezpieczeństwa z aplikacji Twojego kontaktu.";
@@ -2990,6 +3089,9 @@
/* No comment provided by engineer. */
"Servers" = "Serwery";
/* No comment provided by engineer. */
"Session code" = "Kod sesji";
/* No comment provided by engineer. */
"Set 1 day" = "Ustaw 1 dzień";
@@ -3299,6 +3401,9 @@
/* notification title */
"this contact" = "ten kontakt";
/* No comment provided by engineer. */
"This device name" = "Nazwa tego urządzenia";
/* No comment provided by engineer. */
"This group has over %lld members, delivery receipts are not sent." = "Ta grupa ma ponad %lld członków, potwierdzenia dostawy nie są wysyłane.";
@@ -3320,6 +3425,9 @@
/* No comment provided by engineer. */
"To connect, your contact can scan QR code or use the link in the app." = "Aby się połączyć, Twój kontakt może zeskanować kod QR lub skorzystać z linku w aplikacji.";
/* No comment provided by engineer. */
"To hide unwanted messages." = "Aby ukryć niechciane wiadomości.";
/* No comment provided by engineer. */
"To make a new connection" = "Aby nawiązać nowe połączenie";
@@ -3416,6 +3524,12 @@
/* No comment provided by engineer. */
"Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection." = "O ile Twój kontakt nie usunął połączenia lub ten link był już użyty, może to być błąd - zgłoś go.\nAby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połączenia i sprawdź, czy masz stabilne połączenie z siecią.";
/* No comment provided by engineer. */
"Unlink" = "Odłącz";
/* No comment provided by engineer. */
"Unlink desktop?" = "Odłączyć komputer?";
/* No comment provided by engineer. */
"Unlock" = "Odblokuj";
@@ -3470,6 +3584,9 @@
/* No comment provided by engineer. */
"Use for new connections" = "Użyj dla nowych połączeń";
/* No comment provided by engineer. */
"Use from desktop" = "Użyj z komputera";
/* No comment provided by engineer. */
"Use iOS call interface" = "Użyj interfejsu połączeń iOS";
@@ -3491,12 +3608,24 @@
/* No comment provided by engineer. */
"Using SimpleX Chat servers." = "Używanie serwerów SimpleX Chat.";
/* No comment provided by engineer. */
"v%@" = "v%@";
/* No comment provided by engineer. */
"v%@ (%@)" = "v%@ (%@)";
/* No comment provided by engineer. */
"Verify code with desktop" = "Zweryfikuj kod z komputera";
/* No comment provided by engineer. */
"Verify connection" = "Zweryfikuj połączenie";
/* No comment provided by engineer. */
"Verify connection security" = "Weryfikuj bezpieczeństwo połączenia";
/* No comment provided by engineer. */
"Verify connections" = "Zweryfikuj połączenia";
/* No comment provided by engineer. */
"Verify security code" = "Weryfikuj kod bezpieczeństwa";
@@ -3515,6 +3644,9 @@
/* No comment provided by engineer. */
"via relay" = "przez przekaźnik";
/* No comment provided by engineer. */
"Via secure quantum resistant protocol." = "Dzięki bezpiecznemu protokołowi odpornego kwantowo.";
/* No comment provided by engineer. */
"Video call" = "Połączenie wideo";
@@ -3554,6 +3686,9 @@
/* No comment provided by engineer. */
"waiting for confirmation…" = "oczekiwanie na potwierdzenie…";
/* No comment provided by engineer. */
"Waiting for desktop..." = "Oczekiwanie na komputer...";
/* No comment provided by engineer. */
"Waiting for file" = "Oczekiwanie na plik";
@@ -7,6 +7,9 @@
/* Privacy - Face ID Usage Description */
"NSFaceIDUsageDescription" = "SimpleX używa Face ID do lokalnego uwierzytelniania";
/* Privacy - Local Network Usage Description */
"NSLocalNetworkUsageDescription" = "SimpleX używa sieci lokalnej aby pozwolić na dostęp profilom czatu użytkownika przez aplikację komputerową na tej samej sieci.";
/* Privacy - Microphone Usage Description */
"NSMicrophoneUsageDescription" = "SimpleX potrzebuje dostępu do mikrofonu, w celu połączeń audio i wideo oraz nagrywania wiadomości głosowych.";
+339
View File
@@ -25,6 +25,9 @@
/* No comment provided by engineer. */
"- more stable message delivery.\n- a bit better groups.\n- and more!" = "- более стабильная доставка сообщений.\n- немного улучшенные группы.\n- и прочее!";
/* No comment provided by engineer. */
"- optionally notify deleted contacts.\n- profile names with spaces.\n- and more!" = "- опционально уведомляйте удалённые контакты.\n- имена профилей с пробелами.\n- и прочее!";
/* No comment provided by engineer. */
"- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- голосовые сообщения до 5 минут.\n- настройка времени исчезающих сообщений.\n- история редактирования.";
@@ -43,6 +46,12 @@
/* No comment provided by engineer. */
"(" = "(";
/* No comment provided by engineer. */
"(new)" = "(новое)";
/* No comment provided by engineer. */
"(this device v%@)" = "(это устройство v%@)";
/* No comment provided by engineer. */
")" = ")";
@@ -118,12 +127,18 @@
/* No comment provided by engineer. */
"%@ %@" = "%@ %@";
/* No comment provided by engineer. */
"%@ and %@" = "%@ и %@";
/* No comment provided by engineer. */
"%@ and %@ connected" = "%@ и %@ соединены";
/* copied message info, <sender> at <time> */
"%@ at %@:" = "%1$@ в %2$@:";
/* No comment provided by engineer. */
"%@ connected" = "%@ соединен(а)";
/* notification title */
"%@ is connected!" = "Установлено соединение с %@!";
@@ -139,6 +154,9 @@
/* notification title */
"%@ wants to connect!" = "%@ хочет соединиться!";
/* No comment provided by engineer. */
"%@, %@ and %lld members" = "%@, %@ и %lld членов группы";
/* No comment provided by engineer. */
"%@, %@ and %lld other members connected" = "%@, %@ и %lld других членов соединены";
@@ -178,9 +196,21 @@
/* No comment provided by engineer. */
"%lld file(s) with total size of %@" = "%lld файл(ов) общим размером %@";
/* No comment provided by engineer. */
"%lld group events" = "%lld событий";
/* No comment provided by engineer. */
"%lld members" = "Членов группы: %lld";
/* No comment provided by engineer. */
"%lld messages blocked" = "%lld сообщений заблокировано";
/* No comment provided by engineer. */
"%lld messages marked deleted" = "%lld сообщений помечено удалёнными";
/* No comment provided by engineer. */
"%lld messages moderated by %@" = "%lld сообщений модерировано членом %@";
/* No comment provided by engineer. */
"%lld minutes" = "%lld минуты";
@@ -229,6 +259,9 @@
/* No comment provided by engineer. */
"~strike~" = "\\~зачеркнуть~";
/* time to disappear */
"0 sec" = "0 сек";
/* No comment provided by engineer. */
"0s" = "0с";
@@ -371,6 +404,9 @@
/* No comment provided by engineer. */
"All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." = "Все сообщения будут удалены - это действие нельзя отменить! Сообщения будут удалены только для Вас.";
/* No comment provided by engineer. */
"All new messages from %@ will be hidden!" = "Все новые сообщения от %@ будут скрыты!";
/* No comment provided by engineer. */
"All your contacts will remain connected." = "Все контакты, которые соединились через этот адрес, сохранятся.";
@@ -434,6 +470,12 @@
/* No comment provided by engineer. */
"Already connected?" = "Соединение уже установлено?";
/* No comment provided by engineer. */
"Already connecting!" = "Уже соединяется!";
/* No comment provided by engineer. */
"Already joining the group!" = "Вступление в группу уже начато!";
/* pref value */
"always" = "всегда";
@@ -443,6 +485,9 @@
/* No comment provided by engineer. */
"An empty chat profile with the provided name is created, and the app opens as usual." = "Будет создан пустой профиль чата с указанным именем, и приложение откроется в обычном режиме.";
/* No comment provided by engineer. */
"and %lld other events" = "и %lld других событий";
/* No comment provided by engineer. */
"Answer call" = "Принять звонок";
@@ -500,6 +545,9 @@
/* No comment provided by engineer. */
"Authentication unavailable" = "Аутентификация недоступна";
/* member role */
"author" = "автор";
/* No comment provided by engineer. */
"Auto-accept" = "Автоприем";
@@ -512,6 +560,9 @@
/* No comment provided by engineer. */
"Back" = "Назад";
/* No comment provided by engineer. */
"Bad desktop address" = "Неверный адрес компьютера";
/* integrity error chat item */
"bad message hash" = "ошибка хэш сообщения";
@@ -524,9 +575,27 @@
/* No comment provided by engineer. */
"Bad message ID" = "Ошибка ID сообщения";
/* No comment provided by engineer. */
"Better groups" = "Улучшенные группы";
/* No comment provided by engineer. */
"Better messages" = "Улучшенные сообщения";
/* No comment provided by engineer. */
"Block" = "Заблокировать";
/* No comment provided by engineer. */
"Block group members" = "Блокируйте членов группы";
/* No comment provided by engineer. */
"Block member" = "Заблокировать члена группы";
/* No comment provided by engineer. */
"Block member?" = "Заблокировать члена группы?";
/* No comment provided by engineer. */
"blocked" = "заблокировано";
/* No comment provided by engineer. */
"bold" = "жирный";
@@ -720,12 +789,30 @@
/* server test step */
"Connect" = "Соединиться";
/* No comment provided by engineer. */
"Connect automatically" = "Соединяться автоматически";
/* No comment provided by engineer. */
"Connect incognito" = "Соединиться Инкогнито";
/* No comment provided by engineer. */
"Connect to desktop" = "Подключиться к компьютеру";
/* No comment provided by engineer. */
"connect to SimpleX Chat developers." = "соединитесь с разработчиками.";
/* No comment provided by engineer. */
"Connect to yourself?" = "Соединиться с самим собой?";
/* No comment provided by engineer. */
"Connect to yourself?\nThis is your own one-time link!" = "Соединиться с самим собой?\nЭто ваша собственная одноразовая ссылка!";
/* No comment provided by engineer. */
"Connect to yourself?\nThis is your own SimpleX address!" = "Соединиться с самим собой?\nЭто ваш собственный адрес SimpleX!";
/* No comment provided by engineer. */
"Connect via contact address" = "Соединиться через адрес";
/* No comment provided by engineer. */
"Connect via link" = "Соединиться через ссылку";
@@ -735,9 +822,21 @@
/* No comment provided by engineer. */
"Connect via one-time link" = "Соединиться через одноразовую ссылку";
/* No comment provided by engineer. */
"Connect with %@" = "Соединиться с %@";
/* No comment provided by engineer. */
"connected" = "соединение установлено";
/* No comment provided by engineer. */
"Connected desktop" = "Подключенный компьютер";
/* rcv group event chat item */
"connected directly" = "соединен(а) напрямую";
/* No comment provided by engineer. */
"Connected to desktop" = "Компьютер подключен";
/* No comment provided by engineer. */
"connecting" = "соединяется";
@@ -762,6 +861,9 @@
/* No comment provided by engineer. */
"Connecting server… (error: %@)" = "Устанавливается соединение с сервером… (ошибка: %@)";
/* No comment provided by engineer. */
"Connecting to desktop" = "Подключение к компьютеру";
/* chat list item title */
"connecting…" = "соединяется…";
@@ -780,6 +882,9 @@
/* No comment provided by engineer. */
"Connection request sent!" = "Запрос на соединение отправлен!";
/* No comment provided by engineer. */
"Connection terminated" = "Подключение прервано";
/* No comment provided by engineer. */
"Connection timeout" = "Превышено время соединения";
@@ -828,15 +933,24 @@
/* No comment provided by engineer. */
"Core version: v%@" = "Версия ядра: v%@";
/* No comment provided by engineer. */
"Correct name to %@?" = "Исправить имя на %@?";
/* No comment provided by engineer. */
"Create" = "Создать";
/* No comment provided by engineer. */
"Create a group using a random profile." = "Создайте группу, используя случайный профиль.";
/* No comment provided by engineer. */
"Create an address to let people connect with you." = "Создайте адрес, чтобы можно было соединиться с вами.";
/* server test step */
"Create file" = "Создание файла";
/* No comment provided by engineer. */
"Create group" = "Создать группу";
/* No comment provided by engineer. */
"Create group link" = "Создать ссылку группы";
@@ -849,6 +963,9 @@
/* No comment provided by engineer. */
"Create one-time invitation link" = "Создать ссылку-приглашение";
/* No comment provided by engineer. */
"Create profile" = "Создать профиль";
/* server test step */
"Create queue" = "Создание очереди";
@@ -963,6 +1080,9 @@
/* chat item action */
"Delete" = "Удалить";
/* No comment provided by engineer. */
"Delete %lld messages?" = "Удалить %lld сообщений?";
/* No comment provided by engineer. */
"Delete address" = "Удалить адрес";
@@ -975,6 +1095,9 @@
/* No comment provided by engineer. */
"Delete all files" = "Удалить все файлы";
/* No comment provided by engineer. */
"Delete and notify contact" = "Удалить и уведомить контакт";
/* No comment provided by engineer. */
"Delete archive" = "Удалить архив";
@@ -996,6 +1119,9 @@
/* No comment provided by engineer. */
"Delete Contact" = "Удалить контакт";
/* No comment provided by engineer. */
"Delete contact?\nThis cannot be undone!" = "Удалить контакт?\nЭто не может быть отменено!";
/* No comment provided by engineer. */
"Delete database" = "Удалить данные чата";
@@ -1071,6 +1197,9 @@
/* copied message info */
"Deleted at: %@" = "Удалено: %@";
/* rcv direct event chat item */
"deleted contact" = "удалил(а) контакт";
/* rcv group event chat item */
"deleted group" = "удалил(а) группу";
@@ -1086,6 +1215,15 @@
/* No comment provided by engineer. */
"Description" = "Описание";
/* No comment provided by engineer. */
"Desktop address" = "Адрес компьютера";
/* No comment provided by engineer. */
"Desktop app version %@ is not compatible with this app." = "Версия настольного приложения %@ несовместима с этим приложением.";
/* No comment provided by engineer. */
"Desktop devices" = "Компьютеры";
/* No comment provided by engineer. */
"Develop" = "Для разработчиков";
@@ -1149,9 +1287,15 @@
/* server test step */
"Disconnect" = "Разрыв соединения";
/* No comment provided by engineer. */
"Disconnect desktop?" = "Отключить компьютер?";
/* No comment provided by engineer. */
"Discover and join groups" = "Найдите и вступите в группы";
/* No comment provided by engineer. */
"Discover via local network" = "Обнаружение по локальной сети";
/* No comment provided by engineer. */
"Do it later" = "Отложить";
@@ -1287,6 +1431,12 @@
/* chat item text */
"encryption re-negotiation allowed for %@" = "новое соглашение о шифровании разрешено для %@";
/* message decrypt error item */
"Encryption re-negotiation error" = "Ошибка нового соглашения о шифровании";
/* No comment provided by engineer. */
"Encryption re-negotiation failed." = "Ошибка нового соглашения о шифровании.";
/* chat item text */
"encryption re-negotiation required" = "требуется новое соглашение о шифровании";
@@ -1302,6 +1452,9 @@
/* No comment provided by engineer. */
"Enter correct passphrase." = "Введите правильный пароль.";
/* No comment provided by engineer. */
"Enter group name…" = "Введите имя группы…";
/* No comment provided by engineer. */
"Enter Passcode" = "Введите Код";
@@ -1314,12 +1467,18 @@
/* No comment provided by engineer. */
"Enter server manually" = "Ввести сервер вручную";
/* No comment provided by engineer. */
"Enter this device name…" = "Введите имя этого устройства…";
/* placeholder */
"Enter welcome message…" = "Введите приветственное сообщение…";
/* placeholder */
"Enter welcome message… (optional)" = "Введите приветственное сообщение... (опционально)";
/* No comment provided by engineer. */
"Enter your name…" = "Введите ваше имя…";
/* No comment provided by engineer. */
"error" = "ошибка";
@@ -1356,6 +1515,9 @@
/* No comment provided by engineer. */
"Error creating group link" = "Ошибка при создании ссылки группы";
/* No comment provided by engineer. */
"Error creating member contact" = "Ошибка создания контакта с членом группы";
/* No comment provided by engineer. */
"Error creating profile!" = "Ошибка создания профиля!";
@@ -1434,6 +1596,9 @@
/* No comment provided by engineer. */
"Error sending email" = "Ошибка отправки email";
/* No comment provided by engineer. */
"Error sending member contact invitation" = "Ошибка отправки приглашения члену группы";
/* No comment provided by engineer. */
"Error sending message" = "Ошибка при отправке сообщения";
@@ -1485,6 +1650,9 @@
/* No comment provided by engineer. */
"Exit without saving" = "Выйти без сохранения";
/* chat item action */
"Expand" = "Раскрыть";
/* No comment provided by engineer. */
"Export database" = "Экспорт архива чата";
@@ -1503,6 +1671,9 @@
/* No comment provided by engineer. */
"Fast and no wait until the sender is online!" = "Быстрые и не нужно ждать, когда отправитель онлайн!";
/* No comment provided by engineer. */
"Faster joining and more reliable messages." = "Быстрое вступление и надежная доставка сообщений.";
/* No comment provided by engineer. */
"Favorite" = "Избранный";
@@ -1560,6 +1731,9 @@
/* No comment provided by engineer. */
"For console" = "Для консоли";
/* No comment provided by engineer. */
"Found desktop" = "Компьютер найден";
/* No comment provided by engineer. */
"French interface" = "Французский интерфейс";
@@ -1572,6 +1746,9 @@
/* No comment provided by engineer. */
"Full name:" = "Полное имя:";
/* No comment provided by engineer. */
"Fully decentralized visible only to members." = "Группа полностью децентрализована – она видна только членам.";
/* No comment provided by engineer. */
"Fully re-implemented - work in background!" = "Полностью обновлены - работают в фоне!";
@@ -1584,6 +1761,12 @@
/* No comment provided by engineer. */
"Group" = "Группа";
/* No comment provided by engineer. */
"Group already exists" = "Группа уже существует";
/* No comment provided by engineer. */
"Group already exists!" = "Группа уже существует!";
/* No comment provided by engineer. */
"group deleted" = "группа удалена";
@@ -1755,6 +1938,9 @@
/* No comment provided by engineer. */
"Incognito" = "Инкогнито";
/* No comment provided by engineer. */
"Incognito groups" = "Инкогнито группы";
/* No comment provided by engineer. */
"Incognito mode" = "Режим Инкогнито";
@@ -1782,6 +1968,9 @@
/* No comment provided by engineer. */
"Incompatible database version" = "Несовместимая версия базы данных";
/* No comment provided by engineer. */
"Incompatible version" = "Несовместимая версия";
/* PIN entry */
"Incorrect passcode" = "Неправильный код";
@@ -1821,6 +2010,9 @@
/* invalid chat item */
"invalid data" = "ошибка данных";
/* No comment provided by engineer. */
"Invalid name!" = "Неверное имя!";
/* No comment provided by engineer. */
"Invalid server address!" = "Ошибка в адресе сервера!";
@@ -1899,12 +2091,24 @@
/* No comment provided by engineer. */
"Join group" = "Вступить в группу";
/* No comment provided by engineer. */
"Join group?" = "Вступить в группу?";
/* No comment provided by engineer. */
"Join incognito" = "Вступить инкогнито";
/* No comment provided by engineer. */
"Join with current profile" = "Вступить с активным профилем";
/* No comment provided by engineer. */
"Join your group?\nThis is your link for group %@!" = "Вступить в вашу группу?\nЭто ваша ссылка на группу %@!";
/* No comment provided by engineer. */
"Joining group" = "Вступление в группу";
/* No comment provided by engineer. */
"Keep the app open to use it from desktop" = "Оставьте приложение открытым, чтобы использовать его с компьютера";
/* No comment provided by engineer. */
"Keep your connections" = "Сохраните Ваши соединения";
@@ -1941,6 +2145,15 @@
/* No comment provided by engineer. */
"Limitations" = "Ограничения";
/* No comment provided by engineer. */
"Link mobile and desktop apps! 🔗" = "Свяжите мобильное и настольное приложения! 🔗";
/* No comment provided by engineer. */
"Linked desktop options" = "Опции связанных компьютеров";
/* No comment provided by engineer. */
"Linked desktops" = "Связанные компьютеры";
/* No comment provided by engineer. */
"LIVE" = "LIVE";
@@ -2046,6 +2259,9 @@
/* No comment provided by engineer. */
"Messages & files" = "Сообщения";
/* No comment provided by engineer. */
"Messages from %@ will be shown!" = "Сообщения от %@ будут показаны!";
/* No comment provided by engineer. */
"Migrating database archive…" = "Данные чата перемещаются…";
@@ -2193,6 +2409,9 @@
/* copied message info in history */
"no text" = "нет текста";
/* No comment provided by engineer. */
"Not compatible!" = "Несовместимая версия!";
/* No comment provided by engineer. */
"Notifications" = "Уведомления";
@@ -2288,12 +2507,18 @@
/* No comment provided by engineer. */
"Only your contact can send voice messages." = "Только Ваш контакт может отправлять голосовые сообщения.";
/* No comment provided by engineer. */
"Open" = "Открыть";
/* No comment provided by engineer. */
"Open chat" = "Открыть чат";
/* authentication reason */
"Open chat console" = "Открыть консоль";
/* No comment provided by engineer. */
"Open group" = "Открыть группу";
/* No comment provided by engineer. */
"Open Settings" = "Открыть Настройки";
@@ -2330,6 +2555,9 @@
/* No comment provided by engineer. */
"Paste" = "Вставить";
/* No comment provided by engineer. */
"Paste desktop address" = "Вставить адрес компьютера";
/* No comment provided by engineer. */
"Paste image" = "Вставить изображение";
@@ -2426,6 +2654,12 @@
/* No comment provided by engineer. */
"Profile image" = "Аватар";
/* No comment provided by engineer. */
"Profile name" = "Имя профиля";
/* No comment provided by engineer. */
"Profile name:" = "Имя профиля:";
/* No comment provided by engineer. */
"Profile password" = "Пароль профиля";
@@ -2591,6 +2825,12 @@
/* No comment provided by engineer. */
"Renegotiate encryption?" = "Пересогласовать шифрование?";
/* No comment provided by engineer. */
"Repeat connection request?" = "Повторить запрос на соединение?";
/* No comment provided by engineer. */
"Repeat join request?" = "Повторить запрос на вступление?";
/* chat item action */
"Reply" = "Ответить";
@@ -2702,6 +2942,9 @@
/* No comment provided by engineer. */
"Scan QR code" = "Сканировать QR код";
/* No comment provided by engineer. */
"Scan QR code from desktop" = "Сканировать QR код с компьютера";
/* No comment provided by engineer. */
"Scan security code from your contact's app." = "Сканируйте код безопасности из приложения контакта.";
@@ -2756,9 +2999,15 @@
/* No comment provided by engineer. */
"Send delivery receipts to" = "Отправка отчётов о доставке";
/* No comment provided by engineer. */
"send direct message" = "отправьте сообщение";
/* No comment provided by engineer. */
"Send direct message" = "Отправить сообщение";
/* No comment provided by engineer. */
"Send direct message to connect" = "Отправьте сообщение чтобы соединиться";
/* No comment provided by engineer. */
"Send disappearing message" = "Отправить исчезающее сообщение";
@@ -2840,6 +3089,9 @@
/* No comment provided by engineer. */
"Servers" = "Серверы";
/* No comment provided by engineer. */
"Session code" = "Код сессии";
/* No comment provided by engineer. */
"Set 1 day" = "Установить 1 день";
@@ -3026,6 +3278,9 @@
/* No comment provided by engineer. */
"Tap to activate profile." = "Нажмите, чтобы сделать профиль активным.";
/* No comment provided by engineer. */
"Tap to Connect" = "Нажмите чтобы соединиться";
/* No comment provided by engineer. */
"Tap to join" = "Нажмите, чтобы вступить";
@@ -3146,12 +3401,21 @@
/* notification title */
"this contact" = "этот контакт";
/* No comment provided by engineer. */
"This device name" = "Имя этого устройства";
/* No comment provided by engineer. */
"This group has over %lld members, delivery receipts are not sent." = "В группе более %lld членов, отчёты о доставке выключены.";
/* No comment provided by engineer. */
"This group no longer exists." = "Эта группа больше не существует.";
/* No comment provided by engineer. */
"This is your own one-time link!" = "Это ваша собственная одноразовая ссылка!";
/* No comment provided by engineer. */
"This is your own SimpleX address!" = "Это ваш собственный адрес SimpleX!";
/* No comment provided by engineer. */
"This setting applies to messages in your current chat profile **%@**." = "Эта настройка применяется к сообщениям в Вашем текущем профиле чата **%@**.";
@@ -3161,6 +3425,9 @@
/* No comment provided by engineer. */
"To connect, your contact can scan QR code or use the link in the app." = "Чтобы соединиться с Вами, Ваш контакт может отсканировать QR-код или использовать ссылку в приложении.";
/* No comment provided by engineer. */
"To hide unwanted messages." = "Чтобы скрыть нежелательные сообщения.";
/* No comment provided by engineer. */
"To make a new connection" = "Чтобы соединиться";
@@ -3209,6 +3476,15 @@
/* No comment provided by engineer. */
"Unable to record voice message" = "Невозможно записать голосовое сообщение";
/* No comment provided by engineer. */
"Unblock" = "Разблокировать";
/* No comment provided by engineer. */
"Unblock member" = "Разблокировать члена группы";
/* No comment provided by engineer. */
"Unblock member?" = "Разблокировать члена группы?";
/* item status description */
"Unexpected error: %@" = "Неожиданная ошибка: %@";
@@ -3248,6 +3524,12 @@
/* No comment provided by engineer. */
"Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection." = "Возможно, Ваш контакт удалил ссылку, или она уже была использована. Если это не так, то это может быть ошибкой - пожалуйста, сообщите нам об этом.\nЧтобы установить соединение, попросите Ваш контакт создать еще одну ссылку и проверьте Ваше соединение с сетью.";
/* No comment provided by engineer. */
"Unlink" = "Забыть";
/* No comment provided by engineer. */
"Unlink desktop?" = "Забыть компьютер?";
/* No comment provided by engineer. */
"Unlock" = "Разблокировать";
@@ -3302,6 +3584,9 @@
/* No comment provided by engineer. */
"Use for new connections" = "Использовать для новых соединений";
/* No comment provided by engineer. */
"Use from desktop" = "Использовать с компьютера";
/* No comment provided by engineer. */
"Use iOS call interface" = "Использовать интерфейс iOS для звонков";
@@ -3323,12 +3608,24 @@
/* No comment provided by engineer. */
"Using SimpleX Chat servers." = "Используются серверы, предоставленные SimpleX Chat.";
/* No comment provided by engineer. */
"v%@" = "v%@";
/* No comment provided by engineer. */
"v%@ (%@)" = "v%@ (%@)";
/* No comment provided by engineer. */
"Verify code with desktop" = "Сверьте код с компьютером";
/* No comment provided by engineer. */
"Verify connection" = "Проверить соединение";
/* No comment provided by engineer. */
"Verify connection security" = "Проверить безопасность соединения";
/* No comment provided by engineer. */
"Verify connections" = "Проверять соединения";
/* No comment provided by engineer. */
"Verify security code" = "Подтвердить код безопасности";
@@ -3347,6 +3644,9 @@
/* No comment provided by engineer. */
"via relay" = "через relay сервер";
/* No comment provided by engineer. */
"Via secure quantum resistant protocol." = "Через безопасный квантово-устойчивый протокол.";
/* No comment provided by engineer. */
"Video call" = "Видеозвонок";
@@ -3386,6 +3686,9 @@
/* No comment provided by engineer. */
"waiting for confirmation…" = "ожидается подтверждение…";
/* No comment provided by engineer. */
"Waiting for desktop..." = "Ожидается подключение компьютера...";
/* No comment provided by engineer. */
"Waiting for file" = "Ожидается прием файла";
@@ -3455,6 +3758,27 @@
/* No comment provided by engineer. */
"You are already connected to %@." = "Вы уже соединены с контактом %@.";
/* No comment provided by engineer. */
"You are already connecting to %@." = "Вы уже соединяетесь с %@.";
/* No comment provided by engineer. */
"You are already connecting via this one-time link!" = "Вы уже соединяетесь по этой одноразовой ссылке!";
/* No comment provided by engineer. */
"You are already in group %@." = "Вы уже состоите в группе %@.";
/* No comment provided by engineer. */
"You are already joining the group %@." = "Вы уже вступаете в группу %@.";
/* No comment provided by engineer. */
"You are already joining the group via this link!" = "Вы уже вступаете в группу по этой ссылке!";
/* No comment provided by engineer. */
"You are already joining the group via this link." = "Вы уже вступаете в группу по этой ссылке.";
/* No comment provided by engineer. */
"You are already joining the group!\nRepeat join request?" = "Вы уже вступаете в группу!\nПовторить запрос на вступление?";
/* No comment provided by engineer. */
"You are connected to the server used to receive messages from this contact." = "Установлено соединение с сервером, через который Вы получаете сообщения от этого контакта.";
@@ -3530,6 +3854,12 @@
/* No comment provided by engineer. */
"You could not be verified; please try again." = "Верификация не удалась; пожалуйста, попробуйте ещё раз.";
/* No comment provided by engineer. */
"You have already requested connection via this address!" = "Вы уже запросили соединение через этот адрес!";
/* No comment provided by engineer. */
"You have already requested connection!\nRepeat connection request?" = "Вы уже запросили соединение!\nПовторить запрос?";
/* No comment provided by engineer. */
"You have no chats" = "У Вас нет чатов";
@@ -3572,6 +3902,9 @@
/* No comment provided by engineer. */
"You will be connected to group when the group host's device is online, please wait or check later!" = "Соединение с группой будет установлено, когда хост группы будет онлайн. Пожалуйста, подождите или проверьте позже!";
/* No comment provided by engineer. */
"You will be connected when group link host's device is online, please wait or check later!" = "Соединение будет установлено, когда владелец ссылки группы будет онлайн. Пожалуйста, подождите или проверьте позже!";
/* No comment provided by engineer. */
"You will be connected when your connection request is accepted, please wait or check later!" = "Соединение будет установлено, когда Ваш запрос будет принят. Пожалуйста, подождите или проверьте позже!";
@@ -3581,6 +3914,9 @@
/* No comment provided by engineer. */
"You will be required to authenticate when you start or resume the app after 30 seconds in background." = "Вы будете аутентифицированы при запуске и возобновлении приложения, которое было 30 секунд в фоновом режиме.";
/* No comment provided by engineer. */
"You will connect to all group members." = "Вы соединитесь со всеми членами группы.";
/* No comment provided by engineer. */
"You will still receive calls and notifications from muted profiles when they are active." = "Вы все равно получите звонки и уведомления в профилях без звука, когда они активные.";
@@ -3644,6 +3980,9 @@
/* No comment provided by engineer. */
"Your privacy" = "Конфиденциальность";
/* No comment provided by engineer. */
"Your profile" = "Ваш профиль";
/* No comment provided by engineer. */
"Your profile **%@** will be shared." = "Будет отправлен Ваш профиль **%@**.";
@@ -7,6 +7,9 @@
/* Privacy - Face ID Usage Description */
"NSFaceIDUsageDescription" = "SimpleX использует Face ID для аутентификации";
/* Privacy - Local Network Usage Description */
"NSLocalNetworkUsageDescription" = "SimpleX использует доступ к локальной сети, чтобы разрешить использование профиля чата через компьютер в той же сети.";
/* Privacy - Microphone Usage Description */
"NSMicrophoneUsageDescription" = "SimpleX использует микрофон для аудио и видео звонков, и для записи голосовых сообщений.";
+1 -1
View File
@@ -12,7 +12,7 @@ android {
defaultConfig {
applicationId = "chat.simplex.app"
minSdkVersion(26)
minSdkVersion(28)
targetSdkVersion(33)
// !!!
// skip version code after release to F-Droid, as it uses two version codes
@@ -39,6 +39,7 @@
android:exported="true"
android:label="${app_name}"
android:windowSoftInputMode="adjustResize"
android:configChanges="uiMode"
android:theme="@style/Theme.SimpleX">
<intent-filter>
<category android:name="android.intent.category.LAUNCHER" />
@@ -5,6 +5,7 @@ import android.net.Uri
import android.os.*
import android.view.WindowManager
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatDelegate
import androidx.fragment.app.FragmentActivity
import chat.simplex.app.model.NtfManager
import chat.simplex.app.model.NtfManager.getUserIdFromIntent
@@ -22,6 +23,7 @@ import java.lang.ref.WeakReference
class MainActivity: FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
platform.androidSetNightModeIfSupported()
applyAppLocale(ChatModel.controller.appPrefs.appLanguage)
super.onCreate(savedInstanceState)
// testJson()
@@ -41,9 +43,7 @@ class MainActivity: FragmentActivity() {
)
}
setContent {
SimpleXTheme {
AppScreen()
}
AppScreen()
}
SimplexApp.context.schedulePeriodicServiceRestartWorker()
SimplexApp.context.schedulePeriodicWakeUp()
@@ -126,7 +126,9 @@ fun processIntent(intent: Intent?) {
when (intent?.action) {
"android.intent.action.VIEW" -> {
val uri = intent.data
if (uri != null) connectIfOpenedViaUri(chatModel.remoteHostId, uri.toURI(), ChatModel)
if (uri != null) {
chatModel.appOpenUrl.value = null to uri.toURI()
}
}
}
}
@@ -1,7 +1,8 @@
package chat.simplex.app
import android.app.Application
import chat.simplex.common.platform.Log
import android.app.UiModeManager
import android.os.*
import androidx.lifecycle.*
import androidx.work.*
import chat.simplex.app.model.NtfManager
@@ -10,10 +11,12 @@ import chat.simplex.common.helpers.requiresIgnoringBattery
import chat.simplex.common.model.*
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.model.ChatModel.updatingChatsMutex
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.CurrentColors
import chat.simplex.common.ui.theme.DefaultTheme
import chat.simplex.common.views.call.RcvCallInvitation
import chat.simplex.common.views.helpers.*
import chat.simplex.common.views.onboarding.OnboardingStage
import chat.simplex.common.platform.*
import chat.simplex.common.views.call.RcvCallInvitation
import com.jakewharton.processphoenix.ProcessPhoenix
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.withLock
@@ -32,7 +35,24 @@ class SimplexApp: Application(), LifecycleEventObserver {
override fun onCreate() {
super.onCreate()
if (ProcessPhoenix.isPhoenixProcess(this)) {
return;
return
} else {
registerGlobalErrorHandler()
Handler(Looper.getMainLooper()).post {
while (true) {
try {
Looper.loop()
} catch (e: Throwable) {
if (e.message != null && e.message!!.startsWith("Unable to start activity")) {
android.os.Process.killProcess(android.os.Process.myPid())
break
} else {
// Send it to our exception handled because it will not get the exception otherwise
Thread.getDefaultUncaughtExceptionHandler()?.uncaughtException(Looper.getMainLooper().thread, e)
}
}
}
}
}
context = this
initHaskell()
@@ -57,7 +77,7 @@ class SimplexApp: Application(), LifecycleEventObserver {
updatingChatsMutex.withLock {
kotlin.runCatching {
val currentUserId = chatModel.currentUser.value?.userId
val chats = ArrayList(chatController.apiGetChats(chatModel.remoteHostId))
val chats = ArrayList(chatController.apiGetChats(chatModel.remoteHostId()))
/** Active user can be changed in background while [ChatController.apiGetChats] is executing */
if (chatModel.currentUser.value?.userId == currentUserId) {
val currentChatId = chatModel.chatId.value
@@ -75,7 +95,7 @@ class SimplexApp: Application(), LifecycleEventObserver {
}
Lifecycle.Event.ON_RESUME -> {
isAppOnForeground = true
if (chatModel.controller.appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete) {
if (chatModel.controller.appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete && chatModel.currentUser.value != null) {
SimplexService.showBackgroundServiceNoticeIfNeeded()
}
/**
@@ -206,6 +226,23 @@ class SimplexApp: Application(), LifecycleEventObserver {
override fun androidIsBackgroundCallAllowed(): Boolean = !SimplexService.isBackgroundRestricted()
override fun androidSetNightModeIfSupported() {
if (Build.VERSION.SDK_INT < 31) return
val light = if (CurrentColors.value.name == DefaultTheme.SYSTEM.name) {
null
} else {
CurrentColors.value.colors.isLight
}
val mode = when (light) {
null -> UiModeManager.MODE_NIGHT_AUTO
true -> UiModeManager.MODE_NIGHT_NO
false -> UiModeManager.MODE_NIGHT_YES
}
val uiModeManager = androidAppContext.getSystemService(UI_MODE_SERVICE) as UiModeManager
uiModeManager.setApplicationNightMode(mode)
}
override suspend fun androidAskToAllowBackgroundCalls(): Boolean {
if (SimplexService.isBackgroundRestricted()) {
val userChoice: CompletableDeferred<Boolean> = CompletableDeferred()
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.SimpleX" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="android:statusBarColor">@color/black</item>
<item name="android:windowBackground">@color/window_background_dark</item>
</style>
</resources>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.SimpleX" parent="android:Theme.Material.Light.NoActionBar">
<style name="Theme.SimpleX" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="android:statusBarColor">@color/black</item>
</style>
</resources>
+61 -5
View File
@@ -110,7 +110,7 @@ android {
compileSdkVersion(34)
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdkVersion(26)
minSdkVersion(28)
targetSdkVersion(33)
}
compileOptions {
@@ -155,6 +155,34 @@ afterEvaluate {
val endTagRegex = Regex("</")
val anyHtmlRegex = Regex("[^>]*>.*(<|>).*</string>|[^>]*>.*(&lt;|&gt;).*</string>")
val correctHtmlRegex = Regex("[^>]*>.*<b>.*</b>.*</string>|[^>]*>.*<i>.*</i>.*</string>|[^>]*>.*<u>.*</u>.*</string>|[^>]*>.*<font[^>]*>.*</font>.*</string>")
val possibleFormat = listOf("s", "d", "1\$s", "1\$d", "2s", "f")
fun String.id(): String = replace("<string name=\"", "").trim().substringBefore("\"")
fun String.formatting(filepath: String): List<String> {
if (!contains("%")) return emptyList()
val value = substringAfter("\">").substringBeforeLast("</string>")
val formats = ArrayList<String>()
var substring = value.substringAfter("%")
while (true) {
var foundFormat = false
for (format in possibleFormat) {
if (substring.startsWith(format)) {
formats.add(format)
foundFormat = true
break
}
}
if (!foundFormat) {
throw Exception("Unknown formatting in string. Add it to 'possibleFormat' in common/build.gradle.kts if needed: $this \nin $filepath")
}
val was = substring
substring = substring.substringAfter("%")
if (was.length == substring.length) break
}
return formats
}
fun String.removeCDATA(): String =
if (contains("<![CDATA")) {
@@ -195,20 +223,44 @@ afterEvaluate {
return this
}
val fileRegex = Regex("MR/../strings.xml$|MR/..-.../strings.xml$|MR/..-../strings.xml$|MR/base/strings.xml$")
kotlin.sourceSets["commonMain"].resources.filter { fileRegex.containsMatchIn(it.absolutePath) }.asFileTree.forEach { file ->
val tree = kotlin.sourceSets["commonMain"].resources.filter { fileRegex.containsMatchIn(it.absolutePath.replace("\\", "/")) }.asFileTree
val baseStringsFile = tree.firstOrNull { it.absolutePath.replace("\\", "/").endsWith("base/strings.xml") } ?: throw Exception("No base/strings.xml found")
val treeList = ArrayList(tree.toList())
treeList.remove(baseStringsFile)
treeList.add(0, baseStringsFile)
val baseFormatting = mutableMapOf<String, List<String>>()
treeList.forEachIndexed { index, file ->
val isBase = index == 0
val initialLines = ArrayList<String>()
val finalLines = ArrayList<String>()
val errors = ArrayList<String>()
file.useLines { lines ->
val multiline = ArrayList<String>()
lines.forEach { line ->
initialLines.add(line)
if (stringRegex.matches(line)) {
finalLines.add(line.removeCDATA().addCDATA(file.absolutePath))
val fixedLine = line.removeCDATA().addCDATA(file.absolutePath)
val lineId = fixedLine.id()
if (isBase) {
baseFormatting[lineId] = fixedLine.formatting(file.absolutePath)
} else if (baseFormatting[lineId] != fixedLine.formatting(file.absolutePath)) {
errors.add("Incorrect formatting in string: $fixedLine \nin ${file.absolutePath}")
}
finalLines.add(fixedLine)
} else if (multiline.isEmpty() && startStringRegex.containsMatchIn(line)) {
multiline.add(line)
} else if (multiline.isNotEmpty() && endStringRegex.containsMatchIn(line)) {
multiline.add(line)
finalLines.addAll(multiline.joinToString("\n").removeCDATA().addCDATA(file.absolutePath).split("\n"))
val fixedLines = multiline.joinToString("\n").removeCDATA().addCDATA(file.absolutePath).split("\n")
val fixedLinesJoined = fixedLines.joinToString("")
val lineId = fixedLinesJoined.id()
if (isBase) {
baseFormatting[lineId] = fixedLinesJoined.formatting(file.absolutePath)
} else if (baseFormatting[lineId] != fixedLinesJoined.formatting(file.absolutePath)) {
errors.add("Incorrect formatting in string: $fixedLinesJoined \nin ${file.absolutePath}")
}
finalLines.addAll(fixedLines)
multiline.clear()
} else if (multiline.isNotEmpty()) {
multiline.add(line)
@@ -217,10 +269,14 @@ afterEvaluate {
}
}
if (multiline.isNotEmpty()) {
throw Exception("Unclosed string tag: ${multiline.joinToString("\n")} \nin ${file.absolutePath}")
errors.add("Unclosed string tag: ${multiline.joinToString("\n")} \nin ${file.absolutePath}")
}
}
if (errors.isNotEmpty()) {
throw Exception("Found errors: \n\n${errors.joinToString("\n\n")}")
}
if (!debug && finalLines != initialLines) {
file.writer().use {
finalLines.forEachIndexed { index, line ->
@@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import com.google.accompanist.insets.navigationBarsWithImePadding
import java.io.File
actual fun Modifier.navigationBarsWithImePadding(): Modifier = navigationBarsWithImePadding()
@@ -19,7 +20,7 @@ actual fun ProvideWindowInsets(
@Composable
actual fun Modifier.desktopOnExternalDrag(
enabled: Boolean,
onFiles: (List<String>) -> Unit,
onFiles: (List<File>) -> Unit,
onImage: (Painter) -> Unit,
onText: (String) -> Unit
): Modifier = this
@@ -27,7 +27,6 @@ import androidx.core.view.inputmethod.EditorInfoCompat
import androidx.core.view.inputmethod.InputConnectionCompat
import androidx.core.widget.doAfterTextChanged
import androidx.core.widget.doOnTextChanged
import chat.simplex.common.*
import chat.simplex.common.R
import chat.simplex.common.helpers.toURI
import chat.simplex.common.model.ChatModel
@@ -45,6 +44,7 @@ import java.net.URI
actual fun PlatformTextField(
composeState: MutableState<ComposeState>,
sendMsgEnabled: Boolean,
sendMsgButtonDisabled: Boolean,
textStyle: MutableState<TextStyle>,
showDeleteTextButton: MutableState<Boolean>,
userIsObserver: Boolean,
@@ -8,15 +8,12 @@ import android.provider.MediaStore
import android.webkit.MimeTypeMap
import androidx.compose.ui.platform.ClipboardManager
import androidx.compose.ui.platform.UriHandler
import androidx.core.content.FileProvider
import androidx.core.net.toUri
import chat.simplex.common.helpers.*
import chat.simplex.common.model.*
import chat.simplex.common.views.helpers.*
import java.io.BufferedOutputStream
import java.io.File
import chat.simplex.res.MR
import java.io.ByteArrayOutputStream
actual fun ClipboardManager.shareText(text: String) {
val sendIntent: Intent = Intent().apply {
@@ -4,14 +4,17 @@ import android.app.Activity
import android.content.Context
import android.content.pm.ActivityInfo
import android.graphics.Rect
import android.os.Build
import android.os.*
import android.view.*
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.activity.compose.setContent
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalView
import chat.simplex.common.views.helpers.KeyboardState
import chat.simplex.common.AppScreen
import chat.simplex.common.views.helpers.*
import androidx.compose.ui.platform.LocalContext as LocalContext1
import chat.simplex.res.MR
actual fun showToast(text: String, timeout: Long) = Toast.makeText(androidAppContext, text, Toast.LENGTH_SHORT).show()
@@ -71,3 +74,44 @@ actual fun hideKeyboard(view: Any?) {
}
actual fun androidIsFinishingMainActivity(): Boolean = (mainActivity.get()?.isFinishing == true)
actual class GlobalExceptionsHandler: Thread.UncaughtExceptionHandler {
actual override fun uncaughtException(thread: Thread, e: Throwable) {
Log.e(TAG, "App crashed, thread name: " + thread.name + ", exception: " + e.stackTraceToString())
includeMoreFailedComposables()
if (ModalManager.start.hasModalsOpen()) {
ModalManager.start.closeModal()
} else if (chatModel.chatId.value != null) {
// Since no modals are open, the problem is probably in ChatView
chatModel.chatId.value = null
chatModel.chatItems.clear()
} else {
// ChatList, nothing to do. Maybe to show other view except ChatList
}
chatModel.activeCall.value?.let {
withBGApi {
chatModel.callManager.endCall(it)
}
}
if (thread.name == "main") {
mainActivity.get()?.recreate()
} else {
mainActivity.get()?.apply {
window
?.decorView
?.findViewById<ViewGroup>(android.R.id.content)
?.removeViewAt(0)
setContent {
AppScreen()
}
}
}
// Wait until activity recreates to prevent showing two alerts (in case `main` was crashed)
Handler(Looper.getMainLooper()).post {
AlertManager.shared.showAlertMsg(
title = generalGetString(MR.strings.app_was_crashed),
text = e.stackTraceToString()
)
}
}
}
@@ -71,7 +71,7 @@ actual class VideoPlayer actual constructor(
private fun start(seek: Long? = null, onProgressUpdate: (position: Long?, state: TrackState) -> Unit): Boolean {
val filepath = getAppFilePath(uri)
if (filepath == null || !File(filepath).exists()) {
Log.e(TAG, "No such file: $uri")
Log.e(TAG, "No such file: $filepath")
brokenVideo.value = true
return false
}
@@ -370,7 +370,6 @@ fun CallInfoView(call: Call, alignment: Alignment.Horizontal) {
InfoText(call.callState.text)
val connInfo = call.connectionInfo
// val connInfoText = if (connInfo == null) "" else " (${connInfo.text}, ${connInfo.protocolText})"
val connInfoText = if (connInfo == null) "" else " (${connInfo.text})"
InfoText(call.encryptionStatus + connInfoText)
}
@@ -585,8 +584,8 @@ fun PreviewActiveCallOverlayVideo() {
localMedia = CallMediaType.Video,
peerMedia = CallMediaType.Video,
connectionInfo = ConnectionInfo(
RTCIceCandidate(RTCIceCandidateType.Host, "tcp", null),
RTCIceCandidate(RTCIceCandidateType.Host, "tcp", null)
RTCIceCandidate(RTCIceCandidateType.Host, "tcp"),
RTCIceCandidate(RTCIceCandidateType.Host, "tcp")
)
),
speakerCanBeEnabled = true,
@@ -611,8 +610,8 @@ fun PreviewActiveCallOverlayAudio() {
localMedia = CallMediaType.Audio,
peerMedia = CallMediaType.Audio,
connectionInfo = ConnectionInfo(
RTCIceCandidate(RTCIceCandidateType.Host, "udp", null),
RTCIceCandidate(RTCIceCandidateType.Host, "udp", null)
RTCIceCandidate(RTCIceCandidateType.Host, "udp"),
RTCIceCandidate(RTCIceCandidateType.Host, "udp")
)
),
speakerCanBeEnabled = true,
@@ -0,0 +1,15 @@
package chat.simplex.common.views.onboarding
import androidx.compose.runtime.Composable
import chat.simplex.common.model.SharedPreference
import chat.simplex.common.model.User
import chat.simplex.res.MR
@Composable
actual fun OnboardingActionButton(user: User?, onboardingStage: SharedPreference<OnboardingStage>, onclick: (() -> Unit)?) {
if (user == null) {
OnboardingActionButton(MR.strings.create_your_profile, onboarding = OnboardingStage.Step2_CreateProfile, true, onclick = onclick)
} else {
OnboardingActionButton(MR.strings.make_private_connection, onboarding = OnboardingStage.OnboardingComplete, true, onclick = onclick)
}
}
@@ -5,7 +5,7 @@
//#include <android/log.h>
// from the RTS
void hs_init(int * argc, char **argv[]);
void hs_init_with_rtsopts(int * argc, char **argv[]);
// from android-support
void setLineBuffering(void);
@@ -32,13 +32,30 @@ Java_chat_simplex_common_platform_CoreKt_pipeStdOutToSocket(JNIEnv *env, __unuse
JNIEXPORT void JNICALL
Java_chat_simplex_common_platform_CoreKt_initHS(__unused JNIEnv *env, __unused jclass clazz) {
hs_init(NULL, NULL);
int argc = 5;
char *argv[] = {
"simplex",
"+RTS", // requires `hs_init_with_rtsopts`
"-A16m", // chunk size for new allocations
"-H64m", // initial heap size
"-xn", // non-moving GC
NULL
};
char **pargv = argv;
hs_init_with_rtsopts(&argc, &pargv);
setLineBuffering();
}
// from simplex-chat
typedef long* chat_ctrl;
/*
When you start using any new function from Haskell libraries,
you have to add the function name to the file libsimplex.dll.def in the root directory.
And do the same by adding it into flake.nix file in the root directory,
Otherwise, Windows and Android libraries cannot be built.
*/
extern char *chat_migrate_init(const char *path, const char *key, const char *confirm, chat_ctrl *ctrl);
extern char *chat_send_cmd(chat_ctrl ctrl, const char *cmd);
extern char *chat_send_remote_cmd(chat_ctrl ctrl, const int rhId, const char *cmd);
@@ -48,9 +65,9 @@ extern char *chat_parse_markdown(const char *str);
extern char *chat_parse_server(const char *str);
extern char *chat_password_hash(const char *pwd, const char *salt);
extern char *chat_valid_name(const char *name);
extern char *chat_write_file(const char *path, char *ptr, int length);
extern char *chat_write_file(chat_ctrl ctrl, const char *path, char *ptr, int length);
extern char *chat_read_file(const char *path, const char *key, const char *nonce);
extern char *chat_encrypt_file(const char *from_path, const char *to_path);
extern char *chat_encrypt_file(chat_ctrl ctrl, const char *from_path, const char *to_path);
extern char *chat_decrypt_file(const char *from_path, const char *key, const char *nonce, const char *to_path);
JNIEXPORT jobjectArray JNICALL
@@ -140,11 +157,11 @@ Java_chat_simplex_common_platform_CoreKt_chatValidName(JNIEnv *env, jclass clazz
}
JNIEXPORT jstring JNICALL
Java_chat_simplex_common_platform_CoreKt_chatWriteFile(JNIEnv *env, jclass clazz, jstring path, jobject buffer) {
Java_chat_simplex_common_platform_CoreKt_chatWriteFile(JNIEnv *env, jclass clazz, jlong controller, jstring path, jobject buffer) {
const char *_path = (*env)->GetStringUTFChars(env, path, JNI_FALSE);
jbyte *buff = (jbyte *) (*env)->GetDirectBufferAddress(env, buffer);
jlong capacity = (*env)->GetDirectBufferCapacity(env, buffer);
jstring res = (*env)->NewStringUTF(env, chat_write_file(_path, buff, capacity));
jstring res = (*env)->NewStringUTF(env, chat_write_file((void*)controller, _path, buff, capacity));
(*env)->ReleaseStringUTFChars(env, path, _path);
return res;
}
@@ -189,10 +206,10 @@ Java_chat_simplex_common_platform_CoreKt_chatReadFile(JNIEnv *env, jclass clazz,
}
JNIEXPORT jstring JNICALL
Java_chat_simplex_common_platform_CoreKt_chatEncryptFile(JNIEnv *env, jclass clazz, jstring from_path, jstring to_path) {
Java_chat_simplex_common_platform_CoreKt_chatEncryptFile(JNIEnv *env, jclass clazz, jlong controller, jstring from_path, jstring to_path) {
const char *_from_path = (*env)->GetStringUTFChars(env, from_path, JNI_FALSE);
const char *_to_path = (*env)->GetStringUTFChars(env, to_path, JNI_FALSE);
jstring res = (*env)->NewStringUTF(env, chat_encrypt_file(_from_path, _to_path));
jstring res = (*env)->NewStringUTF(env, chat_encrypt_file((void*)controller, _from_path, _to_path));
(*env)->ReleaseStringUTFChars(env, from_path, _from_path);
(*env)->ReleaseStringUTFChars(env, to_path, _to_path);
return res;
@@ -4,16 +4,31 @@
#include <stdint.h>
// from the RTS
void hs_init(int * argc, char **argv[]);
void hs_init_with_rtsopts(int * argc, char **argv[]);
JNIEXPORT void JNICALL
Java_chat_simplex_common_platform_CoreKt_initHS(JNIEnv *env, jclass clazz) {
hs_init(NULL, NULL);
#ifdef _WIN32
int argc = 4;
char *argv[] = {"simplex", "+RTS", "-A16m", "-H64m", NULL}; // non-moving GC is broken on windows with GHC 9.4-9.6.3
#else
int argc = 5;
char *argv[] = {"simplex", "+RTS", "-A16m", "-H64m", "-xn", NULL}; // see android/simplex-api.c for details
#endif
char **pargv = argv;
hs_init_with_rtsopts(&argc, &pargv);
}
// from simplex-chat
typedef long* chat_ctrl;
/*
When you start using any new function from Haskell libraries,
you have to add the function name to the file libsimplex.dll.def in the root directory.
And do the same by adding it into flake.nix file in the root directory,
Otherwise, Windows and Android libraries cannot be built.
*/
extern char *chat_migrate_init(const char *path, const char *key, const char *confirm, chat_ctrl *ctrl);
extern char *chat_send_cmd(chat_ctrl ctrl, const char *cmd);
extern char *chat_send_remote_cmd(chat_ctrl ctrl, const int rhId, const char *cmd);
@@ -23,9 +38,9 @@ extern char *chat_parse_markdown(const char *str);
extern char *chat_parse_server(const char *str);
extern char *chat_password_hash(const char *pwd, const char *salt);
extern char *chat_valid_name(const char *name);
extern char *chat_write_file(const char *path, char *ptr, int length);
extern char *chat_write_file(chat_ctrl ctrl, const char *path, char *ptr, int length);
extern char *chat_read_file(const char *path, const char *key, const char *nonce);
extern char *chat_encrypt_file(const char *from_path, const char *to_path);
extern char *chat_encrypt_file(chat_ctrl ctrl, const char *from_path, const char *to_path);
extern char *chat_decrypt_file(const char *from_path, const char *key, const char *nonce, const char *to_path);
// As a reference: https://stackoverflow.com/a/60002045
@@ -152,11 +167,11 @@ Java_chat_simplex_common_platform_CoreKt_chatValidName(JNIEnv *env, jclass clazz
}
JNIEXPORT jstring JNICALL
Java_chat_simplex_common_platform_CoreKt_chatWriteFile(JNIEnv *env, jclass clazz, jstring path, jobject buffer) {
Java_chat_simplex_common_platform_CoreKt_chatWriteFile(JNIEnv *env, jclass clazz, jlong controller, jstring path, jobject buffer) {
const char *_path = encode_to_utf8_chars(env, path);
jbyte *buff = (jbyte *) (*env)->GetDirectBufferAddress(env, buffer);
jlong capacity = (*env)->GetDirectBufferCapacity(env, buffer);
jstring res = decode_to_utf8_string(env, chat_write_file(_path, buff, capacity));
jstring res = decode_to_utf8_string(env, chat_write_file((void*)controller, _path, buff, capacity));
(*env)->ReleaseStringUTFChars(env, path, _path);
return res;
}
@@ -201,10 +216,10 @@ Java_chat_simplex_common_platform_CoreKt_chatReadFile(JNIEnv *env, jclass clazz,
}
JNIEXPORT jstring JNICALL
Java_chat_simplex_common_platform_CoreKt_chatEncryptFile(JNIEnv *env, jclass clazz, jstring from_path, jstring to_path) {
Java_chat_simplex_common_platform_CoreKt_chatEncryptFile(JNIEnv *env, jclass clazz, jlong controller, jstring from_path, jstring to_path) {
const char *_from_path = encode_to_utf8_chars(env, from_path);
const char *_to_path = encode_to_utf8_chars(env, to_path);
jstring res = decode_to_utf8_string(env, chat_encrypt_file(_from_path, _to_path));
jstring res = decode_to_utf8_string(env, chat_encrypt_file((void*)controller, _from_path, _to_path));
(*env)->ReleaseStringUTFChars(env, from_path, _from_path);
(*env)->ReleaseStringUTFChars(env, to_path, _to_path);
return res;
@@ -37,15 +37,16 @@ import kotlinx.coroutines.flow.*
data class SettingsViewState(
val userPickerState: MutableStateFlow<AnimatedViewState>,
val scaffoldState: ScaffoldState,
val switchingUsersAndHosts: MutableState<Boolean>
val scaffoldState: ScaffoldState
)
@Composable
fun AppScreen() {
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
Surface(color = MaterialTheme.colors.background) {
MainScreen()
SimpleXTheme {
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
Surface(color = MaterialTheme.colors.background) {
MainScreen()
}
}
}
}
@@ -65,7 +66,7 @@ fun MainScreen() {
!chatModel.controller.appPrefs.laNoticeShown.get()
&& showAdvertiseLAAlert
&& chatModel.controller.appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete
&& chatModel.chats.isNotEmpty()
&& chatModel.chats.count() > 1
&& chatModel.activeCallInvitation.value == null
) {
AppLock.showLANotice(ChatModel.controller.appPrefs.laNoticeShown) }
@@ -102,11 +103,8 @@ fun MainScreen() {
}
Box {
var onboarding by remember { mutableStateOf(chatModel.controller.appPrefs.onboardingStage.get()) }
LaunchedEffect(Unit) {
snapshotFlow { chatModel.controller.appPrefs.onboardingStage.state.value }.distinctUntilChanged().collect { onboarding = it }
}
val userCreated = chatModel.userCreated.value
val onboarding by remember { chatModel.controller.appPrefs.onboardingStage.state }
val localUserCreated = chatModel.localUserCreated.value
var showInitializationView by remember { mutableStateOf(false) }
when {
chatModel.chatDbStatus.value == null && showInitializationView -> InitializationView()
@@ -115,14 +113,18 @@ fun MainScreen() {
DatabaseErrorView(chatModel.chatDbStatus, chatModel.controller.appPrefs)
}
}
remember { chatModel.chatDbEncrypted }.value == null || userCreated == null -> SplashView()
onboarding == OnboardingStage.OnboardingComplete && userCreated -> {
remember { chatModel.chatDbEncrypted }.value == null || localUserCreated == null -> SplashView()
onboarding == OnboardingStage.OnboardingComplete -> {
Box {
showAdvertiseLAAlert = true
val userPickerState by rememberSaveable(stateSaver = AnimatedViewState.saver()) { mutableStateOf(MutableStateFlow(AnimatedViewState.GONE)) }
val userPickerState by rememberSaveable(stateSaver = AnimatedViewState.saver()) { mutableStateOf(MutableStateFlow(if (chatModel.desktopNoUserNoRemote()) AnimatedViewState.VISIBLE else AnimatedViewState.GONE)) }
KeyChangeEffect(chatModel.desktopNoUserNoRemote) {
if (chatModel.desktopNoUserNoRemote() && !ModalManager.start.hasModalsOpen()) {
userPickerState.value = AnimatedViewState.VISIBLE
}
}
val scaffoldState = rememberScaffoldState()
val switchingUsersAndHosts = rememberSaveable { mutableStateOf(false) }
val settingsState = remember { SettingsViewState(userPickerState, scaffoldState, switchingUsersAndHosts) }
val settingsState = remember { SettingsViewState(userPickerState, scaffoldState) }
if (appPlatform.isAndroid) {
AndroidScreen(settingsState)
} else {
@@ -137,12 +139,14 @@ fun MainScreen() {
}
}
onboarding == OnboardingStage.Step2_CreateProfile -> CreateFirstProfile(chatModel) {}
onboarding == OnboardingStage.LinkAMobile -> LinkAMobile()
onboarding == OnboardingStage.Step2_5_SetupDatabasePassphrase -> SetupDatabasePassphrase(chatModel)
onboarding == OnboardingStage.Step3_CreateSimpleXAddress -> CreateSimpleXAddress(chatModel, null)
onboarding == OnboardingStage.Step4_SetNotificationsMode -> SetNotificationsMode(chatModel)
}
if (appPlatform.isAndroid) {
ModalManager.fullscreen.showInView()
SwitchingUsersView()
}
val unauthorized = remember { derivedStateOf { AppLock.userAuthorized.value != true } }
@@ -158,11 +162,26 @@ fun MainScreen() {
AuthView()
} else {
SplashView()
ModalManager.fullscreen.showPasscodeInView()
}
} else {
if (chatModel.showCallView.value) {
ActiveCallView()
} else {
// It's needed for privacy settings toggle, so it can be shown even if the app is passcode unlocked
ModalManager.fullscreen.showPasscodeInView()
}
AlertManager.privacySensitive.showInView()
if (onboarding == OnboardingStage.OnboardingComplete) {
LaunchedEffect(chatModel.currentUser.value, chatModel.appOpenUrl.value) {
val (rhId, url) = chatModel.appOpenUrl.value ?: (null to null)
if (url != null) {
chatModel.appOpenUrl.value = null
connectIfOpenedViaUri(rhId, url, chatModel)
}
}
}
} else if (chatModel.showCallView.value) {
ActiveCallView()
}
ModalManager.fullscreen.showPasscodeInView()
val invitation = chatModel.activeCallInvitation.value
if (invitation != null) IncomingCallAlertView(invitation, chatModel)
AlertManager.shared.showInView()
@@ -262,7 +281,7 @@ fun CenterPartOfScreen() {
.background(MaterialTheme.colors.background),
contentAlignment = Alignment.Center
) {
Text(stringResource(MR.strings.no_selected_chat))
Text(stringResource(if (chatModel.desktopNoUserNoRemote) MR.strings.no_connected_mobile else MR.strings.no_selected_chat))
}
} else {
ModalManager.center.showInView()
@@ -286,6 +305,7 @@ fun DesktopScreen(settingsState: SettingsViewState) {
}
Box(Modifier.widthIn(max = DEFAULT_START_MODAL_WIDTH)) {
ModalManager.start.showInView()
SwitchingUsersView()
}
Row(Modifier.padding(start = DEFAULT_START_MODAL_WIDTH).clipToBounds()) {
Box(Modifier.widthIn(min = DEFAULT_MIN_CENTER_MODAL_WIDTH).weight(1f)) {
@@ -298,7 +318,7 @@ fun DesktopScreen(settingsState: SettingsViewState) {
EndPartOfScreen()
}
}
val (userPickerState, scaffoldState, switchingUsersAndHosts ) = settingsState
val (userPickerState, scaffoldState ) = settingsState
val scope = rememberCoroutineScope()
if (scaffoldState.drawerState.isOpen) {
Box(
@@ -312,9 +332,11 @@ fun DesktopScreen(settingsState: SettingsViewState) {
)
}
VerticalDivider(Modifier.padding(start = DEFAULT_START_MODAL_WIDTH))
UserPicker(chatModel, userPickerState, switchingUsersAndHosts) {
scope.launch { if (scaffoldState.drawerState.isOpen) scaffoldState.drawerState.close() else scaffoldState.drawerState.open() }
userPickerState.value = AnimatedViewState.GONE
tryOrShowError("UserPicker", error = {}) {
UserPicker(chatModel, userPickerState) {
scope.launch { if (scaffoldState.drawerState.isOpen) scaffoldState.drawerState.close() else scaffoldState.drawerState.open() }
userPickerState.value = AnimatedViewState.GONE
}
}
ModalManager.fullscreen.showInView()
}
@@ -335,3 +357,26 @@ fun InitializationView() {
}
}
}
@Composable
private fun SwitchingUsersView() {
if (remember { chatModel.switchingUsersAndHosts }.value) {
Box(
Modifier.fillMaxSize().clickable(enabled = false, onClick = {}),
contentAlignment = Alignment.Center
) {
ProgressIndicator()
}
}
}
@Composable
private fun ProgressIndicator() {
CircularProgressIndicator(
Modifier
.padding(horizontal = 2.dp)
.size(30.dp),
color = MaterialTheme.colors.secondary,
strokeWidth = 2.5.dp
)
}
@@ -2,7 +2,7 @@ package chat.simplex.common.model
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.runtime.snapshots.SnapshotStateMap
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.*
@@ -27,7 +27,6 @@ import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.*
import java.io.File
import java.net.URI
import java.net.URLDecoder
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.*
@@ -43,7 +42,7 @@ object ChatModel {
val setDeliveryReceipts = mutableStateOf(false)
val currentUser = mutableStateOf<User?>(null)
val users = mutableStateListOf<UserInfo>()
val userCreated = mutableStateOf<Boolean?>(null)
val localUserCreated = mutableStateOf<Boolean?>(null)
val chatRunning = mutableStateOf<Boolean?>(null)
val chatDbChanged = mutableStateOf<Boolean>(false)
val chatDbEncrypted = mutableStateOf<Boolean?>(false)
@@ -51,6 +50,7 @@ object ChatModel {
val chats = mutableStateListOf<Chat>()
// map of connections network statuses, key is agent connection id
val networkStatuses = mutableStateMapOf<String, NetworkStatus>()
val switchingUsersAndHosts = mutableStateOf(false)
// current chat
val chatId = mutableStateOf<String?>(null)
@@ -58,7 +58,7 @@ object ChatModel {
val chatItemStatuses = mutableMapOf<Long, CIStatus>()
val groupMembers = mutableStateListOf<GroupMember>()
val terminalItems = mutableStateListOf<TerminalItem>()
val terminalItems = mutableStateOf<List<TerminalItem>>(listOf())
val userAddress = mutableStateOf<UserContactLinkRec?>(null)
// Allows to temporary save servers that are being edited on multiple screens
val userSMPServersUnsaved = mutableStateOf<(List<ServerCfg>)?>(null)
@@ -67,8 +67,11 @@ object ChatModel {
// set when app opened from external intent
val clearOverlays = mutableStateOf<Boolean>(false)
// set when app is opened via contact or invitation URI
val appOpenUrl = mutableStateOf<URI?>(null)
// Only needed during onboarding when user skipped password setup (left as random password)
val desktopOnboardingRandomPassword = mutableStateOf(false)
// set when app is opened via contact or invitation URI (rhId, uri)
val appOpenUrl = mutableStateOf<Pair<Long?, URI>?>(null)
// preferences
val notificationPreviewMode by lazy {
@@ -108,11 +111,15 @@ object ChatModel {
var updatingChatsMutex: Mutex = Mutex()
val desktopNoUserNoRemote: Boolean @Composable get() = appPlatform.isDesktop && currentUser.value == null && currentRemoteHost.value == null
fun desktopNoUserNoRemote(): Boolean = appPlatform.isDesktop && currentUser.value == null && currentRemoteHost.value == null
// remote controller
val remoteHosts = mutableStateListOf<RemoteHostInfo>()
val currentRemoteHost = mutableStateOf<RemoteHostInfo?>(null)
val remoteHostId: Long? get() = currentRemoteHost?.value?.remoteHostId
val newRemoteHostPairing = mutableStateOf<Pair<RemoteHostInfo?, RemoteHostSessionState>?>(null)
val remoteHostId: Long? @Composable get() = remember { currentRemoteHost }.value?.remoteHostId
fun remoteHostId(): Long? = currentRemoteHost.value?.remoteHostId
val remoteHostPairing = mutableStateOf<Pair<RemoteHostInfo?, RemoteHostSessionState>?>(null)
val remoteCtrlSession = mutableStateOf<RemoteCtrlSession?>(null)
fun getUser(userId: Long): User? = if (currentUser.value?.userId == userId) {
@@ -221,8 +228,23 @@ object ChatModel {
val chat: Chat
if (i >= 0) {
chat = chats[i]
val newPreviewItem = when (cInfo) {
is ChatInfo.Group -> {
val currentPreviewItem = chat.chatItems.firstOrNull()
if (currentPreviewItem != null) {
if (cItem.meta.itemTs >= currentPreviewItem.meta.itemTs) {
cItem
} else {
currentPreviewItem
}
} else {
cItem
}
}
else -> cItem
}
chats[i] = chat.copy(
chatItems = arrayListOf(cItem),
chatItems = arrayListOf(newPreviewItem),
chatStats =
if (cItem.meta.itemStatus is CIStatus.RcvNew) {
val minUnreadId = if(chat.chatStats.minUnreadItemId == 0L) cItem.id else chat.chatStats.minUnreadItemId
@@ -598,12 +620,13 @@ object ChatModel {
}
fun addTerminalItem(item: TerminalItem) {
if (terminalItems.size >= 500) {
terminalItems.removeAt(0)
if (terminalItems.value.size >= 500) {
terminalItems.value = terminalItems.value.subList(1, terminalItems.value.size)
}
terminalItems.add(item)
terminalItems.value += item
}
val connectedToRemote: Boolean @Composable get() = currentRemoteHost.value != null || remoteCtrlSession.value?.active == true
fun connectedToRemote(): Boolean = currentRemoteHost.value != null || remoteCtrlSession.value?.active == true
}
@@ -637,6 +660,9 @@ data class User(
val addressShared: Boolean = profile.contactLink != null
fun updateRemoteHostId(rh: Long?): User =
if (rh == null) this else this.copy(remoteHostId = rh)
companion object {
val sampleData = User(
remoteHostId = null,
@@ -1252,7 +1278,7 @@ data class GroupMember (
fun canChangeRoleTo(groupInfo: GroupInfo): List<GroupMemberRole>? =
if (!canBeRemoved(groupInfo)) null
else groupInfo.membership.memberRole.let { userRole ->
GroupMemberRole.values().filter { it <= userRole }
GroupMemberRole.values().filter { it <= userRole && it != GroupMemberRole.Author }
}
val memberIncognito = memberProfile.profileId != memberContactProfileId
@@ -1294,12 +1320,14 @@ data class GroupMemberIds(
@Serializable
enum class GroupMemberRole(val memberRole: String) {
@SerialName("observer") Observer("observer"), // order matters in comparisons
@SerialName("author") Author("author"),
@SerialName("member") Member("member"),
@SerialName("admin") Admin("admin"),
@SerialName("owner") Owner("owner");
val text: String get() = when (this) {
Observer -> generalGetString(MR.strings.group_member_role_observer)
Author -> generalGetString(MR.strings.group_member_role_author)
Member -> generalGetString(MR.strings.group_member_role_member)
Admin -> generalGetString(MR.strings.group_member_role_admin)
Owner -> generalGetString(MR.strings.group_member_role_owner)
@@ -2298,7 +2326,7 @@ data class CIFile(
sent = fileStatus.sent,
fileSource = fileSource
)
cachedRemoteFileRequests.add(fileSource)
cachedRemoteFileRequests[fileSource] = false
val showAlert = fileSize > 5_000_000 && allowToShowAlert
if (showAlert) {
AlertManager.shared.showAlertMsgWithProgress(
@@ -2307,7 +2335,7 @@ data class CIFile(
)
}
val res = chatModel.controller.getRemoteFile(rh.remoteHostId, rf)
cachedRemoteFileRequests.remove(fileSource)
cachedRemoteFileRequests[fileSource] = res
if (showAlert) {
AlertManager.shared.hideAlert()
}
@@ -2324,7 +2352,7 @@ data class CIFile(
): CIFile =
CIFile(fileId = fileId, fileName = fileName, fileSize = fileSize, fileSource = if (filePath == null) null else CryptoFile.plain(filePath), fileStatus = fileStatus, fileProtocol = FileProtocol.XFTP)
val cachedRemoteFileRequests = SnapshotStateList<CryptoFile>()
val cachedRemoteFileRequests = SnapshotStateMap<CryptoFile, Boolean>()
}
}
@@ -2357,7 +2385,7 @@ data class CryptoFile(
companion object {
fun plain(f: String): CryptoFile = CryptoFile(f, null)
fun desktopPlain(f: URI): CryptoFile = CryptoFile(URLDecoder.decode(f.rawPath, "UTF-8"), null)
fun desktopPlain(f: URI): CryptoFile = CryptoFile(f.toFile().absolutePath, null)
}
}
@@ -2915,7 +2943,7 @@ enum class NotificationPreviewMode {
}
data class RemoteCtrlSession(
val ctrlAppInfo: CtrlAppInfo,
val ctrlAppInfo: CtrlAppInfo?,
val appVersion: String,
val sessionState: UIRemoteCtrlSessionState
) {
@@ -2933,14 +2961,25 @@ data class RemoteCtrlSession(
@Serializable
sealed class RemoteCtrlSessionState {
@Serializable @SerialName("starting") object Starting: RemoteCtrlSessionState()
@Serializable @SerialName("searching") object Searching: RemoteCtrlSessionState()
@Serializable @SerialName("connecting") object Connecting: RemoteCtrlSessionState()
@Serializable @SerialName("pendingConfirmation") data class PendingConfirmation(val sessionCode: String): RemoteCtrlSessionState()
@Serializable @SerialName("connected") data class Connected(val sessionCode: String): RemoteCtrlSessionState()
}
sealed class UIRemoteCtrlSessionState {
@Serializable @SerialName("starting") object Starting: UIRemoteCtrlSessionState()
@Serializable @SerialName("connecting") data class Connecting(val remoteCtrl_: RemoteCtrlInfo? = null): UIRemoteCtrlSessionState()
@Serializable @SerialName("pendingConfirmation") data class PendingConfirmation(val remoteCtrl_: RemoteCtrlInfo? = null, val sessionCode: String): UIRemoteCtrlSessionState()
@Serializable @SerialName("connected") data class Connected(val remoteCtrl: RemoteCtrlInfo, val sessionCode: String): UIRemoteCtrlSessionState()
@Serializable
sealed class RemoteCtrlStopReason {
@Serializable @SerialName("discoveryFailed") class DiscoveryFailed(val chatError: ChatError): RemoteCtrlStopReason()
@Serializable @SerialName("connectionFailed") class ConnectionFailed(val chatError: ChatError): RemoteCtrlStopReason()
@Serializable @SerialName("setupFailed") class SetupFailed(val chatError: ChatError): RemoteCtrlStopReason()
@Serializable @SerialName("disconnected") object Disconnected: RemoteCtrlStopReason()
}
sealed class UIRemoteCtrlSessionState {
object Starting: UIRemoteCtrlSessionState()
object Searching: UIRemoteCtrlSessionState()
data class Found(val remoteCtrl: RemoteCtrlInfo, val compatible: Boolean): UIRemoteCtrlSessionState()
data class Connecting(val remoteCtrl_: RemoteCtrlInfo? = null): UIRemoteCtrlSessionState()
data class PendingConfirmation(val remoteCtrl_: RemoteCtrlInfo? = null, val sessionCode: String): UIRemoteCtrlSessionState()
data class Connected(val remoteCtrl: RemoteCtrlInfo, val sessionCode: String): UIRemoteCtrlSessionState()
}

Some files were not shown because too many files have changed in this diff Show More