Compare commits

...

257 Commits

Author SHA1 Message Date
Evgeny Poberezkin
fe0ff44f92 Merge branch 'master' into ep/add-splice 2023-11-17 11:00:44 +00:00
Evgeny Poberezkin
7a7f3dbd74 core: add simplemq version 2023-11-17 10:51:47 +00:00
Evgeny Poberezkin
0d7a048988 nix: patches for armv7a, fix segfault issues, etc (#3383)
* bump mobile-core-tools

* Update deps

* 🤦

* patch

* needs p

* 32bit patches

* bump haskell.nix

* bump again

* fix broken flake.lock

* bump haskell.nix

* bump haskell.nix (to fix darwin)

---------

Co-authored-by: Moritz Angermann <moritz.angermann@gmail.com>
2023-11-16 22:54:54 +00:00
spaced4ndy
975f6d488e android: fix group join via invitation chat item (#3372) 2023-11-15 11:46:45 +04:00
spaced4ndy
36509a6d79 ios, android: new message decryption error - ratchet synchronization (#3368) 2023-11-14 19:39:32 +04:00
spaced4ndy
5bbde22ffa core: new message decryption error - ratchet synchronization (#3367) 2023-11-14 18:23:05 +04:00
Evgeny Poberezkin
1e8ae6d861 docs: update windows app link 2023-11-14 09:37:24 +00:00
Evgeny Poberezkin
a2fe5cfb66 core: fix incorrect JSON serialization (#3361) 2023-11-13 17:45:10 +00:00
Stanislav Dmitrenko
338417d963 desktop: catch unreadable crypto file (#3359) 2023-11-13 14:06:01 +00:00
spaced4ndy
5beeff5cb6 core: take chat lock when synchronizing ratchet (#3349) 2023-11-12 12:41:41 +00:00
Evgeny Poberezkin
11362941fd 5.4.0-beta.3: iOS 181, Android 160, Desktop 16 2023-11-11 12:12:04 +00:00
Evgeny Poberezkin
f7b4e4b16a core: 5.4.0.3 2023-11-11 09:36:16 +00:00
Evgeny Poberezkin
83aaaa9ada translation: remove duplicate string 2023-11-10 20:45:41 +00:00
Stanislav Dmitrenko
ae286124aa ios: allow sound in silent mode (#3346)
Co-authored-by: Avently <avently@local>
2023-11-10 19:27:06 +00:00
Evgeny Poberezkin
9cc232054c website: translations (#3345)
* core: notify contact about contact deletion

* Translated using Weblate (French)

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/fr/

* Translated using Weblate (Dutch)

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/nl/

* Translated using Weblate (Italian)

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/it/

* 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 (Hebrew)

Currently translated at 34.1% (86 of 252 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (252 of 252 strings)

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

* Translated using Weblate (Chinese (Simplified))

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/zh_Hans/

* Translated using Weblate (Hebrew)

Currently translated at 41.2% (104 of 252 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 74.6% (188 of 252 strings)

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

* Translated using Weblate (Czech)

Currently translated at 92.0% (232 of 252 strings)

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

* 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 (Hebrew)

Currently translated at 90.4% (228 of 252 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 94.8% (239 of 252 strings)

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

* Translated using Weblate (Hebrew)

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/he/

* Translated using Weblate (Hebrew)

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/he/

* Translated using Weblate (German)

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/de/

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: M1K4 <oomikaoo@gmail.com>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Co-authored-by: ItaiShek <itaishek@gmail.com>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: Eric <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: zenobit <zen@osowoso.xyz>
Co-authored-by: mlanp <github@lang.xyz>
2023-11-10 17:57:02 +00:00
Evgeny Poberezkin
e17e6adefb ui: translations (#3343)
* Translated using Weblate (French)

Currently translated at 100.0% (1387 of 1387 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% (1250 of 1250 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1387 of 1387 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% (1250 of 1250 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% (1387 of 1387 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% (1250 of 1250 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 98.6% (1233 of 1250 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 99.2% (1376 of 1387 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 99.7% (1383 of 1387 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1387 of 1387 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% (1250 of 1250 strings)

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

* Translated using Weblate (Czech)

Currently translated at 98.0% (1360 of 1387 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1250 of 1250 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1387 of 1387 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 100.0% (1387 of 1387 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1387 of 1387 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1387 of 1387 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1250 of 1250 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1387 of 1387 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% (1250 of 1250 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1387 of 1387 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1250 of 1250 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1387 of 1387 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% (1250 of 1250 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (1387 of 1387 strings)

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

* Translated using Weblate (Russian)

Currently translated at 99.5% (1381 of 1387 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (1387 of 1387 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1387 of 1387 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% (1250 of 1250 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (1387 of 1387 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1387 of 1387 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% (1250 of 1250 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% (1387 of 1387 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1250 of 1250 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (1387 of 1387 strings)

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

* Translated using Weblate (Czech)

Currently translated at 99.9% (1249 of 1250 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 99.7% (1383 of 1387 strings)

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

* Translated using Weblate (Finnish)

Currently translated at 99.2% (1376 of 1387 strings)

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

* Translated using Weblate (Finnish)

Currently translated at 98.9% (1237 of 1250 strings)

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

* 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/

* corrections

* correction

* fix android translations

* ios: import/export localizations

---------

Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: M1K4 <oomikaoo@gmail.com>
Co-authored-by: a4318 <dalse.077@gmail.com>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: Jan Čejka <posta@jancejka.cz>
Co-authored-by: elgratea <weblate@fastmail.com>
Co-authored-by: ItaiShek <itaishek@gmail.com>
Co-authored-by: Eric <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: zenobit <zen@osowoso.xyz>
Co-authored-by: pazengaz <pazengaz@porcod.io>
Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
Co-authored-by: Shamil Bikineyev <shamilbi@gmail.com>
Co-authored-by: sith-on-mars <groguko34@skiff.com>
Co-authored-by: Jiri Grönroos <jiri.gronroos@iki.fi>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
2023-11-10 16:31:59 +00:00
Stanislav Dmitrenko
e7d6ed66da android: fix crash when playing recorded voice message (#3325)
* android: fix crash when playing recorded voice message

* better
2023-11-10 16:09:19 +00:00
Stanislav Dmitrenko
8d891005d9 ui: disable expanding one item (#3344)
* ui: disable expanding one item

* better

* when
2023-11-10 16:09:01 +00:00
Stanislav Dmitrenko
f648086934 windows: upgrade UUID (#3341) 2023-11-10 11:50:31 +00:00
Stanislav Dmitrenko
fcdd8ce7c1 windows: script for building the lib (#3340)
* windows: script for building the lib

* changes

* change

* change
2023-11-10 11:49:53 +00:00
spaced4ndy
c0be36737d android: connect with contact via address (for preset simplex contact) (#3330) 2023-11-10 10:16:28 +04:00
spaced4ndy
f49ded5ae5 ios: connect with contact via address (for preset simplex contact) (#3323)
* ios: connect with contact via address (for preset simplex contact)

* remove diff

* remove floating button

* refactor active

* open chat

* remove disabled

* fix incognito

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-11-10 10:16:06 +04:00
Stanislav Dmitrenko
96d94d3438 android, desktop: fix linking (#3333)
Co-authored-by: avently <avently@local>
2023-11-09 07:55:01 +00:00
Evgeny Poberezkin
d233d07ddc ci: ghc 9.6.3 (#3328) 2023-11-08 12:50:56 +00:00
spaced4ndy
8722d35278 core: fix deletion of contact without connections (#3327) 2023-11-08 13:15:08 +04:00
spaced4ndy
ee6bd0f839 core: add image to simplex contact profile (#3326) 2023-11-08 10:56:55 +04:00
Stanislav Dmitrenko
e3938f6fb5 android: replaced function that requires higher API (#3324) 2023-11-07 22:58:19 +00:00
Stanislav Dmitrenko
2dc621a56c mobile: keep screen on while playing/recording media (#3317)
* android: keep screen on while playing/recording media

* ios: keep screen on while playing/recording media

* different implementation on ios

* Revert "android: keep screen on while playing/recording media"

This reverts commit d291f006e9.

* different implementation on android

* refactor

---------

Co-authored-by: Avently <avently@local>
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-11-07 16:56:38 +00:00
Stanislav Dmitrenko
3e46c5dfaf android: fixed crash on device rotation in Create link screen (#3322) 2023-11-07 16:48:32 +00:00
spaced4ndy
a04dc5d05b core: preset simplex contact (#3321) 2023-11-07 17:45:59 +04:00
Evgeny Poberezkin
b33fe01e49 core: switch to GHC 9.6.3 (#3307)
* Various fixes aggregated
- windows definisions
- set compile rto 9.6.3
- flake adjust to 9.6.3
- update haskellNix
- add various patches

* Unbreak iOS

* update script and sha256map.nix

* ios: update core lib

---------

Co-authored-by: Moritz Angermann <moritz.angermann@gmail.com>
2023-11-06 11:43:43 +00:00
spaced4ndy
15b55f7924 ios, android: fix contactInfo response encoding (#3319) 2023-11-06 13:43:37 +04:00
Stanislav Dmitrenko
eee233bd02 android, desktop: catching decoding errors (#3314) 2023-11-04 17:21:29 +00:00
Stanislav Dmitrenko
10cbb13c26 desktop: screen sharing in video calls (#3310)
* desktop: screen sharing

* use async function

* fit/fill of the video

* disconnect camera button from screen share

* enable video on audio call

* temp

* Revert "temp"

This reverts commit 8f8a2f7f88.

* Revert "enable video on audio call"

This reverts commit 120068d09a.

* different logic

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-11-04 16:59:07 +00:00
Evgeny Poberezkin
4816150b99 core: contacts without connections (#3313)
* core: contacts without connections

* compiles (some tests don't pass)

* remove commented code

* filter out user contact (fixes tests)

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2023-11-03 18:15:07 +00:00
Stanislav Dmitrenko
3d7258fa58 android: fixed QR code sharing (#3311)
* android: fixed QR code sharing

* remove mime type change

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-11-02 23:11:26 +00:00
Stanislav Dmitrenko
c462dd3704 android, desktop: removed unused plugin (#3309) 2023-11-02 20:59:16 +00:00
Evgeny Poberezkin
34b07d6a3b core: update simplexmq (http2 lib update to fix sending files) 2023-11-02 10:44:24 +00:00
Stanislav Dmitrenko
fad5128a83 android, desktop: updated Compose and changed mac notarization tool (#3303)
* android, desktop: updated Compose and changed mac notarization tool

* imports

* desktop (mac): fix lib building

* imports

---------

Co-authored-by: Avently <avently@local>
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-11-01 19:11:04 +00:00
Stanislav Dmitrenko
4fd38a270c desktop: adding build version code to UI (#3304) 2023-11-01 18:23:41 +00:00
Stanislav Dmitrenko
4cc20a2d32 android, desktop: block members (#3290)
* android, desktop: block members

* fixes

* more fixes

* fix

* fix

* color

* color and icon

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-11-01 13:52:45 +00:00
spaced4ndy
68873464d7 docs: groups integrity DAGs rfc (#3258) 2023-11-01 17:30:40 +04:00
spaced4ndy
c1a0486c1d docs: groups integrity rfc (#3128) 2023-11-01 17:30:19 +04:00
Evgeny Poberezkin
c8c17a2f68 core: fix uri parse to not include trailing punctuation in URIs (#3296)
* core: fix uri parse to not include trailing punctuation in URIs

* simplify
2023-11-01 13:10:19 +00:00
Evgeny Poberezkin
9e8084874f ios: block members (#3248)
* ios: block members (WIP)

* CIBlocked, blocking api

* show item as blocked

* show blocked and merge multiple deleted items

* update block icons

* split sent and received deleted to two categories

* merge chat feature items, refactor CIMergedRange

* merge feature items, two profile images and names on merged items

* ensure range is withing chat items range

* merge group events

* fix/refactor

* make group member changes observable

* exclude some group events from merging

* fix states not updating and other fixes

* load list of members when tapping profile

* refactor

* fix incorrect merging of sent/received marked deleted

* fix incorrect expand/hide on single moderated items without content

* load members list when opening member via item

* comments

* fix member counting in case of name collision
2023-10-31 09:44:57 +00:00
spaced4ndy
07173f7b2f core: add delays to testXFTPMarkToReceive test (#3294) 2023-10-31 10:51:20 +04:00
spaced4ndy
42458a2715 ios, android: process new group link events (#3293) 2023-10-31 10:51:02 +04:00
spaced4ndy
f34bbdbd9c core: improve group link protocol (immediately establish group connection without first creating contact) (#3288) 2023-10-30 20:40:20 +04:00
Evgeny Poberezkin
9568279b0f update simplexmq 2023-10-29 18:21:51 +00:00
spaced4ndy
a7b5dfb74c android: create new group with incognito membership (#3285) 2023-10-27 09:33:59 +04:00
spaced4ndy
7102723c23 ios: create new group with incognito membership (#3284)
* ios: create new group with incognito membership

* layout

* fix button

* update layout

* layout

* layout

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-10-26 18:51:45 +04:00
spaced4ndy
4a5fdd3e0e ios, android: show progress indicator on joining group (#3281) 2023-10-26 10:32:11 +04:00
Evgeny Poberezkin
4a8da196ad core: more permissive display name validation, only allow single quotes in CLI for the names with spaces (#3282) 2023-10-25 11:55:06 +01:00
Stanislav Dmitrenko
743597e848 ios: making message text view working better (#3279)
* ios: making message text view working better

* style for ternaries

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-10-25 09:56:59 +01:00
spaced4ndy
b0f55d6de5 core: update simplexmq (check snd queue) (#3280) 2023-10-25 10:45:36 +04:00
Stanislav Dmitrenko
1dcd2760b0 ui: show alert after saving profile with existing name (#3273)
* android, desktop: show alert after saving profile with existing name

* ios: show alert after saving profile with existing name

* return statements

* better way of showing alert

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-10-24 23:01:47 +01:00
Stanislav Dmitrenko
10f79aae66 android: alert on unsupported file path when sharing (#3265)
* android: alert on unsupported file path when sharing

* update text

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-10-24 21:39:43 +01:00
Stanislav Dmitrenko
b58d61c339 android: delete files after sharing correctly (#3264) 2023-10-24 21:27:58 +01:00
spaced4ndy
239765e482 core: create new group with incognito membership (#3277) 2023-10-24 20:59:06 +04:00
spaced4ndy
f8332bac7f core: take chat lock earlier when joining group (#3272) 2023-10-24 18:13:19 +04:00
spaced4ndy
ed1eef7362 core: update simplexmq (inv locks) (#3274) 2023-10-24 17:38:16 +04:00
Evgeny Poberezkin
66d8bb94d6 website: downloads page 2023-10-23 21:16:36 +01:00
Evgeny Poberezkin
6eb09625ab website: update copy 2023-10-23 20:53:12 +01:00
Evgeny Poberezkin
0bd59364fd 5.4.0-beta.2: iOS 180, Android 159, Desktop 15 2023-10-22 22:43:23 +01:00
Evgeny Poberezkin
795c54343a android, desktop: reduce browser call logs, check Worker availability directly (#3256) 2023-10-22 20:51:08 +01:00
Evgeny Poberezkin
f026a38a75 ios: update core library 2023-10-22 19:22:46 +01:00
Stanislav Dmitrenko
530ec70171 android, desktop: support calls on desktop and moved www dir to different root (#3219)
* android, desktop: support calls on desktop and moved www dir to different root

* add page title, fix links on Android, change timeouts

* using worker in desktop Chrome and Safari

* ui changes

* end call button in app bar

* fix android

* a lot of enhancements

* fix after merge master

* layout

* sound play on call

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-10-22 18:47:27 +01:00
Era Dorta
1401f56288 cli: update docker image to use ghc 9.6.2 (#3234) 2023-10-22 18:15:15 +01:00
Evgeny Poberezkin
d172b0cb6d Merge branch 'stable' 2023-10-22 17:51:30 +01:00
Evgeny Poberezkin
3e0b6826bf ios: 5.3.2 build 179 2023-10-22 17:50:08 +01:00
Evgeny Poberezkin
79275424ea core: 5.4.0.2 2023-10-22 15:06:55 +01:00
Evgeny Poberezkin
b25c2e3a09 ui: add 10 minutes SimpleX Lock delay (#3255) 2023-10-22 14:56:51 +01:00
Evgeny Poberezkin
8891314507 core: update simplexmq (fixes ordering issue during message delivery) 2023-10-22 14:19:24 +01:00
Evgeny Poberezkin
5c57987e9f add smp11, 12 and 14 to preset servers 2023-10-22 13:58:51 +01:00
spaced4ndy
87d84cfccc core: filter connection plan query results by user_id (#3251) 2023-10-21 19:13:32 +04:00
spaced4ndy
c090b68bdd ios, android: ask to notify contact or not on contact deletion (#3247) 2023-10-19 19:52:59 +04:00
Evgeny Poberezkin
2219cea026 core: fix type for JSON 2023-10-18 21:15:37 +01:00
spaced4ndy
852e77b1d9 android: connect plan (#3242) 2023-10-18 16:58:33 +04:00
Evgeny Poberezkin
706d6bf65b core, ui: prevent old sent items re-added to chat, and "new" status overriding "sent" (#3246)
* core, ui: prevent old sent items re-added to chat, and "new" status overriding "sent"

* clear item statuses when changing current chat

* remove iOS hack

* remote state/published from chatItemStatuses
2023-10-18 11:23:35 +01:00
Evgeny Poberezkin
a02886ca5d core: fix editing and status changes removing reactions from view (#3245)
* core: fix editing and status changes removing reactions from view

* refactor

* refactor 2

* case
2023-10-18 10:19:24 +01:00
Evgeny Poberezkin
29c8ab7c9b ui: file & media preference for groups (#3243) 2023-10-17 18:05:16 +01:00
spaced4ndy
d8d47d706d ios: connection plan improvements; remove browser mode for simplex links (#3237) 2023-10-17 12:56:12 +04:00
spaced4ndy
99c458406f android: remove browser mode for simplex links (#3239) 2023-10-17 11:44:35 +04:00
spaced4ndy
e4c8386f3f core: replace simplex:/ with simplex.chat links in view; remove trustedUri flag from simplex links markdown format (#3235) 2023-10-16 19:23:38 +04:00
spaced4ndy
9ed31261e1 core: check saved links and hashes by both connection request uri schemas for connection plan (#3233) 2023-10-16 16:16:03 +04:00
spaced4ndy
4b6df43e97 core: confirm to reconnect via address plan (#3212)
* core: confirm to reconnect plan

* rework query to prefer connections with contacts
2023-10-16 16:10:56 +04:00
Evgeny Poberezkin
43b67ba157 ui: set local file encryption in the core (#3227) 2023-10-15 20:58:39 +01:00
Evgeny Poberezkin
e6b0983c3e Merge branch 'stable' 2023-10-15 18:52:46 +01:00
Evgeny Poberezkin
c2a320640b core: local encryption for auto-received inline files (e.g. small voice messages) (#3224)
* core: local encryption for auto-received inline files

* update view, test
2023-10-15 18:16:12 +01:00
Evgeny Poberezkin
838751fe78 ios: fix Protect screen not hiding message previews (#3226) 2023-10-15 18:12:13 +01:00
Evgeny Poberezkin
a35dc263b7 website: remove address from connect page 2023-10-13 22:28:55 +01:00
spaced4ndy
c609303348 ios: connect plan (#3205)
* ios: connect plan

* improvements

* wording

* fixes

* rework to use dismissAllSheets with callback

* rework

* update texts

* Update apps/ios/Shared/Views/NewChat/NewChatButton.swift

* Update apps/ios/Shared/Views/NewChat/NewChatButton.swift

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-10-13 19:19:00 +04:00
Evgeny Poberezkin
07047a3ef3 ios: fix pattern match 2023-10-13 14:29:48 +01:00
Evgeny Poberezkin
ab290fb068 core: track network statuses, use in commands/events (#3211)
* core: track network statuses, use in commands/events

* ui types, test

* remove comment
2023-10-13 11:51:01 +01:00
Stanislav Dmitrenko
675fc19745 rfc: desktop calls (#3208)
* rfc: desktop calls

* errors

* html

* link

* screen sharing

* additions

* addition

* correction

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-10-13 00:05:06 +01:00
Stanislav Dmitrenko
8ffe1c23c1 android, desktop: better handling of parallel updates of chats (#3204)
* android, desktop: better handling of parallel updates of chats

* one more case
2023-10-12 13:19:57 +01:00
spaced4ndy
5d078bec53 tests: simplify testMemberContactInvitedConnectionReplaced (for stable) 2023-10-12 12:21:03 +04:00
spaced4ndy
b956988a83 tests: simplify testMemberContactInvitedConnectionReplaced (for stable) 2023-10-12 12:20:31 +04:00
spaced4ndy
247f2c9e61 tests: modify testMemberContactInvitedConnectionReplaced to not rely on chat item order, print output (#3198) 2023-10-12 11:55:38 +04:00
spaced4ndy
7b488c7f1b tests: improve tests (#3203) 2023-10-12 11:52:14 +04:00
Evgeny Poberezkin
4df8ea2e78 ui: update types for notification and member settings (#3201) 2023-10-11 23:07:05 +01:00
Evgeny Poberezkin
8ff6b392c2 core: rename "reference" to "mention" 2023-10-11 21:15:31 +01:00
Evgeny Poberezkin
bca9473d77 core: settings to hide member messages, to show only reply (and mention) notifications (#3190)
* core: settings to hide member messages, to show only reply (and mention) notifications

* change type for showMessages

* commands for member settings

* member and notification settings

* test

* take member settings into account when showing messages and notifications

* fix to show sent messages

* store blocked items

* types

* rename to MFMentions
2023-10-11 19:10:38 +01:00
spaced4ndy
b03fe183bb tests: modify testMemberContactInvitedConnectionReplaced to not rely on chat item order, print output (#3198) 2023-10-11 15:26:44 +04:00
Evgeny Poberezkin
4ecf94dfad core: move CLI notifications and active chat to view layer (for remote CLI) (#3196)
* core: move CLI notifications to view layer (to allow notifications in remote CLI)

* remove unused

* refactor activeTo

* move activeTo to ChatTerminal

* refactor

* move back

* remove extension
2023-10-11 09:50:11 +01:00
spaced4ndy
a67b79952b core: connection plan api; check connection plan before connecting in terminal api (#3176) 2023-10-10 21:19:04 +04:00
Evgeny Poberezkin
eb5081624a Merge branch 'stable' 2023-10-09 19:52:30 +01:00
Evgeny Poberezkin
86c2f29920 5.3.2: ios 178, android 157, desktop 14 2023-10-09 18:30:59 +01:00
Evgeny Poberezkin
dffbd32c76 Merge branch 'stable' 2023-10-09 18:04:11 +01:00
Evgeny Poberezkin
3ddf7b2680 ios: close database connections when app is terminating (#3188)
* ios: close database connections when app is terminating

* update

* remove ()

* close when suspended too

* additional check

* fix

* refactore

* reset "terminating" flag
2023-10-09 18:03:03 +01:00
Evgeny Poberezkin
c0e22d74c4 core: 5.4.0.1 2023-10-09 17:30:48 +01:00
Stanislav Dmitrenko
d764b3485a desktop (windows): Github action for packaging (#3167)
* desktop (windows): Github action for packaging

* env

* path changes
2023-10-09 17:10:47 +01:00
Evgeny Poberezkin
09e5798d59 ios: correctly parse json responses (#3193) 2023-10-09 16:56:42 +01:00
Evgeny Poberezkin
f31b4e4632 Merge branch 'stable' 2023-10-09 16:00:24 +01:00
spaced4ndy
d72c04682f Merge pull request #3174 from simplex-chat/contact-merge-improvements
contact merge improvements
2023-10-09 18:05:46 +04:00
Evgeny Poberezkin
20995c6912 core: 5.3.2.0 2023-10-09 14:35:44 +01:00
Moritz Angermann
73b3ea3648 Drop entropy patch (#3191)
* Drop entropy patch

We don't need the patch anymore. We can set -fDoNotGetEntropy these days to achieve the same.

* remove entropy.patch

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-10-09 14:04:29 +01:00
Evgeny Poberezkin
bc26c23d58 fix MobileTests (add single field JSON tag) 2023-10-09 10:35:13 +01:00
spaced4ndy
4a581cb292 Merge branch 'master' into contact-merge-improvements 2023-10-09 10:21:54 +04:00
spaced4ndy
ab46cbc5dd core: relax contact probing: don't send probe hashes to new contacts except group link hosts; still send probe hashes to group members (#3180) 2023-10-09 09:46:58 +04:00
Evgeny Poberezkin
985b9837c3 core: api to close database connection (#3186) 2023-10-08 16:22:53 +01:00
Moritz Angermann
d50c7ad7f6 bump hackage (#3185) 2023-10-08 08:37:28 +01:00
Evgeny Poberezkin
82faaebb33 Merge branch 'stable' 2023-10-08 08:17:05 +01:00
Stanislav Dmitrenko
76fb5b6dca android: fix lock not showing (#3181)
* android: fix lock not showing

* better fix
2023-10-07 19:00:40 +01:00
Evgeny Poberezkin
b05a45f559 core: updated simplexmq (add tag to platform-specific sum type encoding that uses single field objects with swift/iOS) 2023-10-07 12:32:39 +01:00
spaced4ndy
c738c6c522 Merge branch 'master' into contact-merge-improvements 2023-10-06 14:38:56 +04:00
spaced4ndy
1be70169ba docs: contact merge issues rfc (#3179) 2023-10-06 14:27:13 +04:00
Stanislav Dmitrenko
34e1e44338 android, desktop: profile names (remove full name) (#3177)
* desktop, android: profile names (remove full name)

* rename back

* disallow spaces only in names

* ios: disallow spaces only in names

* changes
2023-10-05 21:49:18 +01:00
spaced4ndy
303d0eedf5 core: merge new contacts with existing contacts and group members (#3173) 2023-10-04 20:58:22 +04:00
Evgeny Poberezkin
0d8558a6d0 ios: profile names (remove full name) (#3168)
* ios: profile names (remove full name)

* create/update groups

* focus display name
2023-10-04 17:45:39 +01:00
Stanislav Dmitrenko
91fc238ddc desktop: libs refactoring (#3169)
* desktop: libs refactoring

* mac fix

* windows fix

* .gitignore

* unused lines

* desktop (windows): adapting Windows build to new libs

* removed unused code

---------

Co-authored-by: avently <avently@local>
2023-10-04 17:06:23 +01:00
Stanislav Dmitrenko
cc95fa6b30 desktop: paste files/images to attach to message (#3165)
* desktop: paste files/images to attach to message

* Windows

* copy files inside the app

* change

* encrypted files support

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-10-03 13:46:17 +01:00
Evgeny Poberezkin
38be27271f core: profile names with spaces (#3105)
* core: profile names with spaces

* test

* more test

* update name validation, export C API

* refactor

* validate name when creating profile in CLI

* validate display name in all APIs when it is changed
2023-10-02 21:56:11 +01:00
zenobit
da2a94578a typo (#3121) 2023-10-02 21:21:26 +01:00
Stanislav Dmitrenko
77db70139b windows: shortcut for installator (#3156)
Co-authored-by: avently <avently@local>
2023-10-02 17:25:49 +01:00
Stanislav Dmitrenko
fdf3da73aa desktop: making chat list item to have a hover effect (#3162)
* desktop: making chat list item to have a hover effect

* changes

* fix

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-10-02 17:23:38 +01:00
Stanislav Dmitrenko
0d93dab692 android, desktop: added useful logs (#3163) 2023-10-02 15:46:30 +01:00
spaced4ndy
d4cbef1ba1 core: notify about contact deletion only if contact is ready, catch errors (#3160) 2023-10-02 16:29:13 +04:00
Evgeny Poberezkin
8545a1e8f9 ci: make docs update rebuild website 2023-10-01 20:46:30 +01:00
Evgeny Poberezkin
157ea59ebb docs: update downloads page 2023-10-01 18:53:58 +01:00
Evgeny Poberezkin
7231201c3c v5.4-beta.0: ios 176, android 156, desktop 12
* desktop: v5.4-beta.0 build 12

* v5.4-beta.0: ios 176, android 156, desktop 12
2023-10-01 18:31:52 +01:00
Stanislav Dmitrenko
695d47da2d desktop: Windows build (#3143)
* desktop: Windows build

* temp

* temp

* new way of libs loading

* new way of libs loading

* Revert "new way of libs loading"

This reverts commit 8632f8a8f7.

* made VLC working on Windows

* unused lib

* scripts

* updated script

* fix path

* fix lib loading

* fix lib loading

* packaging options

* different file manager implementation on Windows

---------

Co-authored-by: Avently <avently.local>
Co-authored-by: avently <avently@local>
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-10-01 13:33:15 +01:00
Evgeny Poberezkin
968d8e9c34 core: 5.4.0.0 2023-10-01 13:19:32 +01:00
Stanislav Dmitrenko
d72c9a6de0 desktop: ability to always show terminal view (#3074)
* desktop: ability to always show terminal view

* only show toggle with dev tools enabled

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-10-01 12:12:17 +01:00
Alexander Bondarenko
5ce388522e Move toView and withStore* to a common module (#3147) 2023-09-29 15:50:20 +01:00
Evgeny Poberezkin
70a65e8969 core: close stores before import/delete/encryption operations to make compatible with windows, make encryption more resilient (#3146)
* core: close stores before import/delete/encryption operations to make compatible with windows, make encryption more resilient

* remove file names

* do not remove files if already removed
2023-09-29 13:09:48 +01:00
Evgeny Poberezkin
1d34500fba core: revert stop/close changes made for Windows (#3145)
* Revert "core: return error response when wrong passphrase is passed to start"

This reverts commit ea319313f1.

* Revert "core: support closing/re-opening store on chat stop/start (#3132)"

This reverts commit 3c7fc6b0ee.
2023-09-29 11:14:10 +01:00
spaced4ndy
bc7baf560b core: filter out connections of deleted contacts and group members on subscribe (#3144) 2023-09-29 11:24:16 +04:00
Stanislav Dmitrenko
c1854b7d50 desktop: fix script for building the lib (#3141) 2023-09-28 11:39:43 +01:00
spaced4ndy
682dfe503c android, desktop: notify contact about contact deletion (#3139)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-09-28 13:52:43 +04:00
spaced4ndy
957f3b3eb0 core: delete unused contact silently (#3140) 2023-09-28 13:16:03 +04:00
Evgeny Poberezkin
dea96df27b docs: update join team 2023-09-28 09:26:54 +01:00
Evgeny Poberezkin
942e5eb8c4 docs: update branches 2023-09-27 22:19:20 +01:00
Evgeny Poberezkin
ea319313f1 core: return error response when wrong passphrase is passed to start 2023-09-27 21:15:19 +01:00
spaced4ndy
bbe329072e ios: notify contact about contact deletion (#3135) 2023-09-27 20:07:32 +04:00
spaced4ndy
c64d1e8361 core: notify contact about contact deletion (#3131) 2023-09-27 19:36:13 +04:00
Stanislav Dmitrenko
7e17ed7b1b desktop (mac): removing rpaths (#3136)
* desktop (mac): removing rpaths

* one more lib

* added check for dir existence in linking

* new line

* patching libapp on mac
2023-09-27 15:34:46 +01:00
Evgeny Poberezkin
3c7fc6b0ee core: support closing/re-opening store on chat stop/start (#3132)
* core: support closing/re-opening store on chat stop/start

* update .nix refs

* kotlin: types

* add test

* update simplexmq
2023-09-27 15:26:03 +01:00
Stanislav Dmitrenko
8709ad6ff3 desktop: enhanced video player + inline player (#3130)
* desktop: enhanced video player + inline player

* simplify

* simplify

* removed unused code

* follow up

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-09-27 10:19:48 +01:00
Evgeny Poberezkin
50d624ef6b blog: move image 2023-09-25 20:09:08 +01:00
Evgeny Poberezkin
11e448267d website: update post, downloads page 2023-09-25 18:12:47 +01:00
Evgeny Poberezkin
aacf741ef5 ci: exclude -fdroid tags from github builds 2023-09-25 16:51:10 +01:00
spaced4ndy
420d80ad6c 5.3.1: android 154, ios 174, desktop 11
* 5.3.1

* 5.3.1: ios 174, desktop 11

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-09-25 16:41:53 +01:00
spaced4ndy
343131c64e core: 5.3.1.0 2023-09-25 17:43:03 +04:00
spaced4ndy
9b107fbdeb core: fix invited member contact (do not display invitation context in UI) (#3122) 2023-09-25 16:39:27 +04:00
spaced4ndy
60d13e258e ios, android: show rcv integrity error items based on developer tools default (#3123) 2023-09-25 16:38:48 +04:00
Evgeny Poberezkin
4f42c2b0d8 blog: v5.3 announcement (#3093)
* blog: v5.3 announcement draft

* update

* update post

* add images and previews

* website: add imageWide property

* website: add .float-to-left class

* update

* update images

* update readme

* fix typo

---------

Co-authored-by: M Sarmad Qadeer <MSarmadQadeer@gmail.com>
2023-09-25 09:32:27 +01:00
Stanislav Dmitrenko
48ae1111a6 desktop: fix lib copy (#3120) 2023-09-25 09:30:44 +01:00
Stanislav Dmitrenko
76dbe32cfc desktop: fix JNI (#3119) 2023-09-25 09:30:21 +01:00
Evgeny Poberezkin
120f42cbba readme: update languages 2023-09-23 18:37:03 +01:00
Evgeny Poberezkin
5f46433f40 docs: update translations in readme 2023-09-23 18:33:54 +01:00
Evgeny Poberezkin
7b71078c76 update downloads page 2023-09-23 17:36:44 +01:00
Evgeny Poberezkin
d6b62d0c18 5.3: ios 173, android 152, desktop 10 2023-09-22 21:09:36 +01:00
Evgeny Poberezkin
ba4c427bec ios: update core libraries 2023-09-22 17:43:03 +01:00
Evgeny Poberezkin
a179154e87 core: 5.3.0.10 2023-09-22 17:20:30 +01:00
Stanislav Dmitrenko
5eea3da7f4 desktop: check chat id before changing chat items (#3103) 2023-09-22 14:59:14 +01:00
Evgeny Poberezkin
b3e880ee54 core: optimize C apis (#3100)
* core: optimize C apis

* more

* fix tests

* use pokeByteOff

* write lazy bytestring to buffer without conversion to strict

* avoid conversion of JSON to strict bytestrings
2023-09-22 13:45:16 +01:00
Stanislav Dmitrenko
08ea5dc2e7 desktop: ability to send a video (#3102) 2023-09-22 12:43:45 +01:00
M. Sarmad Qadeer
20f90ee865 website: add feeds filter (#3098) 2023-09-22 09:39:18 +01:00
spaced4ndy
5ca2ab6138 ios: fix delete contact crashing from chat view (#3099) 2023-09-22 12:23:53 +04:00
Evgeny Poberezkin
ba61b15225 docs: update downloads 2023-09-22 08:21:00 +01:00
Stanislav Dmitrenko
9ac13a3433 action: prevent AppImage generator from failing a job (#3096)
* action: prevent AppImage generator from failing a job

* corrected
2023-09-21 22:43:48 +01:00
Evgeny Poberezkin
b9407c0157 v5.3-beta.9: ios 172, android 151, desktop 9 2023-09-21 20:05:39 +01:00
Evgeny Poberezkin
5e8cfec653 ios: update core library 2023-09-21 19:10:19 +01:00
Evgeny Poberezkin
cb10f8b080 ui: optionally encrypt received SMP files (#3095) 2023-09-21 19:02:47 +01:00
Evgeny Poberezkin
b7c562fb10 ui: increast default timeouts per KB for XFTP (#3094) 2023-09-21 18:21:49 +01:00
Stanislav Dmitrenko
2d7655281f desktop: video and audio players (#3052)
* desktop: video and audio players

* making player working without preinstalled VLC

* mac support

* don't use vlc lib when not needed

* updated jna version

* changes in script

* video player lazy loading

* mac script changes

* updated build gradle for preserving atrributes of file while copying

* apply the same file stats on libs to make VLC checker happy

* updated script

* changes

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-09-21 18:03:47 +01:00
Evgeny Poberezkin
6de0ed4766 core: 5.3.0.9 2023-09-21 17:03:56 +01:00
Stanislav Dmitrenko
5b87206c22 desktop: AppImage metadata (#2952)
* desktop: AppImage metadata

* update appimage metadata

* add screenshots

* update screenshots

* update

* update URLs

* update link

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-09-21 13:19:24 +01:00
Evgeny Poberezkin
be3eb740a7 website: redirect to connect to the team 2023-09-21 13:01:18 +01:00
Evgeny Poberezkin
bbd778a6be core: update simplexmq (fix xftp agent) 2023-09-21 12:01:21 +01:00
Evgeny Poberezkin
027ce2c559 website: enable Japanese language 2023-09-21 09:34:36 +01:00
Evgeny Poberezkin
02864a894e website: add missing files 2023-09-21 09:28:58 +01:00
Evgeny Poberezkin
3419ce293b website: downloads page (#3088)
* website: downloads page

* link

* website: fix h2 headers issue

* website: fix sidebar issue

* website: fix language changer for downloads page in navbar

* update, add security assessment section

---------

Co-authored-by: M Sarmad Qadeer <MSarmadQadeer@gmail.com>
2023-09-21 09:28:12 +01:00
Evgeny Poberezkin
b08768ea71 5.3-beta.8: desktop 8 (#3086)
* unpin unix package

* desktop: 1.6.0, build 8

* action: file hashes (#3087)

* action: file hashes

* better output

* correct name

---------

Co-authored-by: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com>
2023-09-20 21:21:56 +01:00
Evgeny Poberezkin
6f481356f7 5.3-beta.8: ios 171, android 150 (#3085)
* ios: build 171

* android: build 150

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2023-09-20 18:21:51 +01:00
M. Sarmad Qadeer
0a2513c9e7 website: add careers page (#3039)
* website: add careers page

* website: pagename from careers to career

* website: change pagename from career to jobs

* website: add jobs string to english language strings file

* website: add job tabs

* update

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-09-20 17:54:02 +01:00
spaced4ndy
ae6996b2ee android: create contacts with group members (#3078) 2023-09-20 17:58:10 +04:00
Evgeny Poberezkin
648a9761f9 core: 5.3.0.8 2023-09-20 14:54:46 +01:00
Evgeny Poberezkin
ba71a42aa0 website: translations (#3084)
* Translated using Weblate (Dutch)

Currently translated at 100.0% (245 of 245 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (245 of 245 strings)

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

---------

Co-authored-by: M1K4 <oomikaoo@gmail.com>
2023-09-20 14:51:54 +01:00
Evgeny Poberezkin
f16388323b ui: translations (#3083)
* Translated using Weblate (Russian)

Currently translated at 100.0% (1381 of 1381 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% (1244 of 1244 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1381 of 1381 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% (1244 of 1244 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1381 of 1381 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% (1244 of 1244 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% (1381 of 1381 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% (1244 of 1244 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 99.5% (1375 of 1381 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1381 of 1381 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% (1244 of 1244 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1244 of 1244 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1381 of 1381 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (1381 of 1381 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% (1244 of 1244 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1381 of 1381 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% (1244 of 1244 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1381 of 1381 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% (1244 of 1244 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% (1381 of 1381 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% (1244 of 1244 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 99.5% (1375 of 1381 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1381 of 1381 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% (1244 of 1244 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1244 of 1244 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1381 of 1381 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1381 of 1381 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 99.2% (1235 of 1244 strings)

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

* import/export localizations

---------

Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: M1K4 <oomikaoo@gmail.com>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
Co-authored-by: elgratea <weblate@fastmail.com>
Co-authored-by: Citrus <cwuni@126.com>
2023-09-20 14:48:26 +01:00
Evgeny Poberezkin
92ac3e2a8a core: update contact and member profiles for both sides when contact is created with member (WIP) (#3081)
* core: update contact and member profiles for both sides when contact is created with member (WIP)

* send both sides, correctly process update

* refactor

* revert diff

* comments

* test

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2023-09-20 16:45:04 +04:00
Evgeny Poberezkin
52966e7e3d core: optionally encrypt SMP files (#3082)
* core: optionally encrypt SMP files

* encrypt to temp file and rename or remove encryption args if it fails

* fix file encryption error handling
2023-09-20 13:05:09 +01:00
Evgeny Poberezkin
f19fae615d ios: add Bulgarian language 2023-09-20 10:53:54 +01:00
spaced4ndy
0dab4491e2 ios: create contacts with group members (#3077)
* wip

* observed

* network status

* contact ready

* add padding

* Update apps/ios/Shared/Views/Chat/ComposeMessage/ContextInvitingContactMemberView.swift

* setContactNetworkStatus

* update text

* different context view

* update text, icon

* spinner

* reload all chats on swipe

* Revert "reload all chats on swipe"

This reverts commit 22c8372301.

* export localizations

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-09-20 09:26:16 +01:00
spaced4ndy
1928256b09 core: connect existing contacts to new members when profile matches, enable skipping direct connections in groups (#3056)
* core: test group members are assigned different LDNs in group when direct connections aren't created

* disable test output

* core: connect existing contacts to new members when profile matches (#3059)

* core: connect existing contacts to new members when profile matches

* fix migration

* progress

* xInfoProbeOk for member

* fix tests

* add test

* fix tests

* tests

* remove deleteSentProbe

* remove deleteContactProfile_

* views

* don't check connections in deleteUnusedProfile_

* Revert "don't check connections in deleteUnusedProfile_"

This reverts commit 2016a0efde.

* fix test

* core: update member merge

* update saved schema

* fix queries and tests

* rename tables to original names

* remove index, corrections

* update schema dump

---------

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

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-09-19 21:26:03 +01:00
Evgeny Poberezkin
ed3fb0b222 core: refactor incognito membership (#3079) 2023-09-19 18:50:10 +01:00
Evgeny Poberezkin
2524609a97 Merge branch 'master-ghc9' 2023-09-18 22:15:20 +01:00
Evgeny Poberezkin
0d0097326e core: update simplexmq 2023-09-18 21:55:39 +01:00
Evgeny Poberezkin
f1101b09ce core: update for GHC 9.6.2 2023-09-18 21:52:51 +01:00
Evgeny Poberezkin
904b758e79 Merge branch 'master' into master-ghc9 2023-09-18 21:46:27 +01:00
Evgeny Poberezkin
fb1e944744 docs: new branches and process (#3068)
* docs: new branches and process

* update

* update
2023-09-18 21:29:56 +01:00
Evgeny Poberezkin
3cd279bd20 website: translations (#3072)
* Translated using Weblate (Dutch)

Currently translated at 100.0% (245 of 245 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (245 of 245 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (245 of 245 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 100.0% (245 of 245 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 0.4% (1 of 245 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (245 of 245 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (245 of 245 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (245 of 245 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 100.0% (245 of 245 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 0.4% (1 of 245 strings)

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

---------

Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: Float <float.hu+@gmail.com>
Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
Co-authored-by: a4318 <dalse.077@gmail.com>
Co-authored-by: elgratea <weblate@fastmail.com>
2023-09-18 16:22:44 +01:00
Evgeny Poberezkin
2e231209d1 ui: translations (#3071)
* Translated using Weblate (German)

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 99.9% (1368 of 1369 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1232 of 1232 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1232 of 1232 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1369 of 1369 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% (1232 of 1232 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 99.1% (1358 of 1369 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 99.7% (1365 of 1369 strings)

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

* Translated using Weblate (Finnish)

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 89.8% (1107 of 1232 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1234 of 1234 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1234 of 1234 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1234 of 1234 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1234 of 1234 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1369 of 1369 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% (1234 of 1234 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1234 of 1234 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% (1369 of 1369 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 98.8% (1220 of 1234 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1371 of 1371 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% (1234 of 1234 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 99.7% (1367 of 1371 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1371 of 1371 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% (1234 of 1234 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (Finnish)

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (Finnish)

Currently translated at 100.0% (1234 of 1234 strings)

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

* Translated using Weblate (Russian)

Currently translated at 98.9% (1357 of 1371 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 99.2% (1361 of 1371 strings)

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

* Translated using Weblate (Czech)

Currently translated at 98.7% (1354 of 1371 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1371 of 1371 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% (1234 of 1234 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 99.9% (1368 of 1369 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1232 of 1232 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1232 of 1232 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1369 of 1369 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% (1232 of 1232 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 99.1% (1358 of 1369 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 99.7% (1365 of 1369 strings)

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

* Translated using Weblate (Finnish)

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 89.8% (1107 of 1232 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1234 of 1234 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1234 of 1234 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1234 of 1234 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1234 of 1234 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1369 of 1369 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% (1234 of 1234 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1234 of 1234 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% (1369 of 1369 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 98.8% (1220 of 1234 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1371 of 1371 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% (1234 of 1234 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 99.7% (1367 of 1371 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1371 of 1371 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% (1234 of 1234 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (Finnish)

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (Finnish)

Currently translated at 100.0% (1234 of 1234 strings)

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

* Translated using Weblate (Russian)

Currently translated at 98.9% (1357 of 1371 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 99.2% (1361 of 1371 strings)

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

* Translated using Weblate (Czech)

Currently translated at 98.7% (1354 of 1371 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1371 of 1371 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% (1234 of 1234 strings)

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

* android: fix translations formatting

* ios: import/export localizations

---------

Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: Float <float.hu+@gmail.com>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: a4318 <dalse.077@gmail.com>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Co-authored-by: petri <pkajander@gmail.com>
Co-authored-by: elgratea <weblate@fastmail.com>
Co-authored-by: 小连招 <onecombo666@gmail.com>
Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
Co-authored-by: M1K4 <oomikaoo@gmail.com>
Co-authored-by: pazengaz <pazengaz@porcod.io>
Co-authored-by: 橙子 <legiorange@163.com>
Co-authored-by: zenobit <zen@osowoso.xyz>
2023-09-18 16:19:02 +01:00
Evgeny Poberezkin
603e745aa1 ui: new in v5.3 (#3070)
* ios: new in v5.3

* correction, blog placeholder

* android: new in v5.3

* export localizations
2023-09-18 13:45:13 +01:00
spaced4ndy
071d6b3686 Merge pull request #3064 from simplex-chat/member-contact
core: api to create contacts with group members
2023-09-18 12:55:21 +04:00
Stanislav Dmitrenko
82e3310c54 desktop: disabled gestures (#3045)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-09-17 09:41:01 +01:00
spaced4ndy
04770fb30d core: terminal api to send message to / connect with member contact (#3065)
* core: terminal api to send message to / connect with member contact

* style

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-09-16 21:30:20 +04:00
spaced4ndy
0e5b16498a core: api to create contacts with group members (#3053)
* core: api to create contacts with group members

* implementation

* fix contact replace, more tests

* comment

* rename fields

* fix

* fix

* test group is still incognito

* fix

* replace connection instead of contact

* fix

* check version

* style, names

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-09-16 17:55:48 +04:00
Stanislav Dmitrenko
b5e4f127a4 action: fix building CLI on Windows (#3058)
* action: fix building on Windows

* fix package version on Windows

* tail
2023-09-15 17:17:55 +01:00
spaced4ndy
8178e8183e core: show rcv file error (#3054) 2023-09-13 15:28:46 +04:00
Evgeny Poberezkin
70fb10189b website: "linux" images 2023-09-12 18:35:25 +01:00
Evgeny Poberezkin
c6dbbcc84e website: add desktop app images 2023-09-12 18:01:38 +01:00
spaced4ndy
7c70577822 ios, android: add new error types (#3050) 2023-09-12 18:29:14 +04:00
spaced4ndy
a1790d6ac0 core: use JVersionRange in UI facing types (#3048) 2023-09-12 17:59:09 +04:00
spaced4ndy
01f99baaac Revert "core: direct messages in group (#2994)"
This reverts commit 5fddf64adb.
2023-09-12 17:36:47 +04:00
spaced4ndy
75f18bc5f0 docs: group member contacts rfc (#3049) 2023-09-12 17:24:41 +04:00
Stanislav Dmitrenko
4b88a2abfd multiplatform: voice playing fix (#3046) 2023-09-11 20:32:31 +01:00
spaced4ndy
5fddf64adb core: direct messages in group (#2994) 2023-09-11 18:38:57 +04:00
Evgeny Poberezkin
614b724602 core: fix version of unix package to 2.8.1.1 2023-09-11 13:17:37 +01:00
Evgeny Poberezkin
181323ce13 ios: update team ID in Apple .well-known site associations 2023-09-11 09:08:50 +01:00
Evgeny Poberezkin
28e1d5fb1b Merge branch 'master' into master-ghc9 2023-09-10 23:23:57 +01:00
Evgeny Poberezkin
b0c28e77c4 v5.3.0-beta.7: ios 170, android 149, desktop 1.5.0 (7) 2023-09-10 23:23:35 +01:00
Evgeny Poberezkin
fd5b40fd8e Merge branch 'master' into master-ghc9 2023-09-10 21:13:52 +01:00
Evgeny Poberezkin
ff657a444c core: 5.3.0.7 2023-09-10 21:12:52 +01:00
Evgeny Poberezkin
fedc31c72c Merge branch 'master' into master-ghc9 2023-09-10 21:11:35 +01:00
Alexander Bondarenko
2dff6c8859 core: do not subscribe to new connections from iOS NSE (subscribe=off flag), subscribe in app when it activates (#3016)
* Trace auto-subs flag

* Replace Bool with SubscriptionMode

* Add subscriptionMode to chat controller

* Start using subscriptionMode in event handlers

* Add need_subs to chat connections

* Add onlyNeeded to subscribeUserConnections

* Post-rebase fixes

* Pass onlyNeeded to Store functions

* Drop needs_sub for connections registered with agent

* update simplexmq, fix activate

* fix rebase, reduce diff

* fix rebase, tests

* fix rebase, executeMany, always subscribe on activate

* test

* update queries

* Update src/Simplex/Chat.hs

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

* unset connections to subscribe on start

* update simplexmq

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2023-09-10 20:40:15 +01:00
Evgeny Poberezkin
7b582b2cf9 android, desktop: update SOCKS notice 2023-09-10 20:04:50 +01:00
Evgeny Poberezkin
55954a004b android, desktop: notices about SOCKS proxy limitations (#3044) 2023-09-10 18:53:34 +01:00
Stanislav Dmitrenko
54e1e10382 multiplatform: local file encryption (#3043)
* multiplatform: file encryption

* setting

* fixed sharing

* check

* fixes, change lock icon

* update JNI bindings (crashes on desktop)

* fix JNI

* fix errors and warnings

* fix saving

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-09-10 17:05:12 +01:00
Evgeny Poberezkin
a87aaa50c7 website: add nostr.json for NIP-05 2023-09-10 11:21:40 +01:00
Evgeny Poberezkin
fdef37e8f3 Merge branch 'stable' 2023-09-10 11:01:42 +01:00
Evgeny Poberezkin
364d889056 docs: add mastodon rel=me to readme 2023-09-10 10:53:38 +01:00
Evgeny Poberezkin
923a5e2984 Merge branch 'stable' 2023-09-10 10:45:46 +01:00
Evgeny Poberezkin
272b02b686 docs: readme follow updates section, add rel=me for mastodon link 2023-09-10 10:45:30 +01:00
Evgeny Poberezkin
281d9c7f79 ios: add Finnish and Ukranian interface languages (#3040) 2023-09-08 13:21:56 +01:00
sh
45682aa7ce website: fix the apple-app-site-association (#3038) 2023-09-08 11:45:22 +01:00
Stanislav Dmitrenko
215020b1df desktop (mac): fixed linking (#2993) 2023-08-30 16:27:36 +01:00
Evgeny Poberezkin
5d580c3a1b Merge branch 'master' into master-ghc9 2023-08-30 16:27:11 +01:00
Stanislav Dmitrenko
7103524174 desktop: signing and notarizing mac build in Github action (#2986)
* desktop: signing and notarizing mac build in Github action

* changed path
2023-08-29 08:22:04 +01:00
Stanislav Dmitrenko
38ff7d173c desktop: fixed gradle (#2982)
* fix gradle

* correct cert identity

* proper file paths

* moving to secrets

* order of lines

* returned back
2023-08-27 09:16:29 +01:00
Evgeny Poberezkin
3cefe19264 Merge branch 'master' into master-ghc9 2023-08-26 21:02:07 +01:00
Evgeny Poberezkin
a2aac72dd1 Merge branch 'master' into master-ghc9 2023-08-26 18:44:34 +01:00
Evgeny Poberezkin
7ef8ae9f42 Merge branch 'master' into master-ghc9 2023-08-25 17:50:01 +01:00
Evgeny Poberezkin
9134624e2a Merge branch 'master' into master-ghc9 2023-08-25 14:13:30 +01:00
Evgeny Poberezkin
fde39e74ee Merge branch 'master' into master-ghc9 2023-08-25 11:32:37 +01:00
Moritz Angermann
761ddac55d core: use GHC 9.6.2 (#2641)
* Make it compiler with 9.6

Can be built with:

cabal build all -j --allow-newer=base --allow-newer=ghc-prim --allow-newer=template-haskell --allow-newer=bytestring --allow-newer=memory --allow-newer=cryptonite

Using ghc 9.6

It mostly runs afoul of https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0366-no-ambiguous-field-access.rst

* compile with GHC 9.6.2: dependencies, imports, code

* update GHC version in CI

* update GHC version in desktop build scripts

* update simplexmq, sha256map.nix

* update compiler

* update simplexmq, direct-sqlcipher

* remove missing files from .cabal

* building on desktop

* mac build changes

* added version back

* building libffi from source

* update simplexmq

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
2023-08-24 21:56:37 +01:00
494 changed files with 50698 additions and 20307 deletions

View File

@@ -8,6 +8,7 @@ on:
- users
tags:
- "v*"
- "!*-fdroid"
pull_request:
jobs:
@@ -78,10 +79,10 @@ jobs:
uses: actions/checkout@v3
- name: Setup Haskell
uses: haskell/actions/setup@v2
uses: haskell-actions/setup@v2
with:
ghc-version: "8.10.7"
cabal-version: "latest"
ghc-version: "9.6.3"
cabal-version: "3.10.1.0"
- name: Cache dependencies
uses: actions/cache@v3
@@ -125,7 +126,9 @@ jobs:
shell: bash
run: |
cabal build --enable-tests
echo "::set-output name=bin_path::$(cabal list-bin simplex-chat)"
path=$(cabal list-bin simplex-chat)
echo "bin_path=$path" >> $GITHUB_OUTPUT
echo "bin_hash=$(echo SHA2-512\(${{ matrix.asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT
- name: Unix upload CLI binary to release
if: startsWith(github.ref, 'refs/tags/v') && matrix.os != 'windows-latest'
@@ -136,6 +139,16 @@ jobs:
asset_name: ${{ matrix.asset_name }}
tag: ${{ github.ref }}
- name: Unix update CLI binary hash
if: startsWith(github.ref, 'refs/tags/v') && matrix.os != 'windows-latest'
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
append_body: true
body: |
${{ steps.unix_cli_build.outputs.bin_hash }}
- name: Setup Java
if: startsWith(github.ref, 'refs/tags/v')
uses: actions/setup-java@v3
@@ -152,7 +165,9 @@ jobs:
scripts/desktop/build-lib-linux.sh
cd apps/multiplatform
./gradlew packageDeb
echo "::set-output name=package_path::$(echo $PWD/release/main/deb/simplex_*_amd64.deb)"
path=$(echo $PWD/release/main/deb/simplex_*_amd64.deb)
echo "package_path=$path" >> $GITHUB_OUTPUT
echo "package_hash=$(echo SHA2-512\(${{ matrix.desktop_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT
- name: Linux make AppImage
id: linux_appimage_build
@@ -160,7 +175,9 @@ jobs:
shell: bash
run: |
scripts/desktop/make-appimage-linux.sh
echo "::set-output name=appimage_path::$(echo $PWD/apps/multiplatform/release/main/*imple*.AppImage)"
path=$(echo $PWD/apps/multiplatform/release/main/*imple*.AppImage)
echo "appimage_path=$path" >> $GITHUB_OUTPUT
echo "appimage_hash=$(echo SHA2-512\(simplex-desktop-x86_64.AppImage\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT
- name: Mac build desktop
id: mac_desktop_build
@@ -171,8 +188,10 @@ jobs:
APPLE_SIMPLEX_NOTARIZATION_APPLE_ID: ${{ secrets.APPLE_SIMPLEX_NOTARIZATION_APPLE_ID }}
APPLE_SIMPLEX_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_SIMPLEX_NOTARIZATION_PASSWORD }}
run: |
scripts/desktop/build-desktop-mac-ci.sh
echo "::set-output name=package_path::$(echo $PWD/release/main/dmg/SimpleX-*.dmg)"
scripts/ci/build-desktop-mac.sh
path=$(echo $PWD/apps/multiplatform/release/main/dmg/SimpleX-*.dmg)
echo "package_path=$path" >> $GITHUB_OUTPUT
echo "package_hash=$(echo SHA2-512\(${{ matrix.desktop_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT
- name: Linux upload desktop package to release
if: startsWith(github.ref, 'refs/tags/v') && (matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04')
@@ -183,6 +202,16 @@ jobs:
asset_name: ${{ matrix.desktop_asset_name }}
tag: ${{ github.ref }}
- name: Linux update desktop package hash
if: startsWith(github.ref, 'refs/tags/v') && (matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04')
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
append_body: true
body: |
${{ steps.linux_desktop_build.outputs.package_hash }}
- name: Linux upload AppImage to release
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'ubuntu-20.04'
uses: svenstaro/upload-release-action@v2
@@ -192,6 +221,16 @@ jobs:
asset_name: simplex-desktop-x86_64.AppImage
tag: ${{ github.ref }}
- name: Linux update AppImage hash
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'ubuntu-20.04'
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
append_body: true
body: |
${{ steps.linux_appimage_build.outputs.appimage_hash }}
- name: Mac upload desktop package to release
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'macos-latest'
uses: svenstaro/upload-release-action@v2
@@ -201,6 +240,16 @@ jobs:
asset_name: ${{ matrix.desktop_asset_name }}
tag: ${{ github.ref }}
- name: Mac update desktop package hash
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'macos-latest'
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
append_body: true
body: |
${{ steps.mac_desktop_build.outputs.package_hash }}
- name: Unix test
if: matrix.os != 'windows-latest'
timeout-minutes: 30
@@ -210,21 +259,22 @@ jobs:
# Unix /
# / Windows
# * In powershell multiline commands do not fail if individual commands fail - https://github.community/t/multiline-commands-on-windows-do-not-fail-if-individual-commands-fail/16753
# * And GitHub Actions does not support parameterizing shell in a matrix job - https://github.community/t/using-matrix-to-specify-shell-is-it-possible/17065
# rm -rf dist-newstyle/src/direct-sq* is here because of the bug in cabal's dependency which prevents second build from finishing
- name: Windows build
id: windows_build
if: matrix.os == 'windows-latest'
shell: cmd
shell: bash
run: |
rm -rf dist-newstyle/src/direct-sq*
sed -i "s/, unix /--, unix /" simplex-chat.cabal
cabal build --enable-tests
cabal list-bin simplex-chat > tmp_bin_path
set /p bin_path= < tmp_bin_path
echo ::set-output name=bin_path::%bin_path%
rm -rf dist-newstyle/src/direct-sq*
path=$(cabal list-bin simplex-chat | tail -n 1)
echo "bin_path=$path" >> $GITHUB_OUTPUT
echo "bin_hash=$(echo SHA2-512\(${{ matrix.asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT
- name: Windows upload binary to release
- name: Windows upload CLI binary to release
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest'
uses: svenstaro/upload-release-action@v2
with:
@@ -233,4 +283,60 @@ jobs:
asset_name: ${{ matrix.asset_name }}
tag: ${{ github.ref }}
- name: Windows update CLI binary hash
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest'
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
append_body: true
body: |
${{ steps.windows_build.outputs.bin_hash }}
- name: 'Setup MSYS2'
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest'
uses: msys2/setup-msys2@v2
with:
msystem: ucrt64
update: true
install: >-
git
perl
make
pacboy: >-
toolchain:p
cmake:p
- name: Windows build desktop
id: windows_desktop_build
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest'
shell: msys2 {0}
run: |
export PATH=$PATH:/c/ghcup/bin
scripts/desktop/build-lib-windows.sh
cd apps/multiplatform
./gradlew packageMsi
path=$(echo $PWD/release/main/msi/*imple*.msi | sed 's#/\([a-z]\)#\1:#' | sed 's#/#\\#g')
echo "package_path=$path" >> $GITHUB_OUTPUT
echo "package_hash=$(echo SHA2-512\(${{ matrix.desktop_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT
- name: Windows upload desktop package to release
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest'
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ${{ steps.windows_desktop_build.outputs.package_path }}
asset_name: ${{ matrix.desktop_asset_name }}
tag: ${{ github.ref }}
- name: Windows update desktop package hash
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest'
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
append_body: true
body: |
${{ steps.windows_desktop_build.outputs.package_hash }}
# Windows /

View File

@@ -9,6 +9,7 @@ on:
- website/**
- images/**
- blog/**
- docs/**
- .github/workflows/web.yml
jobs:

View File

@@ -8,12 +8,12 @@ RUN a=$(arch); curl https://downloads.haskell.org/~ghcup/$a-linux-ghcup -o /usr/
chmod +x /usr/bin/ghcup
# Install ghc
RUN ghcup install ghc 8.10.7
RUN ghcup install ghc 9.6.3
# Install cabal
RUN ghcup install cabal
RUN ghcup install cabal 3.10.1.0
# Set both as default
RUN ghcup set ghc 8.10.7 && \
ghcup set cabal
RUN ghcup set ghc 9.6.3 && \
ghcup set cabal 3.10.1.0
COPY . /project
WORKDIR /project

View File

@@ -2,7 +2,7 @@
[![GitHub downloads](https://img.shields.io/github/downloads/simplex-chat/simplex-chat/total)](https://github.com/simplex-chat/simplex-chat/releases)
[![GitHub release](https://img.shields.io/github/v/release/simplex-chat/simplex-chat)](https://github.com/simplex-chat/simplex-chat/releases)
[![Join on Reddit](https://img.shields.io/reddit/subreddit-subscribers/SimpleXChat?style=social)](https://www.reddit.com/r/SimpleXChat)
[![Follow on Mastodon](https://img.shields.io/mastodon/follow/108619463746856738?domain=https%3A%2F%2Fmastodon.social&style=social)](https://mastodon.social/@simplex)
<a rel="me" href="https://mastodon.social/@simplex">![Follow on Mastodon](https://img.shields.io/mastodon/follow/108619463746856738?domain=https%3A%2F%2Fmastodon.social&style=social)</a>
| 30/03/2023 | EN, [FR](/docs/lang/fr/README.md), [CZ](/docs/lang/cs/README.md) |
@@ -15,7 +15,7 @@
## Welcome to SimpleX Chat!
1. 📲 [Install the app](#install-the-app).
2. ↔️ [Connect to the team](#connect-to-the-team-via-the-app) and [join user groups](#join-user-groups).
2. ↔️ [Connect to the team](#connect-to-the-team), [join user groups](#join-user-groups) and [follow our updates](#follow-our-updates).
3. 🤝 [Make a private connection](#make-a-private-connection) with a friend.
4. 🔤 [Help translating SimpleX Chat](#help-translating-simplex-chat).
5. ⚡️ [Contribute](#contribute) and [help us with donations](#help-us-with-donations).
@@ -40,14 +40,22 @@
- 🚀 [TestFlight preview for iOS](https://testflight.apple.com/join/DWuT2LQu) with the new features 1-2 weeks earlier - **limited to 10,000 users**!
- 🖥 Available as a terminal (console) [app / CLI](#zap-quick-installation-of-a-terminal-app) on Linux, MacOS, Windows.
## Connect to the team via the app
## Connect to the team
You can connect to the team via the app using "chat with the developers button" available when you have no conversations in the profile, "Send questions and ideas" in the app settings or via our [SimpleX address](https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23%2F%3Fv%3D1%26dh%3DMCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion). Please connect to:
- to ask any questions
- to suggest any improvements
- to share anything relevant
We are replying the questions manually, so it is not instant it can take up to 24 hours.
If you are interested in helping us to integrate open-source language models, and in [joining our team](./docs/JOIN_TEAM.md), please get in touch.
## Join user groups
You can join the groups created by other users via the new [directory service](https://simplex.chat/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion). We are not responsible for the content shared in these groups.
**Please note**: The groups below are created for the users to be able to ask questions, make suggestions and ask questions about SimpleX Chat only.
You also can:
@@ -79,7 +87,14 @@ There are groups in other languages, that we have the apps interface translated
You can join either by opening these links in the app or by opening them in a desktop browser and scanning the QR code.
You can also join the group created by other users by searching for them via the [directory service](https://simplex.chat/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion). We are not responsible for the content shared in these groups.
## Follow our updates
We publish our updates and releases via:
- [Reddit](https://www.reddit.com/r/SimpleXChat/), [Twitter](https://twitter.com/SimpleXChat), [Lemmy](https://lemmy.ml/c/simplex), [Mastodon](https://mastodon.social/@simplex) and [Nostr](https://snort.social/p/npub1exv22uulqnmlluszc4yk92jhs2e5ajcs6mu3t00a6avzjcalj9csm7d828).
- SimpleX Chat [team profile](#connect-to-the-team).
- [blog](https://simplex.chat/blog/) and [RSS feed](https://simplex.chat/feed.rss).
- [mailing list](https://simplex.chat/#join-simplex), very rarely.
## Make a private connection
@@ -104,19 +119,22 @@ Join our translators to help SimpleX grow!
|locale|language |contributor|[Android](https://play.google.com/store/apps/details?id=chat.simplex.app) and [iOS](https://apps.apple.com/us/app/simplex-chat/id1605771084)|[website](https://simplex.chat)|Github docs|
|:----:|:-------:|:---------:|:---------:|:---------:|:---------:|
|🇬🇧 en|English | |✓|✓|✓|✓|
|ar|العربية |[jermanuts](https://github.com/jermanuts)||[![website](https://hosted.weblate.org/widgets/simplex-chat/ar/website/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/website/ar/)||
|🇧🇬 bg|Български |-|[![android app](https://hosted.weblate.org/widgets/simplex-chat/bg/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/bg/)<br>-|||
|ar|العربية |[jermanuts](https://github.com/jermanuts)|[![android app](https://hosted.weblate.org/widgets/simplex-chat/ar/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/ar/)<br>-|[![website](https://hosted.weblate.org/widgets/simplex-chat/ar/website/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/website/ar/)||
|🇧🇬 bg|Български | |[![android app](https://hosted.weblate.org/widgets/simplex-chat/bg/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/bg/)<br>[![ios app](https://hosted.weblate.org/widget/simplex-chat/ios/bg/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/bg/)|||
|🇨🇿 cs|Čeština |[zen0bit](https://github.com/zen0bit)|[![android app](https://hosted.weblate.org/widgets/simplex-chat/cs/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/cs/)<br>[![ios app](https://hosted.weblate.org/widgets/simplex-chat/cs/ios/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/cs/)|[![website](https://hosted.weblate.org/widgets/simplex-chat/cs/website/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/website/cs/)|[](https://github.com/simplex-chat/simplex-chat/tree/master/docs/lang/cs)|
|🇩🇪 de|Deutsch |[mlanp](https://github.com/mlanp)|[![android app](https://hosted.weblate.org/widgets/simplex-chat/de/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/de/)<br>[![ios app](https://hosted.weblate.org/widgets/simplex-chat/de/ios/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/de/)|[![website](https://hosted.weblate.org/widgets/simplex-chat/de/website/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/website/de/)||
|🇪🇸 es|Español |[Mateyhv](https://github.com/Mateyhv)|[![android app](https://hosted.weblate.org/widgets/simplex-chat/es/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/es/)<br>[![ios app](https://hosted.weblate.org/widgets/simplex-chat/es/ios/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/es/)|[![website](https://hosted.weblate.org/widgets/simplex-chat/es/website/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/website/es/)||
|🇫🇮 fi|Suomi | |[![android app](https://hosted.weblate.org/widgets/simplex-chat/fi/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/fi/)<br>[![ios app](https://hosted.weblate.org/widgets/simplex-chat/fi/ios/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/fi/)|[![website](https://hosted.weblate.org/widgets/simplex-chat/fi/website/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/website/fi/)||
|🇫🇷 fr|Français |[ishi_sama](https://github.com/ishi-sama)|[![android app](https://hosted.weblate.org/widgets/simplex-chat/fr/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/fr/)<br>[![ios app](https://hosted.weblate.org/widgets/simplex-chat/fr/ios/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/fr/)|[![website](https://hosted.weblate.org/widgets/simplex-chat/fr/website/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/website/fr/)|[](https://github.com/simplex-chat/simplex-chat/tree/master/docs/lang/fr)|
|🇮🇱 he|עִברִית | |[![android app](https://hosted.weblate.org/widgets/simplex-chat/he/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/he/)<br>-|||
|🇮🇹 it|Italiano |[unbranched](https://github.com/unbranched)|[![android app](https://hosted.weblate.org/widgets/simplex-chat/it/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/it/)<br>[![ios app](https://hosted.weblate.org/widgets/simplex-chat/it/ios/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/it/)|[![website](https://hosted.weblate.org/widgets/simplex-chat/it/website/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/website/it/)||
|🇯🇵 ja|Japanese ||[![android app](https://hosted.weblate.org/widgets/simplex-chat/ja/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/ja/)<br>[![ios app](https://hosted.weblate.org/widgets/simplex-chat/ja/ios/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/ja/)|||
|🇯🇵 ja|日本語 | |[![android app](https://hosted.weblate.org/widgets/simplex-chat/ja/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/ja/)<br>[![ios app](https://hosted.weblate.org/widgets/simplex-chat/ja/ios/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/ja/)|[![website](https://hosted.weblate.org/widgets/simplex-chat/ja/website/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/website/ja/)||
|🇳🇱 nl|Nederlands|[mika-nl](https://github.com/mika-nl)|[![android app](https://hosted.weblate.org/widgets/simplex-chat/nl/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/nl/)<br>[![ios app](https://hosted.weblate.org/widgets/simplex-chat/nl/ios/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/nl/)|[![website](https://hosted.weblate.org/widgets/simplex-chat/nl/website/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/website/nl/)||
|🇵🇱 pl|Polski |[BxOxSxS](https://github.com/BxOxSxS)|[![android app](https://hosted.weblate.org/widgets/simplex-chat/pl/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/pl/)<br>[![ios app](https://hosted.weblate.org/widgets/simplex-chat/pl/ios/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/pl/)|||
|🇧🇷 pt-BR|Português||[![android app](https://hosted.weblate.org/widgets/simplex-chat/pt_BR/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/)<br>-|[![website](https://hosted.weblate.org/widgets/simplex-chat/pt_BR/website/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/website/pt_BR/)||
|🇷🇺 ru|Русский ||[![android app](https://hosted.weblate.org/widgets/simplex-chat/ru/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/ru/)<br>[![ios app](https://hosted.weblate.org/widgets/simplex-chat/ru/ios/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/ru/)|||
|🇹🇭 th|ภาษาไทย |[titapa-punpun](https://github.com/titapa-punpun)|[![android app](https://hosted.weblate.org/widgets/simplex-chat/th/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/th/)<br>[![ios app](https://hosted.weblate.org/widgets/simplex-chat/th/ios/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/th/)|||
|🇺🇦 uk|Українська| |[![android app](https://hosted.weblate.org/widgets/simplex-chat/uk/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/uk/)<br>[![ios app](https://hosted.weblate.org/widgets/simplex-chat/uk/ios/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/uk/)|[![website](https://hosted.weblate.org/widgets/simplex-chat/uk/website/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/website/uk/)||
|🇨🇳 zh-CHS|简体中文|[sith-on-mars](https://github.com/sith-on-mars)<br><br>[Float-hu](https://github.com/Float-hu)|[![android app](https://hosted.weblate.org/widgets/simplex-chat/zh_Hans/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/)<br>[![ios app](https://hosted.weblate.org/widgets/simplex-chat/zh_Hans/ios/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hans/)<br>&nbsp;|<br><br>[![website](https://hosted.weblate.org/widgets/simplex-chat/zh_Hans/website/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/)||
Languages in progress: Arabic, Japanese, Korean, Portuguese and [others](https://hosted.weblate.org/projects/simplex-chat/#languages). We will be adding more languages as some of the already added are completed please suggest new languages, review the [translation guide](./docs/TRANSLATIONS.md) and get in touch with us!
@@ -212,24 +230,18 @@ You can use SimpleX with your own servers and still communicate with people usin
## News and updates
Recent updates:
Recent and important updates:
[July 22, 2023. SimpleX Chat: v5.2 released with message delivery receipts](./blog/20230722-simplex-chat-v5-2-message-delivery-receipts.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).
[May 23, 2023. SimpleX Chat: v5.1 released with message reactions and self-destruct passcode](./blog/20230523-simplex-chat-v5-1-message-reactions-self-destruct-passcode.md).
[Apr 22, 2023. SimpleX Chat: vision and funding, v5.0 released with videos and files up to 1gb](./blog/20230422-simplex-chat-vision-funding-v5-videos-files-passcode.md).
[Mar 28, 2023. v4.6 released - with Android 8+ and ARMv7a support, hidden profiles, community moderation, improved audio/video calls and reduced battery usage](./blog/20230328-simplex-chat-v4-6-hidden-profiles.md).
[Mar 1, 2023. SimpleX File Transfer Protocol send large files efficiently, privately and securely, soon to be integrated into SimpleX Chat apps.](./blog/20230301-simplex-file-transfer-protocol.md).
[Feb 4, 2023. v4.5 released - with multiple user profiles, message draft, transport isolation and Italian interface](./blog/20230204-simplex-chat-v4-5-user-chat-profiles.md).
[Jan 3, 2023. v4.4 released - with disappearing messages, "live" messages, connection security verifications, GIFs and stickers and with French interface language](./blog/20230103-simplex-chat-v4.4-disappearing-messages.md).
[Dec 6, 2022. November reviews and v4.3 released - with instant voice messages, irreversible deletion of sent messages and improved server configuration](./blog/20221206-simplex-chat-v4.3-voice-messages.md).
[Nov 8, 2022. Security audit by Trail of Bits, the new website and v4.2 released](./blog/20221108-simplex-chat-v4.2-security-audit-new-website.md).
[Sep 28, 2022. v4.0: encrypted local chat database and many other changes](./blog/20220928-simplex-chat-v4-encrypted-database.md).
@@ -275,18 +287,20 @@ What is already implemented:
3. [Double ratchet](./docs/GLOSSARY.md#double-ratchet-algorithm) end-to-end encryption in each conversation between two users (or group members). This is the same algorithm that is used in Signal and many other messaging apps; it provides OTR messaging with [forward secrecy](./docs/GLOSSARY.md#forward-secrecy) (each message is encrypted by its own ephemeral key) and [break-in recovery](./docs/GLOSSARY.md#post-compromise-security) (the keys are frequently re-negotiated as part of the message exchange). Two pairs of Curve448 keys are used for the initial [key agreement](./docs/GLOSSARY.md#key-agreement-protocol), initiating party passes these keys via the connection link, accepting side - in the header of the confirmation message.
4. Additional layer of encryption using NaCL cryptobox for the messages delivered from the server to the recipient. This layer avoids having any ciphertext in common between sent and received traffic of the server inside TLS (and there are no identifiers in common as well).
5. Several levels of [content padding](./docs/GLOSSARY.md#message-padding) to frustrate message size attacks.
6. Starting from v2 of SMP protocol (the current version is v4) all message metadata, including the time when the message was received by the server (rounded to a second) is sent to the recipients inside an encrypted envelope, so even if TLS is compromised it cannot be observed.
6. All message metadata, including the time when the message was received by the server (rounded to a second) is sent to the recipients inside an encrypted envelope, so even if TLS is compromised it cannot be observed.
7. Only TLS 1.2/1.3 are allowed for client-server connections, limited to cryptographic algorithms: CHACHA20POLY1305_SHA256, Ed25519/Ed448, Curve25519/Curve448.
8. To protect against replay attacks SimpleX servers require [tlsunique channel binding](https://www.rfc-editor.org/rfc/rfc5929.html) as session ID in each client command signed with per-queue ephemeral key.
9. To protect your IP address all SimpleX Chat clients support accessing messaging servers via Tor - see [v3.1 release announcement](./blog/20220808-simplex-chat-v3.1-chat-groups.md) for more details.
10. Local database encryption with passphrase - your contacts, groups and all sent and received messages are stored encrypted. If you used SimpleX Chat before v4.0 you need to enable the encryption via the app settings.
11. Transport isolation - different TCP connections and Tor circuits are used for traffic of different user profiles, optionally - for different contacts and group member connections.
12. Manual messaging queue rotations to move conversation to another SMP relay.
13. Sending end-to-end encrypted files using [XFTP protocol](https://simplex.chat/blog/20230301-simplex-file-transfer-protocol.html).
14. Local files encryption, except videos (to be added later).
We plan to add:
1. Local files encryption. Currently the images and files you send and receive are stored in the app unencrypted, you can delete them via `Settings / Database passphrase & export`. This is currently in progress.
2. Senders' SMP relays and recipients' XFTP relays to reduce traffic and conceal IP addresses from the relays chosen, and potentially controlled, by another party.
1. Senders' SMP relays and recipients' XFTP relays to reduce traffic and conceal IP addresses from the relays chosen, and potentially controlled, by another party.
2. Post-quantum resistant key exchange in double ratchet protocol.
3. Automatic message queue rotation and redundancy. Currently the queues created between two users are used until the queue is manually changed by the user or contact is deleted. We are planning to add automatic queue rotation to make these identifiers temporary and rotate based on some schedule TBC (e.g., every X messages, or every X hours/days).
4. Message "mixing" - adding latency to message delivery, to protect against traffic correlation by message time.
5. Reproducible builds this is the limitation of the development stack, but we will be investing into solving this problem. Users can still build all applications and services from the source code.
@@ -350,22 +364,26 @@ Please also join [#simplex-devs](https://simplex.chat/contact#/?v=1-2&smp=smp%3A
- ✅ Message editing history
- ✅ Reduced battery and traffic usage in large groups.
- ✅ Message delivery confirmation (with sender opt-out per contact).
- 🏗 Desktop client.
- Desktop client.
- ✅ Encryption of local files stored in the app.
- 🏗 Using mobile profiles from the desktop app.
- 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.
- Local app files encryption.
- Improved navigation and search in the conversation (expand and scroll to quoted message, scroll to search results, etc.).
- Large groups, communities and public channels.
- Feeds/broadcasts.
- Ephemeral/disappearing/OTR conversations with the existing contacts.
- Privately share your location.
- Web widgets for custom interactivity in the chats.
- Programmable chat automations / rules (automatic replies/forward/deletion/sending, reminders, etc.).
- Supporting the same profile on multiple devices.
- Privacy-preserving identity server for optional DNS-based contact/group addresses to simplify connection and discovery, but not used to deliver messages:
- keep all your contacts and groups even if you lose the domain.
- the server doesn't have information about your contacts and groups.
- Message delivery relay for senders (to conceal IP address from the recipients' servers and to reduce the traffic).
- High capacity multi-node SMP relays.
## Disclaimers

View File

@@ -55,7 +55,6 @@ class AppDelegate: NSObject, UIApplicationDelegate {
didReceiveRemoteNotification userInfo: [AnyHashable : Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
logger.debug("AppDelegate: didReceiveRemoteNotification")
print("*** userInfo", userInfo)
let m = ChatModel.shared
if let ntfData = userInfo["notificationData"] as? [AnyHashable : Any],
m.notificationMode != .off {
@@ -121,6 +120,10 @@ class AppDelegate: NSObject, UIApplicationDelegate {
BGManager.shared.receiveMessages(complete)
}
static func keepScreenOn(_ on: Bool) {
UIApplication.shared.isIdleTimerDisabled = on
}
}
class SceneDelegate: NSObject, ObservableObject, UIWindowSceneDelegate {

View File

@@ -31,11 +31,11 @@ struct ContentView: View {
@State private var chatListActionSheet: ChatListActionSheet? = nil
private enum ChatListActionSheet: Identifiable {
case connectViaUrl(action: ConnReqType, link: String)
case planAndConnectSheet(sheet: PlanAndConnectActionSheet)
var id: String {
switch self {
case .connectViaUrl: return "connectViaUrl \(link)"
case let .planAndConnectSheet(sheet): return sheet.id
}
}
}
@@ -93,7 +93,7 @@ struct ContentView: View {
mainView()
.actionSheet(item: $chatListActionSheet) { sheet in
switch sheet {
case let .connectViaUrl(action, link): return connectViaUrlSheet(action, link)
case let .planAndConnectSheet(sheet): return planAndConnectActionSheet(sheet, dismiss: false)
}
}
} else {
@@ -285,36 +285,30 @@ struct ContentView: View {
}
func connectViaUrl() {
let m = ChatModel.shared
if let url = m.appOpenUrl {
m.appOpenUrl = nil
var path = url.path
logger.debug("ContentView.connectViaUrl path: \(path)")
if (path == "/contact" || path == "/invitation") {
path.removeFirst()
let action: ConnReqType = path == "contact" ? .contact : .invitation
let link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)")
chatListActionSheet = .connectViaUrl(action: action, link: link)
} else {
AlertManager.shared.showAlert(Alert(title: Text("Error: URL is invalid")))
dismissAllSheets() {
let m = ChatModel.shared
if let url = m.appOpenUrl {
m.appOpenUrl = nil
var path = url.path
if (path == "/contact" || path == "/invitation") {
path.removeFirst()
let link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)")
planAndConnect(
link,
showAlert: showPlanAndConnectAlert,
showActionSheet: { chatListActionSheet = .planAndConnectSheet(sheet: $0) },
dismiss: false,
incognito: nil
)
} else {
AlertManager.shared.showAlert(Alert(title: Text("Error: URL is invalid")))
}
}
}
}
private func connectViaUrlSheet(_ action: ConnReqType, _ link: String) -> ActionSheet {
let title: LocalizedStringKey
switch action {
case .contact: title = "Connect via contact link"
case .invitation: title = "Connect via one-time link"
}
return ActionSheet(
title: Text(title),
buttons: [
.default(Text("Use current profile")) { connectViaLink(link, incognito: false) },
.default(Text("Use new incognito profile")) { connectViaLink(link, incognito: true) },
.cancel()
]
)
private func showPlanAndConnectAlert(_ alert: PlanAndConnectAlert) {
AlertManager.shared.showAlert(planAndConnectAlert(alert, dismiss: false))
}
}

View File

@@ -46,6 +46,7 @@ class AudioRecorder {
audioRecorder?.record(forDuration: MAX_VOICE_MESSAGE_LENGTH)
await MainActor.run {
AppDelegate.keepScreenOn(true)
recordingTimer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { timer in
guard let time = self.audioRecorder?.currentTime else { return }
self.onTimer?(time)
@@ -57,6 +58,10 @@ class AudioRecorder {
}
return nil
} catch let error {
await MainActor.run {
AppDelegate.keepScreenOn(false)
}
try? av.setCategory(AVAudioSession.Category.soloAmbient)
logger.error("AudioRecorder startAudioRecording error \(error.localizedDescription)")
return .error(error.localizedDescription)
}
@@ -71,6 +76,8 @@ class AudioRecorder {
timer.invalidate()
}
recordingTimer = nil
AppDelegate.keepScreenOn(false)
try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.soloAmbient)
}
private func checkPermission() async -> Bool {
@@ -121,14 +128,19 @@ class AudioPlayer: NSObject, AVAudioPlayerDelegate {
playbackTimer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { timer in
if self.audioPlayer?.isPlaying ?? false {
AppDelegate.keepScreenOn(true)
guard let time = self.audioPlayer?.currentTime else { return }
self.onTimer?(time)
AudioPlayer.changeAudioSession(true)
} else {
AudioPlayer.changeAudioSession(false)
}
}
}
func pause() {
audioPlayer?.pause()
AppDelegate.keepScreenOn(false)
}
func play() {
@@ -149,6 +161,8 @@ class AudioPlayer: NSObject, AVAudioPlayerDelegate {
func stop() {
if let player = audioPlayer {
player.stop()
AppDelegate.keepScreenOn(false)
AudioPlayer.changeAudioSession(false)
}
audioPlayer = nil
if let timer = playbackTimer {
@@ -157,6 +171,24 @@ class AudioPlayer: NSObject, AVAudioPlayerDelegate {
playbackTimer = nil
}
static func changeAudioSession(_ playback: Bool) {
// When there is a audio recording, setting any other category will disable sound
if AVAudioSession.sharedInstance().category == .playAndRecord {
return
}
if playback {
if AVAudioSession.sharedInstance().category != .playback {
logger.log("AudioSession: playback")
try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, options: .duckOthers)
}
} else {
if AVAudioSession.sharedInstance().category != .soloAmbient {
logger.log("AudioSession: soloAmbient")
try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.soloAmbient)
}
}
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
stop()
self.onFinishPlayback?()

View File

@@ -62,8 +62,9 @@ final class ChatModel: ObservableObject {
// current chat
@Published var chatId: String?
@Published var reversedChatItems: [ChatItem] = []
var chatItemStatuses: Dictionary<Int64, CIStatus> = [:]
@Published var chatToTop: String?
@Published var groupMembers: [GroupMember] = []
@Published var groupMembers: [GMember] = []
// items in the terminal view
@Published var showingTerminal = false
@Published var terminalItems: [TerminalItem] = []
@@ -152,6 +153,20 @@ final class ChatModel: ObservableObject {
}
}
func getGroupChat(_ groupId: Int64) -> Chat? {
chats.first { chat in
if case let .group(groupInfo) = chat.chatInfo {
return groupInfo.groupId == groupId
} else {
return false
}
}
}
func getGroupMember(_ groupMemberId: Int64) -> GMember? {
groupMembers.first { $0.groupMemberId == groupMemberId }
}
private func getChatIndex(_ id: String) -> Int? {
chats.firstIndex(where: { $0.id == id })
}
@@ -165,6 +180,7 @@ final class ChatModel: ObservableObject {
func updateChatInfo(_ cInfo: ChatInfo) {
if let i = getChatIndex(cInfo.id) {
chats[i].chatInfo = cInfo
chats[i].created = Date.now
}
}
@@ -178,7 +194,7 @@ final class ChatModel: ObservableObject {
func updateContactConnectionStats(_ contact: Contact, _ connectionStats: ConnectionStats) {
var updatedConn = contact.activeConn
updatedConn.connectionStats = connectionStats
updatedConn?.connectionStats = connectionStats
var updatedContact = contact
updatedContact.activeConn = updatedConn
updateContact(updatedContact)
@@ -296,7 +312,11 @@ final class ChatModel: ObservableObject {
return false
} else {
withAnimation(itemAnimation()) {
reversedChatItems.insert(cItem, at: hasLiveDummy ? 1 : 0)
var ci = cItem
if let status = chatItemStatuses.removeValue(forKey: ci.id), case .sndNew = ci.meta.itemStatus {
ci.meta.itemStatus = status
}
reversedChatItems.insert(ci, at: hasLiveDummy ? 1 : 0)
}
return true
}
@@ -309,26 +329,22 @@ final class ChatModel: ObservableObject {
}
}
func updateChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) {
func updateChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem, status: CIStatus? = nil) {
if chatId == cInfo.id, let i = getChatItemIndex(cItem) {
withAnimation {
_updateChatItem(at: i, with: cItem)
}
} else if let status = status {
chatItemStatuses.updateValue(status, forKey: cItem.id)
}
}
private func _updateChatItem(at i: Int, with cItem: ChatItem) {
let ci = reversedChatItems[i]
reversedChatItems[i] = cItem
reversedChatItems[i].viewTimestamp = .now
// on some occasions the confirmation of message being accepted by the server (tick)
// arrives earlier than the response from API, and item remains without tick
if case .sndNew = cItem.meta.itemStatus {
reversedChatItems[i].meta.itemStatus = ci.meta.itemStatus
}
}
private func getChatItemIndex(_ cItem: ChatItem) -> Int? {
func getChatItemIndex(_ cItem: ChatItem) -> Int? {
reversedChatItems.firstIndex(where: { $0.id == cItem.id })
}
@@ -464,6 +480,7 @@ final class ChatModel: ObservableObject {
}
// clear current chat
if chatId == cInfo.id {
chatItemStatuses = [:]
reversedChatItems = []
}
}
@@ -516,27 +533,62 @@ final class ChatModel: ObservableObject {
users.filter { !$0.user.activeUser }.reduce(0, { unread, next -> Int in unread + next.unreadCount })
}
func getConnectedMemberNames(_ ci: ChatItem) -> [String] {
guard var i = getChatItemIndex(ci) else { return [] }
// this function analyses "connected" events and assumes that each member will be there only once
func getConnectedMemberNames(_ chatItem: ChatItem) -> (Int, [String]) {
var count = 0
var ns: [String] = []
while i < reversedChatItems.count, let m = reversedChatItems[i].memberConnected {
ns.append(m.displayName)
i += 1
if let ciCategory = chatItem.mergeCategory,
var i = getChatItemIndex(chatItem) {
while i < reversedChatItems.count {
let ci = reversedChatItems[i]
if ci.mergeCategory != ciCategory { break }
if let m = ci.memberConnected {
ns.append(m.displayName)
}
count += 1
i += 1
}
}
return ns
return (count, ns)
}
func getChatItemNeighbors(_ ci: ChatItem) -> (ChatItem?, ChatItem?) {
if let i = getChatItemIndex(ci) {
return (
i + 1 < reversedChatItems.count ? reversedChatItems[i + 1] : nil,
i - 1 >= 0 ? reversedChatItems[i - 1] : nil
)
// returns the index of the passed item and the next item (it has smaller index)
func getNextChatItem(_ ci: ChatItem) -> (Int?, ChatItem?) {
if let i = getChatItemIndex(ci) {
(i, i > 0 ? reversedChatItems[i - 1] : nil)
} else {
return (nil, nil)
(nil, nil)
}
}
// returns the index of the first item in the same merged group (the first hidden item)
// and the previous visible item with another merge category
func getPrevShownChatItem(_ ciIndex: Int?, _ ciCategory: CIMergeCategory?) -> (Int?, ChatItem?) {
guard var i = ciIndex else { return (nil, nil) }
let fst = reversedChatItems.count - 1
while i < fst {
i = i + 1
let ci = reversedChatItems[i]
if ciCategory == nil || ciCategory != ci.mergeCategory {
return (i - 1, ci)
}
}
return (i, nil)
}
// returns the previous member in the same merge group and the count of members in this group
func getPrevHiddenMember(_ member: GroupMember, _ range: ClosedRange<Int>) -> (GroupMember?, Int) {
var prevMember: GroupMember? = nil
var memberIds: Set<Int64> = []
for i in range {
if case let .groupRcv(m) = reversedChatItems[i].chatDir {
if prevMember == nil && m.groupMemberId != member.groupMemberId { prevMember = m }
memberIds.insert(m.groupMemberId)
}
}
return (prevMember, memberIds.count)
}
func popChat(_ id: String) {
if let i = getChatIndex(id) {
popChat_(i)
@@ -571,13 +623,14 @@ final class ChatModel: ObservableObject {
}
// update current chat
if chatId == groupInfo.id {
if let i = groupMembers.firstIndex(where: { $0.id == member.id }) {
if let i = groupMembers.firstIndex(where: { $0.groupMemberId == member.groupMemberId }) {
withAnimation(.default) {
self.groupMembers[i] = member
self.groupMembers[i].wrapped = member
self.groupMembers[i].created = Date.now
}
return false
} else {
withAnimation { groupMembers.append(member) }
withAnimation { groupMembers.append(GMember(member)) }
return true
}
} else {
@@ -586,11 +639,10 @@ final class ChatModel: ObservableObject {
}
func updateGroupMemberConnectionStats(_ groupInfo: GroupInfo, _ member: GroupMember, _ connectionStats: ConnectionStats) {
if let conn = member.activeConn {
var updatedConn = conn
updatedConn.connectionStats = connectionStats
if var conn = member.activeConn {
conn.connectionStats = connectionStats
var updatedMember = member
updatedMember.activeConn = updatedConn
updatedMember.activeConn = conn
_ = upsertGroupMember(groupInfo, updatedMember)
}
}
@@ -619,11 +671,17 @@ final class ChatModel: ObservableObject {
}
func setContactNetworkStatus(_ contact: Contact, _ status: NetworkStatus) {
networkStatuses[contact.activeConn.agentConnId] = status
if let conn = contact.activeConn {
networkStatuses[conn.agentConnId] = status
}
}
func contactNetworkStatus(_ contact: Contact) -> NetworkStatus {
networkStatuses[contact.activeConn.agentConnId] ?? .unknown
if let conn = contact.activeConn {
networkStatuses[conn.agentConnId] ?? .unknown
} else {
.unknown
}
}
}
@@ -689,40 +747,18 @@ final class Chat: ObservableObject, Identifiable {
public static var sampleData: Chat = Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: [])
}
enum NetworkStatus: Decodable, Equatable {
case unknown
case connected
case disconnected
case error(String)
final class GMember: ObservableObject, Identifiable {
@Published var wrapped: GroupMember
var created = Date.now
var statusString: LocalizedStringKey {
get {
switch self {
case .connected: return "connected"
case .error: return "error"
default: return "connecting"
}
}
init(_ member: GroupMember) {
self.wrapped = member
}
var statusExplanation: LocalizedStringKey {
get {
switch self {
case .connected: return "You are connected to the server used to receive messages from this contact."
case let .error(err): return "Trying to connect to the server used to receive messages from this contact (error: \(err))."
default: return "Trying to connect to the server used to receive messages from this contact."
}
}
}
var imageName: String {
get {
switch self {
case .unknown: return "circle.dotted"
case .connected: return "circle.fill"
case .disconnected: return "ellipsis.circle.fill"
case .error: return "exclamationmark.circle.fill"
}
}
}
var id: String { wrapped.id }
var groupId: Int64 { wrapped.groupId }
var groupMemberId: Int64 { wrapped.groupMemberId }
var displayName: String { wrapped.displayName }
var viewId: String { get { "\(wrapped.id) \(created.timeIntervalSince1970)" } }
static let sampleData = GMember(GroupMember.sampleData)
}

View File

@@ -15,6 +15,12 @@ import SimpleXChat
private var chatController: chat_ctrl?
// currentChatVersion in core
public let CURRENT_CHAT_VERSION: Int = 2
// version range that supports establishing direct connection with a group member (xGrpDirectInvVRange in core)
public let CREATE_MEMBER_CONTACT_VRANGE = VersionRange(minVersion: 2, maxVersion: CURRENT_CHAT_VERSION)
enum TerminalItem: Identifiable {
case cmd(Date, ChatCommand)
case resp(Date, ChatResponse)
@@ -251,6 +257,12 @@ func setXFTPConfig(_ cfg: XFTPFileConfig?) throws {
throw r
}
func apiSetEncryptLocalFiles(_ enable: Bool) throws {
let r = chatSendCmdSync(.apiSetEncryptLocalFiles(enable: enable))
if case .cmdOk = r { return }
throw r
}
func apiExportArchive(config: ArchiveConfig) async throws {
try await sendCommandOkResp(.apiExportArchive(config: config))
}
@@ -300,6 +312,7 @@ func loadChat(chat: Chat, search: String = "") {
do {
let cInfo = chat.chatInfo
let m = ChatModel.shared
m.chatItemStatuses = [:]
m.reversedChatItems = []
let chat = try apiGetChat(type: cInfo.chatType, id: cInfo.apiId, search: search)
m.updateChatInfo(chat.chatInfo)
@@ -489,6 +502,10 @@ func apiSetChatSettings(type: ChatType, id: Int64, chatSettings: ChatSettings) a
try await sendCommandOkResp(.apiSetChatSettings(type: type, id: id, chatSettings: chatSettings))
}
func apiSetMemberSettings(_ groupId: Int64, _ groupMemberId: Int64, _ memberSettings: GroupMemberSettings) async throws {
try await sendCommandOkResp(.apiSetMemberSettings(groupId: groupId, groupMemberId: groupMemberId, memberSettings: memberSettings))
}
func apiContactInfo(_ contactId: Int64) async throws -> (ConnectionStats?, Profile?) {
let r = await chatSendCmd(.apiContactInfo(contactId: contactId))
if case let .contactInfo(_, _, connStats, customUserProfile) = r { return (connStats, customUserProfile) }
@@ -580,6 +597,14 @@ func apiSetConnectionIncognito(connId: Int64, incognito: Bool) async throws -> P
throw r
}
func apiConnectPlan(connReq: String) async throws -> ConnectionPlan {
let userId = try currentUserId("apiConnectPlan")
let r = await chatSendCmd(.apiConnectPlan(userId: userId, connReq: connReq))
if case let .connectionPlan(_, connectionPlan) = r { return connectionPlan }
logger.error("apiConnectPlan error: \(responseError(r))")
throw r
}
func apiConnect(incognito: Bool, connReq: String) async -> ConnReqType? {
let (connReqType, alert) = await apiConnect_(incognito: incognito, connReq: connReq)
if let alert = alert {
@@ -604,10 +629,7 @@ func apiConnect_(incognito: Bool, connReq: String) async -> (ConnReqType?, Alert
if let c = m.getContactChat(contact.contactId) {
await MainActor.run { m.chatId = c.id }
}
let alert = mkAlert(
title: "Contact already exists",
message: "You are already connected to \(contact.displayName)."
)
let alert = contactAlreadyExistsAlert(contact)
return (nil, alert)
case .chatCmdError(_, .error(.invalidConnReq)):
let alert = mkAlert(
@@ -635,6 +657,13 @@ func apiConnect_(incognito: Bool, connReq: String) async -> (ConnReqType?, Alert
return (nil, alert)
}
func contactAlreadyExistsAlert(_ contact: Contact) -> Alert {
mkAlert(
title: "Contact already exists",
message: "You are already connected to \(contact.displayName)."
)
}
private func connectionErrorAlert(_ r: ChatResponse) -> Alert {
if let networkErrorAlert = networkErrorAlert(r) {
return networkErrorAlert
@@ -646,18 +675,30 @@ private func connectionErrorAlert(_ r: ChatResponse) -> Alert {
}
}
func apiDeleteChat(type: ChatType, id: Int64) async throws {
let r = await chatSendCmd(.apiDeleteChat(type: type, id: id), bgTask: false)
func apiConnectContactViaAddress(incognito: Bool, contactId: Int64) async -> (Contact?, Alert?) {
guard let userId = ChatModel.shared.currentUser?.userId else {
logger.error("apiConnectContactViaAddress: no current user")
return (nil, nil)
}
let r = await chatSendCmd(.apiConnectContactViaAddress(userId: userId, incognito: incognito, contactId: contactId))
if case let .sentInvitationToContact(_, contact, _) = r { return (contact, nil) }
logger.error("apiConnectContactViaAddress error: \(responseError(r))")
let alert = connectionErrorAlert(r)
return (nil, alert)
}
func apiDeleteChat(type: ChatType, id: Int64, notify: Bool? = nil) async throws {
let r = await chatSendCmd(.apiDeleteChat(type: type, id: id, notify: notify), bgTask: false)
if case .direct = type, case .contactDeleted = r { return }
if case .contactConnection = type, case .contactConnectionDeleted = r { return }
if case .group = type, case .groupDeletedUser = r { return }
throw r
}
func deleteChat(_ chat: Chat) async {
func deleteChat(_ chat: Chat, notify: Bool? = nil) async {
do {
let cInfo = chat.chatInfo
try await apiDeleteChat(type: cInfo.chatType, id: cInfo.apiId)
try await apiDeleteChat(type: cInfo.chatType, id: cInfo.apiId, notify: notify)
DispatchQueue.main.async { ChatModel.shared.removeChat(cInfo.id) }
} catch let error {
logger.error("deleteChat apiDeleteChat error: \(responseError(error))")
@@ -695,8 +736,9 @@ func apiUpdateProfile(profile: Profile) async throws -> (Profile, [Contact])? {
let userId = try currentUserId("apiUpdateProfile")
let r = await chatSendCmd(.apiUpdateProfile(userId: userId, profile: profile))
switch r {
case .userProfileNoChange: return nil
case .userProfileNoChange: return (profile, [])
case let .userProfileUpdated(_, _, toProfile, updateSummary): return (toProfile, updateSummary.changedContacts)
case .chatCmdError(_, .errorStore(.duplicateName)): return nil;
default: throw r
}
}
@@ -938,6 +980,12 @@ func apiCallStatus(_ contact: Contact, _ status: String) async throws {
}
}
func apiGetNetworkStatuses() throws -> [ConnNetworkStatus] {
let r = chatSendCmdSync(.apiGetNetworkStatuses)
if case let .networkStatuses(_, statuses) = r { return statuses }
throw r
}
func markChatRead(_ chat: Chat, aboveItem: ChatItem? = nil) async {
do {
if chat.chatStats.unreadCount > 0 {
@@ -985,9 +1033,9 @@ private func sendCommandOkResp(_ cmd: ChatCommand) async throws {
throw r
}
func apiNewGroup(_ p: GroupProfile) throws -> GroupInfo {
func apiNewGroup(incognito: Bool, groupProfile: GroupProfile) throws -> GroupInfo {
let userId = try currentUserId("apiNewGroup")
let r = chatSendCmdSync(.apiNewGroup(userId: userId, groupProfile: p))
let r = chatSendCmdSync(.apiNewGroup(userId: userId, incognito: incognito, groupProfile: groupProfile))
if case let .groupCreated(_, groupInfo) = r { return groupInfo }
throw r
}
@@ -1047,8 +1095,8 @@ func apiListMembers(_ groupId: Int64) async -> [GroupMember] {
return []
}
func filterMembersToAdd(_ ms: [GroupMember]) -> [Contact] {
let memberContactIds = ms.compactMap{ m in m.memberCurrent ? m.memberContactId : nil }
func filterMembersToAdd(_ ms: [GMember]) -> [Contact] {
let memberContactIds = ms.compactMap{ m in m.wrapped.memberCurrent ? m.wrapped.memberContactId : nil }
return ChatModel.shared.chats
.compactMap{ $0.chatInfo.contact }
.filter{ !memberContactIds.contains($0.apiId) }
@@ -1090,6 +1138,18 @@ func apiGetGroupLink(_ groupId: Int64) throws -> (String, GroupMemberRole)? {
}
}
func apiCreateMemberContact(_ groupId: Int64, _ groupMemberId: Int64) async throws -> Contact {
let r = await chatSendCmd(.apiCreateMemberContact(groupId: groupId, groupMemberId: groupMemberId))
if case let .newMemberContact(_, contact, _, _) = r { return contact }
throw r
}
func apiSendMemberContactInvitation(_ contactId: Int64, _ msg: MsgContent) async throws -> Contact {
let r = await chatSendCmd(.apiSendMemberContactInvitation(contactId: contactId, msg: msg), bgDelay: msgDelay)
if case let .newMemberContactSentInv(_, contact, _, _) = r { return contact }
throw r
}
func apiGetVersion() throws -> CoreVersionInfo {
let r = chatSendCmdSync(.showVersion)
if case let .versionInfo(info, _, _) = r { return info }
@@ -1115,6 +1175,7 @@ func initializeChat(start: Bool, dbKey: String? = nil, refreshInvitations: Bool
try apiSetTempFolder(tempFolder: getTempFilesDirectory().path)
try apiSetFilesFolder(filesFolder: getAppFilesDirectory().path)
try setXFTPConfig(getXFTPCfg())
try apiSetEncryptLocalFiles(privacyEncryptLocalFilesGroupDefault.get())
m.chatInitialized = true
m.currentUser = try apiGetActiveUser()
if m.currentUser == nil {
@@ -1267,12 +1328,20 @@ func processReceivedMsg(_ res: ChatResponse) async {
m.removeChat(connection.id)
}
}
case let .contactDeletedByContact(user, contact):
if active(user) && contact.directOrUsed {
await MainActor.run {
m.updateContact(contact)
}
}
case let .contactConnected(user, contact, _):
if active(user) && contact.directOrUsed {
await MainActor.run {
m.updateContact(contact)
m.dismissConnReqView(contact.activeConn.id)
m.removeChat(contact.activeConn.id)
if let conn = contact.activeConn {
m.dismissConnReqView(conn.id)
m.removeChat(conn.id)
}
}
}
if contact.directOrUsed {
@@ -1285,8 +1354,10 @@ func processReceivedMsg(_ res: ChatResponse) async {
if active(user) && contact.directOrUsed {
await MainActor.run {
m.updateContact(contact)
m.dismissConnReqView(contact.activeConn.id)
m.removeChat(contact.activeConn.id)
if let conn = contact.activeConn {
m.dismissConnReqView(conn.id)
m.removeChat(conn.id)
}
}
}
case let .receivedContactRequest(user, contactRequest):
@@ -1311,6 +1382,12 @@ func processReceivedMsg(_ res: ChatResponse) async {
m.updateChatInfo(cInfo)
}
}
case let .groupMemberUpdated(user, groupInfo, _, toMember):
if active(user) {
await MainActor.run {
_ = m.upsertGroupMember(groupInfo, toMember)
}
}
case let .contactsMerged(user, intoContact, mergedContact):
if active(user) && m.hasChat(mergedContact.id) {
await MainActor.run {
@@ -1324,13 +1401,6 @@ func processReceivedMsg(_ res: ChatResponse) async {
await updateContactsStatus(contactRefs, status: .connected)
case let .contactsDisconnected(_, contactRefs):
await updateContactsStatus(contactRefs, status: .disconnected)
case let .contactSubError(user, contact, chatError):
await MainActor.run {
if active(user) {
m.updateContact(contact)
}
processContactSubError(contact, chatError)
}
case let .contactSubSummary(_, contactSubscriptions):
await MainActor.run {
for sub in contactSubscriptions {
@@ -1345,6 +1415,18 @@ func processReceivedMsg(_ res: ChatResponse) async {
}
}
}
case let .networkStatus(status, connections):
await MainActor.run {
for cId in connections {
m.networkStatuses[cId] = status
}
}
case let .networkStatuses(_, statuses): ()
await MainActor.run {
for s in statuses {
m.networkStatuses[s.agentConnId] = s.networkStatus
}
}
case let .newChatItem(user, aChatItem):
let cInfo = aChatItem.chatInfo
let cItem = aChatItem.chatItem
@@ -1366,11 +1448,8 @@ func processReceivedMsg(_ res: ChatResponse) async {
case let .chatItemStatusUpdated(user, aChatItem):
let cInfo = aChatItem.chatInfo
let cItem = aChatItem.chatItem
if !cItem.isDeletedContent {
let added = active(user) ? await MainActor.run { m.upsertChatItem(cInfo, cItem) } : true
if added && cItem.showNotification {
NtfManager.shared.notifyMessageReceived(user, cInfo, cItem)
}
if !cItem.isDeletedContent && active(user) {
await MainActor.run { m.updateChatItem(cInfo, cItem, status: cItem.meta.itemStatus) }
}
if let endTask = m.messageDelivery[cItem.id] {
switch cItem.meta.itemStatus {
@@ -1417,9 +1496,19 @@ func processReceivedMsg(_ res: ChatResponse) async {
await MainActor.run {
m.updateGroup(groupInfo)
if let hostContact = hostContact {
m.dismissConnReqView(hostContact.activeConn.id)
m.removeChat(hostContact.activeConn.id)
if let conn = hostContact?.activeConn {
m.dismissConnReqView(conn.id)
m.removeChat(conn.id)
}
}
case let .groupLinkConnecting(user, groupInfo, hostMember):
if !active(user) { return }
await MainActor.run {
m.updateGroup(groupInfo)
if let hostConn = hostMember.activeConn {
m.dismissConnReqView(hostConn.id)
m.removeChat(hostConn.id)
}
}
case let .joinedGroupMemberConnecting(user, groupInfo, _, member):
@@ -1481,10 +1570,17 @@ func processReceivedMsg(_ res: ChatResponse) async {
m.updateGroup(toGroup)
}
}
case let .memberRole(user, groupInfo, _, _, _, _):
case let .memberRole(user, groupInfo, byMember: _, member: member, fromRole: _, toRole: _):
if active(user) {
await MainActor.run {
m.updateGroup(groupInfo)
_ = m.upsertGroupMember(groupInfo, member)
}
}
case let .newMemberContactReceivedInv(user, contact, _, _):
if active(user) {
await MainActor.run {
m.updateContact(contact)
}
}
case let .rcvFileAccepted(user, aChatItem): // usually rcvFileAccepted is a response, but it's also an event for XFTP files auto-accepted from NSE
@@ -1619,7 +1715,7 @@ func processContactSubError(_ contact: Contact, _ chatError: ChatError) {
case .errorAgent(agentError: .SMP(smpErr: .AUTH)): err = "contact deleted"
default: err = String(describing: chatError)
}
m.setContactNetworkStatus(contact, .error(err))
m.setContactNetworkStatus(contact, .error(connectionError: err))
}
func refreshCallInvitations() throws {

View File

@@ -19,7 +19,11 @@ let bgSuspendTimeout: Int = 5 // seconds
let terminationTimeout: Int = 3 // seconds
private func _suspendChat(timeout: Int) {
if ChatModel.ok {
// this is a redundant check to prevent logical errors, like the one fixed in this PR
let state = appStateGroupDefault.get()
if !state.canSuspend {
logger.error("_suspendChat called, current state: \(state.rawValue, privacy: .public)")
} else if ChatModel.ok {
appStateGroupDefault.set(.suspending)
apiSuspendChat(timeoutMicroseconds: timeout * 1000000)
let endTask = beginBGTask(chatSuspended)
@@ -31,9 +35,7 @@ private func _suspendChat(timeout: Int) {
func suspendChat() {
suspendLockQueue.sync {
if appStateGroupDefault.get() != .stopped {
_suspendChat(timeout: appSuspendTimeout)
}
_suspendChat(timeout: appSuspendTimeout)
}
}
@@ -45,15 +47,25 @@ func suspendBgRefresh() {
}
}
private var terminating = false
func terminateChat() {
logger.debug("terminateChat")
suspendLockQueue.sync {
switch appStateGroupDefault.get() {
case .suspending:
// suspend instantly if already suspending
_chatSuspended()
// when apiSuspendChat is called with timeout 0, it won't send any events on suspension
if ChatModel.ok { apiSuspendChat(timeoutMicroseconds: 0) }
case .stopped: ()
chatCloseStore()
case .suspended:
chatCloseStore()
case .stopped:
chatCloseStore()
default:
terminating = true
// the store will be closed in _chatSuspended when event is received
_suspendChat(timeout: terminationTimeout)
}
}
@@ -73,10 +85,14 @@ private func _chatSuspended() {
if ChatModel.shared.chatRunning == true {
ChatReceiver.shared.stop()
}
if terminating {
chatCloseStore()
}
}
func activateChat(appState: AppState = .active) {
logger.debug("DEBUGGING: activateChat")
terminating = false
suspendLockQueue.sync {
appStateGroupDefault.set(appState)
if ChatModel.ok { apiActivateChat() }
@@ -85,6 +101,7 @@ func activateChat(appState: AppState = .active) {
}
func initChatAndMigrate(refreshInvitations: Bool = true) {
terminating = false
let m = ChatModel.shared
if (!m.chatInitialized) {
do {
@@ -97,6 +114,7 @@ func initChatAndMigrate(refreshInvitations: Bool = true) {
}
func startChatAndActivate() {
terminating = false
logger.debug("DEBUGGING: startChatAndActivate")
if ChatModel.shared.chatRunning == true {
ChatReceiver.shared.start()

View File

@@ -39,6 +39,7 @@ struct ActiveCallView: View {
}
.onAppear {
logger.debug("ActiveCallView: appear client is nil \(client == nil), scenePhase \(String(describing: scenePhase), privacy: .public), canConnectCall \(canConnectCall)")
AppDelegate.keepScreenOn(true)
createWebRTCClient()
dismissAllSheets()
}
@@ -48,6 +49,7 @@ struct ActiveCallView: View {
}
.onDisappear {
logger.debug("ActiveCallView: disappear")
AppDelegate.keepScreenOn(false)
client?.endCall()
}
.onChange(of: m.callCommand) { _ in sendCommandToClient()}

View File

@@ -108,7 +108,6 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
try audioSession.setActive(true)
logger.debug("audioSession activated")
} catch {
print(error)
logger.error("failed activating audio session")
}
}
@@ -121,7 +120,6 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
try audioSession.setActive(false)
logger.debug("audioSession deactivated")
} catch {
print(error)
logger.error("failed deactivating audio session")
}
suspendOnEndCall()

View File

@@ -99,12 +99,12 @@ struct ChatInfoView: View {
@Binding var connectionCode: String?
@FocusState private var aliasTextFieldFocused: Bool
@State private var alert: ChatInfoViewAlert? = nil
@State private var showDeleteContactActionSheet = false
@State private var sendReceipts = SendReceipts.userDefault(true)
@State private var sendReceiptsUserDefault = true
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
enum ChatInfoViewAlert: Identifiable {
case deleteContactAlert
case clearChatAlert
case networkStatusAlert
case switchAddressAlert
@@ -114,7 +114,6 @@ struct ChatInfoView: View {
var id: String {
switch self {
case .deleteContactAlert: return "deleteContactAlert"
case .clearChatAlert: return "clearChatAlert"
case .networkStatusAlert: return "networkStatusAlert"
case .switchAddressAlert: return "switchAddressAlert"
@@ -164,12 +163,13 @@ struct ChatInfoView: View {
// synchronizeConnectionButtonForce()
// }
}
.disabled(!contact.ready || !contact.active)
if let contactLink = contact.contactLink {
Section {
QRCode(uri: contactLink)
SimpleXLinkQRCode(uri: contactLink)
Button {
showShareSheet(items: [contactLink])
showShareSheet(items: [simplexChatLink(contactLink)])
} label: {
Label("Share address", systemImage: "square.and.arrow.up")
}
@@ -180,30 +180,32 @@ struct ChatInfoView: View {
}
}
Section("Servers") {
networkStatusRow()
.onTapGesture {
alert = .networkStatusAlert
}
if let connStats = connectionStats {
Button("Change receiving address") {
alert = .switchAddressAlert
}
.disabled(
connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil }
|| connStats.ratchetSyncSendProhibited
)
if connStats.rcvQueuesInfo.contains(where: { $0.rcvSwitchStatus != nil }) {
Button("Abort changing address") {
alert = .abortSwitchAddressAlert
if contact.ready && contact.active {
Section("Servers") {
networkStatusRow()
.onTapGesture {
alert = .networkStatusAlert
}
if let connStats = connectionStats {
Button("Change receiving address") {
alert = .switchAddressAlert
}
.disabled(
connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil && !$0.canAbortSwitch }
connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil }
|| connStats.ratchetSyncSendProhibited
)
if connStats.rcvQueuesInfo.contains(where: { $0.rcvSwitchStatus != nil }) {
Button("Abort changing address") {
alert = .abortSwitchAddressAlert
}
.disabled(
connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil && !$0.canAbortSwitch }
|| connStats.ratchetSyncSendProhibited
)
}
smpServers("Receiving via", connStats.rcvQueuesInfo.map { $0.rcvServer })
smpServers("Sending via", connStats.sndQueuesInfo.map { $0.sndServer })
}
smpServers("Receiving via", connStats.rcvQueuesInfo.map { $0.rcvServer })
smpServers("Sending via", connStats.sndQueuesInfo.map { $0.sndServer })
}
}
@@ -230,7 +232,6 @@ struct ChatInfoView: View {
}
.alert(item: $alert) { alertItem in
switch(alertItem) {
case .deleteContactAlert: return deleteContactAlert()
case .clearChatAlert: return clearChatAlert()
case .networkStatusAlert: return networkStatusAlert()
case .switchAddressAlert: return switchAddressAlert(switchContactAddress)
@@ -239,6 +240,26 @@ struct ChatInfoView: View {
case let .error(title, error): return mkAlert(title: title, message: error)
}
}
.actionSheet(isPresented: $showDeleteContactActionSheet) {
if contact.ready && contact.active {
return ActionSheet(
title: Text("Delete contact?\nThis cannot be undone!"),
buttons: [
.destructive(Text("Delete and notify contact")) { deleteContact(notify: true) },
.destructive(Text("Delete")) { deleteContact(notify: false) },
.cancel()
]
)
} else {
return ActionSheet(
title: Text("Delete contact?\nThis cannot be undone!"),
buttons: [
.destructive(Text("Delete")) { deleteContact() },
.cancel()
]
)
}
}
}
private func contactInfoHeader() -> some View {
@@ -317,7 +338,7 @@ struct ChatInfoView: View {
verify: { code in
if let r = apiVerifyContact(chat.chatInfo.apiId, connectionCode: code) {
let (verified, existingCode) = r
contact.activeConn.connectionCode = verified ? SecurityCode(securityCode: existingCode, verifiedAt: .now) : nil
contact.activeConn?.connectionCode = verified ? SecurityCode(securityCode: existingCode, verifiedAt: .now) : nil
connectionCode = existingCode
DispatchQueue.main.async {
chat.chatInfo = .direct(contact: contact)
@@ -411,7 +432,7 @@ struct ChatInfoView: View {
private func deleteContactButton() -> some View {
Button(role: .destructive) {
alert = .deleteContactAlert
showDeleteContactActionSheet = true
} label: {
Label("Delete contact", systemImage: "trash")
.foregroundColor(Color.red)
@@ -427,30 +448,23 @@ struct ChatInfoView: View {
}
}
private func deleteContactAlert() -> Alert {
Alert(
title: Text("Delete contact?"),
message: Text("Contact and all messages will be deleted - this cannot be undone!"),
primaryButton: .destructive(Text("Delete")) {
Task {
do {
try await apiDeleteChat(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId)
await MainActor.run {
chatModel.removeChat(chat.chatInfo.id)
chatModel.chatId = nil
dismiss()
}
} catch let error {
logger.error("deleteContactAlert apiDeleteChat error: \(responseError(error))")
let a = getErrorAlert(error, "Error deleting contact")
await MainActor.run {
alert = .error(title: a.title, error: a.message)
}
}
private func deleteContact(notify: Bool? = nil) {
Task {
do {
try await apiDeleteChat(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId, notify: notify)
await MainActor.run {
dismiss()
chatModel.chatId = nil
chatModel.removeChat(chat.chatInfo.id)
}
},
secondaryButton: .cancel()
)
} catch let error {
logger.error("deleteContactAlert apiDeleteChat error: \(responseError(error))")
let a = getErrorAlert(error, "Error deleting contact")
await MainActor.run {
alert = .error(title: a.title, error: a.message)
}
}
}
}
private func clearChatAlert() -> Alert {

View File

@@ -11,7 +11,7 @@ import SimpleXChat
struct CICallItemView: View {
@EnvironmentObject var m: ChatModel
var chatInfo: ChatInfo
@ObservedObject var chat: Chat
var chatItem: ChatItem
var status: CICallStatus
var duration: Int
@@ -60,7 +60,7 @@ struct CICallItemView: View {
@ViewBuilder private func acceptCallButton() -> some View {
if case let .direct(contact) = chatInfo {
if case let .direct(contact) = chat.chatInfo {
Button {
if let invitation = m.callInvitations[contact.id] {
CallController.shared.answerCall(invitation: invitation)

View File

@@ -10,20 +10,92 @@ import SwiftUI
import SimpleXChat
struct CIChatFeatureView: View {
@EnvironmentObject var m: ChatModel
var chatItem: ChatItem
@Binding var revealed: Bool
var feature: Feature
var icon: String? = nil
var iconColor: Color
var body: some View {
if !revealed, let fs = mergedFeautures() {
HStack {
ForEach(fs, content: featureIconView)
}
.padding(.horizontal, 6)
.padding(.vertical, 6)
} else {
fullFeatureView
}
}
private struct FeatureInfo: Identifiable {
var icon: String
var scale: CGFloat
var color: Color
var param: String?
init(_ f: Feature, _ color: Color, _ param: Int?) {
self.icon = f.iconFilled
self.scale = f.iconScale
self.color = color
self.param = f.hasParam && param != nil ? timeText(param) : nil
}
var id: String {
"\(icon) \(color) \(param ?? "")"
}
}
private func mergedFeautures() -> [FeatureInfo]? {
var fs: [FeatureInfo] = []
var icons: Set<String> = []
if var i = m.getChatItemIndex(chatItem) {
while i < m.reversedChatItems.count,
let f = featureInfo(m.reversedChatItems[i]) {
if !icons.contains(f.icon) {
fs.insert(f, at: 0)
icons.insert(f.icon)
}
i += 1
}
}
return fs.count > 1 ? fs : nil
}
private func featureInfo(_ ci: ChatItem) -> FeatureInfo? {
switch ci.content {
case let .rcvChatFeature(feature, enabled, param): FeatureInfo(feature, enabled.iconColor, param)
case let .sndChatFeature(feature, enabled, param): FeatureInfo(feature, enabled.iconColor, param)
case let .rcvGroupFeature(feature, preference, param): FeatureInfo(feature, preference.enable.iconColor, param)
case let .sndGroupFeature(feature, preference, param): FeatureInfo(feature, preference.enable.iconColor, param)
default: nil
}
}
@ViewBuilder private func featureIconView(_ f: FeatureInfo) -> some View {
let i = Image(systemName: f.icon)
.foregroundColor(f.color)
.scaleEffect(f.scale)
if let param = f.param {
HStack {
i
chatEventText(Text(param)).lineLimit(1)
}
} else {
i
}
}
private var fullFeatureView: some View {
HStack(alignment: .bottom, spacing: 4) {
Image(systemName: icon ?? feature.iconFilled)
.foregroundColor(iconColor)
.scaleEffect(feature.iconScale)
chatEventText(chatItem)
}
.padding(.leading, 6)
.padding(.bottom, 6)
.padding(.horizontal, 6)
.padding(.vertical, 4)
.textSelection(.disabled)
}
}
@@ -31,6 +103,6 @@ struct CIChatFeatureView: View {
struct CIChatFeatureView_Previews: PreviewProvider {
static var previews: some View {
let enabled = FeatureEnabled(forUser: false, forContact: false)
CIChatFeatureView(chatItem: ChatItem.getChatFeatureSample(.fullDelete, enabled), feature: ChatFeature.fullDelete, iconColor: enabled.iconColor)
CIChatFeatureView(chatItem: ChatItem.getChatFeatureSample(.fullDelete, enabled), revealed: Binding.constant(true), feature: ChatFeature.fullDelete, iconColor: enabled.iconColor)
}
}

View File

@@ -13,11 +13,9 @@ struct CIEventView: View {
var eventText: Text
var body: some View {
HStack(alignment: .bottom, spacing: 0) {
eventText
}
.padding(.leading, 6)
.padding(.bottom, 6)
eventText
.padding(.horizontal, 6)
.padding(.vertical, 4)
.textSelection(.disabled)
}
}

View File

@@ -10,7 +10,7 @@ import SwiftUI
import SimpleXChat
struct CIFeaturePreferenceView: View {
@EnvironmentObject var chat: Chat
@ObservedObject var chat: Chat
var chatItem: ChatItem
var feature: ChatFeature
var allowed: FeatureAllowed
@@ -80,7 +80,6 @@ struct CIFeaturePreferenceView_Previews: PreviewProvider {
quotedItem: nil,
file: nil
)
CIFeaturePreferenceView(chatItem: chatItem, feature: ChatFeature.timedMessages, allowed: .yes, param: 30)
.environmentObject(Chat.sampleData)
CIFeaturePreferenceView(chat: Chat.sampleData, chatItem: chatItem, feature: ChatFeature.timedMessages, allowed: .yes, param: 30)
}
}

View File

@@ -10,6 +10,7 @@ import SwiftUI
import SimpleXChat
struct CIFileView: View {
@EnvironmentObject var m: ChatModel
@Environment(\.colorScheme) var colorScheme
let file: CIFile?
let edited: Bool
@@ -83,8 +84,8 @@ struct CIFileView: View {
if fileSizeValid() {
Task {
logger.debug("CIFileView fileAction - in .rcvInvitation, in Task")
if let user = ChatModel.shared.currentUser {
let encrypted = file.fileProtocol == .xftp && privacyEncryptLocalFilesGroupDefault.get()
if let user = m.currentUser {
let encrypted = privacyEncryptLocalFilesGroupDefault.get()
await receiveFile(user: user, fileId: file.fileId, encrypted: encrypted)
}
}
@@ -234,18 +235,17 @@ struct CIFileView_Previews: PreviewProvider {
file: nil
)
Group {
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: sentFile, revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(fileName: "some_long_file_name_here", fileStatus: .rcvInvitation), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvAccepted), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvCancelled), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(fileSize: 1_000_000_000, fileStatus: .rcvInvitation), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(text: "Hello there", fileStatus: .rcvInvitation), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", fileStatus: .rcvInvitation), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: fileChatItemWtFile, revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: sentFile, revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileName: "some_long_file_name_here", fileStatus: .rcvInvitation), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvAccepted), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvCancelled), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileSize: 1_000_000_000, fileStatus: .rcvInvitation), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(text: "Hello there", fileStatus: .rcvInvitation), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", fileStatus: .rcvInvitation), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: fileChatItemWtFile, revealed: Binding.constant(false))
}
.previewLayout(.fixed(width: 360, height: 360))
.environmentObject(Chat.sampleData)
}
}

View File

@@ -17,34 +17,45 @@ struct CIGroupInvitationView: View {
var memberRole: GroupMemberRole
var chatIncognito: Bool = false
@State private var frameWidth: CGFloat = 0
@State private var inProgress = false
@State private var progressByTimeout = false
var body: some View {
let action = !chatItem.chatDir.sent && groupInvitation.status == .pending
let v = ZStack(alignment: .bottomTrailing) {
VStack(alignment: .leading) {
groupInfoView(action)
.padding(.horizontal, 2)
.padding(.top, 8)
.padding(.bottom, 6)
.overlay(DetermineWidth())
ZStack {
VStack(alignment: .leading) {
groupInfoView(action)
.padding(.horizontal, 2)
.padding(.top, 8)
.padding(.bottom, 6)
.overlay(DetermineWidth())
Divider().frame(width: frameWidth)
Divider().frame(width: frameWidth)
if action {
groupInvitationText()
.overlay(DetermineWidth())
Text(chatIncognito ? "Tap to join incognito" : "Tap to join")
.foregroundColor(chatIncognito ? .indigo : .accentColor)
.font(.callout)
.padding(.trailing, 60)
.overlay(DetermineWidth())
} else {
groupInvitationText()
.padding(.trailing, 60)
.overlay(DetermineWidth())
if action {
VStack(alignment: .leading, spacing: 2) {
groupInvitationText()
.overlay(DetermineWidth())
Text(chatIncognito ? "Tap to join incognito" : "Tap to join")
.foregroundColor(inProgress ? .secondary : chatIncognito ? .indigo : .accentColor)
.font(.callout)
.padding(.trailing, 60)
.overlay(DetermineWidth())
}
} else {
groupInvitationText()
.padding(.trailing, 60)
.overlay(DetermineWidth())
}
}
.padding(.bottom, 2)
if progressByTimeout {
ProgressView().scaleEffect(2)
}
}
.padding(.bottom, 2)
chatItem.timestampText
.font(.caption)
.foregroundColor(.secondary)
@@ -55,11 +66,24 @@ struct CIGroupInvitationView: View {
.cornerRadius(18)
.textSelection(.disabled)
.onPreferenceChange(DetermineWidth.Key.self) { frameWidth = $0 }
.onChange(of: inProgress) { inProgress in
if inProgress {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
progressByTimeout = inProgress
}
} else {
progressByTimeout = false
}
}
if action {
v.onTapGesture {
joinGroup(groupInvitation.groupId)
inProgress = true
joinGroup(groupInvitation.groupId) {
await MainActor.run { inProgress = false }
}
}
.disabled(inProgress)
} else {
v
}
@@ -67,7 +91,7 @@ struct CIGroupInvitationView: View {
private func groupInfoView(_ action: Bool) -> some View {
var color: Color
if action {
if action && !inProgress {
color = chatIncognito ? .indigo : .accentColor
} else {
color = Color(uiColor: .tertiaryLabel)

View File

@@ -10,6 +10,7 @@ import SwiftUI
import SimpleXChat
struct CIImageView: View {
@EnvironmentObject var m: ChatModel
@Environment(\.colorScheme) var colorScheme
let chatItem: ChatItem
let image: String
@@ -36,7 +37,7 @@ struct CIImageView: View {
switch file.fileStatus {
case .rcvInvitation:
Task {
if let user = ChatModel.shared.currentUser {
if let user = m.currentUser {
await receiveFile(user: user, fileId: file.fileId, encrypted: chatItem.encryptLocalFile)
}
}

View File

@@ -0,0 +1,80 @@
//
// CIMemberCreatedContactView.swift
// SimpleX (iOS)
//
// Created by spaced4ndy on 19.09.2023.
// Copyright © 2023 SimpleX Chat. All rights reserved.
//
import SwiftUI
import SimpleXChat
struct CIMemberCreatedContactView: View {
@EnvironmentObject var m: ChatModel
var chatItem: ChatItem
var body: some View {
HStack(alignment: .bottom, spacing: 0) {
switch chatItem.chatDir {
case let .groupRcv(groupMember):
if let contactId = groupMember.memberContactId {
memberCreatedContactView(openText: "Open")
.onTapGesture {
dismissAllSheets(animated: true)
DispatchQueue.main.async {
m.chatId = "@\(contactId)"
}
}
} else {
memberCreatedContactView()
}
default:
EmptyView()
}
}
.padding(.leading, 6)
.padding(.bottom, 6)
.textSelection(.disabled)
}
private func memberCreatedContactView(openText: LocalizedStringKey? = nil) -> some View {
var r = eventText()
if let openText {
r = r
+ Text(openText)
.fontWeight(.medium)
.foregroundColor(.accentColor)
+ Text(" ")
}
r = r + chatItem.timestampText
.fontWeight(.light)
.foregroundColor(.secondary)
return r.font(.caption)
}
private func eventText() -> Text {
if let member = chatItem.memberDisplayName {
return Text(member + " " + chatItem.content.text + " ")
.fontWeight(.light)
.foregroundColor(.secondary)
} else {
return Text(chatItem.content.text + " ")
.fontWeight(.light)
.foregroundColor(.secondary)
}
}
}
struct CIMemberCreatedContactView_Previews: PreviewProvider {
static var previews: some View {
let content = CIContent.rcvGroupEvent(rcvGroupEvent: .memberCreatedContact)
let chatItem = ChatItem(
chatDir: .groupRcv(groupMember: GroupMember.sampleData),
meta: CIMeta.getSample(1, .now, content.text, .rcvRead),
content: content,
quotedItem: nil,
file: nil
)
CIMemberCreatedContactView(chatItem: chatItem)
}
}

View File

@@ -10,7 +10,7 @@ import SwiftUI
import SimpleXChat
struct CIMetaView: View {
@EnvironmentObject var chat: Chat
@ObservedObject var chat: Chat
var chatItem: ChatItem
var metaColor = Color.secondary
var paleMetaColor = Color(UIColor.tertiaryLabel)
@@ -95,15 +95,14 @@ private func statusIconText(_ icon: String, _ color: Color) -> Text {
struct CIMetaView_Previews: PreviewProvider {
static var previews: some View {
Group {
CIMetaView(chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete)))
CIMetaView(chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .partial)))
CIMetaView(chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndRcvd(msgRcptStatus: .ok, sndProgress: .complete)))
CIMetaView(chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndRcvd(msgRcptStatus: .ok, sndProgress: .partial)))
CIMetaView(chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndRcvd(msgRcptStatus: .badMsgHash, sndProgress: .complete)))
CIMetaView(chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), itemEdited: true))
CIMetaView(chatItem: ChatItem.getDeletedContentSample())
CIMetaView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete)))
CIMetaView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .partial)))
CIMetaView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndRcvd(msgRcptStatus: .ok, sndProgress: .complete)))
CIMetaView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndRcvd(msgRcptStatus: .ok, sndProgress: .partial)))
CIMetaView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndRcvd(msgRcptStatus: .badMsgHash, sndProgress: .complete)))
CIMetaView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), itemEdited: true))
CIMetaView(chat: Chat.sampleData, chatItem: ChatItem.getDeletedContentSample())
}
.previewLayout(.fixed(width: 360, height: 100))
.environmentObject(Chat.sampleData)
}
}

View File

@@ -12,7 +12,8 @@ import SimpleXChat
let decryptErrorReason: LocalizedStringKey = "It can happen when you or your connection used the old database backup."
struct CIRcvDecryptionError: View {
@EnvironmentObject var chat: Chat
@EnvironmentObject var m: ChatModel
@ObservedObject var chat: Chat
var msgDecryptError: MsgDecryptError
var msgCount: UInt32
var chatItem: ChatItem
@@ -45,7 +46,7 @@ struct CIRcvDecryptionError: View {
do {
let (member, stats) = try apiGroupMemberInfo(groupInfo.apiId, groupMember.groupMemberId)
if let s = stats {
ChatModel.shared.updateGroupMemberConnectionStats(groupInfo, member, s)
m.updateGroupMemberConnectionStats(groupInfo, member, s)
}
} catch let error {
logger.error("apiGroupMemberInfo error: \(responseError(error))")
@@ -65,7 +66,7 @@ struct CIRcvDecryptionError: View {
@ViewBuilder private func viewBody() -> some View {
if case let .direct(contact) = chat.chatInfo,
let contactStats = contact.activeConn.connectionStats {
let contactStats = contact.activeConn?.connectionStats {
if contactStats.ratchetSyncAllowed {
decryptionErrorItemFixButton(syncSupported: true) {
alert = .syncAllowedAlert { syncContactConnection(contact) }
@@ -79,8 +80,8 @@ struct CIRcvDecryptionError: View {
}
} else if case let .group(groupInfo) = chat.chatInfo,
case let .groupRcv(groupMember) = chatItem.chatDir,
let modelMember = ChatModel.shared.groupMembers.first(where: { $0.id == groupMember.id }),
let memberStats = modelMember.activeConn?.connectionStats {
let mem = m.getGroupMember(groupMember.groupMemberId),
let memberStats = mem.wrapped.activeConn?.connectionStats {
if memberStats.ratchetSyncAllowed {
decryptionErrorItemFixButton(syncSupported: true) {
alert = .syncAllowedAlert { syncMemberConnection(groupInfo, groupMember) }
@@ -122,7 +123,7 @@ struct CIRcvDecryptionError: View {
)
}
.padding(.horizontal, 12)
CIMetaView(chatItem: chatItem)
CIMetaView(chat: chat, chatItem: chatItem)
.padding(.horizontal, 12)
}
.onTapGesture(perform: { onClick() })
@@ -142,7 +143,7 @@ struct CIRcvDecryptionError: View {
+ ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true)
}
.padding(.horizontal, 12)
CIMetaView(chatItem: chatItem)
CIMetaView(chat: chat, chatItem: chatItem)
.padding(.horizontal, 12)
}
.onTapGesture(perform: { onClick() })
@@ -164,6 +165,8 @@ struct CIRcvDecryptionError: View {
message = Text("\(msgCount) messages failed to decrypt.") + Text("\n") + why
case .other:
message = Text("\(msgCount) messages failed to decrypt.") + Text("\n") + why
case .ratchetSync:
message = Text("Encryption re-negotiation failed.")
}
return message
}
@@ -173,7 +176,7 @@ struct CIRcvDecryptionError: View {
do {
let (mem, stats) = try apiSyncGroupMemberRatchet(groupInfo.apiId, member.groupMemberId, false)
await MainActor.run {
ChatModel.shared.updateGroupMemberConnectionStats(groupInfo, mem, stats)
m.updateGroupMemberConnectionStats(groupInfo, mem, stats)
}
} catch let error {
logger.error("syncMemberConnection apiSyncGroupMemberRatchet error: \(responseError(error))")
@@ -190,7 +193,7 @@ struct CIRcvDecryptionError: View {
do {
let stats = try apiSyncContactRatchet(contact.apiId, false)
await MainActor.run {
ChatModel.shared.updateContactConnectionStats(contact, stats)
m.updateContactConnectionStats(contact, stats)
}
} catch let error {
logger.error("syncContactConnection apiSyncContactRatchet error: \(responseError(error))")

View File

@@ -9,8 +9,10 @@
import SwiftUI
import AVKit
import SimpleXChat
import Combine
struct CIVideoView: View {
@EnvironmentObject var m: ChatModel
@Environment(\.colorScheme) var colorScheme
private let chatItem: ChatItem
private let image: String
@@ -27,6 +29,7 @@ struct CIVideoView: View {
@State private var showFullScreenPlayer = false
@State private var timeObserver: Any? = nil
@State private var fullScreenTimeObserver: Any? = nil
@State private var publisher: AnyCancellable? = nil
init(chatItem: ChatItem, image: String, duration: Int, maxWidth: CGFloat, videoWidth: Binding<CGFloat?>, scrollProxy: ScrollViewProxy?) {
self.chatItem = chatItem
@@ -101,7 +104,7 @@ struct CIVideoView: View {
let canBePlayed = !chatItem.chatDir.sent || file.fileStatus == CIFileStatus.sndComplete
VideoPlayerView(player: player, url: url, showControls: false)
.frame(width: w, height: w * preview.size.height / preview.size.width)
.onChange(of: ChatModel.shared.stopPreviousRecPlay) { playingUrl in
.onChange(of: m.stopPreviousRecPlay) { playingUrl in
if playingUrl != url {
player.pause()
videoPlaying = false
@@ -124,7 +127,7 @@ struct CIVideoView: View {
}
if !videoPlaying {
Button {
ChatModel.shared.stopPreviousRecPlay = url
m.stopPreviousRecPlay = url
player.play()
} label: {
playPauseIcon(canBePlayed ? "play.fill" : "play.slash")
@@ -256,7 +259,7 @@ struct CIVideoView: View {
// TODO encrypt: where file size is checked?
private func receiveFileIfValidSize(file: CIFile, encrypted: Bool, receiveFile: @escaping (User, Int64, Bool, Bool) async -> Void) {
Task {
if let user = ChatModel.shared.currentUser {
if let user = m.currentUser {
await receiveFile(user, file.fileId, encrypted, false)
}
}
@@ -290,9 +293,17 @@ struct CIVideoView: View {
)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now()) {
ChatModel.shared.stopPreviousRecPlay = url
m.stopPreviousRecPlay = url
if let player = fullPlayer {
player.play()
var played = false
publisher = player.publisher(for: \.timeControlStatus).sink { status in
if played || status == .playing {
AppDelegate.keepScreenOn(status == .playing)
AudioPlayer.changeAudioSession(status == .playing)
}
played = status == .playing
}
fullScreenTimeObserver = NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player.currentItem, queue: .main) { _ in
player.seek(to: CMTime.zero)
player.play()
@@ -307,6 +318,7 @@ struct CIVideoView: View {
fullScreenTimeObserver = nil
fullPlayer?.pause()
fullPlayer?.seek(to: CMTime.zero)
publisher?.cancel()
}
}
}

View File

@@ -10,6 +10,7 @@ import SwiftUI
import SimpleXChat
struct CIVoiceView: View {
@ObservedObject var chat: Chat
var chatItem: ChatItem
let recordingFile: CIFile?
let duration: Int
@@ -91,7 +92,7 @@ struct CIVoiceView: View {
}
private func metaView() -> some View {
CIMetaView(chatItem: chatItem)
CIMetaView(chat: chat, chatItem: chatItem)
}
}
@@ -219,7 +220,7 @@ struct VoiceMessagePlayer: View {
private func downloadButton(_ recordingFile: CIFile) -> some View {
Button {
Task {
if let user = ChatModel.shared.currentUser {
if let user = chatModel.currentUser {
await receiveFile(user: user, fileId: recordingFile.fileId, encrypted: privacyEncryptLocalFilesGroupDefault.get())
}
}
@@ -284,6 +285,7 @@ struct CIVoiceView_Previews: PreviewProvider {
)
Group {
CIVoiceView(
chat: Chat.sampleData,
chatItem: ChatItem.getVoiceMsgContentSample(),
recordingFile: CIFile.getSample(fileName: "voice.m4a", fileSize: 65536, fileStatus: .rcvComplete),
duration: 30,
@@ -292,12 +294,11 @@ struct CIVoiceView_Previews: PreviewProvider {
playbackTime: .constant(TimeInterval(20)),
allowMenu: Binding.constant(true)
)
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: sentVoiceMessage, revealed: Binding.constant(false), allowMenu: .constant(true), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(), revealed: Binding.constant(false), allowMenu: .constant(true), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), revealed: Binding.constant(false), allowMenu: .constant(true), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: voiceMessageWtFile, revealed: Binding.constant(false), allowMenu: .constant(true), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
ChatItemView(chat: Chat.sampleData, chatItem: sentVoiceMessage, revealed: Binding.constant(false), allowMenu: .constant(true), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(), revealed: Binding.constant(false), allowMenu: .constant(true), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), revealed: Binding.constant(false), allowMenu: .constant(true), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
ChatItemView(chat: Chat.sampleData, chatItem: voiceMessageWtFile, revealed: Binding.constant(false), allowMenu: .constant(true), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
}
.previewLayout(.fixed(width: 360, height: 360))
.environmentObject(Chat.sampleData)
}
}

View File

@@ -11,6 +11,7 @@ import SimpleXChat
struct DeletedItemView: View {
@Environment(\.colorScheme) var colorScheme
@ObservedObject var chat: Chat
var chatItem: ChatItem
var body: some View {
@@ -18,7 +19,7 @@ struct DeletedItemView: View {
Text(chatItem.content.text)
.foregroundColor(.secondary)
.italic()
CIMetaView(chatItem: chatItem)
CIMetaView(chat: chat, chatItem: chatItem)
.padding(.horizontal, 12)
}
.padding(.leading, 12)
@@ -32,8 +33,8 @@ struct DeletedItemView: View {
struct DeletedItemView_Previews: PreviewProvider {
static var previews: some View {
Group {
DeletedItemView(chatItem: ChatItem.getDeletedContentSample())
DeletedItemView(chatItem: ChatItem.getDeletedContentSample(dir: .groupRcv(groupMember: GroupMember.sampleData)))
DeletedItemView(chat: Chat.sampleData, chatItem: ChatItem.getDeletedContentSample())
DeletedItemView(chat: Chat.sampleData, chatItem: ChatItem.getDeletedContentSample(dir: .groupRcv(groupMember: GroupMember.sampleData)))
}
.previewLayout(.fixed(width: 360, height: 200))
}

View File

@@ -10,6 +10,7 @@ import SwiftUI
import SimpleXChat
struct EmojiItemView: View {
@ObservedObject var chat: Chat
var chatItem: ChatItem
var body: some View {
@@ -17,7 +18,7 @@ struct EmojiItemView: View {
emojiText(chatItem.content.text)
.padding(.top, 8)
.padding(.horizontal, 6)
CIMetaView(chatItem: chatItem)
CIMetaView(chat: chat, chatItem: chatItem)
.padding(.bottom, 8)
.padding(.horizontal, 12)
}
@@ -32,8 +33,8 @@ func emojiText(_ text: String) -> Text {
struct EmojiItemView_Previews: PreviewProvider {
static var previews: some View {
Group{
EmojiItemView(chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂", .sndSent(sndProgress: .complete)))
EmojiItemView(chatItem: ChatItem.getSample(2, .directRcv, .now, "👍"))
EmojiItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂", .sndSent(sndProgress: .complete)))
EmojiItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "👍"))
}
.previewLayout(.fixed(width: 360, height: 70))
}

View File

@@ -88,13 +88,12 @@ struct FramedCIVoiceView_Previews: PreviewProvider {
file: CIFile.getSample(fileStatus: .sndComplete)
)
Group {
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: sentVoiceMessage, revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there"), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there", fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: voiceMessageWithQuote, revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: sentVoiceMessage, revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there"), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there", fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: voiceMessageWithQuote, revealed: Binding.constant(false))
}
.previewLayout(.fixed(width: 360, height: 360))
.environmentObject(Chat.sampleData)
}
}

File diff suppressed because one or more lines are too long

View File

@@ -150,7 +150,7 @@ struct FullScreenMediaView: View {
private func startPlayerAndNotify() {
if let player = player {
ChatModel.shared.stopPreviousRecPlay = url
m.stopPreviousRecPlay = url
player.play()
}
}

View File

@@ -10,11 +10,12 @@ import SwiftUI
import SimpleXChat
struct IntegrityErrorItemView: View {
@ObservedObject var chat: Chat
var msgError: MsgErrorType
var chatItem: ChatItem
var body: some View {
CIMsgError(chatItem: chatItem) {
CIMsgError(chat: chat, chatItem: chatItem) {
switch msgError {
case .msgSkipped:
AlertManager.shared.showAlertMsg(
@@ -52,6 +53,7 @@ struct IntegrityErrorItemView: View {
}
struct CIMsgError: View {
@ObservedObject var chat: Chat
var chatItem: ChatItem
var onTap: () -> Void
@@ -60,7 +62,7 @@ struct CIMsgError: View {
Text(chatItem.content.text)
.foregroundColor(.red)
.italic()
CIMetaView(chatItem: chatItem)
CIMetaView(chat: chat, chatItem: chatItem)
.padding(.horizontal, 12)
}
.padding(.leading, 12)
@@ -74,6 +76,6 @@ struct CIMsgError: View {
struct IntegrityErrorItemView_Previews: PreviewProvider {
static var previews: some View {
IntegrityErrorItemView(msgError: .msgBadHash, chatItem: ChatItem.getIntegrityErrorSample())
IntegrityErrorItemView(chat: Chat.sampleData, msgError: .msgBadHash, chatItem: ChatItem.getIntegrityErrorSample())
}
}

View File

@@ -10,39 +10,70 @@ import SwiftUI
import SimpleXChat
struct MarkedDeletedItemView: View {
@EnvironmentObject var m: ChatModel
@Environment(\.colorScheme) var colorScheme
@ObservedObject var chat: Chat
var chatItem: ChatItem
@Binding var revealed: Bool
var body: some View {
HStack(alignment: .bottom, spacing: 0) {
if case let .moderated(_, byGroupMember) = chatItem.meta.itemDeleted {
markedDeletedText("moderated by \(byGroupMember.chatViewName)")
} else {
markedDeletedText("marked deleted")
}
CIMetaView(chatItem: chatItem)
.padding(.horizontal, 12)
}
.padding(.leading, 12)
(Text(mergedMarkedDeletedText).italic() + Text(" ") + chatItem.timestampText)
.font(.caption)
.foregroundColor(.secondary)
.padding(.horizontal, 12)
.padding(.vertical, 6)
.background(chatItemFrameColor(chatItem, colorScheme))
.cornerRadius(18)
.textSelection(.disabled)
}
func markedDeletedText(_ s: LocalizedStringKey) -> some View {
Text(s)
.font(.caption)
.foregroundColor(.secondary)
.italic()
.lineLimit(1)
var mergedMarkedDeletedText: LocalizedStringKey {
if !revealed,
let ciCategory = chatItem.mergeCategory,
var i = m.getChatItemIndex(chatItem) {
var moderated = 0
var blocked = 0
var deleted = 0
var moderatedBy: Set<String> = []
while i < m.reversedChatItems.count,
let ci = .some(m.reversedChatItems[i]),
ci.mergeCategory == ciCategory,
let itemDeleted = ci.meta.itemDeleted {
switch itemDeleted {
case let .moderated(_, byGroupMember):
moderated += 1
moderatedBy.insert(byGroupMember.displayName)
case .blocked: blocked += 1
case .deleted: deleted += 1
}
i += 1
}
let total = moderated + blocked + deleted
return total <= 1
? markedDeletedText
: total == moderated
? "\(total) messages moderated by \(moderatedBy.joined(separator: ", "))"
: total == blocked
? "\(total) messages blocked"
: "\(total) messages marked deleted"
} else {
return markedDeletedText
}
}
var markedDeletedText: LocalizedStringKey {
switch chatItem.meta.itemDeleted {
case let .moderated(_, byGroupMember): "moderated by \(byGroupMember.displayName)"
case .blocked: "blocked"
default: "marked deleted"
}
}
}
struct MarkedDeletedItemView_Previews: PreviewProvider {
static var previews: some View {
Group {
MarkedDeletedItemView(chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)))
MarkedDeletedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true))
}
.previewLayout(.fixed(width: 360, height: 200))
}

View File

@@ -25,7 +25,7 @@ private func typing(_ w: Font.Weight = .light) -> Text {
}
struct MsgContentView: View {
@EnvironmentObject var chat: Chat
@ObservedObject var chat: Chat
var text: String
var formattedText: [FormattedText]? = nil
var sender: String? = nil
@@ -121,13 +121,11 @@ private func formatText(_ ft: FormattedText, _ preview: Bool) -> Text {
case .secret: return Text(t).foregroundColor(.clear).underline(color: .primary)
case let .colored(color): return Text(t).foregroundColor(color.uiColor)
case .uri: return linkText(t, t, preview, prefix: "")
case let .simplexLink(linkType, simplexUri, trustedUri, smpHosts):
case let .simplexLink(linkType, simplexUri, smpHosts):
switch privacySimplexLinkModeDefault.get() {
case .description: return linkText(simplexLinkText(linkType, smpHosts), simplexUri, preview, prefix: "")
case .full: return linkText(t, simplexUri, preview, prefix: "")
case .browser: return trustedUri
? linkText(t, t, preview, prefix: "")
: linkText(t, t, preview, prefix: "", color: .red, uiColor: .red)
case .browser: return linkText(t, simplexUri, preview, prefix: "")
}
case .email: return linkText(t, t, preview, prefix: "mailto:")
case .phone: return linkText(t, t.replacingOccurrences(of: " ", with: ""), preview, prefix: "tel:")
@@ -154,6 +152,7 @@ struct MsgContentView_Previews: PreviewProvider {
static var previews: some View {
let chatItem = ChatItem.getSample(1, .directSnd, .now, "hello")
return MsgContentView(
chat: Chat.sampleData,
text: chatItem.text,
formattedText: chatItem.formattedText,
sender: chatItem.memberDisplayName,

View File

@@ -10,6 +10,7 @@ import SwiftUI
import SimpleXChat
struct ChatItemInfoView: View {
@EnvironmentObject var chatModel: ChatModel
@Environment(\.colorScheme) var colorScheme
var ci: ChatItem
@Binding var chatItemInfo: ChatItemInfo?
@@ -290,8 +291,8 @@ struct ChatItemInfoView: View {
private func membersStatuses(_ memberDeliveryStatuses: [MemberDeliveryStatus]) -> [(GroupMember, CIStatus)] {
memberDeliveryStatuses.compactMap({ mds in
if let mem = ChatModel.shared.groupMembers.first(where: { $0.groupMemberId == mds.groupMemberId }) {
return (mem, mds.memberDeliveryStatus)
if let mem = chatModel.getGroupMember(mds.groupMemberId) {
return (mem.wrapped, mds.memberDeliveryStatus)
} else {
return nil
}

View File

@@ -10,7 +10,7 @@ import SwiftUI
import SimpleXChat
struct ChatItemView: View {
var chatInfo: ChatInfo
@ObservedObject var chat: Chat
var chatItem: ChatItem
var maxWidth: CGFloat = .infinity
@State var scrollProxy: ScrollViewProxy? = nil
@@ -19,8 +19,19 @@ struct ChatItemView: View {
@Binding var audioPlayer: AudioPlayer?
@Binding var playbackState: VoiceMessagePlaybackState
@Binding var playbackTime: TimeInterval?
init(chatInfo: ChatInfo, chatItem: ChatItem, showMember: Bool = false, maxWidth: CGFloat = .infinity, scrollProxy: ScrollViewProxy? = nil, revealed: Binding<Bool>, allowMenu: Binding<Bool> = .constant(false), audioPlayer: Binding<AudioPlayer?> = .constant(nil), playbackState: Binding<VoiceMessagePlaybackState> = .constant(.noPlayback), playbackTime: Binding<TimeInterval?> = .constant(nil)) {
self.chatInfo = chatInfo
init(
chat: Chat,
chatItem: ChatItem,
showMember: Bool = false,
maxWidth: CGFloat = .infinity,
scrollProxy: ScrollViewProxy? = nil,
revealed: Binding<Bool>,
allowMenu: Binding<Bool> = .constant(false),
audioPlayer: Binding<AudioPlayer?> = .constant(nil),
playbackState: Binding<VoiceMessagePlaybackState> = .constant(.noPlayback),
playbackTime: Binding<TimeInterval?> = .constant(nil)
) {
self.chat = chat
self.chatItem = chatItem
self.maxWidth = maxWidth
_scrollProxy = .init(initialValue: scrollProxy)
@@ -33,15 +44,15 @@ struct ChatItemView: View {
var body: some View {
let ci = chatItem
if chatItem.meta.itemDeleted != nil && !revealed {
MarkedDeletedItemView(chatItem: chatItem)
if chatItem.meta.itemDeleted != nil && (!revealed || chatItem.isDeletedContent) {
MarkedDeletedItemView(chat: chat, chatItem: chatItem, revealed: $revealed)
} else if ci.quotedItem == nil && ci.meta.itemDeleted == nil && !ci.meta.isLive {
if let mc = ci.content.msgContent, mc.isText && isShortEmoji(ci.content.text) {
EmojiItemView(chatItem: ci)
EmojiItemView(chat: chat, chatItem: ci)
} else if ci.content.text.isEmpty, case let .voice(_, duration) = ci.content.msgContent {
CIVoiceView(chatItem: ci, recordingFile: ci.file, duration: duration, audioPlayer: $audioPlayer, playbackState: $playbackState, playbackTime: $playbackTime, allowMenu: $allowMenu)
CIVoiceView(chat: chat, chatItem: ci, recordingFile: ci.file, duration: duration, audioPlayer: $audioPlayer, playbackState: $playbackState, playbackTime: $playbackTime, allowMenu: $allowMenu)
} else if ci.content.msgContent == nil {
ChatItemContentView(chatInfo: chatInfo, chatItem: chatItem, msgContentView: { Text(ci.text) }) // msgContent is unreachable branch in this case
ChatItemContentView(chat: chat, chatItem: chatItem, revealed: $revealed, msgContentView: { Text(ci.text) }) // msgContent is unreachable branch in this case
} else {
framedItemView()
}
@@ -51,15 +62,17 @@ struct ChatItemView: View {
}
private func framedItemView() -> some View {
FramedItemView(chatInfo: chatInfo, chatItem: chatItem, maxWidth: maxWidth, scrollProxy: scrollProxy, allowMenu: $allowMenu, audioPlayer: $audioPlayer, playbackState: $playbackState, playbackTime: $playbackTime)
FramedItemView(chat: chat, chatItem: chatItem, revealed: $revealed, maxWidth: maxWidth, scrollProxy: scrollProxy, allowMenu: $allowMenu, audioPlayer: $audioPlayer, playbackState: $playbackState, playbackTime: $playbackTime)
}
}
struct ChatItemContentView<Content: View>: View {
@EnvironmentObject var chatModel: ChatModel
var chatInfo: ChatInfo
@ObservedObject var chat: Chat
var chatItem: ChatItem
@Binding var revealed: Bool
var msgContentView: () -> Content
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
var body: some View {
switch chatItem.content {
@@ -69,11 +82,17 @@ struct ChatItemContentView<Content: View>: View {
case .rcvDeleted: deletedItemView()
case let .sndCall(status, duration): callItemView(status, duration)
case let .rcvCall(status, duration): callItemView(status, duration)
case let .rcvIntegrityError(msgError): IntegrityErrorItemView(msgError: msgError, chatItem: chatItem)
case let .rcvDecryptionError(msgDecryptError, msgCount): CIRcvDecryptionError(msgDecryptError: msgDecryptError, msgCount: msgCount, chatItem: chatItem)
case let .rcvIntegrityError(msgError):
if developerTools {
IntegrityErrorItemView(chat: chat, msgError: msgError, chatItem: chatItem)
} else {
ZStack {}
}
case let .rcvDecryptionError(msgDecryptError, msgCount): CIRcvDecryptionError(chat: chat, msgDecryptError: msgDecryptError, msgCount: msgCount, chatItem: chatItem)
case let .rcvGroupInvitation(groupInvitation, memberRole): groupInvitationItemView(groupInvitation, memberRole)
case let .sndGroupInvitation(groupInvitation, memberRole): groupInvitationItemView(groupInvitation, memberRole)
case .rcvGroupEvent(.memberConnected): CIEventView(eventText: membersConnectedItemText)
case .rcvDirectEvent: eventItemView()
case .rcvGroupEvent(.memberCreatedContact): CIMemberCreatedContactView(chatItem: chatItem)
case .rcvGroupEvent: eventItemView()
case .sndGroupEvent: eventItemView()
case .rcvConnEvent: eventItemView()
@@ -81,9 +100,9 @@ struct ChatItemContentView<Content: View>: View {
case let .rcvChatFeature(feature, enabled, _): chatFeatureView(feature, enabled.iconColor)
case let .sndChatFeature(feature, enabled, _): chatFeatureView(feature, enabled.iconColor)
case let .rcvChatPreference(feature, allowed, param):
CIFeaturePreferenceView(chatItem: chatItem, feature: feature, allowed: allowed, param: param)
CIFeaturePreferenceView(chat: chat, chatItem: chatItem, feature: feature, allowed: allowed, param: param)
case let .sndChatPreference(feature, _, _):
CIChatFeatureView(chatItem: chatItem, feature: feature, icon: feature.icon, iconColor: .secondary)
CIChatFeatureView(chatItem: chatItem, revealed: $revealed, feature: feature, icon: feature.icon, iconColor: .secondary)
case let .rcvGroupFeature(feature, preference, _): chatFeatureView(feature, preference.enable.iconColor)
case let .sndGroupFeature(feature, preference, _): chatFeatureView(feature, preference.enable.iconColor)
case let .rcvChatFeatureRejected(feature): chatFeatureView(feature, .red)
@@ -95,15 +114,15 @@ struct ChatItemContentView<Content: View>: View {
}
private func deletedItemView() -> some View {
DeletedItemView(chatItem: chatItem)
DeletedItemView(chat: chat, chatItem: chatItem)
}
private func callItemView(_ status: CICallStatus, _ duration: Int) -> some View {
CICallItemView(chatInfo: chatInfo, chatItem: chatItem, status: status, duration: duration)
CICallItemView(chat: chat, chatItem: chatItem, status: status, duration: duration)
}
private func groupInvitationItemView(_ groupInvitation: CIGroupInvitation, _ memberRole: GroupMemberRole) -> some View {
CIGroupInvitationView(chatItem: chatItem, groupInvitation: groupInvitation, memberRole: memberRole, chatIncognito: chatInfo.incognito)
CIGroupInvitationView(chatItem: chatItem, groupInvitation: groupInvitation, memberRole: memberRole, chatIncognito: chat.chatInfo.incognito)
}
private func eventItemView() -> some View {
@@ -111,7 +130,9 @@ struct ChatItemContentView<Content: View>: View {
}
private func eventItemViewText() -> Text {
if let member = chatItem.memberDisplayName {
if !revealed, let t = mergedGroupEventText {
return chatEventText(t + Text(" ") + chatItem.timestampText)
} else if let member = chatItem.memberDisplayName {
return Text(member + " ")
.font(.caption)
.foregroundColor(.secondary)
@@ -123,36 +144,44 @@ struct ChatItemContentView<Content: View>: View {
}
private func chatFeatureView(_ feature: Feature, _ iconColor: Color) -> some View {
CIChatFeatureView(chatItem: chatItem, feature: feature, iconColor: iconColor)
CIChatFeatureView(chatItem: chatItem, revealed: $revealed, feature: feature, iconColor: iconColor)
}
private var membersConnectedItemText: Text {
if let t = membersConnectedText {
return chatEventText(t, chatItem.timestampText)
private var mergedGroupEventText: Text? {
let (count, ns) = chatModel.getConnectedMemberNames(chatItem)
let members: LocalizedStringKey =
switch ns.count {
case 1: "\(ns[0]) connected"
case 2: "\(ns[0]) and \(ns[1]) connected"
case 3: "\(ns[0] + ", " + ns[1]) and \(ns[2]) connected"
default:
ns.count > 3
? "\(ns[0]), \(ns[1]) and \(ns.count - 2) other members connected"
: ""
}
return if count <= 1 {
nil
} else if ns.count == 0 {
Text("\(count) group events")
} else if count > ns.count {
Text(members) + Text(" ") + Text("and \(count - ns.count) other events")
} else {
return eventItemViewText()
Text(members)
}
}
private var membersConnectedText: LocalizedStringKey? {
let ns = chatModel.getConnectedMemberNames(chatItem)
return ns.count > 3
? "\(ns[0]), \(ns[1]) and \(ns.count - 2) other members connected"
: ns.count == 3
? "\(ns[0] + ", " + ns[1]) and \(ns[2]) connected"
: ns.count == 2
? "\(ns[0]) and \(ns[1]) connected"
: nil
}
}
func chatEventText(_ eventText: LocalizedStringKey, _ ts: Text) -> Text {
(Text(eventText) + Text(" ") + ts)
func chatEventText(_ text: Text) -> Text {
text
.font(.caption)
.foregroundColor(.secondary)
.fontWeight(.light)
}
func chatEventText(_ eventText: LocalizedStringKey, _ ts: Text) -> Text {
chatEventText(Text(eventText) + Text(" ") + ts)
}
func chatEventText(_ ci: ChatItem) -> Text {
chatEventText("\(ci.content.text)", ci.timestampText)
}
@@ -160,15 +189,15 @@ func chatEventText(_ ci: ChatItem) -> Text {
struct ChatItemView_Previews: PreviewProvider {
static var previews: some View {
Group{
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too"), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂"), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂"), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂🙂"), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getDeletedContentSample(), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂", .sndSent(sndProgress: .complete), itemLive: true), revealed: Binding.constant(true))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemLive: true), revealed: Binding.constant(true))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too"), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂"), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂"), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂🙂"), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getDeletedContentSample(), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(false))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂", .sndSent(sndProgress: .complete), itemLive: true), revealed: Binding.constant(true))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemLive: true), revealed: Binding.constant(true))
}
.previewLayout(.fixed(width: 360, height: 70))
.environmentObject(Chat.sampleData)
@@ -180,7 +209,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
let ciFeatureContent = CIContent.rcvChatFeature(feature: .fullDelete, enabled: FeatureEnabled(forUser: false, forContact: false), param: nil)
Group{
ChatItemView(
chatInfo: ChatInfo.sampleData.direct,
chat: Chat.sampleData,
chatItem: ChatItem(
chatDir: .directRcv,
meta: CIMeta.getSample(1, .now, "1 skipped message", .rcvRead, itemDeleted: .deleted(deletedTs: .now)),
@@ -191,7 +220,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
revealed: Binding.constant(true)
)
ChatItemView(
chatInfo: ChatInfo.sampleData.direct,
chat: Chat.sampleData,
chatItem: ChatItem(
chatDir: .directRcv,
meta: CIMeta.getSample(1, .now, "1 skipped message", .rcvRead),
@@ -202,7 +231,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
revealed: Binding.constant(true)
)
ChatItemView(
chatInfo: ChatInfo.sampleData.direct,
chat: Chat.sampleData,
chatItem: ChatItem(
chatDir: .directRcv,
meta: CIMeta.getSample(1, .now, "received invitation to join group team as admin", .rcvRead, itemDeleted: .deleted(deletedTs: .now)),
@@ -213,7 +242,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
revealed: Binding.constant(true)
)
ChatItemView(
chatInfo: ChatInfo.sampleData.direct,
chat: Chat.sampleData,
chatItem: ChatItem(
chatDir: .directRcv,
meta: CIMeta.getSample(1, .now, "group event text", .rcvRead, itemDeleted: .deleted(deletedTs: .now)),
@@ -224,7 +253,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
revealed: Binding.constant(true)
)
ChatItemView(
chatInfo: ChatInfo.sampleData.direct,
chat: Chat.sampleData,
chatItem: ChatItem(
chatDir: .directRcv,
meta: CIMeta.getSample(1, .now, ciFeatureContent.text, .rcvRead, itemDeleted: .deleted(deletedTs: .now)),

View File

@@ -21,9 +21,7 @@ struct ChatView: View {
@State private var showChatInfoSheet: Bool = false
@State private var showAddMembersSheet: Bool = false
@State private var composeState = ComposeState()
@State private var deletingItem: ChatItem? = nil
@State private var keyboardVisible = false
@State private var showDeleteMessage = false
@State private var connectionStats: ConnectionStats?
@State private var customUserProfile: Profile?
@State private var connectionCode: String?
@@ -36,7 +34,12 @@ struct ChatView: View {
@State private var searchText: String = ""
@FocusState private var searchFocussed
// opening GroupMemberInfoView on member icon
@State private var selectedMember: GroupMember? = nil
@State private var membersLoaded = false
@State private var selectedMember: GMember? = nil
// opening GroupLinkView on link button (incognito)
@State private var showGroupLinkSheet: Bool = false
@State private var groupLink: String?
@State private var groupLinkMemberRole: GroupMemberRole = .member
var body: some View {
if #available(iOS 16.0, *) {
@@ -64,6 +67,7 @@ struct ChatView: View {
Spacer(minLength: 0)
connectingText()
ComposeView(
chat: chat,
composeState: $composeState,
@@ -90,7 +94,10 @@ struct ChatView: View {
chatModel.chatId = nil
DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
if chatModel.chatId == nil {
chatModel.chatItemStatuses = [:]
chatModel.reversedChatItems = []
chatModel.groupMembers = []
membersLoaded = false
}
}
}
@@ -107,7 +114,7 @@ struct ChatView: View {
connectionStats = stats
customUserProfile = profile
connectionCode = code
if contact.activeConn.connectionCode != ct.activeConn.connectionCode {
if contact.activeConn?.connectionCode != ct.activeConn?.connectionCode {
chat.chatInfo = .direct(contact: ct)
}
}
@@ -128,18 +135,21 @@ struct ChatView: View {
}
} else if case let .group(groupInfo) = cInfo {
Button {
Task {
let groupMembers = await apiListMembers(groupInfo.groupId)
await MainActor.run {
ChatModel.shared.groupMembers = groupMembers
showChatInfoSheet = true
}
}
Task { await loadGroupMembers(groupInfo) { showChatInfoSheet = true } }
} label: {
ChatInfoToolbar(chat: chat)
}
.appSheet(isPresented: $showChatInfoSheet) {
GroupChatInfoView(chat: chat, groupInfo: groupInfo)
GroupChatInfoView(
chat: chat,
groupInfo: Binding(
get: { groupInfo },
set: { gInfo in
chat.chatInfo = .group(groupInfo: gInfo)
chat.created = Date.now
}
)
)
}
}
}
@@ -149,6 +159,7 @@ struct ChatView: View {
HStack {
if contact.allowsFeature(.calls) {
callButton(contact, .audio, imageName: "phone")
.disabled(!contact.ready || !contact.active)
}
Menu {
if contact.allowsFeature(.calls) {
@@ -157,9 +168,11 @@ struct ChatView: View {
} label: {
Label("Video call", systemImage: "video")
}
.disabled(!contact.ready || !contact.active)
}
searchButton()
toggleNtfsButton(chat)
.disabled(!contact.ready || !contact.active)
} label: {
Image(systemName: "ellipsis")
}
@@ -168,9 +181,16 @@ struct ChatView: View {
HStack {
if groupInfo.canAddMembers {
if (chat.chatInfo.incognito) {
Image(systemName: "person.crop.circle.badge.plus")
.foregroundColor(Color(uiColor: .tertiaryLabel))
.onTapGesture { AlertManager.shared.showAlert(cantInviteIncognitoAlert()) }
groupLinkButton()
.appSheet(isPresented: $showGroupLinkSheet) {
GroupLinkView(
groupId: groupInfo.groupId,
groupLink: $groupLink,
groupLinkMemberRole: $groupLinkMemberRole,
showTitle: true,
creatingGroup: false
)
}
} else {
addMembersButton()
.appSheet(isPresented: $showAddMembersSheet) {
@@ -192,6 +212,17 @@ struct ChatView: View {
}
}
private func loadGroupMembers(_ groupInfo: GroupInfo, updateView: @escaping () -> Void = {}) async {
let groupMembers = await apiListMembers(groupInfo.groupId)
await MainActor.run {
if chatModel.chatId == groupInfo.id {
chatModel.groupMembers = groupMembers.map { GMember.init($0) }
membersLoaded = true
updateView()
}
}
}
private func initChatView() {
let cInfo = chat.chatInfo
if case let .direct(contact) = cInfo {
@@ -313,6 +344,20 @@ struct ChatView: View {
}
.scaleEffect(x: 1, y: -1, anchor: .center)
}
@ViewBuilder private func connectingText() -> some View {
if case let .direct(contact) = chat.chatInfo,
!contact.ready,
contact.active,
!contact.nextSendGrpInv {
Text("connecting…")
.font(.caption)
.foregroundColor(.secondary)
.padding(.top)
} else {
EmptyView()
}
}
private func floatingButtons(_ proxy: ScrollViewProxy) -> some View {
let counts = chatModel.unreadChatItemCounts(itemsInView: itemsInView)
@@ -386,19 +431,32 @@ struct ChatView: View {
private func addMembersButton() -> some View {
Button {
if case let .group(gInfo) = chat.chatInfo {
Task {
let groupMembers = await apiListMembers(gInfo.groupId)
await MainActor.run {
ChatModel.shared.groupMembers = groupMembers
showAddMembersSheet = true
}
}
Task { await loadGroupMembers(gInfo) { showAddMembersSheet = true } }
}
} label: {
Image(systemName: "person.crop.circle.badge.plus")
}
}
private func groupLinkButton() -> some View {
Button {
if case let .group(gInfo) = chat.chatInfo {
Task {
do {
if let link = try apiGetGroupLink(gInfo.groupId) {
(groupLink, groupLinkMemberRole) = link
}
} catch let error {
logger.error("ChatView apiGetGroupLink: \(responseError(error))")
}
showGroupLinkSheet = true
}
}
} label: {
Image(systemName: "link.badge.plus")
}
}
private func loadChatItems(_ cInfo: ChatInfo, _ ci: ChatItem, _ proxy: ScrollViewProxy) {
if let firstItem = chatModel.reversedChatItems.last, firstItem.id == ci.id {
if loadingItems || firstPage { return }
@@ -428,73 +486,30 @@ struct ChatView: View {
}
@ViewBuilder private func chatItemView(_ ci: ChatItem, _ maxWidth: CGFloat) -> some View {
if case let .groupRcv(member) = ci.chatDir,
case let .group(groupInfo) = chat.chatInfo {
let (prevItem, nextItem) = chatModel.getChatItemNeighbors(ci)
if ci.memberConnected != nil && nextItem?.memberConnected != nil {
// memberConnected events are aggregated at the last chat item in a row of such events, see ChatItemView
ZStack {} // scroll doesn't work if it's EmptyView()
} else {
if prevItem == nil || showMemberImage(member, prevItem) {
VStack(alignment: .leading, spacing: 4) {
if ci.content.showMemberName {
Text(member.displayName)
.font(.caption)
.foregroundStyle(.secondary)
.padding(.leading, memberImageSize + 14)
.padding(.top, 7)
}
HStack(alignment: .top, spacing: 8) {
ProfileImage(imageStr: member.memberProfile.image)
.frame(width: memberImageSize, height: memberImageSize)
.onTapGesture { selectedMember = member }
.appSheet(item: $selectedMember) { member in
GroupMemberInfoView(groupInfo: groupInfo, member: member, navigation: true)
}
chatItemWithMenu(ci, maxWidth)
}
}
.padding(.top, 5)
.padding(.trailing)
.padding(.leading, 12)
} else {
chatItemWithMenu(ci, maxWidth)
.padding(.top, 5)
.padding(.trailing)
.padding(.leading, memberImageSize + 8 + 12)
}
}
} else {
chatItemWithMenu(ci, maxWidth)
.padding(.horizontal)
.padding(.top, 5)
}
}
private func chatItemWithMenu(_ ci: ChatItem, _ maxWidth: CGFloat) -> some View {
ChatItemWithMenu(
ci: ci,
chat: chat,
chatItem: ci,
maxWidth: maxWidth,
scrollProxy: scrollProxy,
deleteMessage: deleteMessage,
deletingItem: $deletingItem,
composeState: $composeState,
showDeleteMessage: $showDeleteMessage
selectedMember: $selectedMember,
chatView: self
)
.environmentObject(chat)
}
private struct ChatItemWithMenu: View {
@EnvironmentObject var chat: Chat
@EnvironmentObject var m: ChatModel
@Environment(\.colorScheme) var colorScheme
var ci: ChatItem
@ObservedObject var chat: Chat
var chatItem: ChatItem
var maxWidth: CGFloat
var scrollProxy: ScrollViewProxy?
var deleteMessage: (CIDeleteMode) -> Void
@Binding var deletingItem: ChatItem?
@Binding var composeState: ComposeState
@Binding var showDeleteMessage: Bool
@Binding var selectedMember: GMember?
var chatView: ChatView
@State private var deletingItem: ChatItem? = nil
@State private var showDeleteMessage = false
@State private var deletingItems: [Int64] = []
@State private var showDeleteMessages = false
@State private var revealed = false
@State private var showChatItemInfoSheet: Bool = false
@State private var chatItemInfo: ChatItemInfo?
@@ -506,18 +521,114 @@ struct ChatView: View {
@State private var playbackTime: TimeInterval?
var body: some View {
let (currIndex, nextItem) = m.getNextChatItem(chatItem)
let ciCategory = chatItem.mergeCategory
if (ciCategory != nil && ciCategory == nextItem?.mergeCategory) {
// memberConnected events and deleted items are aggregated at the last chat item in a row, see ChatItemView
ZStack {} // scroll doesn't work if it's EmptyView()
} else {
let (prevHidden, prevItem) = m.getPrevShownChatItem(currIndex, ciCategory)
let range = itemsRange(currIndex, prevHidden)
if revealed, let range = range {
let items = Array(zip(Array(range), m.reversedChatItems[range]))
ForEach(items, id: \.1.viewId) { (i, ci) in
let prev = i == prevHidden ? prevItem : m.reversedChatItems[i + 1]
chatItemView(ci, nil, prev)
}
} else {
chatItemView(chatItem, range, prevItem)
}
}
}
@ViewBuilder func chatItemView(_ ci: ChatItem, _ range: ClosedRange<Int>?, _ prevItem: ChatItem?) -> some View {
if case let .groupRcv(member) = ci.chatDir,
case let .group(groupInfo) = chat.chatInfo {
let (prevMember, memCount): (GroupMember?, Int) =
if let range = range {
m.getPrevHiddenMember(member, range)
} else {
(nil, 1)
}
if prevItem == nil || showMemberImage(member, prevItem) || prevMember != nil {
VStack(alignment: .leading, spacing: 4) {
if ci.content.showMemberName {
Text(memberNames(member, prevMember, memCount))
.font(.caption)
.foregroundStyle(.secondary)
.padding(.leading, memberImageSize + 14)
.padding(.top, 7)
}
HStack(alignment: .top, spacing: 8) {
ProfileImage(imageStr: member.memberProfile.image)
.frame(width: memberImageSize, height: memberImageSize)
.onTapGesture {
if chatView.membersLoaded {
selectedMember = m.getGroupMember(member.groupMemberId)
} else {
Task {
await chatView.loadGroupMembers(groupInfo) {
selectedMember = m.getGroupMember(member.groupMemberId)
}
}
}
}
.appSheet(item: $selectedMember) { member in
GroupMemberInfoView(groupInfo: groupInfo, groupMember: member, navigation: true)
}
chatItemWithMenu(ci, range, maxWidth)
}
}
.padding(.top, 5)
.padding(.trailing)
.padding(.leading, 12)
} else {
chatItemWithMenu(ci, range, maxWidth)
.padding(.top, 5)
.padding(.trailing)
.padding(.leading, memberImageSize + 8 + 12)
}
} else {
chatItemWithMenu(ci, range, maxWidth)
.padding(.horizontal)
.padding(.top, 5)
}
}
private func memberNames(_ member: GroupMember, _ prevMember: GroupMember?, _ memCount: Int) -> LocalizedStringKey {
let name = member.displayName
return if let prevName = prevMember?.displayName {
memCount > 2
? "\(name), \(prevName) and \(memCount - 2) members"
: "\(name) and \(prevName)"
} else {
"\(name)"
}
}
@ViewBuilder func chatItemWithMenu(_ ci: ChatItem, _ range: ClosedRange<Int>?, _ maxWidth: CGFloat) -> some View {
let alignment: Alignment = ci.chatDir.sent ? .trailing : .leading
let uiMenu: Binding<UIMenu> = Binding(
get: { UIMenu(title: "", children: menu(live: composeState.liveMessage != nil)) },
get: { UIMenu(title: "", children: menu(ci, range, live: composeState.liveMessage != nil)) },
set: { _ in }
)
VStack(alignment: alignment.horizontal, spacing: 3) {
ChatItemView(chatInfo: chat.chatInfo, chatItem: ci, maxWidth: maxWidth, scrollProxy: scrollProxy, revealed: $revealed, allowMenu: $allowMenu, audioPlayer: $audioPlayer, playbackState: $playbackState, playbackTime: $playbackTime)
.uiKitContextMenu(menu: uiMenu, allowMenu: $allowMenu)
.accessibilityLabel("")
ChatItemView(
chat: chat,
chatItem: ci,
maxWidth: maxWidth,
scrollProxy: chatView.scrollProxy,
revealed: $revealed,
allowMenu: $allowMenu,
audioPlayer: $audioPlayer,
playbackState: $playbackState,
playbackTime: $playbackTime
)
.uiKitContextMenu(menu: uiMenu, allowMenu: $allowMenu)
.accessibilityLabel("")
if ci.content.msgContent != nil && (ci.meta.itemDeleted == nil || revealed) && ci.reactions.count > 0 {
chatItemReactions()
chatItemReactions(ci)
.padding(.bottom, 4)
}
}
@@ -531,6 +642,11 @@ struct ChatView: View {
}
}
}
.confirmationDialog(deleteMessagesTitle, isPresented: $showDeleteMessages, titleVisibility: .visible) {
Button("Delete for me", role: .destructive) {
deleteMessages()
}
}
.frame(maxWidth: maxWidth, maxHeight: .infinity, alignment: alignment)
.frame(minWidth: 0, maxWidth: .infinity, alignment: alignment)
.onDisappear {
@@ -548,7 +664,15 @@ struct ChatView: View {
}
}
private func chatItemReactions() -> some View {
private func showMemberImage(_ member: GroupMember, _ prevItem: ChatItem?) -> Bool {
switch (prevItem?.chatDir) {
case .groupSnd: return true
case let .groupRcv(prevMember): return prevMember.groupMemberId != member.groupMemberId
default: return false
}
}
private func chatItemReactions(_ ci: ChatItem) -> some View {
HStack(spacing: 4) {
ForEach(ci.reactions, id: \.reaction) { r in
let v = HStack(spacing: 4) {
@@ -568,7 +692,7 @@ struct ChatView: View {
if chat.chatInfo.featureEnabled(.reactions) && (ci.allowAddReaction || r.userReacted) {
v.onTapGesture {
setReaction(add: !r.userReacted, reaction: r.reaction)
setReaction(ci, add: !r.userReacted, reaction: r.reaction)
}
} else {
v
@@ -577,10 +701,10 @@ struct ChatView: View {
}
}
private func menu(live: Bool) -> [UIMenuElement] {
private func menu(_ ci: ChatItem, _ range: ClosedRange<Int>?, live: Bool) -> [UIMenuElement] {
var menu: [UIMenuElement] = []
if let mc = ci.content.msgContent, ci.meta.itemDeleted == nil || revealed {
let rs = allReactions()
let rs = allReactions(ci)
if chat.chatInfo.featureEnabled(.reactions) && ci.allowAddReaction,
rs.count > 0 {
var rm: UIMenu
@@ -597,10 +721,10 @@ struct ChatView: View {
menu.append(rm)
}
if ci.meta.itemDeleted == nil && !ci.isLiveDummy && !live {
menu.append(replyUIAction())
menu.append(replyUIAction(ci))
}
menu.append(shareUIAction())
menu.append(copyUIAction())
menu.append(shareUIAction(ci))
menu.append(copyUIAction(ci))
if let fileSource = getLoadedFileSource(ci.file) {
if case .image = ci.content.msgContent, let image = getLoadedImage(ci.file) {
if image.imageData != nil {
@@ -613,9 +737,9 @@ struct ChatView: View {
}
}
if ci.meta.editable && !mc.isVoice && !live {
menu.append(editAction())
menu.append(editAction(ci))
}
menu.append(viewInfoUIAction())
menu.append(viewInfoUIAction(ci))
if revealed {
menu.append(hideUIAction())
}
@@ -625,25 +749,31 @@ struct ChatView: View {
menu.append(cancelFileUIAction(file.fileId, cancelAction))
}
if !live || !ci.meta.isLive {
menu.append(deleteUIAction())
menu.append(deleteUIAction(ci))
}
if let (groupInfo, _) = ci.memberToModerate(chat.chatInfo) {
menu.append(moderateUIAction(groupInfo))
menu.append(moderateUIAction(ci, groupInfo))
}
} else if ci.meta.itemDeleted != nil {
if !ci.isDeletedContent {
if revealed {
menu.append(hideUIAction())
} else if !ci.isDeletedContent {
menu.append(revealUIAction())
} else if range != nil {
menu.append(expandUIAction())
}
menu.append(viewInfoUIAction())
menu.append(deleteUIAction())
menu.append(viewInfoUIAction(ci))
menu.append(deleteUIAction(ci))
} else if ci.isDeletedContent {
menu.append(viewInfoUIAction())
menu.append(deleteUIAction())
menu.append(viewInfoUIAction(ci))
menu.append(deleteUIAction(ci))
} else if ci.mergeCategory != nil && ((range?.count ?? 0) > 1 || revealed) {
menu.append(revealed ? shrinkUIAction() : expandUIAction())
}
return menu
}
private func replyUIAction() -> UIAction {
private func replyUIAction(_ ci: ChatItem) -> UIAction {
UIAction(
title: NSLocalizedString("Reply", comment: "chat item action"),
image: UIImage(systemName: "arrowshape.turn.up.left")
@@ -678,11 +808,11 @@ struct ChatView: View {
)
}
private func allReactions() -> [UIAction] {
private func allReactions(_ ci: ChatItem) -> [UIAction] {
MsgReaction.values.compactMap { r in
ci.reactions.contains(where: { $0.userReacted && $0.reaction == r })
? nil
: UIAction(title: r.text) { _ in setReaction(add: true, reaction: r) }
: UIAction(title: r.text) { _ in setReaction(ci, add: true, reaction: r) }
}
}
@@ -690,7 +820,7 @@ struct ChatView: View {
rs.count > 4 ? 3 : 4
}
private func setReaction(add: Bool, reaction: MsgReaction) {
private func setReaction(_ ci: ChatItem, add: Bool, reaction: MsgReaction) {
Task {
do {
let cInfo = chat.chatInfo
@@ -702,7 +832,7 @@ struct ChatView: View {
reaction: reaction
)
await MainActor.run {
ChatModel.shared.updateChatItem(chat.chatInfo, chatItem)
m.updateChatItem(chat.chatInfo, chatItem)
}
} catch let error {
logger.error("apiChatItemReaction error: \(responseError(error))")
@@ -710,7 +840,7 @@ struct ChatView: View {
}
}
private func shareUIAction() -> UIAction {
private func shareUIAction(_ ci: ChatItem) -> UIAction {
UIAction(
title: NSLocalizedString("Share", comment: "chat item action"),
image: UIImage(systemName: "square.and.arrow.up")
@@ -723,7 +853,7 @@ struct ChatView: View {
}
}
private func copyUIAction() -> UIAction {
private func copyUIAction(_ ci: ChatItem) -> UIAction {
UIAction(
title: NSLocalizedString("Copy", comment: "chat item action"),
image: UIImage(systemName: "doc.on.doc")
@@ -756,7 +886,7 @@ struct ChatView: View {
}
}
private func editAction() -> UIAction {
private func editAction(_ ci: ChatItem) -> UIAction {
UIAction(
title: NSLocalizedString("Edit", comment: "chat item action"),
image: UIImage(systemName: "square.and.pencil")
@@ -767,7 +897,7 @@ struct ChatView: View {
}
}
private func viewInfoUIAction() -> UIAction {
private func viewInfoUIAction(_ ci: ChatItem) -> UIAction {
UIAction(
title: NSLocalizedString("Info", comment: "chat item action"),
image: UIImage(systemName: "info.circle")
@@ -780,10 +910,7 @@ struct ChatView: View {
chatItemInfo = ciInfo
}
if case let .group(gInfo) = chat.chatInfo {
let groupMembers = await apiListMembers(gInfo.groupId)
await MainActor.run {
ChatModel.shared.groupMembers = groupMembers
}
await chatView.loadGroupMembers(gInfo)
}
} catch let error {
logger.error("apiGetChatItemInfo error: \(responseError(error))")
@@ -804,7 +931,7 @@ struct ChatView: View {
message: Text(cancelAction.alert.message),
primaryButton: .destructive(Text(cancelAction.alert.confirm)) {
Task {
if let user = ChatModel.shared.currentUser {
if let user = m.currentUser {
await cancelFile(user: user, fileId: fileId)
}
}
@@ -825,18 +952,45 @@ struct ChatView: View {
}
}
private func deleteUIAction() -> UIAction {
private func deleteUIAction(_ ci: ChatItem) -> UIAction {
UIAction(
title: NSLocalizedString("Delete", comment: "chat item action"),
image: UIImage(systemName: "trash"),
attributes: [.destructive]
) { _ in
showDeleteMessage = true
deletingItem = ci
if !revealed && ci.meta.itemDeleted != nil,
let currIndex = m.getChatItemIndex(ci),
let ciCategory = ci.mergeCategory {
let (prevHidden, _) = m.getPrevShownChatItem(currIndex, ciCategory)
if let range = itemsRange(currIndex, prevHidden) {
var itemIds: [Int64] = []
for i in range {
itemIds.append(m.reversedChatItems[i].id)
}
showDeleteMessages = true
deletingItems = itemIds
} else {
showDeleteMessage = true
deletingItem = ci
}
} else {
showDeleteMessage = true
deletingItem = ci
}
}
}
private func moderateUIAction(_ groupInfo: GroupInfo) -> UIAction {
private func itemsRange(_ currIndex: Int?, _ prevHidden: Int?) -> ClosedRange<Int>? {
if let currIndex = currIndex,
let prevHidden = prevHidden,
prevHidden > currIndex {
currIndex...prevHidden
} else {
nil
}
}
private func moderateUIAction(_ ci: ChatItem, _ groupInfo: GroupInfo) -> UIAction {
UIAction(
title: NSLocalizedString("Moderate", comment: "chat item action"),
image: UIImage(systemName: "flag"),
@@ -868,20 +1022,105 @@ struct ChatView: View {
}
}
}
private func expandUIAction() -> UIAction {
UIAction(
title: NSLocalizedString("Expand", comment: "chat item action"),
image: UIImage(systemName: "arrow.up.and.line.horizontal.and.arrow.down")
) { _ in
withAnimation {
revealed = true
}
}
}
private func shrinkUIAction() -> UIAction {
UIAction(
title: NSLocalizedString("Hide", comment: "chat item action"),
image: UIImage(systemName: "arrow.down.and.line.horizontal.and.arrow.up")
) { _ in
withAnimation {
revealed = false
}
}
}
private var broadcastDeleteButtonText: LocalizedStringKey {
chat.chatInfo.featureEnabled(.fullDelete) ? "Delete for everyone" : "Mark deleted for everyone"
}
}
private func showMemberImage(_ member: GroupMember, _ prevItem: ChatItem?) -> Bool {
switch (prevItem?.chatDir) {
case .groupSnd: return true
case let .groupRcv(prevMember): return prevMember.groupMemberId != member.groupMemberId
default: return false
var deleteMessagesTitle: LocalizedStringKey {
let n = deletingItems.count
return n == 1 ? "Delete message?" : "Delete \(n) messages?"
}
private func deleteMessages() {
let itemIds = deletingItems
if itemIds.count > 0 {
let chatInfo = chat.chatInfo
Task {
var deletedItems: [ChatItem] = []
for itemId in itemIds {
do {
let (di, _) = try await apiDeleteChatItem(
type: chatInfo.chatType,
id: chatInfo.apiId,
itemId: itemId,
mode: .cidmInternal
)
deletedItems.append(di)
} catch {
logger.error("ChatView.deleteMessage error: \(error.localizedDescription)")
}
}
await MainActor.run {
for di in deletedItems {
m.removeChatItem(chatInfo, di)
}
}
}
}
}
private func deleteMessage(_ mode: CIDeleteMode) {
logger.debug("ChatView deleteMessage")
Task {
logger.debug("ChatView deleteMessage: in Task")
do {
if let di = deletingItem {
var deletedItem: ChatItem
var toItem: ChatItem?
if case .cidmBroadcast = mode,
let (groupInfo, groupMember) = di.memberToModerate(chat.chatInfo) {
(deletedItem, toItem) = try await apiDeleteMemberChatItem(
groupId: groupInfo.apiId,
groupMemberId: groupMember.groupMemberId,
itemId: di.id
)
} else {
(deletedItem, toItem) = try await apiDeleteChatItem(
type: chat.chatInfo.chatType,
id: chat.chatInfo.apiId,
itemId: di.id,
mode: mode
)
}
DispatchQueue.main.async {
deletingItem = nil
if let toItem = toItem {
_ = m.upsertChatItem(chat.chatInfo, toItem)
} else {
m.removeChatItem(chat.chatInfo, deletedItem)
}
}
}
} catch {
logger.error("ChatView.deleteMessage error: \(error.localizedDescription)")
}
}
}
}
private func scrollToBottom(_ proxy: ScrollViewProxy) {
if let ci = chatModel.reversedChatItems.first {
withAnimation { proxy.scrollTo(ci.viewId, anchor: .top) }
@@ -893,44 +1132,6 @@ struct ChatView: View {
withAnimation { proxy.scrollTo(ci.viewId, anchor: .top) }
}
}
private func deleteMessage(_ mode: CIDeleteMode) {
logger.debug("ChatView deleteMessage")
Task {
logger.debug("ChatView deleteMessage: in Task")
do {
if let di = deletingItem {
var deletedItem: ChatItem
var toItem: ChatItem?
if case .cidmBroadcast = mode,
let (groupInfo, groupMember) = di.memberToModerate(chat.chatInfo) {
(deletedItem, toItem) = try await apiDeleteMemberChatItem(
groupId: groupInfo.apiId,
groupMemberId: groupMember.groupMemberId,
itemId: di.id
)
} else {
(deletedItem, toItem) = try await apiDeleteChatItem(
type: chat.chatInfo.chatType,
id: chat.chatInfo.apiId,
itemId: di.id,
mode: mode
)
}
DispatchQueue.main.async {
deletingItem = nil
if let toItem = toItem {
_ = chatModel.upsertChatItem(chat.chatInfo, toItem)
} else {
chatModel.removeChatItem(chat.chatInfo, deletedItem)
}
}
}
} catch {
logger.error("ChatView.deleteMessage error: \(error.localizedDescription)")
}
}
}
}
@ViewBuilder func toggleNtfsButton(_ chat: Chat) -> some View {
@@ -947,7 +1148,7 @@ struct ChatView: View {
func toggleNotifications(_ chat: Chat, enableNtfs: Bool) {
var chatSettings = chat.chatInfo.chatSettings ?? ChatSettings.defaults
chatSettings.enableNtfs = enableNtfs
chatSettings.enableNtfs = enableNtfs ? .all : .none
updateChatSettings(chat, chatSettings: chatSettings)
}

View File

@@ -257,6 +257,9 @@ struct ComposeView: View {
var body: some View {
VStack(spacing: 0) {
if chat.chatInfo.contact?.nextSendGrpInv ?? false {
ContextInvitingContactMemberView()
}
contextItemView()
switch (composeState.editing, composeState.preview) {
case (true, .filePreview): EmptyView()
@@ -270,7 +273,7 @@ struct ComposeView: View {
Image(systemName: "paperclip")
.resizable()
}
.disabled(composeState.attachmentDisabled || !chat.userCanSend)
.disabled(composeState.attachmentDisabled || !chat.userCanSend || (chat.chatInfo.contact?.nextSendGrpInv ?? false))
.frame(width: 25, height: 25)
.padding(.bottom, 12)
.padding(.leading, 12)
@@ -298,6 +301,7 @@ struct ComposeView: View {
composeState.liveMessage = nil
chatModel.removeLiveDummy()
},
nextSendGrpInv: chat.chatInfo.contact?.nextSendGrpInv ?? false,
voiceMessageAllowed: chat.chatInfo.featureEnabled(.voice),
showEnableVoiceMessagesAlert: chat.chatInfo.showEnableVoiceMessagesAlert,
startVoiceMessageRecording: {
@@ -588,12 +592,14 @@ struct ComposeView: View {
EmptyView()
case let .quotedItem(chatItem: quotedItem):
ContextItemView(
chat: chat,
contextItem: quotedItem,
contextIcon: "arrowshape.turn.up.left",
cancelContextItem: { composeState = composeState.copy(contextItem: .noContextItem) }
)
case let .editingItem(chatItem: editingItem):
ContextItemView(
chat: chat,
contextItem: editingItem,
contextIcon: "pencil",
cancelContextItem: { clearState() }
@@ -617,7 +623,9 @@ struct ComposeView: View {
if liveMessage != nil { composeState = composeState.copy(liveMessage: nil) }
await sending()
}
if case let .editingItem(ci) = composeState.contextItem {
if chat.chatInfo.contact?.nextSendGrpInv ?? false {
await sendMemberContactInvitation()
} else if case let .editingItem(ci) = composeState.contextItem {
sent = await updateMessage(ci, live: live)
} else if let liveMessage = liveMessage, liveMessage.sentMsg != nil {
sent = await updateMessage(liveMessage.chatItem, live: live)
@@ -669,6 +677,19 @@ struct ComposeView: View {
await MainActor.run { composeState.inProgress = true }
}
func sendMemberContactInvitation() async {
do {
let mc = checkLinkPreview()
let contact = try await apiSendMemberContactInvitation(chat.chatInfo.apiId, mc)
await MainActor.run {
self.chatModel.updateContact(contact)
}
} catch {
logger.error("ChatView.sendMemberContactInvitation error: \(error.localizedDescription)")
AlertManager.shared.showAlertMsg(title: "Error sending member contact invitation", message: "Error: \(responseError(error))")
}
}
func updateMessage(_ ei: ChatItem, live: Bool) async -> ChatItem? {
if let oldMsgContent = ei.content.msgContent {
do {

View File

@@ -0,0 +1,32 @@
//
// ContextInvitingContactMemberView.swift
// SimpleX (iOS)
//
// Created by spaced4ndy on 18.09.2023.
// Copyright © 2023 SimpleX Chat. All rights reserved.
//
import SwiftUI
struct ContextInvitingContactMemberView: View {
@Environment(\.colorScheme) var colorScheme
var body: some View {
HStack {
Image(systemName: "message")
.foregroundColor(.secondary)
Text("Send direct message to connect")
}
.padding(12)
.frame(minHeight: 50)
.frame(maxWidth: .infinity, alignment: .leading)
.background(colorScheme == .light ? sentColorLight : sentColorDark)
.padding(.top, 8)
}
}
struct ContextInvitingContactMemberView_Previews: PreviewProvider {
static var previews: some View {
ContextInvitingContactMemberView()
}
}

View File

@@ -11,6 +11,7 @@ import SimpleXChat
struct ContextItemView: View {
@Environment(\.colorScheme) var colorScheme
@ObservedObject var chat: Chat
let contextItem: ChatItem
let contextIcon: String
let cancelContextItem: () -> Void
@@ -48,6 +49,7 @@ struct ContextItemView: View {
private func msgContentView(lines: Int) -> some View {
MsgContentView(
chat: chat,
text: contextItem.text,
formattedText: contextItem.formattedText
)
@@ -59,6 +61,6 @@ struct ContextItemView: View {
struct ContextItemView_Previews: PreviewProvider {
static var previews: some View {
let contextItem: ChatItem = ChatItem.getSample(1, .directSnd, .now, "hello")
return ContextItemView(contextItem: contextItem, contextIcon: "pencil.circle", cancelContextItem: {})
return ContextItemView(chat: Chat.sampleData, contextItem: contextItem, contextIcon: "pencil.circle", cancelContextItem: {})
}
}

View File

@@ -14,20 +14,28 @@ import PhotosUI
struct NativeTextEditor: UIViewRepresentable {
@Binding var text: String
@Binding var disableEditing: Bool
let height: CGFloat
let font: UIFont
@Binding var height: CGFloat
@Binding var focused: Bool
let alignment: TextAlignment
let onImagesAdded: ([UploadContent]) -> Void
private let minHeight: CGFloat = 37
private let defaultHeight: CGFloat = {
let field = CustomUITextField(height: Binding.constant(0))
field.textContainerInset = UIEdgeInsets(top: 8, left: 5, bottom: 6, right: 4)
return min(max(field.sizeThatFits(CGSizeMake(field.frame.size.width, CGFloat.greatestFiniteMagnitude)).height, 37), 360).rounded(.down)
}()
func makeUIView(context: Context) -> UITextView {
let field = CustomUITextField()
let field = CustomUITextField(height: _height)
field.text = text
field.font = font
field.textAlignment = alignment == .leading ? .left : .right
field.autocapitalizationType = .sentences
field.setOnTextChangedListener { newText, images in
if !disableEditing {
// Speed up the process of updating layout, reduce jumping content on screen
if !isShortEmoji(newText) { updateHeight(field) }
text = newText
} else {
field.text = text
@@ -39,24 +47,72 @@ struct NativeTextEditor: UIViewRepresentable {
field.setOnFocusChangedListener { focused = $0 }
field.delegate = field
field.textContainerInset = UIEdgeInsets(top: 8, left: 5, bottom: 6, right: 4)
updateFont(field)
updateHeight(field)
return field
}
func updateUIView(_ field: UITextView, context: Context) {
field.text = text
field.font = font
field.textAlignment = alignment == .leading ? .left : .right
updateFont(field)
updateHeight(field)
}
private func updateHeight(_ field: UITextView) {
let maxHeight = min(360, field.font!.lineHeight * 12)
// When having emoji in text view and then removing it, sizeThatFits shows previous size (too big for empty text view), so using work around with default size
let newHeight = field.text == ""
? defaultHeight
: min(max(field.sizeThatFits(CGSizeMake(field.frame.size.width, CGFloat.greatestFiniteMagnitude)).height, minHeight), maxHeight).rounded(.down)
if field.frame.size.height != newHeight {
field.frame.size = CGSizeMake(field.frame.size.width, newHeight)
(field as! CustomUITextField).invalidateIntrinsicContentHeight(newHeight)
}
}
private func updateFont(_ field: UITextView) {
field.font = isShortEmoji(field.text)
? (field.text.count < 4 ? largeEmojiUIFont : mediumEmojiUIFont)
: UIFont.preferredFont(forTextStyle: .body)
}
}
private class CustomUITextField: UITextView, UITextViewDelegate {
var height: Binding<CGFloat>
var newHeight: CGFloat = 0
var onTextChanged: (String, [UploadContent]) -> Void = { newText, image in }
var onFocusChanged: (Bool) -> Void = { focused in }
init(height: Binding<CGFloat>) {
self.height = height
super.init(frame: .zero, textContainer: nil)
}
required init?(coder: NSCoder) {
fatalError("Not implemented")
}
// This func here needed because using frame.size.height in intrinsicContentSize while loading a screen with text (for example. when you have a draft),
// produces incorrect height because at that point intrinsicContentSize has old value of frame.size.height even if it was set to new value right before the call
// (who knows why...)
func invalidateIntrinsicContentHeight(_ newHeight: CGFloat) {
self.newHeight = newHeight
invalidateIntrinsicContentSize()
}
override var intrinsicContentSize: CGSize {
if height.wrappedValue != newHeight {
DispatchQueue.main.asyncAfter(deadline: .now(), execute: { self.height.wrappedValue = self.newHeight })
}
return CGSizeMake(0, newHeight)
}
func setOnTextChangedListener(onTextChanged: @escaping (String, [UploadContent]) -> Void) {
self.onTextChanged = onTextChanged
}
func setOnFocusChangedListener(onFocusChanged: @escaping (Bool) -> Void) {
self.onFocusChanged = onFocusChanged
}
@@ -144,14 +200,14 @@ private class CustomUITextField: UITextView, UITextViewDelegate {
struct NativeTextEditor_Previews: PreviewProvider{
static var previews: some View {
return NativeTextEditor(
NativeTextEditor(
text: Binding.constant("Hello, world!"),
disableEditing: Binding.constant(false),
height: 100,
font: UIFont.preferredFont(forTextStyle: .body),
height: Binding.constant(100),
focused: Binding.constant(false),
alignment: TextAlignment.leading,
onImagesAdded: { _ in }
)
.fixedSize(horizontal: false, vertical: true)
}
}

View File

@@ -17,6 +17,7 @@ struct SendMessageView: View {
var sendLiveMessage: (() async -> Void)? = nil
var updateLiveMessage: (() async -> Void)? = nil
var cancelLiveMessage: (() -> Void)? = nil
var nextSendGrpInv: Bool = false
var showVoiceMessageButton: Bool = true
var voiceMessageAllowed: Bool = true
var showEnableVoiceMessagesAlert: ChatInfo.ShowEnableVoiceMessagesAlert = .other
@@ -31,15 +32,12 @@ struct SendMessageView: View {
var sendButtonColor = Color.accentColor
@State private var teHeight: CGFloat = 42
@State private var teFont: Font = .body
@State private var teUiFont: UIFont = UIFont.preferredFont(forTextStyle: .body)
@State private var sendButtonSize: CGFloat = 29
@State private var sendButtonOpacity: CGFloat = 1
@State private var showCustomDisappearingMessageDialogue = false
@State private var showCustomTimePicker = false
@State private var selectedDisappearingMessageTime: Int? = customDisappearingMessageTimeDefault.get()
@State private var progressByTimeout = false
var maxHeight: CGFloat = 360
var minHeight: CGFloat = 37
@AppStorage(DEFAULT_LIVE_MESSAGE_ALERT_SHOWN) private var liveMessageAlertShown = false
var body: some View {
@@ -56,30 +54,16 @@ struct SendMessageView: View {
.frame(maxWidth: .infinity)
} else {
let alignment: TextAlignment = isRightToLeft(composeState.message) ? .trailing : .leading
Text(composeState.message)
.lineLimit(10)
.font(teFont)
.multilineTextAlignment(alignment)
// put text on top (after NativeTextEditor) and set color to precisely align it on changes
// .foregroundColor(.red)
.foregroundColor(.clear)
.padding(.horizontal, 10)
.padding(.top, 8)
.padding(.bottom, 6)
.matchedGeometryEffect(id: "te", in: namespace)
.background(GeometryReader(content: updateHeight))
NativeTextEditor(
text: $composeState.message,
disableEditing: $composeState.inProgress,
height: teHeight,
font: teUiFont,
height: $teHeight,
focused: $keyboardVisible,
alignment: alignment,
onImagesAdded: onMediaAdded
)
.allowsTightening(false)
.frame(height: teHeight)
.fixedSize(horizontal: false, vertical: true)
}
}
@@ -99,11 +83,13 @@ struct SendMessageView: View {
.frame(height: teHeight, alignment: .bottom)
}
}
.padding(.vertical, 1)
.overlay(
RoundedRectangle(cornerSize: CGSize(width: 20, height: 20))
.strokeBorder(.secondary, lineWidth: 0.3, antialiased: true)
.frame(height: teHeight)
)
}
.onChange(of: composeState.message, perform: { text in updateFont(text) })
.onChange(of: composeState.inProgress) { inProgress in
if inProgress {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
@@ -118,7 +104,9 @@ struct SendMessageView: View {
@ViewBuilder private func composeActionButtons() -> some View {
let vmrs = composeState.voiceMessageRecordingState
if showVoiceMessageButton
if nextSendGrpInv {
inviteMemberContactButton()
} else if showVoiceMessageButton
&& composeState.message.isEmpty
&& !composeState.editing
&& composeState.liveMessage == nil
@@ -162,6 +150,24 @@ struct SendMessageView: View {
.padding([.top, .trailing], 4)
}
private func inviteMemberContactButton() -> some View {
Button {
sendMessage(nil)
} label: {
Image(systemName: "arrow.up.circle.fill")
.resizable()
.foregroundColor(sendButtonColor)
.frame(width: sendButtonSize, height: sendButtonSize)
.opacity(sendButtonOpacity)
}
.disabled(
!composeState.sendEnabled ||
composeState.inProgress
)
.frame(width: 29, height: 29)
.padding([.bottom, .trailing], 4)
}
private func sendMessageButton() -> some View {
Button {
sendMessage(nil)
@@ -394,16 +400,12 @@ struct SendMessageView: View {
.padding([.bottom, .trailing], 4)
}
private func updateHeight(_ g: GeometryProxy) -> Color {
private func updateFont(_ text: String) {
DispatchQueue.main.async {
teHeight = min(max(g.frame(in: .local).size.height, minHeight), maxHeight)
(teFont, teUiFont) = isShortEmoji(composeState.message)
? composeState.message.count < 4
? (largeEmojiFont, largeEmojiUIFont)
: (mediumEmojiFont, mediumEmojiUIFont)
: (.body, UIFont.preferredFont(forTextStyle: .body))
teFont = isShortEmoji(text)
? (text.count < 4 ? largeEmojiFont : mediumEmojiFont)
: .body
}
return Color.clear
}
}

View File

@@ -144,7 +144,7 @@ struct AddGroupMembersViewCommon: View {
do {
for contactId in selectedContacts {
let member = try await apiAddMember(groupInfo.groupId, contactId, selectedRole)
await MainActor.run { _ = ChatModel.shared.upsertGroupMember(groupInfo, member) }
await MainActor.run { _ = chatModel.upsertGroupMember(groupInfo, member) }
}
addedMembersCb(selectedContacts)
} catch {

View File

@@ -15,7 +15,7 @@ struct GroupChatInfoView: View {
@EnvironmentObject var chatModel: ChatModel
@Environment(\.dismiss) var dismiss: DismissAction
@ObservedObject var chat: Chat
@State var groupInfo: GroupInfo
@Binding var groupInfo: GroupInfo
@ObservedObject private var alertManager = AlertManager.shared
@State private var alert: GroupChatInfoViewAlert? = nil
@State private var groupLink: String?
@@ -35,14 +35,30 @@ struct GroupChatInfoView: View {
case leaveGroupAlert
case cantInviteIncognitoAlert
case largeGroupReceiptsDisabled
case blockMemberAlert(mem: GroupMember)
case unblockMemberAlert(mem: GroupMember)
case removeMemberAlert(mem: GroupMember)
case error(title: LocalizedStringKey, error: LocalizedStringKey)
var id: GroupChatInfoViewAlert { get { self } }
var id: String {
switch self {
case .deleteGroupAlert: return "deleteGroupAlert"
case .clearChatAlert: return "clearChatAlert"
case .leaveGroupAlert: return "leaveGroupAlert"
case .cantInviteIncognitoAlert: return "cantInviteIncognitoAlert"
case .largeGroupReceiptsDisabled: return "largeGroupReceiptsDisabled"
case let .blockMemberAlert(mem): return "blockMemberAlert \(mem.groupMemberId)"
case let .unblockMemberAlert(mem): return "unblockMemberAlert \(mem.groupMemberId)"
case let .removeMemberAlert(mem): return "removeMemberAlert \(mem.groupMemberId)"
case let .error(title, _): return "error \(title)"
}
}
}
var body: some View {
NavigationView {
let members = chatModel.groupMembers
.filter { $0.memberStatus != .memLeft && $0.memberStatus != .memRemoved }
.filter { m in let status = m.wrapped.memberStatus; return status != .memLeft && status != .memRemoved }
.sorted { $0.displayName.lowercased() < $1.displayName.lowercased() }
List {
@@ -57,7 +73,7 @@ struct GroupChatInfoView: View {
addOrEditWelcomeMessage()
}
groupPreferencesButton($groupInfo)
if members.filter({ $0.memberCurrent }).count <= SMALL_GROUPS_RCPS_MEM_LIMIT {
if members.filter({ $0.wrapped.memberCurrent }).count <= SMALL_GROUPS_RCPS_MEM_LIMIT {
sendReceiptsOption()
} else {
sendReceiptsOptionDisabled()
@@ -84,17 +100,17 @@ struct GroupChatInfoView: View {
.padding(.leading, 8)
}
let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase
let filteredMembers = s == "" ? members : members.filter { $0.chatViewName.localizedLowercase.contains(s) }
memberView(groupInfo.membership, user: true)
let filteredMembers = s == "" ? members : members.filter { $0.wrapped.chatViewName.localizedLowercase.contains(s) }
MemberRowView(groupInfo: groupInfo, groupMember: GMember(groupInfo.membership), user: true, alert: $alert)
ForEach(filteredMembers) { member in
ZStack {
NavigationLink {
memberInfoView(member.groupMemberId)
memberInfoView(member)
} label: {
EmptyView()
}
.opacity(0)
memberView(member)
MemberRowView(groupInfo: groupInfo, groupMember: member, alert: $alert)
}
}
}
@@ -126,6 +142,10 @@ struct GroupChatInfoView: View {
case .leaveGroupAlert: return leaveGroupAlert()
case .cantInviteIncognitoAlert: return cantInviteIncognitoAlert()
case .largeGroupReceiptsDisabled: return largeGroupReceiptsDisabledAlert()
case let .blockMemberAlert(mem): return blockMemberAlert(groupInfo, mem)
case let .unblockMemberAlert(mem): return unblockMemberAlert(groupInfo, mem)
case let .removeMemberAlert(mem): return removeMemberAlert(mem)
case let .error(title, error): return Alert(title: Text(title), message: Text(error))
}
}
.onAppear {
@@ -174,7 +194,7 @@ struct GroupChatInfoView: View {
Task {
let groupMembers = await apiListMembers(groupInfo.groupId)
await MainActor.run {
ChatModel.shared.groupMembers = groupMembers
chatModel.groupMembers = groupMembers.map { GMember.init($0) }
}
}
}
@@ -183,51 +203,92 @@ struct GroupChatInfoView: View {
}
}
private func memberView(_ member: GroupMember, user: Bool = false) -> some View {
HStack{
ProfileImage(imageStr: member.image)
.frame(width: 38, height: 38)
.padding(.trailing, 2)
// TODO server connection status
VStack(alignment: .leading) {
let t = Text(member.chatViewName).foregroundColor(member.memberIncognito ? .indigo : .primary)
(member.verified ? memberVerifiedShield + t : t)
.lineLimit(1)
let s = Text(member.memberStatus.shortText)
(user ? Text ("you: ") + s : s)
.lineLimit(1)
.font(.caption)
.foregroundColor(.secondary)
private struct MemberRowView: View {
var groupInfo: GroupInfo
@ObservedObject var groupMember: GMember
var user: Bool = false
@Binding var alert: GroupChatInfoViewAlert?
var body: some View {
let member = groupMember.wrapped
let v = HStack{
ProfileImage(imageStr: member.image)
.frame(width: 38, height: 38)
.padding(.trailing, 2)
// TODO server connection status
VStack(alignment: .leading) {
let t = Text(member.chatViewName).foregroundColor(member.memberIncognito ? .indigo : .primary)
(member.verified ? memberVerifiedShield + t : t)
.lineLimit(1)
let s = Text(member.memberStatus.shortText)
(user ? Text ("you: ") + s : s)
.lineLimit(1)
.font(.caption)
.foregroundColor(.secondary)
}
Spacer()
let role = member.memberRole
if role == .owner || role == .admin {
Text(member.memberRole.text)
.foregroundColor(.secondary)
}
}
Spacer()
let role = member.memberRole
if role == .owner || role == .admin {
Text(member.memberRole.text)
.foregroundColor(.secondary)
if user {
v
} else if member.canBeRemoved(groupInfo: groupInfo) {
removeSwipe(member, blockSwipe(member, v))
} else {
blockSwipe(member, v)
}
}
private func blockSwipe<V: View>(_ member: GroupMember, _ v: V) -> some View {
v.swipeActions(edge: .leading) {
if member.memberSettings.showMessages {
Button {
alert = .blockMemberAlert(mem: member)
} label: {
Label("Block member", systemImage: "hand.raised").foregroundColor(.secondary)
}
} else {
Button {
alert = .unblockMemberAlert(mem: member)
} label: {
Label("Unblock member", systemImage: "hand.raised.slash").foregroundColor(.accentColor)
}
}
}
}
private func removeSwipe<V: View>(_ member: GroupMember, _ v: V) -> some View {
v.swipeActions(edge: .trailing) {
Button(role: .destructive) {
alert = .removeMemberAlert(mem: member)
} label: {
Label("Remove member", systemImage: "trash")
.foregroundColor(Color.red)
}
}
}
}
private var memberVerifiedShield: Text {
(Text(Image(systemName: "checkmark.shield")) + Text(" "))
.font(.caption)
.baselineOffset(2)
.kerning(-2)
.foregroundColor(.secondary)
}
@ViewBuilder private func memberInfoView(_ groupMemberId: Int64?) -> some View {
if let mId = groupMemberId, let member = chatModel.groupMembers.first(where: { $0.groupMemberId == mId }) {
GroupMemberInfoView(groupInfo: groupInfo, member: member)
.navigationBarHidden(false)
}
private func memberInfoView(_ groupMember: GMember) -> some View {
GroupMemberInfoView(groupInfo: groupInfo, groupMember: groupMember)
.navigationBarHidden(false)
}
private func groupLinkButton() -> some View {
NavigationLink {
GroupLinkView(groupId: groupInfo.groupId, groupLink: $groupLink, groupLinkMemberRole: $groupLinkMemberRole)
.navigationBarTitle("Group link")
.navigationBarTitleDisplayMode(.large)
GroupLinkView(
groupId: groupInfo.groupId,
groupLink: $groupLink,
groupLinkMemberRole: $groupLinkMemberRole,
showTitle: false,
creatingGroup: false
)
.navigationBarTitle("Group link")
.navigationBarTitleDisplayMode(.large)
} label: {
if groupLink == nil {
Label("Create group link", systemImage: "link.badge.plus")
@@ -299,9 +360,9 @@ struct GroupChatInfoView: View {
do {
try await apiDeleteChat(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId)
await MainActor.run {
chatModel.removeChat(chat.chatInfo.id)
chatModel.chatId = nil
dismiss()
chatModel.chatId = nil
chatModel.removeChat(chat.chatInfo.id)
}
} catch let error {
logger.error("deleteGroupAlert apiDeleteChat error: \(error.localizedDescription)")
@@ -375,6 +436,28 @@ struct GroupChatInfoView: View {
alert = .largeGroupReceiptsDisabled
}
}
private func removeMemberAlert(_ mem: GroupMember) -> Alert {
Alert(
title: Text("Remove member?"),
message: Text("Member will be removed from group - this cannot be undone!"),
primaryButton: .destructive(Text("Remove")) {
Task {
do {
let updatedMember = try await apiRemoveMember(groupInfo.groupId, mem.groupMemberId)
await MainActor.run {
_ = chatModel.upsertGroupMember(groupInfo, updatedMember)
}
} catch let error {
logger.error("apiRemoveMember error: \(responseError(error))")
let a = getErrorAlert(error, "Error removing member")
alert = .error(title: a.title, error: a.message)
}
}
},
secondaryButton: .cancel()
)
}
}
func groupPreferencesButton(_ groupInfo: Binding<GroupInfo>, _ creatingGroup: Bool = false) -> some View {
@@ -396,6 +479,14 @@ func groupPreferencesButton(_ groupInfo: Binding<GroupInfo>, _ creatingGroup: Bo
}
}
private var memberVerifiedShield: Text {
(Text(Image(systemName: "checkmark.shield")) + Text(" "))
.font(.caption)
.baselineOffset(2)
.kerning(-2)
.foregroundColor(.secondary)
}
func cantInviteIncognitoAlert() -> Alert {
Alert(
title: Text("Can't invite contacts!"),
@@ -412,6 +503,9 @@ func largeGroupReceiptsDisabledAlert() -> Alert {
struct GroupChatInfoView_Previews: PreviewProvider {
static var previews: some View {
GroupChatInfoView(chat: Chat(chatInfo: ChatInfo.sampleData.group, chatItems: []), groupInfo: GroupInfo.sampleData)
GroupChatInfoView(
chat: Chat(chatInfo: ChatInfo.sampleData.group, chatItems: []),
groupInfo: Binding.constant(GroupInfo.sampleData)
)
}
}

View File

@@ -13,6 +13,9 @@ struct GroupLinkView: View {
var groupId: Int64
@Binding var groupLink: String?
@Binding var groupLinkMemberRole: GroupMemberRole
var showTitle: Bool = false
var creatingGroup: Bool = false
var linkCreatedCb: (() -> Void)? = nil
@State private var creatingLink = false
@State private var alert: GroupLinkAlert?
@@ -29,10 +32,35 @@ struct GroupLinkView: View {
}
var body: some View {
if creatingGroup {
NavigationView {
groupLinkView()
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button ("Continue") { linkCreatedCb?() }
}
}
}
} else {
groupLinkView()
}
}
private func groupLinkView() -> some View {
List {
Text("You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it.")
.listRowBackground(Color.clear)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
Group {
if showTitle {
Text("Group link")
.font(.largeTitle)
.bold()
.fixedSize(horizontal: false, vertical: true)
}
Text("You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it.")
}
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
Section {
if let groupLink = groupLink {
Picker("Initial role", selection: $groupLinkMemberRole) {
@@ -41,15 +69,17 @@ struct GroupLinkView: View {
}
}
.frame(height: 36)
QRCode(uri: groupLink)
SimpleXLinkQRCode(uri: groupLink)
Button {
showShareSheet(items: [groupLink])
showShareSheet(items: [simplexChatLink(groupLink)])
} label: {
Label("Share link", systemImage: "square.and.arrow.up")
}
Button(role: .destructive) { alert = .deleteLink } label: {
Label("Delete link", systemImage: "trash")
if !creatingGroup {
Button(role: .destructive) { alert = .deleteLink } label: {
Label("Delete link", systemImage: "trash")
}
}
} else {
Button(action: createGroupLink) {

View File

@@ -12,37 +12,40 @@ import SimpleXChat
struct GroupMemberInfoView: View {
@EnvironmentObject var chatModel: ChatModel
@Environment(\.dismiss) var dismiss: DismissAction
var groupInfo: GroupInfo
@State var member: GroupMember
@State var groupInfo: GroupInfo
@ObservedObject var groupMember: GMember
var navigation: Bool = false
@State private var connectionStats: ConnectionStats? = nil
@State private var connectionCode: String? = nil
@State private var newRole: GroupMemberRole = .member
@State private var alert: GroupMemberInfoViewAlert?
@State private var connectToMemberDialog: Bool = false
@State private var sheet: PlanAndConnectActionSheet?
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
@State private var justOpened = true
@State private var progressIndicator = false
enum GroupMemberInfoViewAlert: Identifiable {
case blockMemberAlert(mem: GroupMember)
case unblockMemberAlert(mem: GroupMember)
case removeMemberAlert(mem: GroupMember)
case changeMemberRoleAlert(mem: GroupMember, role: GroupMemberRole)
case switchAddressAlert
case abortSwitchAddressAlert
case syncConnectionForceAlert
case connRequestSentAlert(type: ConnReqType)
case planAndConnectAlert(alert: PlanAndConnectAlert)
case error(title: LocalizedStringKey, error: LocalizedStringKey)
case other(alert: Alert)
var id: String {
switch self {
case .removeMemberAlert: return "removeMemberAlert"
case let .changeMemberRoleAlert(_, role): return "changeMemberRoleAlert \(role.rawValue)"
case let .blockMemberAlert(mem): return "blockMemberAlert \(mem.groupMemberId)"
case let .unblockMemberAlert(mem): return "unblockMemberAlert \(mem.groupMemberId)"
case let .removeMemberAlert(mem): return "removeMemberAlert \(mem.groupMemberId)"
case let .changeMemberRoleAlert(mem, role): return "changeMemberRoleAlert \(mem.groupMemberId) \(role.rawValue)"
case .switchAddressAlert: return "switchAddressAlert"
case .abortSwitchAddressAlert: return "abortSwitchAddressAlert"
case .syncConnectionForceAlert: return "syncConnectionForceAlert"
case .connRequestSentAlert: return "connRequestSentAlert"
case let .planAndConnectAlert(alert): return "planAndConnectAlert \(alert.id)"
case let .error(title, _): return "error \(title)"
case let .other(alert): return "other \(alert)"
}
}
}
@@ -65,171 +68,181 @@ struct GroupMemberInfoView: View {
}
private func groupMemberInfoView() -> some View {
VStack {
List {
groupMemberInfoHeader(member)
.listRowBackground(Color.clear)
ZStack {
VStack {
let member = groupMember.wrapped
List {
groupMemberInfoHeader(member)
.listRowBackground(Color.clear)
if member.memberActive {
Section {
if let contactId = member.memberContactId {
if let chat = knownDirectChat(contactId) {
if member.memberActive {
Section {
if let contactId = member.memberContactId, let chat = knownDirectChat(contactId) {
knownDirectChatButton(chat)
} else if groupInfo.fullGroupPreferences.directMessages.on {
newDirectChatButton(contactId)
if let contactId = member.memberContactId {
newDirectChatButton(contactId)
} else if member.activeConn?.peerChatVRange.isCompatibleRange(CREATE_MEMBER_CONTACT_VRANGE) ?? false {
createMemberContactButton()
}
}
if let code = connectionCode { verifyCodeButton(code) }
if let connStats = connectionStats,
connStats.ratchetSyncAllowed {
synchronizeConnectionButton()
}
// } else if developerTools {
// synchronizeConnectionButtonForce()
// }
}
if let code = connectionCode { verifyCodeButton(code) }
if let connStats = connectionStats,
connStats.ratchetSyncAllowed {
synchronizeConnectionButton()
}
// } else if developerTools {
// synchronizeConnectionButtonForce()
// }
}
}
if let contactLink = member.contactLink {
Section {
QRCode(uri: contactLink)
Button {
showShareSheet(items: [contactLink])
} label: {
Label("Share address", systemImage: "square.and.arrow.up")
}
if let contactId = member.memberContactId {
if knownDirectChat(contactId) == nil && !groupInfo.fullGroupPreferences.directMessages.on {
if let contactLink = member.contactLink {
Section {
SimpleXLinkQRCode(uri: contactLink)
Button {
showShareSheet(items: [simplexChatLink(contactLink)])
} label: {
Label("Share address", systemImage: "square.and.arrow.up")
}
if let contactId = member.memberContactId {
if knownDirectChat(contactId) == nil && !groupInfo.fullGroupPreferences.directMessages.on {
connectViaAddressButton(contactLink)
}
} else {
connectViaAddressButton(contactLink)
}
} else {
connectViaAddressButton(contactLink)
} header: {
Text("Address")
} footer: {
Text("You can share this address with your contacts to let them connect with **\(member.displayName)**.")
}
} header: {
Text("Address")
} footer: {
Text("You can share this address with your contacts to let them connect with **\(member.displayName)**.")
}
}
Section("Member") {
infoRow("Group", groupInfo.displayName)
Section("Member") {
infoRow("Group", groupInfo.displayName)
if let roles = member.canChangeRoleTo(groupInfo: groupInfo) {
Picker("Change role", selection: $newRole) {
ForEach(roles) { role in
Text(role.text)
if let roles = member.canChangeRoleTo(groupInfo: groupInfo) {
Picker("Change role", selection: $newRole) {
ForEach(roles) { role in
Text(role.text)
}
}
.frame(height: 36)
} else {
infoRow("Role", member.memberRole.text)
}
// TODO invited by - need to get contact by contact id
if let conn = member.activeConn {
let connLevelDesc = conn.connLevel == 0 ? NSLocalizedString("direct", comment: "connection level description") : String.localizedStringWithFormat(NSLocalizedString("indirect (%d)", comment: "connection level description"), conn.connLevel)
infoRow("Connection", connLevelDesc)
}
.frame(height: 36)
} else {
infoRow("Role", member.memberRole.text)
}
// TODO invited by - need to get contact by contact id
if let conn = member.activeConn {
let connLevelDesc = conn.connLevel == 0 ? NSLocalizedString("direct", comment: "connection level description") : String.localizedStringWithFormat(NSLocalizedString("indirect (%d)", comment: "connection level description"), conn.connLevel)
infoRow("Connection", connLevelDesc)
}
}
if let connStats = connectionStats {
Section("Servers") {
// TODO network connection status
Button("Change receiving address") {
alert = .switchAddressAlert
}
.disabled(
connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil }
|| connStats.ratchetSyncSendProhibited
)
if connStats.rcvQueuesInfo.contains(where: { $0.rcvSwitchStatus != nil }) {
Button("Abort changing address") {
alert = .abortSwitchAddressAlert
if let connStats = connectionStats {
Section("Servers") {
// TODO network connection status
Button("Change receiving address") {
alert = .switchAddressAlert
}
.disabled(
connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil && !$0.canAbortSwitch }
connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil }
|| connStats.ratchetSyncSendProhibited
)
if connStats.rcvQueuesInfo.contains(where: { $0.rcvSwitchStatus != nil }) {
Button("Abort changing address") {
alert = .abortSwitchAddressAlert
}
.disabled(
connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil && !$0.canAbortSwitch }
|| connStats.ratchetSyncSendProhibited
)
}
smpServers("Receiving via", connStats.rcvQueuesInfo.map { $0.rcvServer })
smpServers("Sending via", connStats.sndQueuesInfo.map { $0.sndServer })
}
smpServers("Receiving via", connStats.rcvQueuesInfo.map { $0.rcvServer })
smpServers("Sending via", connStats.sndQueuesInfo.map { $0.sndServer })
}
}
if member.canBeRemoved(groupInfo: groupInfo) {
Section {
removeMemberButton(member)
if member.memberSettings.showMessages {
blockMemberButton(member)
} else {
unblockMemberButton(member)
}
if member.canBeRemoved(groupInfo: groupInfo) {
removeMemberButton(member)
}
}
}
if developerTools {
Section("For console") {
infoRow("Local name", member.localDisplayName)
infoRow("Database ID", "\(member.groupMemberId)")
if developerTools {
Section("For console") {
infoRow("Local name", member.localDisplayName)
infoRow("Database ID", "\(member.groupMemberId)")
}
}
}
}
.navigationBarHidden(true)
.onAppear {
if #unavailable(iOS 16) {
// this condition prevents re-setting picker
if !justOpened { return }
.navigationBarHidden(true)
.onAppear {
if #unavailable(iOS 16) {
// 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
}
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)
member = 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 {
alert = .changeMemberRoleAlert(mem: member, role: newRole)
}
}
justOpened = false
}
.onChange(of: newRole) { _ in
if newRole != member.memberRole {
alert = .changeMemberRoleAlert(mem: member, role: newRole)
.onChange(of: member.memberRole) { role in
newRole = role
}
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.alert(item: $alert) { alertItem in
switch(alertItem) {
case let .removeMemberAlert(mem): return removeMemberAlert(mem)
case let .changeMemberRoleAlert(mem, _): return changeMemberRoleAlert(mem)
case .switchAddressAlert: return switchAddressAlert(switchMemberAddress)
case .abortSwitchAddressAlert: return abortSwitchAddressAlert(abortSwitchMemberAddress)
case .syncConnectionForceAlert: return syncConnectionForceAlert({ syncMemberConnection(force: true) })
case let .connRequestSentAlert(type): return connReqSentAlert(type)
case let .error(title, error): return Alert(title: Text(title), message: Text(error))
case let .other(alert): return alert
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.alert(item: $alert) { alertItem in
switch(alertItem) {
case let .blockMemberAlert(mem): return blockMemberAlert(groupInfo, mem)
case let .unblockMemberAlert(mem): return unblockMemberAlert(groupInfo, mem)
case let .removeMemberAlert(mem): return removeMemberAlert(mem)
case let .changeMemberRoleAlert(mem, _): return changeMemberRoleAlert(mem)
case .switchAddressAlert: return switchAddressAlert(switchMemberAddress)
case .abortSwitchAddressAlert: return abortSwitchAddressAlert(abortSwitchMemberAddress)
case .syncConnectionForceAlert: return syncConnectionForceAlert({ syncMemberConnection(force: true) })
case let .planAndConnectAlert(alert): return planAndConnectAlert(alert, dismiss: true)
case let .error(title, error): return Alert(title: Text(title), message: Text(error))
}
}
.actionSheet(item: $sheet) { s in planAndConnectActionSheet(s, dismiss: true) }
if progressIndicator {
ProgressView().scaleEffect(2)
}
}
}
func connectViaAddressButton(_ contactLink: String) -> some View {
Button {
connectToMemberDialog = true
planAndConnect(
contactLink,
showAlert: { alert = .planAndConnectAlert(alert: $0) },
showActionSheet: { sheet = $0 },
dismiss: true,
incognito: nil
)
} label: {
Label("Connect", systemImage: "link")
}
.confirmationDialog("Connect directly", isPresented: $connectToMemberDialog, titleVisibility: .visible) {
Button("Use current profile") { connectViaAddress(incognito: false, contactLink: contactLink) }
Button("Use new incognito profile") { connectViaAddress(incognito: true, contactLink: contactLink) }
}
}
func connectViaAddress(incognito: Bool, contactLink: String) {
Task {
let (connReqType, connectAlert) = await apiConnect_(incognito: incognito, connReq: contactLink)
if let connReqType = connReqType {
alert = .connRequestSentAlert(type: connReqType)
} else if let connectAlert = connectAlert {
alert = .other(alert: connectAlert)
}
}
}
func knownDirectChatButton(_ chat: Chat) -> some View {
@@ -260,6 +273,33 @@ struct GroupMemberInfoView: View {
}
}
func createMemberContactButton() -> some View {
Button {
progressIndicator = true
Task {
do {
let memberContact = try await apiCreateMemberContact(groupInfo.apiId, groupMember.groupMemberId)
await MainActor.run {
progressIndicator = false
chatModel.addChat(Chat(chatInfo: .direct(contact: memberContact)))
dismissAllSheets(animated: true)
chatModel.chatId = memberContact.id
chatModel.setContactNetworkStatus(memberContact, .connected)
}
} catch let error {
logger.error("createMemberContactButton apiCreateMemberContact error: \(responseError(error))")
let a = getErrorAlert(error, "Error creating member contact")
await MainActor.run {
progressIndicator = false
alert = .error(title: a.title, error: a.message)
}
}
}
} label: {
Label("Send direct message", systemImage: "message")
}
}
private func groupMemberInfoHeader(_ mem: GroupMember) -> some View {
VStack {
ProfileImage(imageStr: mem.image, color: Color(uiColor: .tertiarySystemFill))
@@ -296,20 +336,20 @@ struct GroupMemberInfoView: View {
}
private func verifyCodeButton(_ code: String) -> some View {
NavigationLink {
let member = groupMember.wrapped
return NavigationLink {
VerifyCodeView(
displayName: member.displayName,
connectionCode: code,
connectionVerified: member.verified,
verify: { code in
var member = groupMember.wrapped
if let r = apiVerifyGroupMember(member.groupId, member.groupMemberId, connectionCode: code) {
let (verified, existingCode) = r
let connCode = verified ? SecurityCode(securityCode: existingCode, verifiedAt: .now) : nil
connectionCode = existingCode
member.activeConn?.connectionCode = connCode
if let i = chatModel.groupMembers.firstIndex(where: { $0.groupMemberId == member.groupMemberId }) {
chatModel.groupMembers[i].activeConn?.connectionCode = connCode
}
_ = chatModel.upsertGroupMember(groupInfo, member)
return r
}
return nil
@@ -343,12 +383,29 @@ struct GroupMemberInfoView: View {
}
}
private func blockMemberButton(_ mem: GroupMember) -> some View {
Button(role: .destructive) {
alert = .blockMemberAlert(mem: mem)
} label: {
Label("Block member", systemImage: "hand.raised")
.foregroundColor(.red)
}
}
private func unblockMemberButton(_ mem: GroupMember) -> some View {
Button {
alert = .unblockMemberAlert(mem: mem)
} label: {
Label("Unblock member", systemImage: "hand.raised.slash")
}
}
private func removeMemberButton(_ mem: GroupMember) -> some View {
Button(role: .destructive) {
alert = .removeMemberAlert(mem: mem)
} label: {
Label("Remove member", systemImage: "trash")
.foregroundColor(Color.red)
.foregroundColor(.red)
}
}
@@ -384,7 +441,6 @@ struct GroupMemberInfoView: View {
do {
let updatedMember = try await apiMemberRole(groupInfo.groupId, mem.groupMemberId, newRole)
await MainActor.run {
member = updatedMember
_ = chatModel.upsertGroupMember(groupInfo, updatedMember)
}
@@ -405,10 +461,10 @@ struct GroupMemberInfoView: View {
private func switchMemberAddress() {
Task {
do {
let stats = try apiSwitchGroupMember(groupInfo.apiId, member.groupMemberId)
let stats = try apiSwitchGroupMember(groupInfo.apiId, groupMember.groupMemberId)
connectionStats = stats
await MainActor.run {
chatModel.updateGroupMemberConnectionStats(groupInfo, member, stats)
chatModel.updateGroupMemberConnectionStats(groupInfo, groupMember.wrapped, stats)
dismiss()
}
} catch let error {
@@ -424,10 +480,10 @@ struct GroupMemberInfoView: View {
private func abortSwitchMemberAddress() {
Task {
do {
let stats = try apiAbortSwitchGroupMember(groupInfo.apiId, member.groupMemberId)
let stats = try apiAbortSwitchGroupMember(groupInfo.apiId, groupMember.groupMemberId)
connectionStats = stats
await MainActor.run {
chatModel.updateGroupMemberConnectionStats(groupInfo, member, stats)
chatModel.updateGroupMemberConnectionStats(groupInfo, groupMember.wrapped, stats)
}
} catch let error {
logger.error("abortSwitchMemberAddress apiAbortSwitchGroupMember error: \(responseError(error))")
@@ -442,7 +498,7 @@ struct GroupMemberInfoView: View {
private func syncMemberConnection(force: Bool) {
Task {
do {
let (mem, stats) = try apiSyncGroupMemberRatchet(groupInfo.apiId, member.groupMemberId, force)
let (mem, stats) = try apiSyncGroupMemberRatchet(groupInfo.apiId, groupMember.groupMemberId, force)
connectionStats = stats
await MainActor.run {
chatModel.updateGroupMemberConnectionStats(groupInfo, mem, stats)
@@ -459,11 +515,54 @@ struct GroupMemberInfoView: View {
}
}
func blockMemberAlert(_ gInfo: GroupInfo, _ mem: GroupMember) -> Alert {
Alert(
title: Text("Block member?"),
message: Text("All new messages from \(mem.chatViewName) will be hidden!"),
primaryButton: .destructive(Text("Block")) {
toggleShowMemberMessages(gInfo, mem, false)
},
secondaryButton: .cancel()
)
}
func unblockMemberAlert(_ gInfo: GroupInfo, _ mem: GroupMember) -> Alert {
Alert(
title: Text("Unblock member?"),
message: Text("Messages from \(mem.chatViewName) will be shown!"),
primaryButton: .default(Text("Unblock")) {
toggleShowMemberMessages(gInfo, mem, true)
},
secondaryButton: .cancel()
)
}
func toggleShowMemberMessages(_ gInfo: GroupInfo, _ member: GroupMember, _ showMessages: Bool) {
var memberSettings = member.memberSettings
memberSettings.showMessages = showMessages
updateMemberSettings(gInfo, member, memberSettings)
}
func updateMemberSettings(_ gInfo: GroupInfo, _ member: GroupMember, _ memberSettings: GroupMemberSettings) {
Task {
do {
try await apiSetMemberSettings(gInfo.groupId, member.groupMemberId, memberSettings)
await MainActor.run {
var mem = member
mem.memberSettings = memberSettings
_ = ChatModel.shared.upsertGroupMember(gInfo, mem)
}
} catch let error {
logger.error("apiSetMemberSettings error \(responseError(error))")
}
}
}
struct GroupMemberInfoView_Previews: PreviewProvider {
static var previews: some View {
GroupMemberInfoView(
groupInfo: GroupInfo.sampleData,
member: GroupMember.sampleData
groupMember: GMember.sampleData
)
}
}

View File

@@ -27,8 +27,7 @@ struct GroupPreferencesView: View {
featureSection(.directMessages, $preferences.directMessages.enable)
featureSection(.reactions, $preferences.reactions.enable)
featureSection(.voice, $preferences.voice.enable)
// TODO uncomment in 5.3
// featureSection(.files, $preferences.files.enable)
featureSection(.files, $preferences.files.enable)
if groupInfo.canEdit {
Section {

View File

@@ -9,6 +9,18 @@
import SwiftUI
import SimpleXChat
enum GroupProfileAlert: Identifiable {
case saveError(err: String)
case invalidName(validName: String)
var id: String {
switch self {
case let .saveError(err): return "saveError \(err)"
case let .invalidName(validName): return "invalidName \(validName)"
}
}
}
struct GroupProfileView: View {
@EnvironmentObject var chatModel: ChatModel
@Environment(\.dismiss) var dismiss: DismissAction
@@ -18,8 +30,7 @@ struct GroupProfileView: View {
@State private var showImagePicker = false
@State private var showTakePhoto = false
@State private var chosenImage: UIImage? = nil
@State private var showSaveErrorAlert = false
@State private var saveGroupError: String? = nil
@State private var alert: GroupProfileAlert?
@FocusState private var focusDisplayName
var body: some View {
@@ -47,20 +58,29 @@ struct GroupProfileView: View {
.frame(maxWidth: .infinity, alignment: .center)
VStack(alignment: .leading) {
ZStack(alignment: .leading) {
if !validDisplayName(groupProfile.displayName) {
Image(systemName: "exclamationmark.circle")
.foregroundColor(.red)
.padding(.bottom, 10)
ZStack(alignment: .topLeading) {
if !validNewProfileName() {
Button {
alert = .invalidName(validName: mkValidName(groupProfile.displayName))
} label: {
Image(systemName: "exclamationmark.circle").foregroundColor(.red)
}
} else {
Image(systemName: "exclamationmark.circle").foregroundColor(.clear)
}
profileNameTextEdit("Group display name", $groupProfile.displayName)
.focused($focusDisplayName)
}
profileNameTextEdit("Group full name (optional)", $groupProfile.fullName)
.padding(.bottom)
let fullName = groupInfo.groupProfile.fullName
if fullName != "" && fullName != groupProfile.displayName {
profileNameTextEdit("Group full name (optional)", $groupProfile.fullName)
.padding(.bottom)
}
HStack(spacing: 20) {
Button("Cancel") { dismiss() }
Button("Save group profile") { saveProfile() }
.disabled(groupProfile.displayName == "" || !validDisplayName(groupProfile.displayName))
.disabled(!canUpdateProfile())
}
}
.frame(maxWidth: .infinity, minHeight: 120, alignment: .leading)
@@ -99,27 +119,39 @@ struct GroupProfileView: View {
focusDisplayName = true
}
}
.alert(isPresented: $showSaveErrorAlert) {
Alert(
title: Text("Error saving group profile"),
message: Text("\(saveGroupError ?? "Unexpected error")")
)
.alert(item: $alert) { a in
switch a {
case let .saveError(err):
return Alert(
title: Text("Error saving group profile"),
message: Text(err)
)
case let .invalidName(name):
return createInvalidNameAlert(name, $groupProfile.displayName)
}
}
.contentShape(Rectangle())
.onTapGesture { hideKeyboard() }
}
private func canUpdateProfile() -> Bool {
groupProfile.displayName.trimmingCharacters(in: .whitespaces) != "" && validNewProfileName()
}
private func validNewProfileName() -> Bool {
groupProfile.displayName == groupInfo.groupProfile.displayName
|| validDisplayName(groupProfile.displayName.trimmingCharacters(in: .whitespaces))
}
func profileNameTextEdit(_ label: LocalizedStringKey, _ name: Binding<String>) -> some View {
TextField(label, text: name)
.textInputAutocapitalization(.never)
.disableAutocorrection(true)
.padding(.bottom)
.padding(.leading, 28)
.padding(.leading, 32)
}
func saveProfile() {
Task {
do {
groupProfile.displayName = groupProfile.displayName.trimmingCharacters(in: .whitespaces)
let gInfo = try await apiUpdateGroup(groupInfo.groupId, groupProfile)
await MainActor.run {
groupInfo = gInfo
@@ -128,8 +160,7 @@ struct GroupProfileView: View {
}
} catch let error {
let err = responseError(error)
saveGroupError = err
showSaveErrorAlert = true
alert = .saveError(err: err)
logger.error("GroupProfile apiUpdateGroup error: \(err)")
}
}

View File

@@ -32,56 +32,102 @@ struct ChatListNavLink: View {
@State private var showJoinGroupDialog = false
@State private var showContactConnectionInfo = false
@State private var showInvalidJSON = false
@State private var showDeleteContactActionSheet = false
@State private var showConnectContactViaAddressDialog = false
@State private var inProgress = false
@State private var progressByTimeout = false
var body: some View {
switch chat.chatInfo {
case let .direct(contact):
contactNavLink(contact)
case let .group(groupInfo):
groupNavLink(groupInfo)
case let .contactRequest(cReq):
contactRequestNavLink(cReq)
case let .contactConnection(cConn):
contactConnectionNavLink(cConn)
case let .invalidJSON(json):
invalidJSONPreview(json)
Group {
switch chat.chatInfo {
case let .direct(contact):
contactNavLink(contact)
case let .group(groupInfo):
groupNavLink(groupInfo)
case let .contactRequest(cReq):
contactRequestNavLink(cReq)
case let .contactConnection(cConn):
contactConnectionNavLink(cConn)
case let .invalidJSON(json):
invalidJSONPreview(json)
}
}
.onChange(of: inProgress) { inProgress in
if inProgress {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
progressByTimeout = inProgress
}
} else {
progressByTimeout = false
}
}
}
@ViewBuilder private func contactNavLink(_ contact: Contact) -> some View {
let v = NavLinkPlain(
tag: chat.chatInfo.id,
selection: $chatModel.chatId,
label: { ChatPreviewView(chat: chat) },
disabled: !contact.ready
)
.swipeActions(edge: .leading, allowsFullSwipe: true) {
markReadButton()
toggleFavoriteButton()
toggleNtfsButton(chat)
}
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
if !chat.chatItems.isEmpty {
clearChatButton()
}
Button {
AlertManager.shared.showAlert(
contact.ready
? deleteContactAlert(chat.chatInfo)
: deletePendingContactAlert(chat, contact)
Group {
if contact.activeConn == nil && contact.profile.contactLink != nil {
ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false))
.frame(height: rowHeights[dynamicTypeSize])
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button {
showDeleteContactActionSheet = true
} label: {
Label("Delete", systemImage: "trash")
}
.tint(.red)
}
.onTapGesture { showConnectContactViaAddressDialog = true }
.confirmationDialog("Connect with \(contact.chatViewName)", isPresented: $showConnectContactViaAddressDialog, titleVisibility: .visible) {
Button("Use current profile") { connectContactViaAddress_(contact, false) }
Button("Use new incognito profile") { connectContactViaAddress_(contact, true) }
}
} else {
NavLinkPlain(
tag: chat.chatInfo.id,
selection: $chatModel.chatId,
label: { ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false)) }
)
} label: {
Label("Delete", systemImage: "trash")
.swipeActions(edge: .leading, allowsFullSwipe: true) {
markReadButton()
toggleFavoriteButton()
toggleNtfsButton(chat)
}
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
if !chat.chatItems.isEmpty {
clearChatButton()
}
Button {
if contact.ready || !contact.active {
showDeleteContactActionSheet = true
} else {
AlertManager.shared.showAlert(deletePendingContactAlert(chat, contact))
}
} label: {
Label("Delete", systemImage: "trash")
}
.tint(.red)
}
.frame(height: rowHeights[dynamicTypeSize])
}
.tint(.red)
}
.frame(height: rowHeights[dynamicTypeSize])
if contact.ready {
v
} else {
v.onTapGesture {
AlertManager.shared.showAlert(pendingContactAlert(chat, contact))
.actionSheet(isPresented: $showDeleteContactActionSheet) {
if contact.ready && contact.active {
return ActionSheet(
title: Text("Delete contact?\nThis cannot be undone!"),
buttons: [
.destructive(Text("Delete and notify contact")) { Task { await deleteChat(chat, notify: true) } },
.destructive(Text("Delete")) { Task { await deleteChat(chat, notify: false) } },
.cancel()
]
)
} else {
return ActionSheet(
title: Text("Delete contact?\nThis cannot be undone!"),
buttons: [
.destructive(Text("Delete")) { Task { await deleteChat(chat) } },
.cancel()
]
)
}
}
}
@@ -89,7 +135,7 @@ struct ChatListNavLink: View {
@ViewBuilder private func groupNavLink(_ groupInfo: GroupInfo) -> some View {
switch (groupInfo.membership.memberStatus) {
case .memInvited:
ChatPreviewView(chat: chat)
ChatPreviewView(chat: chat, progressByTimeout: $progressByTimeout)
.frame(height: rowHeights[dynamicTypeSize])
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
joinGroupButton()
@@ -100,12 +146,16 @@ struct ChatListNavLink: View {
.onTapGesture { showJoinGroupDialog = true }
.confirmationDialog("Group invitation", isPresented: $showJoinGroupDialog, titleVisibility: .visible) {
Button(chat.chatInfo.incognito ? "Join incognito" : "Join group") {
joinGroup(groupInfo.groupId)
inProgress = true
joinGroup(groupInfo.groupId) {
await MainActor.run { inProgress = false }
}
}
Button("Delete invitation", role: .destructive) { Task { await deleteChat(chat) } }
}
.disabled(inProgress)
case .memAccepted:
ChatPreviewView(chat: chat)
ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false))
.frame(height: rowHeights[dynamicTypeSize])
.onTapGesture {
AlertManager.shared.showAlert(groupInvitationAcceptedAlert())
@@ -122,7 +172,7 @@ struct ChatListNavLink: View {
NavLinkPlain(
tag: chat.chatInfo.id,
selection: $chatModel.chatId,
label: { ChatPreviewView(chat: chat) },
label: { ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false)) },
disabled: !groupInfo.ready
)
.frame(height: rowHeights[dynamicTypeSize])
@@ -147,7 +197,10 @@ struct ChatListNavLink: View {
private func joinGroupButton() -> some View {
Button {
joinGroup(chat.chatInfo.apiId)
inProgress = true
joinGroup(chat.chatInfo.apiId) {
await MainActor.run { inProgress = false }
}
} label: {
Label("Join", systemImage: chat.chatInfo.incognito ? "theatermasks" : "ipad.and.arrow.forward")
}
@@ -278,17 +331,6 @@ struct ChatListNavLink: View {
}
}
private func deleteContactAlert(_ chatInfo: ChatInfo) -> Alert {
Alert(
title: Text("Delete contact?"),
message: Text("Contact and all messages will be deleted - this cannot be undone!"),
primaryButton: .destructive(Text("Delete")) {
Task { await deleteChat(chat) }
},
secondaryButton: .cancel()
)
}
private func deleteGroupAlert(_ groupInfo: GroupInfo) -> Alert {
Alert(
title: Text("Delete group?"),
@@ -390,6 +432,17 @@ struct ChatListNavLink: View {
.environment(\EnvironmentValues.refresh as! WritableKeyPath<EnvironmentValues, RefreshAction?>, nil)
}
}
private func connectContactViaAddress_(_ contact: Contact, _ incognito: Bool) {
Task {
let ok = await connectContactViaAddress(contact.contactId, incognito)
if ok {
await MainActor.run {
chatModel.chatId = contact.id
}
}
}
}
}
func deleteContactConnectionAlert(_ contactConnection: PendingContactConnection, showError: @escaping (ErrorAlert) -> Void, success: @escaping () -> Void = {}) -> Alert {
@@ -418,7 +471,22 @@ func deleteContactConnectionAlert(_ contactConnection: PendingContactConnection,
)
}
func joinGroup(_ groupId: Int64) {
func connectContactViaAddress(_ contactId: Int64, _ incognito: Bool) async -> Bool {
let (contact, alert) = await apiConnectContactViaAddress(incognito: incognito, contactId: contactId)
if let alert = alert {
AlertManager.shared.showAlert(alert)
return false
} else if let contact = contact {
await MainActor.run {
ChatModel.shared.updateContact(contact)
AlertManager.shared.showAlert(connReqSentAlert(.contact))
}
return true
}
return false
}
func joinGroup(_ groupId: Int64, _ onComplete: @escaping () async -> Void) {
Task {
logger.debug("joinGroup")
do {
@@ -433,7 +501,9 @@ func joinGroup(_ groupId: Int64) {
AlertManager.shared.showAlertMsg(title: "No group!", message: "This group no longer exists.")
await deleteGroup()
}
await onComplete()
} catch let error {
await onComplete()
let a = getErrorAlert(error, "Error joining group")
AlertManager.shared.showAlertMsg(title: a.title, message: a.message)
}

View File

@@ -29,7 +29,7 @@ struct ChatListView: View {
ZStack(alignment: .topLeading) {
NavStackCompat(
isActive: Binding(
get: { ChatModel.shared.chatId != nil },
get: { chatModel.chatId != nil },
set: { _ in }
),
destination: chatView
@@ -177,13 +177,6 @@ struct ChatListView: View {
showAddChat = true
}
connectButton("or chat with the developers") {
DispatchQueue.main.async {
UIApplication.shared.open(simplexTeamURL)
}
}
.padding(.top, 10)
Spacer()
Text("You have no chats")
.foregroundColor(.secondary)

View File

@@ -12,6 +12,7 @@ import SimpleXChat
struct ChatPreviewView: View {
@EnvironmentObject var chatModel: ChatModel
@ObservedObject var chat: Chat
@Binding var progressByTimeout: Bool
@Environment(\.colorScheme) var colorScheme
var darkGreen = Color(red: 0, green: 0.5, blue: 0)
@@ -57,19 +58,26 @@ struct ChatPreviewView: View {
}
@ViewBuilder private func chatPreviewImageOverlayIcon() -> some View {
if case let .group(groupInfo) = chat.chatInfo {
switch chat.chatInfo {
case let .direct(contact):
if !contact.active {
inactiveIcon()
} else {
EmptyView()
}
case let .group(groupInfo):
switch (groupInfo.membership.memberStatus) {
case .memLeft: groupInactiveIcon()
case .memRemoved: groupInactiveIcon()
case .memGroupDeleted: groupInactiveIcon()
case .memLeft: inactiveIcon()
case .memRemoved: inactiveIcon()
case .memGroupDeleted: inactiveIcon()
default: EmptyView()
}
} else {
default:
EmptyView()
}
}
@ViewBuilder private func groupInactiveIcon() -> some View {
@ViewBuilder private func inactiveIcon() -> some View {
Image(systemName: "multiply.circle.fill")
.foregroundColor(.secondary.opacity(0.65))
.background(Circle().foregroundColor(Color(uiColor: .systemBackground)))
@@ -80,7 +88,6 @@ struct ChatPreviewView: View {
switch chat.chatInfo {
case let .direct(contact):
previewTitle(contact.verified == true ? verifiedIcon + t : t)
.foregroundColor(chat.chatInfo.ready ? .primary : .secondary)
case let .group(groupInfo):
let v = previewTitle(t)
switch (groupInfo.membership.memberStatus) {
@@ -105,14 +112,17 @@ struct ChatPreviewView: View {
private func chatPreviewLayout(_ text: Text, draft: Bool = false) -> some View {
ZStack(alignment: .topTrailing) {
text
let t = text
.lineLimit(2)
.multilineTextAlignment(.leading)
.frame(maxWidth: .infinity, alignment: .topLeading)
.padding(.leading, 8)
.padding(.trailing, 36)
.privacySensitive(!showChatPreviews && !draft)
.redacted(reason: .privacy)
if !showChatPreviews && !draft {
t.privacySensitive(true).redacted(reason: .privacy)
} else {
t
}
let s = chat.chatStats
if s.unreadCount > 0 || s.unreadChat {
unreadCountText(s.unreadCount)
@@ -180,8 +190,15 @@ struct ChatPreviewView: View {
} else {
switch (chat.chatInfo) {
case let .direct(contact):
if !contact.ready {
chatPreviewInfoText("connecting…")
if contact.activeConn == nil && contact.profile.contactLink != nil {
chatPreviewInfoText("Tap to Connect")
.foregroundColor(.accentColor)
} else if !contact.ready && contact.activeConn != nil {
if contact.nextSendGrpInv {
chatPreviewInfoText("send direct message")
} else if contact.active {
chatPreviewInfoText("connecting…")
}
}
case let .group(groupInfo):
switch (groupInfo.membership.memberStatus) {
@@ -224,16 +241,26 @@ struct ChatPreviewView: View {
@ViewBuilder private func chatStatusImage() -> some View {
switch chat.chatInfo {
case let .direct(contact):
switch (chatModel.contactNetworkStatus(contact)) {
case .connected: incognitoIcon(chat.chatInfo.incognito)
case .error:
Image(systemName: "exclamationmark.circle")
.resizable()
.scaledToFit()
.frame(width: 17, height: 17)
.foregroundColor(.secondary)
default:
if contact.active && contact.activeConn != nil {
switch (chatModel.contactNetworkStatus(contact)) {
case .connected: incognitoIcon(chat.chatInfo.incognito)
case .error:
Image(systemName: "exclamationmark.circle")
.resizable()
.scaledToFit()
.frame(width: 17, height: 17)
.foregroundColor(.secondary)
default:
ProgressView()
}
} else {
incognitoIcon(chat.chatInfo.incognito)
}
case .group:
if progressByTimeout {
ProgressView()
} else {
incognitoIcon(chat.chatInfo.incognito)
}
default:
incognitoIcon(chat.chatInfo.incognito)
@@ -263,30 +290,30 @@ struct ChatPreviewView_Previews: PreviewProvider {
ChatPreviewView(chat: Chat(
chatInfo: ChatInfo.sampleData.direct,
chatItems: []
))
), progressByTimeout: Binding.constant(false))
ChatPreviewView(chat: Chat(
chatInfo: ChatInfo.sampleData.direct,
chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete))]
))
), progressByTimeout: Binding.constant(false))
ChatPreviewView(chat: Chat(
chatInfo: ChatInfo.sampleData.direct,
chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete))],
chatStats: ChatStats(unreadCount: 11, minUnreadItemId: 0)
))
), progressByTimeout: Binding.constant(false))
ChatPreviewView(chat: Chat(
chatInfo: ChatInfo.sampleData.direct,
chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now))]
))
), progressByTimeout: Binding.constant(false))
ChatPreviewView(chat: Chat(
chatInfo: ChatInfo.sampleData.direct,
chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete))],
chatStats: ChatStats(unreadCount: 3, minUnreadItemId: 0)
))
), progressByTimeout: Binding.constant(false))
ChatPreviewView(chat: Chat(
chatInfo: ChatInfo.sampleData.group,
chatItems: [ChatItem.getSample(1, .directSnd, .now, "Lorem ipsum dolor sit amet, d. consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")],
chatStats: ChatStats(unreadCount: 11, minUnreadItemId: 0)
))
), progressByTimeout: Binding.constant(false))
}
.previewLayout(.fixed(width: 360, height: 78))
}

View File

@@ -61,7 +61,7 @@ struct ContactConnectionInfo: View {
if contactConnection.initiated,
let connReqInv = contactConnection.connReqInv {
QRCode(uri: connReqInv)
SimpleXLinkQRCode(uri: simplexChatLink(connReqInv))
incognitoEnabled()
shareLinkButton(connReqInv)
oneTimeLinkLearnMoreButton()
@@ -119,7 +119,7 @@ struct ContactConnectionInfo: View {
if let conn = try await apiSetConnectionAlias(connId: contactConnection.pccConnId, localAlias: localAlias) {
await MainActor.run {
contactConnection = conn
ChatModel.shared.updateContactConnection(conn)
m.updateContactConnection(conn)
dismiss()
}
}

View File

@@ -6,6 +6,7 @@
import Foundation
import SwiftUI
import AVKit
import Combine
struct VideoPlayerView: UIViewRepresentable {
@@ -37,6 +38,14 @@ struct VideoPlayerView: UIViewRepresentable {
player.seek(to: CMTime.zero)
player.play()
}
var played = false
context.coordinator.publisher = player.publisher(for: \.timeControlStatus).sink { status in
if played || status == .playing {
AppDelegate.keepScreenOn(status == .playing)
AudioPlayer.changeAudioSession(status == .playing)
}
played = status == .playing
}
return controller.view
}
@@ -50,12 +59,13 @@ struct VideoPlayerView: UIViewRepresentable {
class Coordinator: NSObject {
var controller: AVPlayerViewController?
var timeObserver: Any? = nil
var publisher: AnyCancellable? = nil
deinit {
print("deinit coordinator of VideoPlayer")
if let timeObserver = timeObserver {
NotificationCenter.default.removeObserver(timeObserver)
}
publisher?.cancel()
}
}
}

View File

@@ -21,7 +21,7 @@ struct AddContactView: View {
List {
Section {
if connReqInvitation != "" {
QRCode(uri: connReqInvitation)
SimpleXLinkQRCode(uri: connReqInvitation)
} else {
ProgressView()
.progressViewStyle(.circular)
@@ -48,7 +48,7 @@ struct AddContactView: View {
let conn = try await apiSetConnectionIncognito(connId: contactConn.pccConnId, incognito: incognito) {
await MainActor.run {
contactConnection = conn
ChatModel.shared.updateContactConnection(conn)
chatModel.updateContactConnection(conn)
}
}
} catch {
@@ -99,7 +99,7 @@ func sharedProfileInfo(_ incognito: Bool) -> Text {
func shareLinkButton(_ connReqInvitation: String) -> some View {
Button {
showShareSheet(items: [connReqInvitation])
showShareSheet(items: [simplexChatLink(connReqInvitation)])
} label: {
settingsRow("square.and.arrow.up") {
Text("Share 1-time link")

View File

@@ -12,27 +12,45 @@ import SimpleXChat
struct AddGroupView: View {
@EnvironmentObject var m: ChatModel
@Environment(\.dismiss) var dismiss: DismissAction
@AppStorage(GROUP_DEFAULT_INCOGNITO, store: groupDefaults) private var incognitoDefault = false
@State private var chat: Chat?
@State private var groupInfo: GroupInfo?
@State private var profile = GroupProfile(displayName: "", fullName: "")
@FocusState private var focusDisplayName
@FocusState private var focusFullName
@State private var showChooseSource = false
@State private var showImagePicker = false
@State private var showTakePhoto = false
@State private var chosenImage: UIImage? = nil
@State private var showInvalidNameAlert = false
@State private var groupLink: String?
@State private var groupLinkMemberRole: GroupMemberRole = .member
var body: some View {
if let chat = chat, let groupInfo = groupInfo {
AddGroupMembersViewCommon(
chat: chat,
groupInfo: groupInfo,
creatingGroup: true,
showFooterCounter: false
) { _ in
dismiss()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
m.chatId = groupInfo.id
if !groupInfo.membership.memberIncognito {
AddGroupMembersViewCommon(
chat: chat,
groupInfo: groupInfo,
creatingGroup: true,
showFooterCounter: false
) { _ in
dismiss()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
m.chatId = groupInfo.id
}
}
} else {
GroupLinkView(
groupId: groupInfo.groupId,
groupLink: $groupLink,
groupLinkMemberRole: $groupLinkMemberRole,
showTitle: true,
creatingGroup: true
) {
dismiss()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
m.chatId = groupInfo.id
}
}
}
} else {
@@ -41,79 +59,62 @@ struct AddGroupView: View {
}
func createGroupView() -> some View {
VStack(alignment: .leading) {
Text("Create secret group")
.font(.largeTitle)
.padding(.vertical, 4)
Text("The group is fully decentralized it is visible only to the members.")
.padding(.bottom, 4)
List {
Group {
Text("Create secret group")
.font(.largeTitle)
.bold()
.fixedSize(horizontal: false, vertical: true)
.padding(.bottom, 24)
.onTapGesture(perform: hideKeyboard)
HStack {
Image(systemName: "info.circle").foregroundColor(.secondary).font(.footnote)
Spacer().frame(width: 8)
Text("Your chat profile will be sent to group members").font(.footnote)
}
.padding(.bottom)
ZStack(alignment: .center) {
ZStack(alignment: .topTrailing) {
profileImageView(profile.image)
if profile.image != nil {
Button {
profile.image = nil
} label: {
Image(systemName: "multiply")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 12)
ZStack(alignment: .center) {
ZStack(alignment: .topTrailing) {
ProfileImage(imageStr: profile.image, color: Color(uiColor: .secondarySystemGroupedBackground))
.aspectRatio(1, contentMode: .fit)
.frame(maxWidth: 128, maxHeight: 128)
if profile.image != nil {
Button {
profile.image = nil
} label: {
Image(systemName: "multiply")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 12)
}
}
}
editImageButton { showChooseSource = true }
.buttonStyle(BorderlessButtonStyle()) // otherwise whole "list row" is clickable
}
editImageButton { showChooseSource = true }
.frame(maxWidth: .infinity, alignment: .center)
}
.frame(maxWidth: .infinity, alignment: .center)
.padding(.bottom, 4)
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
ZStack(alignment: .topLeading) {
if !validDisplayName(profile.displayName) {
Image(systemName: "exclamationmark.circle")
.foregroundColor(.red)
.padding(.top, 4)
Section {
groupNameTextField()
Button(action: createGroup) {
settingsRow("checkmark", color: .accentColor) { Text("Create group") }
}
textField("Group display name", text: $profile.displayName)
.focused($focusDisplayName)
.submitLabel(.next)
.onSubmit {
if canCreateProfile() { focusFullName = true }
else { focusDisplayName = true }
}
}
textField("Group full name (optional)", text: $profile.fullName)
.focused($focusFullName)
.submitLabel(.go)
.onSubmit {
if canCreateProfile() { createGroup() }
else { focusFullName = true }
.disabled(!canCreateProfile())
IncognitoToggle(incognitoEnabled: $incognitoDefault)
} footer: {
VStack(alignment: .leading, spacing: 4) {
sharedGroupProfileInfo(incognitoDefault)
Text("Fully decentralized visible only to members.")
}
Spacer()
Button {
createGroup()
} label: {
Text("Create")
Image(systemName: "greaterthan")
.frame(maxWidth: .infinity, alignment: .leading)
.onTapGesture(perform: hideKeyboard)
}
.disabled(!canCreateProfile())
.frame(maxWidth: .infinity, alignment: .trailing)
}
.onAppear() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
focusDisplayName = true
}
}
.padding()
.confirmationDialog("Group image", isPresented: $showChooseSource, titleVisibility: .visible) {
Button("Take picture") {
showTakePhoto = true
@@ -133,6 +134,9 @@ struct AddGroupView: View {
didSelectItem in showImagePicker = false
}
}
.alert(isPresented: $showInvalidNameAlert) {
createInvalidNameAlert(mkValidName(profile.displayName), $profile.displayName)
}
.onChange(of: chosenImage) { image in
if let image = image {
profile.image = resizeImageToStrSize(cropToSquare(image), maxDataSize: 12500)
@@ -140,26 +144,52 @@ struct AddGroupView: View {
profile.image = nil
}
}
.contentShape(Rectangle())
.onTapGesture { hideKeyboard() }
}
func groupNameTextField() -> some View {
ZStack(alignment: .leading) {
let name = profile.displayName.trimmingCharacters(in: .whitespaces)
if name != mkValidName(name) {
Button {
showInvalidNameAlert = true
} label: {
Image(systemName: "exclamationmark.circle").foregroundColor(.red)
}
} else {
Image(systemName: "pencil").foregroundColor(.secondary)
}
textField("Enter group name…", text: $profile.displayName)
.focused($focusDisplayName)
.submitLabel(.continue)
.onSubmit {
if canCreateProfile() { createGroup() }
}
}
}
func textField(_ placeholder: LocalizedStringKey, text: Binding<String>) -> some View {
TextField(placeholder, text: text)
.textInputAutocapitalization(.never)
.disableAutocorrection(true)
.padding(.leading, 28)
.padding(.bottom)
.padding(.leading, 36)
}
func sharedGroupProfileInfo(_ incognito: Bool) -> Text {
let name = ChatModel.shared.currentUser?.displayName ?? ""
return Text(
incognito
? "A new random profile will be shared."
: "Your profile **\(name)** will be shared."
)
}
func createGroup() {
hideKeyboard()
do {
let gInfo = try apiNewGroup(profile)
profile.displayName = profile.displayName.trimmingCharacters(in: .whitespaces)
let gInfo = try apiNewGroup(incognito: incognitoDefault, groupProfile: profile)
Task {
let groupMembers = await apiListMembers(gInfo.groupId)
await MainActor.run {
ChatModel.shared.groupMembers = groupMembers
m.groupMembers = groupMembers.map { GMember.init($0) }
}
}
let c = Chat(chatInfo: .group(groupInfo: gInfo), chatItems: [])
@@ -180,7 +210,8 @@ struct AddGroupView: View {
}
func canCreateProfile() -> Bool {
profile.displayName != "" && validDisplayName(profile.displayName)
let name = profile.displayName.trimmingCharacters(in: .whitespaces)
return name != "" && validDisplayName(name)
}
}

View File

@@ -58,65 +58,398 @@ struct NewChatButton: View {
}
}
enum ConnReqType: Equatable {
case contact
case invitation
enum PlanAndConnectAlert: Identifiable {
case ownInvitationLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool)
case invitationLinkConnecting(connectionLink: String)
case ownContactAddressConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool)
case contactAddressConnectingConfirmReconnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool)
case groupLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool)
case groupLinkConnectingConfirmReconnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool)
case groupLinkConnecting(connectionLink: String, groupInfo: GroupInfo?)
var id: String {
switch self {
case let .ownInvitationLinkConfirmConnect(connectionLink, _, _): return "ownInvitationLinkConfirmConnect \(connectionLink)"
case let .invitationLinkConnecting(connectionLink): return "invitationLinkConnecting \(connectionLink)"
case let .ownContactAddressConfirmConnect(connectionLink, _, _): return "ownContactAddressConfirmConnect \(connectionLink)"
case let .contactAddressConnectingConfirmReconnect(connectionLink, _, _): return "contactAddressConnectingConfirmReconnect \(connectionLink)"
case let .groupLinkConfirmConnect(connectionLink, _, _): return "groupLinkConfirmConnect \(connectionLink)"
case let .groupLinkConnectingConfirmReconnect(connectionLink, _, _): return "groupLinkConnectingConfirmReconnect \(connectionLink)"
case let .groupLinkConnecting(connectionLink, _): return "groupLinkConnecting \(connectionLink)"
}
}
}
func connectViaLink(_ connectionLink: String, dismiss: DismissAction? = nil, incognito: Bool) {
Task {
if let connReqType = await apiConnect(incognito: incognito, connReq: connectionLink) {
DispatchQueue.main.async {
dismiss?()
AlertManager.shared.showAlert(connReqSentAlert(connReqType))
}
func planAndConnectAlert(_ alert: PlanAndConnectAlert, dismiss: Bool) -> Alert {
switch alert {
case let .ownInvitationLinkConfirmConnect(connectionLink, connectionPlan, incognito):
return Alert(
title: Text("Connect to yourself?"),
message: Text("This is your own one-time link!"),
primaryButton: .destructive(
Text(incognito ? "Connect incognito" : "Connect"),
action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito) }
),
secondaryButton: .cancel()
)
case .invitationLinkConnecting:
return Alert(
title: Text("Already connecting!"),
message: Text("You are already connecting via this one-time link!")
)
case let .ownContactAddressConfirmConnect(connectionLink, connectionPlan, incognito):
return Alert(
title: Text("Connect to yourself?"),
message: Text("This is your own SimpleX address!"),
primaryButton: .destructive(
Text(incognito ? "Connect incognito" : "Connect"),
action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito) }
),
secondaryButton: .cancel()
)
case let .contactAddressConnectingConfirmReconnect(connectionLink, connectionPlan, incognito):
return Alert(
title: Text("Repeat connection request?"),
message: Text("You have already requested connection via this address!"),
primaryButton: .destructive(
Text(incognito ? "Connect incognito" : "Connect"),
action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito) }
),
secondaryButton: .cancel()
)
case let .groupLinkConfirmConnect(connectionLink, connectionPlan, incognito):
return Alert(
title: Text("Join group?"),
message: Text("You will connect to all group members."),
primaryButton: .default(
Text(incognito ? "Join incognito" : "Join"),
action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito) }
),
secondaryButton: .cancel()
)
case let .groupLinkConnectingConfirmReconnect(connectionLink, connectionPlan, incognito):
return Alert(
title: Text("Repeat join request?"),
message: Text("You are already joining the group via this link!"),
primaryButton: .destructive(
Text(incognito ? "Join incognito" : "Join"),
action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito) }
),
secondaryButton: .cancel()
)
case let .groupLinkConnecting(_, groupInfo):
if let groupInfo = groupInfo {
return Alert(
title: Text("Group already exists!"),
message: Text("You are already joining the group \(groupInfo.displayName).")
)
} else {
DispatchQueue.main.async {
dismiss?()
return Alert(
title: Text("Already joining the group!"),
message: Text("You are already joining the group via this link.")
)
}
}
}
enum PlanAndConnectActionSheet: Identifiable {
case askCurrentOrIncognitoProfile(connectionLink: String, connectionPlan: ConnectionPlan?, title: LocalizedStringKey)
case askCurrentOrIncognitoProfileDestructive(connectionLink: String, connectionPlan: ConnectionPlan, title: LocalizedStringKey)
case askCurrentOrIncognitoProfileConnectContactViaAddress(contact: Contact)
case ownGroupLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool?, groupInfo: GroupInfo)
var id: String {
switch self {
case let .askCurrentOrIncognitoProfile(connectionLink, _, _): return "askCurrentOrIncognitoProfile \(connectionLink)"
case let .askCurrentOrIncognitoProfileDestructive(connectionLink, _, _): return "askCurrentOrIncognitoProfileDestructive \(connectionLink)"
case let .askCurrentOrIncognitoProfileConnectContactViaAddress(contact): return "askCurrentOrIncognitoProfileConnectContactViaAddress \(contact.contactId)"
case let .ownGroupLinkConfirmConnect(connectionLink, _, _, _): return "ownGroupLinkConfirmConnect \(connectionLink)"
}
}
}
func planAndConnectActionSheet(_ sheet: PlanAndConnectActionSheet, dismiss: Bool) -> ActionSheet {
switch sheet {
case let .askCurrentOrIncognitoProfile(connectionLink, connectionPlan, title):
return ActionSheet(
title: Text(title),
buttons: [
.default(Text("Use current profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: false) },
.default(Text("Use new incognito profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: true) },
.cancel()
]
)
case let .askCurrentOrIncognitoProfileDestructive(connectionLink, connectionPlan, title):
return ActionSheet(
title: Text(title),
buttons: [
.destructive(Text("Use current profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: false) },
.destructive(Text("Use new incognito profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: true) },
.cancel()
]
)
case let .askCurrentOrIncognitoProfileConnectContactViaAddress(contact):
return ActionSheet(
title: Text("Connect with \(contact.chatViewName)"),
buttons: [
.default(Text("Use current profile")) { connectContactViaAddress_(contact, dismiss: dismiss, incognito: false) },
.default(Text("Use new incognito profile")) { connectContactViaAddress_(contact, dismiss: dismiss, incognito: true) },
.cancel()
]
)
case let .ownGroupLinkConfirmConnect(connectionLink, connectionPlan, incognito, groupInfo):
if let incognito = incognito {
return ActionSheet(
title: Text("Join your group?\nThis is your link for group \(groupInfo.displayName)!"),
buttons: [
.default(Text("Open group")) { openKnownGroup(groupInfo, dismiss: dismiss, showAlreadyExistsAlert: nil) },
.destructive(Text(incognito ? "Join incognito" : "Join with current profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito) },
.cancel()
]
)
} else {
return ActionSheet(
title: Text("Join your group?\nThis is your link for group \(groupInfo.displayName)!"),
buttons: [
.default(Text("Open group")) { openKnownGroup(groupInfo, dismiss: dismiss, showAlreadyExistsAlert: nil) },
.destructive(Text("Use current profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: false) },
.destructive(Text("Use new incognito profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: true) },
.cancel()
]
)
}
}
}
func planAndConnect(
_ connectionLink: String,
showAlert: @escaping (PlanAndConnectAlert) -> Void,
showActionSheet: @escaping (PlanAndConnectActionSheet) -> Void,
dismiss: Bool,
incognito: Bool?
) {
Task {
do {
let connectionPlan = try await apiConnectPlan(connReq: connectionLink)
switch connectionPlan {
case let .invitationLink(ilp):
switch ilp {
case .ok:
logger.debug("planAndConnect, .invitationLink, .ok, incognito=\(incognito?.description ?? "nil")")
if let incognito = incognito {
connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito)
} else {
showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect via one-time link"))
}
case .ownLink:
logger.debug("planAndConnect, .invitationLink, .ownLink, incognito=\(incognito?.description ?? "nil")")
if let incognito = incognito {
showAlert(.ownInvitationLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito))
} else {
showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own one-time link!"))
}
case let .connecting(contact_):
logger.debug("planAndConnect, .invitationLink, .connecting, incognito=\(incognito?.description ?? "nil")")
if let contact = contact_ {
openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyConnectingAlert(contact)) }
} else {
showAlert(.invitationLinkConnecting(connectionLink: connectionLink))
}
case let .known(contact):
logger.debug("planAndConnect, .invitationLink, .known, incognito=\(incognito?.description ?? "nil")")
openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyExistsAlert(contact)) }
}
case let .contactAddress(cap):
switch cap {
case .ok:
logger.debug("planAndConnect, .contactAddress, .ok, incognito=\(incognito?.description ?? "nil")")
if let incognito = incognito {
connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito)
} else {
showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect via contact address"))
}
case .ownLink:
logger.debug("planAndConnect, .contactAddress, .ownLink, incognito=\(incognito?.description ?? "nil")")
if let incognito = incognito {
showAlert(.ownContactAddressConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito))
} else {
showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own SimpleX address!"))
}
case .connectingConfirmReconnect:
logger.debug("planAndConnect, .contactAddress, .connectingConfirmReconnect, incognito=\(incognito?.description ?? "nil")")
if let incognito = incognito {
showAlert(.contactAddressConnectingConfirmReconnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito))
} else {
showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "You have already requested connection!\nRepeat connection request?"))
}
case let .connectingProhibit(contact):
logger.debug("planAndConnect, .contactAddress, .connectingProhibit, incognito=\(incognito?.description ?? "nil")")
openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyConnectingAlert(contact)) }
case let .known(contact):
logger.debug("planAndConnect, .contactAddress, .known, incognito=\(incognito?.description ?? "nil")")
openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyExistsAlert(contact)) }
case let .contactViaAddress(contact):
logger.debug("planAndConnect, .contactAddress, .contactViaAddress, incognito=\(incognito?.description ?? "nil")")
if let incognito = incognito {
connectContactViaAddress_(contact, dismiss: dismiss, incognito: incognito)
} else {
showActionSheet(.askCurrentOrIncognitoProfileConnectContactViaAddress(contact: contact))
}
}
case let .groupLink(glp):
switch glp {
case .ok:
if let incognito = incognito {
showAlert(.groupLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito))
} else {
showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Join group"))
}
case let .ownLink(groupInfo):
logger.debug("planAndConnect, .groupLink, .ownLink, incognito=\(incognito?.description ?? "nil")")
showActionSheet(.ownGroupLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito, groupInfo: groupInfo))
case .connectingConfirmReconnect:
logger.debug("planAndConnect, .groupLink, .connectingConfirmReconnect, incognito=\(incognito?.description ?? "nil")")
if let incognito = incognito {
showAlert(.groupLinkConnectingConfirmReconnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito))
} else {
showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "You are already joining the group!\nRepeat join request?"))
}
case let .connectingProhibit(groupInfo_):
logger.debug("planAndConnect, .groupLink, .connectingProhibit, incognito=\(incognito?.description ?? "nil")")
showAlert(.groupLinkConnecting(connectionLink: connectionLink, groupInfo: groupInfo_))
case let .known(groupInfo):
logger.debug("planAndConnect, .groupLink, .known, incognito=\(incognito?.description ?? "nil")")
openKnownGroup(groupInfo, dismiss: dismiss) { AlertManager.shared.showAlert(groupAlreadyExistsAlert(groupInfo)) }
}
}
} catch {
logger.debug("planAndConnect, plan error")
if let incognito = incognito {
connectViaLink(connectionLink, connectionPlan: nil, dismiss: dismiss, incognito: incognito)
} else {
showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: nil, title: "Connect via link"))
}
}
}
}
struct CReqClientData: Decodable {
var type: String
var groupLinkId: String?
}
func parseLinkQueryData(_ connectionLink: String) -> CReqClientData? {
if let hashIndex = connectionLink.firstIndex(of: "#"),
let urlQuery = URL(string: String(connectionLink[connectionLink.index(after: hashIndex)...])),
let components = URLComponents(url: urlQuery, resolvingAgainstBaseURL: false),
let data = components.queryItems?.first(where: { $0.name == "data" })?.value,
let d = data.data(using: .utf8),
let crData = try? getJSONDecoder().decode(CReqClientData.self, from: d) {
return crData
} else {
return nil
private func connectContactViaAddress_(_ contact: Contact, dismiss: Bool, incognito: Bool) {
Task {
if dismiss {
DispatchQueue.main.async {
dismissAllSheets(animated: true)
}
}
_ = await connectContactViaAddress(contact.contactId, incognito)
}
}
func checkCRDataGroup(_ crData: CReqClientData) -> Bool {
return crData.type == "group" && crData.groupLinkId != nil
private func connectViaLink(_ connectionLink: String, connectionPlan: ConnectionPlan?, dismiss: Bool, incognito: Bool) {
Task {
if let connReqType = await apiConnect(incognito: incognito, connReq: connectionLink) {
let crt: ConnReqType
if let plan = connectionPlan {
crt = planToConnReqType(plan)
} else {
crt = connReqType
}
DispatchQueue.main.async {
if dismiss {
dismissAllSheets(animated: true) {
AlertManager.shared.showAlert(connReqSentAlert(crt))
}
} else {
AlertManager.shared.showAlert(connReqSentAlert(crt))
}
}
} else {
if dismiss {
DispatchQueue.main.async {
dismissAllSheets(animated: true)
}
}
}
}
}
func groupLinkAlert(_ connectionLink: String, incognito: Bool) -> Alert {
return Alert(
title: Text("Connect via group link?"),
message: Text("You will join a group this link refers to and connect to its group members."),
primaryButton: .default(Text(incognito ? "Connect incognito" : "Connect")) {
connectViaLink(connectionLink, incognito: incognito)
},
secondaryButton: .cancel()
func openKnownContact(_ contact: Contact, dismiss: Bool, showAlreadyExistsAlert: (() -> Void)?) {
Task {
let m = ChatModel.shared
if let c = m.getContactChat(contact.contactId) {
DispatchQueue.main.async {
if dismiss {
dismissAllSheets(animated: true) {
m.chatId = c.id
showAlreadyExistsAlert?()
}
} else {
m.chatId = c.id
showAlreadyExistsAlert?()
}
}
}
}
}
func openKnownGroup(_ groupInfo: GroupInfo, dismiss: Bool, showAlreadyExistsAlert: (() -> Void)?) {
Task {
let m = ChatModel.shared
if let g = m.getGroupChat(groupInfo.groupId) {
DispatchQueue.main.async {
if dismiss {
dismissAllSheets(animated: true) {
m.chatId = g.id
showAlreadyExistsAlert?()
}
} else {
m.chatId = g.id
showAlreadyExistsAlert?()
}
}
}
}
}
func contactAlreadyConnectingAlert(_ contact: Contact) -> Alert {
mkAlert(
title: "Contact already exists",
message: "You are already connecting to \(contact.displayName)."
)
}
func groupAlreadyExistsAlert(_ groupInfo: GroupInfo) -> Alert {
mkAlert(
title: "Group already exists",
message: "You are already in group \(groupInfo.displayName)."
)
}
enum ConnReqType: Equatable {
case invitation
case contact
case groupLink
var connReqSentText: LocalizedStringKey {
switch self {
case .invitation: return "You will be connected when your contact's device is online, please wait or check later!"
case .contact: return "You will be connected when your connection request is accepted, please wait or check later!"
case .groupLink: return "You will be connected when group link host's device is online, please wait or check later!"
}
}
}
private func planToConnReqType(_ connectionPlan: ConnectionPlan) -> ConnReqType {
switch connectionPlan {
case .invitationLink: return .invitation
case .contactAddress: return .contact
case .groupLink: return .groupLink
}
}
func connReqSentAlert(_ type: ConnReqType) -> Alert {
return mkAlert(
title: "Connection request sent!",
message: type == .contact
? "You will be connected when your connection request is accepted, please wait or check later!"
: "You will be connected when your contact's device is online, please wait or check later!"
message: type.connReqSentText
)
}

View File

@@ -14,6 +14,8 @@ struct PasteToConnectView: View {
@State private var connectionLink: String = ""
@AppStorage(GROUP_DEFAULT_INCOGNITO, store: groupDefaults) private var incognitoDefault = false
@FocusState private var linkEditorFocused: Bool
@State private var alert: PlanAndConnectAlert?
@State private var sheet: PlanAndConnectActionSheet?
var body: some View {
List {
@@ -52,11 +54,15 @@ struct PasteToConnectView: View {
IncognitoToggle(incognitoEnabled: $incognitoDefault)
} footer: {
sharedProfileInfo(incognitoDefault)
+ Text(String("\n\n"))
+ Text("You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button.")
VStack(alignment: .leading, spacing: 4) {
sharedProfileInfo(incognitoDefault)
Text("You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button.")
}
.frame(maxWidth: .infinity, alignment: .leading)
}
}
.alert(item: $alert) { a in planAndConnectAlert(a, dismiss: true) }
.actionSheet(item: $sheet) { s in planAndConnectActionSheet(s, dismiss: true) }
}
private func linkEditor() -> some View {
@@ -83,13 +89,13 @@ struct PasteToConnectView: View {
private func connect() {
let link = connectionLink.trimmingCharacters(in: .whitespaces)
if let crData = parseLinkQueryData(link),
checkCRDataGroup(crData) {
dismiss()
AlertManager.shared.showAlert(groupLinkAlert(link, incognito: incognitoDefault))
} else {
connectViaLink(link, dismiss: dismiss, incognito: incognitoDefault)
}
planAndConnect(
link,
showAlert: { alert = $0 },
showActionSheet: { sheet = $0 },
dismiss: true,
incognito: incognitoDefault
)
}
}

View File

@@ -28,6 +28,22 @@ struct MutableQRCode: View {
}
}
struct SimpleXLinkQRCode: View {
let uri: String
var withLogo: Bool = true
var tintColor = UIColor(red: 0.023, green: 0.176, blue: 0.337, alpha: 1)
var body: some View {
QRCode(uri: simplexChatLink(uri), withLogo: withLogo, tintColor: tintColor)
}
}
func simplexChatLink(_ uri: String) -> String {
uri.starts(with: "simplex:/")
? uri.replacingOccurrences(of: "simplex:/", with: "https://simplex.chat/")
: uri
}
struct QRCode: View {
let uri: String
var withLogo: Bool = true

View File

@@ -13,6 +13,8 @@ import CodeScanner
struct ScanToConnectView: View {
@Environment(\.dismiss) var dismiss: DismissAction
@AppStorage(GROUP_DEFAULT_INCOGNITO, store: groupDefaults) private var incognitoDefault = false
@State private var alert: PlanAndConnectAlert?
@State private var sheet: PlanAndConnectActionSheet?
var body: some View {
ScrollView {
@@ -36,11 +38,11 @@ struct ScanToConnectView: View {
)
.padding(.top)
Group {
VStack(alignment: .leading, spacing: 4) {
sharedProfileInfo(incognitoDefault)
+ Text(String("\n\n"))
+ Text("If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link.")
Text("If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link.")
}
.frame(maxWidth: .infinity, alignment: .leading)
.font(.footnote)
.foregroundColor(.secondary)
.padding(.horizontal)
@@ -49,18 +51,20 @@ struct ScanToConnectView: View {
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
}
.background(Color(.systemGroupedBackground))
.alert(item: $alert) { a in planAndConnectAlert(a, dismiss: true) }
.actionSheet(item: $sheet) { s in planAndConnectActionSheet(s, dismiss: true) }
}
func processQRCode(_ resp: Result<ScanResult, ScanError>) {
switch resp {
case let .success(r):
if let crData = parseLinkQueryData(r.string),
checkCRDataGroup(crData) {
dismiss()
AlertManager.shared.showAlert(groupLinkAlert(r.string, incognito: incognitoDefault))
} else {
Task { connectViaLink(r.string, dismiss: dismiss, incognito: incognitoDefault) }
}
planAndConnect(
r.string,
showAlert: { alert = $0 },
showActionSheet: { sheet = $0 },
dismiss: true,
incognito: incognitoDefault
)
case let .failure(e):
logger.error("ConnectContactView.processQRCode QR code error: \(e.localizedDescription)")
dismiss()

View File

@@ -9,175 +9,244 @@
import SwiftUI
import SimpleXChat
enum UserProfileAlert: Identifiable {
case duplicateUserError
case createUserError(error: LocalizedStringKey)
case invalidNameError(validName: String)
var id: String {
switch self {
case .duplicateUserError: return "duplicateUserError"
case .createUserError: return "createUserError"
case let .invalidNameError(validName): return "invalidNameError \(validName)"
}
}
}
struct CreateProfile: View {
@Environment(\.dismiss) var dismiss
@State private var displayName: String = ""
@FocusState private var focusDisplayName
@State private var alert: UserProfileAlert?
var body: some View {
List {
Section {
TextField("Enter your name…", text: $displayName)
.focused($focusDisplayName)
Button {
createProfile(displayName, showAlert: { alert = $0 }, dismiss: dismiss)
} label: {
Label("Create profile", systemImage: "checkmark")
}
.disabled(!canCreateProfile(displayName))
} header: {
HStack {
Text("Your profile")
let name = displayName.trimmingCharacters(in: .whitespaces)
let validName = mkValidName(name)
if name != validName {
Spacer()
Image(systemName: "exclamationmark.circle")
.foregroundColor(.red)
.onTapGesture {
alert = .invalidNameError(validName: validName)
}
}
}
.frame(height: 20)
} footer: {
VStack(alignment: .leading, spacing: 8) {
Text("Your profile, contacts and delivered messages are stored on your device.")
Text("The profile is only shared with your contacts.")
}
.frame(maxWidth: .infinity, alignment: .leading)
}
}
.navigationTitle("Create your profile")
.alert(item: $alert) { a in userProfileAlert(a, $displayName) }
.onAppear() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
focusDisplayName = true
}
}
.keyboardPadding()
}
}
struct CreateFirstProfile: View {
@EnvironmentObject var m: ChatModel
@Environment(\.dismiss) var dismiss
@State private var displayName: String = ""
@State private var fullName: String = ""
@FocusState private var focusDisplayName
@FocusState private var focusFullName
@State private var alert: CreateProfileAlert?
private enum CreateProfileAlert: Identifiable {
case duplicateUserError
case createUserError(error: LocalizedStringKey)
var id: String {
switch self {
case .duplicateUserError: return "duplicateUserError"
case .createUserError: return "createUserError"
}
}
}
var body: some View {
VStack(alignment: .leading) {
Text("Create your profile")
.font(.largeTitle)
.bold()
.padding(.bottom, 4)
.frame(maxWidth: .infinity)
Text("Your profile, contacts and delivered messages are stored on your device.")
.padding(.bottom, 4)
Text("The profile is only shared with your contacts.")
.padding(.bottom)
Group {
Text("Create your profile")
.font(.largeTitle)
.bold()
Text("Your profile, contacts and delivered messages are stored on your device.")
.foregroundColor(.secondary)
Text("The profile is only shared with your contacts.")
.foregroundColor(.secondary)
.padding(.bottom)
}
.padding(.bottom)
ZStack(alignment: .topLeading) {
if !validDisplayName(displayName) {
Image(systemName: "exclamationmark.circle")
.foregroundColor(.red)
.padding(.top, 4)
let name = displayName.trimmingCharacters(in: .whitespaces)
let validName = mkValidName(name)
if name != validName {
Button {
showAlert(.invalidNameError(validName: validName))
} label: {
Image(systemName: "exclamationmark.circle").foregroundColor(.red)
}
} else {
Image(systemName: "exclamationmark.circle").foregroundColor(.clear)
}
textField("Display name", text: $displayName)
TextField("Enter your name", text: $displayName)
.focused($focusDisplayName)
.submitLabel(.next)
.onSubmit {
if canCreateProfile() { focusFullName = true }
else { focusDisplayName = true }
}
.padding(.leading, 32)
}
textField("Full name (optional)", text: $fullName)
.focused($focusFullName)
.submitLabel(.go)
.onSubmit {
if canCreateProfile() { createProfile() }
else { focusFullName = true }
}
.padding(.bottom)
Spacer()
HStack {
if m.users.isEmpty {
Button {
hideKeyboard()
withAnimation {
m.onboardingStage = .step1_SimpleXInfo
}
} label: {
HStack {
Image(systemName: "lessthan")
Text("About SimpleX")
}
}
}
Spacer()
HStack {
Button {
createProfile()
} label: {
Text("Create")
Image(systemName: "greaterthan")
}
.disabled(!canCreateProfile())
}
}
onboardingButtons()
}
.onAppear() {
focusDisplayName = true
setLastVersionDefault()
}
.alert(item: $alert) { a in
switch a {
case .duplicateUserError: return duplicateUserAlert
case let .createUserError(err): return creatUserErrorAlert(err)
}
}
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
.keyboardPadding()
}
func textField(_ placeholder: LocalizedStringKey, text: Binding<String>) -> some View {
TextField(placeholder, text: text)
.textInputAutocapitalization(.never)
.disableAutocorrection(true)
.padding(.leading, 28)
.padding(.bottom)
}
func createProfile() {
hideKeyboard()
let profile = Profile(
displayName: displayName,
fullName: fullName
)
do {
m.currentUser = try apiCreateActiveUser(profile)
if m.users.isEmpty {
try startChat()
func onboardingButtons() -> some View {
HStack {
Button {
hideKeyboard()
withAnimation {
onboardingStageDefault.set(.step3_CreateSimpleXAddress)
m.onboardingStage = .step3_CreateSimpleXAddress
m.onboardingStage = .step1_SimpleXInfo
}
} else {
onboardingStageDefault.set(.onboardingComplete)
m.onboardingStage = .onboardingComplete
dismiss()
m.users = try listUsers()
try getUserChatData()
}
} catch let error {
switch error as? ChatResponse {
case .chatCmdError(_, .errorStore(.duplicateName)),
.chatCmdError(_, .error(.userExists)):
if m.currentUser == nil {
AlertManager.shared.showAlert(duplicateUserAlert)
} else {
alert = .duplicateUserError
}
default:
let err: LocalizedStringKey = "Error: \(responseError(error))"
if m.currentUser == nil {
AlertManager.shared.showAlert(creatUserErrorAlert(err))
} else {
alert = .createUserError(error: err)
} label: {
HStack {
Image(systemName: "lessthan")
Text("About SimpleX")
}
}
logger.error("Failed to create user or start chat: \(responseError(error))")
Spacer()
Button {
createProfile(displayName, showAlert: showAlert, dismiss: dismiss)
} label: {
HStack {
Text("Create")
Image(systemName: "greaterthan")
}
}
.disabled(!canCreateProfile(displayName))
}
}
func canCreateProfile() -> Bool {
displayName != "" && validDisplayName(displayName)
}
private var duplicateUserAlert: Alert {
Alert(
title: Text("Duplicate display name!"),
message: Text("You already have a chat profile with the same display name. Please choose another name.")
)
}
private func creatUserErrorAlert(_ err: LocalizedStringKey) -> Alert {
Alert(
title: Text("Error creating profile!"),
message: Text(err)
)
private func showAlert(_ alert: UserProfileAlert) {
AlertManager.shared.showAlert(userProfileAlert(alert, $displayName))
}
}
private func createProfile(_ displayName: String, showAlert: (UserProfileAlert) -> Void, dismiss: DismissAction) {
hideKeyboard()
let profile = Profile(
displayName: displayName.trimmingCharacters(in: .whitespaces),
fullName: ""
)
let m = ChatModel.shared
do {
m.currentUser = try apiCreateActiveUser(profile)
if m.users.isEmpty {
try startChat()
withAnimation {
onboardingStageDefault.set(.step3_CreateSimpleXAddress)
m.onboardingStage = .step3_CreateSimpleXAddress
}
} else {
onboardingStageDefault.set(.onboardingComplete)
m.onboardingStage = .onboardingComplete
dismiss()
m.users = try listUsers()
try getUserChatData()
}
} catch let error {
switch error as? ChatResponse {
case .chatCmdError(_, .errorStore(.duplicateName)),
.chatCmdError(_, .error(.userExists)):
if m.currentUser == nil {
AlertManager.shared.showAlert(duplicateUserAlert)
} else {
showAlert(.duplicateUserError)
}
default:
let err: LocalizedStringKey = "Error: \(responseError(error))"
if m.currentUser == nil {
AlertManager.shared.showAlert(creatUserErrorAlert(err))
} else {
showAlert(.createUserError(error: err))
}
}
logger.error("Failed to create user or start chat: \(responseError(error))")
}
}
private func canCreateProfile(_ displayName: String) -> Bool {
let name = displayName.trimmingCharacters(in: .whitespaces)
return name != "" && mkValidName(name) == name
}
func userProfileAlert(_ alert: UserProfileAlert, _ displayName: Binding<String>) -> Alert {
switch alert {
case .duplicateUserError: return duplicateUserAlert
case let .createUserError(err): return creatUserErrorAlert(err)
case let .invalidNameError(name): return createInvalidNameAlert(name, displayName)
}
}
private var duplicateUserAlert: Alert {
Alert(
title: Text("Duplicate display name!"),
message: Text("You already have a chat profile with the same display name. Please choose another name.")
)
}
private func creatUserErrorAlert(_ err: LocalizedStringKey) -> Alert {
Alert(
title: Text("Error creating profile!"),
message: Text(err)
)
}
func createInvalidNameAlert(_ name: String, _ displayName: Binding<String>) -> Alert {
name == ""
? Alert(title: Text("Invalid name!"))
: Alert(
title: Text("Invalid name!"),
message: Text("Correct name to \(name)?"),
primaryButton: .default(
Text("Ok"),
action: { displayName.wrappedValue = name }
),
secondaryButton: .cancel()
)
}
func validDisplayName(_ name: String) -> Bool {
name.firstIndex(of: " ") == nil && name.first != "@" && name.first != "#"
mkValidName(name.trimmingCharacters(in: .whitespaces)) == name
}
func mkValidName(_ s: String) -> String {
var c = s.cString(using: .utf8)!
return fromCString(chat_valid_name(&c)!)
}
struct CreateProfile_Previews: PreviewProvider {

View File

@@ -31,7 +31,7 @@ struct CreateSimpleXAddress: View {
Spacer()
if let userAddress = m.userAddress {
QRCode(uri: userAddress.connReqContact)
SimpleXLinkQRCode(uri: userAddress.connReqContact)
.frame(maxHeight: g.size.width)
shareQRCodeButton(userAddress)
.frame(maxWidth: .infinity)
@@ -126,7 +126,7 @@ struct CreateSimpleXAddress: View {
private func shareQRCodeButton(_ userAddress: UserContactLink) -> some View {
Button {
showShareSheet(items: [userAddress.connReqContact])
showShareSheet(items: [simplexChatLink(userAddress.connReqContact)])
} label: {
Label("Share", systemImage: "square.and.arrow.up")
}
@@ -194,7 +194,7 @@ struct SendAddressMailView: View {
let messageBody = String(format: NSLocalizedString("""
<p>Hi!</p>
<p><a href="%@">Connect to me via SimpleX Chat</a></p>
""", comment: "email text"), userAddress.connReqContact)
""", comment: "email text"), simplexChatLink(userAddress.connReqContact))
MailView(
isShowing: self.$showMailView,
result: $mailViewResult,

View File

@@ -14,7 +14,7 @@ struct OnboardingView: View {
var body: some View {
switch onboarding {
case .step1_SimpleXInfo: SimpleXInfo(onboarding: true)
case .step2_CreateProfile: CreateProfile()
case .step2_CreateProfile: CreateFirstProfile()
case .step3_CreateSimpleXAddress: CreateSimpleXAddress()
case .step4_SetNotificationsMode: SetNotificationsMode()
case .onboardingComplete: EmptyView()

View File

@@ -251,7 +251,38 @@ private let versionDescriptions: [VersionDescription] = [
description: "- more stable message delivery.\n- a bit better groups.\n- and more!"
),
]
)
),
VersionDescription(
version: "v5.3",
post: URL(string: "https://simplex.chat/blog/20230925-simplex-chat-v5-3-desktop-app-local-file-encryption-directory-service.html"),
features: [
FeatureDescription(
icon: "desktopcomputer",
title: "New desktop app!",
description: "Create new profile in [desktop app](https://simplex.chat/downloads/). 💻"
),
FeatureDescription(
icon: "lock",
title: "Encrypt stored files & media",
description: "App encrypts new local files (except videos)."
),
FeatureDescription(
icon: "magnifyingglass",
title: "Discover and join groups",
description: "- connect to [directory service](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!\n- delivery receipts (up to 20 members).\n- faster and more stable."
),
FeatureDescription(
icon: "theatermasks",
title: "Simplified incognito mode",
description: "Toggle incognito when connecting."
),
FeatureDescription(
icon: "character",
title: "\(4) new interface languages",
description: "Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"
),
]
),
]
private let lastVersion = versionDescriptions.last!.version
@@ -321,12 +352,15 @@ struct WhatsNewView: View {
private func featureDescription(_ icon: String, _ title: LocalizedStringKey, _ description: LocalizedStringKey) -> some View {
VStack(alignment: .leading, spacing: 4) {
HStack(alignment: .center, spacing: 4) {
Image(systemName: icon).foregroundColor(.secondary)
Image(systemName: icon)
.symbolRenderingMode(.monochrome)
.foregroundColor(.secondary)
.frame(minWidth: 30, alignment: .center)
Text(title).font(.title3).bold()
}
Text(description)
.multilineTextAlignment(.leading)
.lineLimit(10)
}
}

View File

@@ -53,7 +53,7 @@ struct AdvancedNetworkSettings: View {
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: [10_000, 20_000, 40_000, 75_000, 100_000], label: secondsLabel)
timeoutSettingPicker("Protocol timeout per KB", selection: $netCfg.tcpTimeoutPerKb, values: [15_000, 30_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)

View File

@@ -66,6 +66,9 @@ struct PrivacySettings: View {
Section {
settingsRow("lock.doc") {
Toggle("Encrypt local files", isOn: $encryptLocalFiles)
.onChange(of: encryptLocalFiles) {
setEncryptLocalFiles($0)
}
}
settingsRow("photo") {
Toggle("Auto-accept images", isOn: $autoAcceptImages)
@@ -90,7 +93,9 @@ struct PrivacySettings: View {
}
settingsRow("link") {
Picker("SimpleX links", selection: $simplexLinkMode) {
ForEach(SimpleXLinkMode.values) { mode in
ForEach(
SimpleXLinkMode.values + (SimpleXLinkMode.values.contains(simplexLinkMode) ? [] : [simplexLinkMode])
) { mode in
Text(mode.text)
}
}
@@ -101,10 +106,6 @@ struct PrivacySettings: View {
}
} header: {
Text("Chats")
} footer: {
if case .browser = simplexLinkMode {
Text("Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red.")
}
}
Section {
@@ -118,7 +119,7 @@ struct PrivacySettings: View {
Text("Send delivery receipts to")
} footer: {
VStack(alignment: .leading) {
Text("These settings are for your current profile **\(ChatModel.shared.currentUser?.displayName ?? "")**.")
Text("These settings are for your current profile **\(m.currentUser?.displayName ?? "")**.")
Text("They can be overridden in contact and group settings.")
}
.frame(maxWidth: .infinity, alignment: .leading)
@@ -183,6 +184,16 @@ struct PrivacySettings: View {
}
}
private func setEncryptLocalFiles(_ enable: Bool) {
do {
try apiSetEncryptLocalFiles(enable)
} catch let error {
let err = responseError(error)
logger.error("apiSetEncryptLocalFiles \(err)")
alert = .error(title: "Error", error: "\(err)")
}
}
private func setOrAskSendReceiptsContacts(_ enable: Bool) {
contactReceiptsOverrides = m.chats.reduce(0) { count, chat in
let sendRcpts = chat.chatInfo.contact?.chatSettings.sendRcpts
@@ -345,7 +356,7 @@ struct SimplexLockView: View {
var id: Self { self }
}
let laDelays: [Int] = [10, 30, 60, 180, 0]
let laDelays: [Int] = [10, 30, 60, 180, 600, 0]
func laDelayText(_ t: Int) -> LocalizedStringKey {
let m = t / 60
@@ -367,6 +378,7 @@ struct SimplexLockView: View {
Text(mode.text)
}
}
.frame(height: 36)
if performLA {
Picker("Lock after", selection: $laLockDelay) {
let delays = laDelays.contains(laLockDelay) ? laDelays : [laLockDelay] + laDelays
@@ -374,6 +386,7 @@ struct SimplexLockView: View {
Text(laDelayText(t))
}
}
.frame(height: 36)
if showChangePassword && laMode == .passcode {
Button("Change passcode") {
changeLAPassword()

View File

@@ -93,7 +93,7 @@ enum SimpleXLinkMode: String, Identifiable {
case full
case browser
static var values: [SimpleXLinkMode] = [.description, .full, .browser]
static var values: [SimpleXLinkMode] = [.description, .full]
public var id: Self { self }
@@ -381,7 +381,9 @@ struct ProfilePreview: View {
Text(profileOf.displayName)
.fontWeight(.bold)
.font(.title2)
Text(profileOf.fullName)
if profileOf.fullName != "" && profileOf.fullName != profileOf.displayName {
Text(profileOf.fullName)
}
}
}
}

View File

@@ -190,7 +190,7 @@ struct UserAddressView: View {
@ViewBuilder private func existingAddressView(_ userAddress: UserContactLink) -> some View {
Section {
MutableQRCode(uri: Binding.constant(userAddress.connReqContact))
MutableQRCode(uri: Binding.constant(simplexChatLink(userAddress.connReqContact)))
shareQRCodeButton(userAddress)
if MFMailComposeViewController.canSendMail() {
shareViaEmailButton(userAddress)
@@ -248,7 +248,7 @@ struct UserAddressView: View {
private func shareQRCodeButton(_ userAddress: UserContactLink) -> some View {
Button {
showShareSheet(items: [userAddress.connReqContact])
showShareSheet(items: [simplexChatLink(userAddress.connReqContact)])
} label: {
settingsRow("square.and.arrow.up") {
Text("Share address")

View File

@@ -17,6 +17,8 @@ struct UserProfile: View {
@State private var showImagePicker = false
@State private var showTakePhoto = false
@State private var chosenImage: UIImage? = nil
@State private var alert: UserProfileAlert?
@FocusState private var focusDisplayName
var body: some View {
let user: User = chatModel.currentUser!
@@ -47,18 +49,27 @@ struct UserProfile: View {
VStack(alignment: .leading) {
ZStack(alignment: .leading) {
if !validDisplayName(profile.displayName) {
Image(systemName: "exclamationmark.circle")
.foregroundColor(.red)
.padding(.bottom, 10)
if !validNewProfileName(user) {
Button {
alert = .invalidNameError(validName: mkValidName(profile.displayName))
} label: {
Image(systemName: "exclamationmark.circle").foregroundColor(.red)
}
} else {
Image(systemName: "exclamationmark.circle").foregroundColor(.clear)
}
profileNameTextEdit("Display name", $profile.displayName)
profileNameTextEdit("Profile name", $profile.displayName)
.focused($focusDisplayName)
}
.padding(.bottom)
if showFullName(user) {
profileNameTextEdit("Full name (optional)", $profile.fullName)
.padding(.bottom)
}
profileNameTextEdit("Full name (optional)", $profile.fullName)
HStack(spacing: 20) {
Button("Cancel") { editProfile = false }
Button("Save (and notify contacts)") { saveProfile() }
.disabled(profile.displayName == "" || !validDisplayName(profile.displayName))
.disabled(!canSaveProfile(user))
}
}
.frame(maxWidth: .infinity, minHeight: 120, alignment: .leading)
@@ -74,11 +85,14 @@ struct UserProfile: View {
.frame(maxWidth: .infinity, alignment: .center)
VStack(alignment: .leading) {
profileNameView("Display name:", user.profile.displayName)
profileNameView("Full name:", user.profile.fullName)
profileNameView("Profile name:", user.profile.displayName)
if showFullName(user) {
profileNameView("Full name:", user.profile.fullName)
}
Button("Edit") {
profile = fromLocalProfile(user.profile)
editProfile = true
focusDisplayName = true
}
}
.frame(maxWidth: .infinity, minHeight: 120, alignment: .leading)
@@ -117,14 +131,12 @@ struct UserProfile: View {
profile.image = nil
}
}
.alert(item: $alert) { a in userProfileAlert(a, $profile.displayName) }
}
func profileNameTextEdit(_ label: LocalizedStringKey, _ name: Binding<String>) -> some View {
TextField(label, text: name)
.textInputAutocapitalization(.never)
.disableAutocorrection(true)
.padding(.bottom)
.padding(.leading, 28)
.padding(.leading, 32)
}
func profileNameView(_ label: LocalizedStringKey, _ name: String) -> some View {
@@ -141,19 +153,34 @@ struct UserProfile: View {
showChooseSource = true
}
private func validNewProfileName(_ user: User) -> Bool {
profile.displayName == user.profile.displayName || validDisplayName(profile.displayName.trimmingCharacters(in: .whitespaces))
}
private func showFullName(_ user: User) -> Bool {
user.profile.fullName != "" && user.profile.fullName != user.profile.displayName
}
private func canSaveProfile(_ user: User) -> Bool {
profile.displayName.trimmingCharacters(in: .whitespaces) != "" && validNewProfileName(user)
}
func saveProfile() {
Task {
do {
profile.displayName = profile.displayName.trimmingCharacters(in: .whitespaces)
if let (newProfile, _) = try await apiUpdateProfile(profile: profile) {
DispatchQueue.main.async {
chatModel.updateCurrentUser(newProfile)
profile = newProfile
}
editProfile = false
} else {
alert = .duplicateUserError
}
} catch {
logger.error("UserProfile apiUpdateProfile error: \(responseError(error))")
}
editProfile = false
}
}
}

View File

@@ -0,0 +1,15 @@
{
"colors" : [
{
"idiom" : "universal",
"locale" : "bg"
}
],
"properties" : {
"localizable" : true
},
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,23 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"red" : "0.000",
"alpha" : "1.000",
"blue" : "1.000",
"green" : "0.533"
}
},
"idiom" : "universal"
}
],
"properties" : {
"localizable" : true
},
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,6 @@
/* Bundle display name */
"CFBundleDisplayName" = "SimpleX NSE";
/* Bundle name */
"CFBundleName" = "SimpleX NSE";
/* Copyright (human-readable) */
"NSHumanReadableCopyright" = "Copyright © 2022 SimpleX Chat. All rights reserved.";

View File

@@ -0,0 +1,30 @@
/* No comment provided by engineer. */
"_italic_" = "\\_italic_";
/* No comment provided by engineer. */
"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact.";
/* No comment provided by engineer. */
"*bold*" = "\\*bold*";
/* No comment provided by engineer. */
"`a + b`" = "\\`a + b`";
/* No comment provided by engineer. */
"~strike~" = "\\~strike~";
/* call status */
"connecting call" = "connecting call…";
/* No comment provided by engineer. */
"Connecting server…" = "Connecting to server…";
/* No comment provided by engineer. */
"Connecting server… (error: %@)" = "Connecting to server… (error: %@)";
/* rcv group event chat item */
"member connected" = "connected";
/* No comment provided by engineer. */
"No group!" = "Group not found!";

View File

@@ -0,0 +1,10 @@
/* Bundle name */
"CFBundleName" = "SimpleX";
/* Privacy - Camera Usage Description */
"NSCameraUsageDescription" = "SimpleX needs camera access to scan QR codes to connect to other users and for video calls.";
/* Privacy - Face ID Usage Description */
"NSFaceIDUsageDescription" = "SimpleX uses Face ID for local authentication";
/* Privacy - Microphone Usage Description */
"NSMicrophoneUsageDescription" = "SimpleX needs microphone access for audio and video calls, and to record voice messages.";
/* Privacy - Photo Library Additions Usage Description */
"NSPhotoLibraryAddUsageDescription" = "SimpleX needs access to Photo Library for saving captured and received media";

View File

@@ -0,0 +1,12 @@
{
"developmentRegion" : "en",
"project" : "SimpleX.xcodeproj",
"targetLocale" : "bg",
"toolInfo" : {
"toolBuildNumber" : "15A240d",
"toolID" : "com.apple.dt.xcode",
"toolName" : "Xcode",
"toolVersion" : "15.0"
},
"version" : "1.0"
}

View File

@@ -2,7 +2,7 @@
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd">
<file original="en.lproj/Localizable.strings" source-language="en" target-language="cs" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A5219j"/>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A240d"/>
</header>
<body>
<trans-unit id="&#10;" xml:space="preserve">
@@ -87,6 +87,10 @@
<target>%@ / %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@ and %@" xml:space="preserve">
<source>%@ and %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@ and %@ connected" xml:space="preserve">
<source>%@ and %@ connected</source>
<target>%@ a %@ připojen</target>
@@ -97,6 +101,10 @@
<target>%1$@ na %2$@:</target>
<note>copied message info, &lt;sender&gt; at &lt;time&gt;</note>
</trans-unit>
<trans-unit id="%@ connected" xml:space="preserve">
<source>%@ connected</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@ is connected!" xml:space="preserve">
<source>%@ is connected!</source>
<target>%@ je připojen!</target>
@@ -122,6 +130,10 @@
<target>%@ se chce připojit!</target>
<note>notification title</note>
</trans-unit>
<trans-unit id="%@, %@ and %lld members" xml:space="preserve">
<source>%@, %@ and %lld members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@, %@ and %lld other members connected" xml:space="preserve">
<source>%@, %@ and %lld other members connected</source>
<target>%@, %@ a %lld ostatní členové připojeni</target>
@@ -187,16 +199,37 @@
<target>%lld soubor(y) s celkovou velikostí %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld group events" xml:space="preserve">
<source>%lld group events</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld members" xml:space="preserve">
<source>%lld members</source>
<target>%lld členové</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld messages blocked" xml:space="preserve">
<source>%lld messages blocked</source>
<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>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld minutes" xml:space="preserve">
<source>%lld minutes</source>
<target>%lld minut</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld new interface languages" xml:space="preserve">
<source>%lld new interface languages</source>
<target>%d nové jazyky rozhraní</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld second(s)" xml:space="preserve">
<source>%lld second(s)</source>
<target>%lld vteřin</target>
@@ -327,6 +360,15 @@
<target>, </target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- connect to [directory service](simplex:/contact#/?v=1-4&amp;smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!&#10;- delivery receipts (up to 20 members).&#10;- faster and more stable." xml:space="preserve">
<source>- connect to [directory service](simplex:/contact#/?v=1-4&amp;smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!
- delivery receipts (up to 20 members).
- faster and more stable.</source>
<target>- připojit k [adresářová služba](simplex:/contact#/?v=1-4&amp;smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.cibule) (BETA)!
- doručenky (až 20 členů).
- Rychlejší a stabilnější.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- more stable message delivery.&#10;- a bit better groups.&#10;- and more!" xml:space="preserve">
<source>- more stable message delivery.
- a bit better groups.
@@ -350,6 +392,10 @@
<target>.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="0 sec" xml:space="preserve">
<source>0 sec</source>
<note>time to disappear</note>
</trans-unit>
<trans-unit id="0s" xml:space="preserve">
<source>0s</source>
<target>0s</target>
@@ -575,6 +621,10 @@
<target>Všechny zprávy budou smazány tuto akci nelze vrátit zpět! Zprávy budou smazány POUZE pro vás.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="All new messages from %@ will be hidden!" xml:space="preserve">
<source>All new messages from %@ will be hidden!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="All your contacts will remain connected." xml:space="preserve">
<source>All your contacts will remain connected.</source>
<target>Všechny vaše kontakty zůstanou připojeny.</target>
@@ -680,6 +730,14 @@
<target>Již připojeno?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Already connecting!" xml:space="preserve">
<source>Already connecting!</source>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Always use relay" xml:space="preserve">
<source>Always use relay</source>
<target>Spojení přes relé</target>
@@ -700,6 +758,11 @@
<target>Sestavení aplikace: %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="App encrypts new local files (except videos)." xml:space="preserve">
<source>App encrypts new local files (except videos).</source>
<target>Aplikace šifruje nové místní soubory (s výjimkou videí).</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="App icon" xml:space="preserve">
<source>App icon</source>
<target>Ikona aplikace</target>
@@ -810,6 +873,18 @@
<target>Lepší zprávy</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block" xml:space="preserve">
<source>Block</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>
</trans-unit>
<trans-unit id="Block member?" xml:space="preserve">
<source>Block member?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Both you and your contact can add message reactions." xml:space="preserve">
<source>Both you and your contact can add message reactions.</source>
<target>Vy i váš kontakt můžete přidávat reakce na zprávy.</target>
@@ -835,6 +910,11 @@
<target>Hlasové zprávy můžete posílat vy i váš kontakt.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" xml:space="preserve">
<source>Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!</source>
<target>Bulharský, finský, thajský a ukrajinský - díky uživatelům a [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." xml:space="preserve">
<source>By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).</source>
<target>Podle chat profilu (výchozí) nebo [podle připojení](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).</target>
@@ -1066,24 +1146,27 @@
<target>Připojit</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Connect directly" xml:space="preserve">
<source>Connect directly</source>
<target>Připojit přímo</target>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect via contact link" xml:space="preserve">
<source>Connect via contact link</source>
<target>Připojit se přes odkaz</target>
<trans-unit id="Connect to yourself?" xml:space="preserve">
<source>Connect to yourself?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect via group link?" xml:space="preserve">
<source>Connect via group link?</source>
<target>Připojit se přes odkaz skupiny?</target>
<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>
<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>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect via link" xml:space="preserve">
@@ -1101,6 +1184,10 @@
<target>Připojit se jednorázovým odkazem</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect with %@" xml:space="preserve">
<source>Connect with %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connecting server…" xml:space="preserve">
<source>Connecting to server…</source>
<target>Připojování k serveru…</target>
@@ -1146,11 +1233,6 @@
<target>Kontakt již existuje</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Contact and all messages will be deleted - this cannot be undone!" xml:space="preserve">
<source>Contact and all messages will be deleted - this cannot be undone!</source>
<target>Kontakt a všechny zprávy budou smazány - nelze to vzít zpět!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Contact hidden:" xml:space="preserve">
<source>Contact hidden:</source>
<target>Skrytý kontakt:</target>
@@ -1201,6 +1283,10 @@
<target>Verze jádra: v%@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Correct name to %@?" xml:space="preserve">
<source>Correct name to %@?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create" xml:space="preserve">
<source>Create</source>
<target>Vytvořit</target>
@@ -1221,6 +1307,10 @@
<target>Vytvořit soubor</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Create group" xml:space="preserve">
<source>Create group</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create group link" xml:space="preserve">
<source>Create group link</source>
<target>Vytvořit odkaz na skupinu</target>
@@ -1231,11 +1321,20 @@
<target>Vytvořit odkaz</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" xml:space="preserve">
<source>Create new profile in [desktop app](https://simplex.chat/downloads/). 💻</source>
<target>Vytvořit nový profil v [desktop app](https://simplex.chat/downloads/). 💻</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create one-time invitation link" xml:space="preserve">
<source>Create one-time invitation link</source>
<target>Vytvořit jednorázovou pozvánku</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create profile" xml:space="preserve">
<source>Create profile</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create queue" xml:space="preserve">
<source>Create queue</source>
<target>Vytvořit frontu</target>
@@ -1394,6 +1493,10 @@
<target>Smazat</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Delete %lld messages?" xml:space="preserve">
<source>Delete %lld messages?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete Contact" xml:space="preserve">
<source>Delete Contact</source>
<target>Smazat kontakt</target>
@@ -1419,6 +1522,10 @@
<target>Odstranit všechny soubory</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete and notify contact" xml:space="preserve">
<source>Delete and notify contact</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete archive" xml:space="preserve">
<source>Delete archive</source>
<target>Smazat archiv</target>
@@ -1449,9 +1556,9 @@
<target>Smazat kontakt</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete contact?" xml:space="preserve">
<source>Delete contact?</source>
<target>Smazat kontakt?</target>
<trans-unit id="Delete contact?&#10;This cannot be undone!" xml:space="preserve">
<source>Delete contact?
This cannot be undone!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete database" xml:space="preserve">
@@ -1684,14 +1791,9 @@
<target>Odpojit</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Display name" xml:space="preserve">
<source>Display name</source>
<target>Zobrazované jméno</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Display name:" xml:space="preserve">
<source>Display name:</source>
<target>Zobrazované jméno:</target>
<trans-unit id="Discover and join groups" xml:space="preserve">
<source>Discover and join groups</source>
<target>Objevte a připojte skupiny</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do NOT use SimpleX for emergency calls." xml:space="preserve">
@@ -1819,6 +1921,16 @@
<target>Šifrovat databázi?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Encrypt local files" xml:space="preserve">
<source>Encrypt local files</source>
<target>Šifrovat místní soubory</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Encrypt stored files &amp; media" xml:space="preserve">
<source>Encrypt stored files &amp; media</source>
<target>Šifrovat uložené soubory a média</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Encrypted database" xml:space="preserve">
<source>Encrypted database</source>
<target>Zašifrovaná databáze</target>
@@ -1864,6 +1976,10 @@
<target>Zadejte správnou přístupovou frázi.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enter group name…" xml:space="preserve">
<source>Enter group name…</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enter passphrase…" xml:space="preserve">
<source>Enter passphrase…</source>
<target>Zadejte přístupovou frázi…</target>
@@ -1889,6 +2005,10 @@
<target>Zadat uvítací zprávu... (volitelně)</target>
<note>placeholder</note>
</trans-unit>
<trans-unit id="Enter your name…" xml:space="preserve">
<source>Enter your name…</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error" xml:space="preserve">
<source>Error</source>
<target>Chyba</target>
@@ -1944,11 +2064,21 @@
<target>Chyba při vytváření odkazu skupiny</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error creating member contact" xml:space="preserve">
<source>Error creating member contact</source>
<target>Chyba vytvoření kontaktu člena</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error creating profile!" xml:space="preserve">
<source>Error creating profile!</source>
<target>Chyba při vytváření profilu!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error decrypting file" xml:space="preserve">
<source>Error decrypting file</source>
<target>Chyba dešifrování souboru</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error deleting chat database" xml:space="preserve">
<source>Error deleting chat database</source>
<target>Chyba při mazání databáze chatu</target>
@@ -2069,6 +2199,11 @@
<target>Chyba odesílání e-mailu</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error sending member contact invitation" xml:space="preserve">
<source>Error sending member contact invitation</source>
<target>Chyba odeslání pozvánky kontaktu</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error sending message" xml:space="preserve">
<source>Error sending message</source>
<target>Chyba při odesílání zprávy</target>
@@ -2149,6 +2284,10 @@
<target>Ukončit bez uložení</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Expand" xml:space="preserve">
<source>Expand</source>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Export database" xml:space="preserve">
<source>Export database</source>
<target>Export databáze</target>
@@ -2294,6 +2433,10 @@
<target>Celé jméno:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Fully decentralized visible only to members." xml:space="preserve">
<source>Fully decentralized visible only to members.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Fully re-implemented - work in background!" xml:space="preserve">
<source>Fully re-implemented - work in background!</source>
<target>Plně přepracováno, prácuje na pozadí!</target>
@@ -2314,6 +2457,14 @@
<target>Skupina</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Group already exists" xml:space="preserve">
<source>Group already exists</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Group already exists!" xml:space="preserve">
<source>Group already exists!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Group display name" xml:space="preserve">
<source>Group display name</source>
<target>Zobrazovaný název skupiny</target>
@@ -2661,6 +2812,10 @@
<target>Neplatný odkaz na spojení</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Invalid name!" xml:space="preserve">
<source>Invalid name!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Invalid server address!" xml:space="preserve">
<source>Invalid server address!</source>
<target>Neplatná adresa serveru!</target>
@@ -2752,11 +2907,24 @@
<target>Připojit ke skupině</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Join group?" xml:space="preserve">
<source>Join group?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Join incognito" xml:space="preserve">
<source>Join incognito</source>
<target>Připojit se inkognito</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Join with current profile" xml:space="preserve">
<source>Join with current profile</source>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Joining group" xml:space="preserve">
<source>Joining group</source>
<target>Připojování ke skupině</target>
@@ -2972,6 +3140,10 @@
<target>Zprávy</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Messages from %@ will be shown!" xml:space="preserve">
<source>Messages from %@ will be shown!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Migrating database archive…" xml:space="preserve">
<source>Migrating database archive…</source>
<target>Přenášení archivu databáze…</target>
@@ -3082,6 +3254,11 @@
<target>Archiv nové databáze</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New desktop app!" xml:space="preserve">
<source>New desktop app!</source>
<target>Nová desktopová aplikace!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New display name" xml:space="preserve">
<source>New display name</source>
<target>Nově zobrazované jméno</target>
@@ -3129,6 +3306,7 @@
</trans-unit>
<trans-unit id="No delivery information" xml:space="preserve">
<source>No delivery information</source>
<target>Žádné informace o dodání</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No device token!" xml:space="preserve">
@@ -3295,6 +3473,11 @@
<target>Hlasové zprávy může odesílat pouze váš kontakt.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open" xml:space="preserve">
<source>Open</source>
<target>Otevřít</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open Settings" xml:space="preserve">
<source>Open Settings</source>
<target>Otevřít nastavení</target>
@@ -3310,6 +3493,10 @@
<target>Otevřete konzolu chatu</target>
<note>authentication reason</note>
</trans-unit>
<trans-unit id="Open group" xml:space="preserve">
<source>Open group</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open user profiles" xml:space="preserve">
<source>Open user profiles</source>
<target>Otevřít uživatelské profily</target>
@@ -3325,11 +3512,6 @@
<target>Otvírání databáze…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red." xml:space="preserve">
<source>Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red.</source>
<target>Otevření odkazu v prohlížeči může snížit soukromí a bezpečnost připojení. Nedůvěryhodné odkazy SimpleX budou červené.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="PING count" xml:space="preserve">
<source>PING count</source>
<target>Počet PING</target>
@@ -3520,6 +3702,14 @@
<target>Profilový obrázek</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Profile name" xml:space="preserve">
<source>Profile name</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Profile name:" xml:space="preserve">
<source>Profile name:</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Profile password" xml:space="preserve">
<source>Profile password</source>
<target>Heslo profilu</target>
@@ -3637,6 +3827,7 @@
</trans-unit>
<trans-unit id="Receipts are disabled" xml:space="preserve">
<source>Receipts are disabled</source>
<target>Informace o dodání jsou zakázány</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Received at" xml:space="preserve">
@@ -3764,6 +3955,14 @@
<target>Znovu vyjednat šifrování?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Repeat connection request?" xml:space="preserve">
<source>Repeat connection request?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Repeat join request?" xml:space="preserve">
<source>Repeat join request?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Reply" xml:space="preserve">
<source>Reply</source>
<target>Odpověď</target>
@@ -4029,6 +4228,11 @@
<target>Odeslat přímou zprávu</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Send direct message to connect" xml:space="preserve">
<source>Send direct message to connect</source>
<target>Odeslat přímou zprávu pro připojení</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Send disappearing message" xml:space="preserve">
<source>Send disappearing message</source>
<target>Poslat mizící zprávu</target>
@@ -4101,6 +4305,7 @@
</trans-unit>
<trans-unit id="Sending receipts is disabled for %lld groups" xml:space="preserve">
<source>Sending receipts is disabled for %lld groups</source>
<target>Odesílání potvrzení o doručení vypnuto pro %lld skupiny</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Sending receipts is enabled for %lld contacts" xml:space="preserve">
@@ -4110,6 +4315,7 @@
</trans-unit>
<trans-unit id="Sending receipts is enabled for %lld groups" xml:space="preserve">
<source>Sending receipts is enabled for %lld groups</source>
<target>Odesílání potvrzení o doručení povoleno pro %lld skupiny</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Sending via" xml:space="preserve">
@@ -4254,6 +4460,7 @@
</trans-unit>
<trans-unit id="Show last messages" xml:space="preserve">
<source>Show last messages</source>
<target>Zobrazit poslední zprávy</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Show preview" xml:space="preserve">
@@ -4326,6 +4533,11 @@
<target>Jednorázová pozvánka SimpleX</target>
<note>simplex link type</note>
</trans-unit>
<trans-unit id="Simplified incognito mode" xml:space="preserve">
<source>Simplified incognito mode</source>
<target>Zjednodušený inkognito režim</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Skip" xml:space="preserve">
<source>Skip</source>
<target>Přeskočit</target>
@@ -4338,6 +4550,7 @@
</trans-unit>
<trans-unit id="Small groups (max 20)" xml:space="preserve">
<source>Small groups (max 20)</source>
<target>Malé skupiny (max. 20)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Some non-fatal errors occurred during import - you may see Chat console for more details." xml:space="preserve">
@@ -4465,6 +4678,10 @@
<target>Klepněte na tlačítko </target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Tap to Connect" xml:space="preserve">
<source>Tap to Connect</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Tap to activate profile." xml:space="preserve">
<source>Tap to activate profile.</source>
<target>Klepnutím aktivujete profil.</target>
@@ -4562,11 +4779,6 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován
<target>Šifrování funguje a nové povolení šifrování není vyžadováno. To může vyvolat chybu v připojení!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The group is fully decentralized it is visible only to the members." xml:space="preserve">
<source>The group is fully decentralized it is visible only to the members.</source>
<target>Skupina je plně decentralizovaná - je viditelná pouze pro členy.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The hash of the previous message is different." xml:space="preserve">
<source>The hash of the previous message is different.</source>
<target>Hash předchozí zprávy se liší.</target>
@@ -4654,6 +4866,7 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován
</trans-unit>
<trans-unit id="This group has over %lld members, delivery receipts are not sent." xml:space="preserve">
<source>This group has over %lld members, delivery receipts are not sent.</source>
<target>Tato skupina má více než %lld členů, potvrzení o doručení nejsou odesílány.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="This group no longer exists." xml:space="preserve">
@@ -4661,6 +4874,14 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován
<target>Tato skupina již neexistuje.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="This is your own SimpleX address!" xml:space="preserve">
<source>This is your own SimpleX address!</source>
<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>
<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">
<source>This setting applies to messages in your current chat profile **%@**.</source>
<target>Toto nastavení platí pro zprávy ve vašem aktuálním chat profilu **%@**.</target>
@@ -4718,6 +4939,11 @@ Před zapnutím této funkce budete vyzváni k dokončení ověření.</target>
<target>Chcete-li ověřit koncové šifrování u svého kontaktu, porovnejte (nebo naskenujte) kód na svých zařízeních.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Toggle incognito when connecting." xml:space="preserve">
<source>Toggle incognito when connecting.</source>
<target>Změnit inkognito režim při připojení.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Transport isolation" xml:space="preserve">
<source>Transport isolation</source>
<target>Izolace transportu</target>
@@ -4753,6 +4979,18 @@ Před zapnutím této funkce budete vyzváni k dokončení ověření.</target>
<target>Nelze nahrát hlasovou zprávu</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unblock" xml:space="preserve">
<source>Unblock</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unblock member" xml:space="preserve">
<source>Unblock member</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unblock member?" xml:space="preserve">
<source>Unblock member?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unexpected error: %@" xml:space="preserve">
<source>Unexpected error: %@</source>
<target>Neočekávaná chyba: %@</target>
@@ -4897,6 +5135,7 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu
</trans-unit>
<trans-unit id="Use current profile" xml:space="preserve">
<source>Use current profile</source>
<target>Použít aktuální profil</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Use for new connections" xml:space="preserve">
@@ -4911,6 +5150,7 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu
</trans-unit>
<trans-unit id="Use new incognito profile" xml:space="preserve">
<source>Use new incognito profile</source>
<target>Použít nový inkognito profil</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Use server" xml:space="preserve">
@@ -5098,6 +5338,35 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu
<target>Již jste připojeni k %@.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You are already connecting to %@." xml:space="preserve">
<source>You are already connecting to %@.</source>
<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>
<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>
<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>
<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>
<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>
<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>
<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">
<source>You are connected to the server used to receive messages from this contact.</source>
<target>Jste připojeni k serveru, který se používá k přijímání zpráv od tohoto kontaktu.</target>
@@ -5193,6 +5462,15 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu
<target>Nemohli jste být ověřeni; Zkuste to prosím znovu.</target>
<note>No comment provided by engineer.</note>
</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>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You have no chats" xml:space="preserve">
<source>You have no chats</source>
<target>Nemáte žádné konverzace</target>
@@ -5243,6 +5521,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu
<target>Ke skupině budete připojeni, až bude zařízení hostitele skupiny online, vyčkejte prosím nebo se podívejte později!</target>
<note>No comment provided by engineer.</note>
</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>
<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">
<source>You will be connected when your connection request is accepted, please wait or check later!</source>
<target>Budete připojeni, jakmile bude vaše žádost o připojení přijata, vyčkejte prosím nebo se podívejte později!</target>
@@ -5258,9 +5540,8 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu
<target>Při spuštění nebo obnovení aplikace po 30 sekundách na pozadí budete požádáni o ověření.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You will join a group this link refers to and connect to its group members." xml:space="preserve">
<source>You will join a group this link refers to and connect to its group members.</source>
<target>Připojíte se ke skupině, na kterou tento odkaz odkazuje, a spojíte se s jejími členy.</target>
<trans-unit id="You will connect to all group members." xml:space="preserve">
<source>You will connect to all group members.</source>
<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">
@@ -5328,11 +5609,6 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu
<target>Vaše chat databáze není šifrována nastavte přístupovou frázi pro její šifrování.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your chat profile will be sent to group members" xml:space="preserve">
<source>Your chat profile will be sent to group members</source>
<target>Váš chat profil bude zaslán členům skupiny</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your chat profiles" xml:space="preserve">
<source>Your chat profiles</source>
<target>Vaše chat profily</target>
@@ -5387,8 +5663,13 @@ Můžete ji změnit v Nastavení.</target>
<target>Vaše soukromí</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your profile" xml:space="preserve">
<source>Your profile</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your profile **%@** will be shared." xml:space="preserve">
<source>Your profile **%@** will be shared.</source>
<target>Váš profil **%@** bude sdílen.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your profile is stored on your device and shared only with your contacts.&#10;SimpleX servers cannot see your profile." xml:space="preserve">
@@ -5478,6 +5759,10 @@ Servery SimpleX nevidí váš profil.</target>
<target>vždy</target>
<note>pref value</note>
</trans-unit>
<trans-unit id="and %lld other events" xml:space="preserve">
<source>and %lld other events</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="audio call (not e2e encrypted)" xml:space="preserve">
<source>audio call (not e2e encrypted)</source>
<target>zvukový hovor (nešifrovaný e2e)</target>
@@ -5493,6 +5778,10 @@ Servery SimpleX nevidí váš profil.</target>
<target>špatný hash zprávy</target>
<note>integrity error chat item</note>
</trans-unit>
<trans-unit id="blocked" xml:space="preserve">
<source>blocked</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="bold" xml:space="preserve">
<source>bold</source>
<target>tučně</target>
@@ -5563,6 +5852,11 @@ Servery SimpleX nevidí váš profil.</target>
<target>připojeno</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="connected directly" xml:space="preserve">
<source>connected directly</source>
<target>připojeno přímo</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="connecting" xml:space="preserve">
<source>connecting</source>
<target>připojování</target>
@@ -5658,6 +5952,10 @@ Servery SimpleX nevidí váš profil.</target>
<target>smazáno</target>
<note>deleted chat item</note>
</trans-unit>
<trans-unit id="deleted contact" xml:space="preserve">
<source>deleted contact</source>
<note>rcv direct event chat item</note>
</trans-unit>
<trans-unit id="deleted group" xml:space="preserve">
<source>deleted group</source>
<target>odstraněna skupina</target>
@@ -5675,6 +5973,7 @@ Servery SimpleX nevidí váš profil.</target>
</trans-unit>
<trans-unit id="disabled" xml:space="preserve">
<source>disabled</source>
<target>vypnut</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="duplicate message" xml:space="preserve">
@@ -5940,7 +6239,8 @@ Servery SimpleX nevidí váš profil.</target>
<source>off</source>
<target>vypnuto</target>
<note>enabled status
group pref value</note>
group pref value
time to disappear</note>
</trans-unit>
<trans-unit id="offered %@" xml:space="preserve">
<source>offered %@</source>
@@ -5957,11 +6257,6 @@ Servery SimpleX nevidí váš profil.</target>
<target>zapnuto</target>
<note>group pref value</note>
</trans-unit>
<trans-unit id="or chat with the developers" xml:space="preserve">
<source>or chat with the developers</source>
<target>nebo chat s vývojáři</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="owner" xml:space="preserve">
<source>owner</source>
<target>vlastník</target>
@@ -6022,6 +6317,11 @@ Servery SimpleX nevidí váš profil.</target>
<target>bezpečnostní kód změněn</target>
<note>chat item text</note>
</trans-unit>
<trans-unit id="send direct message" xml:space="preserve">
<source>send direct message</source>
<target>odeslat přímou zprávu</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="starting…" xml:space="preserve">
<source>starting…</source>
<target>začíná…</target>
@@ -6166,7 +6466,7 @@ Servery SimpleX nevidí váš profil.</target>
</file>
<file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="cs" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A5219j"/>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A240d"/>
</header>
<body>
<trans-unit id="CFBundleName" xml:space="preserve">
@@ -6198,7 +6498,7 @@ Servery SimpleX nevidí váš profil.</target>
</file>
<file original="SimpleX NSE/en.lproj/InfoPlist.strings" source-language="en" target-language="cs" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A5219j"/>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A240d"/>
</header>
<body>
<trans-unit id="CFBundleDisplayName" xml:space="preserve">

View File

@@ -3,7 +3,7 @@
"project" : "SimpleX.xcodeproj",
"targetLocale" : "cs",
"toolInfo" : {
"toolBuildNumber" : "15A5219j",
"toolBuildNumber" : "15A240d",
"toolID" : "com.apple.dt.xcode",
"toolName" : "Xcode",
"toolVersion" : "15.0"

View File

@@ -2,7 +2,7 @@
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd">
<file original="en.lproj/Localizable.strings" source-language="en" target-language="de" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A5219j"/>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A240d"/>
</header>
<body>
<trans-unit id="&#10;" xml:space="preserve">
@@ -87,6 +87,10 @@
<target>%@ / %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@ and %@" xml:space="preserve">
<source>%@ and %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@ and %@ connected" xml:space="preserve">
<source>%@ and %@ connected</source>
<target>%@ und %@ wurden verbunden</target>
@@ -97,6 +101,10 @@
<target>%1$@ an %2$@:</target>
<note>copied message info, &lt;sender&gt; at &lt;time&gt;</note>
</trans-unit>
<trans-unit id="%@ connected" xml:space="preserve">
<source>%@ connected</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@ is connected!" xml:space="preserve">
<source>%@ is connected!</source>
<target>%@ ist mit Ihnen verbunden!</target>
@@ -122,6 +130,10 @@
<target>%@ will sich mit Ihnen verbinden!</target>
<note>notification title</note>
</trans-unit>
<trans-unit id="%@, %@ and %lld members" xml:space="preserve">
<source>%@, %@ and %lld members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@, %@ and %lld other members connected" xml:space="preserve">
<source>%@, %@ and %lld other members connected</source>
<target>%@, %@ und %lld weitere Mitglieder wurden verbunden</target>
@@ -187,16 +199,37 @@
<target>%lld Datei(en) mit einem Gesamtspeicherverbrauch von %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld group events" xml:space="preserve">
<source>%lld group events</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld members" xml:space="preserve">
<source>%lld members</source>
<target>%lld Mitglieder</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld messages blocked" xml:space="preserve">
<source>%lld messages blocked</source>
<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>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld minutes" xml:space="preserve">
<source>%lld minutes</source>
<target>%lld Minuten</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld new interface languages" xml:space="preserve">
<source>%lld new interface languages</source>
<target>%lld neue Sprachen für die Bedienoberfläche</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld second(s)" xml:space="preserve">
<source>%lld second(s)</source>
<target>%lld Sekunde(n)</target>
@@ -327,6 +360,15 @@
<target>, </target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- connect to [directory service](simplex:/contact#/?v=1-4&amp;smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!&#10;- delivery receipts (up to 20 members).&#10;- faster and more stable." xml:space="preserve">
<source>- connect to [directory service](simplex:/contact#/?v=1-4&amp;smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!
- delivery receipts (up to 20 members).
- faster and more stable.</source>
<target>- Verbinden mit dem [Directory-Service](simplex:/contact#/?v=1-4&amp;smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!
- Empfangsbestätigungen (für bis zu 20 Mitglieder).
- Schneller und stabiler.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- more stable message delivery.&#10;- a bit better groups.&#10;- and more!" xml:space="preserve">
<source>- more stable message delivery.
- a bit better groups.
@@ -350,6 +392,10 @@
<target>.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="0 sec" xml:space="preserve">
<source>0 sec</source>
<note>time to disappear</note>
</trans-unit>
<trans-unit id="0s" xml:space="preserve">
<source>0s</source>
<target>0s</target>
@@ -575,6 +621,10 @@
<target>Alle Nachrichten werden gelöscht - dies kann nicht rückgängig gemacht werden! Die Nachrichten werden NUR bei Ihnen gelöscht.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="All new messages from %@ will be hidden!" xml:space="preserve">
<source>All new messages from %@ will be hidden!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="All your contacts will remain connected." xml:space="preserve">
<source>All your contacts will remain connected.</source>
<target>Alle Ihre Kontakte bleiben verbunden.</target>
@@ -680,6 +730,14 @@
<target>Sind Sie bereits verbunden?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Already connecting!" xml:space="preserve">
<source>Already connecting!</source>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Always use relay" xml:space="preserve">
<source>Always use relay</source>
<target>Über ein Relais verbinden</target>
@@ -700,6 +758,11 @@
<target>App Build: %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="App encrypts new local files (except videos)." xml:space="preserve">
<source>App encrypts new local files (except videos).</source>
<target>Neue lokale Dateien (außer Video-Dateien) werden von der App verschlüsselt.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="App icon" xml:space="preserve">
<source>App icon</source>
<target>App-Icon</target>
@@ -810,6 +873,18 @@
<target>Verbesserungen bei Nachrichten</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block" xml:space="preserve">
<source>Block</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>
</trans-unit>
<trans-unit id="Block member?" xml:space="preserve">
<source>Block member?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Both you and your contact can add message reactions." xml:space="preserve">
<source>Both you and your contact can add message reactions.</source>
<target>Sowohl Sie, als auch Ihr Kontakt können Reaktionen auf Nachrichten geben.</target>
@@ -835,6 +910,11 @@
<target>Sowohl Ihr Kontakt, als auch Sie können Sprachnachrichten senden.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" xml:space="preserve">
<source>Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!</source>
<target>Bulgarisch, Finnisch, Thailändisch und Ukrainisch - Dank der Nutzer und [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." xml:space="preserve">
<source>By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).</source>
<target>Per Chat-Profil (Voreinstellung) oder [per Verbindung](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).</target>
@@ -1066,24 +1146,27 @@
<target>Verbinden</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Connect directly" xml:space="preserve">
<source>Connect directly</source>
<target>Direkt 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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect via contact link" xml:space="preserve">
<source>Connect via contact link</source>
<target>Über den Kontakt-Link verbinden</target>
<trans-unit id="Connect to yourself?" xml:space="preserve">
<source>Connect to yourself?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect via group link?" xml:space="preserve">
<source>Connect via group link?</source>
<target>Über den Gruppen-Link verbinden?</target>
<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>
<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>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect via link" xml:space="preserve">
@@ -1101,6 +1184,10 @@
<target>Über einen Einmal-Link verbinden</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect with %@" xml:space="preserve">
<source>Connect with %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connecting server…" xml:space="preserve">
<source>Connecting to server…</source>
<target>Mit dem Server verbinden…</target>
@@ -1146,11 +1233,6 @@
<target>Der Kontakt ist bereits vorhanden</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Contact and all messages will be deleted - this cannot be undone!" xml:space="preserve">
<source>Contact and all messages will be deleted - this cannot be undone!</source>
<target>Der Kontakt und alle Nachrichten werden gelöscht - dies kann nicht rückgängig gemacht werden!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Contact hidden:" xml:space="preserve">
<source>Contact hidden:</source>
<target>Kontakt verborgen:</target>
@@ -1201,6 +1283,10 @@
<target>Core Version: v%@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Correct name to %@?" xml:space="preserve">
<source>Correct name to %@?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create" xml:space="preserve">
<source>Create</source>
<target>Erstellen</target>
@@ -1221,6 +1307,10 @@
<target>Datei erstellen</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Create group" xml:space="preserve">
<source>Create group</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create group link" xml:space="preserve">
<source>Create group link</source>
<target>Gruppenlink erstellen</target>
@@ -1231,11 +1321,20 @@
<target>Link erzeugen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" xml:space="preserve">
<source>Create new profile in [desktop app](https://simplex.chat/downloads/). 💻</source>
<target>Neues Profil in der [Desktop-App] erstellen (https://simplex.chat/downloads/). 💻</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create one-time invitation link" xml:space="preserve">
<source>Create one-time invitation link</source>
<target>Einmal-Einladungslink erstellen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create profile" xml:space="preserve">
<source>Create profile</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create queue" xml:space="preserve">
<source>Create queue</source>
<target>Erzeuge Warteschlange</target>
@@ -1394,6 +1493,10 @@
<target>Löschen</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Delete %lld messages?" xml:space="preserve">
<source>Delete %lld messages?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete Contact" xml:space="preserve">
<source>Delete Contact</source>
<target>Kontakt löschen</target>
@@ -1419,6 +1522,10 @@
<target>Alle Dateien löschen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete and notify contact" xml:space="preserve">
<source>Delete and notify contact</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete archive" xml:space="preserve">
<source>Delete archive</source>
<target>Archiv löschen</target>
@@ -1449,9 +1556,9 @@
<target>Kontakt löschen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete contact?" xml:space="preserve">
<source>Delete contact?</source>
<target>Kontakt löschen?</target>
<trans-unit id="Delete contact?&#10;This cannot be undone!" xml:space="preserve">
<source>Delete contact?
This cannot be undone!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete database" xml:space="preserve">
@@ -1684,14 +1791,9 @@
<target>Trennen</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Display name" xml:space="preserve">
<source>Display name</source>
<target>Angezeigter Name</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Display name:" xml:space="preserve">
<source>Display name:</source>
<target>Angezeigter Name:</target>
<trans-unit id="Discover and join groups" xml:space="preserve">
<source>Discover and join groups</source>
<target>Gruppen entdecken und ihnen beitreten</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do NOT use SimpleX for emergency calls." xml:space="preserve">
@@ -1819,6 +1921,16 @@
<target>Datenbank verschlüsseln?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Encrypt local files" xml:space="preserve">
<source>Encrypt local files</source>
<target>Lokale Dateien verschlüsseln</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Encrypt stored files &amp; media" xml:space="preserve">
<source>Encrypt stored files &amp; media</source>
<target>Gespeicherte Dateien &amp; Medien verschlüsseln</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Encrypted database" xml:space="preserve">
<source>Encrypted database</source>
<target>Verschlüsselte Datenbank</target>
@@ -1864,6 +1976,10 @@
<target>Geben Sie das korrekte Passwort ein.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enter group name…" xml:space="preserve">
<source>Enter group name…</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enter passphrase…" xml:space="preserve">
<source>Enter passphrase…</source>
<target>Passwort eingeben…</target>
@@ -1889,6 +2005,10 @@
<target>Geben Sie eine Begrüßungsmeldung ein … (optional)</target>
<note>placeholder</note>
</trans-unit>
<trans-unit id="Enter your name…" xml:space="preserve">
<source>Enter your name…</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error" xml:space="preserve">
<source>Error</source>
<target>Fehler</target>
@@ -1944,11 +2064,21 @@
<target>Fehler beim Erzeugen des Gruppen-Links</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error creating member contact" xml:space="preserve">
<source>Error creating member contact</source>
<target>Fehler beim Anlegen eines Mitglied-Kontaktes</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error creating profile!" xml:space="preserve">
<source>Error creating profile!</source>
<target>Fehler beim Erstellen des Profils!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error decrypting file" xml:space="preserve">
<source>Error decrypting file</source>
<target>Fehler beim Entschlüsseln der Datei</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error deleting chat database" xml:space="preserve">
<source>Error deleting chat database</source>
<target>Fehler beim Löschen der Chat-Datenbank</target>
@@ -2069,6 +2199,11 @@
<target>Fehler beim Senden der eMail</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error sending member contact invitation" xml:space="preserve">
<source>Error sending member contact invitation</source>
<target>Fehler beim Senden einer Mitglied-Kontakt-Einladung</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error sending message" xml:space="preserve">
<source>Error sending message</source>
<target>Fehler beim Senden der Nachricht</target>
@@ -2149,6 +2284,10 @@
<target>Beenden ohne Speichern</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Expand" xml:space="preserve">
<source>Expand</source>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Export database" xml:space="preserve">
<source>Export database</source>
<target>Datenbank exportieren</target>
@@ -2294,6 +2433,10 @@
<target>Vollständiger Name:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Fully decentralized visible only to members." xml:space="preserve">
<source>Fully decentralized visible only to members.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Fully re-implemented - work in background!" xml:space="preserve">
<source>Fully re-implemented - work in background!</source>
<target>Komplett neu umgesetzt - arbeitet nun im Hintergrund!</target>
@@ -2314,6 +2457,14 @@
<target>Gruppe</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Group already exists" xml:space="preserve">
<source>Group already exists</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Group already exists!" xml:space="preserve">
<source>Group already exists!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Group display name" xml:space="preserve">
<source>Group display name</source>
<target>Anzeigename der Gruppe</target>
@@ -2661,6 +2812,10 @@
<target>Ungültiger Verbindungslink</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Invalid name!" xml:space="preserve">
<source>Invalid name!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Invalid server address!" xml:space="preserve">
<source>Invalid server address!</source>
<target>Ungültige Serveradresse!</target>
@@ -2752,11 +2907,24 @@
<target>Treten Sie der Gruppe bei</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Join group?" xml:space="preserve">
<source>Join group?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Join incognito" xml:space="preserve">
<source>Join incognito</source>
<target>Inkognito beitreten</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Join with current profile" xml:space="preserve">
<source>Join with current profile</source>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Joining group" xml:space="preserve">
<source>Joining group</source>
<target>Der Gruppe beitreten</target>
@@ -2972,6 +3140,10 @@
<target>Nachrichten</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Messages from %@ will be shown!" xml:space="preserve">
<source>Messages from %@ will be shown!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Migrating database archive…" xml:space="preserve">
<source>Migrating database archive…</source>
<target>Datenbank-Archiv wird migriert…</target>
@@ -3082,6 +3254,11 @@
<target>Neues Datenbankarchiv</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New desktop app!" xml:space="preserve">
<source>New desktop app!</source>
<target>Neue Desktop-App!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New display name" xml:space="preserve">
<source>New display name</source>
<target>Neuer Anzeigename</target>
@@ -3296,6 +3473,11 @@
<target>Nur Ihr Kontakt kann Sprachnachrichten versenden.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open" xml:space="preserve">
<source>Open</source>
<target>Öffnen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open Settings" xml:space="preserve">
<source>Open Settings</source>
<target>Geräte-Einstellungen öffnen</target>
@@ -3311,6 +3493,10 @@
<target>Chat-Konsole öffnen</target>
<note>authentication reason</note>
</trans-unit>
<trans-unit id="Open group" xml:space="preserve">
<source>Open group</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open user profiles" xml:space="preserve">
<source>Open user profiles</source>
<target>Benutzerprofile öffnen</target>
@@ -3326,11 +3512,6 @@
<target>Öffne Datenbank …</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red." xml:space="preserve">
<source>Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red.</source>
<target>Das Öffnen des Links über den Browser kann die Privatsphäre und Sicherheit der Verbindung reduzieren. SimpleX-Links, denen nicht vertraut wird, werden Rot sein.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="PING count" xml:space="preserve">
<source>PING count</source>
<target>PING-Zähler</target>
@@ -3521,6 +3702,14 @@
<target>Profilbild</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Profile name" xml:space="preserve">
<source>Profile name</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Profile name:" xml:space="preserve">
<source>Profile name:</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Profile password" xml:space="preserve">
<source>Profile password</source>
<target>Passwort für Profil</target>
@@ -3766,6 +3955,14 @@
<target>Verschlüsselung neu aushandeln?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Repeat connection request?" xml:space="preserve">
<source>Repeat connection request?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Repeat join request?" xml:space="preserve">
<source>Repeat join request?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Reply" xml:space="preserve">
<source>Reply</source>
<target>Antwort</target>
@@ -4031,6 +4228,11 @@
<target>Direktnachricht senden</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Send direct message to connect" xml:space="preserve">
<source>Send direct message to connect</source>
<target>Eine Direktnachricht zum Verbinden senden</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Send disappearing message" xml:space="preserve">
<source>Send disappearing message</source>
<target>Verschwindende Nachricht senden</target>
@@ -4331,6 +4533,11 @@
<target>SimpleX-Einmal-Einladung</target>
<note>simplex link type</note>
</trans-unit>
<trans-unit id="Simplified incognito mode" xml:space="preserve">
<source>Simplified incognito mode</source>
<target>Vereinfachter Inkognito-Modus</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Skip" xml:space="preserve">
<source>Skip</source>
<target>Überspringen</target>
@@ -4471,6 +4678,10 @@
<target>Schaltfläche antippen </target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Tap to Connect" xml:space="preserve">
<source>Tap to Connect</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Tap to activate profile." xml:space="preserve">
<source>Tap to activate profile.</source>
<target>Tippen Sie auf das Profil um es zu aktivieren.</target>
@@ -4568,11 +4779,6 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro
<target>Die Verschlüsselung funktioniert und ein neues Verschlüsselungsabkommen ist nicht erforderlich. Es kann zu Verbindungsfehlern kommen!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The group is fully decentralized it is visible only to the members." xml:space="preserve">
<source>The group is fully decentralized it is visible only to the members.</source>
<target>Die Gruppe ist vollständig dezentralisiert sie ist nur für Mitglieder sichtbar.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The hash of the previous message is different." xml:space="preserve">
<source>The hash of the previous message is different.</source>
<target>Der Hash der vorherigen Nachricht unterscheidet sich.</target>
@@ -4668,6 +4874,14 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro
<target>Diese Gruppe existiert nicht mehr.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="This is your own SimpleX address!" xml:space="preserve">
<source>This is your own SimpleX address!</source>
<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>
<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">
<source>This setting applies to messages in your current chat profile **%@**.</source>
<target>Diese Einstellung gilt für Nachrichten in Ihrem aktuellen Chat-Profil **%@**.</target>
@@ -4725,6 +4939,11 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt
<target>Um die Ende-zu-Ende-Verschlüsselung mit Ihrem Kontakt zu überprüfen, müssen Sie den Sicherheitscode in Ihren Apps vergleichen oder scannen.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Toggle incognito when connecting." xml:space="preserve">
<source>Toggle incognito when connecting.</source>
<target>Inkognito beim Verbinden einschalten.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Transport isolation" xml:space="preserve">
<source>Transport isolation</source>
<target>Transport-Isolation</target>
@@ -4760,6 +4979,18 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt
<target>Die Aufnahme einer Sprachnachricht ist nicht möglich</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unblock" xml:space="preserve">
<source>Unblock</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unblock member" xml:space="preserve">
<source>Unblock member</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unblock member?" xml:space="preserve">
<source>Unblock member?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unexpected error: %@" xml:space="preserve">
<source>Unexpected error: %@</source>
<target>Unerwarteter Fehler: %@</target>
@@ -5107,6 +5338,35 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
<target>Sie sind bereits mit %@ verbunden.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You are already connecting to %@." xml:space="preserve">
<source>You are already connecting to %@.</source>
<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>
<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>
<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>
<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>
<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>
<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>
<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">
<source>You are connected to the server used to receive messages from this contact.</source>
<target>Sie sind mit dem Server verbunden, der für den Empfang von Nachrichten mit diesem Kontakt genutzt wird.</target>
@@ -5202,6 +5462,15 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
<target>Sie konnten nicht überprüft werden; bitte versuchen Sie es erneut.</target>
<note>No comment provided by engineer.</note>
</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>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You have no chats" xml:space="preserve">
<source>You have no chats</source>
<target>Sie haben keine Chats</target>
@@ -5252,6 +5521,10 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
<target>Sie werden mit der Gruppe verbunden, sobald das Endgerät des Gruppen-Hosts online ist. Bitte warten oder schauen Sie später nochmal nach!</target>
<note>No comment provided by engineer.</note>
</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>
<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">
<source>You will be connected when your connection request is accepted, please wait or check later!</source>
<target>Sie werden verbunden, sobald Ihre Verbindungsanfrage akzeptiert wird. Bitte warten oder schauen Sie später nochmal nach!</target>
@@ -5267,9 +5540,8 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
<target>Sie müssen sich authentifizieren, wenn Sie die im Hintergrund befindliche App nach 30 Sekunden starten oder fortsetzen.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You will join a group this link refers to and connect to its group members." xml:space="preserve">
<source>You will join a group this link refers to and connect to its group members.</source>
<target>Sie werden der Gruppe beitreten, auf die sich dieser Link bezieht und sich mit deren Gruppenmitgliedern verbinden.</target>
<trans-unit id="You will connect to all group members." xml:space="preserve">
<source>You will connect to all group members.</source>
<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">
@@ -5337,11 +5609,6 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
<target>Ihre Chat-Datenbank ist nicht verschlüsselt. Bitte legen Sie ein Passwort fest, um sie zu schützen.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your chat profile will be sent to group members" xml:space="preserve">
<source>Your chat profile will be sent to group members</source>
<target>Ihr Chat-Profil wird an Gruppenmitglieder gesendet</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your chat profiles" xml:space="preserve">
<source>Your chat profiles</source>
<target>Meine Chat-Profile</target>
@@ -5396,6 +5663,10 @@ Sie können es in den Einstellungen ändern.</target>
<target>Meine Privatsphäre</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your profile" xml:space="preserve">
<source>Your profile</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your profile **%@** will be shared." xml:space="preserve">
<source>Your profile **%@** will be shared.</source>
<target>Ihr Profil **%@** wird geteilt.</target>
@@ -5488,6 +5759,10 @@ SimpleX-Server können Ihr Profil nicht einsehen.</target>
<target>Immer</target>
<note>pref value</note>
</trans-unit>
<trans-unit id="and %lld other events" xml:space="preserve">
<source>and %lld other events</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="audio call (not e2e encrypted)" xml:space="preserve">
<source>audio call (not e2e encrypted)</source>
<target>Audioanruf (nicht E2E verschlüsselt)</target>
@@ -5503,6 +5778,10 @@ SimpleX-Server können Ihr Profil nicht einsehen.</target>
<target>Ungültiger Nachrichten-Hash</target>
<note>integrity error chat item</note>
</trans-unit>
<trans-unit id="blocked" xml:space="preserve">
<source>blocked</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="bold" xml:space="preserve">
<source>bold</source>
<target>fett</target>
@@ -5573,6 +5852,11 @@ SimpleX-Server können Ihr Profil nicht einsehen.</target>
<target>Verbunden</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="connected directly" xml:space="preserve">
<source>connected directly</source>
<target>Direkt miteinander verbunden</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="connecting" xml:space="preserve">
<source>connecting</source>
<target>verbinde</target>
@@ -5668,6 +5952,10 @@ SimpleX-Server können Ihr Profil nicht einsehen.</target>
<target>Gelöscht</target>
<note>deleted chat item</note>
</trans-unit>
<trans-unit id="deleted contact" xml:space="preserve">
<source>deleted contact</source>
<note>rcv direct event chat item</note>
</trans-unit>
<trans-unit id="deleted group" xml:space="preserve">
<source>deleted group</source>
<target>Gruppe gelöscht</target>
@@ -5952,7 +6240,8 @@ SimpleX-Server können Ihr Profil nicht einsehen.</target>
<source>off</source>
<target>Aus</target>
<note>enabled status
group pref value</note>
group pref value
time to disappear</note>
</trans-unit>
<trans-unit id="offered %@" xml:space="preserve">
<source>offered %@</source>
@@ -5969,11 +6258,6 @@ SimpleX-Server können Ihr Profil nicht einsehen.</target>
<target>Ein</target>
<note>group pref value</note>
</trans-unit>
<trans-unit id="or chat with the developers" xml:space="preserve">
<source>or chat with the developers</source>
<target>oder chatten Sie mit den Entwicklern</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="owner" xml:space="preserve">
<source>owner</source>
<target>Eigentümer</target>
@@ -6034,6 +6318,11 @@ SimpleX-Server können Ihr Profil nicht einsehen.</target>
<target>Sicherheitscode wurde geändert</target>
<note>chat item text</note>
</trans-unit>
<trans-unit id="send direct message" xml:space="preserve">
<source>send direct message</source>
<target>Direktnachricht senden</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="starting…" xml:space="preserve">
<source>starting…</source>
<target>Verbindung wird gestartet…</target>
@@ -6178,7 +6467,7 @@ SimpleX-Server können Ihr Profil nicht einsehen.</target>
</file>
<file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="de" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A5219j"/>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A240d"/>
</header>
<body>
<trans-unit id="CFBundleName" xml:space="preserve">
@@ -6210,7 +6499,7 @@ SimpleX-Server können Ihr Profil nicht einsehen.</target>
</file>
<file original="SimpleX NSE/en.lproj/InfoPlist.strings" source-language="en" target-language="de" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A5219j"/>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A240d"/>
</header>
<body>
<trans-unit id="CFBundleDisplayName" xml:space="preserve">

View File

@@ -3,7 +3,7 @@
"project" : "SimpleX.xcodeproj",
"targetLocale" : "de",
"toolInfo" : {
"toolBuildNumber" : "15A5219j",
"toolBuildNumber" : "15A240d",
"toolID" : "com.apple.dt.xcode",
"toolName" : "Xcode",
"toolVersion" : "15.0"

View File

@@ -2,7 +2,7 @@
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd">
<file original="en.lproj/Localizable.strings" source-language="en" target-language="en" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A5219j"/>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A240d"/>
</header>
<body>
<trans-unit id="&#10;" xml:space="preserve">
@@ -87,6 +87,11 @@
<target>%@ / %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@ and %@" xml:space="preserve">
<source>%@ and %@</source>
<target>%@ and %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@ and %@ connected" xml:space="preserve">
<source>%@ and %@ connected</source>
<target>%@ and %@ connected</target>
@@ -97,6 +102,11 @@
<target>%1$@ at %2$@:</target>
<note>copied message info, &lt;sender&gt; at &lt;time&gt;</note>
</trans-unit>
<trans-unit id="%@ connected" xml:space="preserve">
<source>%@ connected</source>
<target>%@ connected</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@ is connected!" xml:space="preserve">
<source>%@ is connected!</source>
<target>%@ is connected!</target>
@@ -122,6 +132,11 @@
<target>%@ wants to connect!</target>
<note>notification title</note>
</trans-unit>
<trans-unit id="%@, %@ and %lld members" xml:space="preserve">
<source>%@, %@ and %lld members</source>
<target>%@, %@ and %lld members</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@, %@ and %lld other members connected" xml:space="preserve">
<source>%@, %@ and %lld other members connected</source>
<target>%@, %@ and %lld other members connected</target>
@@ -187,16 +202,41 @@
<target>%lld file(s) with total size of %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld group events" xml:space="preserve">
<source>%lld group events</source>
<target>%lld group events</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld members" xml:space="preserve">
<source>%lld members</source>
<target>%lld members</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld messages blocked" xml:space="preserve">
<source>%lld messages blocked</source>
<target>%lld messages blocked</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 messages marked deleted</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 messages moderated by %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld minutes" xml:space="preserve">
<source>%lld minutes</source>
<target>%lld minutes</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld new interface languages" xml:space="preserve">
<source>%lld new interface languages</source>
<target>%lld new interface languages</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld second(s)" xml:space="preserve">
<source>%lld second(s)</source>
<target>%lld second(s)</target>
@@ -327,6 +367,15 @@
<target>, </target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- connect to [directory service](simplex:/contact#/?v=1-4&amp;smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!&#10;- delivery receipts (up to 20 members).&#10;- faster and more stable." xml:space="preserve">
<source>- connect to [directory service](simplex:/contact#/?v=1-4&amp;smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!
- delivery receipts (up to 20 members).
- faster and more stable.</source>
<target>- connect to [directory service](simplex:/contact#/?v=1-4&amp;smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!
- delivery receipts (up to 20 members).
- faster and more stable.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- more stable message delivery.&#10;- a bit better groups.&#10;- and more!" xml:space="preserve">
<source>- more stable message delivery.
- a bit better groups.
@@ -350,6 +399,11 @@
<target>.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="0 sec" xml:space="preserve">
<source>0 sec</source>
<target>0 sec</target>
<note>time to disappear</note>
</trans-unit>
<trans-unit id="0s" xml:space="preserve">
<source>0s</source>
<target>0s</target>
@@ -575,6 +629,11 @@
<target>All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you.</target>
<note>No comment provided by engineer.</note>
</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>All new messages from %@ will be hidden!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="All your contacts will remain connected." xml:space="preserve">
<source>All your contacts will remain connected.</source>
<target>All your contacts will remain connected.</target>
@@ -680,6 +739,16 @@
<target>Already connected?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Already connecting!" xml:space="preserve">
<source>Already connecting!</source>
<target>Already connecting!</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>Already joining the group!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Always use relay" xml:space="preserve">
<source>Always use relay</source>
<target>Always use relay</target>
@@ -700,6 +769,11 @@
<target>App build: %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="App encrypts new local files (except videos)." xml:space="preserve">
<source>App encrypts new local files (except videos).</source>
<target>App encrypts new local files (except videos).</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="App icon" xml:space="preserve">
<source>App icon</source>
<target>App icon</target>
@@ -810,6 +884,21 @@
<target>Better messages</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block" xml:space="preserve">
<source>Block</source>
<target>Block</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>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Both you and your contact can add message reactions." xml:space="preserve">
<source>Both you and your contact can add message reactions.</source>
<target>Both you and your contact can add message reactions.</target>
@@ -835,6 +924,11 @@
<target>Both you and your contact can send voice messages.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" xml:space="preserve">
<source>Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!</source>
<target>Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." xml:space="preserve">
<source>By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).</source>
<target>By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).</target>
@@ -1066,24 +1160,33 @@
<target>Connect</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Connect directly" xml:space="preserve">
<source>Connect directly</source>
<target>Connect directly</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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect via contact link" xml:space="preserve">
<source>Connect via contact link</source>
<target>Connect via contact link</target>
<trans-unit id="Connect to yourself?" xml:space="preserve">
<source>Connect to yourself?</source>
<target>Connect to yourself?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect via group link?" xml:space="preserve">
<source>Connect via group link?</source>
<target>Connect via group link?</target>
<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>Connect to yourself?
This is your own SimpleX address!</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>Connect to yourself?
This is your own one-time link!</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>Connect via contact address</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect via link" xml:space="preserve">
@@ -1101,6 +1204,11 @@
<target>Connect via one-time link</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect with %@" xml:space="preserve">
<source>Connect with %@</source>
<target>Connect with %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connecting server…" xml:space="preserve">
<source>Connecting to server…</source>
<target>Connecting to server…</target>
@@ -1146,11 +1254,6 @@
<target>Contact already exists</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Contact and all messages will be deleted - this cannot be undone!" xml:space="preserve">
<source>Contact and all messages will be deleted - this cannot be undone!</source>
<target>Contact and all messages will be deleted - this cannot be undone!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Contact hidden:" xml:space="preserve">
<source>Contact hidden:</source>
<target>Contact hidden:</target>
@@ -1201,6 +1304,11 @@
<target>Core version: v%@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Correct name to %@?" xml:space="preserve">
<source>Correct name to %@?</source>
<target>Correct name to %@?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create" xml:space="preserve">
<source>Create</source>
<target>Create</target>
@@ -1221,6 +1329,11 @@
<target>Create file</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Create group" xml:space="preserve">
<source>Create group</source>
<target>Create group</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create group link" xml:space="preserve">
<source>Create group link</source>
<target>Create group link</target>
@@ -1231,11 +1344,21 @@
<target>Create link</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" xml:space="preserve">
<source>Create new profile in [desktop app](https://simplex.chat/downloads/). 💻</source>
<target>Create new profile in [desktop app](https://simplex.chat/downloads/). 💻</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create one-time invitation link" xml:space="preserve">
<source>Create one-time invitation link</source>
<target>Create one-time invitation link</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create profile" xml:space="preserve">
<source>Create profile</source>
<target>Create profile</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create queue" xml:space="preserve">
<source>Create queue</source>
<target>Create queue</target>
@@ -1394,6 +1517,11 @@
<target>Delete</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Delete %lld messages?" xml:space="preserve">
<source>Delete %lld messages?</source>
<target>Delete %lld messages?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete Contact" xml:space="preserve">
<source>Delete Contact</source>
<target>Delete Contact</target>
@@ -1419,6 +1547,11 @@
<target>Delete all files</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete and notify contact" xml:space="preserve">
<source>Delete and notify contact</source>
<target>Delete and notify contact</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete archive" xml:space="preserve">
<source>Delete archive</source>
<target>Delete archive</target>
@@ -1449,9 +1582,11 @@
<target>Delete contact</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete contact?" xml:space="preserve">
<source>Delete contact?</source>
<target>Delete contact?</target>
<trans-unit id="Delete contact?&#10;This cannot be undone!" xml:space="preserve">
<source>Delete contact?
This cannot be undone!</source>
<target>Delete contact?
This cannot be undone!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete database" xml:space="preserve">
@@ -1684,14 +1819,9 @@
<target>Disconnect</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Display name" xml:space="preserve">
<source>Display name</source>
<target>Display name</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Display name:" xml:space="preserve">
<source>Display name:</source>
<target>Display name:</target>
<trans-unit id="Discover and join groups" xml:space="preserve">
<source>Discover and join groups</source>
<target>Discover and join groups</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do NOT use SimpleX for emergency calls." xml:space="preserve">
@@ -1819,6 +1949,16 @@
<target>Encrypt database?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Encrypt local files" xml:space="preserve">
<source>Encrypt local files</source>
<target>Encrypt local files</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Encrypt stored files &amp; media" xml:space="preserve">
<source>Encrypt stored files &amp; media</source>
<target>Encrypt stored files &amp; media</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Encrypted database" xml:space="preserve">
<source>Encrypted database</source>
<target>Encrypted database</target>
@@ -1864,6 +2004,11 @@
<target>Enter correct passphrase.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enter group name…" xml:space="preserve">
<source>Enter group name…</source>
<target>Enter group name…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enter passphrase…" xml:space="preserve">
<source>Enter passphrase…</source>
<target>Enter passphrase…</target>
@@ -1889,6 +2034,11 @@
<target>Enter welcome message… (optional)</target>
<note>placeholder</note>
</trans-unit>
<trans-unit id="Enter your name…" xml:space="preserve">
<source>Enter your name…</source>
<target>Enter your name…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error" xml:space="preserve">
<source>Error</source>
<target>Error</target>
@@ -1944,11 +2094,21 @@
<target>Error creating group link</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error creating member contact" xml:space="preserve">
<source>Error creating member contact</source>
<target>Error creating member contact</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error creating profile!" xml:space="preserve">
<source>Error creating profile!</source>
<target>Error creating profile!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error decrypting file" xml:space="preserve">
<source>Error decrypting file</source>
<target>Error decrypting file</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error deleting chat database" xml:space="preserve">
<source>Error deleting chat database</source>
<target>Error deleting chat database</target>
@@ -2069,6 +2229,11 @@
<target>Error sending email</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error sending member contact invitation" xml:space="preserve">
<source>Error sending member contact invitation</source>
<target>Error sending member contact invitation</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error sending message" xml:space="preserve">
<source>Error sending message</source>
<target>Error sending message</target>
@@ -2149,6 +2314,11 @@
<target>Exit without saving</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Expand" xml:space="preserve">
<source>Expand</source>
<target>Expand</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Export database" xml:space="preserve">
<source>Export database</source>
<target>Export database</target>
@@ -2294,6 +2464,11 @@
<target>Full name:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Fully decentralized visible only to members." xml:space="preserve">
<source>Fully decentralized visible only to members.</source>
<target>Fully decentralized visible only to members.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Fully re-implemented - work in background!" xml:space="preserve">
<source>Fully re-implemented - work in background!</source>
<target>Fully re-implemented - work in background!</target>
@@ -2314,6 +2489,16 @@
<target>Group</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>Group already exists</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>Group already exists!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Group display name" xml:space="preserve">
<source>Group display name</source>
<target>Group display name</target>
@@ -2661,6 +2846,11 @@
<target>Invalid connection link</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Invalid name!" xml:space="preserve">
<source>Invalid name!</source>
<target>Invalid name!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Invalid server address!" xml:space="preserve">
<source>Invalid server address!</source>
<target>Invalid server address!</target>
@@ -2752,11 +2942,28 @@
<target>Join group</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Join group?" xml:space="preserve">
<source>Join group?</source>
<target>Join group?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Join incognito" xml:space="preserve">
<source>Join incognito</source>
<target>Join incognito</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Join with current profile" xml:space="preserve">
<source>Join with current profile</source>
<target>Join with current profile</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>Join your group?
This is your link for group %@!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Joining group" xml:space="preserve">
<source>Joining group</source>
<target>Joining group</target>
@@ -2972,6 +3179,11 @@
<target>Messages &amp; files</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Messages from %@ will be shown!" xml:space="preserve">
<source>Messages from %@ will be shown!</source>
<target>Messages from %@ will be shown!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Migrating database archive…" xml:space="preserve">
<source>Migrating database archive…</source>
<target>Migrating database archive…</target>
@@ -3082,6 +3294,11 @@
<target>New database archive</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New desktop app!" xml:space="preserve">
<source>New desktop app!</source>
<target>New desktop app!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New display name" xml:space="preserve">
<source>New display name</source>
<target>New display name</target>
@@ -3296,6 +3513,11 @@
<target>Only your contact can send voice messages.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open" xml:space="preserve">
<source>Open</source>
<target>Open</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open Settings" xml:space="preserve">
<source>Open Settings</source>
<target>Open Settings</target>
@@ -3311,6 +3533,11 @@
<target>Open chat console</target>
<note>authentication reason</note>
</trans-unit>
<trans-unit id="Open group" xml:space="preserve">
<source>Open group</source>
<target>Open group</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open user profiles" xml:space="preserve">
<source>Open user profiles</source>
<target>Open user profiles</target>
@@ -3326,11 +3553,6 @@
<target>Opening database…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red." xml:space="preserve">
<source>Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red.</source>
<target>Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="PING count" xml:space="preserve">
<source>PING count</source>
<target>PING count</target>
@@ -3521,6 +3743,16 @@
<target>Profile image</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Profile name" xml:space="preserve">
<source>Profile name</source>
<target>Profile name</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Profile name:" xml:space="preserve">
<source>Profile name:</source>
<target>Profile name:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Profile password" xml:space="preserve">
<source>Profile password</source>
<target>Profile password</target>
@@ -3766,6 +3998,16 @@
<target>Renegotiate encryption?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Repeat connection request?" xml:space="preserve">
<source>Repeat connection request?</source>
<target>Repeat connection request?</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>Repeat join request?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Reply" xml:space="preserve">
<source>Reply</source>
<target>Reply</target>
@@ -4031,6 +4273,11 @@
<target>Send direct message</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Send direct message to connect" xml:space="preserve">
<source>Send direct message to connect</source>
<target>Send direct message to connect</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Send disappearing message" xml:space="preserve">
<source>Send disappearing message</source>
<target>Send disappearing message</target>
@@ -4331,6 +4578,11 @@
<target>SimpleX one-time invitation</target>
<note>simplex link type</note>
</trans-unit>
<trans-unit id="Simplified incognito mode" xml:space="preserve">
<source>Simplified incognito mode</source>
<target>Simplified incognito mode</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Skip" xml:space="preserve">
<source>Skip</source>
<target>Skip</target>
@@ -4471,6 +4723,11 @@
<target>Tap button </target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Tap to Connect" xml:space="preserve">
<source>Tap to Connect</source>
<target>Tap to Connect</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Tap to activate profile." xml:space="preserve">
<source>Tap to activate profile.</source>
<target>Tap to activate profile.</target>
@@ -4568,11 +4825,6 @@ It can happen because of some bug or when the connection is compromised.</target
<target>The encryption is working and the new encryption agreement is not required. It may result in connection errors!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The group is fully decentralized it is visible only to the members." xml:space="preserve">
<source>The group is fully decentralized it is visible only to the members.</source>
<target>The group is fully decentralized it is visible only to the members.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The hash of the previous message is different." xml:space="preserve">
<source>The hash of the previous message is different.</source>
<target>The hash of the previous message is different.</target>
@@ -4668,6 +4920,16 @@ It can happen because of some bug or when the connection is compromised.</target
<target>This group no longer exists.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="This is your own SimpleX address!" xml:space="preserve">
<source>This is your own SimpleX address!</source>
<target>This is your own SimpleX address!</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>This is your own one-time link!</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">
<source>This setting applies to messages in your current chat profile **%@**.</source>
<target>This setting applies to messages in your current chat profile **%@**.</target>
@@ -4725,6 +4987,11 @@ You will be prompted to complete authentication before this feature is enabled.<
<target>To verify end-to-end encryption with your contact compare (or scan) the code on your devices.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Toggle incognito when connecting." xml:space="preserve">
<source>Toggle incognito when connecting.</source>
<target>Toggle incognito when connecting.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Transport isolation" xml:space="preserve">
<source>Transport isolation</source>
<target>Transport isolation</target>
@@ -4760,6 +5027,21 @@ You will be prompted to complete authentication before this feature is enabled.<
<target>Unable to record voice message</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unblock" xml:space="preserve">
<source>Unblock</source>
<target>Unblock</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unblock member" xml:space="preserve">
<source>Unblock member</source>
<target>Unblock member</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unblock member?" xml:space="preserve">
<source>Unblock member?</source>
<target>Unblock member?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unexpected error: %@" xml:space="preserve">
<source>Unexpected error: %@</source>
<target>Unexpected error: %@</target>
@@ -5107,6 +5389,43 @@ To connect, please ask your contact to create another connection link and check
<target>You are already connected to %@.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You are already connecting to %@." xml:space="preserve">
<source>You are already connecting to %@.</source>
<target>You are already connecting to %@.</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>You are already connecting via this one-time link!</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>You are already in group %@.</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>You are already joining the group %@.</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>You are already joining the group via this link!</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>You are already joining the group via this link.</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>You are already joining the group!
Repeat join request?</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">
<source>You are connected to the server used to receive messages from this contact.</source>
<target>You are connected to the server used to receive messages from this contact.</target>
@@ -5202,6 +5521,18 @@ To connect, please ask your contact to create another connection link and check
<target>You could not be verified; please try again.</target>
<note>No comment provided by engineer.</note>
</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>You have already requested connection via this address!</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>You have already requested connection!
Repeat connection request?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You have no chats" xml:space="preserve">
<source>You have no chats</source>
<target>You have no chats</target>
@@ -5252,6 +5583,11 @@ To connect, please ask your contact to create another connection link and check
<target>You will be connected to group when the group host's device is online, please wait or check later!</target>
<note>No comment provided by engineer.</note>
</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>You will be connected when group link host's device is online, please wait or check later!</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">
<source>You will be connected when your connection request is accepted, please wait or check later!</source>
<target>You will be connected when your connection request is accepted, please wait or check later!</target>
@@ -5267,9 +5603,9 @@ To connect, please ask your contact to create another connection link and check
<target>You will be required to authenticate when you start or resume the app after 30 seconds in background.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You will join a group this link refers to and connect to its group members." xml:space="preserve">
<source>You will join a group this link refers to and connect to its group members.</source>
<target>You will join a group this link refers to and connect to its group members.</target>
<trans-unit id="You will connect to all group members." xml:space="preserve">
<source>You will connect to all group members.</source>
<target>You will connect to all group members.</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">
@@ -5337,11 +5673,6 @@ To connect, please ask your contact to create another connection link and check
<target>Your chat database is not encrypted - set passphrase to encrypt it.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your chat profile will be sent to group members" xml:space="preserve">
<source>Your chat profile will be sent to group members</source>
<target>Your chat profile will be sent to group members</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your chat profiles" xml:space="preserve">
<source>Your chat profiles</source>
<target>Your chat profiles</target>
@@ -5396,6 +5727,11 @@ You can change it in Settings.</target>
<target>Your privacy</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your profile" xml:space="preserve">
<source>Your profile</source>
<target>Your profile</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your profile **%@** will be shared." xml:space="preserve">
<source>Your profile **%@** will be shared.</source>
<target>Your profile **%@** will be shared.</target>
@@ -5488,6 +5824,11 @@ SimpleX servers cannot see your profile.</target>
<target>always</target>
<note>pref value</note>
</trans-unit>
<trans-unit id="and %lld other events" xml:space="preserve">
<source>and %lld other events</source>
<target>and %lld other events</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="audio call (not e2e encrypted)" xml:space="preserve">
<source>audio call (not e2e encrypted)</source>
<target>audio call (not e2e encrypted)</target>
@@ -5503,6 +5844,11 @@ SimpleX servers cannot see your profile.</target>
<target>bad message hash</target>
<note>integrity error chat item</note>
</trans-unit>
<trans-unit id="blocked" xml:space="preserve">
<source>blocked</source>
<target>blocked</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="bold" xml:space="preserve">
<source>bold</source>
<target>bold</target>
@@ -5573,6 +5919,11 @@ SimpleX servers cannot see your profile.</target>
<target>connected</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="connected directly" xml:space="preserve">
<source>connected directly</source>
<target>connected directly</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="connecting" xml:space="preserve">
<source>connecting</source>
<target>connecting</target>
@@ -5668,6 +6019,11 @@ SimpleX servers cannot see your profile.</target>
<target>deleted</target>
<note>deleted chat item</note>
</trans-unit>
<trans-unit id="deleted contact" xml:space="preserve">
<source>deleted contact</source>
<target>deleted contact</target>
<note>rcv direct event chat item</note>
</trans-unit>
<trans-unit id="deleted group" xml:space="preserve">
<source>deleted group</source>
<target>deleted group</target>
@@ -5952,7 +6308,8 @@ SimpleX servers cannot see your profile.</target>
<source>off</source>
<target>off</target>
<note>enabled status
group pref value</note>
group pref value
time to disappear</note>
</trans-unit>
<trans-unit id="offered %@" xml:space="preserve">
<source>offered %@</source>
@@ -5969,11 +6326,6 @@ SimpleX servers cannot see your profile.</target>
<target>on</target>
<note>group pref value</note>
</trans-unit>
<trans-unit id="or chat with the developers" xml:space="preserve">
<source>or chat with the developers</source>
<target>or chat with the developers</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="owner" xml:space="preserve">
<source>owner</source>
<target>owner</target>
@@ -6034,6 +6386,11 @@ SimpleX servers cannot see your profile.</target>
<target>security code changed</target>
<note>chat item text</note>
</trans-unit>
<trans-unit id="send direct message" xml:space="preserve">
<source>send direct message</source>
<target>send direct message</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="starting…" xml:space="preserve">
<source>starting…</source>
<target>starting…</target>
@@ -6178,7 +6535,7 @@ SimpleX servers cannot see your profile.</target>
</file>
<file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="en" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A5219j"/>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A240d"/>
</header>
<body>
<trans-unit id="CFBundleName" xml:space="preserve">
@@ -6210,7 +6567,7 @@ SimpleX servers cannot see your profile.</target>
</file>
<file original="SimpleX NSE/en.lproj/InfoPlist.strings" source-language="en" target-language="en" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A5219j"/>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A240d"/>
</header>
<body>
<trans-unit id="CFBundleDisplayName" xml:space="preserve">

View File

@@ -3,7 +3,7 @@
"project" : "SimpleX.xcodeproj",
"targetLocale" : "en",
"toolInfo" : {
"toolBuildNumber" : "15A5219j",
"toolBuildNumber" : "15A240d",
"toolID" : "com.apple.dt.xcode",
"toolName" : "Xcode",
"toolVersion" : "15.0"

View File

@@ -2,7 +2,7 @@
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd">
<file original="en.lproj/Localizable.strings" source-language="en" target-language="es" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A5219j"/>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A240d"/>
</header>
<body>
<trans-unit id="&#10;" xml:space="preserve">
@@ -87,6 +87,10 @@
<target>%@ / %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@ and %@" xml:space="preserve">
<source>%@ and %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@ and %@ connected" xml:space="preserve">
<source>%@ and %@ connected</source>
<target>%@ y %@ conectados</target>
@@ -97,6 +101,10 @@
<target>%1$@ a las %2$@:</target>
<note>copied message info, &lt;sender&gt; at &lt;time&gt;</note>
</trans-unit>
<trans-unit id="%@ connected" xml:space="preserve">
<source>%@ connected</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@ is connected!" xml:space="preserve">
<source>%@ is connected!</source>
<target>%@ ¡está conectado!</target>
@@ -122,6 +130,10 @@
<target>¡ %@ quiere contactar!</target>
<note>notification title</note>
</trans-unit>
<trans-unit id="%@, %@ and %lld members" xml:space="preserve">
<source>%@, %@ and %lld members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@, %@ and %lld other members connected" xml:space="preserve">
<source>%@, %@ and %lld other members connected</source>
<target>%@, %@ y %lld miembros más conectados</target>
@@ -187,16 +199,37 @@
<target>%lld archivo(s) con un tamaño total de %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld group events" xml:space="preserve">
<source>%lld group events</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld members" xml:space="preserve">
<source>%lld members</source>
<target>%lld miembros</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld messages blocked" xml:space="preserve">
<source>%lld messages blocked</source>
<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>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld minutes" xml:space="preserve">
<source>%lld minutes</source>
<target>%lld minutos</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld new interface languages" xml:space="preserve">
<source>%lld new interface languages</source>
<target>%lld idiomas de interfaz nuevos</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld second(s)" xml:space="preserve">
<source>%lld second(s)</source>
<target>%lld segundo(s)</target>
@@ -244,12 +277,12 @@
</trans-unit>
<trans-unit id="%u messages failed to decrypt." xml:space="preserve">
<source>%u messages failed to decrypt.</source>
<target>%u mensajes no pudieron ser descifrados.</target>
<target>%u mensaje(s) no ha(n) podido ser descifrado(s).</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%u messages skipped." xml:space="preserve">
<source>%u messages skipped.</source>
<target>%u mensajes omitidos.</target>
<target>%u mensaje(s) omitido(s).</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="(" xml:space="preserve">
@@ -327,6 +360,15 @@
<target>, </target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- connect to [directory service](simplex:/contact#/?v=1-4&amp;smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!&#10;- delivery receipts (up to 20 members).&#10;- faster and more stable." xml:space="preserve">
<source>- connect to [directory service](simplex:/contact#/?v=1-4&amp;smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!
- delivery receipts (up to 20 members).
- faster and more stable.</source>
<target>- conexión al [servicio de directorio](simplex:/contact#/?v=1-4&amp;smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!
- confirmaciones de entrega (hasta 20 miembros).
- mayor rapidez y estabilidad.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- more stable message delivery.&#10;- a bit better groups.&#10;- and more!" xml:space="preserve">
<source>- more stable message delivery.
- a bit better groups.
@@ -350,6 +392,10 @@
<target>.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="0 sec" xml:space="preserve">
<source>0 sec</source>
<note>time to disappear</note>
</trans-unit>
<trans-unit id="0s" xml:space="preserve">
<source>0s</source>
<target>0s</target>
@@ -575,6 +621,10 @@
<target>Se eliminarán todos los mensajes SOLO para tí. ¡No podrá deshacerse!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="All new messages from %@ will be hidden!" xml:space="preserve">
<source>All new messages from %@ will be hidden!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="All your contacts will remain connected." xml:space="preserve">
<source>All your contacts will remain connected.</source>
<target>Todos tus contactos permanecerán conectados.</target>
@@ -680,6 +730,14 @@
<target>¿Ya está conectado?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Already connecting!" xml:space="preserve">
<source>Already connecting!</source>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Always use relay" xml:space="preserve">
<source>Always use relay</source>
<target>Usar siempre retransmisor</target>
@@ -700,6 +758,11 @@
<target>Compilación app: %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="App encrypts new local files (except videos)." xml:space="preserve">
<source>App encrypts new local files (except videos).</source>
<target>Cifrado de los nuevos archivos locales (excepto vídeos).</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="App icon" xml:space="preserve">
<source>App icon</source>
<target>Icono aplicación</target>
@@ -810,6 +873,18 @@
<target>Mensajes mejorados</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block" xml:space="preserve">
<source>Block</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>
</trans-unit>
<trans-unit id="Block member?" xml:space="preserve">
<source>Block member?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Both you and your contact can add message reactions." xml:space="preserve">
<source>Both you and your contact can add message reactions.</source>
<target>Tanto tú como tu contacto podéis añadir reacciones a los mensajes.</target>
@@ -835,6 +910,11 @@
<target>Tanto tú como tu contacto podéis enviar mensajes de voz.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" xml:space="preserve">
<source>Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!</source>
<target>Búlgaro, Finlandés, Tailandés y Ucraniano - gracias a los usuarios y [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." xml:space="preserve">
<source>By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).</source>
<target>Mediante perfil (por defecto) o [por conexión](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).</target>
@@ -1066,24 +1146,27 @@
<target>Conectar</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Connect directly" xml:space="preserve">
<source>Connect directly</source>
<target>Conectar directamente</target>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect via contact link" xml:space="preserve">
<source>Connect via contact link</source>
<target>Conectar mediante enlace de contacto</target>
<trans-unit id="Connect to yourself?" xml:space="preserve">
<source>Connect to yourself?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect via group link?" xml:space="preserve">
<source>Connect via group link?</source>
<target>¿Conectar mediante enlace de grupo?</target>
<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>
<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>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect via link" xml:space="preserve">
@@ -1101,6 +1184,10 @@
<target>Conectar mediante enlace de un sólo uso</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect with %@" xml:space="preserve">
<source>Connect with %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connecting server…" xml:space="preserve">
<source>Connecting to server…</source>
<target>Conectando con el servidor…</target>
@@ -1146,11 +1233,6 @@
<target>El contácto ya existe</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Contact and all messages will be deleted - this cannot be undone!" xml:space="preserve">
<source>Contact and all messages will be deleted - this cannot be undone!</source>
<target>El contacto y todos los mensajes serán eliminados. ¡No podrá deshacerse!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Contact hidden:" xml:space="preserve">
<source>Contact hidden:</source>
<target>Contacto oculto:</target>
@@ -1201,6 +1283,10 @@
<target>Versión Core: v%@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Correct name to %@?" xml:space="preserve">
<source>Correct name to %@?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create" xml:space="preserve">
<source>Create</source>
<target>Crear</target>
@@ -1221,6 +1307,10 @@
<target>Crear archivo</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Create group" xml:space="preserve">
<source>Create group</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create group link" xml:space="preserve">
<source>Create group link</source>
<target>Crear enlace de grupo</target>
@@ -1231,11 +1321,20 @@
<target>Crear enlace</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" xml:space="preserve">
<source>Create new profile in [desktop app](https://simplex.chat/downloads/). 💻</source>
<target>Crea perfil nuevo en la [aplicación para PC](https://simplex.Descargas/de chat/). 💻</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create one-time invitation link" xml:space="preserve">
<source>Create one-time invitation link</source>
<target>Crea enlace de invitación de un uso</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create profile" xml:space="preserve">
<source>Create profile</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create queue" xml:space="preserve">
<source>Create queue</source>
<target>Crear cola</target>
@@ -1394,6 +1493,10 @@
<target>Eliminar</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Delete %lld messages?" xml:space="preserve">
<source>Delete %lld messages?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete Contact" xml:space="preserve">
<source>Delete Contact</source>
<target>Eliminar contacto</target>
@@ -1419,6 +1522,10 @@
<target>Eliminar todos los archivos</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete and notify contact" xml:space="preserve">
<source>Delete and notify contact</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete archive" xml:space="preserve">
<source>Delete archive</source>
<target>Eliminar archivo</target>
@@ -1449,9 +1556,9 @@
<target>Eliminar contacto</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete contact?" xml:space="preserve">
<source>Delete contact?</source>
<target>Eliminar contacto?</target>
<trans-unit id="Delete contact?&#10;This cannot be undone!" xml:space="preserve">
<source>Delete contact?
This cannot be undone!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete database" xml:space="preserve">
@@ -1636,7 +1743,7 @@
</trans-unit>
<trans-unit id="Disable (keep overrides)" xml:space="preserve">
<source>Disable (keep overrides)</source>
<target>Desactivar (conservar anulaciones)</target>
<target>Desactivar (conservando anulaciones)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Disable SimpleX Lock" xml:space="preserve">
@@ -1684,14 +1791,9 @@
<target>Desconectar</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Display name" xml:space="preserve">
<source>Display name</source>
<target>Nombre mostrado</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Display name:" xml:space="preserve">
<source>Display name:</source>
<target>Nombre mostrado:</target>
<trans-unit id="Discover and join groups" xml:space="preserve">
<source>Discover and join groups</source>
<target>Descubre y únete a grupos</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do NOT use SimpleX for emergency calls." xml:space="preserve">
@@ -1819,6 +1921,16 @@
<target>¿Cifrar base de datos?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Encrypt local files" xml:space="preserve">
<source>Encrypt local files</source>
<target>Cifra archivos locales</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Encrypt stored files &amp; media" xml:space="preserve">
<source>Encrypt stored files &amp; media</source>
<target>Cifra archivos almacenados y multimedia</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Encrypted database" xml:space="preserve">
<source>Encrypted database</source>
<target>Base de datos cifrada</target>
@@ -1864,6 +1976,10 @@
<target>Introduce la contraseña correcta.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enter group name…" xml:space="preserve">
<source>Enter group name…</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enter passphrase…" xml:space="preserve">
<source>Enter passphrase…</source>
<target>Introduce la contraseña…</target>
@@ -1889,6 +2005,10 @@
<target>Introduce mensaje de bienvenida… (opcional)</target>
<note>placeholder</note>
</trans-unit>
<trans-unit id="Enter your name…" xml:space="preserve">
<source>Enter your name…</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error" xml:space="preserve">
<source>Error</source>
<target>Error</target>
@@ -1944,11 +2064,21 @@
<target>Error al crear enlace de grupo</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error creating member contact" xml:space="preserve">
<source>Error creating member contact</source>
<target>Error al establecer contacto con el miembro</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error creating profile!" xml:space="preserve">
<source>Error creating profile!</source>
<target>¡Error al crear perfil!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error decrypting file" xml:space="preserve">
<source>Error decrypting file</source>
<target>Error al descifrar el archivo</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error deleting chat database" xml:space="preserve">
<source>Error deleting chat database</source>
<target>Error al eliminar base de datos</target>
@@ -2069,6 +2199,11 @@
<target>Error al enviar email</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error sending member contact invitation" xml:space="preserve">
<source>Error sending member contact invitation</source>
<target>Error al enviar mensaje de invitación al contacto</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error sending message" xml:space="preserve">
<source>Error sending message</source>
<target>Error al enviar mensaje</target>
@@ -2149,6 +2284,10 @@
<target>Salir sin guardar</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Expand" xml:space="preserve">
<source>Expand</source>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Export database" xml:space="preserve">
<source>Export database</source>
<target>Exportar base de datos</target>
@@ -2294,6 +2433,10 @@
<target>Nombre completo:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Fully decentralized visible only to members." xml:space="preserve">
<source>Fully decentralized visible only to members.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Fully re-implemented - work in background!" xml:space="preserve">
<source>Fully re-implemented - work in background!</source>
<target>Completamente reimplementado: ¡funciona en segundo plano!</target>
@@ -2314,6 +2457,14 @@
<target>Grupo</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Group already exists" xml:space="preserve">
<source>Group already exists</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Group already exists!" xml:space="preserve">
<source>Group already exists!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Group display name" xml:space="preserve">
<source>Group display name</source>
<target>Nombre mostrado del grupo</target>
@@ -2416,12 +2567,12 @@
</trans-unit>
<trans-unit id="Group will be deleted for all members - this cannot be undone!" xml:space="preserve">
<source>Group will be deleted for all members - this cannot be undone!</source>
<target>El grupo se elimina para todos los miembros. ¡No podrá deshacerse!</target>
<target>El grupo se eliminado para todos los miembros. ¡No podrá deshacerse!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Group will be deleted for you - this cannot be undone!" xml:space="preserve">
<source>Group will be deleted for you - this cannot be undone!</source>
<target>El grupo se elimina para tí. ¡No podrá deshacerse!</target>
<target>El grupo se eliminado para tí. ¡No podrá deshacerse!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Help" xml:space="preserve">
@@ -2661,6 +2812,10 @@
<target>Enlace de conexión no válido</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Invalid name!" xml:space="preserve">
<source>Invalid name!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Invalid server address!" xml:space="preserve">
<source>Invalid server address!</source>
<target>¡Dirección de servidor no válida!</target>
@@ -2752,11 +2907,24 @@
<target>Únete al grupo</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Join group?" xml:space="preserve">
<source>Join group?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Join incognito" xml:space="preserve">
<source>Join incognito</source>
<target>Únete en modo incógnito</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Join with current profile" xml:space="preserve">
<source>Join with current profile</source>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Joining group" xml:space="preserve">
<source>Joining group</source>
<target>Entrando al grupo</target>
@@ -2972,6 +3140,10 @@
<target>Mensajes</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Messages from %@ will be shown!" xml:space="preserve">
<source>Messages from %@ will be shown!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Migrating database archive…" xml:space="preserve">
<source>Migrating database archive…</source>
<target>Migrando base de datos…</target>
@@ -3082,6 +3254,11 @@
<target>Nuevo archivo de bases de datos</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New desktop app!" xml:space="preserve">
<source>New desktop app!</source>
<target>Nueva aplicación para PC!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New display name" xml:space="preserve">
<source>New display name</source>
<target>Nuevo nombre mostrado</target>
@@ -3296,6 +3473,11 @@
<target>Sólo tu contacto puede enviar mensajes de voz.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open" xml:space="preserve">
<source>Open</source>
<target>Abrir</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open Settings" xml:space="preserve">
<source>Open Settings</source>
<target>Abrir Configuración</target>
@@ -3311,6 +3493,10 @@
<target>Abrir consola de Chat</target>
<note>authentication reason</note>
</trans-unit>
<trans-unit id="Open group" xml:space="preserve">
<source>Open group</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open user profiles" xml:space="preserve">
<source>Open user profiles</source>
<target>Abrir perfil de usuario</target>
@@ -3326,11 +3512,6 @@
<target>Abriendo base de datos…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red." xml:space="preserve">
<source>Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red.</source>
<target>Abrir el enlace en el navegador puede reducir la privacidad y seguridad de la conexión. Los enlaces SimpleX que no son de confianza aparecerán en rojo.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="PING count" xml:space="preserve">
<source>PING count</source>
<target>Contador PING</target>
@@ -3521,6 +3702,14 @@
<target>Imagen del perfil</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Profile name" xml:space="preserve">
<source>Profile name</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Profile name:" xml:space="preserve">
<source>Profile name:</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Profile password" xml:space="preserve">
<source>Profile password</source>
<target>Contraseña del perfil</target>
@@ -3766,6 +3955,14 @@
<target>¿Renegociar cifrado?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Repeat connection request?" xml:space="preserve">
<source>Repeat connection request?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Repeat join request?" xml:space="preserve">
<source>Repeat join request?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Reply" xml:space="preserve">
<source>Reply</source>
<target>Responder</target>
@@ -4031,6 +4228,11 @@
<target>Enviar mensaje directo</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Send direct message to connect" xml:space="preserve">
<source>Send direct message to connect</source>
<target>Enviar mensaje directo para conectar</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Send disappearing message" xml:space="preserve">
<source>Send disappearing message</source>
<target>Enviar mensaje temporal</target>
@@ -4331,6 +4533,11 @@
<target>Invitación SimpleX de un uso</target>
<note>simplex link type</note>
</trans-unit>
<trans-unit id="Simplified incognito mode" xml:space="preserve">
<source>Simplified incognito mode</source>
<target>Modo incógnito simplificado</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Skip" xml:space="preserve">
<source>Skip</source>
<target>Omitir</target>
@@ -4471,6 +4678,10 @@
<target>Pulsa el botón </target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Tap to Connect" xml:space="preserve">
<source>Tap to Connect</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Tap to activate profile." xml:space="preserve">
<source>Tap to activate profile.</source>
<target>Pulsa sobre un perfil para activarlo.</target>
@@ -4568,11 +4779,6 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida.</target>
<target>El cifrado funciona y un cifrado nuevo no es necesario. ¡Podría dar lugar a errores de conexión!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The group is fully decentralized it is visible only to the members." xml:space="preserve">
<source>The group is fully decentralized it is visible only to the members.</source>
<target>El grupo está totalmente descentralizado y sólo es visible para los miembros.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The hash of the previous message is different." xml:space="preserve">
<source>The hash of the previous message is different.</source>
<target>El hash del mensaje anterior es diferente.</target>
@@ -4668,6 +4874,14 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida.</target>
<target>Este grupo ya no existe.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="This is your own SimpleX address!" xml:space="preserve">
<source>This is your own SimpleX address!</source>
<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>
<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">
<source>This setting applies to messages in your current chat profile **%@**.</source>
<target>Esta configuración se aplica a los mensajes del perfil actual **%@**.</target>
@@ -4725,6 +4939,11 @@ Se te pedirá que completes la autenticación antes de activar esta función.</t
<target>Para comprobar el cifrado de extremo a extremo con tu contacto compara (o escanea) el código en tus dispositivos.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Toggle incognito when connecting." xml:space="preserve">
<source>Toggle incognito when connecting.</source>
<target>Activa incógnito al conectar.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Transport isolation" xml:space="preserve">
<source>Transport isolation</source>
<target>Aislamiento de transporte</target>
@@ -4760,6 +4979,18 @@ Se te pedirá que completes la autenticación antes de activar esta función.</t
<target>No se puede grabar mensaje de voz</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unblock" xml:space="preserve">
<source>Unblock</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unblock member" xml:space="preserve">
<source>Unblock member</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unblock member?" xml:space="preserve">
<source>Unblock member?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unexpected error: %@" xml:space="preserve">
<source>Unexpected error: %@</source>
<target>Error inesperado: %@</target>
@@ -5108,6 +5339,35 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb
<target>Ya estás conectado a %@.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You are already connecting to %@." xml:space="preserve">
<source>You are already connecting to %@.</source>
<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>
<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>
<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>
<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>
<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>
<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>
<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">
<source>You are connected to the server used to receive messages from this contact.</source>
<target>Estás conectado al servidor usado para recibir mensajes de este contacto.</target>
@@ -5203,6 +5463,15 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb
<target>No has podido ser autenticado. Inténtalo de nuevo.</target>
<note>No comment provided by engineer.</note>
</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>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You have no chats" xml:space="preserve">
<source>You have no chats</source>
<target>No tienes chats</target>
@@ -5253,6 +5522,10 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb
<target>Te conectarás al grupo cuando el dispositivo del anfitrión esté en línea, por favor espera o compruébalo más tarde.</target>
<note>No comment provided by engineer.</note>
</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>
<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">
<source>You will be connected when your connection request is accepted, please wait or check later!</source>
<target>Te conectarás cuando tu solicitud se acepte, por favor espera o compruébalo más tarde.</target>
@@ -5268,9 +5541,8 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb
<target>Se te pedirá identificarte cuándo inicies o continues usando la aplicación tras 30 segundos en segundo plano.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You will join a group this link refers to and connect to its group members." xml:space="preserve">
<source>You will join a group this link refers to and connect to its group members.</source>
<target>Te unirás al grupo al que hace referencia este enlace y te conectarás con sus miembros.</target>
<trans-unit id="You will connect to all group members." xml:space="preserve">
<source>You will connect to all group members.</source>
<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">
@@ -5338,11 +5610,6 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb
<target>La base de datos no está cifrada - establece una contraseña para cifrarla.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your chat profile will be sent to group members" xml:space="preserve">
<source>Your chat profile will be sent to group members</source>
<target>Tu perfil será enviado a los miembros del grupo</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your chat profiles" xml:space="preserve">
<source>Your chat profiles</source>
<target>Mis perfiles</target>
@@ -5397,6 +5664,10 @@ Puedes cambiarlo en Configuración.</target>
<target>Privacidad</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your profile" xml:space="preserve">
<source>Your profile</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your profile **%@** will be shared." xml:space="preserve">
<source>Your profile **%@** will be shared.</source>
<target>Tu perfil **%@** será compartido.</target>
@@ -5489,6 +5760,10 @@ Los servidores de SimpleX no pueden ver tu perfil.</target>
<target>siempre</target>
<note>pref value</note>
</trans-unit>
<trans-unit id="and %lld other events" xml:space="preserve">
<source>and %lld other events</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="audio call (not e2e encrypted)" xml:space="preserve">
<source>audio call (not e2e encrypted)</source>
<target>llamada (sin cifrar)</target>
@@ -5504,6 +5779,10 @@ Los servidores de SimpleX no pueden ver tu perfil.</target>
<target>hash de mensaje erróneo</target>
<note>integrity error chat item</note>
</trans-unit>
<trans-unit id="blocked" xml:space="preserve">
<source>blocked</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="bold" xml:space="preserve">
<source>bold</source>
<target>negrita</target>
@@ -5574,6 +5853,11 @@ Los servidores de SimpleX no pueden ver tu perfil.</target>
<target>conectado</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="connected directly" xml:space="preserve">
<source>connected directly</source>
<target>conectado directamente</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="connecting" xml:space="preserve">
<source>connecting</source>
<target>conectando</target>
@@ -5669,6 +5953,10 @@ Los servidores de SimpleX no pueden ver tu perfil.</target>
<target>eliminado</target>
<note>deleted chat item</note>
</trans-unit>
<trans-unit id="deleted contact" xml:space="preserve">
<source>deleted contact</source>
<note>rcv direct event chat item</note>
</trans-unit>
<trans-unit id="deleted group" xml:space="preserve">
<source>deleted group</source>
<target>grupo eliminado</target>
@@ -5771,6 +6059,7 @@ Los servidores de SimpleX no pueden ver tu perfil.</target>
</trans-unit>
<trans-unit id="event happened" xml:space="preserve">
<source>event happened</source>
<target>evento ocurrido</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group deleted" xml:space="preserve">
@@ -5952,7 +6241,8 @@ Los servidores de SimpleX no pueden ver tu perfil.</target>
<source>off</source>
<target>desactivado</target>
<note>enabled status
group pref value</note>
group pref value
time to disappear</note>
</trans-unit>
<trans-unit id="offered %@" xml:space="preserve">
<source>offered %@</source>
@@ -5969,11 +6259,6 @@ Los servidores de SimpleX no pueden ver tu perfil.</target>
<target>Activado</target>
<note>group pref value</note>
</trans-unit>
<trans-unit id="or chat with the developers" xml:space="preserve">
<source>or chat with the developers</source>
<target>o contacta mediante Chat con los desarrolladores</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="owner" xml:space="preserve">
<source>owner</source>
<target>propietario</target>
@@ -6034,6 +6319,11 @@ Los servidores de SimpleX no pueden ver tu perfil.</target>
<target>código de seguridad cambiado</target>
<note>chat item text</note>
</trans-unit>
<trans-unit id="send direct message" xml:space="preserve">
<source>send direct message</source>
<target>Enviar mensaje directo</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="starting…" xml:space="preserve">
<source>starting…</source>
<target>inicializando…</target>
@@ -6178,7 +6468,7 @@ Los servidores de SimpleX no pueden ver tu perfil.</target>
</file>
<file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="es" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A5219j"/>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A240d"/>
</header>
<body>
<trans-unit id="CFBundleName" xml:space="preserve">
@@ -6210,7 +6500,7 @@ Los servidores de SimpleX no pueden ver tu perfil.</target>
</file>
<file original="SimpleX NSE/en.lproj/InfoPlist.strings" source-language="en" target-language="es" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A5219j"/>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A240d"/>
</header>
<body>
<trans-unit id="CFBundleDisplayName" xml:space="preserve">

View File

@@ -3,7 +3,7 @@
"project" : "SimpleX.xcodeproj",
"targetLocale" : "es",
"toolInfo" : {
"toolBuildNumber" : "15A5219j",
"toolBuildNumber" : "15A240d",
"toolID" : "com.apple.dt.xcode",
"toolName" : "Xcode",
"toolVersion" : "15.0"

View File

@@ -0,0 +1,15 @@
{
"colors" : [
{
"idiom" : "universal",
"locale" : "fi"
}
],
"properties" : {
"localizable" : true
},
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,23 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"red" : "0.000",
"alpha" : "1.000",
"blue" : "1.000",
"green" : "0.533"
}
},
"idiom" : "universal"
}
],
"properties" : {
"localizable" : true
},
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,6 @@
/* Bundle display name */
"CFBundleDisplayName" = "SimpleX NSE";
/* Bundle name */
"CFBundleName" = "SimpleX NSE";
/* Copyright (human-readable) */
"NSHumanReadableCopyright" = "Copyright © 2022 SimpleX Chat. All rights reserved.";

View File

@@ -0,0 +1,30 @@
/* No comment provided by engineer. */
"_italic_" = "\\_italic_";
/* No comment provided by engineer. */
"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact.";
/* No comment provided by engineer. */
"*bold*" = "\\*bold*";
/* No comment provided by engineer. */
"`a + b`" = "\\`a + b`";
/* No comment provided by engineer. */
"~strike~" = "\\~strike~";
/* call status */
"connecting call" = "connecting call…";
/* No comment provided by engineer. */
"Connecting server…" = "Connecting to server…";
/* No comment provided by engineer. */
"Connecting server… (error: %@)" = "Connecting to server… (error: %@)";
/* rcv group event chat item */
"member connected" = "connected";
/* No comment provided by engineer. */
"No group!" = "Group not found!";

View File

@@ -0,0 +1,10 @@
/* Bundle name */
"CFBundleName" = "SimpleX";
/* Privacy - Camera Usage Description */
"NSCameraUsageDescription" = "SimpleX needs camera access to scan QR codes to connect to other users and for video calls.";
/* Privacy - Face ID Usage Description */
"NSFaceIDUsageDescription" = "SimpleX uses Face ID for local authentication";
/* Privacy - Microphone Usage Description */
"NSMicrophoneUsageDescription" = "SimpleX needs microphone access for audio and video calls, and to record voice messages.";
/* Privacy - Photo Library Additions Usage Description */
"NSPhotoLibraryAddUsageDescription" = "SimpleX needs access to Photo Library for saving captured and received media";

View File

@@ -0,0 +1,12 @@
{
"developmentRegion" : "en",
"project" : "SimpleX.xcodeproj",
"targetLocale" : "fi",
"toolInfo" : {
"toolBuildNumber" : "15A240d",
"toolID" : "com.apple.dt.xcode",
"toolName" : "Xcode",
"toolVersion" : "15.0"
},
"version" : "1.0"
}

View File

@@ -2,7 +2,7 @@
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd">
<file original="en.lproj/Localizable.strings" source-language="en" target-language="fr" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A5219j"/>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A240d"/>
</header>
<body>
<trans-unit id="&#10;" xml:space="preserve">
@@ -87,6 +87,10 @@
<target>%@ / %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@ and %@" xml:space="preserve">
<source>%@ and %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@ and %@ connected" xml:space="preserve">
<source>%@ and %@ connected</source>
<target>%@ et %@ sont connecté.es</target>
@@ -97,6 +101,10 @@
<target>%1$@ à %2$@:</target>
<note>copied message info, &lt;sender&gt; at &lt;time&gt;</note>
</trans-unit>
<trans-unit id="%@ connected" xml:space="preserve">
<source>%@ connected</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@ is connected!" xml:space="preserve">
<source>%@ is connected!</source>
<target>%@ est connecté·e !</target>
@@ -122,6 +130,10 @@
<target>%@ veut se connecter !</target>
<note>notification title</note>
</trans-unit>
<trans-unit id="%@, %@ and %lld members" xml:space="preserve">
<source>%@, %@ and %lld members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@, %@ and %lld other members connected" xml:space="preserve">
<source>%@, %@ and %lld other members connected</source>
<target>%@, %@ et %lld autres membres sont connectés</target>
@@ -187,16 +199,37 @@
<target>%lld fichier·s pour une taille totale de %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld group events" xml:space="preserve">
<source>%lld group events</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld members" xml:space="preserve">
<source>%lld members</source>
<target>%lld membres</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld messages blocked" xml:space="preserve">
<source>%lld messages blocked</source>
<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>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld minutes" xml:space="preserve">
<source>%lld minutes</source>
<target>%lld minutes</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld new interface languages" xml:space="preserve">
<source>%lld new interface languages</source>
<target>%lld nouvelles langues d'interface</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld second(s)" xml:space="preserve">
<source>%lld second(s)</source>
<target>%lld seconde·s</target>
@@ -327,6 +360,15 @@
<target>, </target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- connect to [directory service](simplex:/contact#/?v=1-4&amp;smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!&#10;- delivery receipts (up to 20 members).&#10;- faster and more stable." xml:space="preserve">
<source>- connect to [directory service](simplex:/contact#/?v=1-4&amp;smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!
- delivery receipts (up to 20 members).
- faster and more stable.</source>
<target>- connexion au [service d'annuaire](simplex:/contact#/?v=1-4&amp;smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA) !
- les accusés de réception (jusqu'à 20 membres).
- plus rapide et plus stable.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- more stable message delivery.&#10;- a bit better groups.&#10;- and more!" xml:space="preserve">
<source>- more stable message delivery.
- a bit better groups.
@@ -350,6 +392,10 @@
<target>.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="0 sec" xml:space="preserve">
<source>0 sec</source>
<note>time to disappear</note>
</trans-unit>
<trans-unit id="0s" xml:space="preserve">
<source>0s</source>
<target>0s</target>
@@ -575,6 +621,10 @@
<target>Tous les messages seront supprimés - impossible de revenir en arrière ! Les messages seront supprimés UNIQUEMENT pour vous.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="All new messages from %@ will be hidden!" xml:space="preserve">
<source>All new messages from %@ will be hidden!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="All your contacts will remain connected." xml:space="preserve">
<source>All your contacts will remain connected.</source>
<target>Tous vos contacts resteront connectés.</target>
@@ -680,6 +730,14 @@
<target>Déjà connecté ?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Already connecting!" xml:space="preserve">
<source>Already connecting!</source>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Always use relay" xml:space="preserve">
<source>Always use relay</source>
<target>Se connecter via relais</target>
@@ -700,6 +758,11 @@
<target>Build de l'app : %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="App encrypts new local files (except videos)." xml:space="preserve">
<source>App encrypts new local files (except videos).</source>
<target>L'application chiffre les nouveaux fichiers locaux (sauf les vidéos).</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="App icon" xml:space="preserve">
<source>App icon</source>
<target>Icône de l'app</target>
@@ -810,6 +873,18 @@
<target>Meilleurs messages</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block" xml:space="preserve">
<source>Block</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>
</trans-unit>
<trans-unit id="Block member?" xml:space="preserve">
<source>Block member?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Both you and your contact can add message reactions." xml:space="preserve">
<source>Both you and your contact can add message reactions.</source>
<target>Vous et votre contact pouvez ajouter des réactions aux messages.</target>
@@ -835,6 +910,11 @@
<target>Vous et votre contact êtes tous deux en mesure d'envoyer des messages vocaux.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" xml:space="preserve">
<source>Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!</source>
<target>Bulgare, finnois, thaïlandais et ukrainien - grâce aux utilisateurs et à [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat) !</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." xml:space="preserve">
<source>By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).</source>
<target>Par profil de chat (par défaut) ou [par connexion](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).</target>
@@ -1066,24 +1146,27 @@
<target>Se connecter</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Connect directly" xml:space="preserve">
<source>Connect directly</source>
<target>Se connecter directement</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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect via contact link" xml:space="preserve">
<source>Connect via contact link</source>
<target>Se connecter via un lien de contact</target>
<trans-unit id="Connect to yourself?" xml:space="preserve">
<source>Connect to yourself?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect via group link?" xml:space="preserve">
<source>Connect via group link?</source>
<target>Se connecter via le lien du groupe ?</target>
<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>
<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>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect via link" xml:space="preserve">
@@ -1101,6 +1184,10 @@
<target>Se connecter via un lien unique</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect with %@" xml:space="preserve">
<source>Connect with %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connecting server…" xml:space="preserve">
<source>Connecting to server…</source>
<target>Connexion au serveur…</target>
@@ -1146,11 +1233,6 @@
<target>Contact déjà existant</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Contact and all messages will be deleted - this cannot be undone!" xml:space="preserve">
<source>Contact and all messages will be deleted - this cannot be undone!</source>
<target>Le contact et tous les messages seront supprimés - impossible de revenir en arrière !</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Contact hidden:" xml:space="preserve">
<source>Contact hidden:</source>
<target>Contact masqué:</target>
@@ -1201,6 +1283,10 @@
<target>Version du cœur : v%@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Correct name to %@?" xml:space="preserve">
<source>Correct name to %@?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create" xml:space="preserve">
<source>Create</source>
<target>Créer</target>
@@ -1221,6 +1307,10 @@
<target>Créer un fichier</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Create group" xml:space="preserve">
<source>Create group</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create group link" xml:space="preserve">
<source>Create group link</source>
<target>Créer un lien de groupe</target>
@@ -1231,11 +1321,20 @@
<target>Créer un lien</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" xml:space="preserve">
<source>Create new profile in [desktop app](https://simplex.chat/downloads/). 💻</source>
<target>Créer un nouveau profil sur [l'application de bureau](https://simplex.chat/downloads/). 💻</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create one-time invitation link" xml:space="preserve">
<source>Create one-time invitation link</source>
<target>Créer un lien d'invitation unique</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create profile" xml:space="preserve">
<source>Create profile</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create queue" xml:space="preserve">
<source>Create queue</source>
<target>Créer une file d'attente</target>
@@ -1394,6 +1493,10 @@
<target>Supprimer</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Delete %lld messages?" xml:space="preserve">
<source>Delete %lld messages?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete Contact" xml:space="preserve">
<source>Delete Contact</source>
<target>Supprimer le contact</target>
@@ -1419,6 +1522,10 @@
<target>Effacer tous les fichiers</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete and notify contact" xml:space="preserve">
<source>Delete and notify contact</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete archive" xml:space="preserve">
<source>Delete archive</source>
<target>Supprimer l'archive</target>
@@ -1449,9 +1556,9 @@
<target>Supprimer le contact</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete contact?" xml:space="preserve">
<source>Delete contact?</source>
<target>Supprimer le contact ?</target>
<trans-unit id="Delete contact?&#10;This cannot be undone!" xml:space="preserve">
<source>Delete contact?
This cannot be undone!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete database" xml:space="preserve">
@@ -1684,14 +1791,9 @@
<target>Se déconnecter</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Display name" xml:space="preserve">
<source>Display name</source>
<target>Nom affiché</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Display name:" xml:space="preserve">
<source>Display name:</source>
<target>Nom affiché :</target>
<trans-unit id="Discover and join groups" xml:space="preserve">
<source>Discover and join groups</source>
<target>Découvrir et rejoindre des groupes</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do NOT use SimpleX for emergency calls." xml:space="preserve">
@@ -1819,6 +1921,16 @@
<target>Chiffrer la base de données ?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Encrypt local files" xml:space="preserve">
<source>Encrypt local files</source>
<target>Chiffrer les fichiers locaux</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Encrypt stored files &amp; media" xml:space="preserve">
<source>Encrypt stored files &amp; media</source>
<target>Chiffrement des fichiers et des médias stockés</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Encrypted database" xml:space="preserve">
<source>Encrypted database</source>
<target>Base de données chiffrée</target>
@@ -1864,6 +1976,10 @@
<target>Entrez la phrase secrète correcte.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enter group name…" xml:space="preserve">
<source>Enter group name…</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enter passphrase…" xml:space="preserve">
<source>Enter passphrase…</source>
<target>Entrez la phrase secrète…</target>
@@ -1889,6 +2005,10 @@
<target>Entrez un message de bienvenue… (facultatif)</target>
<note>placeholder</note>
</trans-unit>
<trans-unit id="Enter your name…" xml:space="preserve">
<source>Enter your name…</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error" xml:space="preserve">
<source>Error</source>
<target>Erreur</target>
@@ -1944,11 +2064,21 @@
<target>Erreur lors de la création du lien du groupe</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error creating member contact" xml:space="preserve">
<source>Error creating member contact</source>
<target>Erreur lors de la création du contact du membre</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error creating profile!" xml:space="preserve">
<source>Error creating profile!</source>
<target>Erreur lors de la création du profil !</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error decrypting file" xml:space="preserve">
<source>Error decrypting file</source>
<target>Erreur lors du déchiffrement du fichier</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error deleting chat database" xml:space="preserve">
<source>Error deleting chat database</source>
<target>Erreur lors de la suppression de la base de données du chat</target>
@@ -2069,6 +2199,11 @@
<target>Erreur lors de l'envoi de l'e-mail</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error sending member contact invitation" xml:space="preserve">
<source>Error sending member contact invitation</source>
<target>Erreur lors de l'envoi de l'invitation de contact d'un membre</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error sending message" xml:space="preserve">
<source>Error sending message</source>
<target>Erreur lors de l'envoi du message</target>
@@ -2149,6 +2284,10 @@
<target>Quitter sans sauvegarder</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Expand" xml:space="preserve">
<source>Expand</source>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Export database" xml:space="preserve">
<source>Export database</source>
<target>Exporter la base de données</target>
@@ -2294,6 +2433,10 @@
<target>Nom complet :</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Fully decentralized visible only to members." xml:space="preserve">
<source>Fully decentralized visible only to members.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Fully re-implemented - work in background!" xml:space="preserve">
<source>Fully re-implemented - work in background!</source>
<target>Entièrement réimplémenté - fonctionne en arrière-plan !</target>
@@ -2314,6 +2457,14 @@
<target>Groupe</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Group already exists" xml:space="preserve">
<source>Group already exists</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Group already exists!" xml:space="preserve">
<source>Group already exists!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Group display name" xml:space="preserve">
<source>Group display name</source>
<target>Nom d'affichage du groupe</target>
@@ -2661,6 +2812,10 @@
<target>Lien de connection invalide</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Invalid name!" xml:space="preserve">
<source>Invalid name!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Invalid server address!" xml:space="preserve">
<source>Invalid server address!</source>
<target>Adresse de serveur invalide !</target>
@@ -2752,11 +2907,24 @@
<target>Rejoindre le groupe</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Join group?" xml:space="preserve">
<source>Join group?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Join incognito" xml:space="preserve">
<source>Join incognito</source>
<target>Rejoindre en incognito</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Join with current profile" xml:space="preserve">
<source>Join with current profile</source>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Joining group" xml:space="preserve">
<source>Joining group</source>
<target>Entrain de rejoindre le groupe</target>
@@ -2972,6 +3140,10 @@
<target>Messages</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Messages from %@ will be shown!" xml:space="preserve">
<source>Messages from %@ will be shown!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Migrating database archive…" xml:space="preserve">
<source>Migrating database archive…</source>
<target>Migration de l'archive de la base de données…</target>
@@ -3082,6 +3254,11 @@
<target>Nouvelle archive de base de données</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New desktop app!" xml:space="preserve">
<source>New desktop app!</source>
<target>Nouvelle application de bureau !</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New display name" xml:space="preserve">
<source>New display name</source>
<target>Nouveau nom d'affichage</target>
@@ -3296,6 +3473,11 @@
<target>Seul votre contact peut envoyer des messages vocaux.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open" xml:space="preserve">
<source>Open</source>
<target>Ouvrir</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open Settings" xml:space="preserve">
<source>Open Settings</source>
<target>Ouvrir les Paramètres</target>
@@ -3311,6 +3493,10 @@
<target>Ouvrir la console du chat</target>
<note>authentication reason</note>
</trans-unit>
<trans-unit id="Open group" xml:space="preserve">
<source>Open group</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open user profiles" xml:space="preserve">
<source>Open user profiles</source>
<target>Ouvrir les profils d'utilisateurs</target>
@@ -3326,11 +3512,6 @@
<target>Ouverture de la base de données…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red." xml:space="preserve">
<source>Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red.</source>
<target>Ouvrir le lien dans le navigateur peut réduire la confidentialité et la sécurité de la connexion. Les liens SimpleX non fiables seront en rouge.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="PING count" xml:space="preserve">
<source>PING count</source>
<target>Nombre de PING</target>
@@ -3521,6 +3702,14 @@
<target>Image de profil</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Profile name" xml:space="preserve">
<source>Profile name</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Profile name:" xml:space="preserve">
<source>Profile name:</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Profile password" xml:space="preserve">
<source>Profile password</source>
<target>Mot de passe de profil</target>
@@ -3766,6 +3955,14 @@
<target>Renégocier le chiffrement?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Repeat connection request?" xml:space="preserve">
<source>Repeat connection request?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Repeat join request?" xml:space="preserve">
<source>Repeat join request?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Reply" xml:space="preserve">
<source>Reply</source>
<target>Répondre</target>
@@ -4028,7 +4225,12 @@
</trans-unit>
<trans-unit id="Send direct message" xml:space="preserve">
<source>Send direct message</source>
<target>Envoi de message direct</target>
<target>Envoyer un message direct</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Send direct message to connect" xml:space="preserve">
<source>Send direct message to connect</source>
<target>Envoyer un message direct pour vous connecter</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Send disappearing message" xml:space="preserve">
@@ -4331,6 +4533,11 @@
<target>Invitation unique SimpleX</target>
<note>simplex link type</note>
</trans-unit>
<trans-unit id="Simplified incognito mode" xml:space="preserve">
<source>Simplified incognito mode</source>
<target>Mode incognito simplifié</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Skip" xml:space="preserve">
<source>Skip</source>
<target>Passer</target>
@@ -4471,6 +4678,10 @@
<target>Appuyez sur le bouton </target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Tap to Connect" xml:space="preserve">
<source>Tap to Connect</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Tap to activate profile." xml:space="preserve">
<source>Tap to activate profile.</source>
<target>Appuyez pour activer un profil.</target>
@@ -4568,11 +4779,6 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise.
<target>Le chiffrement fonctionne et le nouvel accord de chiffrement n'est pas nécessaire. Cela peut provoquer des erreurs de connexion !</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The group is fully decentralized it is visible only to the members." xml:space="preserve">
<source>The group is fully decentralized it is visible only to the members.</source>
<target>Le groupe est entièrement décentralisé il n'est visible que par ses membres.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The hash of the previous message is different." xml:space="preserve">
<source>The hash of the previous message is different.</source>
<target>Le hash du message précédent est différent.</target>
@@ -4668,6 +4874,14 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise.
<target>Ce groupe n'existe plus.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="This is your own SimpleX address!" xml:space="preserve">
<source>This is your own SimpleX address!</source>
<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>
<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">
<source>This setting applies to messages in your current chat profile **%@**.</source>
<target>Ce paramètre s'applique aux messages de votre profil de chat actuel **%@**.</target>
@@ -4725,6 +4939,11 @@ Vous serez invité à confirmer l'authentification avant que cette fonction ne s
<target>Pour vérifier le chiffrement de bout en bout avec votre contact, comparez (ou scannez) le code sur vos appareils.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Toggle incognito when connecting." xml:space="preserve">
<source>Toggle incognito when connecting.</source>
<target>Basculer en mode incognito lors de la connexion.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Transport isolation" xml:space="preserve">
<source>Transport isolation</source>
<target>Transport isolé</target>
@@ -4760,6 +4979,18 @@ Vous serez invité à confirmer l'authentification avant que cette fonction ne s
<target>Impossible d'enregistrer un message vocal</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unblock" xml:space="preserve">
<source>Unblock</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unblock member" xml:space="preserve">
<source>Unblock member</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unblock member?" xml:space="preserve">
<source>Unblock member?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unexpected error: %@" xml:space="preserve">
<source>Unexpected error: %@</source>
<target>Erreur inattendue: %@</target>
@@ -5107,6 +5338,35 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien
<target>Vous êtes déjà connecté·e à %@ via ce lien.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You are already connecting to %@." xml:space="preserve">
<source>You are already connecting to %@.</source>
<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>
<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>
<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>
<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>
<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>
<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>
<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">
<source>You are connected to the server used to receive messages from this contact.</source>
<target>Vous êtes connecté·e au serveur utilisé pour recevoir les messages de ce contact.</target>
@@ -5202,6 +5462,15 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien
<target>Vous n'avez pas pu être vérifié·e; veuillez réessayer.</target>
<note>No comment provided by engineer.</note>
</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>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You have no chats" xml:space="preserve">
<source>You have no chats</source>
<target>Vous n'avez aucune discussion</target>
@@ -5252,6 +5521,10 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien
<target>Vous serez connecté·e au groupe lorsque l'appareil de l'hôte sera en ligne, veuillez attendre ou vérifier plus tard !</target>
<note>No comment provided by engineer.</note>
</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>
<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">
<source>You will be connected when your connection request is accepted, please wait or check later!</source>
<target>Vous serez connecté·e lorsque votre demande de connexion sera acceptée, veuillez attendre ou vérifier plus tard !</target>
@@ -5267,9 +5540,8 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien
<target>Il vous sera demandé de vous authentifier lorsque vous démarrez ou reprenez l'application après 30 secondes en arrière-plan.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You will join a group this link refers to and connect to its group members." xml:space="preserve">
<source>You will join a group this link refers to and connect to its group members.</source>
<target>Vous allez rejoindre le groupe correspondant à ce lien et être mis en relation avec les autres membres du groupe.</target>
<trans-unit id="You will connect to all group members." xml:space="preserve">
<source>You will connect to all group members.</source>
<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">
@@ -5337,11 +5609,6 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien
<target>Votre base de données de chat n'est pas chiffrée - définisez une phrase secrète.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your chat profile will be sent to group members" xml:space="preserve">
<source>Your chat profile will be sent to group members</source>
<target>Votre profil de chat sera envoyé aux membres du groupe</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your chat profiles" xml:space="preserve">
<source>Your chat profiles</source>
<target>Vos profils de chat</target>
@@ -5396,6 +5663,10 @@ Vous pouvez modifier ce choix dans les Paramètres.</target>
<target>Votre vie privée</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your profile" xml:space="preserve">
<source>Your profile</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your profile **%@** will be shared." xml:space="preserve">
<source>Your profile **%@** will be shared.</source>
<target>Votre profil **%@** sera partagé.</target>
@@ -5488,6 +5759,10 @@ Les serveurs SimpleX ne peuvent pas voir votre profil.</target>
<target>toujours</target>
<note>pref value</note>
</trans-unit>
<trans-unit id="and %lld other events" xml:space="preserve">
<source>and %lld other events</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="audio call (not e2e encrypted)" xml:space="preserve">
<source>audio call (not e2e encrypted)</source>
<target>appel audio (sans chiffrement)</target>
@@ -5503,6 +5778,10 @@ Les serveurs SimpleX ne peuvent pas voir votre profil.</target>
<target>hash de message incorrect</target>
<note>integrity error chat item</note>
</trans-unit>
<trans-unit id="blocked" xml:space="preserve">
<source>blocked</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="bold" xml:space="preserve">
<source>bold</source>
<target>gras</target>
@@ -5573,6 +5852,11 @@ Les serveurs SimpleX ne peuvent pas voir votre profil.</target>
<target>connecté</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="connected directly" xml:space="preserve">
<source>connected directly</source>
<target>s'est connecté.e de manière directe</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="connecting" xml:space="preserve">
<source>connecting</source>
<target>connexion</target>
@@ -5668,6 +5952,10 @@ Les serveurs SimpleX ne peuvent pas voir votre profil.</target>
<target>supprimé</target>
<note>deleted chat item</note>
</trans-unit>
<trans-unit id="deleted contact" xml:space="preserve">
<source>deleted contact</source>
<note>rcv direct event chat item</note>
</trans-unit>
<trans-unit id="deleted group" xml:space="preserve">
<source>deleted group</source>
<target>groupe supprimé</target>
@@ -5952,7 +6240,8 @@ Les serveurs SimpleX ne peuvent pas voir votre profil.</target>
<source>off</source>
<target>off</target>
<note>enabled status
group pref value</note>
group pref value
time to disappear</note>
</trans-unit>
<trans-unit id="offered %@" xml:space="preserve">
<source>offered %@</source>
@@ -5969,11 +6258,6 @@ Les serveurs SimpleX ne peuvent pas voir votre profil.</target>
<target>on</target>
<note>group pref value</note>
</trans-unit>
<trans-unit id="or chat with the developers" xml:space="preserve">
<source>or chat with the developers</source>
<target>ou ici pour discuter avec les développeurs</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="owner" xml:space="preserve">
<source>owner</source>
<target>propriétaire</target>
@@ -6034,6 +6318,11 @@ Les serveurs SimpleX ne peuvent pas voir votre profil.</target>
<target>code de sécurité modifié</target>
<note>chat item text</note>
</trans-unit>
<trans-unit id="send direct message" xml:space="preserve">
<source>send direct message</source>
<target>envoyer un message direct</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="starting…" xml:space="preserve">
<source>starting…</source>
<target>lancement…</target>
@@ -6178,7 +6467,7 @@ Les serveurs SimpleX ne peuvent pas voir votre profil.</target>
</file>
<file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="fr" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A5219j"/>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A240d"/>
</header>
<body>
<trans-unit id="CFBundleName" xml:space="preserve">
@@ -6210,7 +6499,7 @@ Les serveurs SimpleX ne peuvent pas voir votre profil.</target>
</file>
<file original="SimpleX NSE/en.lproj/InfoPlist.strings" source-language="en" target-language="fr" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A5219j"/>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A240d"/>
</header>
<body>
<trans-unit id="CFBundleDisplayName" xml:space="preserve">

View File

@@ -3,7 +3,7 @@
"project" : "SimpleX.xcodeproj",
"targetLocale" : "fr",
"toolInfo" : {
"toolBuildNumber" : "15A5219j",
"toolBuildNumber" : "15A240d",
"toolID" : "com.apple.dt.xcode",
"toolName" : "Xcode",
"toolVersion" : "15.0"

View File

@@ -2,7 +2,7 @@
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd">
<file original="en.lproj/Localizable.strings" source-language="en" target-language="it" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A5219j"/>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A240d"/>
</header>
<body>
<trans-unit id="&#10;" xml:space="preserve">
@@ -87,6 +87,10 @@
<target>%@ / %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@ and %@" xml:space="preserve">
<source>%@ and %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@ and %@ connected" xml:space="preserve">
<source>%@ and %@ connected</source>
<target>%@ e %@ sono connessi/e</target>
@@ -97,6 +101,10 @@
<target>%1$@ alle %2$@:</target>
<note>copied message info, &lt;sender&gt; at &lt;time&gt;</note>
</trans-unit>
<trans-unit id="%@ connected" xml:space="preserve">
<source>%@ connected</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@ is connected!" xml:space="preserve">
<source>%@ is connected!</source>
<target>%@ è connesso/a!</target>
@@ -122,6 +130,10 @@
<target>%@ si vuole connettere!</target>
<note>notification title</note>
</trans-unit>
<trans-unit id="%@, %@ and %lld members" xml:space="preserve">
<source>%@, %@ and %lld members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@, %@ and %lld other members connected" xml:space="preserve">
<source>%@, %@ and %lld other members connected</source>
<target>%@, %@ e altri %lld membri sono connessi</target>
@@ -187,16 +199,37 @@
<target>%lld file con dimensione totale di %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld group events" xml:space="preserve">
<source>%lld group events</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld members" xml:space="preserve">
<source>%lld members</source>
<target>%lld membri</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld messages blocked" xml:space="preserve">
<source>%lld messages blocked</source>
<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>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld minutes" xml:space="preserve">
<source>%lld minutes</source>
<target>%lld minuti</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld new interface languages" xml:space="preserve">
<source>%lld new interface languages</source>
<target>%lld nuove lingue dell'interfaccia</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld second(s)" xml:space="preserve">
<source>%lld second(s)</source>
<target>%lld secondo/i</target>
@@ -327,6 +360,15 @@
<target>, </target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- connect to [directory service](simplex:/contact#/?v=1-4&amp;smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!&#10;- delivery receipts (up to 20 members).&#10;- faster and more stable." xml:space="preserve">
<source>- connect to [directory service](simplex:/contact#/?v=1-4&amp;smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!
- delivery receipts (up to 20 members).
- faster and more stable.</source>
<target>- connessione al [servizio directory](simplex:/contact#/?v=1-4&amp;smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!
- ricevute di consegna (fino a 20 membri).
- più veloce e più stabile.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- more stable message delivery.&#10;- a bit better groups.&#10;- and more!" xml:space="preserve">
<source>- more stable message delivery.
- a bit better groups.
@@ -350,6 +392,10 @@
<target>.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="0 sec" xml:space="preserve">
<source>0 sec</source>
<note>time to disappear</note>
</trans-unit>
<trans-unit id="0s" xml:space="preserve">
<source>0s</source>
<target>0s</target>
@@ -575,6 +621,10 @@
<target>Tutti i messaggi verranno eliminati, non è reversibile! I messaggi verranno eliminati SOLO per te.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="All new messages from %@ will be hidden!" xml:space="preserve">
<source>All new messages from %@ will be hidden!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="All your contacts will remain connected." xml:space="preserve">
<source>All your contacts will remain connected.</source>
<target>Tutti i tuoi contatti resteranno connessi.</target>
@@ -680,6 +730,14 @@
<target>Già connesso/a?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Already connecting!" xml:space="preserve">
<source>Already connecting!</source>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Always use relay" xml:space="preserve">
<source>Always use relay</source>
<target>Connetti via relay</target>
@@ -700,6 +758,11 @@
<target>Build dell'app: %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="App encrypts new local files (except videos)." xml:space="preserve">
<source>App encrypts new local files (except videos).</source>
<target>L'app cripta i nuovi file locali (eccetto i video).</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="App icon" xml:space="preserve">
<source>App icon</source>
<target>Icona app</target>
@@ -810,6 +873,18 @@
<target>Messaggi migliorati</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block" xml:space="preserve">
<source>Block</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>
</trans-unit>
<trans-unit id="Block member?" xml:space="preserve">
<source>Block member?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Both you and your contact can add message reactions." xml:space="preserve">
<source>Both you and your contact can add message reactions.</source>
<target>Sia tu che il tuo contatto potete aggiungere reazioni ai messaggi.</target>
@@ -835,6 +910,11 @@
<target>Sia tu che il tuo contatto potete inviare messaggi vocali.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" xml:space="preserve">
<source>Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!</source>
<target>Bulgaro, finlandese, tailandese e ucraino - grazie agli utenti e a [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." xml:space="preserve">
<source>By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).</source>
<target>Per profilo di chat (predefinito) o [per connessione](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).</target>
@@ -1066,24 +1146,27 @@
<target>Connetti</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Connect directly" xml:space="preserve">
<source>Connect directly</source>
<target>Connetti direttamente</target>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect via contact link" xml:space="preserve">
<source>Connect via contact link</source>
<target>Connetti via link del contatto</target>
<trans-unit id="Connect to yourself?" xml:space="preserve">
<source>Connect to yourself?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect via group link?" xml:space="preserve">
<source>Connect via group link?</source>
<target>Connettere via link del gruppo?</target>
<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>
<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>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect via link" xml:space="preserve">
@@ -1101,6 +1184,10 @@
<target>Connetti via link una tantum</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect with %@" xml:space="preserve">
<source>Connect with %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connecting server…" xml:space="preserve">
<source>Connecting to server…</source>
<target>Connessione al server…</target>
@@ -1146,11 +1233,6 @@
<target>Il contatto esiste già</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Contact and all messages will be deleted - this cannot be undone!" xml:space="preserve">
<source>Contact and all messages will be deleted - this cannot be undone!</source>
<target>Il contatto e tutti i messaggi verranno eliminati, non è reversibile!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Contact hidden:" xml:space="preserve">
<source>Contact hidden:</source>
<target>Contatto nascosto:</target>
@@ -1201,6 +1283,10 @@
<target>Versione core: v%@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Correct name to %@?" xml:space="preserve">
<source>Correct name to %@?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create" xml:space="preserve">
<source>Create</source>
<target>Crea</target>
@@ -1221,6 +1307,10 @@
<target>Crea file</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Create group" xml:space="preserve">
<source>Create group</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create group link" xml:space="preserve">
<source>Create group link</source>
<target>Crea link del gruppo</target>
@@ -1231,11 +1321,20 @@
<target>Crea link</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" xml:space="preserve">
<source>Create new profile in [desktop app](https://simplex.chat/downloads/). 💻</source>
<target>Crea un nuovo profilo nell'[app desktop](https://simplex.chat/downloads/). 💻</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create one-time invitation link" xml:space="preserve">
<source>Create one-time invitation link</source>
<target>Crea link di invito una tantum</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create profile" xml:space="preserve">
<source>Create profile</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create queue" xml:space="preserve">
<source>Create queue</source>
<target>Crea coda</target>
@@ -1394,6 +1493,10 @@
<target>Elimina</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Delete %lld messages?" xml:space="preserve">
<source>Delete %lld messages?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete Contact" xml:space="preserve">
<source>Delete Contact</source>
<target>Elimina contatto</target>
@@ -1419,6 +1522,10 @@
<target>Elimina tutti i file</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete and notify contact" xml:space="preserve">
<source>Delete and notify contact</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete archive" xml:space="preserve">
<source>Delete archive</source>
<target>Elimina archivio</target>
@@ -1449,9 +1556,9 @@
<target>Elimina contatto</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete contact?" xml:space="preserve">
<source>Delete contact?</source>
<target>Eliminare il contatto?</target>
<trans-unit id="Delete contact?&#10;This cannot be undone!" xml:space="preserve">
<source>Delete contact?
This cannot be undone!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete database" xml:space="preserve">
@@ -1684,14 +1791,9 @@
<target>Disconnetti</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Display name" xml:space="preserve">
<source>Display name</source>
<target>Nome da mostrare</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Display name:" xml:space="preserve">
<source>Display name:</source>
<target>Nome da mostrare:</target>
<trans-unit id="Discover and join groups" xml:space="preserve">
<source>Discover and join groups</source>
<target>Scopri ed unisciti ai gruppi</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do NOT use SimpleX for emergency calls." xml:space="preserve">
@@ -1819,6 +1921,16 @@
<target>Crittografare il database?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Encrypt local files" xml:space="preserve">
<source>Encrypt local files</source>
<target>Cripta i file locali</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Encrypt stored files &amp; media" xml:space="preserve">
<source>Encrypt stored files &amp; media</source>
<target>Crittografia di file e media memorizzati</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Encrypted database" xml:space="preserve">
<source>Encrypted database</source>
<target>Database crittografato</target>
@@ -1864,6 +1976,10 @@
<target>Inserisci la password giusta.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enter group name…" xml:space="preserve">
<source>Enter group name…</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enter passphrase…" xml:space="preserve">
<source>Enter passphrase…</source>
<target>Inserisci la password…</target>
@@ -1889,6 +2005,10 @@
<target>Inserisci il messaggio di benvenuto… (facoltativo)</target>
<note>placeholder</note>
</trans-unit>
<trans-unit id="Enter your name…" xml:space="preserve">
<source>Enter your name…</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error" xml:space="preserve">
<source>Error</source>
<target>Errore</target>
@@ -1944,11 +2064,21 @@
<target>Errore nella creazione del link del gruppo</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error creating member contact" xml:space="preserve">
<source>Error creating member contact</source>
<target>Errore di creazione del contatto</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error creating profile!" xml:space="preserve">
<source>Error creating profile!</source>
<target>Errore nella creazione del profilo!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error decrypting file" xml:space="preserve">
<source>Error decrypting file</source>
<target>Errore decifrando il file</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error deleting chat database" xml:space="preserve">
<source>Error deleting chat database</source>
<target>Errore nell'eliminazione del database della chat</target>
@@ -2069,6 +2199,11 @@
<target>Errore nell'invio dell'email</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error sending member contact invitation" xml:space="preserve">
<source>Error sending member contact invitation</source>
<target>Errore di invio dell'invito al contatto</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error sending message" xml:space="preserve">
<source>Error sending message</source>
<target>Errore nell'invio del messaggio</target>
@@ -2149,6 +2284,10 @@
<target>Esci senza salvare</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Expand" xml:space="preserve">
<source>Expand</source>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Export database" xml:space="preserve">
<source>Export database</source>
<target>Esporta database</target>
@@ -2294,6 +2433,10 @@
<target>Nome completo:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Fully decentralized visible only to members." xml:space="preserve">
<source>Fully decentralized visible only to members.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Fully re-implemented - work in background!" xml:space="preserve">
<source>Fully re-implemented - work in background!</source>
<target>Completamente reimplementato - funziona in secondo piano!</target>
@@ -2314,6 +2457,14 @@
<target>Gruppo</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Group already exists" xml:space="preserve">
<source>Group already exists</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Group already exists!" xml:space="preserve">
<source>Group already exists!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Group display name" xml:space="preserve">
<source>Group display name</source>
<target>Nome mostrato del gruppo</target>
@@ -2661,6 +2812,10 @@
<target>Link di connessione non valido</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Invalid name!" xml:space="preserve">
<source>Invalid name!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Invalid server address!" xml:space="preserve">
<source>Invalid server address!</source>
<target>Indirizzo del server non valido!</target>
@@ -2752,11 +2907,24 @@
<target>Entra nel gruppo</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Join group?" xml:space="preserve">
<source>Join group?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Join incognito" xml:space="preserve">
<source>Join incognito</source>
<target>Entra in incognito</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Join with current profile" xml:space="preserve">
<source>Join with current profile</source>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Joining group" xml:space="preserve">
<source>Joining group</source>
<target>Ingresso nel gruppo</target>
@@ -2972,6 +3140,10 @@
<target>Messaggi</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Messages from %@ will be shown!" xml:space="preserve">
<source>Messages from %@ will be shown!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Migrating database archive…" xml:space="preserve">
<source>Migrating database archive…</source>
<target>Migrazione archivio del database…</target>
@@ -3082,6 +3254,11 @@
<target>Nuovo archivio database</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New desktop app!" xml:space="preserve">
<source>New desktop app!</source>
<target>Nuova app desktop!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New display name" xml:space="preserve">
<source>New display name</source>
<target>Nuovo nome da mostrare</target>
@@ -3296,6 +3473,11 @@
<target>Solo il tuo contatto può inviare messaggi vocali.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open" xml:space="preserve">
<source>Open</source>
<target>Apri</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open Settings" xml:space="preserve">
<source>Open Settings</source>
<target>Apri le impostazioni</target>
@@ -3311,6 +3493,10 @@
<target>Apri la console della chat</target>
<note>authentication reason</note>
</trans-unit>
<trans-unit id="Open group" xml:space="preserve">
<source>Open group</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open user profiles" xml:space="preserve">
<source>Open user profiles</source>
<target>Apri i profili utente</target>
@@ -3326,11 +3512,6 @@
<target>Apertura del database…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red." xml:space="preserve">
<source>Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red.</source>
<target>Aprire il link nel browser può ridurre la privacy e la sicurezza della connessione. I link SimpleX non fidati saranno in rosso.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="PING count" xml:space="preserve">
<source>PING count</source>
<target>Conteggio PING</target>
@@ -3521,6 +3702,14 @@
<target>Immagine del profilo</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Profile name" xml:space="preserve">
<source>Profile name</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Profile name:" xml:space="preserve">
<source>Profile name:</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Profile password" xml:space="preserve">
<source>Profile password</source>
<target>Password del profilo</target>
@@ -3766,6 +3955,14 @@
<target>Rinegoziare la crittografia?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Repeat connection request?" xml:space="preserve">
<source>Repeat connection request?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Repeat join request?" xml:space="preserve">
<source>Repeat join request?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Reply" xml:space="preserve">
<source>Reply</source>
<target>Rispondi</target>
@@ -3828,7 +4025,7 @@
</trans-unit>
<trans-unit id="Revert" xml:space="preserve">
<source>Revert</source>
<target>Annulla</target>
<target>Ripristina</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Revoke" xml:space="preserve">
@@ -4031,6 +4228,11 @@
<target>Invia messaggio diretto</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Send direct message to connect" xml:space="preserve">
<source>Send direct message to connect</source>
<target>Invia messaggio diretto per connetterti</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Send disappearing message" xml:space="preserve">
<source>Send disappearing message</source>
<target>Invia messaggio a tempo</target>
@@ -4331,6 +4533,11 @@
<target>Invito SimpleX una tantum</target>
<note>simplex link type</note>
</trans-unit>
<trans-unit id="Simplified incognito mode" xml:space="preserve">
<source>Simplified incognito mode</source>
<target>Modalità incognito semplificata</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Skip" xml:space="preserve">
<source>Skip</source>
<target>Salta</target>
@@ -4471,6 +4678,10 @@
<target>Tocca il pulsante </target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Tap to Connect" xml:space="preserve">
<source>Tap to Connect</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Tap to activate profile." xml:space="preserve">
<source>Tap to activate profile.</source>
<target>Tocca per attivare il profilo.</target>
@@ -4568,11 +4779,6 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.</ta
<target>La crittografia funziona e il nuovo accordo sulla crittografia non è richiesto. Potrebbero verificarsi errori di connessione!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The group is fully decentralized it is visible only to the members." xml:space="preserve">
<source>The group is fully decentralized it is visible only to the members.</source>
<target>Il gruppo è completamente decentralizzato: è visibile solo ai membri.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The hash of the previous message is different." xml:space="preserve">
<source>The hash of the previous message is different.</source>
<target>L'hash del messaggio precedente è diverso.</target>
@@ -4668,6 +4874,14 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.</ta
<target>Questo gruppo non esiste più.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="This is your own SimpleX address!" xml:space="preserve">
<source>This is your own SimpleX address!</source>
<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>
<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">
<source>This setting applies to messages in your current chat profile **%@**.</source>
<target>Questa impostazione si applica ai messaggi del profilo di chat attuale **%@**.</target>
@@ -4725,6 +4939,11 @@ Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzio
<target>Per verificare la crittografia end-to-end con il tuo contatto, confrontate (o scansionate) il codice sui vostri dispositivi.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Toggle incognito when connecting." xml:space="preserve">
<source>Toggle incognito when connecting.</source>
<target>Attiva/disattiva l'incognito quando ti colleghi.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Transport isolation" xml:space="preserve">
<source>Transport isolation</source>
<target>Isolamento del trasporto</target>
@@ -4760,6 +4979,18 @@ Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzio
<target>Impossibile registrare il messaggio vocale</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unblock" xml:space="preserve">
<source>Unblock</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unblock member" xml:space="preserve">
<source>Unblock member</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unblock member?" xml:space="preserve">
<source>Unblock member?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unexpected error: %@" xml:space="preserve">
<source>Unexpected error: %@</source>
<target>Errore imprevisto: % @</target>
@@ -5107,6 +5338,35 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e
<target>Sei già connesso/a a %@.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You are already connecting to %@." xml:space="preserve">
<source>You are already connecting to %@.</source>
<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>
<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>
<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>
<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>
<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>
<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>
<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">
<source>You are connected to the server used to receive messages from this contact.</source>
<target>Sei connesso/a al server usato per ricevere messaggi da questo contatto.</target>
@@ -5202,6 +5462,15 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e
<target>Non è stato possibile verificarti, riprova.</target>
<note>No comment provided by engineer.</note>
</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>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You have no chats" xml:space="preserve">
<source>You have no chats</source>
<target>Non hai chat</target>
@@ -5252,6 +5521,10 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e
<target>Verrai connesso/a al gruppo quando il dispositivo dell'host del gruppo sarà in linea, attendi o controlla più tardi!</target>
<note>No comment provided by engineer.</note>
</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>
<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">
<source>You will be connected when your connection request is accepted, please wait or check later!</source>
<target>Verrai connesso/a quando la tua richiesta di connessione verrà accettata, attendi o controlla più tardi!</target>
@@ -5267,9 +5540,8 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e
<target>Dovrai autenticarti quando avvii o riapri l'app dopo 30 secondi in secondo piano.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You will join a group this link refers to and connect to its group members." xml:space="preserve">
<source>You will join a group this link refers to and connect to its group members.</source>
<target>Entrerai in un gruppo a cui si riferisce questo link e ti connetterai ai suoi membri.</target>
<trans-unit id="You will connect to all group members." xml:space="preserve">
<source>You will connect to all group members.</source>
<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">
@@ -5337,11 +5609,6 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e
<target>Il tuo database della chat non è crittografato: imposta la password per crittografarlo.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your chat profile will be sent to group members" xml:space="preserve">
<source>Your chat profile will be sent to group members</source>
<target>Il tuo profilo di chat verrà inviato ai membri del gruppo</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your chat profiles" xml:space="preserve">
<source>Your chat profiles</source>
<target>I tuoi profili di chat</target>
@@ -5396,6 +5663,10 @@ Puoi modificarlo nelle impostazioni.</target>
<target>La tua privacy</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your profile" xml:space="preserve">
<source>Your profile</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your profile **%@** will be shared." xml:space="preserve">
<source>Your profile **%@** will be shared.</source>
<target>Il tuo profilo **%@** verrà condiviso.</target>
@@ -5488,6 +5759,10 @@ I server di SimpleX non possono vedere il tuo profilo.</target>
<target>sempre</target>
<note>pref value</note>
</trans-unit>
<trans-unit id="and %lld other events" xml:space="preserve">
<source>and %lld other events</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="audio call (not e2e encrypted)" xml:space="preserve">
<source>audio call (not e2e encrypted)</source>
<target>chiamata audio (non crittografata e2e)</target>
@@ -5503,6 +5778,10 @@ I server di SimpleX non possono vedere il tuo profilo.</target>
<target>hash del messaggio errato</target>
<note>integrity error chat item</note>
</trans-unit>
<trans-unit id="blocked" xml:space="preserve">
<source>blocked</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="bold" xml:space="preserve">
<source>bold</source>
<target>grassetto</target>
@@ -5573,6 +5852,11 @@ I server di SimpleX non possono vedere il tuo profilo.</target>
<target>connesso/a</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="connected directly" xml:space="preserve">
<source>connected directly</source>
<target>si è connesso/a direttamente</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="connecting" xml:space="preserve">
<source>connecting</source>
<target>in connessione</target>
@@ -5668,6 +5952,10 @@ I server di SimpleX non possono vedere il tuo profilo.</target>
<target>eliminato</target>
<note>deleted chat item</note>
</trans-unit>
<trans-unit id="deleted contact" xml:space="preserve">
<source>deleted contact</source>
<note>rcv direct event chat item</note>
</trans-unit>
<trans-unit id="deleted group" xml:space="preserve">
<source>deleted group</source>
<target>gruppo eliminato</target>
@@ -5885,7 +6173,7 @@ I server di SimpleX non possono vedere il tuo profilo.</target>
</trans-unit>
<trans-unit id="member connected" xml:space="preserve">
<source>connected</source>
<target>è connesso/a</target>
<target>si è connesso/a</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="message received" xml:space="preserve">
@@ -5952,7 +6240,8 @@ I server di SimpleX non possono vedere il tuo profilo.</target>
<source>off</source>
<target>off</target>
<note>enabled status
group pref value</note>
group pref value
time to disappear</note>
</trans-unit>
<trans-unit id="offered %@" xml:space="preserve">
<source>offered %@</source>
@@ -5969,11 +6258,6 @@ I server di SimpleX non possono vedere il tuo profilo.</target>
<target>on</target>
<note>group pref value</note>
</trans-unit>
<trans-unit id="or chat with the developers" xml:space="preserve">
<source>or chat with the developers</source>
<target>o scrivi agli sviluppatori</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="owner" xml:space="preserve">
<source>owner</source>
<target>proprietario</target>
@@ -6001,7 +6285,7 @@ I server di SimpleX non possono vedere il tuo profilo.</target>
</trans-unit>
<trans-unit id="removed" xml:space="preserve">
<source>removed</source>
<target>ha rimosso</target>
<target>rimosso</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="removed %@" xml:space="preserve">
@@ -6011,7 +6295,7 @@ I server di SimpleX non possono vedere il tuo profilo.</target>
</trans-unit>
<trans-unit id="removed you" xml:space="preserve">
<source>removed you</source>
<target>sei stato/a rimosso/a</target>
<target>ti ha rimosso/a</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="sec" xml:space="preserve">
@@ -6034,6 +6318,11 @@ I server di SimpleX non possono vedere il tuo profilo.</target>
<target>codice di sicurezza modificato</target>
<note>chat item text</note>
</trans-unit>
<trans-unit id="send direct message" xml:space="preserve">
<source>send direct message</source>
<target>invia messaggio diretto</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="starting…" xml:space="preserve">
<source>starting…</source>
<target>avvio…</target>
@@ -6178,7 +6467,7 @@ I server di SimpleX non possono vedere il tuo profilo.</target>
</file>
<file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="it" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A5219j"/>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A240d"/>
</header>
<body>
<trans-unit id="CFBundleName" xml:space="preserve">
@@ -6210,7 +6499,7 @@ I server di SimpleX non possono vedere il tuo profilo.</target>
</file>
<file original="SimpleX NSE/en.lproj/InfoPlist.strings" source-language="en" target-language="it" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A5219j"/>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A240d"/>
</header>
<body>
<trans-unit id="CFBundleDisplayName" xml:space="preserve">

View File

@@ -3,7 +3,7 @@
"project" : "SimpleX.xcodeproj",
"targetLocale" : "it",
"toolInfo" : {
"toolBuildNumber" : "15A5219j",
"toolBuildNumber" : "15A240d",
"toolID" : "com.apple.dt.xcode",
"toolName" : "Xcode",
"toolVersion" : "15.0"

View File

@@ -2,7 +2,7 @@
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd">
<file original="en.lproj/Localizable.strings" source-language="en" target-language="ja" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A5219j"/>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A240d"/>
</header>
<body>
<trans-unit id="&#10;" xml:space="preserve">
@@ -87,6 +87,10 @@
<target>%@ / %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@ and %@" xml:space="preserve">
<source>%@ and %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@ and %@ connected" xml:space="preserve">
<source>%@ and %@ connected</source>
<target>%@ と %@ は接続中</target>
@@ -97,6 +101,10 @@
<target>%1$@ at %2$@:</target>
<note>copied message info, &lt;sender&gt; at &lt;time&gt;</note>
</trans-unit>
<trans-unit id="%@ connected" xml:space="preserve">
<source>%@ connected</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@ is connected!" xml:space="preserve">
<source>%@ is connected!</source>
<target>%@ 接続中!</target>
@@ -122,6 +130,10 @@
<target>%@ が接続を希望しています!</target>
<note>notification title</note>
</trans-unit>
<trans-unit id="%@, %@ and %lld members" xml:space="preserve">
<source>%@, %@ and %lld members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@, %@ and %lld other members connected" xml:space="preserve">
<source>%@, %@ and %lld other members connected</source>
<target>%@, %@ および %lld 人のメンバーが接続中</target>
@@ -187,16 +199,37 @@
<target>%lld 個のファイル(合計サイズ: %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld group events" xml:space="preserve">
<source>%lld group events</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld members" xml:space="preserve">
<source>%lld members</source>
<target>%lld 人のメンバー</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld messages blocked" xml:space="preserve">
<source>%lld messages blocked</source>
<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>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld minutes" xml:space="preserve">
<source>%lld minutes</source>
<target>%lld 分</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld new interface languages" xml:space="preserve">
<source>%lld new interface languages</source>
<target>%lldつの新しいインターフェース言語</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld second(s)" xml:space="preserve">
<source>%lld second(s)</source>
<target>%lld 秒</target>
@@ -327,6 +360,12 @@
<target>, </target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- connect to [directory service](simplex:/contact#/?v=1-4&amp;smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!&#10;- delivery receipts (up to 20 members).&#10;- faster and more stable." xml:space="preserve">
<source>- connect to [directory service](simplex:/contact#/?v=1-4&amp;smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!
- delivery receipts (up to 20 members).
- faster and more stable.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="- more stable message delivery.&#10;- a bit better groups.&#10;- and more!" xml:space="preserve">
<source>- more stable message delivery.
- a bit better groups.
@@ -350,6 +389,10 @@
<target>.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="0 sec" xml:space="preserve">
<source>0 sec</source>
<note>time to disappear</note>
</trans-unit>
<trans-unit id="0s" xml:space="preserve">
<source>0s</source>
<target>0秒</target>
@@ -575,6 +618,10 @@
<target>全てのメッセージが削除されます(※注意:元に戻せません!※)。削除されるのは片方あなたのメッセージのみ。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="All new messages from %@ will be hidden!" xml:space="preserve">
<source>All new messages from %@ will be hidden!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="All your contacts will remain connected." xml:space="preserve">
<source>All your contacts will remain connected.</source>
<target>あなたの連絡先が繋がったまま継続します。</target>
@@ -680,6 +727,14 @@
<target>すでに接続済みですか?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Already connecting!" xml:space="preserve">
<source>Already connecting!</source>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Always use relay" xml:space="preserve">
<source>Always use relay</source>
<target>常にリレーを経由する</target>
@@ -700,6 +755,11 @@
<target>アプリのビルド: %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="App encrypts new local files (except videos)." xml:space="preserve">
<source>App encrypts new local files (except videos).</source>
<target>アプリは新しいローカルファイル(ビデオを除く)を暗号化します。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="App icon" xml:space="preserve">
<source>App icon</source>
<target>アプリのアイコン</target>
@@ -810,6 +870,18 @@
<target>より良いメッセージ</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Block" xml:space="preserve">
<source>Block</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>
</trans-unit>
<trans-unit id="Block member?" xml:space="preserve">
<source>Block member?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Both you and your contact can add message reactions." xml:space="preserve">
<source>Both you and your contact can add message reactions.</source>
<target>自分も相手もメッセージへのリアクションを追加できます。</target>
@@ -835,6 +907,11 @@
<target>あなたと連絡相手が音声メッセージを送信できます。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" xml:space="preserve">
<source>Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!</source>
<target>ブルガリア語、フィンランド語、タイ語、ウクライナ語 - ユーザーと [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)に感謝します!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." xml:space="preserve">
<source>By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).</source>
<target>チャット プロファイル経由 (デフォルト) または [接続経由](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).</target>
@@ -1066,24 +1143,27 @@
<target>接続</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Connect directly" xml:space="preserve">
<source>Connect directly</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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect via contact link" xml:space="preserve">
<source>Connect via contact link</source>
<target>連絡先リンク経由で接続しますか?</target>
<trans-unit id="Connect to yourself?" xml:space="preserve">
<source>Connect to yourself?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect via group link?" xml:space="preserve">
<source>Connect via group link?</source>
<target>グループリンク経由で接続しますか?</target>
<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>
<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>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect via link" xml:space="preserve">
@@ -1101,6 +1181,10 @@
<target>使い捨てリンク経由で接続しますか?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect with %@" xml:space="preserve">
<source>Connect with %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connecting server…" xml:space="preserve">
<source>Connecting to server…</source>
<target>サーバーに接続中…</target>
@@ -1146,11 +1230,6 @@
<target>連絡先に既に存在します</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Contact and all messages will be deleted - this cannot be undone!" xml:space="preserve">
<source>Contact and all messages will be deleted - this cannot be undone!</source>
<target>連絡先と全メッセージが削除されます (※元に戻せません※)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Contact hidden:" xml:space="preserve">
<source>Contact hidden:</source>
<target>連絡先が非表示:</target>
@@ -1201,6 +1280,10 @@
<target>コアのバージョン: v%@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Correct name to %@?" xml:space="preserve">
<source>Correct name to %@?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create" xml:space="preserve">
<source>Create</source>
<target>作成</target>
@@ -1221,6 +1304,10 @@
<target>ファイルを作成</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Create group" xml:space="preserve">
<source>Create group</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create group link" xml:space="preserve">
<source>Create group link</source>
<target>グループのリンクを生成する</target>
@@ -1231,11 +1318,20 @@
<target>リンクを生成する</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" xml:space="preserve">
<source>Create new profile in [desktop app](https://simplex.chat/downloads/). 💻</source>
<target>[デスクトップアプリ](https://simplex.chat/downloads/)で新しいプロファイルを作成します。 💻</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create one-time invitation link" xml:space="preserve">
<source>Create one-time invitation link</source>
<target>使い捨ての招待リンクを生成する</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create profile" xml:space="preserve">
<source>Create profile</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create queue" xml:space="preserve">
<source>Create queue</source>
<target>キューの作成</target>
@@ -1394,6 +1490,10 @@
<target>削除</target>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Delete %lld messages?" xml:space="preserve">
<source>Delete %lld messages?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete Contact" xml:space="preserve">
<source>Delete Contact</source>
<target>連絡先を削除</target>
@@ -1419,6 +1519,10 @@
<target>ファイルを全て削除</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete and notify contact" xml:space="preserve">
<source>Delete and notify contact</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete archive" xml:space="preserve">
<source>Delete archive</source>
<target>アーカイブを削除</target>
@@ -1449,9 +1553,9 @@
<target>連絡先を削除</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete contact?" xml:space="preserve">
<source>Delete contact?</source>
<target>連絡先を削除しますか?</target>
<trans-unit id="Delete contact?&#10;This cannot be undone!" xml:space="preserve">
<source>Delete contact?
This cannot be undone!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete database" xml:space="preserve">
@@ -1586,6 +1690,7 @@
</trans-unit>
<trans-unit id="Delivery receipts!" xml:space="preserve">
<source>Delivery receipts!</source>
<target>配信通知!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Description" xml:space="preserve">
@@ -1683,14 +1788,9 @@
<target>切断</target>
<note>server test step</note>
</trans-unit>
<trans-unit id="Display name" xml:space="preserve">
<source>Display name</source>
<target>表示名</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Display name:" xml:space="preserve">
<source>Display name:</source>
<target>表示名:</target>
<trans-unit id="Discover and join groups" xml:space="preserve">
<source>Discover and join groups</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">
@@ -1818,6 +1918,16 @@
<target>データベースを暗号化しますか?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Encrypt local files" xml:space="preserve">
<source>Encrypt local files</source>
<target>ローカルファイルを暗号化する</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Encrypt stored files &amp; media" xml:space="preserve">
<source>Encrypt stored files &amp; media</source>
<target>保存されたファイルとメディアを暗号化する</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Encrypted database" xml:space="preserve">
<source>Encrypted database</source>
<target>暗号化済みデータベース</target>
@@ -1863,6 +1973,10 @@
<target>正しいパスフレーズを入力してください。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enter group name…" xml:space="preserve">
<source>Enter group name…</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enter passphrase…" xml:space="preserve">
<source>Enter passphrase…</source>
<target>暗証フレーズを入力…</target>
@@ -1888,6 +2002,10 @@
<target>ウェルカムメッセージを入力…(オプション)</target>
<note>placeholder</note>
</trans-unit>
<trans-unit id="Enter your name…" xml:space="preserve">
<source>Enter your name…</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error" xml:space="preserve">
<source>Error</source>
<target>エラー</target>
@@ -1943,11 +2061,21 @@
<target>グループリンク生成にエラー発生</target>
<note>No comment provided by engineer.</note>
</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">
<source>Error creating profile!</source>
<target>プロフィール作成にエラー発生!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error decrypting file" xml:space="preserve">
<source>Error decrypting file</source>
<target>ファイルの復号エラー</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error deleting chat database" xml:space="preserve">
<source>Error deleting chat database</source>
<target>チャットデータベース削除にエラー発生</target>
@@ -2067,6 +2195,11 @@
<target>メールの送信にエラー発生</target>
<note>No comment provided by engineer.</note>
</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">
<source>Error sending message</source>
<target>メッセージ送信にエラー発生</target>
@@ -2146,6 +2279,10 @@
<target>保存せずに閉じる</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Expand" xml:space="preserve">
<source>Expand</source>
<note>chat item action</note>
</trans-unit>
<trans-unit id="Export database" xml:space="preserve">
<source>Export database</source>
<target>データベースをエキスポート</target>
@@ -2291,6 +2428,10 @@
<target>フルネーム:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Fully decentralized visible only to members." xml:space="preserve">
<source>Fully decentralized visible only to members.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Fully re-implemented - work in background!" xml:space="preserve">
<source>Fully re-implemented - work in background!</source>
<target>完全に再実装されました - バックグラウンドで動作します!</target>
@@ -2311,6 +2452,14 @@
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Group already exists!" xml:space="preserve">
<source>Group already exists!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Group display name" xml:space="preserve">
<source>Group display name</source>
<target>グループ表示の名前</target>
@@ -2658,6 +2807,10 @@
<target>無効な接続リンク</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Invalid name!" xml:space="preserve">
<source>Invalid name!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Invalid server address!" xml:space="preserve">
<source>Invalid server address!</source>
<target>無効なサーバアドレス!</target>
@@ -2749,11 +2902,24 @@
<target>グループに参加</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Join group?" xml:space="preserve">
<source>Join group?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Join incognito" xml:space="preserve">
<source>Join incognito</source>
<target>シークレットモードで参加</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Join with current profile" xml:space="preserve">
<source>Join with current profile</source>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Joining group" xml:space="preserve">
<source>Joining group</source>
<target>グループに参加</target>
@@ -2968,6 +3134,10 @@
<target>メッセージ &amp; ファイル</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Messages from %@ will be shown!" xml:space="preserve">
<source>Messages from %@ will be shown!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Migrating database archive…" xml:space="preserve">
<source>Migrating database archive…</source>
<target>データベースのアーカイブを移行しています…</target>
@@ -3078,6 +3248,11 @@
<target>新しいデータベースのアーカイブ</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New desktop app!" xml:space="preserve">
<source>New desktop app!</source>
<target>新しいデスクトップアプリ!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New display name" xml:space="preserve">
<source>New display name</source>
<target>新たな表示名</target>
@@ -3292,6 +3467,11 @@
<target>音声メッセージを送れるのはあなたの連絡相手だけです。</target>
<note>No comment provided by engineer.</note>
</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">
<source>Open Settings</source>
<target>設定を開く</target>
@@ -3307,6 +3487,10 @@
<target>チャットのコンソールを開く</target>
<note>authentication reason</note>
</trans-unit>
<trans-unit id="Open group" xml:space="preserve">
<source>Open group</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Open user profiles" xml:space="preserve">
<source>Open user profiles</source>
<target>ユーザープロフィールを開く</target>
@@ -3322,11 +3506,6 @@
<target>データベースを開いています…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red." xml:space="preserve">
<source>Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red.</source>
<target>ブラウザでリンクを開くと接続のプライバシーとセキュリティが下がる可能性があります。信頼されないSimpleXリンクは読み込まれません。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="PING count" xml:space="preserve">
<source>PING count</source>
<target>PING回数</target>
@@ -3517,6 +3696,14 @@
<target>プロフィール画像</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Profile name" xml:space="preserve">
<source>Profile name</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Profile name:" xml:space="preserve">
<source>Profile name:</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Profile password" xml:space="preserve">
<source>Profile password</source>
<target>プロフィールのパスワード</target>
@@ -3761,6 +3948,14 @@
<target>暗号化を再ネゴシエートしますか?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Repeat connection request?" xml:space="preserve">
<source>Repeat connection request?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Repeat join request?" xml:space="preserve">
<source>Repeat join request?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Reply" xml:space="preserve">
<source>Reply</source>
<target>返信</target>
@@ -4025,6 +4220,11 @@
<target>ダイレクトメッセージを送信</target>
<note>No comment provided by engineer.</note>
</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">
<source>Send disappearing message</source>
<target>消えるメッセージを送信</target>
@@ -4318,6 +4518,11 @@
<target>SimpleX使い捨て招待リンク</target>
<note>simplex link type</note>
</trans-unit>
<trans-unit id="Simplified incognito mode" xml:space="preserve">
<source>Simplified incognito mode</source>
<target>シークレットモードの簡素化</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Skip" xml:space="preserve">
<source>Skip</source>
<target>スキップ</target>
@@ -4458,6 +4663,10 @@
<target>ボタンをタップ </target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Tap to Connect" xml:space="preserve">
<source>Tap to Connect</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Tap to activate profile." xml:space="preserve">
<source>Tap to activate profile.</source>
<target>タップしてプロフィールを有効化する。</target>
@@ -4555,11 +4764,6 @@ 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="The group is fully decentralized it is visible only to the members." xml:space="preserve">
<source>The group is fully decentralized it is visible only to the members.</source>
<target>グループは完全分散型で、メンバーしか内容を見れません。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The hash of the previous message is different." xml:space="preserve">
<source>The hash of the previous message is different.</source>
<target>以前のメッセージとハッシュ値が異なります。</target>
@@ -4654,6 +4858,14 @@ 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="This is your own SimpleX address!" xml:space="preserve">
<source>This is your own SimpleX address!</source>
<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>
<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">
<source>This setting applies to messages in your current chat profile **%@**.</source>
<target>この設定は現在のチャットプロフィール **%@** のメッセージに適用されます。</target>
@@ -4711,6 +4923,10 @@ You will be prompted to complete authentication before this feature is enabled.<
<target>エンドツーエンド暗号化を確認するには、ご自分の端末と連絡先の端末のコードを比べます (スキャンします)。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Toggle incognito when connecting." xml:space="preserve">
<source>Toggle incognito when connecting.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Transport isolation" xml:space="preserve">
<source>Transport isolation</source>
<target>トランスポート隔離</target>
@@ -4746,6 +4962,18 @@ You will be prompted to complete authentication before this feature is enabled.<
<target>音声メッセージを録音できません</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unblock" xml:space="preserve">
<source>Unblock</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unblock member" xml:space="preserve">
<source>Unblock member</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unblock member?" xml:space="preserve">
<source>Unblock member?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unexpected error: %@" xml:space="preserve">
<source>Unexpected error: %@</source>
<target>予期しないエラー: %@</target>
@@ -5093,6 +5321,35 @@ 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="You are already connecting to %@." xml:space="preserve">
<source>You are already connecting to %@.</source>
<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>
<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>
<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>
<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>
<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>
<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>
<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">
<source>You are connected to the server used to receive messages from this contact.</source>
<target>この連絡先から受信するメッセージのサーバに既に接続してます。</target>
@@ -5188,6 +5445,15 @@ 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="You have already requested connection via this address!" xml:space="preserve">
<source>You have already requested connection via this address!</source>
<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>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You have no chats" xml:space="preserve">
<source>You have no chats</source>
<target>あなたはチャットがありません</target>
@@ -5238,6 +5504,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="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>
<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">
<source>You will be connected when your connection request is accepted, please wait or check later!</source>
<target>連絡先が繋がりリクエストを承認したら、接続されます。後でチェックするか、しばらくお待ちください!</target>
@@ -5253,9 +5523,8 @@ To connect, please ask your contact to create another connection link and check
<target>起動時、または非アクティブ状態で30秒が経った後に戻ると、認証する必要となります。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You will join a group this link refers to and connect to its group members." xml:space="preserve">
<source>You will join a group this link refers to and connect to its group members.</source>
<target>このリンクのグループに参加し、そのメンバーに繋がります。</target>
<trans-unit id="You will connect to all group members." xml:space="preserve">
<source>You will connect to all group members.</source>
<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">
@@ -5323,11 +5592,6 @@ 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="Your chat profile will be sent to group members" xml:space="preserve">
<source>Your chat profile will be sent to group members</source>
<target>あなたのチャットプロフィールが他のグループメンバーに送られます</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your chat profiles" xml:space="preserve">
<source>Your chat profiles</source>
<target>あなたのチャットプロフィール</target>
@@ -5382,6 +5646,10 @@ You can change it in Settings.</source>
<target>あなたのプライバシー</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your profile" xml:space="preserve">
<source>Your profile</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Your profile **%@** will be shared." xml:space="preserve">
<source>Your profile **%@** will be shared.</source>
<target>あなたのプロファイル **%@** が共有されます。</target>
@@ -5474,6 +5742,10 @@ SimpleX サーバーはあなたのプロファイルを参照できません。
<target>常に</target>
<note>pref value</note>
</trans-unit>
<trans-unit id="and %lld other events" xml:space="preserve">
<source>and %lld other events</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="audio call (not e2e encrypted)" xml:space="preserve">
<source>audio call (not e2e encrypted)</source>
<target>音声通話 (エンドツーエンド暗号化なし)</target>
@@ -5489,6 +5761,10 @@ SimpleX サーバーはあなたのプロファイルを参照できません。
<target>メッセージのハッシュ値問題</target>
<note>integrity error chat item</note>
</trans-unit>
<trans-unit id="blocked" xml:space="preserve">
<source>blocked</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="bold" xml:space="preserve">
<source>bold</source>
<target>太文字</target>
@@ -5559,6 +5835,10 @@ SimpleX サーバーはあなたのプロファイルを参照できません。
<target>接続中</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="connected directly" xml:space="preserve">
<source>connected directly</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="connecting" xml:space="preserve">
<source>connecting</source>
<target>接続待ち</target>
@@ -5654,6 +5934,10 @@ SimpleX サーバーはあなたのプロファイルを参照できません。
<target>削除完了</target>
<note>deleted chat item</note>
</trans-unit>
<trans-unit id="deleted contact" xml:space="preserve">
<source>deleted contact</source>
<note>rcv direct event chat item</note>
</trans-unit>
<trans-unit id="deleted group" xml:space="preserve">
<source>deleted group</source>
<target>削除されたグループ</target>
@@ -5938,7 +6222,8 @@ SimpleX サーバーはあなたのプロファイルを参照できません。
<source>off</source>
<target>オフ</target>
<note>enabled status
group pref value</note>
group pref value
time to disappear</note>
</trans-unit>
<trans-unit id="offered %@" xml:space="preserve">
<source>offered %@</source>
@@ -5955,11 +6240,6 @@ SimpleX サーバーはあなたのプロファイルを参照できません。
<target>オン</target>
<note>group pref value</note>
</trans-unit>
<trans-unit id="or chat with the developers" xml:space="preserve">
<source>or chat with the developers</source>
<target>または開発者とチャットする</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="owner" xml:space="preserve">
<source>owner</source>
<target>オーナー</target>
@@ -6020,6 +6300,10 @@ SimpleX サーバーはあなたのプロファイルを参照できません。
<target>セキュリティコードが変更されました</target>
<note>chat item text</note>
</trans-unit>
<trans-unit id="send direct message" xml:space="preserve">
<source>send direct message</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="starting…" xml:space="preserve">
<source>starting…</source>
<target>接続中…</target>
@@ -6164,7 +6448,7 @@ SimpleX サーバーはあなたのプロファイルを参照できません。
</file>
<file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="ja" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A5219j"/>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A240d"/>
</header>
<body>
<trans-unit id="CFBundleName" xml:space="preserve">
@@ -6196,7 +6480,7 @@ SimpleX サーバーはあなたのプロファイルを参照できません。
</file>
<file original="SimpleX NSE/en.lproj/InfoPlist.strings" source-language="en" target-language="ja" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A5219j"/>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A240d"/>
</header>
<body>
<trans-unit id="CFBundleDisplayName" xml:space="preserve">

View File

@@ -3,7 +3,7 @@
"project" : "SimpleX.xcodeproj",
"targetLocale" : "ja",
"toolInfo" : {
"toolBuildNumber" : "15A5219j",
"toolBuildNumber" : "15A240d",
"toolID" : "com.apple.dt.xcode",
"toolName" : "Xcode",
"toolVersion" : "15.0"

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