Compare commits

..

77 commits

Author SHA1 Message Date
Evgeny Poberezkin
1b75ca5258
6.4-beta.1: ios 279, android 291, desktop 103 2025-05-21 13:33:55 +01:00
Evgeny
dd2dc16fbf
website: translations (#5937)
* Translated using Weblate (Hungarian)

Currently translated at 100.0% (257 of 257 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (257 of 257 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (257 of 257 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (257 of 257 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (257 of 257 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (257 of 257 strings)

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

---------

Co-authored-by: summoner001 <summoner@vivaldi.net>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
2025-05-21 11:49:58 +01:00
Evgeny
cef63f2c87
ui: translations (#5936)
* Translated using Weblate (German)

Currently translated at 100.0% (2404 of 2404 strings)

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

* Translated using Weblate (Russian)

Currently translated at 98.3% (2365 of 2404 strings)

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (2404 of 2404 strings)

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

* Translated using Weblate (Russian)

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

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

* ios: process localizations

* update

---------

Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: Vitaly Kanevsky <v.kanevsky@tuta.com>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2025-05-21 11:47:45 +01:00
Evgeny Poberezkin
0e99e8010a
core: 6.4.0.2 (simplexmq 6.4.0.4) 2025-05-21 10:12:53 +01:00
spaced4ndy
e84016e1b9
core, ui: update group scope unread stats on item deletions; create reports as read (reports use a different attention mechanism) (#5930) 2025-05-20 16:18:23 +00:00
Evgeny
f162f96be8
website: translations (#5932)
* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (257 of 257 strings)

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

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (257 of 257 strings)

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

* Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 78.5% (202 of 257 strings)

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

* Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 78.5% (202 of 257 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (257 of 257 strings)

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

* Translated using Weblate (Russian)

Currently translated at 99.6% (256 of 257 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (257 of 257 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (257 of 257 strings)

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

---------

Co-authored-by: Bezruchenko Simon <worcposj44@gmail.com>
Co-authored-by: dns <dnstxqet@users.noreply.hosted.weblate.org>
Co-authored-by: 4 Bi 5aYzVk 93FCVjWLWxh44XH3984teVSfjwFYmUGUrbvnHwGirk9 <userfifteen.seventeen@mailfence.com>
Co-authored-by: summoner001 <summoner@vivaldi.net>
Co-authored-by: Vitaly Kanevsky <v.kanevsky@tuta.com>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
2025-05-20 15:44:33 +01:00
Evgeny
1ef53d1530
ui: translations (#5931)
* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (2404 of 2404 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (2404 of 2404 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 99.0% (2071 of 2090 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 99.0% (2071 of 2090 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 99.9% (2089 of 2090 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 99.9% (2089 of 2090 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (2404 of 2404 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (2090 of 2090 strings)

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

* ios: import

---------

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: summoner001 <summoner@vivaldi.net>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
2025-05-20 15:38:00 +01:00
Evgeny Poberezkin
a8e7db0b8f
android, desktop: update translation key 2025-05-20 15:27:03 +01:00
Evgeny Poberezkin
22364a28be
ios: export localizations 2025-05-20 14:11:49 +01:00
Evgeny
b441527f7d
ui: translations (#5929)
* Translated using Weblate (German)

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

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

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (2356 of 2356 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (2356 of 2356 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (2054 of 2054 strings)

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

* Translated using Weblate (Indonesian)

Currently translated at 100.0% (2356 of 2356 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (2389 of 2389 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (2054 of 2054 strings)

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

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (2389 of 2389 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (2054 of 2054 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (2389 of 2389 strings)

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

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (2389 of 2389 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (2389 of 2389 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (2054 of 2054 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (2053 of 2053 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (2389 of 2389 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (2053 of 2053 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (2389 of 2389 strings)

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

* process localizations

---------

Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: Bezruchenko Simon <worcposj44@gmail.com>
Co-authored-by: summoner001 <summoner@vivaldi.net>
Co-authored-by: Rafi <rafimuhmad90@protonmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2025-05-20 12:46:52 +01:00
spaced4ndy
1a80ecfc29
core, ui: allow to delete member support chat; rename reject action (#5927) 2025-05-20 09:07:44 +00:00
spaced4ndy
ca9f6267e4
ui: check member supports support chat (#5926) 2025-05-19 17:05:07 +00:00
spaced4ndy
b0547cf6f7 Merge branch 'stable' 2025-05-19 20:02:49 +04:00
spaced4ndy
fa81df24ee
core, ui: allow files and links in support scope (#5925) 2025-05-19 11:31:10 +00:00
Evgeny
a2577c3df3
core: support observers send messages to admins, CLI commands (#5921)
* core: allow observers send messages to admins

* improve parser, CLI command to approve members
2025-05-19 11:14:43 +01:00
spaced4ndy
98f35a38e8
core: fix member introduction when inviting admin accepts member; fix order of items for accepted member (#5924) 2025-05-19 09:08:28 +00:00
Evgeny Poberezkin
cc7bc0536d
6.4-beta.0: ios 278, android 290, desktop 102 2025-05-16 21:11:21 +01:00
Evgeny Poberezkin
507feb92a5
core: 6.4.0.1 2025-05-16 19:26:46 +01:00
spaced4ndy
041807c870
core: decrease membersRequireAttention counter when member is deleted or leaves (#5919) 2025-05-16 15:03:15 +00:00
spaced4ndy
5e7f77d1d1
android: go back to chat list after closing support chat when invitee is pending (#5918) 2025-05-16 10:54:13 +00:00
spaced4ndy
a92c74d3e5
android, desktop: better Chat with admins indications for members; unread badges (#5917) 2025-05-16 08:26:28 +00:00
Evgeny Poberezkin
c82919cdab
core: 6.4.0.0 (simplexmq 6.4.0.3) 2025-05-15 21:57:00 +01:00
spaced4ndy
a8da7c1c44
ios: fix auto-closing support chat for invitee when accepted; core: add accepted item to main scope (#5916) 2025-05-15 16:05:18 +00:00
spaced4ndy
409d7e8532
ios: scroll to reported item (#5915) 2025-05-15 15:04:03 +00:00
Evgeny Poberezkin
9225b896b0
Merge branch 'stable' 2025-05-15 15:03:12 +01:00
spaced4ndy
b32c052f13
ios: reports fixes - don't create in main chat, show notice "view in chat with admins" (#5911) 2025-05-15 13:59:12 +00:00
spaced4ndy
775f6f14bf
android, desktop: fix navigation to quoted messages in support chats (#5912) 2025-05-15 13:42:23 +00:00
Evgeny Poberezkin
143aa30c5a
Merge branch 'stable' 2025-05-15 14:27:04 +01:00
spaced4ndy
be28022bc6
ios: open support chat with any member (#5910) 2025-05-14 16:21:01 +00:00
spaced4ndy
b97e1e0f11
ui: show new messages from support scope in main chat preview when invitee is pending (#5909) 2025-05-14 15:14:34 +00:00
spaced4ndy
d3bc22726a
core: fix group being displayed as connecting after join (#5908) 2025-05-14 11:25:24 +00:00
Evgeny
e1ab178fe6
Merge pull request #5797 from simplex-chat/group-knocking
core, ui: group knocking
2025-05-14 10:38:22 +01:00
Evgeny
9478172a82
ios: fix toolbar in member support chat (#5902)
* ios: fix toolbar in member support chat

* refactor

* refactor nav buttons

* ios: unread reports, members requiring attention

* top bar destop (special case for 1 support chat)

* put Navigation view higher in hierarchy

* use nav title for knocking

* refactor loading secondary chat, overlay fake toolbar for knocking chats

* fix member reports toolbar

* unread counter for member's support chat
2025-05-14 10:36:54 +01:00
spaced4ndy
22b022149c
android, desktop: rename chat with member button (#5907) 2025-05-14 06:44:21 +00:00
Evgeny Poberezkin
94eb43eb50
Merge branch 'master' into group-knocking 2025-05-13 18:56:30 +01:00
spaced4ndy
e29a9371b6
core, kotlin: open support chat with any member (#5904) 2025-05-13 16:29:47 +00:00
spaced4ndy
b81e5b13b3
ui: fix messages being sent in wrong scope after receiving an item in support scope (#5903) 2025-05-13 14:01:53 +00:00
spaced4ndy
db1ffb2cf2
core: create "accepted by you" item for moderator (#5901) 2025-05-13 12:35:54 +00:00
spaced4ndy
e9414d713e Merge branch 'master' into group-knocking 2025-05-13 13:05:48 +04:00
spaced4ndy
671dc2537d
core: accepted message to older clients (#5897) 2025-05-12 16:50:30 +00:00
spaced4ndy
9190c2a772
core: reset unanswered counter for support chats with members only if moderator message is newer (#5889) 2025-05-12 12:19:20 +00:00
spaced4ndy
79991e84c9
core: member requires attention counter (#5881) 2025-05-09 15:36:06 +00:00
spaced4ndy
2a9df3e10b
ui: knocking UI improvements (texts, icons, layout, chat view shortcut split toolbar, set admission on creating group) (#5879) 2025-05-08 13:44:55 +00:00
spaced4ndy
f5926e8b84
core, ui: update chat ts and pop chat for group on messages for secondary chat scope (#5878) 2025-05-08 08:22:46 +00:00
spaced4ndy
e3957ba156
core: postgres migration for knocking; fix previous migration (#5877) 2025-05-07 14:50:17 +00:00
Evgeny Poberezkin
ec36930160
Merge branch 'master' into group-knocking 2025-05-07 13:52:12 +01:00
spaced4ndy
6b71cd2ee9
core: add index for reading scoped chats stats (#5876) 2025-05-07 12:50:27 +00:00
spaced4ndy
79dadabf61 Merge branch 'master' into group-knocking 2025-05-07 13:48:55 +04:00
spaced4ndy
1ba863bedc ios: close support chat when accepting/removing pending member 2025-05-07 12:38:24 +04:00
spaced4ndy
d087059153
ios: close support chat for invitee when accepted (#5873) 2025-05-07 07:37:17 +00:00
spaced4ndy
3c36b0c692 ios: fix chat view closing for invitee after being accepted 2025-05-07 11:25:23 +04:00
spaced4ndy
d193658cac
ios: auto-open support chat for invitee in pending status (#5871) 2025-05-06 14:55:54 +00:00
Evgeny Poberezkin
c3bb1e660d
Merge branch 'master' into group-knocking 2025-05-05 12:53:05 +01:00
spaced4ndy
3aa6d6c3b3 ios: fix member support swipe actions 2025-05-05 13:43:00 +04:00
spaced4ndy
28a588628c
ios: correct list of members possible to mention in support scope (#5870) 2025-05-05 09:32:57 +00:00
spaced4ndy
0ea1363a27 ios: dynamically mark items read 2025-05-05 12:32:55 +04:00
Evgeny Poberezkin
92787df8a3
Merge branch 'master' into group-knocking 2025-05-04 23:25:50 +01:00
spaced4ndy
32fb5a5d12
ios: knocking ui (#5851)
* ios: knocking ui types

* update types

* member admission

* remove chatItemStatuses

* member support view

* member support chat view wip

* ios: secondary ItemsModel (#5862)

* toolbar

* more chats

* remove theme

* preview icon

* chat toolbar fast markers

* change icon

* change icon

* remove/accept buttons

* item style

* get item ItemsModel in chat model methods (chat view doesn't work dynamically)

* fix support chat

* fix other chats

* refresh on exit

* refresh button

* dynamic marker in chat list

* prohibit multi select actions

* prohibited send field
2025-05-04 22:27:20 +01:00
spaced4ndy
524daf178b Merge branch 'master' into group-knocking 2025-05-02 17:05:40 +04:00
spaced4ndy
ca2f019249 Merge branch 'master' into group-knocking 2025-04-29 20:28:45 +04:00
spaced4ndy
6abffeb295 core: only introduce to supporting moderators 2025-04-28 15:03:17 +04:00
spaced4ndy
dd01ef5ae8
core: create knocking message for moderators (#5857) 2025-04-28 10:45:42 +00:00
spaced4ndy
60bfd9933a
core: correctly communicate group acceptance state (#5852) 2025-04-28 06:28:40 +00:00
Evgeny Poberezkin
82be7d00fd
Merge branch 'master' into group-knocking 2025-04-25 07:54:13 +01:00
spaced4ndy
18fa5dbf04
core, ui: admission message (#5848) 2025-04-24 16:29:27 +00:00
spaced4ndy
5262b8caca
core: support chat unread stats (#5844) 2025-04-23 14:15:17 +00:00
spaced4ndy
b9adc7b345
android, desktop: member support list updates, refresh button (#5842) 2025-04-23 08:04:54 +00:00
spaced4ndy
ef12bb9a6c
android, desktop: knocking UI improvements and fixes (#5841) 2025-04-22 16:01:37 +00:00
spaced4ndy
4a103c94c1
android, desktop: fix item statuses updates (#5840) 2025-04-22 10:47:17 +00:00
spaced4ndy
805a69da94
android, desktop: group knocking (#5816) 2025-04-21 15:17:21 +00:00
spaced4ndy
3095b980cb Merge branch 'master' into group-knocking 2025-04-15 19:27:36 +04:00
spaced4ndy
c9f398b106
core: group knocking - acceptance to group chat item (#5810) 2025-04-07 07:55:56 +00:00
spaced4ndy
d85ac4af04
core: member support chat stats (#5803)
* core: member support chat stats

* schema

* update counts

* mark read wip

* dec counts on read

* rename

* plans

* test, fixes

* plans

* refactor

* rename

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2025-04-05 12:25:45 +01:00
spaced4ndy
c12817ac81
core: rework knocking protocol (#5802) 2025-04-03 09:36:28 +00:00
spaced4ndy
9654e193af
core: member admission (#5798) 2025-04-03 08:55:32 +00:00
spaced4ndy
4718494329 Merge branch 'master' into group-knocking 2025-04-02 12:02:29 +04:00
spaced4ndy
be1c4018cc
core: group knocking main functionality (#5726)
* docs: knocking rfc

* types, schema wip

* more comments

* rename

* more types

* change types

* wip compiles

* construct snd scope

* fix tests

* hide output

* sending

* fix member filtration

* plans

* conversations store/api model draft

* xgrpmemnew

* rework types wip

* wip

* wip

* remove tag

* wip (compiles)

* fix parsing

* fix query

* fixes, tests

* plans, comments

* fix member filtration

* wip

* protocol wip

* protocol wip

* fix

* fix tests

* fixes, test

* fix (remove NotInHistory)

* fix

* fix tests

* fix

* output

* fix tests

* tests

* gcsi in view

* fix tests

* plans

* refactor, make scopes Maybe

* remove groupChatFilter

* fix test

* change schema

* refactor

* rename

* rename

* rename

* refactor

* rename

* remove scope from item direction

* rework view

* change apis

* make api for cli

* change text

* refactor

* fixes

* comment

* rename

* refactor fmap/mapM

* refactor includeInHistory

* query plans

* knocking: patch scope (#5796)

* knocking: patch scope wip

* more patches

* schema

* remove space

* plans

* fix test

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2025-04-02 07:57:18 +00:00
166 changed files with 11874 additions and 3226 deletions

View file

@ -10,7 +10,7 @@
# SimpleX - the first messaging platform that has no user identifiers of any kind - 100% private by design!
[<img src="./images/trail-of-bits.jpg" height="80">](http://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html) &nbsp;&nbsp;&nbsp; [<img src="./images/privacy-guides.jpg" height="64">](https://www.privacyguides.org/en/real-time-communication/#simplex-chat) &nbsp;&nbsp;&nbsp; [<img src="./images/whonix-logo.jpg" height="64">](https://www.whonix.org/wiki/Chat#Recommendation) &nbsp;&nbsp;&nbsp; [<img src="./images/kuketz-blog.jpg" height="64">](https://www.kuketz-blog.de/simplex-eindruecke-vom-messenger-ohne-identifier/)
[<img src="./images/trail-of-bits.jpg" height="100">](http://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html) &nbsp;&nbsp;&nbsp; [<img src="./images/privacy-guides.jpg" height="80">](https://www.privacyguides.org/en/real-time-communication/#simplex-chat) &nbsp;&nbsp;&nbsp; [<img src="./images/kuketz-blog.jpg" height="80">](https://www.kuketz-blog.de/simplex-eindruecke-vom-messenger-ohne-identifier/)
## Welcome to SimpleX Chat!
@ -110,15 +110,6 @@ After you connect, you can [verify connection security code](./blog/20230103-sim
Read about the app features and settings in the new [User guide](./docs/guide/README.md).
## Contribute
We would love to have you join the development! You can help us with:
- [share the color theme](./docs/THEMES.md) you use in Android app!
- writing a tutorial or recipes about hosting servers, chat bot automations, etc.
- contributing to SimpleX Chat knowledge-base.
- developing features - please connect to us via chat so we can help you get started.
## Help translating SimpleX Chat
Thanks to our users and [Weblate](https://hosted.weblate.org/engage/simplex-chat/), SimpleX Chat apps, website and documents are translated to many other languages.
@ -150,6 +141,15 @@ Join our translators to help SimpleX grow!
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!
## Contribute
We would love to have you join the development! You can help us with:
- [share the color theme](./docs/THEMES.md) you use in Android app!
- writing a tutorial or recipes about hosting servers, chat bot automations, etc.
- contributing to SimpleX Chat knowledge-base.
- developing features - please connect to us via chat so we can help you get started.
## Please support us with your donations
Huge thank you to everybody who donated to SimpleX Chat!

View file

@ -39,9 +39,9 @@ enum ChatCommand: ChatCmdProtocol {
case apiGetSettings(settings: AppSettings)
case apiGetChatTags(userId: Int64)
case apiGetChats(userId: Int64)
case apiGetChat(chatId: ChatId, pagination: ChatPagination, search: String)
case apiGetChatItemInfo(type: ChatType, id: Int64, itemId: Int64)
case apiSendMessages(type: ChatType, id: Int64, live: Bool, ttl: Int?, composedMessages: [ComposedMessage])
case apiGetChat(chatId: ChatId, scope: GroupChatScope?, contentTag: MsgContentTag?, pagination: ChatPagination, search: String)
case apiGetChatItemInfo(type: ChatType, id: Int64, scope: GroupChatScope?, itemId: Int64)
case apiSendMessages(type: ChatType, id: Int64, scope: GroupChatScope?, live: Bool, ttl: Int?, composedMessages: [ComposedMessage])
case apiCreateChatTag(tag: ChatTagData)
case apiSetChatTags(type: ChatType, id: Int64, tagIds: [Int64])
case apiDeleteChatTag(tagId: Int64)
@ -49,15 +49,15 @@ enum ChatCommand: ChatCmdProtocol {
case apiReorderChatTags(tagIds: [Int64])
case apiCreateChatItems(noteFolderId: Int64, composedMessages: [ComposedMessage])
case apiReportMessage(groupId: Int64, chatItemId: Int64, reportReason: ReportReason, reportText: String)
case apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, updatedMessage: UpdatedMessage, live: Bool)
case apiDeleteChatItem(type: ChatType, id: Int64, itemIds: [Int64], mode: CIDeleteMode)
case apiUpdateChatItem(type: ChatType, id: Int64, scope: GroupChatScope?, itemId: Int64, updatedMessage: UpdatedMessage, live: Bool)
case apiDeleteChatItem(type: ChatType, id: Int64, scope: GroupChatScope?, itemIds: [Int64], mode: CIDeleteMode)
case apiDeleteMemberChatItem(groupId: Int64, itemIds: [Int64])
case apiArchiveReceivedReports(groupId: Int64)
case apiDeleteReceivedReports(groupId: Int64, itemIds: [Int64], mode: CIDeleteMode)
case apiChatItemReaction(type: ChatType, id: Int64, itemId: Int64, add: Bool, reaction: MsgReaction)
case apiChatItemReaction(type: ChatType, id: Int64, scope: GroupChatScope?, itemId: Int64, add: Bool, reaction: MsgReaction)
case apiGetReactionMembers(userId: Int64, groupId: Int64, itemId: Int64, reaction: MsgReaction)
case apiPlanForwardChatItems(toChatType: ChatType, toChatId: Int64, itemIds: [Int64])
case apiForwardChatItems(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemIds: [Int64], ttl: Int?)
case apiPlanForwardChatItems(fromChatType: ChatType, fromChatId: Int64, fromScope: GroupChatScope?, itemIds: [Int64])
case apiForwardChatItems(toChatType: ChatType, toChatId: Int64, toScope: GroupChatScope?, fromChatType: ChatType, fromChatId: Int64, fromScope: GroupChatScope?, itemIds: [Int64], ttl: Int?)
case apiGetNtfToken
case apiRegisterToken(token: DeviceToken, notificationMode: NotificationsMode)
case apiVerifyToken(token: DeviceToken, nonce: String, code: String)
@ -68,6 +68,8 @@ enum ChatCommand: ChatCmdProtocol {
case apiNewGroup(userId: Int64, incognito: Bool, groupProfile: GroupProfile)
case apiAddMember(groupId: Int64, contactId: Int64, memberRole: GroupMemberRole)
case apiJoinGroup(groupId: Int64)
case apiAcceptMember(groupId: Int64, groupMemberId: Int64, memberRole: GroupMemberRole)
case apiDeleteMemberSupportChat(groupId: Int64, groupMemberId: Int64)
case apiMembersRole(groupId: Int64, memberIds: [Int64], memberRole: GroupMemberRole)
case apiBlockMembersForAll(groupId: Int64, memberIds: [Int64], blocked: Bool)
case apiRemoveMembers(groupId: Int64, memberIds: [Int64], withMessages: Bool)
@ -147,8 +149,8 @@ enum ChatCommand: ChatCmdProtocol {
case apiCallStatus(contact: Contact, callStatus: WebRTCCallStatus)
// WebRTC calls /
case apiGetNetworkStatuses
case apiChatRead(type: ChatType, id: Int64)
case apiChatItemsRead(type: ChatType, id: Int64, itemIds: [Int64])
case apiChatRead(type: ChatType, id: Int64, scope: GroupChatScope?)
case apiChatItemsRead(type: ChatType, id: Int64, scope: GroupChatScope?, itemIds: [Int64])
case apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool)
case receiveFile(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool?, inline: Bool?)
case setFileToReceive(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool?)
@ -209,15 +211,16 @@ enum ChatCommand: ChatCmdProtocol {
case let .apiGetSettings(settings): return "/_get app settings \(encodeJSON(settings))"
case let .apiGetChatTags(userId): return "/_get tags \(userId)"
case let .apiGetChats(userId): return "/_get chats \(userId) pcc=on"
case let .apiGetChat(chatId, pagination, search): return "/_get chat \(chatId) \(pagination.cmdString)" +
(search == "" ? "" : " search=\(search)")
case let .apiGetChatItemInfo(type, id, itemId): return "/_get item info \(ref(type, id)) \(itemId)"
case let .apiSendMessages(type, id, live, ttl, composedMessages):
case let .apiGetChat(chatId, scope, contentTag, pagination, search):
let tag = contentTag != nil ? " content=\(contentTag!.rawValue)" : ""
return "/_get chat \(chatId)\(scopeRef(scope: scope))\(tag) \(pagination.cmdString)" + (search == "" ? "" : " search=\(search)")
case let .apiGetChatItemInfo(type, id, scope, itemId): return "/_get item info \(ref(type, id, scope: scope)) \(itemId)"
case let .apiSendMessages(type, id, scope, live, ttl, composedMessages):
let msgs = encodeJSON(composedMessages)
let ttlStr = ttl != nil ? "\(ttl!)" : "default"
return "/_send \(ref(type, id)) live=\(onOff(live)) ttl=\(ttlStr) json \(msgs)"
return "/_send \(ref(type, id, scope: scope)) live=\(onOff(live)) ttl=\(ttlStr) json \(msgs)"
case let .apiCreateChatTag(tag): return "/_create tag \(encodeJSON(tag))"
case let .apiSetChatTags(type, id, tagIds): return "/_tags \(ref(type, id)) \(tagIds.map({ "\($0)" }).joined(separator: ","))"
case let .apiSetChatTags(type, id, tagIds): return "/_tags \(ref(type, id, scope: nil)) \(tagIds.map({ "\($0)" }).joined(separator: ","))"
case let .apiDeleteChatTag(tagId): return "/_delete tag \(tagId)"
case let .apiUpdateChatTag(tagId, tagData): return "/_update tag \(tagId) \(encodeJSON(tagData))"
case let .apiReorderChatTags(tagIds): return "/_reorder tags \(tagIds.map({ "\($0)" }).joined(separator: ","))"
@ -226,17 +229,17 @@ enum ChatCommand: ChatCmdProtocol {
return "/_create *\(noteFolderId) json \(msgs)"
case let .apiReportMessage(groupId, chatItemId, reportReason, reportText):
return "/_report #\(groupId) \(chatItemId) reason=\(reportReason) \(reportText)"
case let .apiUpdateChatItem(type, id, itemId, um, live): return "/_update item \(ref(type, id)) \(itemId) live=\(onOff(live)) \(um.cmdString)"
case let .apiDeleteChatItem(type, id, itemIds, mode): return "/_delete item \(ref(type, id)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) \(mode.rawValue)"
case let .apiUpdateChatItem(type, id, scope, itemId, um, live): return "/_update item \(ref(type, id, scope: scope)) \(itemId) live=\(onOff(live)) \(um.cmdString)"
case let .apiDeleteChatItem(type, id, scope, itemIds, mode): return "/_delete item \(ref(type, id, scope: scope)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) \(mode.rawValue)"
case let .apiDeleteMemberChatItem(groupId, itemIds): return "/_delete member item #\(groupId) \(itemIds.map({ "\($0)" }).joined(separator: ","))"
case let .apiArchiveReceivedReports(groupId): return "/_archive reports #\(groupId)"
case let .apiDeleteReceivedReports(groupId, itemIds, mode): return "/_delete reports #\(groupId) \(itemIds.map({ "\($0)" }).joined(separator: ",")) \(mode.rawValue)"
case let .apiChatItemReaction(type, id, itemId, add, reaction): return "/_reaction \(ref(type, id)) \(itemId) \(onOff(add)) \(encodeJSON(reaction))"
case let .apiChatItemReaction(type, id, scope, itemId, add, reaction): return "/_reaction \(ref(type, id, scope: scope)) \(itemId) \(onOff(add)) \(encodeJSON(reaction))"
case let .apiGetReactionMembers(userId, groupId, itemId, reaction): return "/_reaction members \(userId) #\(groupId) \(itemId) \(encodeJSON(reaction))"
case let .apiPlanForwardChatItems(type, id, itemIds): return "/_forward plan \(ref(type, id)) \(itemIds.map({ "\($0)" }).joined(separator: ","))"
case let .apiForwardChatItems(toChatType, toChatId, fromChatType, fromChatId, itemIds, ttl):
case let .apiPlanForwardChatItems(type, id, scope, itemIds): return "/_forward plan \(ref(type, id, scope: scope)) \(itemIds.map({ "\($0)" }).joined(separator: ","))"
case let .apiForwardChatItems(toChatType, toChatId, toScope, fromChatType, fromChatId, fromScope, itemIds, ttl):
let ttlStr = ttl != nil ? "\(ttl!)" : "default"
return "/_forward \(ref(toChatType, toChatId)) \(ref(fromChatType, fromChatId)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) ttl=\(ttlStr)"
return "/_forward \(ref(toChatType, toChatId, scope: toScope)) \(ref(fromChatType, fromChatId, scope: fromScope)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) ttl=\(ttlStr)"
case .apiGetNtfToken: return "/_ntf get "
case let .apiRegisterToken(token, notificationMode): return "/_ntf register \(token.cmdString) \(notificationMode.rawValue)"
case let .apiVerifyToken(token, nonce, code): return "/_ntf verify \(token.cmdString) \(nonce) \(code)"
@ -247,6 +250,8 @@ enum ChatCommand: ChatCmdProtocol {
case let .apiNewGroup(userId, incognito, groupProfile): return "/_group \(userId) incognito=\(onOff(incognito)) \(encodeJSON(groupProfile))"
case let .apiAddMember(groupId, contactId, memberRole): return "/_add #\(groupId) \(contactId) \(memberRole)"
case let .apiJoinGroup(groupId): return "/_join #\(groupId)"
case let .apiAcceptMember(groupId, groupMemberId, memberRole): return "/_accept member #\(groupId) \(groupMemberId) \(memberRole.rawValue)"
case let .apiDeleteMemberSupportChat(groupId, groupMemberId): return "/_delete member chat #\(groupId) \(groupMemberId)"
case let .apiMembersRole(groupId, memberIds, memberRole): return "/_member role #\(groupId) \(memberIds.map({ "\($0)" }).joined(separator: ",")) \(memberRole.rawValue)"
case let .apiBlockMembersForAll(groupId, memberIds, blocked): return "/_block #\(groupId) \(memberIds.map({ "\($0)" }).joined(separator: ",")) blocked=\(onOff(blocked))"
case let .apiRemoveMembers(groupId, memberIds, withMessages): return "/_remove #\(groupId) \(memberIds.map({ "\($0)" }).joined(separator: ",")) messages=\(onOff(withMessages))"
@ -270,13 +275,13 @@ enum ChatCommand: ChatCmdProtocol {
case let .apiAcceptConditions(conditionsId, operatorIds): return "/_accept_conditions \(conditionsId) \(joinedIds(operatorIds))"
case let .apiSetChatItemTTL(userId, seconds): return "/_ttl \(userId) \(chatItemTTLStr(seconds: seconds))"
case let .apiGetChatItemTTL(userId): return "/_ttl \(userId)"
case let .apiSetChatTTL(userId, type, id, seconds): return "/_ttl \(userId) \(ref(type, id)) \(chatItemTTLStr(seconds: seconds))"
case let .apiSetChatTTL(userId, type, id, seconds): return "/_ttl \(userId) \(ref(type, id, scope: nil)) \(chatItemTTLStr(seconds: seconds))"
case let .apiSetNetworkConfig(networkConfig): return "/_network \(encodeJSON(networkConfig))"
case .apiGetNetworkConfig: return "/network"
case let .apiSetNetworkInfo(networkInfo): return "/_network info \(encodeJSON(networkInfo))"
case .reconnectAllServers: return "/reconnect"
case let .reconnectServer(userId, smpServer): return "/reconnect \(userId) \(smpServer)"
case let .apiSetChatSettings(type, id, chatSettings): return "/_settings \(ref(type, id)) \(encodeJSON(chatSettings))"
case let .apiSetChatSettings(type, id, chatSettings): return "/_settings \(ref(type, id, scope: nil)) \(encodeJSON(chatSettings))"
case let .apiSetMemberSettings(groupId, groupMemberId, memberSettings): return "/_member settings #\(groupId) \(groupMemberId) \(encodeJSON(memberSettings))"
case let .apiContactInfo(contactId): return "/_info @\(contactId)"
case let .apiGroupMemberInfo(groupId, groupMemberId): return "/_info #\(groupId) \(groupMemberId)"
@ -308,8 +313,8 @@ enum ChatCommand: ChatCmdProtocol {
case let .apiConnectPlan(userId, connLink): return "/_connect plan \(userId) \(connLink)"
case let .apiConnect(userId, incognito, connLink): return "/_connect \(userId) incognito=\(onOff(incognito)) \(connLink.connFullLink) \(connLink.connShortLink ?? "")"
case let .apiConnectContactViaAddress(userId, incognito, contactId): return "/_connect contact \(userId) incognito=\(onOff(incognito)) \(contactId)"
case let .apiDeleteChat(type, id, chatDeleteMode): return "/_delete \(ref(type, id)) \(chatDeleteMode.cmdString)"
case let .apiClearChat(type, id): return "/_clear chat \(ref(type, id))"
case let .apiDeleteChat(type, id, chatDeleteMode): return "/_delete \(ref(type, id, scope: nil)) \(chatDeleteMode.cmdString)"
case let .apiClearChat(type, id): return "/_clear chat \(ref(type, id, scope: nil))"
case let .apiListContacts(userId): return "/_contacts \(userId)"
case let .apiUpdateProfile(userId, profile): return "/_profile \(userId) \(encodeJSON(profile))"
case let .apiSetContactPrefs(contactId, preferences): return "/_set prefs @\(contactId) \(encodeJSON(preferences))"
@ -334,9 +339,9 @@ enum ChatCommand: ChatCmdProtocol {
case .apiGetCallInvitations: return "/_call get"
case let .apiCallStatus(contact, callStatus): return "/_call status @\(contact.apiId) \(callStatus.rawValue)"
case .apiGetNetworkStatuses: return "/_network_statuses"
case let .apiChatRead(type, id): return "/_read chat \(ref(type, id))"
case let .apiChatItemsRead(type, id, itemIds): return "/_read chat items \(ref(type, id)) \(joinedIds(itemIds))"
case let .apiChatUnread(type, id, unreadChat): return "/_unread chat \(ref(type, id)) \(onOff(unreadChat))"
case let .apiChatRead(type, id, scope): return "/_read chat \(ref(type, id, scope: scope))"
case let .apiChatItemsRead(type, id, scope, itemIds): return "/_read chat items \(ref(type, id, scope: scope)) \(joinedIds(itemIds))"
case let .apiChatUnread(type, id, unreadChat): return "/_unread chat \(ref(type, id, scope: nil)) \(onOff(unreadChat))"
case let .receiveFile(fileId, userApprovedRelays, encrypt, inline): return "/freceive \(fileId)\(onOffParam("approved_relays", userApprovedRelays))\(onOffParam("encrypt", encrypt))\(onOffParam("inline", inline))"
case let .setFileToReceive(fileId, userApprovedRelays, encrypt): return "/_set_file_to_receive \(fileId)\(onOffParam("approved_relays", userApprovedRelays))\(onOffParam("encrypt", encrypt))"
case let .cancelFile(fileId): return "/fcancel \(fileId)"
@ -421,6 +426,8 @@ enum ChatCommand: ChatCmdProtocol {
case .apiNewGroup: return "apiNewGroup"
case .apiAddMember: return "apiAddMember"
case .apiJoinGroup: return "apiJoinGroup"
case .apiAcceptMember: return "apiAcceptMember"
case .apiDeleteMemberSupportChat: return "apiDeleteMemberSupportChat"
case .apiMembersRole: return "apiMembersRole"
case .apiBlockMembersForAll: return "apiBlockMembersForAll"
case .apiRemoveMembers: return "apiRemoveMembers"
@ -523,8 +530,20 @@ enum ChatCommand: ChatCmdProtocol {
}
}
func ref(_ type: ChatType, _ id: Int64) -> String {
"\(type.rawValue)\(id)"
func ref(_ type: ChatType, _ id: Int64, scope: GroupChatScope?) -> String {
"\(type.rawValue)\(id)\(scopeRef(scope: scope))"
}
func scopeRef(scope: GroupChatScope?) -> String {
switch (scope) {
case .none: ""
case let .memberSupport(groupMemberId_):
if let groupMemberId = groupMemberId_ {
"(_support:\(groupMemberId))"
} else {
"(_support)"
}
}
}
func joinedIds(_ ids: [Int64]) -> String {
@ -711,6 +730,7 @@ enum ChatResponse1: Decodable, ChatAPIResult {
case contactDeleted(user: UserRef, contact: Contact)
case contactConnectionDeleted(user: UserRef, connection: PendingContactConnection)
case groupDeletedUser(user: UserRef, groupInfo: GroupInfo)
case itemsReadForChat(user: UserRef, chatInfo: ChatInfo)
case chatCleared(user: UserRef, chatInfo: ChatInfo)
case userProfileNoChange(user: User)
case userProfileUpdated(user: User, fromProfile: Profile, toProfile: Profile, updateSummary: UserProfileUpdateSummary)
@ -749,6 +769,7 @@ enum ChatResponse1: Decodable, ChatAPIResult {
case .contactDeleted: "contactDeleted"
case .contactConnectionDeleted: "contactConnectionDeleted"
case .groupDeletedUser: "groupDeletedUser"
case .itemsReadForChat: "itemsReadForChat"
case .chatCleared: "chatCleared"
case .userProfileNoChange: "userProfileNoChange"
case .userProfileUpdated: "userProfileUpdated"
@ -781,6 +802,7 @@ enum ChatResponse1: Decodable, ChatAPIResult {
case let .contactDeleted(u, contact): return withUser(u, String(describing: contact))
case let .contactConnectionDeleted(u, connection): return withUser(u, String(describing: connection))
case let .groupDeletedUser(u, groupInfo): return withUser(u, String(describing: groupInfo))
case let .itemsReadForChat(u, chatInfo): return withUser(u, String(describing: chatInfo))
case let .chatCleared(u, chatInfo): return withUser(u, String(describing: chatInfo))
case .userProfileNoChange: return noDetails
case let .userProfileUpdated(u, _, toProfile, _): return withUser(u, String(describing: toProfile))
@ -831,6 +853,8 @@ enum ChatResponse2: Decodable, ChatAPIResult {
case userDeletedMembers(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], withMessages: Bool)
case leftMemberUser(user: UserRef, groupInfo: GroupInfo)
case groupMembers(user: UserRef, group: SimpleXChat.Group)
case memberAccepted(user: UserRef, groupInfo: GroupInfo, member: GroupMember)
case memberSupportChatDeleted(user: UserRef, groupInfo: GroupInfo, member: GroupMember)
case membersRoleUser(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], toRole: GroupMemberRole)
case membersBlockedForAllUser(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], blocked: Bool)
case groupUpdated(user: UserRef, toGroup: GroupInfo)
@ -879,6 +903,8 @@ enum ChatResponse2: Decodable, ChatAPIResult {
case .userDeletedMembers: "userDeletedMembers"
case .leftMemberUser: "leftMemberUser"
case .groupMembers: "groupMembers"
case .memberAccepted: "memberAccepted"
case .memberSupportChatDeleted: "memberSupportChatDeleted"
case .membersRoleUser: "membersRoleUser"
case .membersBlockedForAllUser: "membersBlockedForAllUser"
case .groupUpdated: "groupUpdated"
@ -923,6 +949,8 @@ enum ChatResponse2: Decodable, ChatAPIResult {
case let .userDeletedMembers(u, groupInfo, members, withMessages): return withUser(u, "groupInfo: \(groupInfo)\nmembers: \(members)\nwithMessages: \(withMessages)")
case let .leftMemberUser(u, groupInfo): return withUser(u, String(describing: groupInfo))
case let .groupMembers(u, group): return withUser(u, String(describing: group))
case let .memberAccepted(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)")
case let .memberSupportChatDeleted(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)")
case let .membersRoleUser(u, groupInfo, members, toRole): return withUser(u, "groupInfo: \(groupInfo)\nmembers: \(members)\ntoRole: \(toRole)")
case let .membersBlockedForAllUser(u, groupInfo, members, blocked): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(members)\nblocked: \(blocked)")
case let .groupUpdated(u, toGroup): return withUser(u, String(describing: toGroup))
@ -988,6 +1016,7 @@ enum ChatEvent: Decodable, ChatAPIResult {
case groupLinkConnecting(user: UserRef, groupInfo: GroupInfo, hostMember: GroupMember)
case businessLinkConnecting(user: UserRef, groupInfo: GroupInfo, hostMember: GroupMember, fromContact: Contact)
case joinedGroupMemberConnecting(user: UserRef, groupInfo: GroupInfo, hostMember: GroupMember, member: GroupMember)
case memberAcceptedByOther(user: UserRef, groupInfo: GroupInfo, acceptingMember: GroupMember, member: GroupMember)
case memberRole(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, member: GroupMember, fromRole: GroupMemberRole, toRole: GroupMemberRole)
case memberBlockedForAll(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, member: GroupMember, blocked: Bool)
case deletedMemberUser(user: UserRef, groupInfo: GroupInfo, member: GroupMember, withMessages: Bool)
@ -1064,6 +1093,7 @@ enum ChatEvent: Decodable, ChatAPIResult {
case .groupLinkConnecting: "groupLinkConnecting"
case .businessLinkConnecting: "businessLinkConnecting"
case .joinedGroupMemberConnecting: "joinedGroupMemberConnecting"
case .memberAcceptedByOther: "memberAcceptedByOther"
case .memberRole: "memberRole"
case .memberBlockedForAll: "memberBlockedForAll"
case .deletedMemberUser: "deletedMemberUser"
@ -1144,6 +1174,7 @@ enum ChatEvent: Decodable, ChatAPIResult {
case let .groupLinkConnecting(u, groupInfo, hostMember): return withUser(u, "groupInfo: \(groupInfo)\nhostMember: \(String(describing: hostMember))")
case let .businessLinkConnecting(u, groupInfo, hostMember, fromContact): return withUser(u, "groupInfo: \(groupInfo)\nhostMember: \(String(describing: hostMember))\nfromContact: \(String(describing: fromContact))")
case let .joinedGroupMemberConnecting(u, groupInfo, hostMember, member): return withUser(u, "groupInfo: \(groupInfo)\nhostMember: \(hostMember)\nmember: \(member)")
case let .memberAcceptedByOther(u, groupInfo, acceptingMember, member): return withUser(u, "groupInfo: \(groupInfo)\nacceptingMember: \(acceptingMember)\nmember: \(member)")
case let .memberRole(u, groupInfo, byMember, member, fromRole, toRole): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\nmember: \(member)\nfromRole: \(fromRole)\ntoRole: \(toRole)")
case let .memberBlockedForAll(u, groupInfo, byMember, member, blocked): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\nmember: \(member)\nblocked: \(blocked)")
case let .deletedMemberUser(u, groupInfo, member, withMessages): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)\nwithMessages: \(withMessages)")

View file

@ -52,8 +52,26 @@ private func addTermItem(_ items: inout [TerminalItem], _ item: TerminalItem) {
items.append(item)
}
// analogue for SecondaryContextFilter in Kotlin
enum SecondaryItemsModelFilter {
case groupChatScopeContext(groupScopeInfo: GroupChatScopeInfo)
case msgContentTagContext(contentTag: MsgContentTag)
func descr() -> String {
switch self {
case let .groupChatScopeContext(groupScopeInfo):
return "groupChatScopeContext \(groupScopeInfo.toChatScope())"
case let .msgContentTagContext(contentTag):
return "msgContentTagContext \(contentTag.rawValue)"
}
}
}
// analogue for ChatsContext in Kotlin
class ItemsModel: ObservableObject {
static let shared = ItemsModel()
static let shared = ItemsModel(secondaryIMFilter: nil)
public var secondaryIMFilter: SecondaryItemsModelFilter?
public var preloadState = PreloadState()
private let publisher = ObservableObjectPublisher()
private var bag = Set<AnyCancellable>()
var reversedChatItems: [ChatItem] = [] {
@ -77,13 +95,20 @@ class ItemsModel: ObservableObject {
chatState.splits.isEmpty || chatState.splits.first != reversedChatItems.first?.id
}
init() {
init(secondaryIMFilter: SecondaryItemsModelFilter? = nil) {
self.secondaryIMFilter = secondaryIMFilter
publisher
.throttle(for: 0.2, scheduler: DispatchQueue.main, latest: true)
.sink { self.objectWillChange.send() }
.store(in: &bag)
}
static func loadSecondaryChat(_ chatId: ChatId, chatFilter: SecondaryItemsModelFilter, willNavigate: @escaping () -> Void = {}) {
let im = ItemsModel(secondaryIMFilter: chatFilter)
ChatModel.shared.secondaryIM = im
im.loadOpenChat(chatId, willNavigate: willNavigate)
}
func loadOpenChat(_ chatId: ChatId, willNavigate: @escaping () -> Void = {}) {
navigationTimeoutTask?.cancel()
loadChatTask?.cancel()
@ -99,7 +124,7 @@ class ItemsModel: ObservableObject {
loadChatTask = Task {
await MainActor.run { self.isLoading = true }
// try? await Task.sleep(nanoseconds: 1000_000000)
await loadChat(chatId: chatId)
await loadChat(chatId: chatId, im: self)
if !Task.isCancelled {
await MainActor.run {
self.isLoading = false
@ -114,7 +139,7 @@ class ItemsModel: ObservableObject {
loadChatTask?.cancel()
loadChatTask = Task {
// try? await Task.sleep(nanoseconds: 1000_000000)
await loadChat(chatId: chatId, openAroundItemId: openAroundItemId, clearItems: openAroundItemId == nil)
await loadChat(chatId: chatId, im: self, openAroundItemId: openAroundItemId, clearItems: openAroundItemId == nil)
if !Task.isCancelled {
await MainActor.run {
if openAroundItemId == nil {
@ -124,6 +149,34 @@ class ItemsModel: ObservableObject {
}
}
}
public var contentTag: MsgContentTag? {
switch secondaryIMFilter {
case nil: nil
case .groupChatScopeContext: nil
case let .msgContentTagContext(contentTag): contentTag
}
}
public var groupScopeInfo: GroupChatScopeInfo? {
switch secondaryIMFilter {
case nil: nil
case let .groupChatScopeContext(scopeInfo): scopeInfo
case .msgContentTagContext: nil
}
}
}
class PreloadState {
var prevFirstVisible: Int64 = Int64.min
var prevItemsCount: Int = 0
var preloading: Bool = false
func clear() {
prevFirstVisible = Int64.min
prevItemsCount = 0
preloading = false
}
}
class ChatTagsModel: ObservableObject {
@ -287,7 +340,6 @@ final class ChatModel: ObservableObject {
// current chat
@Published var chatId: String?
@Published var openAroundItemId: ChatItem.ID? = nil
var chatItemStatuses: Dictionary<Int64, CIStatus> = [:]
@Published var chatToTop: String?
@Published var groupMembers: [GMember] = []
@Published var groupMembersIndexes: Dictionary<Int64, Int> = [:] // groupMemberId to index in groupMembers list
@ -336,6 +388,10 @@ final class ChatModel: ObservableObject {
let im = ItemsModel.shared
// ItemsModel for secondary chat view (such as support scope chat), as opposed to ItemsModel.shared used for primary chat
@Published var secondaryIM: ItemsModel? = nil
@Published var secondaryPendingInviteeChatOpened = false
static var ok: Bool { ChatModel.shared.chatDbStatus == .ok }
let ntfEnableLocal = true
@ -393,7 +449,7 @@ final class ChatModel: ObservableObject {
func getGroupChat(_ groupId: Int64) -> Chat? {
chats.first { chat in
if case let .group(groupInfo) = chat.chatInfo {
if case let .group(groupInfo, _) = chat.chatInfo {
return groupInfo.groupId == groupId
} else {
return false
@ -446,7 +502,11 @@ final class ChatModel: ObservableObject {
func updateChatInfo(_ cInfo: ChatInfo) {
if let i = getChatIndex(cInfo.id) {
chats[i].chatInfo = cInfo
if case let .group(groupInfo, groupChatScope) = cInfo, groupChatScope != nil {
chats[i].chatInfo = .group(groupInfo: groupInfo, groupChatScope: nil)
} else {
chats[i].chatInfo = cInfo
}
chats[i].created = Date.now
}
}
@ -468,7 +528,7 @@ final class ChatModel: ObservableObject {
}
func updateGroup(_ groupInfo: GroupInfo) {
updateChat(.group(groupInfo: groupInfo))
updateChat(.group(groupInfo: groupInfo, groupChatScope: nil))
}
private func updateChat(_ cInfo: ChatInfo, addMissing: Bool = true) {
@ -511,77 +571,112 @@ final class ChatModel: ObservableObject {
// }
func addChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) {
// updates membersRequireAttention
updateChatInfo(cInfo)
// mark chat non deleted
if case let .direct(contact) = cInfo, contact.chatDeleted {
var updatedContact = contact
updatedContact.chatDeleted = false
updateContact(updatedContact)
}
// update previews
// update chat list
if let i = getChatIndex(cInfo.id) {
chats[i].chatItems = switch cInfo {
case .group:
if let currentPreviewItem = chats[i].chatItems.first {
if cItem.meta.itemTs >= currentPreviewItem.meta.itemTs {
[cItem]
// update preview
if cInfo.groupChatScope() == nil || cInfo.groupInfo?.membership.memberPending ?? false {
chats[i].chatItems = switch cInfo {
case .group:
if let currentPreviewItem = chats[i].chatItems.first {
if cItem.meta.itemTs >= currentPreviewItem.meta.itemTs {
[cItem]
} else {
[currentPreviewItem]
}
} else {
[currentPreviewItem]
[cItem]
}
} else {
default:
[cItem]
}
default:
[cItem]
}
if case .rcvNew = cItem.meta.itemStatus {
unreadCollector.changeUnreadCounter(cInfo.id, by: 1, unreadMentions: cItem.meta.userMention ? 1 : 0)
if case .rcvNew = cItem.meta.itemStatus {
unreadCollector.changeUnreadCounter(cInfo.id, by: 1, unreadMentions: cItem.meta.userMention ? 1 : 0)
}
}
// pop chat
popChatCollector.throttlePopChat(cInfo.id, currentPosition: i)
} else {
addChat(Chat(chatInfo: cInfo, chatItems: [cItem]))
if cInfo.groupChatScope() == nil {
addChat(Chat(chatInfo: cInfo, chatItems: [cItem]))
} else {
addChat(Chat(chatInfo: cInfo, chatItems: []))
}
}
// add to current chat
if chatId == cInfo.id {
_ = _upsertChatItem(cInfo, cItem)
// add to current scope
if let ciIM = getCIItemsModel(cInfo, cItem) {
_ = _upsertChatItem(ciIM, cInfo, cItem)
}
}
func getCIItemsModel(_ cInfo: ChatInfo, _ ci: ChatItem) -> ItemsModel? {
let cInfoScope = cInfo.groupChatScope()
if let cInfoScope = cInfoScope {
switch cInfoScope {
case .memberSupport:
switch secondaryIM?.secondaryIMFilter {
case .none:
return nil
case let .groupChatScopeContext(groupScopeInfo):
return (cInfo.id == chatId && sameChatScope(cInfoScope, groupScopeInfo.toChatScope())) ? secondaryIM : nil
case let .msgContentTagContext(contentTag):
return (cInfo.id == chatId && ci.isReport && contentTag == .report) ? secondaryIM : nil
}
}
} else {
return cInfo.id == chatId ? im : nil
}
}
func upsertChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) -> Bool {
// update previews
var res: Bool
if let chat = getChat(cInfo.id) {
if let pItem = chat.chatItems.last {
if pItem.id == cItem.id || (chatId == cInfo.id && im.reversedChatItems.first(where: { $0.id == cItem.id }) == nil) {
// update chat list
var itemAdded: Bool = false
if cInfo.groupChatScope() == nil {
if let chat = getChat(cInfo.id) {
if let pItem = chat.chatItems.last {
if pItem.id == cItem.id || (chatId == cInfo.id && im.reversedChatItems.first(where: { $0.id == cItem.id }) == nil) {
chat.chatItems = [cItem]
}
} else {
chat.chatItems = [cItem]
}
} else {
chat.chatItems = [cItem]
addChat(Chat(chatInfo: cInfo, chatItems: [cItem]))
itemAdded = true
}
if cItem.isDeletedContent || cItem.meta.itemDeleted != nil {
VoiceItemState.stopVoiceInChatView(cInfo, cItem)
}
res = false
} else {
addChat(Chat(chatInfo: cInfo, chatItems: [cItem]))
res = true
}
if cItem.isDeletedContent || cItem.meta.itemDeleted != nil {
VoiceItemState.stopVoiceInChatView(cInfo, cItem)
// update current scope
if let ciIM = getCIItemsModel(cInfo, cItem) {
itemAdded = _upsertChatItem(ciIM, cInfo, cItem)
}
// update current chat
return chatId == cInfo.id ? _upsertChatItem(cInfo, cItem) : res
return itemAdded
}
private func _upsertChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) -> Bool {
if let i = getChatItemIndex(cItem) {
_updateChatItem(at: i, with: cItem)
ChatItemDummyModel.shared.sendUpdate()
private func _upsertChatItem(_ ciIM: ItemsModel, _ cInfo: ChatInfo, _ cItem: ChatItem) -> Bool {
if let i = getChatItemIndex(ciIM, cItem) {
let oldStatus = ciIM.reversedChatItems[i].meta.itemStatus
let newStatus = cItem.meta.itemStatus
var ci = cItem
if shouldKeepOldSndCIStatus(oldStatus: oldStatus, newStatus: newStatus) {
ci.meta.itemStatus = oldStatus
}
_updateChatItem(ciIM: ciIM, at: i, with: ci)
ChatItemDummyModel.shared.sendUpdate() // TODO [knocking] review what's this
return false
} else {
var ci = cItem
if let status = chatItemStatuses.removeValue(forKey: ci.id), case .sndNew = ci.meta.itemStatus {
ci.meta.itemStatus = status
}
im.reversedChatItems.insert(ci, at: hasLiveDummy ? 1 : 0)
im.chatState.itemAdded((ci.id, ci.isRcvNew), hasLiveDummy ? 1 : 0)
im.itemAdded = true
ciIM.reversedChatItems.insert(cItem, at: hasLiveDummy ? 1 : 0)
ciIM.chatState.itemAdded((cItem.id, cItem.isRcvNew), hasLiveDummy ? 1 : 0)
ciIM.itemAdded = true
ChatItemDummyModel.shared.sendUpdate()
return true
}
@ -595,40 +690,42 @@ final class ChatModel: ObservableObject {
}
func updateChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem, status: CIStatus? = nil) {
if chatId == cInfo.id, let i = getChatItemIndex(cItem) {
if let ciIM = getCIItemsModel(cInfo, cItem),
let i = getChatItemIndex(ciIM, cItem) {
withConditionalAnimation {
_updateChatItem(at: i, with: cItem)
_updateChatItem(ciIM: ciIM, at: i, with: cItem)
}
} else if let status = status {
chatItemStatuses.updateValue(status, forKey: cItem.id)
}
}
private func _updateChatItem(at i: Int, with cItem: ChatItem) {
im.reversedChatItems[i] = cItem
im.reversedChatItems[i].viewTimestamp = .now
private func _updateChatItem(ciIM: ItemsModel, at i: Int, with cItem: ChatItem) {
ciIM.reversedChatItems[i] = cItem
ciIM.reversedChatItems[i].viewTimestamp = .now
}
func getChatItemIndex(_ cItem: ChatItem) -> Int? {
im.reversedChatItems.firstIndex(where: { $0.id == cItem.id })
func getChatItemIndex(_ ciIM: ItemsModel, _ cItem: ChatItem) -> Int? {
ciIM.reversedChatItems.firstIndex(where: { $0.id == cItem.id })
}
func removeChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) {
if cItem.isRcvNew {
unreadCollector.changeUnreadCounter(cInfo.id, by: -1, unreadMentions: cItem.meta.userMention ? -1 : 0)
}
// update previews
if let chat = getChat(cInfo.id) {
if let pItem = chat.chatItems.last, pItem.id == cItem.id {
chat.chatItems = [ChatItem.deletedItemDummy()]
// update chat list
if cInfo.groupChatScope() == nil {
if cItem.isRcvNew {
unreadCollector.changeUnreadCounter(cInfo.id, by: -1, unreadMentions: cItem.meta.userMention ? -1 : 0)
}
// update previews
if let chat = getChat(cInfo.id) {
if let pItem = chat.chatItems.last, pItem.id == cItem.id {
chat.chatItems = [ChatItem.deletedItemDummy()]
}
}
}
// remove from current chat
if chatId == cInfo.id {
if let i = getChatItemIndex(cItem) {
// remove from current scope
if let ciIM = getCIItemsModel(cInfo, cItem) {
if let i = getChatItemIndex(ciIM, cItem) {
withAnimation {
let item = im.reversedChatItems.remove(at: i)
im.chatState.itemsRemoved([(item.id, i, item.isRcvNew)], im.reversedChatItems.reversed())
let item = ciIM.reversedChatItems.remove(at: i)
ciIM.chatState.itemsRemoved([(item.id, i, item.isRcvNew)], im.reversedChatItems.reversed())
}
}
}
@ -644,7 +741,7 @@ final class ChatModel: ObservableObject {
if chatId == groupInfo.id {
for i in 0..<im.reversedChatItems.count {
if let updatedItem = removedUpdatedItem(im.reversedChatItems[i]) {
_updateChatItem(at: i, with: updatedItem)
_updateChatItem(ciIM: im, at: i, with: updatedItem) // TODO [knocking] review: use getCIItemsModel?
}
}
} else if let chat = getChat(groupInfo.id),
@ -736,7 +833,7 @@ final class ChatModel: ObservableObject {
im.reversedChatItems.first?.isLiveDummy == true
}
func markAllChatItemsRead(_ cInfo: ChatInfo) {
func markAllChatItemsRead(_ chatIM: ItemsModel, _ cInfo: ChatInfo) {
// update preview
_updateChat(cInfo.id) { chat in
self.decreaseUnreadCounter(user: self.currentUser!, chat: chat)
@ -747,7 +844,7 @@ final class ChatModel: ObservableObject {
if chatId == cInfo.id {
var i = 0
while i < im.reversedChatItems.count {
markChatItemRead_(i)
markChatItemRead_(chatIM, i)
i += 1
}
im.chatState.itemsRead(nil, im.reversedChatItems.reversed())
@ -772,27 +869,26 @@ final class ChatModel: ObservableObject {
}
// clear current chat
if chatId == cInfo.id {
chatItemStatuses = [:]
im.reversedChatItems = []
im.chatState.clear()
}
}
func markChatItemsRead(_ cInfo: ChatInfo, _ itemIds: [ChatItem.ID], _ mentionsRead: Int) {
func markChatItemsRead(_ chatIM: ItemsModel, _ cInfo: ChatInfo, _ itemIds: [ChatItem.ID], _ mentionsRead: Int) {
if self.chatId == cInfo.id {
var unreadItemIds: Set<ChatItem.ID> = []
var i = 0
var ids = Set(itemIds)
while i < im.reversedChatItems.count && !ids.isEmpty {
let item = im.reversedChatItems[i]
while i < chatIM.reversedChatItems.count && !ids.isEmpty {
let item = chatIM.reversedChatItems[i]
if ids.contains(item.id) && item.isRcvNew {
markChatItemRead_(i)
markChatItemRead_(chatIM, i)
unreadItemIds.insert(item.id)
ids.remove(item.id)
}
i += 1
}
im.chatState.itemsRead(unreadItemIds, im.reversedChatItems.reversed())
chatIM.chatState.itemsRead(unreadItemIds, chatIM.reversedChatItems.reversed())
}
self.unreadCollector.changeUnreadCounter(cInfo.id, by: -itemIds.count, unreadMentions: -mentionsRead)
}
@ -888,13 +984,13 @@ final class ChatModel: ObservableObject {
}
}
private func markChatItemRead_(_ i: Int) {
let meta = im.reversedChatItems[i].meta
private func markChatItemRead_(_ chatIM: ItemsModel, _ i: Int) {
let meta = chatIM.reversedChatItems[i].meta
if case .rcvNew = meta.itemStatus {
im.reversedChatItems[i].meta.itemStatus = .rcvRead
im.reversedChatItems[i].viewTimestamp = .now
chatIM.reversedChatItems[i].meta.itemStatus = .rcvRead
chatIM.reversedChatItems[i].viewTimestamp = .now
if meta.itemLive != true, let ttl = meta.itemTimed?.ttl {
im.reversedChatItems[i].meta.itemTimed?.deleteAt = .now + TimeInterval(ttl)
chatIM.reversedChatItems[i].meta.itemTimed?.deleteAt = .now + TimeInterval(ttl)
}
}
}
@ -973,7 +1069,7 @@ final class ChatModel: ObservableObject {
var count = 0
var ns: [String] = []
if let ciCategory = chatItem.mergeCategory,
var i = getChatItemIndex(chatItem) {
var i = getChatItemIndex(im, chatItem) { // TODO [knocking] review: use getCIItemsModel?
while i < im.reversedChatItems.count {
let ci = im.reversedChatItems[i]
if ci.mergeCategory != ciCategory { break }
@ -989,7 +1085,7 @@ final class ChatModel: ObservableObject {
// 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) {
if let i = getChatItemIndex(im, ci) { // TODO [knocking] review: use getCIItemsModel?
(i, i > 0 ? im.reversedChatItems[i - 1] : nil)
} else {
(nil, nil)
@ -1100,7 +1196,7 @@ final class ChatModel: ObservableObject {
func removeWallpaperFilesFromChat(_ chat: Chat) {
if case let .direct(contact) = chat.chatInfo {
removeWallpaperFilesFromTheme(contact.uiThemes)
} else if case let .group(groupInfo) = chat.chatInfo {
} else if case let .group(groupInfo, _) = chat.chatInfo {
removeWallpaperFilesFromTheme(groupInfo.uiThemes)
}
}
@ -1164,6 +1260,18 @@ final class Chat: ObservableObject, Identifiable, ChatLike {
var viewId: String { get { "\(chatInfo.id) \(created.timeIntervalSince1970)" } }
var supportUnreadCount: Int {
switch chatInfo {
case let .group(groupInfo, _):
if groupInfo.canModerate {
return groupInfo.membersRequireAttention
} else {
return groupInfo.membership.supportChat?.unread ?? 0
}
default: return 0
}
}
public static var sampleData: Chat = Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: [])
}

View file

@ -344,43 +344,54 @@ func apiGetChatTagsAsync() async throws -> [ChatTag] {
let loadItemsPerPage = 50
func apiGetChat(chatId: ChatId, pagination: ChatPagination, search: String = "") async throws -> (Chat, NavigationInfo) {
let r: ChatResponse0 = try await chatSendCmd(.apiGetChat(chatId: chatId, pagination: pagination, search: search))
func apiGetChat(chatId: ChatId, scope: GroupChatScope?, contentTag: MsgContentTag? = nil, pagination: ChatPagination, search: String = "") async throws -> (Chat, NavigationInfo) {
let r: ChatResponse0 = try await chatSendCmd(.apiGetChat(chatId: chatId, scope: scope, contentTag: contentTag, pagination: pagination, search: search))
if case let .apiChat(_, chat, navInfo) = r { return (Chat.init(chat), navInfo ?? NavigationInfo()) }
throw r.unexpected
}
func loadChat(chat: Chat, search: String = "", clearItems: Bool = true) async {
await loadChat(chatId: chat.chatInfo.id, search: search, clearItems: clearItems)
func loadChat(chat: Chat, im: ItemsModel, search: String = "", clearItems: Bool = true) async {
await loadChat(chatId: chat.chatInfo.id, im: im, search: search, clearItems: clearItems)
}
func loadChat(chatId: ChatId, search: String = "", openAroundItemId: ChatItem.ID? = nil, clearItems: Bool = true) async {
let m = ChatModel.shared
let im = ItemsModel.shared
func loadChat(chatId: ChatId, im: ItemsModel, search: String = "", openAroundItemId: ChatItem.ID? = nil, clearItems: Bool = true) async {
await MainActor.run {
m.chatItemStatuses = [:]
if clearItems {
im.reversedChatItems = []
ItemsModel.shared.chatState.clear()
im.chatState.clear()
}
}
await apiLoadMessages(chatId, openAroundItemId != nil ? .around(chatItemId: openAroundItemId!, count: loadItemsPerPage) : (search == "" ? .initial(count: loadItemsPerPage) : .last(count: loadItemsPerPage)), im.chatState, search, openAroundItemId, { 0...0 })
await apiLoadMessages(
chatId,
im,
( // pagination
openAroundItemId != nil
? .around(chatItemId: openAroundItemId!, count: loadItemsPerPage)
: (
search == ""
? .initial(count: loadItemsPerPage) : .last(count: loadItemsPerPage)
)
),
search,
openAroundItemId,
{ 0...0 }
)
}
func apiGetChatItemInfo(type: ChatType, id: Int64, itemId: Int64) async throws -> ChatItemInfo {
let r: ChatResponse0 = try await chatSendCmd(.apiGetChatItemInfo(type: type, id: id, itemId: itemId))
func apiGetChatItemInfo(type: ChatType, id: Int64, scope: GroupChatScope?, itemId: Int64) async throws -> ChatItemInfo {
let r: ChatResponse0 = try await chatSendCmd(.apiGetChatItemInfo(type: type, id: id, scope: scope, itemId: itemId))
if case let .chatItemInfo(_, _, chatItemInfo) = r { return chatItemInfo }
throw r.unexpected
}
func apiPlanForwardChatItems(type: ChatType, id: Int64, itemIds: [Int64]) async throws -> ([Int64], ForwardConfirmation?) {
let r: ChatResponse1 = try await chatSendCmd(.apiPlanForwardChatItems(toChatType: type, toChatId: id, itemIds: itemIds))
func apiPlanForwardChatItems(type: ChatType, id: Int64, scope: GroupChatScope?, itemIds: [Int64]) async throws -> ([Int64], ForwardConfirmation?) {
let r: ChatResponse1 = try await chatSendCmd(.apiPlanForwardChatItems(fromChatType: type, fromChatId: id, fromScope: scope, itemIds: itemIds))
if case let .forwardPlan(_, chatItemIds, forwardConfimation) = r { return (chatItemIds, forwardConfimation) }
throw r.unexpected
}
func apiForwardChatItems(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemIds: [Int64], ttl: Int?) async -> [ChatItem]? {
let cmd: ChatCommand = .apiForwardChatItems(toChatType: toChatType, toChatId: toChatId, fromChatType: fromChatType, fromChatId: fromChatId, itemIds: itemIds, ttl: ttl)
func apiForwardChatItems(toChatType: ChatType, toChatId: Int64, toScope: GroupChatScope?, fromChatType: ChatType, fromChatId: Int64, fromScope: GroupChatScope?, itemIds: [Int64], ttl: Int?) async -> [ChatItem]? {
let cmd: ChatCommand = .apiForwardChatItems(toChatType: toChatType, toChatId: toChatId, toScope: toScope, fromChatType: fromChatType, fromChatId: fromChatId, fromScope: fromScope, itemIds: itemIds, ttl: ttl)
return await processSendMessageCmd(toChatType: toChatType, cmd: cmd)
}
@ -412,8 +423,8 @@ func apiReorderChatTags(tagIds: [Int64]) async throws {
try await sendCommandOkResp(.apiReorderChatTags(tagIds: tagIds))
}
func apiSendMessages(type: ChatType, id: Int64, live: Bool = false, ttl: Int? = nil, composedMessages: [ComposedMessage]) async -> [ChatItem]? {
let cmd: ChatCommand = .apiSendMessages(type: type, id: id, live: live, ttl: ttl, composedMessages: composedMessages)
func apiSendMessages(type: ChatType, id: Int64, scope: GroupChatScope?, live: Bool = false, ttl: Int? = nil, composedMessages: [ComposedMessage]) async -> [ChatItem]? {
let cmd: ChatCommand = .apiSendMessages(type: type, id: id, scope: scope, live: live, ttl: ttl, composedMessages: composedMessages)
return await processSendMessageCmd(toChatType: type, cmd: cmd)
}
@ -490,8 +501,8 @@ private func createChatItemsErrorAlert(_ r: ChatError) {
)
}
func apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, updatedMessage: UpdatedMessage, live: Bool = false) async throws -> ChatItem {
let r: ChatResponse1 = try await chatSendCmd(.apiUpdateChatItem(type: type, id: id, itemId: itemId, updatedMessage: updatedMessage, live: live), bgDelay: msgDelay)
func apiUpdateChatItem(type: ChatType, id: Int64, scope: GroupChatScope?, itemId: Int64, updatedMessage: UpdatedMessage, live: Bool = false) async throws -> ChatItem {
let r: ChatResponse1 = try await chatSendCmd(.apiUpdateChatItem(type: type, id: id, scope: scope, itemId: itemId, updatedMessage: updatedMessage, live: live), bgDelay: msgDelay)
switch r {
case let .chatItemUpdated(_, aChatItem): return aChatItem.chatItem
case let .chatItemNotChanged(_, aChatItem): return aChatItem.chatItem
@ -499,8 +510,8 @@ func apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, updatedMessage:
}
}
func apiChatItemReaction(type: ChatType, id: Int64, itemId: Int64, add: Bool, reaction: MsgReaction) async throws -> ChatItem {
let r: ChatResponse1 = try await chatSendCmd(.apiChatItemReaction(type: type, id: id, itemId: itemId, add: add, reaction: reaction), bgDelay: msgDelay)
func apiChatItemReaction(type: ChatType, id: Int64, scope: GroupChatScope?, itemId: Int64, add: Bool, reaction: MsgReaction) async throws -> ChatItem {
let r: ChatResponse1 = try await chatSendCmd(.apiChatItemReaction(type: type, id: id, scope: scope, itemId: itemId, add: add, reaction: reaction), bgDelay: msgDelay)
if case let .chatItemReaction(_, _, reaction) = r { return reaction.chatReaction.chatItem }
throw r.unexpected
}
@ -512,8 +523,8 @@ func apiGetReactionMembers(groupId: Int64, itemId: Int64, reaction: MsgReaction)
throw r.unexpected
}
func apiDeleteChatItems(type: ChatType, id: Int64, itemIds: [Int64], mode: CIDeleteMode) async throws -> [ChatItemDeletion] {
let r: ChatResponse1 = try await chatSendCmd(.apiDeleteChatItem(type: type, id: id, itemIds: itemIds, mode: mode), bgDelay: msgDelay)
func apiDeleteChatItems(type: ChatType, id: Int64, scope: GroupChatScope?, itemIds: [Int64], mode: CIDeleteMode) async throws -> [ChatItemDeletion] {
let r: ChatResponse1 = try await chatSendCmd(.apiDeleteChatItem(type: type, id: id, scope: scope, itemIds: itemIds, mode: mode), bgDelay: msgDelay)
if case let .chatItemsDeleted(_, items, _) = r { return items }
throw r.unexpected
}
@ -1237,12 +1248,14 @@ func apiRejectContactRequest(contactReqId: Int64) async throws {
throw r.unexpected
}
func apiChatRead(type: ChatType, id: Int64) async throws {
try await sendCommandOkResp(.apiChatRead(type: type, id: id))
func apiChatRead(type: ChatType, id: Int64, scope: GroupChatScope?) async throws {
try await sendCommandOkResp(.apiChatRead(type: type, id: id, scope: scope))
}
func apiChatItemsRead(type: ChatType, id: Int64, itemIds: [Int64]) async throws {
try await sendCommandOkResp(.apiChatItemsRead(type: type, id: id, itemIds: itemIds))
func apiChatItemsRead(type: ChatType, id: Int64, scope: GroupChatScope?, itemIds: [Int64]) async throws -> ChatInfo {
let r: ChatResponse1 = try await chatSendCmd(.apiChatItemsRead(type: type, id: id, scope: scope, itemIds: itemIds))
if case let .itemsReadForChat(_, updatedChatInfo) = r { return updatedChatInfo }
throw r.unexpected
}
func apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool) async throws {
@ -1545,13 +1558,13 @@ func apiGetNetworkStatuses() throws -> [ConnNetworkStatus] {
throw r.unexpected
}
func markChatRead(_ chat: Chat) async {
func markChatRead(_ im: ItemsModel, _ chat: Chat) async {
do {
if chat.chatStats.unreadCount > 0 {
let cInfo = chat.chatInfo
try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId)
try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId, scope: cInfo.groupChatScope())
await MainActor.run {
withAnimation { ChatModel.shared.markAllChatItemsRead(cInfo) }
withAnimation { ChatModel.shared.markAllChatItemsRead(im, cInfo) }
}
}
if chat.chatStats.unreadChat {
@ -1574,11 +1587,12 @@ func markChatUnread(_ chat: Chat, unreadChat: Bool = true) async {
}
}
func apiMarkChatItemsRead(_ cInfo: ChatInfo, _ itemIds: [ChatItem.ID], mentionsRead: Int) async {
func apiMarkChatItemsRead(_ im: ItemsModel, _ cInfo: ChatInfo, _ itemIds: [ChatItem.ID], mentionsRead: Int) async {
do {
try await apiChatItemsRead(type: cInfo.chatType, id: cInfo.apiId, itemIds: itemIds)
DispatchQueue.main.async {
ChatModel.shared.markChatItemsRead(cInfo, itemIds, mentionsRead)
let updatedChatInfo = try await apiChatItemsRead(type: cInfo.chatType, id: cInfo.apiId, scope: cInfo.groupChatScope(), itemIds: itemIds)
await MainActor.run {
ChatModel.shared.updateChatInfo(updatedChatInfo)
ChatModel.shared.markChatItemsRead(im, cInfo, itemIds, mentionsRead)
}
} catch {
logger.error("apiChatItemsRead error: \(responseError(error))")
@ -1626,9 +1640,21 @@ func apiJoinGroup(_ groupId: Int64) async throws -> JoinGroupResult {
}
}
func apiRemoveMembers(_ groupId: Int64, _ memberIds: [Int64], _ withMessages: Bool = false) async throws -> [GroupMember] {
func apiAcceptMember(_ groupId: Int64, _ groupMemberId: Int64, _ memberRole: GroupMemberRole) async throws -> (GroupInfo, GroupMember) {
let r: ChatResponse2 = try await chatSendCmd(.apiAcceptMember(groupId: groupId, groupMemberId: groupMemberId, memberRole: memberRole))
if case let .memberAccepted(_, groupInfo, member) = r { return (groupInfo, member) }
throw r.unexpected
}
func apiDeleteMemberSupportChat(_ groupId: Int64, _ groupMemberId: Int64) async throws -> (GroupInfo, GroupMember) {
let r: ChatResponse2 = try await chatSendCmd(.apiDeleteMemberSupportChat(groupId: groupId, groupMemberId: groupMemberId))
if case let .memberSupportChatDeleted(_, groupInfo, member) = r { return (groupInfo, member) }
throw r.unexpected
}
func apiRemoveMembers(_ groupId: Int64, _ memberIds: [Int64], _ withMessages: Bool = false) async throws -> (GroupInfo, [GroupMember]) {
let r: ChatResponse2 = try await chatSendCmd(.apiRemoveMembers(groupId: groupId, memberIds: memberIds, withMessages: withMessages), bgTask: false)
if case let .userDeletedMembers(_, _, members, withMessages) = r { return members }
if case let .userDeletedMembers(_, updatedGroupInfo, members, _withMessages) = r { return (updatedGroupInfo, members) }
throw r.unexpected
}
@ -1669,8 +1695,8 @@ func apiListMembers(_ groupId: Int64) async -> [GroupMember] {
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{ c in c.sendMsgEnabled && !c.nextSendGrpInv && !memberContactIds.contains(c.apiId) }
.compactMap{ c in c.chatInfo.sendMsgEnabled ? c.chatInfo.contact : nil }
.filter{ c in !c.nextSendGrpInv && !memberContactIds.contains(c.apiId) }
.sorted{ $0.displayName.lowercased() < $1.displayName.lowercased() }
}
@ -2132,7 +2158,7 @@ func processReceivedMsg(_ res: ChatEvent) async {
let cInfo = chatItem.chatInfo
let cItem = chatItem.chatItem
if !cItem.isDeletedContent && active(user) {
await MainActor.run { m.updateChatItem(cInfo, cItem, status: cItem.meta.itemStatus) }
_ = await MainActor.run { m.upsertChatItem(cInfo, cItem) }
}
if let endTask = m.messageDelivery[cItem.id] {
switch cItem.meta.itemStatus {
@ -2180,6 +2206,9 @@ func processReceivedMsg(_ res: ChatEvent) async {
m.decreaseGroupReportsCounter(item.deletedChatItem.chatInfo.id)
}
}
if let updatedChatInfo = items.last?.deletedChatItem.chatInfo {
m.updateChatInfo(updatedChatInfo)
}
}
case let .groupChatItemsDeleted(user, groupInfo, chatItemIDs, _, member_):
await groupChatItemsDeleted(user, groupInfo, chatItemIDs, member_)
@ -2228,6 +2257,13 @@ func processReceivedMsg(_ res: ChatEvent) async {
_ = m.upsertGroupMember(groupInfo, member)
}
}
case let .memberAcceptedByOther(user, groupInfo, _, member):
if active(user) {
await MainActor.run {
_ = m.upsertGroupMember(groupInfo, member)
m.updateGroup(groupInfo)
}
}
case let .deletedMemberUser(user, groupInfo, member, withMessages): // TODO update user member
if active(user) {
await MainActor.run {
@ -2240,6 +2276,7 @@ func processReceivedMsg(_ res: ChatEvent) async {
case let .deletedMember(user, groupInfo, byMember, deletedMember, withMessages):
if active(user) {
await MainActor.run {
m.updateGroup(groupInfo)
_ = m.upsertGroupMember(groupInfo, deletedMember)
if withMessages {
m.removeMemberItems(deletedMember, byMember: byMember, groupInfo)
@ -2249,6 +2286,7 @@ func processReceivedMsg(_ res: ChatEvent) async {
case let .leftMember(user, groupInfo, member):
if active(user) {
await MainActor.run {
m.updateGroup(groupInfo)
_ = m.upsertGroupMember(groupInfo, member)
}
}
@ -2263,6 +2301,12 @@ func processReceivedMsg(_ res: ChatEvent) async {
await MainActor.run {
m.updateGroup(groupInfo)
}
if m.chatId == groupInfo.id,
case .memberSupport(nil) = m.secondaryIM?.groupScopeInfo {
await MainActor.run {
m.secondaryPendingInviteeChatOpened = false
}
}
}
case let .joinedGroupMember(user, groupInfo, member):
if active(user) {
@ -2549,7 +2593,7 @@ func groupChatItemsDeleted(_ user: UserRef, _ groupInfo: GroupInfo, _ chatItemID
return
}
let im = ItemsModel.shared
let cInfo = ChatInfo.group(groupInfo: groupInfo)
let cInfo = ChatInfo.group(groupInfo: groupInfo, groupChatScope: nil)
await MainActor.run {
m.decreaseGroupReportsCounter(cInfo.id, by: chatItemIDs.count)
}

View file

@ -159,7 +159,7 @@ struct SimpleXApp: App {
if let id = chatModel.chatId,
let chat = chatModel.getChat(id),
!NtfManager.shared.navigatingToChat {
Task { await loadChat(chat: chat, clearItems: false) }
Task { await loadChat(chat: chat, im: ItemsModel.shared, clearItems: false) }
}
if let ncr = chatModel.ntfContactRequest {
await MainActor.run { chatModel.ntfContactRequest = nil }

View file

@ -22,11 +22,28 @@ struct ChatInfoToolbar: View {
Image(systemName: "theatermasks").frame(maxWidth: 24, maxHeight: 24, alignment: .center).foregroundColor(.indigo)
Spacer().frame(width: 16)
}
ChatInfoImage(
chat: chat,
size: imageSize,
color: Color(uiColor: .tertiaryLabel)
)
ZStack(alignment: .bottomTrailing) {
ChatInfoImage(
chat: chat,
size: imageSize,
color: Color(uiColor: .tertiaryLabel)
)
if chat.chatStats.reportsCount > 0 {
Image(systemName: "flag.circle.fill")
.resizable()
.scaledToFit()
.frame(width: 14, height: 14)
.symbolRenderingMode(.palette)
.foregroundStyle(.white, .red)
} else if chat.supportUnreadCount > 0 {
Image(systemName: "flag.circle.fill")
.resizable()
.scaledToFit()
.frame(width: 14, height: 14)
.symbolRenderingMode(.palette)
.foregroundStyle(.white, theme.colors.primary)
}
}
.padding(.trailing, 4)
let t = Text(cInfo.displayName).font(.headline)
(cInfo.contact?.verified == true ? contactVerifiedShield + t : t)

View file

@ -687,7 +687,7 @@ struct ChatTTLOption: View {
let m = ChatModel.shared
do {
try await setChatTTL(chatType: chat.chatInfo.chatType, id: chat.chatInfo.apiId, ttl)
await loadChat(chat: chat, clearItems: true)
await loadChat(chat: chat, im: ItemsModel.shared, clearItems: true)
await MainActor.run {
progressIndicator = false
currentChatItemTTL = chatItemTTL
@ -700,7 +700,7 @@ struct ChatTTLOption: View {
}
catch let error {
logger.error("setChatTTL error \(responseError(error))")
await loadChat(chat: chat, clearItems: true)
await loadChat(chat: chat, im: ItemsModel.shared, clearItems: true)
await MainActor.run {
chatItemTTL = currentChatItemTTL
progressIndicator = false
@ -938,7 +938,7 @@ struct ChatWallpaperEditorSheet: View {
self.chat = chat
self.themes = if case let ChatInfo.direct(contact) = chat.chatInfo, let uiThemes = contact.uiThemes {
uiThemes
} else if case let ChatInfo.group(groupInfo) = chat.chatInfo, let uiThemes = groupInfo.uiThemes {
} else if case let ChatInfo.group(groupInfo, _) = chat.chatInfo, let uiThemes = groupInfo.uiThemes {
uiThemes
} else {
ThemeModeOverrides()
@ -974,7 +974,7 @@ struct ChatWallpaperEditorSheet: View {
private func themesFromChat(_ chat: Chat) -> ThemeModeOverrides {
if case let ChatInfo.direct(contact) = chat.chatInfo, let uiThemes = contact.uiThemes {
uiThemes
} else if case let ChatInfo.group(groupInfo) = chat.chatInfo, let uiThemes = groupInfo.uiThemes {
} else if case let ChatInfo.group(groupInfo, _) = chat.chatInfo, let uiThemes = groupInfo.uiThemes {
uiThemes
} else {
ThemeModeOverrides()
@ -1052,12 +1052,12 @@ struct ChatWallpaperEditorSheet: View {
chat.wrappedValue = Chat.init(chatInfo: ChatInfo.direct(contact: contact))
themes = themesFromChat(chat.wrappedValue)
}
} else if case var ChatInfo.group(groupInfo) = chat.wrappedValue.chatInfo {
} else if case var ChatInfo.group(groupInfo, _) = chat.wrappedValue.chatInfo {
groupInfo.uiThemes = changedThemesConstant
await MainActor.run {
ChatModel.shared.updateChatInfo(ChatInfo.group(groupInfo: groupInfo))
chat.wrappedValue = Chat.init(chatInfo: ChatInfo.group(groupInfo: groupInfo))
ChatModel.shared.updateChatInfo(ChatInfo.group(groupInfo: groupInfo, groupChatScope: nil))
chat.wrappedValue = Chat.init(chatInfo: ChatInfo.group(groupInfo: groupInfo, groupChatScope: nil))
themes = themesFromChat(chat.wrappedValue)
}
}

View file

@ -12,8 +12,8 @@ import SimpleXChat
struct CIChatFeatureView: View {
@EnvironmentObject var m: ChatModel
@Environment(\.revealed) var revealed: Bool
@ObservedObject var im = ItemsModel.shared
@ObservedObject var chat: Chat
@ObservedObject var im: ItemsModel
@EnvironmentObject var theme: AppTheme
var chatItem: ChatItem
var feature: Feature
@ -53,7 +53,7 @@ struct CIChatFeatureView: View {
private func mergedFeatures() -> [FeatureInfo]? {
var fs: [FeatureInfo] = []
var icons: Set<String> = []
if var i = m.getChatItemIndex(chatItem) {
if var i = m.getChatItemIndex(im, chatItem) {
while i < im.reversedChatItems.count,
let f = featureInfo(im.reversedChatItems[i]) {
if !icons.contains(f.icon) {
@ -108,6 +108,7 @@ struct CIChatFeatureView_Previews: PreviewProvider {
let enabled = FeatureEnabled(forUser: false, forContact: false)
CIChatFeatureView(
chat: Chat.sampleData,
im: ItemsModel.shared,
chatItem: ChatItem.getChatFeatureSample(.fullDelete, enabled), feature: ChatFeature.fullDelete, iconColor: enabled.iconColor(.secondary)
).environment(\.revealed, true)
}

View file

@ -278,6 +278,7 @@ func showFileErrorAlert(_ err: FileError, temporary: Bool = false) {
struct CIFileView_Previews: PreviewProvider {
static var previews: some View {
let im = ItemsModel.shared
let sentFile: ChatItem = ChatItem(
chatDir: .directSnd,
meta: CIMeta.getSample(1, .now, "", .sndSent(sndProgress: .complete), itemEdited: true),
@ -293,16 +294,16 @@ struct CIFileView_Previews: PreviewProvider {
file: nil
)
Group {
ChatItemView(chat: Chat.sampleData, chatItem: sentFile, scrollToItemId: { _ in })
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(), scrollToItemId: { _ in })
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileName: "some_long_file_name_here", fileStatus: .rcvInvitation), scrollToItemId: { _ in })
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvAccepted), scrollToItemId: { _ in })
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), scrollToItemId: { _ in })
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvCancelled), scrollToItemId: { _ in })
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileSize: 1_000_000_000, fileStatus: .rcvInvitation), scrollToItemId: { _ in })
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(text: "Hello there", fileStatus: .rcvInvitation), scrollToItemId: { _ in })
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), scrollToItemId: { _ in })
ChatItemView(chat: Chat.sampleData, chatItem: fileChatItemWtFile, scrollToItemId: { _ in })
ChatItemView(chat: Chat.sampleData, im: im, chatItem: sentFile, scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getFileMsgContentSample(), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getFileMsgContentSample(fileName: "some_long_file_name_here", fileStatus: .rcvInvitation), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvAccepted), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvCancelled), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getFileMsgContentSample(fileSize: 1_000_000_000, fileStatus: .rcvInvitation), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getFileMsgContentSample(text: "Hello there", fileStatus: .rcvInvitation), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
ChatItemView(chat: Chat.sampleData, im: im, 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), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
ChatItemView(chat: Chat.sampleData, im: im, chatItem: fileChatItemWtFile, scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
}
.environment(\.revealed, false)
.previewLayout(.fixed(width: 360, height: 360))

View file

@ -12,7 +12,7 @@ import SimpleXChat
struct CIImageView: View {
@EnvironmentObject var m: ChatModel
let chatItem: ChatItem
var scrollToItemId: ((ChatItem.ID) -> Void)? = nil
var scrollToItem: ((ChatItem.ID) -> Void)? = nil
var preview: UIImage?
let maxWidth: CGFloat
var imgWidth: CGFloat?
@ -26,7 +26,7 @@ struct CIImageView: View {
if let uiImage = getLoadedImage(file) {
Group { if smallView { smallViewImageView(uiImage) } else { imageView(uiImage) } }
.fullScreenCover(isPresented: $showFullScreenImage) {
FullScreenMediaView(chatItem: chatItem, scrollToItemId: scrollToItemId, image: uiImage, showView: $showFullScreenImage)
FullScreenMediaView(chatItem: chatItem, scrollToItem: scrollToItem, image: uiImage, showView: $showFullScreenImage)
}
.if(!smallView) { view in
view.modifier(PrivacyBlur(blurred: $blurred))

View file

@ -45,7 +45,7 @@ struct CIRcvDecryptionError: View {
viewBody()
.onAppear {
// for direct chat ConnectionStats are populated on opening chat, see ChatView onAppear
if case let .group(groupInfo) = chat.chatInfo,
if case let .group(groupInfo, _) = chat.chatInfo,
case let .groupRcv(groupMember) = chatItem.chatDir {
do {
let (member, stats) = try apiGroupMemberInfoSync(groupInfo.apiId, groupMember.groupMemberId)
@ -83,7 +83,7 @@ struct CIRcvDecryptionError: View {
} else {
basicDecryptionErrorItem()
}
} else if case let .group(groupInfo) = chat.chatInfo,
} else if case let .group(groupInfo, _) = chat.chatInfo,
case let .groupRcv(groupMember) = chatItem.chatDir,
let mem = m.getGroupMember(groupMember.groupMemberId),
let memberStats = mem.wrapped.activeConn?.connectionStats {

View file

@ -435,6 +435,7 @@ class VoiceItemState {
struct CIVoiceView_Previews: PreviewProvider {
static var previews: some View {
let im = ItemsModel.shared
let sentVoiceMessage: ChatItem = ChatItem(
chatDir: .directSnd,
meta: CIMeta.getSample(1, .now, "", .sndSent(sndProgress: .complete), itemEdited: true),
@ -457,10 +458,10 @@ struct CIVoiceView_Previews: PreviewProvider {
duration: 30,
allowMenu: Binding.constant(true)
)
ChatItemView(chat: Chat.sampleData, chatItem: sentVoiceMessage, scrollToItemId: { _ in }, allowMenu: .constant(true))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(), scrollToItemId: { _ in }, allowMenu: .constant(true))
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), scrollToItemId: { _ in }, allowMenu: .constant(true))
ChatItemView(chat: Chat.sampleData, chatItem: voiceMessageWtFile, scrollToItemId: { _ in }, allowMenu: .constant(true))
ChatItemView(chat: Chat.sampleData, im: im, chatItem: sentVoiceMessage, scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: .constant(true))
ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getVoiceMsgContentSample(), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: .constant(true))
ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getVoiceMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: .constant(true))
ChatItemView(chat: Chat.sampleData, im: im, chatItem: voiceMessageWtFile, scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: .constant(true))
}
.previewLayout(.fixed(width: 360, height: 360))
}

View file

@ -77,6 +77,7 @@ struct FramedCIVoiceView: View {
struct FramedCIVoiceView_Previews: PreviewProvider {
static var previews: some View {
let im = ItemsModel.shared
let sentVoiceMessage: ChatItem = ChatItem(
chatDir: .directSnd,
meta: CIMeta.getSample(1, .now, "", .sndSent(sndProgress: .complete), itemEdited: true),
@ -92,11 +93,11 @@ struct FramedCIVoiceView_Previews: PreviewProvider {
file: CIFile.getSample(fileStatus: .sndComplete)
)
Group {
ChatItemView(chat: Chat.sampleData, chatItem: sentVoiceMessage, scrollToItemId: { _ in })
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there"), scrollToItemId: { _ in })
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there", fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), scrollToItemId: { _ in })
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."), scrollToItemId: { _ in })
ChatItemView(chat: Chat.sampleData, chatItem: voiceMessageWithQuote, scrollToItemId: { _ in })
ChatItemView(chat: Chat.sampleData, im: im, chatItem: sentVoiceMessage, scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there"), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there", fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
ChatItemView(chat: Chat.sampleData, im: im, 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."), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
ChatItemView(chat: Chat.sampleData, im: im, chatItem: voiceMessageWithQuote, scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
}
.environment(\.revealed, false)
.previewLayout(.fixed(width: 360, height: 360))

File diff suppressed because one or more lines are too long

View file

@ -14,7 +14,7 @@ import AVKit
struct FullScreenMediaView: View {
@EnvironmentObject var m: ChatModel
@State var chatItem: ChatItem
var scrollToItemId: ((ChatItem.ID) -> Void)?
var scrollToItem: ((ChatItem.ID) -> Void)?
@State var image: UIImage?
@State var player: AVPlayer? = nil
@State var url: URL? = nil
@ -71,7 +71,7 @@ struct FullScreenMediaView: View {
let w = abs(t.width)
if t.height > 60 && t.height > w * 2 {
showView = false
scrollToItemId?(chatItem.id)
scrollToItem?(chatItem.id)
} else if w > 60 && w > abs(t.height) * 2 && !scrolling {
let previous = t.width > 0
scrolling = true

View file

@ -14,6 +14,7 @@ struct MarkedDeletedItemView: View {
@EnvironmentObject var theme: AppTheme
@Environment(\.revealed) var revealed: Bool
@ObservedObject var chat: Chat
@ObservedObject var im: ItemsModel
var chatItem: ChatItem
var body: some View {
@ -29,14 +30,14 @@ struct MarkedDeletedItemView: View {
var mergedMarkedDeletedText: LocalizedStringKey {
if !revealed,
let ciCategory = chatItem.mergeCategory,
var i = m.getChatItemIndex(chatItem) {
var i = m.getChatItemIndex(im, chatItem) {
var moderated = 0
var blocked = 0
var blockedByAdmin = 0
var deleted = 0
var moderatedBy: Set<String> = []
while i < ItemsModel.shared.reversedChatItems.count,
let ci = .some(ItemsModel.shared.reversedChatItems[i]),
while i < im.reversedChatItems.count,
let ci = .some(im.reversedChatItems[i]),
ci.mergeCategory == ciCategory,
let itemDeleted = ci.meta.itemDeleted {
switch itemDeleted {
@ -85,6 +86,7 @@ struct MarkedDeletedItemView_Previews: PreviewProvider {
Group {
MarkedDeletedItemView(
chat: Chat.sampleData,
im: ItemsModel.shared,
chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now))
).environment(\.revealed, true)
}

View file

@ -40,25 +40,31 @@ extension EnvironmentValues {
struct ChatItemView: View {
@ObservedObject var chat: Chat
@ObservedObject var im: ItemsModel
@EnvironmentObject var theme: AppTheme
@Environment(\.showTimestamp) var showTimestamp: Bool
@Environment(\.revealed) var revealed: Bool
var chatItem: ChatItem
var scrollToItemId: (ChatItem.ID) -> Void
var scrollToItem: (ChatItem.ID) -> Void
@Binding var scrollToItemId: ChatItem.ID?
var maxWidth: CGFloat = .infinity
@Binding var allowMenu: Bool
init(
chat: Chat,
im: ItemsModel,
chatItem: ChatItem,
scrollToItemId: @escaping (ChatItem.ID) -> Void,
scrollToItem: @escaping (ChatItem.ID) -> Void,
scrollToItemId: Binding<ChatItem.ID?> = .constant(nil),
showMember: Bool = false,
maxWidth: CGFloat = .infinity,
allowMenu: Binding<Bool> = .constant(false)
) {
self.chat = chat
self.im = im
self.chatItem = chatItem
self.scrollToItemId = scrollToItemId
self.scrollToItem = scrollToItem
_scrollToItemId = scrollToItemId
self.maxWidth = maxWidth
_allowMenu = allowMenu
}
@ -66,14 +72,14 @@ struct ChatItemView: View {
var body: some View {
let ci = chatItem
if chatItem.meta.itemDeleted != nil && (!revealed || chatItem.isDeletedContent) {
MarkedDeletedItemView(chat: chat, chatItem: chatItem)
MarkedDeletedItemView(chat: chat, im: im, chatItem: chatItem)
} else if ci.quotedItem == nil && ci.meta.itemForwarded == nil && ci.meta.itemDeleted == nil && !ci.meta.isLive {
if let mc = ci.content.msgContent, mc.isText && isShortEmoji(ci.content.text) {
EmojiItemView(chat: chat, chatItem: ci)
} else if ci.content.text.isEmpty, case let .voice(_, duration) = ci.content.msgContent {
CIVoiceView(chat: chat, chatItem: ci, recordingFile: ci.file, duration: duration, allowMenu: $allowMenu)
} else if ci.content.msgContent == nil {
ChatItemContentView(chat: chat, chatItem: chatItem, msgContentView: { Text(ci.text) }) // msgContent is unreachable branch in this case
ChatItemContentView(chat: chat, im: im, chatItem: chatItem, msgContentView: { Text(ci.text) }) // msgContent is unreachable branch in this case
} else {
framedItemView()
}
@ -101,8 +107,10 @@ struct ChatItemView: View {
}()
return FramedItemView(
chat: chat,
im: im,
chatItem: chatItem,
scrollToItemId: scrollToItemId,
scrollToItem: scrollToItem,
scrollToItemId: $scrollToItemId,
preview: preview,
maxWidth: maxWidth,
imgWidth: adjustedMaxWidth,
@ -117,6 +125,7 @@ struct ChatItemContentView<Content: View>: View {
@EnvironmentObject var theme: AppTheme
@Environment(\.revealed) var revealed: Bool
@ObservedObject var chat: Chat
@ObservedObject var im: ItemsModel
var chatItem: ChatItem
var msgContentView: () -> Content
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
@ -140,7 +149,9 @@ struct ChatItemContentView<Content: View>: View {
case let .sndGroupInvitation(groupInvitation, memberRole): groupInvitationItemView(groupInvitation, memberRole)
case .rcvDirectEvent: eventItemView()
case .rcvGroupEvent(.memberCreatedContact): CIMemberCreatedContactView(chatItem: chatItem)
case .rcvGroupEvent(.newMemberPendingReview): CIEventView(eventText: pendingReviewEventItemText())
case .rcvGroupEvent: eventItemView()
case .sndGroupEvent(.userPendingReview): CIEventView(eventText: pendingReviewEventItemText())
case .sndGroupEvent: eventItemView()
case .rcvConnEvent: eventItemView()
case .sndConnEvent: eventItemView()
@ -149,7 +160,7 @@ struct ChatItemContentView<Content: View>: View {
case let .rcvChatPreference(feature, allowed, param):
CIFeaturePreferenceView(chat: chat, chatItem: chatItem, feature: feature, allowed: allowed, param: param)
case let .sndChatPreference(feature, _, _):
CIChatFeatureView(chat: chat, chatItem: chatItem, feature: feature, icon: feature.icon, iconColor: theme.colors.secondary)
CIChatFeatureView(chat: chat, im: im, chatItem: chatItem, feature: feature, icon: feature.icon, iconColor: theme.colors.secondary)
case let .rcvGroupFeature(feature, preference, _, role): chatFeatureView(feature, preference.enabled(role, for: chat.chatInfo.groupInfo?.membership).iconColor(theme.colors.secondary))
case let .sndGroupFeature(feature, preference, _, role): chatFeatureView(feature, preference.enabled(role, for: chat.chatInfo.groupInfo?.membership).iconColor(theme.colors.secondary))
case let .rcvChatFeatureRejected(feature): chatFeatureView(feature, .red)
@ -181,6 +192,13 @@ struct ChatItemContentView<Content: View>: View {
CIEventView(eventText: eventItemViewText(theme.colors.secondary))
}
private func pendingReviewEventItemText() -> Text {
Text(chatItem.content.text)
.font(.caption)
.foregroundColor(theme.colors.secondary)
.fontWeight(.bold)
}
private func eventItemViewText(_ secondaryColor: Color) -> Text {
if !revealed, let t = mergedGroupEventText {
return chatEventText(t + textSpace + chatItem.timestampText, secondaryColor)
@ -196,7 +214,7 @@ struct ChatItemContentView<Content: View>: View {
}
private func chatFeatureView(_ feature: Feature, _ iconColor: Color) -> some View {
CIChatFeatureView(chat: chat, chatItem: chatItem, feature: feature, iconColor: iconColor)
CIChatFeatureView(chat: chat, im: im, chatItem: chatItem, feature: feature, iconColor: iconColor)
}
private var mergedGroupEventText: Text? {
@ -256,16 +274,17 @@ func chatEventText(_ ci: ChatItem, _ secondaryColor: Color) -> Text {
struct ChatItemView_Previews: PreviewProvider {
static var previews: some View {
let im = ItemsModel.shared
Group{
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"), scrollToItemId: { _ in })
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too"), scrollToItemId: { _ in })
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂"), scrollToItemId: { _ in })
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂"), scrollToItemId: { _ in })
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂🙂"), scrollToItemId: { _ in })
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getDeletedContentSample(), scrollToItemId: { _ in })
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in })
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂", .sndSent(sndProgress: .complete), itemLive: true), scrollToItemId: { _ in }).environment(\.revealed, true)
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemLive: true), scrollToItemId: { _ in }).environment(\.revealed, true)
ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too"), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂"), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂"), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂🙂"), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getDeletedContentSample(), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂", .sndSent(sndProgress: .complete), itemLive: true), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)).environment(\.revealed, true)
ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemLive: true), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)).environment(\.revealed, true)
}
.environment(\.revealed, false)
.previewLayout(.fixed(width: 360, height: 70))
@ -275,10 +294,12 @@ struct ChatItemView_Previews: PreviewProvider {
struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
static var previews: some View {
let im = ItemsModel.shared
let ciFeatureContent = CIContent.rcvChatFeature(feature: .fullDelete, enabled: FeatureEnabled(forUser: false, forContact: false), param: nil)
Group{
ChatItemView(
chat: Chat.sampleData,
im: im,
chatItem: ChatItem(
chatDir: .directRcv,
meta: CIMeta.getSample(1, .now, "1 skipped message", .rcvRead, itemDeleted: .deleted(deletedTs: .now)),
@ -286,10 +307,12 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
quotedItem: nil,
file: nil
),
scrollToItemId: { _ in }
scrollToItem: { _ in },
scrollToItemId: Binding.constant(nil)
)
ChatItemView(
chat: Chat.sampleData,
im: im,
chatItem: ChatItem(
chatDir: .directRcv,
meta: CIMeta.getSample(1, .now, "1 skipped message", .rcvRead),
@ -297,10 +320,11 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
quotedItem: nil,
file: nil
),
scrollToItemId: { _ in }
scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)
)
ChatItemView(
chat: Chat.sampleData,
im: im,
chatItem: ChatItem(
chatDir: .directRcv,
meta: CIMeta.getSample(1, .now, "received invitation to join group team as admin", .rcvRead, itemDeleted: .deleted(deletedTs: .now)),
@ -308,10 +332,12 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
quotedItem: nil,
file: nil
),
scrollToItemId: { _ in }
scrollToItem: { _ in },
scrollToItemId: Binding.constant(nil)
)
ChatItemView(
chat: Chat.sampleData,
im: im,
chatItem: ChatItem(
chatDir: .directRcv,
meta: CIMeta.getSample(1, .now, "group event text", .rcvRead, itemDeleted: .deleted(deletedTs: .now)),
@ -319,10 +345,12 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
quotedItem: nil,
file: nil
),
scrollToItemId: { _ in }
scrollToItem: { _ in },
scrollToItemId: Binding.constant(nil)
)
ChatItemView(
chat: Chat.sampleData,
im: im,
chatItem: ChatItem(
chatDir: .directRcv,
meta: CIMeta.getSample(1, .now, ciFeatureContent.text, .rcvRead, itemDeleted: .deleted(deletedTs: .now)),
@ -330,7 +358,8 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
quotedItem: nil,
file: nil
),
scrollToItemId: { _ in }
scrollToItem: { _ in },
scrollToItemId: Binding.constant(nil)
)
}
.environment(\.revealed, true)

View file

@ -13,8 +13,8 @@ let TRIM_KEEP_COUNT = 200
func apiLoadMessages(
_ chatId: ChatId,
_ im: ItemsModel,
_ pagination: ChatPagination,
_ chatState: ActiveChatState,
_ search: String = "",
_ openAroundItemId: ChatItem.ID? = nil,
_ visibleItemIndexesNonReversed: @MainActor () -> ClosedRange<Int> = { 0 ... 0 }
@ -22,7 +22,7 @@ func apiLoadMessages(
let chat: Chat
let navInfo: NavigationInfo
do {
(chat, navInfo) = try await apiGetChat(chatId: chatId, pagination: pagination, search: search)
(chat, navInfo) = try await apiGetChat(chatId: chatId, scope: im.groupScopeInfo?.toChatScope(), contentTag: im.contentTag, pagination: pagination, search: search)
} catch let error {
logger.error("apiLoadMessages error: \(responseError(error))")
return
@ -38,30 +38,31 @@ func apiLoadMessages(
return
}
let unreadAfterItemId = chatState.unreadAfterItemId
let unreadAfterItemId = im.chatState.unreadAfterItemId
let oldItems = Array(ItemsModel.shared.reversedChatItems.reversed())
let oldItems = Array(im.reversedChatItems.reversed())
var newItems: [ChatItem] = []
switch pagination {
case .initial:
let newSplits: [Int64] = if !chat.chatItems.isEmpty && navInfo.afterTotal > 0 { [chat.chatItems.last!.id] } else { [] }
if chatModel.getChat(chat.id) == nil {
if im.secondaryIMFilter == nil && chatModel.getChat(chat.id) == nil {
chatModel.addChat(chat)
}
await MainActor.run {
chatModel.chatItemStatuses.removeAll()
ItemsModel.shared.reversedChatItems = chat.chatItems.reversed()
chatModel.updateChatInfo(chat.chatInfo)
chatState.splits = newSplits
if !chat.chatItems.isEmpty {
chatState.unreadAfterItemId = chat.chatItems.last!.id
im.reversedChatItems = chat.chatItems.reversed()
if im.secondaryIMFilter == nil {
chatModel.updateChatInfo(chat.chatInfo)
}
chatState.totalAfter = navInfo.afterTotal
chatState.unreadTotal = chat.chatStats.unreadCount
chatState.unreadAfter = navInfo.afterUnread
chatState.unreadAfterNewestLoaded = navInfo.afterUnread
im.chatState.splits = newSplits
if !chat.chatItems.isEmpty {
im.chatState.unreadAfterItemId = chat.chatItems.last!.id
}
im.chatState.totalAfter = navInfo.afterTotal
im.chatState.unreadTotal = chat.chatStats.unreadCount
im.chatState.unreadAfter = navInfo.afterUnread
im.chatState.unreadAfterNewestLoaded = navInfo.afterUnread
PreloadState.shared.clear()
im.preloadState.clear()
}
case let .before(paginationChatItemId, _):
newItems.append(contentsOf: oldItems)
@ -71,15 +72,15 @@ func apiLoadMessages(
let wasSize = newItems.count
let visibleItemIndexes = await MainActor.run { visibleItemIndexesNonReversed() }
let modifiedSplits = removeDuplicatesAndModifySplitsOnBeforePagination(
unreadAfterItemId, &newItems, newIds, chatState.splits, visibleItemIndexes
unreadAfterItemId, &newItems, newIds, im.chatState.splits, visibleItemIndexes
)
let insertAt = max((indexInCurrentItems - (wasSize - newItems.count) + modifiedSplits.trimmedIds.count), 0)
newItems.insert(contentsOf: chat.chatItems, at: insertAt)
let newReversed: [ChatItem] = newItems.reversed()
await MainActor.run {
ItemsModel.shared.reversedChatItems = newReversed
chatState.splits = modifiedSplits.newSplits
chatState.moveUnreadAfterItem(modifiedSplits.oldUnreadSplitIndex, modifiedSplits.newUnreadSplitIndex, oldItems)
im.reversedChatItems = newReversed
im.chatState.splits = modifiedSplits.newSplits
im.chatState.moveUnreadAfterItem(modifiedSplits.oldUnreadSplitIndex, modifiedSplits.newUnreadSplitIndex, oldItems)
}
case let .after(paginationChatItemId, _):
newItems.append(contentsOf: oldItems)
@ -89,7 +90,7 @@ func apiLoadMessages(
let mappedItems = mapItemsToIds(chat.chatItems)
let newIds = mappedItems.0
let (newSplits, unreadInLoaded) = removeDuplicatesAndModifySplitsOnAfterPagination(
mappedItems.1, paginationChatItemId, &newItems, newIds, chat, chatState.splits
mappedItems.1, paginationChatItemId, &newItems, newIds, chat, im.chatState.splits
)
let indexToAdd = min(indexInCurrentItems + 1, newItems.count)
let indexToAddIsLast = indexToAdd == newItems.count
@ -97,19 +98,19 @@ func apiLoadMessages(
let new: [ChatItem] = newItems
let newReversed: [ChatItem] = newItems.reversed()
await MainActor.run {
ItemsModel.shared.reversedChatItems = newReversed
chatState.splits = newSplits
chatState.moveUnreadAfterItem(chatState.splits.first ?? new.last!.id, new)
im.reversedChatItems = newReversed
im.chatState.splits = newSplits
im.chatState.moveUnreadAfterItem(im.chatState.splits.first ?? new.last!.id, new)
// loading clear bottom area, updating number of unread items after the newest loaded item
if indexToAddIsLast {
chatState.unreadAfterNewestLoaded -= unreadInLoaded
im.chatState.unreadAfterNewestLoaded -= unreadInLoaded
}
}
case .around:
var newSplits: [Int64]
if openAroundItemId == nil {
newItems.append(contentsOf: oldItems)
newSplits = await removeDuplicatesAndUpperSplits(&newItems, chat, chatState.splits, visibleItemIndexesNonReversed)
newSplits = await removeDuplicatesAndUpperSplits(&newItems, chat, im.chatState.splits, visibleItemIndexesNonReversed)
} else {
newSplits = []
}
@ -120,33 +121,37 @@ func apiLoadMessages(
let newReversed: [ChatItem] = newItems.reversed()
let orderedSplits = newSplits
await MainActor.run {
ItemsModel.shared.reversedChatItems = newReversed
chatState.splits = orderedSplits
chatState.unreadAfterItemId = chat.chatItems.last!.id
chatState.totalAfter = navInfo.afterTotal
chatState.unreadTotal = chat.chatStats.unreadCount
chatState.unreadAfter = navInfo.afterUnread
im.reversedChatItems = newReversed
im.chatState.splits = orderedSplits
im.chatState.unreadAfterItemId = chat.chatItems.last!.id
im.chatState.totalAfter = navInfo.afterTotal
im.chatState.unreadTotal = chat.chatStats.unreadCount
im.chatState.unreadAfter = navInfo.afterUnread
if let openAroundItemId {
chatState.unreadAfterNewestLoaded = navInfo.afterUnread
ChatModel.shared.openAroundItemId = openAroundItemId
ChatModel.shared.chatId = chatId
im.chatState.unreadAfterNewestLoaded = navInfo.afterUnread
if im.secondaryIMFilter == nil {
ChatModel.shared.openAroundItemId = openAroundItemId // TODO [knocking] move openAroundItemId from ChatModel to ItemsModel?
ChatModel.shared.chatId = chat.id
}
} else {
// no need to set it, count will be wrong
// chatState.unreadAfterNewestLoaded = navInfo.afterUnread
}
PreloadState.shared.clear()
im.preloadState.clear()
}
case .last:
newItems.append(contentsOf: oldItems)
let newSplits = await removeDuplicatesAndUnusedSplits(&newItems, chat, chatState.splits)
let newSplits = await removeDuplicatesAndUnusedSplits(&newItems, chat, im.chatState.splits)
newItems.append(contentsOf: chat.chatItems)
let items = newItems
await MainActor.run {
ItemsModel.shared.reversedChatItems = items.reversed()
chatState.splits = newSplits
chatModel.updateChatInfo(chat.chatInfo)
chatState.unreadAfterNewestLoaded = 0
im.reversedChatItems = items.reversed()
im.chatState.splits = newSplits
if im.secondaryIMFilter == nil {
chatModel.updateChatInfo(chat.chatInfo)
}
im.chatState.unreadAfterNewestLoaded = 0
}
}
}

View file

@ -10,6 +10,7 @@ import SwiftUI
import SimpleXChat
struct MergedItems: Hashable, Equatable {
let im: ItemsModel
let items: [MergedItem]
let splits: [SplitRange]
// chat item id, index in list
@ -23,15 +24,15 @@ struct MergedItems: Hashable, Equatable {
hasher.combine("\(items.hashValue)")
}
static func create(_ items: [ChatItem], _ revealedItems: Set<Int64>, _ chatState: ActiveChatState) -> MergedItems {
if items.isEmpty {
return MergedItems(items: [], splits: [], indexInParentItems: [:])
static func create(_ im: ItemsModel, _ revealedItems: Set<Int64>) -> MergedItems {
if im.reversedChatItems.isEmpty {
return MergedItems(im: im, items: [], splits: [], indexInParentItems: [:])
}
let unreadCount = chatState.unreadTotal
let unreadCount = im.chatState.unreadTotal
let unreadAfterItemId = chatState.unreadAfterItemId
let itemSplits = chatState.splits
let unreadAfterItemId = im.chatState.unreadAfterItemId
let itemSplits = im.chatState.splits
var mergedItems: [MergedItem] = []
// Indexes of splits here will be related to reversedChatItems, not chatModel.chatItems
var splitRanges: [SplitRange] = []
@ -40,19 +41,19 @@ struct MergedItems: Hashable, Equatable {
var unclosedSplitIndex: Int? = nil
var unclosedSplitIndexInParent: Int? = nil
var visibleItemIndexInParent = -1
var unreadBefore = unreadCount - chatState.unreadAfterNewestLoaded
var unreadBefore = unreadCount - im.chatState.unreadAfterNewestLoaded
var lastRevealedIdsInMergedItems: BoxedValue<[Int64]>? = nil
var lastRangeInReversedForMergedItems: BoxedValue<ClosedRange<Int>>? = nil
var recent: MergedItem? = nil
while index < items.count {
let item = items[index]
let prev = index >= 1 ? items[index - 1] : nil
let next = index + 1 < items.count ? items[index + 1] : nil
while index < im.reversedChatItems.count {
let item = im.reversedChatItems[index]
let prev = index >= 1 ? im.reversedChatItems[index - 1] : nil
let next = index + 1 < im.reversedChatItems.count ? im.reversedChatItems[index + 1] : nil
let category = item.mergeCategory
let itemIsSplit = itemSplits.contains(item.id)
if item.id == unreadAfterItemId {
unreadBefore = unreadCount - chatState.unreadAfter
unreadBefore = unreadCount - im.chatState.unreadAfter
}
if item.isRcvNew {
unreadBefore -= 1
@ -106,18 +107,19 @@ struct MergedItems: Hashable, Equatable {
// found item that is considered as a split
if let unclosedSplitIndex, let unclosedSplitIndexInParent {
// it was at least second split in the list
splitRanges.append(SplitRange(itemId: items[unclosedSplitIndex].id, indexRangeInReversed: unclosedSplitIndex ... index - 1, indexRangeInParentItems: unclosedSplitIndexInParent ... visibleItemIndexInParent - 1))
splitRanges.append(SplitRange(itemId: im.reversedChatItems[unclosedSplitIndex].id, indexRangeInReversed: unclosedSplitIndex ... index - 1, indexRangeInParentItems: unclosedSplitIndexInParent ... visibleItemIndexInParent - 1))
}
unclosedSplitIndex = index
unclosedSplitIndexInParent = visibleItemIndexInParent
} else if index + 1 == items.count, let unclosedSplitIndex, let unclosedSplitIndexInParent {
} else if index + 1 == im.reversedChatItems.count, let unclosedSplitIndex, let unclosedSplitIndexInParent {
// just one split for the whole list, there will be no more, it's the end
splitRanges.append(SplitRange(itemId: items[unclosedSplitIndex].id, indexRangeInReversed: unclosedSplitIndex ... index, indexRangeInParentItems: unclosedSplitIndexInParent ... visibleItemIndexInParent))
splitRanges.append(SplitRange(itemId: im.reversedChatItems[unclosedSplitIndex].id, indexRangeInReversed: unclosedSplitIndex ... index, indexRangeInParentItems: unclosedSplitIndexInParent ... visibleItemIndexInParent))
}
indexInParentItems[item.id] = visibleItemIndexInParent
index += 1
}
return MergedItems(
im: im,
items: mergedItems,
splits: splitRanges,
indexInParentItems: indexInParentItems
@ -127,7 +129,6 @@ struct MergedItems: Hashable, Equatable {
// Use this check to ensure that mergedItems state based on currently actual state of global
// splits and reversedChatItems
func isActualState() -> Bool {
let im = ItemsModel.shared
// do not load anything if global splits state is different than in merged items because it
// will produce undefined results in terms of loading and placement of items.
// Same applies to reversedChatItems
@ -434,7 +435,7 @@ class BoxedValue<T: Hashable>: Equatable, Hashable {
}
@MainActor
func visibleItemIndexesNonReversed(_ listState: EndlessScrollView<MergedItem>.ListState, _ mergedItems: MergedItems) -> ClosedRange<Int> {
func visibleItemIndexesNonReversed(_ im: ItemsModel, _ listState: EndlessScrollView<MergedItem>.ListState, _ mergedItems: MergedItems) -> ClosedRange<Int> {
let zero = 0 ... 0
let items = mergedItems.items
if items.isEmpty {
@ -445,12 +446,12 @@ func visibleItemIndexesNonReversed(_ listState: EndlessScrollView<MergedItem>.Li
guard let newest, let oldest else {
return zero
}
let size = ItemsModel.shared.reversedChatItems.count
let size = im.reversedChatItems.count
let range = size - oldest ... size - newest
if range.lowerBound < 0 || range.upperBound < 0 {
return zero
}
// visible items mapped to their underlying data structure which is ItemsModel.shared.reversedChatItems.reversed()
// visible items mapped to their underlying data structure which is im.reversedChatItems.reversed()
return range
}

View file

@ -9,7 +9,7 @@
import SwiftUI
import SimpleXChat
func loadLastItems(_ loadingMoreItems: Binding<Bool>, loadingBottomItems: Binding<Bool>, _ chat: Chat) async {
func loadLastItems(_ loadingMoreItems: Binding<Bool>, loadingBottomItems: Binding<Bool>, _ chat: Chat, _ im: ItemsModel) async {
await MainActor.run {
loadingMoreItems.wrappedValue = true
loadingBottomItems.wrappedValue = true
@ -22,27 +22,15 @@ func loadLastItems(_ loadingMoreItems: Binding<Bool>, loadingBottomItems: Bindin
}
return
}
await apiLoadMessages(chat.chatInfo.id, ChatPagination.last(count: 50), ItemsModel.shared.chatState)
await apiLoadMessages(chat.chatInfo.id, im, ChatPagination.last(count: 50))
await MainActor.run {
loadingMoreItems.wrappedValue = false
loadingBottomItems.wrappedValue = false
}
}
class PreloadState {
static let shared = PreloadState()
var prevFirstVisible: Int64 = Int64.min
var prevItemsCount: Int = 0
var preloading: Bool = false
func clear() {
prevFirstVisible = Int64.min
prevItemsCount = 0
preloading = false
}
}
func preloadIfNeeded(
_ im: ItemsModel,
_ allowLoadMoreItems: Binding<Bool>,
_ ignoreLoadingRequests: Binding<Int64?>,
_ listState: EndlessScrollView<MergedItem>.ListState,
@ -50,7 +38,7 @@ func preloadIfNeeded(
loadItems: @escaping (Bool, ChatPagination) async -> Bool,
loadLastItems: @escaping () async -> Void
) {
let state = PreloadState.shared
let state = im.preloadState
guard !listState.isScrolling && !listState.isAnimatedScrolling,
!state.preloading,
listState.totalItemsCount > 0
@ -63,7 +51,7 @@ func preloadIfNeeded(
Task {
defer { state.preloading = false }
var triedToLoad = true
await preloadItems(mergedItems.boxedValue, allowLoadMore, listState, ignoreLoadingRequests) { pagination in
await preloadItems(im, mergedItems.boxedValue, allowLoadMore, listState, ignoreLoadingRequests) { pagination in
triedToLoad = await loadItems(false, pagination)
return triedToLoad
}
@ -73,11 +61,11 @@ func preloadIfNeeded(
}
// it's important to ask last items when the view is fully covered with items. Otherwise, visible items from one
// split will be merged with last items and position of scroll will change unexpectedly.
if listState.itemsCanCoverScreen && !ItemsModel.shared.lastItemsLoaded {
if listState.itemsCanCoverScreen && !im.lastItemsLoaded {
await loadLastItems()
}
}
} else if listState.itemsCanCoverScreen && !ItemsModel.shared.lastItemsLoaded {
} else if listState.itemsCanCoverScreen && !im.lastItemsLoaded {
state.preloading = true
Task {
defer { state.preloading = false }
@ -87,6 +75,7 @@ func preloadIfNeeded(
}
func preloadItems(
_ im: ItemsModel,
_ mergedItems: MergedItems,
_ allowLoadMoreItems: Bool,
_ listState: EndlessScrollView<MergedItem>.ListState,
@ -105,7 +94,7 @@ async {
let splits = mergedItems.splits
let lastVisibleIndex = listState.lastVisibleItemIndex
var lastIndexToLoadFrom: Int? = findLastIndexToLoadFromInSplits(firstVisibleIndex, lastVisibleIndex, remaining, splits)
let items: [ChatItem] = ItemsModel.shared.reversedChatItems.reversed()
let items: [ChatItem] = im.reversedChatItems.reversed()
if splits.isEmpty && !items.isEmpty && lastVisibleIndex > mergedItems.items.count - remaining {
lastIndexToLoadFrom = items.count - 1
}
@ -122,7 +111,7 @@ async {
let sizeWas = items.count
let firstItemIdWas = items.first?.id
let triedToLoad = await loadItems(ChatPagination.before(chatItemId: loadFromItemId, count: ChatPagination.PRELOAD_COUNT))
if triedToLoad && sizeWas == ItemsModel.shared.reversedChatItems.count && firstItemIdWas == ItemsModel.shared.reversedChatItems.last?.id {
if triedToLoad && sizeWas == im.reversedChatItems.count && firstItemIdWas == im.reversedChatItems.last?.id {
ignoreLoadingRequests.wrappedValue = loadFromItemId
return false
}
@ -133,7 +122,7 @@ async {
let splits = mergedItems.splits
let split = splits.last(where: { $0.indexRangeInParentItems.contains(firstVisibleIndex) })
// we're inside a splitRange (top --- [end of the splitRange --- we're here --- start of the splitRange] --- bottom)
let reversedItems: [ChatItem] = ItemsModel.shared.reversedChatItems
let reversedItems: [ChatItem] = im.reversedChatItems
if let split, split.indexRangeInParentItems.lowerBound + remaining > firstVisibleIndex {
let index = split.indexRangeInReversed.lowerBound
if index >= 0 {

File diff suppressed because it is too large Load diff

View file

@ -323,6 +323,7 @@ struct ComposeView: View {
@EnvironmentObject var chatModel: ChatModel
@EnvironmentObject var theme: AppTheme
@ObservedObject var chat: Chat
@ObservedObject var im: ItemsModel
@Binding var composeState: ComposeState
@Binding var keyboardVisible: Bool
@Binding var keyboardHiddenDate: Date
@ -356,6 +357,20 @@ struct ComposeView: View {
var body: some View {
VStack(spacing: 0) {
Divider()
if let groupInfo = chat.chatInfo.groupInfo,
case let .groupChatScopeContext(groupScopeInfo) = im.secondaryIMFilter,
case let .memberSupport(member) = groupScopeInfo,
let member = member,
member.memberPending,
composeState.contextItem == .noContextItem,
composeState.noPreview {
ContextPendingMemberActionsView(
groupInfo: groupInfo,
member: member
)
Divider()
}
if chat.chatInfo.contact?.nextSendGrpInv ?? false {
ContextInvitingContactMemberView()
Divider()
@ -366,8 +381,8 @@ struct ComposeView: View {
Divider()
}
// preference checks should match checks in forwarding list
let simplexLinkProhibited = hasSimplexLink && !chat.groupFeatureEnabled(.simplexLinks)
let fileProhibited = composeState.attachmentPreview && !chat.groupFeatureEnabled(.files)
let simplexLinkProhibited = im.secondaryIMFilter == nil && hasSimplexLink && !chat.groupFeatureEnabled(.simplexLinks)
let fileProhibited = im.secondaryIMFilter == nil && composeState.attachmentPreview && !chat.groupFeatureEnabled(.files)
let voiceProhibited = composeState.voicePreview && !chat.chatInfo.featureEnabled(.voice)
if simplexLinkProhibited {
msgNotAllowedView("SimpleX links not allowed", icon: "link")
@ -397,7 +412,8 @@ struct ComposeView: View {
.padding(.bottom, 16)
.padding(.leading, 12)
.tint(theme.colors.primary)
if case let .group(g) = chat.chatInfo,
if im.secondaryIMFilter == nil,
case let .group(g, _) = chat.chatInfo,
!g.fullGroupPreferences.files.on(for: g.membership) {
b.disabled(true).onTapGesture {
AlertManager.shared.showAlertMsg(
@ -438,8 +454,8 @@ struct ComposeView: View {
keyboardVisible: $keyboardVisible,
keyboardHiddenDate: $keyboardHiddenDate,
sendButtonColor: chat.chatInfo.incognito
? .indigo.opacity(colorScheme == .dark ? 1 : 0.7)
: theme.colors.primary
? .indigo.opacity(colorScheme == .dark ? 1 : 0.7)
: theme.colors.primary
)
.padding(.trailing, 12)
.disabled(!chat.chatInfo.sendMsgEnabled)
@ -939,6 +955,7 @@ struct ComposeView: View {
let chatItem = try await apiUpdateChatItem(
type: chat.chatInfo.chatType,
id: chat.chatInfo.apiId,
scope: chat.chatInfo.groupChatScope(),
itemId: ei.id,
updatedMessage: UpdatedMessage(msgContent: mc, mentions: composeState.memberMentions),
live: live
@ -1001,9 +1018,9 @@ struct ComposeView: View {
reportReason: reportReason,
reportText: msgText
) {
await MainActor.run {
for chatItem in chatItems {
chatModel.addChatItem(chat.chatInfo, chatItem)
if showReportsInSupportChatAlertDefault.get() {
await MainActor.run {
showReportsInSupportChatAlert()
}
}
return chatItems.first
@ -1011,7 +1028,27 @@ struct ComposeView: View {
return nil
}
func showReportsInSupportChatAlert() {
showAlert(
NSLocalizedString("Report sent to moderators", comment: "alert title"),
message: NSLocalizedString("You can view your reports in Chat with admins.", comment: "alert message"),
actions: {[
UIAlertAction(
title: NSLocalizedString("Don't show again", comment: "alert action"),
style: .default,
handler: { _ in
showReportsInSupportChatAlertDefault.set(false)
}
),
UIAlertAction(
title: NSLocalizedString("Ok", comment: "alert action"),
style: .default
)
]}
)
}
func send(_ mc: MsgContent, quoted: Int64?, file: CryptoFile? = nil, live: Bool = false, ttl: Int?, mentions: [String: Int64]) async -> ChatItem? {
await send(
[ComposedMessage(fileSource: file, quotedItemId: quoted, msgContent: mc, mentions: mentions)],
@ -1026,6 +1063,7 @@ struct ComposeView: View {
: await apiSendMessages(
type: chat.chatInfo.chatType,
id: chat.chatInfo.apiId,
scope: chat.chatInfo.groupChatScope(),
live: live,
ttl: ttl,
composedMessages: msgs
@ -1050,8 +1088,10 @@ struct ComposeView: View {
if let chatItems = await apiForwardChatItems(
toChatType: chat.chatInfo.chatType,
toChatId: chat.chatInfo.apiId,
toScope: chat.chatInfo.groupChatScope(),
fromChatType: fromChatInfo.chatType,
fromChatId: fromChatInfo.apiId,
fromScope: fromChatInfo.groupChatScope(),
itemIds: forwardedItems.map { $0.id },
ttl: ttl
) {
@ -1272,12 +1312,14 @@ struct ComposeView: View {
struct ComposeView_Previews: PreviewProvider {
static var previews: some View {
let chat = Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: [])
let im = ItemsModel.shared
@State var composeState = ComposeState(message: "hello")
@State var selectedRange = NSRange()
return Group {
ComposeView(
chat: chat,
im: im,
composeState: $composeState,
keyboardVisible: Binding.constant(true),
keyboardHiddenDate: Binding.constant(Date.now),
@ -1286,6 +1328,7 @@ struct ComposeView_Previews: PreviewProvider {
.environmentObject(ChatModel())
ComposeView(
chat: chat,
im: im,
composeState: $composeState,
keyboardVisible: Binding.constant(true),
keyboardHiddenDate: Binding.constant(Date.now),

View file

@ -0,0 +1,108 @@
//
// ContextPendingMemberActionsView.swift
// SimpleX (iOS)
//
// Created by spaced4ndy on 02.05.2025.
// Copyright © 2025 SimpleX Chat. All rights reserved.
//
import SwiftUI
import SimpleXChat
struct ContextPendingMemberActionsView: View {
@EnvironmentObject var theme: AppTheme
@Environment(\.dismiss) var dismiss
var groupInfo: GroupInfo
var member: GroupMember
var body: some View {
HStack(spacing: 0) {
ZStack {
Text("Reject")
.foregroundColor(.red)
}
.frame(maxWidth: .infinity)
.contentShape(Rectangle())
.onTapGesture {
showRejectMemberAlert(groupInfo, member, dismiss: dismiss)
}
ZStack {
Text("Accept")
.foregroundColor(theme.colors.primary)
}
.frame(maxWidth: .infinity)
.contentShape(Rectangle())
.onTapGesture {
showAcceptMemberAlert(groupInfo, member, dismiss: dismiss)
}
}
.frame(minHeight: 54)
.frame(maxWidth: .infinity)
.background(.thinMaterial)
}
}
func showRejectMemberAlert(_ groupInfo: GroupInfo, _ member: GroupMember, dismiss: DismissAction? = nil) {
showAlert(
title: NSLocalizedString("Reject member?", comment: "alert title"),
buttonTitle: "Reject",
buttonAction: { removeMember(groupInfo, member, dismiss: dismiss) },
cancelButton: true
)
}
func showAcceptMemberAlert(_ groupInfo: GroupInfo, _ member: GroupMember, dismiss: DismissAction? = nil) {
showAlert(
NSLocalizedString("Accept member", comment: "alert title"),
message: NSLocalizedString("Member will join the group, accept member?", comment: "alert message"),
actions: {[
UIAlertAction(
title: NSLocalizedString("Accept as member", comment: "alert action"),
style: .default,
handler: { _ in
acceptMember(groupInfo, member, .member, dismiss: dismiss)
}
),
UIAlertAction(
title: NSLocalizedString("Accept as observer", comment: "alert action"),
style: .default,
handler: { _ in
acceptMember(groupInfo, member, .observer, dismiss: dismiss)
}
),
UIAlertAction(
title: NSLocalizedString("Cancel", comment: "alert action"),
style: .default
)
]}
)
}
func acceptMember(_ groupInfo: GroupInfo, _ member: GroupMember, _ role: GroupMemberRole, dismiss: DismissAction? = nil) {
Task {
do {
let (gInfo, acceptedMember) = try await apiAcceptMember(groupInfo.groupId, member.groupMemberId, role)
await MainActor.run {
_ = ChatModel.shared.upsertGroupMember(gInfo, acceptedMember)
ChatModel.shared.updateGroup(gInfo)
dismiss?()
}
} catch let error {
logger.error("apiAcceptMember error: \(responseError(error))")
await MainActor.run {
showAlert(
NSLocalizedString("Error accepting member", comment: "alert title"),
message: responseError(error)
)
}
}
}
}
#Preview {
ContextPendingMemberActionsView(
groupInfo: GroupInfo.sampleData,
member: GroupMember.sampleData
)
}

View file

@ -78,6 +78,12 @@ struct AddGroupMembersViewCommon: View {
let count = selectedContacts.count
Section {
if creatingGroup {
MemberAdmissionButton(
groupInfo: $groupInfo,
admission: groupInfo.groupProfile.memberAdmission_,
currentAdmission: groupInfo.groupProfile.memberAdmission_,
creatingGroup: true
)
GroupPreferencesButton(
groupInfo: $groupInfo,
preferences: groupInfo.fullGroupPreferences,

View file

@ -17,6 +17,7 @@ struct GroupChatInfoView: View {
@Environment(\.dismiss) var dismiss: DismissAction
@ObservedObject var chat: Chat
@Binding var groupInfo: GroupInfo
@Binding var scrollToItemId: ChatItem.ID?
var onSearch: () -> Void
@State var localAlias: String
@FocusState private var aliasTextFieldFocused: Bool
@ -87,7 +88,25 @@ struct GroupChatInfoView: View {
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
Section {
if groupInfo.canAddMembers && groupInfo.businessChat == nil {
groupLinkButton()
}
if groupInfo.businessChat == nil && groupInfo.membership.memberRole >= .moderator {
memberSupportButton()
}
if groupInfo.canModerate {
GroupReportsChatNavLink(chat: chat, scrollToItemId: $scrollToItemId)
}
if groupInfo.membership.memberActive
&& (groupInfo.membership.memberRole < .moderator || groupInfo.membership.supportChat != nil) {
UserSupportChatNavLink(chat: chat, groupInfo: groupInfo, scrollToItemId: $scrollToItemId)
}
} header: {
Text("")
}
Section {
if groupInfo.isOwner && groupInfo.businessChat == nil {
editGroupButton()
@ -96,19 +115,6 @@ struct GroupChatInfoView: View {
addOrEditWelcomeMessage()
}
GroupPreferencesButton(groupInfo: $groupInfo, preferences: groupInfo.fullGroupPreferences, currentPreferences: groupInfo.fullGroupPreferences)
if members.filter({ $0.wrapped.memberCurrent }).count <= SMALL_GROUPS_RCPS_MEM_LIMIT {
sendReceiptsOption()
} else {
sendReceiptsOptionDisabled()
}
NavigationLink {
ChatWallpaperEditorSheet(chat: chat)
} label: {
Label("Chat theme", systemImage: "photo")
}
} header: {
Text("")
} footer: {
let label: LocalizedStringKey = (
groupInfo.businessChat == nil
@ -120,6 +126,16 @@ struct GroupChatInfoView: View {
}
Section {
if members.filter({ $0.wrapped.memberCurrent }).count <= SMALL_GROUPS_RCPS_MEM_LIMIT {
sendReceiptsOption()
} else {
sendReceiptsOptionDisabled()
}
NavigationLink {
ChatWallpaperEditorSheet(chat: chat)
} label: {
Label("Chat theme", systemImage: "photo")
}
ChatTTLOption(chat: chat, progressIndicator: $progressIndicator)
} footer: {
Text("Delete chat messages from your device.")
@ -127,9 +143,6 @@ struct GroupChatInfoView: View {
Section(header: Text("\(members.count + 1) members").foregroundColor(theme.colors.secondary)) {
if groupInfo.canAddMembers {
if groupInfo.businessChat == nil {
groupLinkButton()
}
if (chat.chatInfo.incognito) {
Label("Invite members", systemImage: "plus")
.foregroundColor(Color(uiColor: .tertiaryLabel))
@ -144,9 +157,16 @@ struct GroupChatInfoView: View {
let filteredMembers = s == ""
? members
: members.filter { $0.wrapped.localAliasAndFullName.localizedLowercase.contains(s) }
MemberRowView(chat: chat, groupInfo: groupInfo, groupMember: GMember(groupInfo.membership), user: true, alert: $alert)
MemberRowView(
chat: chat,
groupInfo: groupInfo,
groupMember: GMember(groupInfo.membership),
scrollToItemId: $scrollToItemId,
user: true,
alert: $alert
)
ForEach(filteredMembers) { member in
MemberRowView(chat: chat, groupInfo: groupInfo, groupMember: member, alert: $alert)
MemberRowView(chat: chat, groupInfo: groupInfo, groupMember: member, scrollToItemId: $scrollToItemId, alert: $alert)
}
}
@ -155,7 +175,7 @@ struct GroupChatInfoView: View {
if groupInfo.canDelete {
deleteGroupButton()
}
if groupInfo.membership.memberCurrent {
if groupInfo.membership.memberCurrentOrPending {
leaveGroupButton()
}
}
@ -353,6 +373,7 @@ struct GroupChatInfoView: View {
var chat: Chat
var groupInfo: GroupInfo
@ObservedObject var groupMember: GMember
@Binding var scrollToItemId: ChatItem.ID?
@EnvironmentObject var theme: AppTheme
var user: Bool = false
@Binding var alert: GroupChatInfoViewAlert?
@ -415,7 +436,7 @@ struct GroupChatInfoView: View {
}
private func memberInfoView() -> some View {
GroupMemberInfoView(groupInfo: groupInfo, chat: chat, groupMember: groupMember)
GroupMemberInfoView(groupInfo: groupInfo, chat: chat, groupMember: groupMember, scrollToItemId: $scrollToItemId)
.navigationBarHidden(false)
}
@ -523,6 +544,89 @@ struct GroupChatInfoView: View {
.navigationBarTitleDisplayMode(.large)
}
struct UserSupportChatNavLink: View {
@ObservedObject var chat: Chat
@EnvironmentObject var theme: AppTheme
var groupInfo: GroupInfo
@EnvironmentObject var chatModel: ChatModel
@Binding var scrollToItemId: ChatItem.ID?
@State private var navLinkActive = false
var body: some View {
let scopeInfo: GroupChatScopeInfo = .memberSupport(groupMember_: nil)
NavigationLink(isActive: $navLinkActive) {
SecondaryChatView(
chat: Chat(chatInfo: .group(groupInfo: groupInfo, groupChatScope: scopeInfo), chatItems: [], chatStats: ChatStats()),
scrollToItemId: $scrollToItemId
)
} label: {
HStack {
Label("Chat with admins", systemImage: chat.supportUnreadCount > 0 ? "flag.fill" : "flag")
Spacer()
if chat.supportUnreadCount > 0 {
UnreadBadge(count: chat.supportUnreadCount, color: theme.colors.primary)
}
}
}
.onChange(of: navLinkActive) { active in
if active {
ItemsModel.loadSecondaryChat(groupInfo.id, chatFilter: .groupChatScopeContext(groupScopeInfo: scopeInfo))
}
}
}
}
private func memberSupportButton() -> some View {
NavigationLink {
MemberSupportView(groupInfo: groupInfo, scrollToItemId: $scrollToItemId)
.navigationBarTitle("Chats with members")
.modifier(ThemedBackground())
.navigationBarTitleDisplayMode(.large)
} label: {
HStack {
Label(
"Chats with members",
systemImage: chat.supportUnreadCount > 0 ? "flag.fill" : "flag"
)
Spacer()
if chat.supportUnreadCount > 0 {
UnreadBadge(count: chat.supportUnreadCount, color: theme.colors.primary)
}
}
}
}
struct GroupReportsChatNavLink: View {
@EnvironmentObject var chatModel: ChatModel
@EnvironmentObject var theme: AppTheme
@State private var navLinkActive = false
@ObservedObject var chat: Chat
@Binding var scrollToItemId: ChatItem.ID?
var body: some View {
NavigationLink(isActive: $navLinkActive) {
SecondaryChatView(chat: chat, scrollToItemId: $scrollToItemId)
} label: {
HStack {
Label {
Text("Member reports")
} icon: {
Image(systemName: chat.chatStats.reportsCount > 0 ? "flag.fill" : "flag").foregroundColor(.red)
}
Spacer()
if chat.chatStats.reportsCount > 0 {
UnreadBadge(count: chat.chatStats.reportsCount, color: .red)
}
}
}
.onChange(of: navLinkActive) { active in
if active {
ItemsModel.loadSecondaryChat(chat.id, chatFilter: .msgContentTagContext(contentTag: .report))
}
}
}
}
private func editGroupButton() -> some View {
NavigationLink {
GroupProfileView(
@ -683,26 +787,36 @@ struct GroupChatInfoView: View {
title: Text("Remove member?"),
message: Text(messageLabel),
primaryButton: .destructive(Text("Remove")) {
Task {
do {
let updatedMembers = try await apiRemoveMembers(groupInfo.groupId, [mem.groupMemberId])
await MainActor.run {
updatedMembers.forEach { updatedMember in
_ = chatModel.upsertGroupMember(groupInfo, updatedMember)
}
}
} catch let error {
logger.error("apiRemoveMembers error: \(responseError(error))")
let a = getErrorAlert(error, "Error removing member")
alert = .error(title: a.title, error: a.message)
}
}
removeMember(groupInfo, mem)
},
secondaryButton: .cancel()
)
}
}
func removeMember(_ groupInfo: GroupInfo, _ mem: GroupMember, dismiss: DismissAction? = nil) {
Task {
do {
let (updatedGroupInfo, updatedMembers) = try await apiRemoveMembers(groupInfo.groupId, [mem.groupMemberId])
await MainActor.run {
ChatModel.shared.updateGroup(updatedGroupInfo)
updatedMembers.forEach { updatedMember in
_ = ChatModel.shared.upsertGroupMember(updatedGroupInfo, updatedMember)
}
dismiss?()
}
} catch let error {
logger.error("apiRemoveMembers error: \(responseError(error))")
await MainActor.run {
showAlert(
NSLocalizedString("Error removing member", comment: "alert title"),
message: responseError(error)
)
}
}
}
}
func deleteGroupAlertMessage(_ groupInfo: GroupInfo) -> Text {
groupInfo.businessChat == nil ? (
groupInfo.membership.memberCurrent ? Text("Group will be deleted for all members - this cannot be undone!") : Text("Group will be deleted for you - this cannot be undone!")
@ -796,6 +910,7 @@ struct GroupChatInfoView_Previews: PreviewProvider {
GroupChatInfoView(
chat: Chat(chatInfo: ChatInfo.sampleData.group, chatItems: []),
groupInfo: Binding.constant(GroupInfo.sampleData),
scrollToItemId: Binding.constant(nil),
onSearch: {},
localAlias: ""
)

View file

@ -16,6 +16,7 @@ struct GroupMemberInfoView: View {
@State var groupInfo: GroupInfo
@ObservedObject var chat: Chat
@ObservedObject var groupMember: GMember
@Binding var scrollToItemId: ChatItem.ID?
var navigation: Bool = false
@State private var connectionStats: ConnectionStats? = nil
@State private var connectionCode: String? = nil
@ -103,6 +104,10 @@ struct GroupMemberInfoView: View {
if member.memberActive {
Section {
if groupInfo.membership.memberRole >= .moderator
&& (member.memberRole < .moderator || member.supportChat != nil) {
MemberInfoSupportChatNavLink(groupInfo: groupInfo, member: groupMember, scrollToItemId: $scrollToItemId)
}
if let code = connectionCode { verifyCodeButton(code) }
if let connStats = connectionStats,
connStats.ratchetSyncAllowed {
@ -278,7 +283,7 @@ struct GroupMemberInfoView: View {
}
}
.onChange(of: chat.chatInfo) { c in
if case let .group(gI) = chat.chatInfo {
if case let .group(gI, _) = chat.chatInfo {
groupInfo = gI
}
}
@ -474,6 +479,31 @@ struct GroupMemberInfoView: View {
.frame(maxWidth: .infinity, alignment: .center)
}
struct MemberInfoSupportChatNavLink: View {
@EnvironmentObject var theme: AppTheme
var groupInfo: GroupInfo
var member: GMember
@Binding var scrollToItemId: ChatItem.ID?
@State private var navLinkActive = false
var body: some View {
let scopeInfo: GroupChatScopeInfo = .memberSupport(groupMember_: member.wrapped)
NavigationLink(isActive: $navLinkActive) {
SecondaryChatView(
chat: Chat(chatInfo: .group(groupInfo: groupInfo, groupChatScope: scopeInfo), chatItems: [], chatStats: ChatStats()),
scrollToItemId: $scrollToItemId
)
} label: {
Label("Chat with member", systemImage: "flag")
}
.onChange(of: navLinkActive) { active in
if active {
ItemsModel.loadSecondaryChat(groupInfo.id, chatFilter: .groupChatScopeContext(groupScopeInfo: scopeInfo))
}
}
}
}
private func verifyCodeButton(_ code: String) -> some View {
let member = groupMember.wrapped
return NavigationLink {
@ -610,10 +640,11 @@ struct GroupMemberInfoView: View {
primaryButton: .destructive(Text("Remove")) {
Task {
do {
let updatedMembers = try await apiRemoveMembers(groupInfo.groupId, [mem.groupMemberId])
let (updatedGroupInfo, updatedMembers) = try await apiRemoveMembers(groupInfo.groupId, [mem.groupMemberId])
await MainActor.run {
chatModel.updateGroup(updatedGroupInfo)
updatedMembers.forEach { updatedMember in
_ = chatModel.upsertGroupMember(groupInfo, updatedMember)
_ = chatModel.upsertGroupMember(updatedGroupInfo, updatedMember)
}
dismiss()
}
@ -821,7 +852,8 @@ struct GroupMemberInfoView_Previews: PreviewProvider {
GroupMemberInfoView(
groupInfo: GroupInfo.sampleData,
chat: Chat.sampleData,
groupMember: GMember.sampleData
groupMember: GMember.sampleData,
scrollToItemId: Binding.constant(nil)
)
}
}

View file

@ -17,6 +17,7 @@ let MAX_VISIBLE_MEMBER_ROWS: CGFloat = 4.8
struct GroupMentionsView: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
var im: ItemsModel
var groupInfo: GroupInfo
@Binding var composeState: ComposeState
@Binding var selectedRange: NSRange
@ -93,12 +94,31 @@ struct GroupMentionsView: View {
currentMessage = composeState.message
}
}
func contextMemberFilter(_ member: GroupMember) -> Bool {
switch im.secondaryIMFilter {
case nil:
return true
case let .groupChatScopeContext(groupScopeInfo):
switch (groupScopeInfo) {
case let .memberSupport(groupMember_):
if let scopeMember = groupMember_ {
return member.memberRole >= .moderator || member.groupMemberId == scopeMember.groupMemberId
} else {
return member.memberRole >= .moderator
}
}
case .msgContentTagContext:
return false
}
}
private func filteredMembers() -> [GMember] {
let s = mentionName.lowercased()
return s.isEmpty
? sortedMembers
: sortedMembers.filter { $0.wrapped.localAliasAndFullName.localizedLowercase.contains(s) }
return sortedMembers.filter {
contextMemberFilter($0.wrapped)
&& (s.isEmpty || $0.wrapped.localAliasAndFullName.localizedLowercase.contains(s))
}
}
private func messageChanged(_ msg: String, _ parsedMsg: [FormattedText], _ range: NSRange) {

View file

@ -30,6 +30,14 @@ struct GroupPreferencesView: View {
let saveText: LocalizedStringKey = creatingGroup ? "Save" : "Save and notify group members"
VStack {
List {
Section {
MemberAdmissionButton(
groupInfo: $groupInfo,
admission: groupInfo.groupProfile.memberAdmission_,
currentAdmission: groupInfo.groupProfile.memberAdmission_,
creatingGroup: creatingGroup
)
}
featureSection(.timedMessages, $preferences.timedMessages.enable)
featureSection(.fullDelete, $preferences.fullDelete.enable)
featureSection(.directMessages, $preferences.directMessages.enable, $preferences.directMessages.role)
@ -140,6 +148,66 @@ struct GroupPreferencesView: View {
}
}
struct MemberAdmissionButton: View {
@Binding var groupInfo: GroupInfo
@State var admission: GroupMemberAdmission
@State var currentAdmission: GroupMemberAdmission
var creatingGroup: Bool = false
var body: some View {
NavigationLink {
MemberAdmissionView(
groupInfo: $groupInfo,
admission: $admission,
currentAdmission: currentAdmission,
creatingGroup: creatingGroup,
saveAdmission: saveAdmission
)
.navigationBarTitle("Member admission")
.modifier(ThemedBackground(grouped: true))
.navigationBarTitleDisplayMode(.large)
.onDisappear {
let saveText = NSLocalizedString(
creatingGroup ? "Save" : "Save and notify group members",
comment: "alert button"
)
if groupInfo.groupProfile.memberAdmission_ != admission {
showAlert(
title: NSLocalizedString("Save admission settings?", comment: "alert title"),
buttonTitle: saveText,
buttonAction: { saveAdmission() },
cancelButton: true
)
}
}
} label: {
if creatingGroup {
Text("Set member admission")
} else {
Label("Member admission", systemImage: "switch.2")
}
}
}
private func saveAdmission() {
Task {
do {
var gp = groupInfo.groupProfile
gp.memberAdmission = admission
let gInfo = try await apiUpdateGroup(groupInfo.groupId, gp)
await MainActor.run {
groupInfo = gInfo
ChatModel.shared.updateGroup(gInfo)
currentAdmission = admission
}
} catch {
logger.error("MemberAdmissionView apiUpdateGroup error: \(responseError(error))")
}
}
}
}
struct GroupPreferencesView_Previews: PreviewProvider {
static var previews: some View {
GroupPreferencesView(

View file

@ -0,0 +1,93 @@
//
// MemberAdmissionView.swift
// SimpleX (iOS)
//
// Created by spaced4ndy on 28.04.2025.
// Copyright © 2025 SimpleX Chat. All rights reserved.
//
import SwiftUI
import SimpleXChat
private let memberCriterias: [(criteria: MemberCriteria?, text: LocalizedStringKey)] = [
(nil, "off"),
(.all, "all")
]
struct MemberAdmissionView: View {
@Environment(\.dismiss) var dismiss: DismissAction
@EnvironmentObject var chatModel: ChatModel
@EnvironmentObject var theme: AppTheme
@Binding var groupInfo: GroupInfo
@Binding var admission: GroupMemberAdmission
var currentAdmission: GroupMemberAdmission
let creatingGroup: Bool
let saveAdmission: () -> Void
@State private var showSaveDialogue = false
var body: some View {
let saveText: LocalizedStringKey = creatingGroup ? "Save" : "Save and notify group members"
VStack {
List {
admissionSection(
NSLocalizedString("Review members", comment: "admission stage"),
NSLocalizedString("Review members before admitting (\"knocking\").", comment: "admission stage description"),
$admission.review
)
if groupInfo.isOwner {
Section {
Button("Reset") { admission = currentAdmission }
Button(saveText) { saveAdmission() }
}
.disabled(currentAdmission == admission)
}
}
}
.modifier(BackButton(disabled: Binding.constant(false)) {
if currentAdmission == admission {
dismiss()
} else {
showSaveDialogue = true
}
})
.confirmationDialog("Save admission settings?", isPresented: $showSaveDialogue) {
Button(saveText) {
saveAdmission()
dismiss()
}
Button("Exit without saving") {
admission = currentAdmission
dismiss()
}
}
}
private func admissionSection(_ admissionStageStr: String, _ admissionStageDescrStr: String, _ memberCriteria: Binding<MemberCriteria?>) -> some View {
Section {
if groupInfo.isOwner {
Picker(admissionStageStr, selection: memberCriteria) {
ForEach(memberCriterias, id: \.criteria) { mc in
Text(mc.text)
}
}
.frame(height: 36)
} else {
infoRow(Text(admissionStageStr), memberCriteria.wrappedValue?.text ?? NSLocalizedString("off", comment: "member criteria value"))
}
} footer: {
Text(admissionStageDescrStr)
.foregroundColor(theme.colors.secondary)
}
}
}
#Preview {
MemberAdmissionView(
groupInfo: Binding.constant(GroupInfo.sampleData),
admission: Binding.constant(GroupMemberAdmission.sampleData),
currentAdmission: GroupMemberAdmission.sampleData,
creatingGroup: false,
saveAdmission: {}
)
}

View file

@ -0,0 +1,44 @@
//
// MemberSupportChatToolbar.swift
// SimpleX (iOS)
//
// Created by spaced4ndy on 01.05.2025.
// Copyright © 2025 SimpleX Chat. All rights reserved.
//
import SwiftUI
import SimpleXChat
struct MemberSupportChatToolbar: View {
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var theme: AppTheme
var groupMember: GroupMember
var imageSize: CGFloat = 32
var body: some View {
return HStack {
MemberProfileImage(groupMember, size: imageSize)
.padding(.trailing, 4)
let t = Text(groupMember.chatViewName).font(.headline)
(groupMember.verified ? memberVerifiedShield + t : t)
.lineLimit(1)
}
.foregroundColor(theme.colors.onBackground)
.frame(width: 220)
}
private var memberVerifiedShield: Text {
(Text(Image(systemName: "checkmark.shield")) + textSpace)
.font(.caption)
.foregroundColor(theme.colors.secondary)
.baselineOffset(1)
.kerning(-2)
}
}
#Preview {
MemberSupportChatToolbar(
groupMember: GroupMember.sampleData
)
.environmentObject(CurrentColors.toAppTheme())
}

View file

@ -0,0 +1,287 @@
//
// MemberSupportView.swift
// SimpleX (iOS)
//
// Created by spaced4ndy on 28.04.2025.
// Copyright © 2025 SimpleX Chat. All rights reserved.
//
import SwiftUI
import SimpleXChat
struct MemberSupportView: View {
@EnvironmentObject var chatModel: ChatModel
@EnvironmentObject var theme: AppTheme
@State private var searchText: String = ""
@FocusState private var searchFocussed
var groupInfo: GroupInfo
@Binding var scrollToItemId: ChatItem.ID?
var body: some View {
viewBody()
.onAppear {
Task {
await chatModel.loadGroupMembers(groupInfo)
}
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
Task {
await chatModel.loadGroupMembers(groupInfo)
}
} label: {
Image(systemName: "arrow.clockwise")
}
}
}
}
@ViewBuilder private func viewBody() -> some View {
let membersWithChats = sortedMembersWithChats()
let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase
let filteredMembersWithChats = s == ""
? membersWithChats
: membersWithChats.filter { $0.wrapped.localAliasAndFullName.localizedLowercase.contains(s) }
if membersWithChats.isEmpty {
Text("No chats with members")
.foregroundColor(.secondary)
} else {
List {
searchFieldView(text: $searchText, focussed: $searchFocussed, theme.colors.onBackground, theme.colors.secondary)
.padding(.leading, 8)
ForEach(filteredMembersWithChats) { memberWithChat in
MemberSupportChatNavLink(
groupInfo: groupInfo,
memberWithChat: memberWithChat,
scrollToItemId: $scrollToItemId
)
}
}
}
}
struct MemberSupportChatNavLink: View {
@EnvironmentObject var chatModel: ChatModel
@EnvironmentObject var theme: AppTheme
@State private var memberSupportChatNavLinkActive = false
var groupInfo: GroupInfo
var memberWithChat: GMember
@Binding var scrollToItemId: ChatItem.ID?
var body: some View {
ZStack {
let scopeInfo: GroupChatScopeInfo = .memberSupport(groupMember_: memberWithChat.wrapped)
Button {
ItemsModel.loadSecondaryChat(groupInfo.id, chatFilter: .groupChatScopeContext(groupScopeInfo: scopeInfo)) {
memberSupportChatNavLinkActive = true
}
} label: {
SupportChatRowView(groupMember: memberWithChat, groupInfo: groupInfo)
}
NavigationLink(isActive: $memberSupportChatNavLinkActive) {
SecondaryChatView(
chat: Chat(chatInfo: .group(groupInfo: groupInfo, groupChatScope: scopeInfo), chatItems: [], chatStats: ChatStats()),
scrollToItemId: $scrollToItemId
)
} label: {
EmptyView()
}
.frame(width: 1, height: 1)
.hidden()
}
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
if memberWithChat.wrapped.memberPending {
Button {
showAcceptMemberAlert(groupInfo, memberWithChat.wrapped)
} label: {
Label("Accept", systemImage: "checkmark")
}
.tint(theme.colors.primary)
} else {
Button {
showDeleteMemberSupportChatAlert(groupInfo, memberWithChat.wrapped)
} label: {
Label("Delete", systemImage: "trash")
}
.tint(.red)
}
}
}
}
func sortedMembersWithChats() -> [GMember] {
chatModel.groupMembers
.filter {
$0.wrapped.supportChat != nil &&
$0.wrapped.memberStatus != .memLeft &&
$0.wrapped.memberStatus != .memRemoved
}
.sorted { (m0: GMember, m1: GMember) -> Bool in
if m0.wrapped.memberPending != m1.wrapped.memberPending {
return m0.wrapped.memberPending
}
let mentions0 = (m0.wrapped.supportChat?.mentions ?? 0) > 0
let mentions1 = (m1.wrapped.supportChat?.mentions ?? 0) > 0
if mentions0 != mentions1 {
return mentions0
}
let attention0 = (m0.wrapped.supportChat?.memberAttention ?? 0) > 0
let attention1 = (m1.wrapped.supportChat?.memberAttention ?? 0) > 0
if attention0 != attention1 {
return attention0
}
let unread0 = (m0.wrapped.supportChat?.unread ?? 0) > 0
let unread1 = (m1.wrapped.supportChat?.unread ?? 0) > 0
if unread0 != unread1 {
return unread0
}
return (m0.wrapped.supportChat?.chatTs ?? .distantPast) > (m1.wrapped.supportChat?.chatTs ?? .distantPast)
}
}
private struct SupportChatRowView: View {
@EnvironmentObject var chatModel: ChatModel
@ObservedObject var groupMember: GMember
@EnvironmentObject var theme: AppTheme
@Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize
var groupInfo: GroupInfo
var dynamicChatInfoSize: CGFloat { dynamicSize(userFont).chatInfoSize }
var body: some View {
let member = groupMember.wrapped
HStack{
MemberProfileImage(member, size: 38)
.padding(.trailing, 2)
VStack(alignment: .leading) {
let t = Text(member.chatViewName).foregroundColor(theme.colors.onBackground)
(member.verified ? memberVerifiedShield + t : t)
.lineLimit(1)
Text(memberStatus(member))
.lineLimit(1)
.font(.caption)
.foregroundColor(theme.colors.secondary)
}
Spacer()
if member.memberPending {
Image(systemName: "flag.fill")
.resizable()
.scaledToFill()
.frame(width: dynamicChatInfoSize * 0.8, height: dynamicChatInfoSize * 0.8)
.foregroundColor(theme.colors.primary)
}
if let supportChat = member.supportChat {
SupportChatUnreadIndicator(supportChat: supportChat)
}
}
}
private func memberStatus(_ member: GroupMember) -> LocalizedStringKey {
if member.activeConn?.connDisabled ?? false {
return "disabled"
} else if member.activeConn?.connInactive ?? false {
return "inactive"
} else if member.memberPending {
return member.memberStatus.text
} else {
return LocalizedStringKey(member.memberRole.text)
}
}
struct SupportChatUnreadIndicator: View {
@EnvironmentObject var theme: AppTheme
@Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize
var supportChat: GroupSupportChat
var dynamicChatInfoSize: CGFloat { dynamicSize(userFont).chatInfoSize }
private var indicatorTint: Color {
if supportChat.mentions > 0 || supportChat.memberAttention > 0 {
return theme.colors.primary
} else {
return theme.colors.secondary
}
}
var body: some View {
HStack(alignment: .center, spacing: 2) {
if supportChat.unread > 0 || supportChat.mentions > 0 || supportChat.memberAttention > 0 {
if supportChat.mentions > 0 && supportChat.unread > 1 {
Text("\(MENTION_START)")
.font(userFont <= .xxxLarge ? .body : .callout)
.foregroundColor(indicatorTint)
.frame(minWidth: dynamicChatInfoSize, minHeight: dynamicChatInfoSize)
.cornerRadius(dynamicSize(userFont).unreadCorner)
.padding(.bottom, 1)
}
let singleUnreadIsMention = supportChat.mentions > 0 && supportChat.unread == 1
(singleUnreadIsMention ? Text("\(MENTION_START)") : unreadCountText(supportChat.unread))
.font(userFont <= .xxxLarge ? .caption : .caption2)
.foregroundColor(.white)
.padding(.horizontal, dynamicSize(userFont).unreadPadding)
.frame(minWidth: dynamicChatInfoSize, minHeight: dynamicChatInfoSize)
.background(indicatorTint)
.cornerRadius(dynamicSize(userFont).unreadCorner)
}
}
.frame(height: dynamicChatInfoSize)
.frame(minWidth: 22)
}
}
private var memberVerifiedShield: Text {
(Text(Image(systemName: "checkmark.shield")) + textSpace)
.font(.caption)
.baselineOffset(2)
.kerning(-2)
.foregroundColor(theme.colors.secondary)
}
}
}
func showDeleteMemberSupportChatAlert(_ groupInfo: GroupInfo, _ member: GroupMember) {
showAlert(
title: NSLocalizedString("Delete chat with member?", comment: "alert title"),
buttonTitle: "Delete",
buttonAction: { deleteMemberSupportChat(groupInfo, member) },
cancelButton: true
)
}
func deleteMemberSupportChat(_ groupInfo: GroupInfo, _ member: GroupMember) {
Task {
do {
let (gInfo, updatedMember) = try await apiDeleteMemberSupportChat(groupInfo.groupId, member.groupMemberId)
await MainActor.run {
_ = ChatModel.shared.upsertGroupMember(gInfo, updatedMember)
ChatModel.shared.updateGroup(gInfo)
}
// TODO member row doesn't get removed from list (upsertGroupMember correctly sets supportChat to nil) - this repopulates list to fix it
await ChatModel.shared.loadGroupMembers(gInfo)
} catch let error {
logger.error("apiDeleteMemberSupportChat error: \(responseError(error))")
await MainActor.run {
showAlert(
NSLocalizedString("Error deleting chat with member", comment: "alert title"),
message: responseError(error)
)
}
}
}
}
#Preview {
MemberSupportView(
groupInfo: GroupInfo.sampleData,
scrollToItemId: Binding.constant(nil)
)
}

View file

@ -0,0 +1,42 @@
//
// SecondaryChatView.swift
// SimpleX (iOS)
//
// Created by spaced4ndy on 29.04.2025.
// Copyright © 2025 SimpleX Chat. All rights reserved.
//
import SwiftUI
import SimpleXChat
struct SecondaryChatView: View {
@EnvironmentObject var chatModel: ChatModel
@ObservedObject var chat: Chat
@Binding var scrollToItemId: ChatItem.ID?
var body: some View {
if let im = chatModel.secondaryIM {
ChatView(
chat: chat,
im: im,
mergedItems: BoxedValue(MergedItems.create(im, [])),
floatingButtonModel: FloatingButtonModel(im: im),
scrollToItemId: $scrollToItemId
)
.onDisappear {
chatModel.secondaryIM = nil
}
}
}
}
#Preview {
SecondaryChatView(
chat: Chat(
chatInfo: .group(groupInfo: GroupInfo.sampleData, groupChatScope: .memberSupport(groupMember_: GroupMember.sampleData)),
chatItems: [],
chatStats: ChatStats()
),
scrollToItemId: Binding.constant(nil)
)
}

View file

@ -25,7 +25,7 @@ struct SelectedItemsTopToolbar: View {
struct SelectedItemsBottomToolbar: View {
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var theme: AppTheme
let chatItems: [ChatItem]
let im: ItemsModel
@Binding var selectedChatItems: Set<Int64>?
var chatInfo: ChatInfo
// Bool - delete for everyone is possible
@ -75,9 +75,9 @@ struct SelectedItemsBottomToolbar: View {
.resizable()
.scaledToFit()
.frame(width: 20, height: 20, alignment: .center)
.foregroundColor(!moderateEnabled || deleteCountProhibited ? theme.colors.secondary : .red)
.foregroundColor(!moderateEnabled || deleteCountProhibited || im.secondaryIMFilter != nil ? theme.colors.secondary : .red)
}
.disabled(!moderateEnabled || deleteCountProhibited)
.disabled(!moderateEnabled || deleteCountProhibited || im.secondaryIMFilter != nil)
.opacity(canModerate ? 1 : 0)
Spacer()
@ -88,24 +88,24 @@ struct SelectedItemsBottomToolbar: View {
.resizable()
.scaledToFit()
.frame(width: 20, height: 20, alignment: .center)
.foregroundColor(!forwardEnabled || forwardCountProhibited ? theme.colors.secondary : theme.colors.primary)
.foregroundColor(!forwardEnabled || forwardCountProhibited || im.secondaryIMFilter != nil ? theme.colors.secondary : theme.colors.primary)
}
.disabled(!forwardEnabled || forwardCountProhibited)
.disabled(!forwardEnabled || forwardCountProhibited || im.secondaryIMFilter != nil)
}
.frame(maxHeight: .infinity)
.padding([.leading, .trailing], 12)
}
.onAppear {
recheckItems(chatInfo, chatItems, selectedChatItems)
recheckItems(chatInfo, im.reversedChatItems, selectedChatItems)
}
.onChange(of: chatInfo) { info in
recheckItems(info, chatItems, selectedChatItems)
recheckItems(info, im.reversedChatItems, selectedChatItems)
}
.onChange(of: chatItems) { items in
.onChange(of: im.reversedChatItems) { items in
recheckItems(chatInfo, items, selectedChatItems)
}
.onChange(of: selectedChatItems) { selected in
recheckItems(chatInfo, chatItems, selected)
recheckItems(chatInfo, im.reversedChatItems, selected)
}
.frame(height: 55.5)
.background(.thinMaterial)
@ -116,7 +116,7 @@ struct SelectedItemsBottomToolbar: View {
deleteCountProhibited = count == 0 || count > 200
forwardCountProhibited = count == 0 || count > 20
canModerate = possibleToModerate(chatInfo)
let groupInfo: GroupInfo? = if case let ChatInfo.group(groupInfo: info) = chatInfo {
let groupInfo: GroupInfo? = if case let ChatInfo.group(groupInfo: info, _) = chatInfo {
info
} else {
nil
@ -145,7 +145,7 @@ struct SelectedItemsBottomToolbar: View {
private func possibleToModerate(_ chatInfo: ChatInfo) -> Bool {
return switch chatInfo {
case let .group(groupInfo):
case let .group(groupInfo, _):
groupInfo.membership.memberRole >= .admin
default: false
}

View file

@ -66,7 +66,7 @@ struct ChatListNavLink: View {
switch chat.chatInfo {
case let .direct(contact):
contactNavLink(contact)
case let .group(groupInfo):
case let .group(groupInfo, _):
groupNavLink(groupInfo)
case let .local(noteFolder):
noteFolderNavLink(noteFolder)
@ -189,7 +189,7 @@ struct ChatListNavLink: View {
}
.swipeActions(edge: .trailing) {
tagChatButton(chat)
if (groupInfo.membership.memberCurrent) {
if (groupInfo.membership.memberCurrentOrPending) {
leaveGroupChatButton(groupInfo)
}
if groupInfo.canDelete {
@ -214,7 +214,7 @@ struct ChatListNavLink: View {
let showReportsButton = chat.chatStats.reportsCount > 0 && groupInfo.membership.memberRole >= .moderator
let showClearButton = !chat.chatItems.isEmpty
let showDeleteGroup = groupInfo.canDelete
let showLeaveGroup = groupInfo.membership.memberCurrent
let showLeaveGroup = groupInfo.membership.memberCurrentOrPending
let totalNumberOfButtons = 1 + (showReportsButton ? 1 : 0) + (showClearButton ? 1 : 0) + (showDeleteGroup ? 1 : 0) + (showLeaveGroup ? 1 : 0)
if showClearButton && totalNumberOfButtons <= 3 {
@ -276,7 +276,7 @@ struct ChatListNavLink: View {
@ViewBuilder private func markReadButton() -> some View {
if chat.chatStats.unreadCount > 0 || chat.chatStats.unreadChat {
Button {
Task { await markChatRead(chat) }
Task { await markChatRead(ItemsModel.shared, chat) }
} label: {
SwipeLabel(NSLocalizedString("Read", comment: "swipe action"), systemImage: "checkmark", inverted: oneHandUI)
}
@ -482,12 +482,10 @@ struct ChatListNavLink: View {
.tint(theme.colors.primary)
}
.appSheet(isPresented: $showContactConnectionInfo) {
Group {
if case let .contactConnection(contactConnection) = chat.chatInfo {
ContactConnectionInfo(contactConnection: contactConnection)
.environment(\EnvironmentValues.refresh as! WritableKeyPath<EnvironmentValues, RefreshAction?>, nil)
.modifier(ThemedBackground(grouped: true))
}
if case let .contactConnection(contactConnection) = chat.chatInfo {
ContactConnectionInfo(contactConnection: contactConnection)
.environment(\EnvironmentValues.refresh as! WritableKeyPath<EnvironmentValues, RefreshAction?>, nil)
.modifier(ThemedBackground(grouped: true))
}
}
.contentShape(Rectangle())

View file

@ -148,6 +148,7 @@ struct ChatListView: View {
@State private var userPickerShown: Bool = false
@State private var sheet: SomeSheet<AnyView>? = nil
@StateObject private var chatTagsModel = ChatTagsModel.shared
@State private var scrollToItemId: ChatItem.ID? = nil
// iOS 15 is required it to show/hide toolbar while chat is hidden/visible
@State private var viewOnScreen = true
@ -446,7 +447,14 @@ struct ChatListView: View {
@ViewBuilder private func chatView() -> some View {
if let chatId = chatModel.chatId, let chat = chatModel.getChat(chatId) {
ChatView(chat: chat)
let im = ItemsModel.shared
ChatView(
chat: chat,
im: im,
mergedItems: BoxedValue(MergedItems.create(im, [])),
floatingButtonModel: FloatingButtonModel(im: im),
scrollToItemId: $scrollToItemId
)
}
}
@ -892,12 +900,12 @@ func presetTagMatchesChat(_ tag: PresetTag, _ chatInfo: ChatInfo, _ chatStats: C
case let .direct(contact): !(contact.activeConn == nil && contact.profile.contactLink != nil && contact.active) && !contact.chatDeleted
case .contactRequest: true
case .contactConnection: true
case let .group(groupInfo): groupInfo.businessChat?.chatType == .customer
case let .group(groupInfo, _): groupInfo.businessChat?.chatType == .customer
default: false
}
case .groups:
switch chatInfo {
case let .group(groupInfo): groupInfo.businessChat == nil
case let .group(groupInfo, _): groupInfo.businessChat == nil
default: false
}
case .business:

View file

@ -141,7 +141,7 @@ struct ChatPreviewView: View {
} else {
EmptyView()
}
case let .group(groupInfo):
case let .group(groupInfo, _):
switch (groupInfo.membership.memberStatus) {
case .memRejected: inactiveIcon()
case .memLeft: inactiveIcon()
@ -165,7 +165,7 @@ struct ChatPreviewView: View {
switch chat.chatInfo {
case let .direct(contact):
previewTitle(contact.verified == true ? verifiedIcon + t : t).foregroundColor(deleting ? Color.secondary : nil)
case let .group(groupInfo):
case let .group(groupInfo, _):
let v = previewTitle(t)
switch (groupInfo.membership.memberStatus) {
case .memInvited: v.foregroundColor(deleting ? theme.colors.secondary : chat.chatInfo.incognito ? .indigo : theme.colors.primary)
@ -341,11 +341,12 @@ struct ChatPreviewView: View {
chatPreviewInfoText("connecting…")
}
}
case let .group(groupInfo):
case let .group(groupInfo, _):
switch (groupInfo.membership.memberStatus) {
case .memRejected: chatPreviewInfoText("rejected")
case .memInvited: groupInvitationPreviewText(groupInfo)
case .memAccepted: chatPreviewInfoText("connecting…")
case .memPendingReview, .memPendingApproval: chatPreviewInfoText("reviewed by admins")
default: EmptyView()
}
default: EmptyView()
@ -439,7 +440,11 @@ struct ChatPreviewView: View {
if progressByTimeout {
ProgressView()
} else if chat.chatStats.reportsCount > 0 {
groupReportsIcon(size: size * 0.8)
flagIcon(size: size * 0.8, color: .red)
} else if chat.supportUnreadCount > 0 {
flagIcon(size: size * 0.8, color: theme.colors.primary)
} else if chat.chatInfo.groupInfo?.membership.memberPending ?? false {
flagIcon(size: size * 0.8, color: theme.colors.secondary)
} else {
incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary, size: size)
}
@ -485,12 +490,12 @@ struct ChatPreviewView: View {
}
}
func groupReportsIcon(size: CGFloat) -> some View {
func flagIcon(size: CGFloat, color: Color) -> some View {
Image(systemName: "flag")
.resizable()
.scaledToFit()
.frame(width: size, height: size)
.foregroundColor(.red)
.foregroundColor(color)
}
func smallContentPreview(size: CGFloat, _ view: @escaping () -> some View) -> some View {

View file

@ -124,7 +124,7 @@ struct UserPicker: View {
ZStack(alignment: .topTrailing) {
ProfileImage(imageStr: u.user.image, size: size, color: Color(uiColor: .tertiarySystemGroupedBackground))
if (u.unreadCount > 0) {
UnreadBadge(userInfo: u).offset(x: 4, y: -4)
userUnreadBadge(u, theme: theme).offset(x: 4, y: -4)
}
}
.padding(.trailing, 6)
@ -171,19 +171,27 @@ struct UserPicker: View {
}
}
@inline(__always)
func userUnreadBadge(_ userInfo: UserInfo, theme: AppTheme) -> some View {
UnreadBadge(
count: userInfo.unreadCount,
color: userInfo.user.showNtfs ? theme.colors.primary : theme.colors.secondary
)
}
struct UnreadBadge: View {
var userInfo: UserInfo
@EnvironmentObject var theme: AppTheme
@Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize
var count: Int
var color: Color
var body: some View {
let size = dynamicSize(userFont).chatInfoSize
unreadCountText(userInfo.unreadCount)
unreadCountText(count)
.font(userFont <= .xxxLarge ? .caption : .caption2)
.foregroundColor(.white)
.padding(.horizontal, dynamicSize(userFont).unreadPadding)
.frame(minWidth: size, minHeight: size)
.background(userInfo.user.showNtfs ? theme.colors.primary : theme.colors.secondary)
.background(color)
.cornerRadius(dynamicSize(userFont).unreadCorner)
}
}

View file

@ -33,7 +33,7 @@ extension View {
func appSheet<Content>(
isPresented: Binding<Bool>,
onDismiss: (() -> Void)? = nil,
content: @escaping () -> Content
@ViewBuilder content: @escaping () -> Content
) -> some View where Content: View {
sheet(isPresented: isPresented, onDismiss: onDismiss) {
content().modifier(PrivacySensitive())
@ -43,7 +43,7 @@ extension View {
func appSheet<T, Content>(
item: Binding<T?>,
onDismiss: (() -> Void)? = nil,
content: @escaping (T) -> Content
@ViewBuilder content: @escaping (T) -> Content
) -> some View where T: Identifiable, Content: View {
sheet(item: item, onDismiss: onDismiss) { it in
content(it).modifier(PrivacySensitive())

View file

@ -66,6 +66,8 @@ struct LocalAuthView: View {
m.chatId = nil
ItemsModel.shared.reversedChatItems = []
ItemsModel.shared.chatState.clear()
ChatModel.shared.secondaryIM?.reversedChatItems = []
ChatModel.shared.secondaryIM?.chatState.clear()
m.updateChats([])
m.users = []
_ = kcAppPassword.set(password)

View file

@ -193,7 +193,7 @@ struct AddGroupView: View {
Task {
await m.loadGroupMembers(gInfo)
}
let c = Chat(chatInfo: .group(groupInfo: gInfo), chatItems: [])
let c = Chat(chatInfo: .group(groupInfo: gInfo, groupChatScope: nil), chatItems: [])
m.addChat(c)
withAnimation {
groupInfo = gInfo

View file

@ -367,13 +367,13 @@ struct ChatThemePreview: View {
let alice = ChatItem.getSample(1, CIDirection.directRcv, Date.now, NSLocalizedString("Good afternoon!", comment: "message preview"))
let bob = ChatItem.getSample(2, CIDirection.directSnd, Date.now, NSLocalizedString("Good morning!", comment: "message preview"), quotedItem: CIQuote.getSample(alice.id, alice.meta.itemTs, alice.content.text, chatDir: alice.chatDir))
HStack {
ChatItemView(chat: Chat.sampleData, chatItem: alice, scrollToItemId: { _ in })
ChatItemView(chat: Chat.sampleData, im: ItemsModel.shared, chatItem: alice, scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
.modifier(ChatItemClipped(alice, tailVisible: true))
Spacer()
}
HStack {
Spacer()
ChatItemView(chat: Chat.sampleData, chatItem: bob, scrollToItemId: { _ in })
ChatItemView(chat: Chat.sampleData, im: ItemsModel.shared, chatItem: bob, scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
.modifier(ChatItemClipped(bob, tailVisible: true))
.frame(alignment: .trailing)
}

View file

@ -58,6 +58,7 @@ let DEFAULT_CONNECT_VIA_LINK_TAB = "connectViaLinkTab"
let DEFAULT_LIVE_MESSAGE_ALERT_SHOWN = "liveMessageAlertShown"
let DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE = "showHiddenProfilesNotice"
let DEFAULT_SHOW_MUTE_PROFILE_ALERT = "showMuteProfileAlert"
let DEFAULT_SHOW_REPORTS_IN_SUPPORT_CHAT_ALERT = "showReportsInSupportChatAlert"
let DEFAULT_WHATS_NEW_VERSION = "defaultWhatsNewVersion"
let DEFAULT_ONBOARDING_STAGE = "onboardingStage"
let DEFAULT_MIGRATION_TO_STAGE = "migrationToStage"
@ -117,6 +118,7 @@ let appDefaults: [String: Any] = [
DEFAULT_LIVE_MESSAGE_ALERT_SHOWN: false,
DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE: true,
DEFAULT_SHOW_MUTE_PROFILE_ALERT: true,
DEFAULT_SHOW_REPORTS_IN_SUPPORT_CHAT_ALERT: true,
DEFAULT_ONBOARDING_STAGE: OnboardingStage.onboardingComplete.rawValue,
DEFAULT_CUSTOM_DISAPPEARING_MESSAGE_TIME: 300,
DEFAULT_SHOW_UNREAD_AND_FAVORITES: false,
@ -144,6 +146,7 @@ let hintDefaults = [
DEFAULT_LIVE_MESSAGE_ALERT_SHOWN,
DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE,
DEFAULT_SHOW_MUTE_PROFILE_ALERT,
DEFAULT_SHOW_REPORTS_IN_SUPPORT_CHAT_ALERT,
DEFAULT_SHOW_DELETE_CONVERSATION_NOTICE,
DEFAULT_SHOW_DELETE_CONTACT_NOTICE
]
@ -195,6 +198,8 @@ let customDisappearingMessageTimeDefault = IntDefault(defaults: UserDefaults.sta
let showDeleteConversationNoticeDefault = BoolDefault(defaults: UserDefaults.standard, forKey: DEFAULT_SHOW_DELETE_CONVERSATION_NOTICE)
let showDeleteContactNoticeDefault = BoolDefault(defaults: UserDefaults.standard, forKey: DEFAULT_SHOW_DELETE_CONTACT_NOTICE)
let showReportsInSupportChatAlertDefault = BoolDefault(defaults: UserDefaults.standard, forKey: DEFAULT_SHOW_REPORTS_IN_SUPPORT_CHAT_ALERT)
/// after importing new database, this flag will be set and unset only after importing app settings in `initializeChat` */
let shouldImportAppSettingsDefault = BoolDefault(defaults: UserDefaults.standard, forKey: DEFAULT_SHOULD_IMPORT_APP_SETTINGS)
let currentThemeDefault = StringDefault(defaults: UserDefaults.standard, forKey: DEFAULT_CURRENT_THEME, withDefault: DefaultTheme.SYSTEM_THEME_NAME)

View file

@ -350,7 +350,7 @@ struct UserProfilesView: View {
Image(systemName: "checkmark").foregroundColor(theme.colors.onBackground)
} else {
if userInfo.unreadCount > 0 {
UnreadBadge(userInfo: userInfo)
userUnreadBadge(userInfo, theme: theme)
}
if user.hidden {
Image(systemName: "lock").foregroundColor(theme.colors.secondary)

View file

@ -554,6 +554,14 @@ time interval</note>
accept incoming call via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept as member" xml:space="preserve">
<source>Accept as member</source>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept as observer" xml:space="preserve">
<source>Accept as observer</source>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept conditions" xml:space="preserve">
<source>Accept conditions</source>
<target>Приеми условията</target>
@ -575,6 +583,10 @@ swipe action</note>
<note>accept contact request via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept member" xml:space="preserve">
<source>Accept member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Accepted conditions" xml:space="preserve">
<source>Accepted conditions</source>
<target>Приети условия</target>
@ -1535,11 +1547,23 @@ set passcode view</note>
<source>Chat will be deleted for you - this cannot be undone!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat with admins" xml:space="preserve">
<source>Chat with admins</source>
<note>chat toolbar</note>
</trans-unit>
<trans-unit id="Chat with member" xml:space="preserve">
<source>Chat with member</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats" xml:space="preserve">
<source>Chats</source>
<target>Чатове</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats with members" xml:space="preserve">
<source>Chats with members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Check messages every 20 min." xml:space="preserve">
<source>Check messages every 20 min.</source>
<note>No comment provided by engineer.</note>
@ -2300,6 +2324,10 @@ swipe action</note>
<target>Изтриване на чат профила?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete chat with member?" xml:space="preserve">
<source>Delete chat with member?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Delete chat?" xml:space="preserve">
<source>Delete chat?</source>
<note>No comment provided by engineer.</note>
@ -2698,7 +2726,7 @@ swipe action</note>
<trans-unit id="Don't show again" xml:space="preserve">
<source>Don't show again</source>
<target>Не показвай отново</target>
<note>No comment provided by engineer.</note>
<note>alert action</note>
</trans-unit>
<trans-unit id="Done" xml:space="preserve">
<source>Done</source>
@ -3006,6 +3034,10 @@ chat item action</note>
<target>Грешка при приемане на заявка за контакт</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error accepting member" xml:space="preserve">
<source>Error accepting member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error adding member(s)" xml:space="preserve">
<source>Error adding member(s)</source>
<target>Грешка при добавяне на член(ове)</target>
@ -3094,6 +3126,10 @@ chat item action</note>
<target>Грешка при изтриване на базата данни</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error deleting chat with member" xml:space="preserve">
<source>Error deleting chat with member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error deleting chat!" xml:space="preserve">
<source>Error deleting chat!</source>
<target>Грешка при изтриването на чата!</target>
@ -3196,7 +3232,7 @@ chat item action</note>
<trans-unit id="Error removing member" xml:space="preserve">
<source>Error removing member</source>
<target>Грешка при отстраняване на член</target>
<note>No comment provided by engineer.</note>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error reordering lists" xml:space="preserve">
<source>Error reordering lists</source>
@ -4508,6 +4544,10 @@ This is your link for group %@!</source>
<target>Член</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member admission" xml:space="preserve">
<source>Member admission</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member inactive" xml:space="preserve">
<source>Member inactive</source>
<note>item status text</note>
@ -4539,6 +4579,10 @@ This is your link for group %@!</source>
<target>Членът ще бъде премахнат от групата - това не може да бъде отменено!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member will join the group, accept member?" xml:space="preserve">
<source>Member will join the group, accept member?</source>
<note>alert message</note>
</trans-unit>
<trans-unit id="Members can add message reactions." xml:space="preserve">
<source>Members can add message reactions.</source>
<target>Членовете на групата могат да добавят реакции към съобщенията.</target>
@ -4931,6 +4975,10 @@ This is your link for group %@!</source>
<target>Нова членска роля</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New member wants to join the group." xml:space="preserve">
<source>New member wants to join the group.</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="New message" xml:space="preserve">
<source>New message</source>
<target>Ново съобщение</target>
@ -4967,6 +5015,10 @@ This is your link for group %@!</source>
<source>No chats in list %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No chats with members" xml:space="preserve">
<source>No chats with members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No contacts selected" xml:space="preserve">
<source>No contacts selected</source>
<target>Няма избрани контакти</target>
@ -5140,7 +5192,8 @@ This is your link for group %@!</source>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
<target>Ок</target>
<note>alert button</note>
<note>alert action
alert button</note>
</trans-unit>
<trans-unit id="Old database" xml:space="preserve">
<source>Old database</source>
@ -5531,6 +5584,10 @@ Error: %@</source>
<source>Please try to disable and re-enable notfications.</source>
<note>token info</note>
</trans-unit>
<trans-unit id="Please wait for group moderators to review your request to join the group." xml:space="preserve">
<source>Please wait for group moderators to review your request to join the group.</source>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="Please wait for token activation to complete." xml:space="preserve">
<source>Please wait for token activation to complete.</source>
<note>token info</note>
@ -5950,6 +6007,10 @@ swipe action</note>
<target>Отхвърли заявката за контакт</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Reject member?" xml:space="preserve">
<source>Reject member?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Relay server is only used if necessary. Another party can observe your IP address." xml:space="preserve">
<source>Relay server is only used if necessary. Another party can observe your IP address.</source>
<target>Реле сървър се използва само ако е необходимо. Друга страна може да наблюдава вашия IP адрес.</target>
@ -6053,6 +6114,10 @@ swipe action</note>
<source>Report reason?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Report sent to moderators" xml:space="preserve">
<source>Report sent to moderators</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Report spam: only group moderators will see it." xml:space="preserve">
<source>Report spam: only group moderators will see it.</source>
<note>report reason</note>
@ -6157,6 +6222,14 @@ swipe action</note>
<source>Review conditions</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Review members" xml:space="preserve">
<source>Review members</source>
<note>admission stage</note>
</trans-unit>
<trans-unit id="Review members before admitting (&quot;knocking&quot;)." xml:space="preserve">
<source>Review members before admitting ("knocking").</source>
<note>admission stage description</note>
</trans-unit>
<trans-unit id="Revoke" xml:space="preserve">
<source>Revoke</source>
<target>Отзови</target>
@ -6210,6 +6283,10 @@ chat item action</note>
<target>Запази (и уведоми контактите)</target>
<note>alert button</note>
</trans-unit>
<trans-unit id="Save admission settings?" xml:space="preserve">
<source>Save admission settings?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Save and notify contact" xml:space="preserve">
<source>Save and notify contact</source>
<target>Запази и уведоми контакта</target>
@ -6689,6 +6766,10 @@ chat item action</note>
<target>Задайте го вместо системната идентификация.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set member admission" xml:space="preserve">
<source>Set member admission</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set message expiration in chats." xml:space="preserve">
<source>Set message expiration in chats.</source>
<note>No comment provided by engineer.</note>
@ -8343,6 +8424,10 @@ Repeat join request?</source>
<target>Можете да видите отново линкът за покана в подробностите за връзката.</target>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can view your reports in Chat with admins." xml:space="preserve">
<source>You can view your reports in Chat with admins.</source>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can't send messages!" xml:space="preserve">
<source>You can't send messages!</source>
<target>Не може да изпращате съобщения!</target>
@ -8635,6 +8720,10 @@ Repeat connection request?</source>
<target>по-горе, след това избери:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="accepted %@" xml:space="preserve">
<source>accepted %@</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="accepted call" xml:space="preserve">
<source>accepted call</source>
<target>обаждането прието</target>
@ -8644,6 +8733,10 @@ Repeat connection request?</source>
<source>accepted invitation</source>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="accepted you" xml:space="preserve">
<source>accepted you</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="admin" xml:space="preserve">
<source>admin</source>
<target>админ</target>
@ -8664,6 +8757,10 @@ Repeat connection request?</source>
<target>съгласуване на криптиране…</target>
<note>chat item text</note>
</trans-unit>
<trans-unit id="all" xml:space="preserve">
<source>all</source>
<note>member criteria value</note>
</trans-unit>
<trans-unit id="all members" xml:space="preserve">
<source>all members</source>
<target>всички членове</target>
@ -8747,6 +8844,10 @@ marked deleted chat item preview text</note>
<target>повикване…</target>
<note>call status</note>
</trans-unit>
<trans-unit id="can't send messages" xml:space="preserve">
<source>can't send messages</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="cancelled %@" xml:space="preserve">
<source>cancelled %@</source>
<target>отменен %@</target>
@ -8852,6 +8953,14 @@ marked deleted chat item preview text</note>
<target>името на контакта %1$@ е променено на %2$@</target>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="contact deleted" xml:space="preserve">
<source>contact deleted</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact disabled" xml:space="preserve">
<source>contact disabled</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact has e2e encryption" xml:space="preserve">
<source>contact has e2e encryption</source>
<target>контактът има e2e криптиране</target>
@ -8862,6 +8971,10 @@ marked deleted chat item preview text</note>
<target>контактът няма e2e криптиране</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact not ready" xml:space="preserve">
<source>contact not ready</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="creator" xml:space="preserve">
<source>creator</source>
<target>създател</target>
@ -9030,6 +9143,10 @@ pref value</note>
<target>групата е изтрита</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group is deleted" xml:space="preserve">
<source>group is deleted</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group profile updated" xml:space="preserve">
<source>group profile updated</source>
<target>профилът на групата е актуализиран</target>
@ -9153,6 +9270,10 @@ pref value</note>
<target>свързан</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="member has old version" xml:space="preserve">
<source>member has old version</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="message" xml:space="preserve">
<source>message</source>
<note>No comment provided by engineer.</note>
@ -9216,6 +9337,10 @@ pref value</note>
<target>няма текст</target>
<note>copied message info in history</note>
</trans-unit>
<trans-unit id="not synchronized" xml:space="preserve">
<source>not synchronized</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="observer" xml:space="preserve">
<source>observer</source>
<target>наблюдател</target>
@ -9226,6 +9351,7 @@ pref value</note>
<target>изключено</target>
<note>enabled status
group pref value
member criteria value
time to disappear</note>
</trans-unit>
<trans-unit id="offered %@" xml:space="preserve">
@ -9274,6 +9400,10 @@ time to disappear</note>
<source>pending approval</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="pending review" xml:space="preserve">
<source>pending review</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="quantum resistant e2e encryption" xml:space="preserve">
<source>quantum resistant e2e encryption</source>
<target>квантово устойчиво e2e криптиране</target>
@ -9313,6 +9443,10 @@ time to disappear</note>
<target>премахнат адрес за контакт</target>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="removed from group" xml:space="preserve">
<source>removed from group</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="removed profile picture" xml:space="preserve">
<source>removed profile picture</source>
<target>премахната профилна снимка</target>
@ -9323,10 +9457,22 @@ time to disappear</note>
<target>ви острани</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="request to join rejected" xml:space="preserve">
<source>request to join rejected</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="requested to connect" xml:space="preserve">
<source>requested to connect</source>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="review" xml:space="preserve">
<source>review</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="reviewed by admins" xml:space="preserve">
<source>reviewed by admins</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="saved" xml:space="preserve">
<source>saved</source>
<target>запазено</target>
@ -9508,6 +9654,10 @@ last received msg: %2$@</source>
<target>вие</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="you accepted this member" xml:space="preserve">
<source>you accepted this member</source>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="you are invited to group" xml:space="preserve">
<source>you are invited to group</source>
<target>вие сте поканени в групата</target>

View file

@ -544,6 +544,14 @@ time interval</note>
accept incoming call via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept as member" xml:space="preserve">
<source>Accept as member</source>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept as observer" xml:space="preserve">
<source>Accept as observer</source>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept conditions" xml:space="preserve">
<source>Accept conditions</source>
<note>No comment provided by engineer.</note>
@ -564,6 +572,10 @@ swipe action</note>
<note>accept contact request via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept member" xml:space="preserve">
<source>Accept member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Accepted conditions" xml:space="preserve">
<source>Accepted conditions</source>
<note>No comment provided by engineer.</note>
@ -1456,11 +1468,23 @@ set passcode view</note>
<source>Chat will be deleted for you - this cannot be undone!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat with admins" xml:space="preserve">
<source>Chat with admins</source>
<note>chat toolbar</note>
</trans-unit>
<trans-unit id="Chat with member" xml:space="preserve">
<source>Chat with member</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats" xml:space="preserve">
<source>Chats</source>
<target>Chaty</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats with members" xml:space="preserve">
<source>Chats with members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Check messages every 20 min." xml:space="preserve">
<source>Check messages every 20 min.</source>
<note>No comment provided by engineer.</note>
@ -2193,6 +2217,10 @@ swipe action</note>
<target>Smazat chat profil?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete chat with member?" xml:space="preserve">
<source>Delete chat with member?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Delete chat?" xml:space="preserve">
<source>Delete chat?</source>
<note>No comment provided by engineer.</note>
@ -2584,7 +2612,7 @@ swipe action</note>
<trans-unit id="Don't show again" xml:space="preserve">
<source>Don't show again</source>
<target>Znovu neukazuj</target>
<note>No comment provided by engineer.</note>
<note>alert action</note>
</trans-unit>
<trans-unit id="Done" xml:space="preserve">
<source>Done</source>
@ -2878,6 +2906,10 @@ chat item action</note>
<target>Chyba při přijímání žádosti o kontakt</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error accepting member" xml:space="preserve">
<source>Error accepting member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error adding member(s)" xml:space="preserve">
<source>Error adding member(s)</source>
<target>Chyba přidávání člena(ů)</target>
@ -2965,6 +2997,10 @@ chat item action</note>
<target>Chyba při mazání databáze chatu</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error deleting chat with member" xml:space="preserve">
<source>Error deleting chat with member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error deleting chat!" xml:space="preserve">
<source>Error deleting chat!</source>
<target>Chyba při mazání chatu!</target>
@ -3065,7 +3101,7 @@ chat item action</note>
<trans-unit id="Error removing member" xml:space="preserve">
<source>Error removing member</source>
<target>Chyba při odebrání člena</target>
<note>No comment provided by engineer.</note>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error reordering lists" xml:space="preserve">
<source>Error reordering lists</source>
@ -4333,6 +4369,10 @@ This is your link for group %@!</source>
<target>Člen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member admission" xml:space="preserve">
<source>Member admission</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member inactive" xml:space="preserve">
<source>Member inactive</source>
<note>item status text</note>
@ -4364,6 +4404,10 @@ This is your link for group %@!</source>
<target>Člen bude odstraněn ze skupiny - toto nelze vzít zpět!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member will join the group, accept member?" xml:space="preserve">
<source>Member will join the group, accept member?</source>
<note>alert message</note>
</trans-unit>
<trans-unit id="Members can add message reactions." xml:space="preserve">
<source>Members can add message reactions.</source>
<target>Členové skupin mohou přidávat reakce na zprávy.</target>
@ -4739,6 +4783,10 @@ This is your link for group %@!</source>
<target>Nová role člena</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New member wants to join the group." xml:space="preserve">
<source>New member wants to join the group.</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="New message" xml:space="preserve">
<source>New message</source>
<target>Nová zpráva</target>
@ -4775,6 +4823,10 @@ This is your link for group %@!</source>
<source>No chats in list %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No chats with members" xml:space="preserve">
<source>No chats with members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No contacts selected" xml:space="preserve">
<source>No contacts selected</source>
<target>Nebyl vybrán žádný kontakt</target>
@ -4945,7 +4997,8 @@ This is your link for group %@!</source>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
<target>Ok</target>
<note>alert button</note>
<note>alert action
alert button</note>
</trans-unit>
<trans-unit id="Old database" xml:space="preserve">
<source>Old database</source>
@ -5320,6 +5373,10 @@ Error: %@</source>
<source>Please try to disable and re-enable notfications.</source>
<note>token info</note>
</trans-unit>
<trans-unit id="Please wait for group moderators to review your request to join the group." xml:space="preserve">
<source>Please wait for group moderators to review your request to join the group.</source>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="Please wait for token activation to complete." xml:space="preserve">
<source>Please wait for token activation to complete.</source>
<note>token info</note>
@ -5731,6 +5788,10 @@ swipe action</note>
<target>Odmítnout žádost o kontakt</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Reject member?" xml:space="preserve">
<source>Reject member?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Relay server is only used if necessary. Another party can observe your IP address." xml:space="preserve">
<source>Relay server is only used if necessary. Another party can observe your IP address.</source>
<target>Přenosový server se používá pouze v případě potřeby. Jiná strana může sledovat vaši IP adresu.</target>
@ -5829,6 +5890,10 @@ swipe action</note>
<source>Report reason?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Report sent to moderators" xml:space="preserve">
<source>Report sent to moderators</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Report spam: only group moderators will see it." xml:space="preserve">
<source>Report spam: only group moderators will see it.</source>
<note>report reason</note>
@ -5932,6 +5997,14 @@ swipe action</note>
<source>Review conditions</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Review members" xml:space="preserve">
<source>Review members</source>
<note>admission stage</note>
</trans-unit>
<trans-unit id="Review members before admitting (&quot;knocking&quot;)." xml:space="preserve">
<source>Review members before admitting ("knocking").</source>
<note>admission stage description</note>
</trans-unit>
<trans-unit id="Revoke" xml:space="preserve">
<source>Revoke</source>
<target>Odvolat</target>
@ -5984,6 +6057,10 @@ chat item action</note>
<target>Uložit (a informovat kontakty)</target>
<note>alert button</note>
</trans-unit>
<trans-unit id="Save admission settings?" xml:space="preserve">
<source>Save admission settings?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Save and notify contact" xml:space="preserve">
<source>Save and notify contact</source>
<target>Uložit a upozornit kontakt</target>
@ -6455,6 +6532,10 @@ chat item action</note>
<target>Nastavte jej namísto ověřování systému.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set member admission" xml:space="preserve">
<source>Set member admission</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set message expiration in chats." xml:space="preserve">
<source>Set message expiration in chats.</source>
<note>No comment provided by engineer.</note>
@ -8044,6 +8125,10 @@ Repeat join request?</source>
<source>You can view invitation link again in connection details.</source>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can view your reports in Chat with admins." xml:space="preserve">
<source>You can view your reports in Chat with admins.</source>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can't send messages!" xml:space="preserve">
<source>You can't send messages!</source>
<target>Nemůžete posílat zprávy!</target>
@ -8330,6 +8415,10 @@ Repeat connection request?</source>
<target>výše, pak vyberte:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="accepted %@" xml:space="preserve">
<source>accepted %@</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="accepted call" xml:space="preserve">
<source>accepted call</source>
<target>přijatý hovor</target>
@ -8339,6 +8428,10 @@ Repeat connection request?</source>
<source>accepted invitation</source>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="accepted you" xml:space="preserve">
<source>accepted you</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="admin" xml:space="preserve">
<source>admin</source>
<target>správce</target>
@ -8358,6 +8451,10 @@ Repeat connection request?</source>
<target>povoluji šifrování…</target>
<note>chat item text</note>
</trans-unit>
<trans-unit id="all" xml:space="preserve">
<source>all</source>
<note>member criteria value</note>
</trans-unit>
<trans-unit id="all members" xml:space="preserve">
<source>all members</source>
<note>feature role</note>
@ -8435,6 +8532,10 @@ marked deleted chat item preview text</note>
<target>volání…</target>
<note>call status</note>
</trans-unit>
<trans-unit id="can't send messages" xml:space="preserve">
<source>can't send messages</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="cancelled %@" xml:space="preserve">
<source>cancelled %@</source>
<target>zrušeno %@</target>
@ -8539,6 +8640,14 @@ marked deleted chat item preview text</note>
<source>contact %1$@ changed to %2$@</source>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="contact deleted" xml:space="preserve">
<source>contact deleted</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact disabled" xml:space="preserve">
<source>contact disabled</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact has e2e encryption" xml:space="preserve">
<source>contact has e2e encryption</source>
<target>kontakt má šifrování e2e</target>
@ -8549,6 +8658,10 @@ marked deleted chat item preview text</note>
<target>kontakt nemá šifrování e2e</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact not ready" xml:space="preserve">
<source>contact not ready</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="creator" xml:space="preserve">
<source>creator</source>
<target>tvůrce</target>
@ -8715,6 +8828,10 @@ pref value</note>
<target>skupina smazána</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group is deleted" xml:space="preserve">
<source>group is deleted</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group profile updated" xml:space="preserve">
<source>group profile updated</source>
<target>profil skupiny aktualizován</target>
@ -8837,6 +8954,10 @@ pref value</note>
<target>připojeno</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="member has old version" xml:space="preserve">
<source>member has old version</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="message" xml:space="preserve">
<source>message</source>
<note>No comment provided by engineer.</note>
@ -8900,6 +9021,10 @@ pref value</note>
<target>žádný text</target>
<note>copied message info in history</note>
</trans-unit>
<trans-unit id="not synchronized" xml:space="preserve">
<source>not synchronized</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="observer" xml:space="preserve">
<source>observer</source>
<target>pozorovatel</target>
@ -8910,6 +9035,7 @@ pref value</note>
<target>vypnuto</target>
<note>enabled status
group pref value
member criteria value
time to disappear</note>
</trans-unit>
<trans-unit id="offered %@" xml:space="preserve">
@ -8957,6 +9083,10 @@ time to disappear</note>
<source>pending approval</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="pending review" xml:space="preserve">
<source>pending review</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="quantum resistant e2e encryption" xml:space="preserve">
<source>quantum resistant e2e encryption</source>
<note>chat item text</note>
@ -8994,6 +9124,10 @@ time to disappear</note>
<source>removed contact address</source>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="removed from group" xml:space="preserve">
<source>removed from group</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="removed profile picture" xml:space="preserve">
<source>removed profile picture</source>
<note>profile update event chat item</note>
@ -9003,10 +9137,22 @@ time to disappear</note>
<target>odstranil vás</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="request to join rejected" xml:space="preserve">
<source>request to join rejected</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="requested to connect" xml:space="preserve">
<source>requested to connect</source>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="review" xml:space="preserve">
<source>review</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="reviewed by admins" xml:space="preserve">
<source>reviewed by admins</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="saved" xml:space="preserve">
<source>saved</source>
<note>No comment provided by engineer.</note>
@ -9178,6 +9324,10 @@ last received msg: %2$@</source>
<source>you</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="you accepted this member" xml:space="preserve">
<source>you accepted this member</source>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="you are invited to group" xml:space="preserve">
<source>you are invited to group</source>
<target>jste pozváni do skupiny</target>

View file

@ -565,6 +565,16 @@ time interval</note>
accept incoming call via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept as member" xml:space="preserve">
<source>Accept as member</source>
<target>Als Mitglied übernehmen</target>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept as observer" xml:space="preserve">
<source>Accept as observer</source>
<target>Als Beobachter übernehmen</target>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept conditions" xml:space="preserve">
<source>Accept conditions</source>
<target>Nutzungsbedingungen akzeptieren</target>
@ -586,6 +596,11 @@ swipe action</note>
<note>accept contact request via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept member" xml:space="preserve">
<source>Accept member</source>
<target>Mitglied übernehmen</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Accepted conditions" xml:space="preserve">
<source>Accepted conditions</source>
<target>Akzeptierte Nutzungsbedingungen</target>
@ -1582,11 +1597,26 @@ set passcode view</note>
<target>Der Chat wird für Sie gelöscht. Dies kann nicht rückgängig gemacht werden!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat with admins" xml:space="preserve">
<source>Chat with admins</source>
<target>Chat mit Administratoren</target>
<note>chat toolbar</note>
</trans-unit>
<trans-unit id="Chat with member" xml:space="preserve">
<source>Chat with member</source>
<target>Chat mit einem Mitglied</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats" xml:space="preserve">
<source>Chats</source>
<target>Chats</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats with members" xml:space="preserve">
<source>Chats with members</source>
<target>Chats mit Mitgliedern</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Check messages every 20 min." xml:space="preserve">
<source>Check messages every 20 min.</source>
<target>Alle 20min Nachrichten überprüfen.</target>
@ -2402,6 +2432,11 @@ swipe action</note>
<target>Chat-Profil löschen?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete chat with member?" xml:space="preserve">
<source>Delete chat with member?</source>
<target>Chat mit dem Mitglied löschen?</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Delete chat?" xml:space="preserve">
<source>Delete chat?</source>
<target>Chat löschen?</target>
@ -2825,7 +2860,7 @@ swipe action</note>
<trans-unit id="Don't show again" xml:space="preserve">
<source>Don't show again</source>
<target>Nicht nochmals anzeigen</target>
<note>No comment provided by engineer.</note>
<note>alert action</note>
</trans-unit>
<trans-unit id="Done" xml:space="preserve">
<source>Done</source>
@ -3143,6 +3178,11 @@ chat item action</note>
<target>Fehler beim Annehmen der Kontaktanfrage</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error accepting member" xml:space="preserve">
<source>Error accepting member</source>
<target>Fehler beim Übernehmen des Mitglieds</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error adding member(s)" xml:space="preserve">
<source>Error adding member(s)</source>
<target>Fehler beim Hinzufügen von Mitgliedern</target>
@ -3238,6 +3278,11 @@ chat item action</note>
<target>Fehler beim Löschen der Chat-Datenbank</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error deleting chat with member" xml:space="preserve">
<source>Error deleting chat with member</source>
<target>Fehler beim Löschen des Chats mit dem Mitglied</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error deleting chat!" xml:space="preserve">
<source>Error deleting chat!</source>
<target>Fehler beim Löschen des Chats!</target>
@ -3346,7 +3391,7 @@ chat item action</note>
<trans-unit id="Error removing member" xml:space="preserve">
<source>Error removing member</source>
<target>Fehler beim Entfernen des Mitglieds</target>
<note>No comment provided by engineer.</note>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error reordering lists" xml:space="preserve">
<source>Error reordering lists</source>
@ -4730,6 +4775,11 @@ Das ist Ihr Link für die Gruppe %@!</target>
<target>Mitglied</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member admission" xml:space="preserve">
<source>Member admission</source>
<target>Aufnahme von Mitgliedern</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member inactive" xml:space="preserve">
<source>Member inactive</source>
<target>Mitglied inaktiv</target>
@ -4765,6 +4815,11 @@ Das ist Ihr Link für die Gruppe %@!</target>
<target>Das Mitglied wird aus der Gruppe entfernt. Dies kann nicht rückgängig gemacht werden!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member will join the group, accept member?" xml:space="preserve">
<source>Member will join the group, accept member?</source>
<target>Ein Mitglied wird der Gruppe beitreten. Übernehmen?</target>
<note>alert message</note>
</trans-unit>
<trans-unit id="Members can add message reactions." xml:space="preserve">
<source>Members can add message reactions.</source>
<target>Gruppenmitglieder können eine Reaktion auf Nachrichten geben.</target>
@ -5185,6 +5240,11 @@ Das ist Ihr Link für die Gruppe %@!</target>
<target>Neue Mitgliedsrolle</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New member wants to join the group." xml:space="preserve">
<source>New member wants to join the group.</source>
<target>Ein neues Mitglied will der Gruppe beitreten.</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="New message" xml:space="preserve">
<source>New message</source>
<target>Neue Nachricht</target>
@ -5225,6 +5285,11 @@ Das ist Ihr Link für die Gruppe %@!</target>
<target>Keine Chats in der Liste %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No chats with members" xml:space="preserve">
<source>No chats with members</source>
<target>Keine Chats mit Mitgliedern</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No contacts selected" xml:space="preserve">
<source>No contacts selected</source>
<target>Keine Kontakte ausgewählt</target>
@ -5417,7 +5482,8 @@ Das ist Ihr Link für die Gruppe %@!</target>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
<target>Ok</target>
<note>alert button</note>
<note>alert action
alert button</note>
</trans-unit>
<trans-unit id="Old database" xml:space="preserve">
<source>Old database</source>
@ -5575,6 +5641,7 @@ Dies erfordert die Aktivierung eines VPNs.</target>
</trans-unit>
<trans-unit id="Open link?" xml:space="preserve">
<source>Open link?</source>
<target>Link öffnen?</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Open migration to another device" xml:space="preserve">
@ -5828,6 +5895,11 @@ Fehler: %@</target>
<target>Bitte versuchen Sie, die Benachrichtigungen zu deaktivieren und wieder zu aktivieren.</target>
<note>token info</note>
</trans-unit>
<trans-unit id="Please wait for group moderators to review your request to join the group." xml:space="preserve">
<source>Please wait for group moderators to review your request to join the group.</source>
<target>Bitte warten Sie auf die Überprüfung Ihrer Anfrage durch die Gruppen-Moderatoren, um der Gruppe beitreten zu können.</target>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="Please wait for token activation to complete." xml:space="preserve">
<source>Please wait for token activation to complete.</source>
<target>Bitte warten Sie, bis die Token-Aktivierung abgeschlossen ist.</target>
@ -6281,6 +6353,11 @@ swipe action</note>
<target>Kontaktanfrage ablehnen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Reject member?" xml:space="preserve">
<source>Reject member?</source>
<target>Mitglied ablehnen?</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Relay server is only used if necessary. Another party can observe your IP address." xml:space="preserve">
<source>Relay server is only used if necessary. Another party can observe your IP address.</source>
<target>Relais-Server werden nur genutzt, wenn sie benötigt werden. Ihre IP-Adresse kann von Anderen erfasst werden.</target>
@ -6391,6 +6468,11 @@ swipe action</note>
<target>Grund der Meldung?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Report sent to moderators" xml:space="preserve">
<source>Report sent to moderators</source>
<target>Meldung wurde an die Moderatoren gesendet</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Report spam: only group moderators will see it." xml:space="preserve">
<source>Report spam: only group moderators will see it.</source>
<target>Spam melden: Nur Gruppenmoderatoren werden es sehen.</target>
@ -6506,6 +6588,16 @@ swipe action</note>
<target>Nutzungsbedingungen einsehen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Review members" xml:space="preserve">
<source>Review members</source>
<target>Überprüfung der Mitglieder</target>
<note>admission stage</note>
</trans-unit>
<trans-unit id="Review members before admitting (&quot;knocking&quot;)." xml:space="preserve">
<source>Review members before admitting ("knocking").</source>
<target>Überprüfung der Mitglieder vor der Aufnahme ("Anklopfen").</target>
<note>admission stage description</note>
</trans-unit>
<trans-unit id="Revoke" xml:space="preserve">
<source>Revoke</source>
<target>Widerrufen</target>
@ -6562,6 +6654,11 @@ chat item action</note>
<target>Speichern (und Kontakte benachrichtigen)</target>
<note>alert button</note>
</trans-unit>
<trans-unit id="Save admission settings?" xml:space="preserve">
<source>Save admission settings?</source>
<target>Speichern der Aufnahme-Einstellungen?</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Save and notify contact" xml:space="preserve">
<source>Save and notify contact</source>
<target>Speichern und Kontakt benachrichtigen</target>
@ -7077,6 +7174,11 @@ chat item action</note>
<target>Anstelle der System-Authentifizierung festlegen.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set member admission" xml:space="preserve">
<source>Set member admission</source>
<target>Aufnahme von Mitgliedern festlegen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set message expiration in chats." xml:space="preserve">
<source>Set message expiration in chats.</source>
<target>Verfallsdatum von Nachrichten in Chats festlegen.</target>
@ -8839,6 +8941,11 @@ Verbindungsanfrage wiederholen?</target>
<target>Den Einladungslink können Sie in den Details der Verbindung nochmals sehen.</target>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can view your reports in Chat with admins." xml:space="preserve">
<source>You can view your reports in Chat with admins.</source>
<target>Sie können Ihre Meldungen im Chat mit den Administratoren sehen.</target>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can't send messages!" xml:space="preserve">
<source>You can't send messages!</source>
<target>Sie können keine Nachrichten versenden!</target>
@ -9078,7 +9185,7 @@ Verbindungsanfrage wiederholen?</target>
</trans-unit>
<trans-unit id="Your profile is stored on your device and only shared with your contacts." xml:space="preserve">
<source>Your profile is stored on your device and only shared with your contacts.</source>
<target>Das Profil wird nur mit Ihren Kontakten geteilt.</target>
<target>Ihr Profil wird auf Ihrem Gerät gespeichert und nur mit Ihren Kontakten geteilt.</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. SimpleX servers cannot see your profile." xml:space="preserve">
@ -9141,6 +9248,11 @@ Verbindungsanfrage wiederholen?</target>
<target>Danach die gewünschte Aktion auswählen:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="accepted %@" xml:space="preserve">
<source>accepted %@</source>
<target>%@ übernommen</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="accepted call" xml:space="preserve">
<source>accepted call</source>
<target>Anruf angenommen</target>
@ -9151,6 +9263,11 @@ Verbindungsanfrage wiederholen?</target>
<target>Einladung angenommen</target>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="accepted you" xml:space="preserve">
<source>accepted you</source>
<target>hat Sie übernommen</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="admin" xml:space="preserve">
<source>admin</source>
<target>Admin</target>
@ -9171,6 +9288,11 @@ Verbindungsanfrage wiederholen?</target>
<target>Verschlüsselung zustimmen…</target>
<note>chat item text</note>
</trans-unit>
<trans-unit id="all" xml:space="preserve">
<source>all</source>
<target>alle</target>
<note>member criteria value</note>
</trans-unit>
<trans-unit id="all members" xml:space="preserve">
<source>all members</source>
<target>Alle Mitglieder</target>
@ -9257,6 +9379,11 @@ marked deleted chat item preview text</note>
<target>Anrufen…</target>
<note>call status</note>
</trans-unit>
<trans-unit id="can't send messages" xml:space="preserve">
<source>can't send messages</source>
<target>Es können keine Nachrichten gesendet werden</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="cancelled %@" xml:space="preserve">
<source>cancelled %@</source>
<target>abgebrochen %@</target>
@ -9362,6 +9489,16 @@ marked deleted chat item preview text</note>
<target>Der Kontaktname wurde von %1$@ auf %2$@ geändert</target>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="contact deleted" xml:space="preserve">
<source>contact deleted</source>
<target>Kontakt gelöscht</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact disabled" xml:space="preserve">
<source>contact disabled</source>
<target>Kontakt deaktiviert</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact has e2e encryption" xml:space="preserve">
<source>contact has e2e encryption</source>
<target>Kontakt nutzt E2E-Verschlüsselung</target>
@ -9372,6 +9509,11 @@ marked deleted chat item preview text</note>
<target>Kontakt nutzt keine E2E-Verschlüsselung</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact not ready" xml:space="preserve">
<source>contact not ready</source>
<target>Kontakt nicht bereit</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="creator" xml:space="preserve">
<source>creator</source>
<target>Ersteller</target>
@ -9543,6 +9685,11 @@ pref value</note>
<target>Gruppe gelöscht</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group is deleted" xml:space="preserve">
<source>group is deleted</source>
<target>Gruppe wird gelöscht</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group profile updated" xml:space="preserve">
<source>group profile updated</source>
<target>Gruppenprofil aktualisiert</target>
@ -9668,6 +9815,11 @@ pref value</note>
<target>ist der Gruppe beigetreten</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="member has old version" xml:space="preserve">
<source>member has old version</source>
<target>Das Mitglied hat eine alte App-Version</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="message" xml:space="preserve">
<source>message</source>
<target>Nachricht</target>
@ -9733,6 +9885,11 @@ pref value</note>
<target>Kein Text</target>
<note>copied message info in history</note>
</trans-unit>
<trans-unit id="not synchronized" xml:space="preserve">
<source>not synchronized</source>
<target>Nicht synchronisiert</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="observer" xml:space="preserve">
<source>observer</source>
<target>Beobachter</target>
@ -9743,6 +9900,7 @@ pref value</note>
<target>Aus</target>
<note>enabled status
group pref value
member criteria value
time to disappear</note>
</trans-unit>
<trans-unit id="offered %@" xml:space="preserve">
@ -9795,6 +9953,11 @@ time to disappear</note>
<target>ausstehende Genehmigung</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="pending review" xml:space="preserve">
<source>pending review</source>
<target>Ausstehende Überprüfung</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="quantum resistant e2e encryption" xml:space="preserve">
<source>quantum resistant e2e encryption</source>
<target>Quantum-resistente E2E-Verschlüsselung</target>
@ -9835,6 +9998,11 @@ time to disappear</note>
<target>Die Kontaktadresse wurde entfernt</target>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="removed from group" xml:space="preserve">
<source>removed from group</source>
<target>Von der Gruppe entfernt</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="removed profile picture" xml:space="preserve">
<source>removed profile picture</source>
<target>Das Profil-Bild wurde entfernt</target>
@ -9845,11 +10013,26 @@ time to disappear</note>
<target>hat Sie aus der Gruppe entfernt</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="request to join rejected" xml:space="preserve">
<source>request to join rejected</source>
<target>Beitrittsanfrage abgelehnt</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="requested to connect" xml:space="preserve">
<source>requested to connect</source>
<target>Zur Verbindung aufgefordert</target>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="review" xml:space="preserve">
<source>review</source>
<target>Überprüfung</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="reviewed by admins" xml:space="preserve">
<source>reviewed by admins</source>
<target>Von Administratoren überprüft</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="saved" xml:space="preserve">
<source>saved</source>
<target>abgespeichert</target>
@ -10039,6 +10222,11 @@ Zuletzt empfangene Nachricht: %2$@</target>
<target>Profil</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="you accepted this member" xml:space="preserve">
<source>you accepted this member</source>
<target>Sie haben dieses Mitglied übernommen</target>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="you are invited to group" xml:space="preserve">
<source>you are invited to group</source>
<target>Sie sind zu der Gruppe eingeladen</target>

View file

@ -565,6 +565,16 @@ time interval</note>
accept incoming call via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept as member" xml:space="preserve">
<source>Accept as member</source>
<target>Accept as member</target>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept as observer" xml:space="preserve">
<source>Accept as observer</source>
<target>Accept as observer</target>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept conditions" xml:space="preserve">
<source>Accept conditions</source>
<target>Accept conditions</target>
@ -586,6 +596,11 @@ swipe action</note>
<note>accept contact request via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept member" xml:space="preserve">
<source>Accept member</source>
<target>Accept member</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Accepted conditions" xml:space="preserve">
<source>Accepted conditions</source>
<target>Accepted conditions</target>
@ -1582,11 +1597,26 @@ set passcode view</note>
<target>Chat will be deleted for you - this cannot be undone!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat with admins" xml:space="preserve">
<source>Chat with admins</source>
<target>Chat with admins</target>
<note>chat toolbar</note>
</trans-unit>
<trans-unit id="Chat with member" xml:space="preserve">
<source>Chat with member</source>
<target>Chat with member</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats" xml:space="preserve">
<source>Chats</source>
<target>Chats</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats with members" xml:space="preserve">
<source>Chats with members</source>
<target>Chats with members</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Check messages every 20 min." xml:space="preserve">
<source>Check messages every 20 min.</source>
<target>Check messages every 20 min.</target>
@ -2402,6 +2432,11 @@ swipe action</note>
<target>Delete chat profile?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete chat with member?" xml:space="preserve">
<source>Delete chat with member?</source>
<target>Delete chat with member?</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Delete chat?" xml:space="preserve">
<source>Delete chat?</source>
<target>Delete chat?</target>
@ -2825,7 +2860,7 @@ swipe action</note>
<trans-unit id="Don't show again" xml:space="preserve">
<source>Don't show again</source>
<target>Don't show again</target>
<note>No comment provided by engineer.</note>
<note>alert action</note>
</trans-unit>
<trans-unit id="Done" xml:space="preserve">
<source>Done</source>
@ -3143,6 +3178,11 @@ chat item action</note>
<target>Error accepting contact request</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error accepting member" xml:space="preserve">
<source>Error accepting member</source>
<target>Error accepting member</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error adding member(s)" xml:space="preserve">
<source>Error adding member(s)</source>
<target>Error adding member(s)</target>
@ -3238,6 +3278,11 @@ chat item action</note>
<target>Error deleting chat database</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error deleting chat with member" xml:space="preserve">
<source>Error deleting chat with member</source>
<target>Error deleting chat with member</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error deleting chat!" xml:space="preserve">
<source>Error deleting chat!</source>
<target>Error deleting chat!</target>
@ -3346,7 +3391,7 @@ chat item action</note>
<trans-unit id="Error removing member" xml:space="preserve">
<source>Error removing member</source>
<target>Error removing member</target>
<note>No comment provided by engineer.</note>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error reordering lists" xml:space="preserve">
<source>Error reordering lists</source>
@ -4730,6 +4775,11 @@ This is your link for group %@!</target>
<target>Member</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member admission" xml:space="preserve">
<source>Member admission</source>
<target>Member admission</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member inactive" xml:space="preserve">
<source>Member inactive</source>
<target>Member inactive</target>
@ -4765,6 +4815,11 @@ This is your link for group %@!</target>
<target>Member will be removed from group - this cannot be undone!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member will join the group, accept member?" xml:space="preserve">
<source>Member will join the group, accept member?</source>
<target>Member will join the group, accept member?</target>
<note>alert message</note>
</trans-unit>
<trans-unit id="Members can add message reactions." xml:space="preserve">
<source>Members can add message reactions.</source>
<target>Members can add message reactions.</target>
@ -5185,6 +5240,11 @@ This is your link for group %@!</target>
<target>New member role</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New member wants to join the group." xml:space="preserve">
<source>New member wants to join the group.</source>
<target>New member wants to join the group.</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="New message" xml:space="preserve">
<source>New message</source>
<target>New message</target>
@ -5225,6 +5285,11 @@ This is your link for group %@!</target>
<target>No chats in list %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No chats with members" xml:space="preserve">
<source>No chats with members</source>
<target>No chats with members</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No contacts selected" xml:space="preserve">
<source>No contacts selected</source>
<target>No contacts selected</target>
@ -5417,7 +5482,8 @@ This is your link for group %@!</target>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
<target>Ok</target>
<note>alert button</note>
<note>alert action
alert button</note>
</trans-unit>
<trans-unit id="Old database" xml:space="preserve">
<source>Old database</source>
@ -5829,6 +5895,11 @@ Error: %@</target>
<target>Please try to disable and re-enable notfications.</target>
<note>token info</note>
</trans-unit>
<trans-unit id="Please wait for group moderators to review your request to join the group." xml:space="preserve">
<source>Please wait for group moderators to review your request to join the group.</source>
<target>Please wait for group moderators to review your request to join the group.</target>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="Please wait for token activation to complete." xml:space="preserve">
<source>Please wait for token activation to complete.</source>
<target>Please wait for token activation to complete.</target>
@ -6282,6 +6353,11 @@ swipe action</note>
<target>Reject contact request</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Reject member?" xml:space="preserve">
<source>Reject member?</source>
<target>Reject member?</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Relay server is only used if necessary. Another party can observe your IP address." xml:space="preserve">
<source>Relay server is only used if necessary. Another party can observe your IP address.</source>
<target>Relay server is only used if necessary. Another party can observe your IP address.</target>
@ -6392,6 +6468,11 @@ swipe action</note>
<target>Report reason?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Report sent to moderators" xml:space="preserve">
<source>Report sent to moderators</source>
<target>Report sent to moderators</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Report spam: only group moderators will see it." xml:space="preserve">
<source>Report spam: only group moderators will see it.</source>
<target>Report spam: only group moderators will see it.</target>
@ -6507,6 +6588,16 @@ swipe action</note>
<target>Review conditions</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Review members" xml:space="preserve">
<source>Review members</source>
<target>Review members</target>
<note>admission stage</note>
</trans-unit>
<trans-unit id="Review members before admitting (&quot;knocking&quot;)." xml:space="preserve">
<source>Review members before admitting ("knocking").</source>
<target>Review members before admitting ("knocking").</target>
<note>admission stage description</note>
</trans-unit>
<trans-unit id="Revoke" xml:space="preserve">
<source>Revoke</source>
<target>Revoke</target>
@ -6563,6 +6654,11 @@ chat item action</note>
<target>Save (and notify contacts)</target>
<note>alert button</note>
</trans-unit>
<trans-unit id="Save admission settings?" xml:space="preserve">
<source>Save admission settings?</source>
<target>Save admission settings?</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Save and notify contact" xml:space="preserve">
<source>Save and notify contact</source>
<target>Save and notify contact</target>
@ -7078,6 +7174,11 @@ chat item action</note>
<target>Set it instead of system authentication.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set member admission" xml:space="preserve">
<source>Set member admission</source>
<target>Set member admission</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set message expiration in chats." xml:space="preserve">
<source>Set message expiration in chats.</source>
<target>Set message expiration in chats.</target>
@ -8840,6 +8941,11 @@ Repeat join request?</target>
<target>You can view invitation link again in connection details.</target>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can view your reports in Chat with admins." xml:space="preserve">
<source>You can view your reports in Chat with admins.</source>
<target>You can view your reports in Chat with admins.</target>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can't send messages!" xml:space="preserve">
<source>You can't send messages!</source>
<target>You can't send messages!</target>
@ -9142,6 +9248,11 @@ Repeat connection request?</target>
<target>above, then choose:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="accepted %@" xml:space="preserve">
<source>accepted %@</source>
<target>accepted %@</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="accepted call" xml:space="preserve">
<source>accepted call</source>
<target>accepted call</target>
@ -9152,6 +9263,11 @@ Repeat connection request?</target>
<target>accepted invitation</target>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="accepted you" xml:space="preserve">
<source>accepted you</source>
<target>accepted you</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="admin" xml:space="preserve">
<source>admin</source>
<target>admin</target>
@ -9172,6 +9288,11 @@ Repeat connection request?</target>
<target>agreeing encryption…</target>
<note>chat item text</note>
</trans-unit>
<trans-unit id="all" xml:space="preserve">
<source>all</source>
<target>all</target>
<note>member criteria value</note>
</trans-unit>
<trans-unit id="all members" xml:space="preserve">
<source>all members</source>
<target>all members</target>
@ -9258,6 +9379,11 @@ marked deleted chat item preview text</note>
<target>calling…</target>
<note>call status</note>
</trans-unit>
<trans-unit id="can't send messages" xml:space="preserve">
<source>can't send messages</source>
<target>can't send messages</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="cancelled %@" xml:space="preserve">
<source>cancelled %@</source>
<target>cancelled %@</target>
@ -9363,6 +9489,16 @@ marked deleted chat item preview text</note>
<target>contact %1$@ changed to %2$@</target>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="contact deleted" xml:space="preserve">
<source>contact deleted</source>
<target>contact deleted</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact disabled" xml:space="preserve">
<source>contact disabled</source>
<target>contact disabled</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact has e2e encryption" xml:space="preserve">
<source>contact has e2e encryption</source>
<target>contact has e2e encryption</target>
@ -9373,6 +9509,11 @@ marked deleted chat item preview text</note>
<target>contact has no e2e encryption</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact not ready" xml:space="preserve">
<source>contact not ready</source>
<target>contact not ready</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="creator" xml:space="preserve">
<source>creator</source>
<target>creator</target>
@ -9544,6 +9685,11 @@ pref value</note>
<target>group deleted</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group is deleted" xml:space="preserve">
<source>group is deleted</source>
<target>group is deleted</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group profile updated" xml:space="preserve">
<source>group profile updated</source>
<target>group profile updated</target>
@ -9669,6 +9815,11 @@ pref value</note>
<target>connected</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="member has old version" xml:space="preserve">
<source>member has old version</source>
<target>member has old version</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="message" xml:space="preserve">
<source>message</source>
<target>message</target>
@ -9734,6 +9885,11 @@ pref value</note>
<target>no text</target>
<note>copied message info in history</note>
</trans-unit>
<trans-unit id="not synchronized" xml:space="preserve">
<source>not synchronized</source>
<target>not synchronized</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="observer" xml:space="preserve">
<source>observer</source>
<target>observer</target>
@ -9744,6 +9900,7 @@ pref value</note>
<target>off</target>
<note>enabled status
group pref value
member criteria value
time to disappear</note>
</trans-unit>
<trans-unit id="offered %@" xml:space="preserve">
@ -9796,6 +9953,11 @@ time to disappear</note>
<target>pending approval</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="pending review" xml:space="preserve">
<source>pending review</source>
<target>pending review</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="quantum resistant e2e encryption" xml:space="preserve">
<source>quantum resistant e2e encryption</source>
<target>quantum resistant e2e encryption</target>
@ -9836,6 +9998,11 @@ time to disappear</note>
<target>removed contact address</target>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="removed from group" xml:space="preserve">
<source>removed from group</source>
<target>removed from group</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="removed profile picture" xml:space="preserve">
<source>removed profile picture</source>
<target>removed profile picture</target>
@ -9846,11 +10013,26 @@ time to disappear</note>
<target>removed you</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="request to join rejected" xml:space="preserve">
<source>request to join rejected</source>
<target>request to join rejected</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="requested to connect" xml:space="preserve">
<source>requested to connect</source>
<target>requested to connect</target>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="review" xml:space="preserve">
<source>review</source>
<target>review</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="reviewed by admins" xml:space="preserve">
<source>reviewed by admins</source>
<target>reviewed by admins</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="saved" xml:space="preserve">
<source>saved</source>
<target>saved</target>
@ -10040,6 +10222,11 @@ last received msg: %2$@</target>
<target>you</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="you accepted this member" xml:space="preserve">
<source>you accepted this member</source>
<target>you accepted this member</target>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="you are invited to group" xml:space="preserve">
<source>you are invited to group</source>
<target>you are invited to group</target>

View file

@ -565,6 +565,14 @@ time interval</note>
accept incoming call via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept as member" xml:space="preserve">
<source>Accept as member</source>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept as observer" xml:space="preserve">
<source>Accept as observer</source>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept conditions" xml:space="preserve">
<source>Accept conditions</source>
<target>Aceptar condiciones</target>
@ -586,6 +594,10 @@ swipe action</note>
<note>accept contact request via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept member" xml:space="preserve">
<source>Accept member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Accepted conditions" xml:space="preserve">
<source>Accepted conditions</source>
<target>Condiciones aceptadas</target>
@ -1582,11 +1594,23 @@ set passcode view</note>
<target>El chat será eliminado para tí. ¡No puede deshacerse!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat with admins" xml:space="preserve">
<source>Chat with admins</source>
<note>chat toolbar</note>
</trans-unit>
<trans-unit id="Chat with member" xml:space="preserve">
<source>Chat with member</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats" xml:space="preserve">
<source>Chats</source>
<target>Chats</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats with members" xml:space="preserve">
<source>Chats with members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Check messages every 20 min." xml:space="preserve">
<source>Check messages every 20 min.</source>
<target>Comprobar mensajes cada 20 min.</target>
@ -2402,6 +2426,10 @@ swipe action</note>
<target>¿Eliminar perfil?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete chat with member?" xml:space="preserve">
<source>Delete chat with member?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Delete chat?" xml:space="preserve">
<source>Delete chat?</source>
<target>¿Eliminar chat?</target>
@ -2825,7 +2853,7 @@ swipe action</note>
<trans-unit id="Don't show again" xml:space="preserve">
<source>Don't show again</source>
<target>No volver a mostrar</target>
<note>No comment provided by engineer.</note>
<note>alert action</note>
</trans-unit>
<trans-unit id="Done" xml:space="preserve">
<source>Done</source>
@ -3143,6 +3171,10 @@ chat item action</note>
<target>Error al aceptar solicitud del contacto</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error accepting member" xml:space="preserve">
<source>Error accepting member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error adding member(s)" xml:space="preserve">
<source>Error adding member(s)</source>
<target>Error al añadir miembro(s)</target>
@ -3238,6 +3270,10 @@ chat item action</note>
<target>Error al eliminar base de datos</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error deleting chat with member" xml:space="preserve">
<source>Error deleting chat with member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error deleting chat!" xml:space="preserve">
<source>Error deleting chat!</source>
<target>¡Error al eliminar chat!</target>
@ -3346,7 +3382,7 @@ chat item action</note>
<trans-unit id="Error removing member" xml:space="preserve">
<source>Error removing member</source>
<target>Error al expulsar miembro</target>
<note>No comment provided by engineer.</note>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error reordering lists" xml:space="preserve">
<source>Error reordering lists</source>
@ -4730,6 +4766,10 @@ This is your link for group %@!</source>
<target>Miembro</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member admission" xml:space="preserve">
<source>Member admission</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member inactive" xml:space="preserve">
<source>Member inactive</source>
<target>Miembro inactivo</target>
@ -4765,6 +4805,10 @@ This is your link for group %@!</source>
<target>El miembro será expulsado del grupo. ¡No puede deshacerse!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member will join the group, accept member?" xml:space="preserve">
<source>Member will join the group, accept member?</source>
<note>alert message</note>
</trans-unit>
<trans-unit id="Members can add message reactions." xml:space="preserve">
<source>Members can add message reactions.</source>
<target>Los miembros pueden añadir reacciones a los mensajes.</target>
@ -5185,6 +5229,10 @@ This is your link for group %@!</source>
<target>Nuevo rol de miembro</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New member wants to join the group." xml:space="preserve">
<source>New member wants to join the group.</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="New message" xml:space="preserve">
<source>New message</source>
<target>Mensaje nuevo</target>
@ -5225,6 +5273,10 @@ This is your link for group %@!</source>
<target>Sin chats en la lista %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No chats with members" xml:space="preserve">
<source>No chats with members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No contacts selected" xml:space="preserve">
<source>No contacts selected</source>
<target>Ningún contacto seleccionado</target>
@ -5417,7 +5469,8 @@ This is your link for group %@!</source>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
<target>Ok</target>
<note>alert button</note>
<note>alert action
alert button</note>
</trans-unit>
<trans-unit id="Old database" xml:space="preserve">
<source>Old database</source>
@ -5828,6 +5881,10 @@ Error: %@</target>
<target>Por favor, intenta desactivar y reactivar las notificaciones.</target>
<note>token info</note>
</trans-unit>
<trans-unit id="Please wait for group moderators to review your request to join the group." xml:space="preserve">
<source>Please wait for group moderators to review your request to join the group.</source>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="Please wait for token activation to complete." xml:space="preserve">
<source>Please wait for token activation to complete.</source>
<target>Por favor, espera a que el token de activación se complete.</target>
@ -6281,6 +6338,10 @@ swipe action</note>
<target>Rechazar solicitud de contacto</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Reject member?" xml:space="preserve">
<source>Reject member?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Relay server is only used if necessary. Another party can observe your IP address." xml:space="preserve">
<source>Relay server is only used if necessary. Another party can observe your IP address.</source>
<target>El servidor de retransmisión sólo se usa en caso de necesidad. Un tercero podría ver tu IP.</target>
@ -6391,6 +6452,10 @@ swipe action</note>
<target>¿Motivo del informe?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Report sent to moderators" xml:space="preserve">
<source>Report sent to moderators</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Report spam: only group moderators will see it." xml:space="preserve">
<source>Report spam: only group moderators will see it.</source>
<target>Informar de spam: sólo los moderadores del grupo lo verán.</target>
@ -6506,6 +6571,14 @@ swipe action</note>
<target>Revisar condiciones</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Review members" xml:space="preserve">
<source>Review members</source>
<note>admission stage</note>
</trans-unit>
<trans-unit id="Review members before admitting (&quot;knocking&quot;)." xml:space="preserve">
<source>Review members before admitting ("knocking").</source>
<note>admission stage description</note>
</trans-unit>
<trans-unit id="Revoke" xml:space="preserve">
<source>Revoke</source>
<target>Revocar</target>
@ -6562,6 +6635,10 @@ chat item action</note>
<target>Guardar (y notificar contactos)</target>
<note>alert button</note>
</trans-unit>
<trans-unit id="Save admission settings?" xml:space="preserve">
<source>Save admission settings?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Save and notify contact" xml:space="preserve">
<source>Save and notify contact</source>
<target>Guardar y notificar contacto</target>
@ -7077,6 +7154,10 @@ chat item action</note>
<target>Úsalo en lugar de la autenticación del sistema.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set member admission" xml:space="preserve">
<source>Set member admission</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set message expiration in chats." xml:space="preserve">
<source>Set message expiration in chats.</source>
<target>Establece el vencimiento para los mensajes en los chats.</target>
@ -8839,6 +8920,10 @@ Repeat join request?</source>
<target>Podrás ver el enlace de invitación en detalles de conexión.</target>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can view your reports in Chat with admins." xml:space="preserve">
<source>You can view your reports in Chat with admins.</source>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can't send messages!" xml:space="preserve">
<source>You can't send messages!</source>
<target>¡No puedes enviar mensajes!</target>
@ -9141,6 +9226,10 @@ Repeat connection request?</source>
<target>y después elige:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="accepted %@" xml:space="preserve">
<source>accepted %@</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="accepted call" xml:space="preserve">
<source>accepted call</source>
<target>llamada aceptada</target>
@ -9151,6 +9240,10 @@ Repeat connection request?</source>
<target>invitación aceptada</target>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="accepted you" xml:space="preserve">
<source>accepted you</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="admin" xml:space="preserve">
<source>admin</source>
<target>administrador</target>
@ -9171,6 +9264,10 @@ Repeat connection request?</source>
<target>acordando cifrado…</target>
<note>chat item text</note>
</trans-unit>
<trans-unit id="all" xml:space="preserve">
<source>all</source>
<note>member criteria value</note>
</trans-unit>
<trans-unit id="all members" xml:space="preserve">
<source>all members</source>
<target>todos los miembros</target>
@ -9257,6 +9354,10 @@ marked deleted chat item preview text</note>
<target>llamando…</target>
<note>call status</note>
</trans-unit>
<trans-unit id="can't send messages" xml:space="preserve">
<source>can't send messages</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="cancelled %@" xml:space="preserve">
<source>cancelled %@</source>
<target>cancelado %@</target>
@ -9362,6 +9463,14 @@ marked deleted chat item preview text</note>
<target>el contacto %1$@ ha cambiado a %2$@</target>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="contact deleted" xml:space="preserve">
<source>contact deleted</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact disabled" xml:space="preserve">
<source>contact disabled</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact has e2e encryption" xml:space="preserve">
<source>contact has e2e encryption</source>
<target>el contacto dispone de cifrado de extremo a extremo</target>
@ -9372,6 +9481,10 @@ marked deleted chat item preview text</note>
<target>el contacto no dispone de cifrado de extremo a extremo</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact not ready" xml:space="preserve">
<source>contact not ready</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="creator" xml:space="preserve">
<source>creator</source>
<target>creador</target>
@ -9543,6 +9656,10 @@ pref value</note>
<target>grupo eliminado</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group is deleted" xml:space="preserve">
<source>group is deleted</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group profile updated" xml:space="preserve">
<source>group profile updated</source>
<target>perfil de grupo actualizado</target>
@ -9668,6 +9785,10 @@ pref value</note>
<target>conectado</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="member has old version" xml:space="preserve">
<source>member has old version</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="message" xml:space="preserve">
<source>message</source>
<target>mensaje</target>
@ -9733,6 +9854,10 @@ pref value</note>
<target>sin texto</target>
<note>copied message info in history</note>
</trans-unit>
<trans-unit id="not synchronized" xml:space="preserve">
<source>not synchronized</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="observer" xml:space="preserve">
<source>observer</source>
<target>observador</target>
@ -9743,6 +9868,7 @@ pref value</note>
<target>desactivado</target>
<note>enabled status
group pref value
member criteria value
time to disappear</note>
</trans-unit>
<trans-unit id="offered %@" xml:space="preserve">
@ -9795,6 +9921,10 @@ time to disappear</note>
<target>pendiente de aprobación</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="pending review" xml:space="preserve">
<source>pending review</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="quantum resistant e2e encryption" xml:space="preserve">
<source>quantum resistant e2e encryption</source>
<target>cifrado e2e resistente a tecnología cuántica</target>
@ -9835,6 +9965,10 @@ time to disappear</note>
<target>dirección de contacto eliminada</target>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="removed from group" xml:space="preserve">
<source>removed from group</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="removed profile picture" xml:space="preserve">
<source>removed profile picture</source>
<target>ha eliminado la imagen del perfil</target>
@ -9845,11 +9979,23 @@ time to disappear</note>
<target>te ha expulsado</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="request to join rejected" xml:space="preserve">
<source>request to join rejected</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="requested to connect" xml:space="preserve">
<source>requested to connect</source>
<target>solicitado para conectar</target>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="review" xml:space="preserve">
<source>review</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="reviewed by admins" xml:space="preserve">
<source>reviewed by admins</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="saved" xml:space="preserve">
<source>saved</source>
<target>guardado</target>
@ -10039,6 +10185,10 @@ last received msg: %2$@</source>
<target>tu</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="you accepted this member" xml:space="preserve">
<source>you accepted this member</source>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="you are invited to group" xml:space="preserve">
<source>you are invited to group</source>
<target>has sido invitado a un grupo</target>

View file

@ -527,6 +527,14 @@ time interval</note>
accept incoming call via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept as member" xml:space="preserve">
<source>Accept as member</source>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept as observer" xml:space="preserve">
<source>Accept as observer</source>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept conditions" xml:space="preserve">
<source>Accept conditions</source>
<note>No comment provided by engineer.</note>
@ -547,6 +555,10 @@ swipe action</note>
<note>accept contact request via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept member" xml:space="preserve">
<source>Accept member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Accepted conditions" xml:space="preserve">
<source>Accepted conditions</source>
<note>No comment provided by engineer.</note>
@ -1437,11 +1449,23 @@ set passcode view</note>
<source>Chat will be deleted for you - this cannot be undone!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat with admins" xml:space="preserve">
<source>Chat with admins</source>
<note>chat toolbar</note>
</trans-unit>
<trans-unit id="Chat with member" xml:space="preserve">
<source>Chat with member</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats" xml:space="preserve">
<source>Chats</source>
<target>Keskustelut</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats with members" xml:space="preserve">
<source>Chats with members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Check messages every 20 min." xml:space="preserve">
<source>Check messages every 20 min.</source>
<note>No comment provided by engineer.</note>
@ -2174,6 +2198,10 @@ swipe action</note>
<target>Poista keskusteluprofiili?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete chat with member?" xml:space="preserve">
<source>Delete chat with member?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Delete chat?" xml:space="preserve">
<source>Delete chat?</source>
<note>No comment provided by engineer.</note>
@ -2565,7 +2593,7 @@ swipe action</note>
<trans-unit id="Don't show again" xml:space="preserve">
<source>Don't show again</source>
<target>Älä näytä uudelleen</target>
<note>No comment provided by engineer.</note>
<note>alert action</note>
</trans-unit>
<trans-unit id="Done" xml:space="preserve">
<source>Done</source>
@ -2858,6 +2886,10 @@ chat item action</note>
<target>Virhe kontaktipyynnön hyväksymisessä</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error accepting member" xml:space="preserve">
<source>Error accepting member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error adding member(s)" xml:space="preserve">
<source>Error adding member(s)</source>
<target>Virhe lisättäessä jäseniä</target>
@ -2944,6 +2976,10 @@ chat item action</note>
<target>Virhe keskustelujen tietokannan poistamisessa</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error deleting chat with member" xml:space="preserve">
<source>Error deleting chat with member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error deleting chat!" xml:space="preserve">
<source>Error deleting chat!</source>
<target>Virhe keskutelun poistamisessa!</target>
@ -3044,7 +3080,7 @@ chat item action</note>
<trans-unit id="Error removing member" xml:space="preserve">
<source>Error removing member</source>
<target>Virhe poistettaessa jäsentä</target>
<note>No comment provided by engineer.</note>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error reordering lists" xml:space="preserve">
<source>Error reordering lists</source>
@ -4311,6 +4347,10 @@ This is your link for group %@!</source>
<target>Jäsen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member admission" xml:space="preserve">
<source>Member admission</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member inactive" xml:space="preserve">
<source>Member inactive</source>
<note>item status text</note>
@ -4342,6 +4382,10 @@ This is your link for group %@!</source>
<target>Jäsen poistetaan ryhmästä - tätä ei voi perua!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member will join the group, accept member?" xml:space="preserve">
<source>Member will join the group, accept member?</source>
<note>alert message</note>
</trans-unit>
<trans-unit id="Members can add message reactions." xml:space="preserve">
<source>Members can add message reactions.</source>
<target>Ryhmän jäsenet voivat lisätä viestireaktioita.</target>
@ -4716,6 +4760,10 @@ This is your link for group %@!</source>
<target>Uusi jäsenrooli</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New member wants to join the group." xml:space="preserve">
<source>New member wants to join the group.</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="New message" xml:space="preserve">
<source>New message</source>
<target>Uusi viesti</target>
@ -4752,6 +4800,10 @@ This is your link for group %@!</source>
<source>No chats in list %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No chats with members" xml:space="preserve">
<source>No chats with members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No contacts selected" xml:space="preserve">
<source>No contacts selected</source>
<target>Kontakteja ei ole valittu</target>
@ -4922,7 +4974,8 @@ This is your link for group %@!</source>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
<target>Ok</target>
<note>alert button</note>
<note>alert action
alert button</note>
</trans-unit>
<trans-unit id="Old database" xml:space="preserve">
<source>Old database</source>
@ -5296,6 +5349,10 @@ Error: %@</source>
<source>Please try to disable and re-enable notfications.</source>
<note>token info</note>
</trans-unit>
<trans-unit id="Please wait for group moderators to review your request to join the group." xml:space="preserve">
<source>Please wait for group moderators to review your request to join the group.</source>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="Please wait for token activation to complete." xml:space="preserve">
<source>Please wait for token activation to complete.</source>
<note>token info</note>
@ -5707,6 +5764,10 @@ swipe action</note>
<target>Hylkää yhteyspyyntö</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Reject member?" xml:space="preserve">
<source>Reject member?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Relay server is only used if necessary. Another party can observe your IP address." xml:space="preserve">
<source>Relay server is only used if necessary. Another party can observe your IP address.</source>
<target>Välityspalvelinta käytetään vain tarvittaessa. Toinen osapuoli voi tarkkailla IP-osoitettasi.</target>
@ -5805,6 +5866,10 @@ swipe action</note>
<source>Report reason?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Report sent to moderators" xml:space="preserve">
<source>Report sent to moderators</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Report spam: only group moderators will see it." xml:space="preserve">
<source>Report spam: only group moderators will see it.</source>
<note>report reason</note>
@ -5908,6 +5973,14 @@ swipe action</note>
<source>Review conditions</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Review members" xml:space="preserve">
<source>Review members</source>
<note>admission stage</note>
</trans-unit>
<trans-unit id="Review members before admitting (&quot;knocking&quot;)." xml:space="preserve">
<source>Review members before admitting ("knocking").</source>
<note>admission stage description</note>
</trans-unit>
<trans-unit id="Revoke" xml:space="preserve">
<source>Revoke</source>
<target>Peruuta</target>
@ -5960,6 +6033,10 @@ chat item action</note>
<target>Tallenna (ja ilmoita kontakteille)</target>
<note>alert button</note>
</trans-unit>
<trans-unit id="Save admission settings?" xml:space="preserve">
<source>Save admission settings?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Save and notify contact" xml:space="preserve">
<source>Save and notify contact</source>
<target>Tallenna ja ilmoita kontaktille</target>
@ -6430,6 +6507,10 @@ chat item action</note>
<target>Aseta se järjestelmän todennuksen sijaan.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set member admission" xml:space="preserve">
<source>Set member admission</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set message expiration in chats." xml:space="preserve">
<source>Set message expiration in chats.</source>
<note>No comment provided by engineer.</note>
@ -8017,6 +8098,10 @@ Repeat join request?</source>
<source>You can view invitation link again in connection details.</source>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can view your reports in Chat with admins." xml:space="preserve">
<source>You can view your reports in Chat with admins.</source>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can't send messages!" xml:space="preserve">
<source>You can't send messages!</source>
<target>Et voi lähettää viestejä!</target>
@ -8303,6 +8388,10 @@ Repeat connection request?</source>
<target>edellä, valitse sitten:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="accepted %@" xml:space="preserve">
<source>accepted %@</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="accepted call" xml:space="preserve">
<source>accepted call</source>
<target>hyväksytty puhelu</target>
@ -8312,6 +8401,10 @@ Repeat connection request?</source>
<source>accepted invitation</source>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="accepted you" xml:space="preserve">
<source>accepted you</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="admin" xml:space="preserve">
<source>admin</source>
<target>ylläpitäjä</target>
@ -8331,6 +8424,10 @@ Repeat connection request?</source>
<target>hyväksyy salausta…</target>
<note>chat item text</note>
</trans-unit>
<trans-unit id="all" xml:space="preserve">
<source>all</source>
<note>member criteria value</note>
</trans-unit>
<trans-unit id="all members" xml:space="preserve">
<source>all members</source>
<note>feature role</note>
@ -8408,6 +8505,10 @@ marked deleted chat item preview text</note>
<target>soittaa…</target>
<note>call status</note>
</trans-unit>
<trans-unit id="can't send messages" xml:space="preserve">
<source>can't send messages</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="cancelled %@" xml:space="preserve">
<source>cancelled %@</source>
<target>peruutettu %@</target>
@ -8511,6 +8612,14 @@ marked deleted chat item preview text</note>
<source>contact %1$@ changed to %2$@</source>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="contact deleted" xml:space="preserve">
<source>contact deleted</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact disabled" xml:space="preserve">
<source>contact disabled</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact has e2e encryption" xml:space="preserve">
<source>contact has e2e encryption</source>
<target>kontaktilla on e2e-salaus</target>
@ -8521,6 +8630,10 @@ marked deleted chat item preview text</note>
<target>kontaktilla ei ole e2e-salausta</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact not ready" xml:space="preserve">
<source>contact not ready</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="creator" xml:space="preserve">
<source>creator</source>
<target>luoja</target>
@ -8687,6 +8800,10 @@ pref value</note>
<target>ryhmä poistettu</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group is deleted" xml:space="preserve">
<source>group is deleted</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group profile updated" xml:space="preserve">
<source>group profile updated</source>
<target>ryhmäprofiili päivitetty</target>
@ -8809,6 +8926,10 @@ pref value</note>
<target>yhdistetty</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="member has old version" xml:space="preserve">
<source>member has old version</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="message" xml:space="preserve">
<source>message</source>
<note>No comment provided by engineer.</note>
@ -8872,6 +8993,10 @@ pref value</note>
<target>ei tekstiä</target>
<note>copied message info in history</note>
</trans-unit>
<trans-unit id="not synchronized" xml:space="preserve">
<source>not synchronized</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="observer" xml:space="preserve">
<source>observer</source>
<target>tarkkailija</target>
@ -8882,6 +9007,7 @@ pref value</note>
<target>pois</target>
<note>enabled status
group pref value
member criteria value
time to disappear</note>
</trans-unit>
<trans-unit id="offered %@" xml:space="preserve">
@ -8929,6 +9055,10 @@ time to disappear</note>
<source>pending approval</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="pending review" xml:space="preserve">
<source>pending review</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="quantum resistant e2e encryption" xml:space="preserve">
<source>quantum resistant e2e encryption</source>
<note>chat item text</note>
@ -8966,6 +9096,10 @@ time to disappear</note>
<source>removed contact address</source>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="removed from group" xml:space="preserve">
<source>removed from group</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="removed profile picture" xml:space="preserve">
<source>removed profile picture</source>
<note>profile update event chat item</note>
@ -8975,10 +9109,22 @@ time to disappear</note>
<target>poisti sinut</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="request to join rejected" xml:space="preserve">
<source>request to join rejected</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="requested to connect" xml:space="preserve">
<source>requested to connect</source>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="review" xml:space="preserve">
<source>review</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="reviewed by admins" xml:space="preserve">
<source>reviewed by admins</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="saved" xml:space="preserve">
<source>saved</source>
<note>No comment provided by engineer.</note>
@ -9149,6 +9295,10 @@ last received msg: %2$@</source>
<source>you</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="you accepted this member" xml:space="preserve">
<source>you accepted this member</source>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="you are invited to group" xml:space="preserve">
<source>you are invited to group</source>
<target>sinut on kutsuttu ryhmään</target>

View file

@ -565,6 +565,14 @@ time interval</note>
accept incoming call via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept as member" xml:space="preserve">
<source>Accept as member</source>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept as observer" xml:space="preserve">
<source>Accept as observer</source>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept conditions" xml:space="preserve">
<source>Accept conditions</source>
<target>Accepter les conditions</target>
@ -586,6 +594,10 @@ swipe action</note>
<note>accept contact request via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept member" xml:space="preserve">
<source>Accept member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Accepted conditions" xml:space="preserve">
<source>Accepted conditions</source>
<target>Conditions acceptées</target>
@ -1580,11 +1592,23 @@ set passcode view</note>
<target>Le discussion sera supprimé pour vous - il n'est pas possible de revenir en arrière!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat with admins" xml:space="preserve">
<source>Chat with admins</source>
<note>chat toolbar</note>
</trans-unit>
<trans-unit id="Chat with member" xml:space="preserve">
<source>Chat with member</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats" xml:space="preserve">
<source>Chats</source>
<target>Discussions</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats with members" xml:space="preserve">
<source>Chats with members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Check messages every 20 min." xml:space="preserve">
<source>Check messages every 20 min.</source>
<target>Consulter les messages toutes les 20 minutes.</target>
@ -2400,6 +2424,10 @@ swipe action</note>
<target>Supprimer le profil du chat ?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete chat with member?" xml:space="preserve">
<source>Delete chat with member?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Delete chat?" xml:space="preserve">
<source>Delete chat?</source>
<target>Supprimer la discussion?</target>
@ -2823,7 +2851,7 @@ swipe action</note>
<trans-unit id="Don't show again" xml:space="preserve">
<source>Don't show again</source>
<target>Ne plus afficher</target>
<note>No comment provided by engineer.</note>
<note>alert action</note>
</trans-unit>
<trans-unit id="Done" xml:space="preserve">
<source>Done</source>
@ -3141,6 +3169,10 @@ chat item action</note>
<target>Erreur de validation de la demande de contact</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error accepting member" xml:space="preserve">
<source>Error accepting member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error adding member(s)" xml:space="preserve">
<source>Error adding member(s)</source>
<target>Erreur lors de l'ajout de membre·s</target>
@ -3236,6 +3268,10 @@ chat item action</note>
<target>Erreur lors de la suppression de la base de données du chat</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error deleting chat with member" xml:space="preserve">
<source>Error deleting chat with member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error deleting chat!" xml:space="preserve">
<source>Error deleting chat!</source>
<target>Erreur lors de la suppression du chat!</target>
@ -3344,7 +3380,7 @@ chat item action</note>
<trans-unit id="Error removing member" xml:space="preserve">
<source>Error removing member</source>
<target>Erreur lors de la suppression d'un membre</target>
<note>No comment provided by engineer.</note>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error reordering lists" xml:space="preserve">
<source>Error reordering lists</source>
@ -4712,6 +4748,10 @@ Voici votre lien pour le groupe %@ !</target>
<target>Membre</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member admission" xml:space="preserve">
<source>Member admission</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member inactive" xml:space="preserve">
<source>Member inactive</source>
<target>Membre inactif</target>
@ -4746,6 +4786,10 @@ Voici votre lien pour le groupe %@ !</target>
<target>Ce membre sera retiré du groupe - impossible de revenir en arrière!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member will join the group, accept member?" xml:space="preserve">
<source>Member will join the group, accept member?</source>
<note>alert message</note>
</trans-unit>
<trans-unit id="Members can add message reactions." xml:space="preserve">
<source>Members can add message reactions.</source>
<target>Les membres du groupe peuvent ajouter des réactions aux messages.</target>
@ -5160,6 +5204,10 @@ Voici votre lien pour le groupe %@ !</target>
<target>Nouveau rôle</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New member wants to join the group." xml:space="preserve">
<source>New member wants to join the group.</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="New message" xml:space="preserve">
<source>New message</source>
<target>Nouveau message</target>
@ -5197,6 +5245,10 @@ Voici votre lien pour le groupe %@ !</target>
<source>No chats in list %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No chats with members" xml:space="preserve">
<source>No chats with members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No contacts selected" xml:space="preserve">
<source>No contacts selected</source>
<target>Aucun contact sélectionné</target>
@ -5383,7 +5435,8 @@ Voici votre lien pour le groupe %@ !</target>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
<target>Ok</target>
<note>alert button</note>
<note>alert action
alert button</note>
</trans-unit>
<trans-unit id="Old database" xml:space="preserve">
<source>Old database</source>
@ -5790,6 +5843,10 @@ Erreur: %@</target>
<source>Please try to disable and re-enable notfications.</source>
<note>token info</note>
</trans-unit>
<trans-unit id="Please wait for group moderators to review your request to join the group." xml:space="preserve">
<source>Please wait for group moderators to review your request to join the group.</source>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="Please wait for token activation to complete." xml:space="preserve">
<source>Please wait for token activation to complete.</source>
<note>token info</note>
@ -6234,6 +6291,10 @@ swipe action</note>
<target>Rejeter la demande de contact</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Reject member?" xml:space="preserve">
<source>Reject member?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Relay server is only used if necessary. Another party can observe your IP address." xml:space="preserve">
<source>Relay server is only used if necessary. Another party can observe your IP address.</source>
<target>Le serveur relais n'est utilisé que si nécessaire. Un tiers peut observer votre adresse IP.</target>
@ -6339,6 +6400,10 @@ swipe action</note>
<source>Report reason?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Report sent to moderators" xml:space="preserve">
<source>Report sent to moderators</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Report spam: only group moderators will see it." xml:space="preserve">
<source>Report spam: only group moderators will see it.</source>
<note>report reason</note>
@ -6449,6 +6514,14 @@ swipe action</note>
<target>Vérifier les conditions</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Review members" xml:space="preserve">
<source>Review members</source>
<note>admission stage</note>
</trans-unit>
<trans-unit id="Review members before admitting (&quot;knocking&quot;)." xml:space="preserve">
<source>Review members before admitting ("knocking").</source>
<note>admission stage description</note>
</trans-unit>
<trans-unit id="Revoke" xml:space="preserve">
<source>Revoke</source>
<target>Révoquer</target>
@ -6505,6 +6578,10 @@ chat item action</note>
<target>Enregistrer (et en informer les contacts)</target>
<note>alert button</note>
</trans-unit>
<trans-unit id="Save admission settings?" xml:space="preserve">
<source>Save admission settings?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Save and notify contact" xml:space="preserve">
<source>Save and notify contact</source>
<target>Enregistrer et en informer le contact</target>
@ -7017,6 +7094,10 @@ chat item action</note>
<target>Il permet de remplacer l'authentification du système.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set member admission" xml:space="preserve">
<source>Set member admission</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set message expiration in chats." xml:space="preserve">
<source>Set message expiration in chats.</source>
<note>No comment provided by engineer.</note>
@ -8762,6 +8843,10 @@ Répéter la demande d'adhésion ?</target>
<target>Vous pouvez à nouveau consulter le lien d'invitation dans les détails de la connexion.</target>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can view your reports in Chat with admins." xml:space="preserve">
<source>You can view your reports in Chat with admins.</source>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can't send messages!" xml:space="preserve">
<source>You can't send messages!</source>
<target>Vous ne pouvez pas envoyer de messages !</target>
@ -9063,6 +9148,10 @@ Répéter la demande de connexion ?</target>
<target>ci-dessus, puis choisissez:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="accepted %@" xml:space="preserve">
<source>accepted %@</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="accepted call" xml:space="preserve">
<source>accepted call</source>
<target>appel accepté</target>
@ -9073,6 +9162,10 @@ Répéter la demande de connexion ?</target>
<target>invitation acceptée</target>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="accepted you" xml:space="preserve">
<source>accepted you</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="admin" xml:space="preserve">
<source>admin</source>
<target>admin</target>
@ -9093,6 +9186,10 @@ Répéter la demande de connexion ?</target>
<target>négociation du chiffrement…</target>
<note>chat item text</note>
</trans-unit>
<trans-unit id="all" xml:space="preserve">
<source>all</source>
<note>member criteria value</note>
</trans-unit>
<trans-unit id="all members" xml:space="preserve">
<source>all members</source>
<target>tous les membres</target>
@ -9178,6 +9275,10 @@ marked deleted chat item preview text</note>
<target>appel…</target>
<note>call status</note>
</trans-unit>
<trans-unit id="can't send messages" xml:space="preserve">
<source>can't send messages</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="cancelled %@" xml:space="preserve">
<source>cancelled %@</source>
<target>annulé %@</target>
@ -9283,6 +9384,14 @@ marked deleted chat item preview text</note>
<target>le contact %1$@ est devenu %2$@</target>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="contact deleted" xml:space="preserve">
<source>contact deleted</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact disabled" xml:space="preserve">
<source>contact disabled</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact has e2e encryption" xml:space="preserve">
<source>contact has e2e encryption</source>
<target>Ce contact a le chiffrement de bout en bout</target>
@ -9293,6 +9402,10 @@ marked deleted chat item preview text</note>
<target>Ce contact n'a pas le chiffrement de bout en bout</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact not ready" xml:space="preserve">
<source>contact not ready</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="creator" xml:space="preserve">
<source>creator</source>
<target>créateur</target>
@ -9464,6 +9577,10 @@ pref value</note>
<target>groupe supprimé</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group is deleted" xml:space="preserve">
<source>group is deleted</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group profile updated" xml:space="preserve">
<source>group profile updated</source>
<target>mise à jour du profil de groupe</target>
@ -9589,6 +9706,10 @@ pref value</note>
<target>est connecté·e</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="member has old version" xml:space="preserve">
<source>member has old version</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="message" xml:space="preserve">
<source>message</source>
<target>message</target>
@ -9653,6 +9774,10 @@ pref value</note>
<target>aucun texte</target>
<note>copied message info in history</note>
</trans-unit>
<trans-unit id="not synchronized" xml:space="preserve">
<source>not synchronized</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="observer" xml:space="preserve">
<source>observer</source>
<target>observateur</target>
@ -9663,6 +9788,7 @@ pref value</note>
<target>off</target>
<note>enabled status
group pref value
member criteria value
time to disappear</note>
</trans-unit>
<trans-unit id="offered %@" xml:space="preserve">
@ -9713,6 +9839,10 @@ time to disappear</note>
<source>pending approval</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="pending review" xml:space="preserve">
<source>pending review</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="quantum resistant e2e encryption" xml:space="preserve">
<source>quantum resistant e2e encryption</source>
<target>chiffrement e2e résistant post-quantique</target>
@ -9752,6 +9882,10 @@ time to disappear</note>
<target>suppression de l'adresse de contact</target>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="removed from group" xml:space="preserve">
<source>removed from group</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="removed profile picture" xml:space="preserve">
<source>removed profile picture</source>
<target>suppression de la photo de profil</target>
@ -9762,11 +9896,23 @@ time to disappear</note>
<target>vous a retiré</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="request to join rejected" xml:space="preserve">
<source>request to join rejected</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="requested to connect" xml:space="preserve">
<source>requested to connect</source>
<target>demande à se connecter</target>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="review" xml:space="preserve">
<source>review</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="reviewed by admins" xml:space="preserve">
<source>reviewed by admins</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="saved" xml:space="preserve">
<source>saved</source>
<target>enregistré</target>
@ -9956,6 +10102,10 @@ dernier message reçu: %2$@</target>
<target>vous</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="you accepted this member" xml:space="preserve">
<source>you accepted this member</source>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="you are invited to group" xml:space="preserve">
<source>you are invited to group</source>
<target>vous êtes invité·e au groupe</target>

View file

@ -565,6 +565,16 @@ time interval</note>
accept incoming call via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept as member" xml:space="preserve">
<source>Accept as member</source>
<target>Befogadás tagként</target>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept as observer" xml:space="preserve">
<source>Accept as observer</source>
<target>Befogadás megfigyelőként</target>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept conditions" xml:space="preserve">
<source>Accept conditions</source>
<target>Feltételek elfogadása</target>
@ -586,6 +596,11 @@ swipe action</note>
<note>accept contact request via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept member" xml:space="preserve">
<source>Accept member</source>
<target>Tag befogadása</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Accepted conditions" xml:space="preserve">
<source>Accepted conditions</source>
<target>Elfogadott feltételek</target>
@ -1582,11 +1597,26 @@ set passcode view</note>
<target>A csevegés törölve lesz az Ön számára ez a művelet nem vonható vissza!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat with admins" xml:space="preserve">
<source>Chat with admins</source>
<target>Csevegés az adminisztrátorokkal</target>
<note>chat toolbar</note>
</trans-unit>
<trans-unit id="Chat with member" xml:space="preserve">
<source>Chat with member</source>
<target>Csevegés a taggal</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats" xml:space="preserve">
<source>Chats</source>
<target>Csevegések</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats with members" xml:space="preserve">
<source>Chats with members</source>
<target>Csevegés a tagokkal</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Check messages every 20 min." xml:space="preserve">
<source>Check messages every 20 min.</source>
<target>Üzenetek ellenőrzése 20 percenként.</target>
@ -2240,7 +2270,7 @@ Ez a saját egyszer használható meghívója!</target>
</trans-unit>
<trans-unit id="Database IDs and Transport isolation option." xml:space="preserve">
<source>Database IDs and Transport isolation option.</source>
<target>Adatbázis-azonosítók és átvitel-izolációs beállítások.</target>
<target>Adatbázis-azonosítók és átvitelelkülönítési beállítások.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Database downgrade" xml:space="preserve">
@ -2338,7 +2368,7 @@ Ez a saját egyszer használható meghívója!</target>
</trans-unit>
<trans-unit id="Decryption error" xml:space="preserve">
<source>Decryption error</source>
<target>Titkosítás visszafejtési hiba</target>
<target>Titkosításvisszafejtési hiba</target>
<note>message decrypt error item</note>
</trans-unit>
<trans-unit id="Delete" xml:space="preserve">
@ -2402,6 +2432,11 @@ swipe action</note>
<target>Törli a csevegési profilt?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete chat with member?" xml:space="preserve">
<source>Delete chat with member?</source>
<target>Törli a taggal való csevegést?</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Delete chat?" xml:space="preserve">
<source>Delete chat?</source>
<target>Törli a csevegést?</target>
@ -2674,7 +2709,7 @@ swipe action</note>
</trans-unit>
<trans-unit id="Different names, avatars and transport isolation." xml:space="preserve">
<source>Different names, avatars and transport isolation.</source>
<target>Különböző nevek, profilképek és átvitel-izoláció.</target>
<target>Különböző nevek, profilképek és átvitelizoláció.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Direct messages" xml:space="preserve">
@ -2825,7 +2860,7 @@ swipe action</note>
<trans-unit id="Don't show again" xml:space="preserve">
<source>Don't show again</source>
<target>Ne mutasd újra</target>
<note>No comment provided by engineer.</note>
<note>alert action</note>
</trans-unit>
<trans-unit id="Done" xml:space="preserve">
<source>Done</source>
@ -3143,6 +3178,11 @@ chat item action</note>
<target>Hiba történt a meghívási kérés elfogadásakor</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error accepting member" xml:space="preserve">
<source>Error accepting member</source>
<target>Hiba a tag befogadásakor</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error adding member(s)" xml:space="preserve">
<source>Error adding member(s)</source>
<target>Hiba történt a tag(ok) hozzáadásakor</target>
@ -3238,6 +3278,11 @@ chat item action</note>
<target>Hiba történt a csevegési adatbázis törlésekor</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error deleting chat with member" xml:space="preserve">
<source>Error deleting chat with member</source>
<target>Hiba a taggal való csevegés törlésekor</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error deleting chat!" xml:space="preserve">
<source>Error deleting chat!</source>
<target>Hiba történt a csevegés törlésekor!</target>
@ -3265,7 +3310,7 @@ chat item action</note>
</trans-unit>
<trans-unit id="Error deleting user profile" xml:space="preserve">
<source>Error deleting user profile</source>
<target>Hiba történt a felhasználó-profil törlésekor</target>
<target>Hiba történt a felhasználói profil törlésekor</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error downloading the archive" xml:space="preserve">
@ -3346,7 +3391,7 @@ chat item action</note>
<trans-unit id="Error removing member" xml:space="preserve">
<source>Error removing member</source>
<target>Hiba történt a tag eltávolításakor</target>
<note>No comment provided by engineer.</note>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error reordering lists" xml:space="preserve">
<source>Error reordering lists</source>
@ -4449,7 +4494,7 @@ További fejlesztések hamarosan!</target>
</trans-unit>
<trans-unit id="It can happen when you or your connection used the old database backup." xml:space="preserve">
<source>It can happen when you or your connection used the old database backup.</source>
<target>Ez akkor fordulhat elő, ha Ön vagy a partnere régi adatbázis biztonsági mentést használt.</target>
<target>Ez akkor fordulhat elő, ha Ön vagy a partnere egy régi adatbázis biztonsági mentését használta.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="It can happen when:&#10;1. The messages expired in the sending client after 2 days or on the server after 30 days.&#10;2. Message decryption failed, because you or your contact used old database backup.&#10;3. The connection was compromised." xml:space="preserve">
@ -4459,7 +4504,7 @@ További fejlesztések hamarosan!</target>
3. The connection was compromised.</source>
<target>Ez akkor fordulhat elő, ha:
1. Az üzenetek 2 nap után, vagy a kiszolgálón 30 nap után lejártak.
2. Nem sikerült az üzenetet visszafejteni, mert Ön, vagy a partnere régebbi adatbázis biztonsági mentést használt.
2. Nem sikerült az üzenetet visszafejteni, mert Ön, vagy a partnere egy régi adatbázis biztonsági mentését használta.
3. A kapcsolat sérült.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@ -4730,6 +4775,11 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz!</target>
<target>Tag</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member admission" xml:space="preserve">
<source>Member admission</source>
<target>Tagbefogadás</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member inactive" xml:space="preserve">
<source>Member inactive</source>
<target>Inaktív tag</target>
@ -4765,6 +4815,11 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz!</target>
<target>A tag el lesz távolítva a csoportból ez a művelet nem vonható vissza!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member will join the group, accept member?" xml:space="preserve">
<source>Member will join the group, accept member?</source>
<target>A tag csatlakozni akar a csoporthoz, befogadja a tagot?</target>
<note>alert message</note>
</trans-unit>
<trans-unit id="Members can add message reactions." xml:space="preserve">
<source>Members can add message reactions.</source>
<target>A tagok reakciókat adhatnak hozzá az üzenetekhez.</target>
@ -4942,12 +4997,12 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz!</target>
</trans-unit>
<trans-unit id="Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." xml:space="preserve">
<source>Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery.</source>
<target>Az üzenetek, a fájlok és a hívások **végpontok közötti titkosítással**, sérülés utáni titkosságvédelemmel és -helyreállítással, továbbá letagadhatósággal vannak védve.</target>
<target>Az üzenetek, a fájlok és a hívások **végpontok közötti titkosítással**, kompromittálás előtti és utáni titkosságvédelemmel, illetve letagadhatósággal vannak védve.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." xml:space="preserve">
<source>Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery.</source>
<target>Az üzenetek, a fájlok és a hívások **végpontok közötti kvantumbiztos titkosítással**, sérülés utáni titkosságvédelemmel és -helyreállítással, továbbá letagadhatósággal vannak védve.</target>
<target>Az üzenetek, a fájlok és a hívások **végpontok közötti kvantumbiztos titkosítással**, kompromittálás előtti és utáni titkosságvédelemmel, illetve letagadhatósággal vannak védve.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Migrate device" xml:space="preserve">
@ -5185,6 +5240,11 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz!</target>
<target>Új tag szerepköre</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New member wants to join the group." xml:space="preserve">
<source>New member wants to join the group.</source>
<target>Új tag szeretne csatlakozni a csoporthoz.</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="New message" xml:space="preserve">
<source>New message</source>
<target>Új üzenet</target>
@ -5225,6 +5285,11 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz!</target>
<target>Nincsenek csevegések a(z) %@ nevű listában</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No chats with members" xml:space="preserve">
<source>No chats with members</source>
<target>Nincsenek csevegések a tagokkal</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No contacts selected" xml:space="preserve">
<source>No contacts selected</source>
<target>Nincs partner kijelölve</target>
@ -5347,7 +5412,7 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz!</target>
</trans-unit>
<trans-unit id="No user identifiers." xml:space="preserve">
<source>No user identifiers.</source>
<target>Nincsenek felhasználó-azonosítók.</target>
<target>Nincsenek felhasználói azonosítók.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Not compatible!" xml:space="preserve">
@ -5417,7 +5482,8 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz!</target>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
<target>Rendben</target>
<note>alert button</note>
<note>alert action
alert button</note>
</trans-unit>
<trans-unit id="Old database" xml:space="preserve">
<source>Old database</source>
@ -5575,6 +5641,7 @@ VPN engedélyezése szükséges.</target>
</trans-unit>
<trans-unit id="Open link?" xml:space="preserve">
<source>Open link?</source>
<target>Megnyitja a hivatkozást?</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Open migration to another device" xml:space="preserve">
@ -5828,6 +5895,11 @@ Hiba: %@</target>
<target>Próbálja meg letiltani és újra engedélyezni az értesítéseket.</target>
<note>token info</note>
</trans-unit>
<trans-unit id="Please wait for group moderators to review your request to join the group." xml:space="preserve">
<source>Please wait for group moderators to review your request to join the group.</source>
<target>Várja meg, amíg a csoport moderátorai áttekintik a csoporthoz való csatlakozási kérelmét.</target>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="Please wait for token activation to complete." xml:space="preserve">
<source>Please wait for token activation to complete.</source>
<target>Várjon, amíg a token aktiválása befejeződik.</target>
@ -6281,14 +6353,19 @@ swipe action</note>
<target>Meghívási kérés elutasítása</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Reject member?" xml:space="preserve">
<source>Reject member?</source>
<target>Elutasítja a tagot?</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Relay server is only used if necessary. Another party can observe your IP address." xml:space="preserve">
<source>Relay server is only used if necessary. Another party can observe your IP address.</source>
<target>A továbbítókiszolgáló csak szükség esetén lesz használva. Egy másik fél megfigyelheti az IP-címet.</target>
<target>A továbbítókiszolgáló csak szükség esetén lesz használva. Egy másik fél megfigyelheti az IP-címét.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Relay server protects your IP address, but it can observe the duration of the call." xml:space="preserve">
<source>Relay server protects your IP address, but it can observe the duration of the call.</source>
<target>A továbbítókiszolgáló megvédi az Ön IP-címét, de megfigyelheti a hívás időtartamát.</target>
<target>A továbbítókiszolgáló megvédi az IP-címét, de megfigyelheti a hívás időtartamát.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Remove" xml:space="preserve">
@ -6391,6 +6468,11 @@ swipe action</note>
<target>Jelentés indoklása?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Report sent to moderators" xml:space="preserve">
<source>Report sent to moderators</source>
<target>A jelentés el lett küldve a moderátoroknak</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Report spam: only group moderators will see it." xml:space="preserve">
<source>Report spam: only group moderators will see it.</source>
<target>Kéretlen tartalom jelentése: csak a csoport moderátorai látják.</target>
@ -6506,6 +6588,16 @@ swipe action</note>
<target>Feltételek felülvizsgálata</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Review members" xml:space="preserve">
<source>Review members</source>
<target>Tagok áttekintése</target>
<note>admission stage</note>
</trans-unit>
<trans-unit id="Review members before admitting (&quot;knocking&quot;)." xml:space="preserve">
<source>Review members before admitting ("knocking").</source>
<target>Tagok áttekintése a befogadás előtt (kopogtatás).</target>
<note>admission stage description</note>
</trans-unit>
<trans-unit id="Revoke" xml:space="preserve">
<source>Revoke</source>
<target>Visszavonás</target>
@ -6562,6 +6654,11 @@ chat item action</note>
<target>Mentés (és a partnerek értesítése)</target>
<note>alert button</note>
</trans-unit>
<trans-unit id="Save admission settings?" xml:space="preserve">
<source>Save admission settings?</source>
<target>Elmenti a befogadási beállításokat?</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Save and notify contact" xml:space="preserve">
<source>Save and notify contact</source>
<target>Mentés és a partner értesítése</target>
@ -7077,6 +7174,11 @@ chat item action</note>
<target>Beállítás a rendszer-hitelesítés helyett.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set member admission" xml:space="preserve">
<source>Set member admission</source>
<target>Tagbefogadás beállítása</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set message expiration in chats." xml:space="preserve">
<source>Set message expiration in chats.</source>
<target>Üzenetek eltűnési idejének módosítása a csevegésekben.</target>
@ -7335,7 +7437,7 @@ chat item action</note>
</trans-unit>
<trans-unit id="SimpleX protocols reviewed by Trail of Bits." xml:space="preserve">
<source>SimpleX protocols reviewed by Trail of Bits.</source>
<target>A SimpleX Chat biztonsága a Trail of Bits által lett felülvizsgálva.</target>
<target>A SimpleX-protokollokat a Trail of Bits auditálta.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Simplified incognito mode" xml:space="preserve">
@ -8027,7 +8129,7 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll
</trans-unit>
<trans-unit id="Transport isolation" xml:space="preserve">
<source>Transport isolation</source>
<target>Átvitel-izoláció</target>
<target>Átvitelelkülönítés</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Transport sessions" xml:space="preserve">
@ -8339,7 +8441,7 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso
</trans-unit>
<trans-unit id="Use private routing with unknown servers when IP address is not protected." xml:space="preserve">
<source>Use private routing with unknown servers when IP address is not protected.</source>
<target>Használjon privát útválasztást ismeretlen kiszolgálókkal, ha az IP-cím nem védett.</target>
<target>Használjon privát útválasztást az ismeretlen kiszolgálókkal, ha az IP-cím nem védett.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Use private routing with unknown servers." xml:space="preserve">
@ -8624,12 +8726,12 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso
</trans-unit>
<trans-unit id="Without Tor or VPN, your IP address will be visible to file servers." xml:space="preserve">
<source>Without Tor or VPN, your IP address will be visible to file servers.</source>
<target>Tor vagy VPN nélkül az Ön IP-címe látható lesz a fájlkiszolgálók számára.</target>
<target>Tor vagy VPN nélkül az IP-címe láthatóvá válik a fájlkiszolgálók számára.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." xml:space="preserve">
<source>Without Tor or VPN, your IP address will be visible to these XFTP relays: %@.</source>
<target>Tor vagy VPN nélkül az Ön IP-címe látható lesz a következő XFTP-továbbítókiszolgálók számára: %@.</target>
<target>Tor vagy VPN nélkül az IP-címe láthatóvá válik a következő XFTP-továbbítókiszolgálók számára: %@.</target>
<note>alert message</note>
</trans-unit>
<trans-unit id="Wrong database passphrase" xml:space="preserve">
@ -8839,6 +8941,11 @@ Megismétli a meghívási kérést?</target>
<target>A meghívási hivatkozást újra megtekintheti a kapcsolat részleteinél.</target>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can view your reports in Chat with admins." xml:space="preserve">
<source>You can view your reports in Chat with admins.</source>
<target>A jelentéseket megtekintheti a „Csevegés az adminisztrátorokkal” menüben.</target>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can't send messages!" xml:space="preserve">
<source>You can't send messages!</source>
<target>Nem lehet üzeneteket küldeni!</target>
@ -9078,7 +9185,7 @@ Megismétli a meghívási kérést?</target>
</trans-unit>
<trans-unit id="Your profile is stored on your device and only shared with your contacts." xml:space="preserve">
<source>Your profile is stored on your device and only shared with your contacts.</source>
<target>A profilja csak a partnereivel van megosztva.</target>
<target>A profilja az eszközén van tárolva és csak a partnereivel van megosztva.</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. SimpleX servers cannot see your profile." xml:space="preserve">
@ -9141,6 +9248,11 @@ Megismétli a meghívási kérést?</target>
<target>gombra fent, majd válassza ki:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="accepted %@" xml:space="preserve">
<source>accepted %@</source>
<target>befogadta őt: %@</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="accepted call" xml:space="preserve">
<source>accepted call</source>
<target>fogadott hívás</target>
@ -9151,6 +9263,11 @@ Megismétli a meghívási kérést?</target>
<target>elfogadott meghívó</target>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="accepted you" xml:space="preserve">
<source>accepted you</source>
<target>befogadta Önt</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="admin" xml:space="preserve">
<source>admin</source>
<target>adminisztrátor</target>
@ -9171,6 +9288,11 @@ Megismétli a meghívási kérést?</target>
<target>titkosítás elfogadása…</target>
<note>chat item text</note>
</trans-unit>
<trans-unit id="all" xml:space="preserve">
<source>all</source>
<target>összes</target>
<note>member criteria value</note>
</trans-unit>
<trans-unit id="all members" xml:space="preserve">
<source>all members</source>
<target>összes tag</target>
@ -9257,6 +9379,11 @@ marked deleted chat item preview text</note>
<target>hívás…</target>
<note>call status</note>
</trans-unit>
<trans-unit id="can't send messages" xml:space="preserve">
<source>can't send messages</source>
<target>nem lehet üzeneteket küldeni</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="cancelled %@" xml:space="preserve">
<source>cancelled %@</source>
<target>%@ visszavonva</target>
@ -9362,6 +9489,16 @@ marked deleted chat item preview text</note>
<target>%1$@ a következőre módosította a nevét: %2$@</target>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="contact deleted" xml:space="preserve">
<source>contact deleted</source>
<target>partner törölve</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact disabled" xml:space="preserve">
<source>contact disabled</source>
<target>partner letiltva</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact has e2e encryption" xml:space="preserve">
<source>contact has e2e encryption</source>
<target>a partner e2e titkosítással rendelkezik</target>
@ -9372,6 +9509,11 @@ marked deleted chat item preview text</note>
<target>a partner nem rendelkezik e2e titkosítással</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact not ready" xml:space="preserve">
<source>contact not ready</source>
<target>a kapcsolat nem áll készen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="creator" xml:space="preserve">
<source>creator</source>
<target>készítő</target>
@ -9543,6 +9685,11 @@ pref value</note>
<target>a csoport törölve</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group is deleted" xml:space="preserve">
<source>group is deleted</source>
<target>csoport törölve</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group profile updated" xml:space="preserve">
<source>group profile updated</source>
<target>csoportprofil frissítve</target>
@ -9668,6 +9815,11 @@ pref value</note>
<target>kapcsolódott</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="member has old version" xml:space="preserve">
<source>member has old version</source>
<target>a tag régi verziót használ</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="message" xml:space="preserve">
<source>message</source>
<target>üzenet</target>
@ -9733,6 +9885,11 @@ pref value</note>
<target>nincs szöveg</target>
<note>copied message info in history</note>
</trans-unit>
<trans-unit id="not synchronized" xml:space="preserve">
<source>not synchronized</source>
<target>nincs szinkronizálva</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="observer" xml:space="preserve">
<source>observer</source>
<target>megfigyelő</target>
@ -9743,6 +9900,7 @@ pref value</note>
<target>kikapcsolva</target>
<note>enabled status
group pref value
member criteria value
time to disappear</note>
</trans-unit>
<trans-unit id="offered %@" xml:space="preserve">
@ -9795,6 +9953,11 @@ time to disappear</note>
<target>jóváhagyásra vár</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="pending review" xml:space="preserve">
<source>pending review</source>
<target>függőben lévő áttekintés</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="quantum resistant e2e encryption" xml:space="preserve">
<source>quantum resistant e2e encryption</source>
<target>végpontok közötti kvantumbiztos titkosítás</target>
@ -9835,6 +9998,11 @@ time to disappear</note>
<target>eltávolította a kapcsolattartási címet</target>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="removed from group" xml:space="preserve">
<source>removed from group</source>
<target>eltávolítva a csoportból</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="removed profile picture" xml:space="preserve">
<source>removed profile picture</source>
<target>eltávolította a profilképét</target>
@ -9845,11 +10013,26 @@ time to disappear</note>
<target>eltávolította Önt</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="request to join rejected" xml:space="preserve">
<source>request to join rejected</source>
<target>csatlakozási kérelem elutasítva</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="requested to connect" xml:space="preserve">
<source>requested to connect</source>
<target>Függőben lévő meghívási kérelem</target>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="review" xml:space="preserve">
<source>review</source>
<target>áttekintés</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="reviewed by admins" xml:space="preserve">
<source>reviewed by admins</source>
<target>áttekintve a moderátorok által</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="saved" xml:space="preserve">
<source>saved</source>
<target>mentett</target>
@ -10039,6 +10222,11 @@ utoljára fogadott üzenet: %2$@</target>
<target>Ön</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="you accepted this member" xml:space="preserve">
<source>you accepted this member</source>
<target>Ön befogadta ezt a tagot</target>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="you are invited to group" xml:space="preserve">
<source>you are invited to group</source>
<target>Ön meghívást kapott a csoportba</target>

View file

@ -565,6 +565,16 @@ time interval</note>
accept incoming call via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept as member" xml:space="preserve">
<source>Accept as member</source>
<target>Accetta come membro</target>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept as observer" xml:space="preserve">
<source>Accept as observer</source>
<target>Accetta come osservatore</target>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept conditions" xml:space="preserve">
<source>Accept conditions</source>
<target>Accetta le condizioni</target>
@ -586,6 +596,11 @@ swipe action</note>
<note>accept contact request via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept member" xml:space="preserve">
<source>Accept member</source>
<target>Accetta membro</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Accepted conditions" xml:space="preserve">
<source>Accepted conditions</source>
<target>Condizioni accettate</target>
@ -1582,11 +1597,26 @@ set passcode view</note>
<target>La chat verrà eliminata solo per te, non è reversibile!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat with admins" xml:space="preserve">
<source>Chat with admins</source>
<target>Chat con amministratori</target>
<note>chat toolbar</note>
</trans-unit>
<trans-unit id="Chat with member" xml:space="preserve">
<source>Chat with member</source>
<target>Chatta con il membro</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats" xml:space="preserve">
<source>Chats</source>
<target>Chat</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats with members" xml:space="preserve">
<source>Chats with members</source>
<target>Chat con membri</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Check messages every 20 min." xml:space="preserve">
<source>Check messages every 20 min.</source>
<target>Controlla i messaggi ogni 20 min.</target>
@ -2402,6 +2432,11 @@ swipe action</note>
<target>Eliminare il profilo di chat?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete chat with member?" xml:space="preserve">
<source>Delete chat with member?</source>
<target>Eliminare la chat con il membro?</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Delete chat?" xml:space="preserve">
<source>Delete chat?</source>
<target>Eliminare la chat?</target>
@ -2825,7 +2860,7 @@ swipe action</note>
<trans-unit id="Don't show again" xml:space="preserve">
<source>Don't show again</source>
<target>Non mostrare più</target>
<note>No comment provided by engineer.</note>
<note>alert action</note>
</trans-unit>
<trans-unit id="Done" xml:space="preserve">
<source>Done</source>
@ -3143,6 +3178,11 @@ chat item action</note>
<target>Errore nell'accettazione della richiesta di contatto</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error accepting member" xml:space="preserve">
<source>Error accepting member</source>
<target>Errore di accettazione del membro</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error adding member(s)" xml:space="preserve">
<source>Error adding member(s)</source>
<target>Errore di aggiunta membro/i</target>
@ -3238,6 +3278,11 @@ chat item action</note>
<target>Errore nell'eliminazione del database della chat</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error deleting chat with member" xml:space="preserve">
<source>Error deleting chat with member</source>
<target>Errore di eliminazione della chat con il membro</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error deleting chat!" xml:space="preserve">
<source>Error deleting chat!</source>
<target>Errore nell'eliminazione della chat!</target>
@ -3346,7 +3391,7 @@ chat item action</note>
<trans-unit id="Error removing member" xml:space="preserve">
<source>Error removing member</source>
<target>Errore nella rimozione del membro</target>
<note>No comment provided by engineer.</note>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error reordering lists" xml:space="preserve">
<source>Error reordering lists</source>
@ -4730,6 +4775,11 @@ Questo è il tuo link per il gruppo %@!</target>
<target>Membro</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member admission" xml:space="preserve">
<source>Member admission</source>
<target>Ammissione del membro</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member inactive" xml:space="preserve">
<source>Member inactive</source>
<target>Membro inattivo</target>
@ -4765,6 +4815,11 @@ Questo è il tuo link per il gruppo %@!</target>
<target>Il membro verrà rimosso dal gruppo, non è reversibile!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member will join the group, accept member?" xml:space="preserve">
<source>Member will join the group, accept member?</source>
<target>Il membro entrerà nel gruppo, accettarlo?</target>
<note>alert message</note>
</trans-unit>
<trans-unit id="Members can add message reactions." xml:space="preserve">
<source>Members can add message reactions.</source>
<target>I membri del gruppo possono aggiungere reazioni ai messaggi.</target>
@ -5185,6 +5240,11 @@ Questo è il tuo link per il gruppo %@!</target>
<target>Nuovo ruolo del membro</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New member wants to join the group." xml:space="preserve">
<source>New member wants to join the group.</source>
<target>Un nuovo membro vuole entrare nel gruppo.</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="New message" xml:space="preserve">
<source>New message</source>
<target>Nuovo messaggio</target>
@ -5225,6 +5285,11 @@ Questo è il tuo link per il gruppo %@!</target>
<target>Nessuna chat nell'elenco %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No chats with members" xml:space="preserve">
<source>No chats with members</source>
<target>Nessuna chat con membri</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No contacts selected" xml:space="preserve">
<source>No contacts selected</source>
<target>Nessun contatto selezionato</target>
@ -5417,7 +5482,8 @@ Questo è il tuo link per il gruppo %@!</target>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
<target>Ok</target>
<note>alert button</note>
<note>alert action
alert button</note>
</trans-unit>
<trans-unit id="Old database" xml:space="preserve">
<source>Old database</source>
@ -5575,6 +5641,7 @@ Richiede l'attivazione della VPN.</target>
</trans-unit>
<trans-unit id="Open link?" xml:space="preserve">
<source>Open link?</source>
<target>Aprire il link?</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Open migration to another device" xml:space="preserve">
@ -5828,6 +5895,11 @@ Errore: %@</target>
<target>Prova a disattivare e riattivare le notifiche.</target>
<note>token info</note>
</trans-unit>
<trans-unit id="Please wait for group moderators to review your request to join the group." xml:space="preserve">
<source>Please wait for group moderators to review your request to join the group.</source>
<target>Attendi che i moderatori del gruppo revisionino la tua richiesta di entrare nel gruppo.</target>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="Please wait for token activation to complete." xml:space="preserve">
<source>Please wait for token activation to complete.</source>
<target>Attendi il completamento dell'attivazione del token.</target>
@ -6281,6 +6353,11 @@ swipe action</note>
<target>Rifiuta la richiesta di contatto</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Reject member?" xml:space="preserve">
<source>Reject member?</source>
<target>Rifiutare il membro?</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Relay server is only used if necessary. Another party can observe your IP address." xml:space="preserve">
<source>Relay server is only used if necessary. Another party can observe your IP address.</source>
<target>Il server relay viene usato solo se necessario. Un altro utente può osservare il tuo indirizzo IP.</target>
@ -6391,6 +6468,11 @@ swipe action</note>
<target>Motivo della segnalazione?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Report sent to moderators" xml:space="preserve">
<source>Report sent to moderators</source>
<target>Segnalazione inviata ai moderatori</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Report spam: only group moderators will see it." xml:space="preserve">
<source>Report spam: only group moderators will see it.</source>
<target>Segnala spam: solo i moderatori del gruppo lo vedranno.</target>
@ -6506,6 +6588,16 @@ swipe action</note>
<target>Leggi le condizioni</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Review members" xml:space="preserve">
<source>Review members</source>
<target>Revisiona i membri</target>
<note>admission stage</note>
</trans-unit>
<trans-unit id="Review members before admitting (&quot;knocking&quot;)." xml:space="preserve">
<source>Review members before admitting ("knocking").</source>
<target>Revisiona i membri prima di ammetterli ("bussare").</target>
<note>admission stage description</note>
</trans-unit>
<trans-unit id="Revoke" xml:space="preserve">
<source>Revoke</source>
<target>Revoca</target>
@ -6562,6 +6654,11 @@ chat item action</note>
<target>Salva (e avvisa i contatti)</target>
<note>alert button</note>
</trans-unit>
<trans-unit id="Save admission settings?" xml:space="preserve">
<source>Save admission settings?</source>
<target>Salvare le impostazioni di ammissione?</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Save and notify contact" xml:space="preserve">
<source>Save and notify contact</source>
<target>Salva e avvisa il contatto</target>
@ -7077,6 +7174,11 @@ chat item action</note>
<target>Impostalo al posto dell'autenticazione di sistema.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set member admission" xml:space="preserve">
<source>Set member admission</source>
<target>Imposta l'ammissione del membro</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set message expiration in chats." xml:space="preserve">
<source>Set message expiration in chats.</source>
<target>Imposta la scadenza dei messaggi nelle chat.</target>
@ -8839,6 +8941,11 @@ Ripetere la richiesta di ingresso?</target>
<target>Puoi vedere di nuovo il link di invito nei dettagli di connessione.</target>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can view your reports in Chat with admins." xml:space="preserve">
<source>You can view your reports in Chat with admins.</source>
<target>Puoi vedere le tue segnalazioni nella chat con gli amministratori.</target>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can't send messages!" xml:space="preserve">
<source>You can't send messages!</source>
<target>Non puoi inviare messaggi!</target>
@ -9141,6 +9248,11 @@ Ripetere la richiesta di connessione?</target>
<target>sopra, quindi scegli:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="accepted %@" xml:space="preserve">
<source>accepted %@</source>
<target>%@ accettato</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="accepted call" xml:space="preserve">
<source>accepted call</source>
<target>chiamata accettata</target>
@ -9151,6 +9263,11 @@ Ripetere la richiesta di connessione?</target>
<target>invito accettato</target>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="accepted you" xml:space="preserve">
<source>accepted you</source>
<target>ti ha accettato/a</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="admin" xml:space="preserve">
<source>admin</source>
<target>amministratore</target>
@ -9171,6 +9288,11 @@ Ripetere la richiesta di connessione?</target>
<target>concordando la crittografia…</target>
<note>chat item text</note>
</trans-unit>
<trans-unit id="all" xml:space="preserve">
<source>all</source>
<target>tutti</target>
<note>member criteria value</note>
</trans-unit>
<trans-unit id="all members" xml:space="preserve">
<source>all members</source>
<target>tutti i membri</target>
@ -9257,6 +9379,11 @@ marked deleted chat item preview text</note>
<target>chiamata…</target>
<note>call status</note>
</trans-unit>
<trans-unit id="can't send messages" xml:space="preserve">
<source>can't send messages</source>
<target>impossibile inviare messaggi</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="cancelled %@" xml:space="preserve">
<source>cancelled %@</source>
<target>annullato %@</target>
@ -9362,6 +9489,16 @@ marked deleted chat item preview text</note>
<target>contatto %1$@ cambiato in %2$@</target>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="contact deleted" xml:space="preserve">
<source>contact deleted</source>
<target>contatto eliminato</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact disabled" xml:space="preserve">
<source>contact disabled</source>
<target>contatto disattivato</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact has e2e encryption" xml:space="preserve">
<source>contact has e2e encryption</source>
<target>il contatto ha la crittografia e2e</target>
@ -9372,6 +9509,11 @@ marked deleted chat item preview text</note>
<target>il contatto non ha la crittografia e2e</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact not ready" xml:space="preserve">
<source>contact not ready</source>
<target>contatto non pronto</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="creator" xml:space="preserve">
<source>creator</source>
<target>creatore</target>
@ -9543,6 +9685,11 @@ pref value</note>
<target>gruppo eliminato</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group is deleted" xml:space="preserve">
<source>group is deleted</source>
<target>il gruppo è eliminato</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group profile updated" xml:space="preserve">
<source>group profile updated</source>
<target>profilo del gruppo aggiornato</target>
@ -9668,6 +9815,11 @@ pref value</note>
<target>si è connesso/a</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="member has old version" xml:space="preserve">
<source>member has old version</source>
<target>il membro ha una versione vecchia</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="message" xml:space="preserve">
<source>message</source>
<target>messaggio</target>
@ -9733,6 +9885,11 @@ pref value</note>
<target>nessun testo</target>
<note>copied message info in history</note>
</trans-unit>
<trans-unit id="not synchronized" xml:space="preserve">
<source>not synchronized</source>
<target>non sincronizzato</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="observer" xml:space="preserve">
<source>observer</source>
<target>osservatore</target>
@ -9743,6 +9900,7 @@ pref value</note>
<target>off</target>
<note>enabled status
group pref value
member criteria value
time to disappear</note>
</trans-unit>
<trans-unit id="offered %@" xml:space="preserve">
@ -9795,6 +9953,11 @@ time to disappear</note>
<target>in attesa di approvazione</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="pending review" xml:space="preserve">
<source>pending review</source>
<target>in attesa di revisione</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="quantum resistant e2e encryption" xml:space="preserve">
<source>quantum resistant e2e encryption</source>
<target>crittografia e2e resistente alla quantistica</target>
@ -9835,6 +9998,11 @@ time to disappear</note>
<target>indirizzo di contatto rimosso</target>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="removed from group" xml:space="preserve">
<source>removed from group</source>
<target>rimosso dal gruppo</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="removed profile picture" xml:space="preserve">
<source>removed profile picture</source>
<target>immagine del profilo rimossa</target>
@ -9845,11 +10013,26 @@ time to disappear</note>
<target>ti ha rimosso/a</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="request to join rejected" xml:space="preserve">
<source>request to join rejected</source>
<target>richiesta di entrare rifiutata</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="requested to connect" xml:space="preserve">
<source>requested to connect</source>
<target>richiesto di connettersi</target>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="review" xml:space="preserve">
<source>review</source>
<target>revisiona</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="reviewed by admins" xml:space="preserve">
<source>reviewed by admins</source>
<target>revisionato dagli amministratori</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="saved" xml:space="preserve">
<source>saved</source>
<target>salvato</target>
@ -10039,6 +10222,11 @@ ultimo msg ricevuto: %2$@</target>
<target>tu</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="you accepted this member" xml:space="preserve">
<source>you accepted this member</source>
<target>hai accettato questo membro</target>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="you are invited to group" xml:space="preserve">
<source>you are invited to group</source>
<target>sei stato/a invitato/a al gruppo</target>

View file

@ -561,6 +561,14 @@ time interval</note>
accept incoming call via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept as member" xml:space="preserve">
<source>Accept as member</source>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept as observer" xml:space="preserve">
<source>Accept as observer</source>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept conditions" xml:space="preserve">
<source>Accept conditions</source>
<note>No comment provided by engineer.</note>
@ -581,6 +589,10 @@ swipe action</note>
<note>accept contact request via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept member" xml:space="preserve">
<source>Accept member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Accepted conditions" xml:space="preserve">
<source>Accepted conditions</source>
<note>No comment provided by engineer.</note>
@ -1488,11 +1500,23 @@ set passcode view</note>
<source>Chat will be deleted for you - this cannot be undone!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat with admins" xml:space="preserve">
<source>Chat with admins</source>
<note>chat toolbar</note>
</trans-unit>
<trans-unit id="Chat with member" xml:space="preserve">
<source>Chat with member</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats" xml:space="preserve">
<source>Chats</source>
<target>チャット</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats with members" xml:space="preserve">
<source>Chats with members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Check messages every 20 min." xml:space="preserve">
<source>Check messages every 20 min.</source>
<note>No comment provided by engineer.</note>
@ -2244,6 +2268,10 @@ swipe action</note>
<target>チャットのプロフィールを削除しますか?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete chat with member?" xml:space="preserve">
<source>Delete chat with member?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Delete chat?" xml:space="preserve">
<source>Delete chat?</source>
<note>No comment provided by engineer.</note>
@ -2637,7 +2665,7 @@ swipe action</note>
<trans-unit id="Don't show again" xml:space="preserve">
<source>Don't show again</source>
<target>次から表示しない</target>
<note>No comment provided by engineer.</note>
<note>alert action</note>
</trans-unit>
<trans-unit id="Done" xml:space="preserve">
<source>Done</source>
@ -2931,6 +2959,10 @@ chat item action</note>
<target>連絡先リクエストの承諾にエラー発生</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error accepting member" xml:space="preserve">
<source>Error accepting member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error adding member(s)" xml:space="preserve">
<source>Error adding member(s)</source>
<target>メンバー追加にエラー発生</target>
@ -3018,6 +3050,10 @@ chat item action</note>
<target>チャットデータベース削除にエラー発生</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error deleting chat with member" xml:space="preserve">
<source>Error deleting chat with member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error deleting chat!" xml:space="preserve">
<source>Error deleting chat!</source>
<target>チャット削除にエラー発生!</target>
@ -3117,7 +3153,7 @@ chat item action</note>
<trans-unit id="Error removing member" xml:space="preserve">
<source>Error removing member</source>
<target>メンバー除名にエラー発生</target>
<note>No comment provided by engineer.</note>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error reordering lists" xml:space="preserve">
<source>Error reordering lists</source>
@ -4384,6 +4420,10 @@ This is your link for group %@!</source>
<target>メンバー</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member admission" xml:space="preserve">
<source>Member admission</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member inactive" xml:space="preserve">
<source>Member inactive</source>
<note>item status text</note>
@ -4415,6 +4455,10 @@ This is your link for group %@!</source>
<target>メンバーをグループから除名する (※元に戻せません※)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member will join the group, accept member?" xml:space="preserve">
<source>Member will join the group, accept member?</source>
<note>alert message</note>
</trans-unit>
<trans-unit id="Members can add message reactions." xml:space="preserve">
<source>Members can add message reactions.</source>
<target>グループメンバーはメッセージへのリアクションを追加できます。</target>
@ -4792,6 +4836,10 @@ This is your link for group %@!</source>
<target>新しいメンバーの役割</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New member wants to join the group." xml:space="preserve">
<source>New member wants to join the group.</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="New message" xml:space="preserve">
<source>New message</source>
<target>新しいメッセージ</target>
@ -4828,6 +4876,10 @@ This is your link for group %@!</source>
<source>No chats in list %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No chats with members" xml:space="preserve">
<source>No chats with members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No contacts selected" xml:space="preserve">
<source>No contacts selected</source>
<target>連絡先が選択されてません</target>
@ -4998,7 +5050,8 @@ This is your link for group %@!</source>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
<target>OK</target>
<note>alert button</note>
<note>alert action
alert button</note>
</trans-unit>
<trans-unit id="Old database" xml:space="preserve">
<source>Old database</source>
@ -5373,6 +5426,10 @@ Error: %@</source>
<source>Please try to disable and re-enable notfications.</source>
<note>token info</note>
</trans-unit>
<trans-unit id="Please wait for group moderators to review your request to join the group." xml:space="preserve">
<source>Please wait for group moderators to review your request to join the group.</source>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="Please wait for token activation to complete." xml:space="preserve">
<source>Please wait for token activation to complete.</source>
<note>token info</note>
@ -5784,6 +5841,10 @@ swipe action</note>
<target>連絡要求を拒否する</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Reject member?" xml:space="preserve">
<source>Reject member?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Relay server is only used if necessary. Another party can observe your IP address." xml:space="preserve">
<source>Relay server is only used if necessary. Another party can observe your IP address.</source>
<target>中継サーバーは必要な場合にのみ使用されます。 別の当事者があなたの IP アドレスを監視できます。</target>
@ -5882,6 +5943,10 @@ swipe action</note>
<source>Report reason?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Report sent to moderators" xml:space="preserve">
<source>Report sent to moderators</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Report spam: only group moderators will see it." xml:space="preserve">
<source>Report spam: only group moderators will see it.</source>
<note>report reason</note>
@ -5985,6 +6050,14 @@ swipe action</note>
<source>Review conditions</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Review members" xml:space="preserve">
<source>Review members</source>
<note>admission stage</note>
</trans-unit>
<trans-unit id="Review members before admitting (&quot;knocking&quot;)." xml:space="preserve">
<source>Review members before admitting ("knocking").</source>
<note>admission stage description</note>
</trans-unit>
<trans-unit id="Revoke" xml:space="preserve">
<source>Revoke</source>
<target>取り消す</target>
@ -6037,6 +6110,10 @@ chat item action</note>
<target>保存(連絡先に通知)</target>
<note>alert button</note>
</trans-unit>
<trans-unit id="Save admission settings?" xml:space="preserve">
<source>Save admission settings?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Save and notify contact" xml:space="preserve">
<source>Save and notify contact</source>
<target>保存して、連絡先にに知らせる</target>
@ -6500,6 +6577,10 @@ chat item action</note>
<target>システム認証の代わりに設定します。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set member admission" xml:space="preserve">
<source>Set member admission</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set message expiration in chats." xml:space="preserve">
<source>Set message expiration in chats.</source>
<note>No comment provided by engineer.</note>
@ -8088,6 +8169,10 @@ Repeat join request?</source>
<source>You can view invitation link again in connection details.</source>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can view your reports in Chat with admins." xml:space="preserve">
<source>You can view your reports in Chat with admins.</source>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can't send messages!" xml:space="preserve">
<source>You can't send messages!</source>
<target>メッセージを送信できませんでした!</target>
@ -8374,6 +8459,10 @@ Repeat connection request?</source>
<target>上で選んでください:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="accepted %@" xml:space="preserve">
<source>accepted %@</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="accepted call" xml:space="preserve">
<source>accepted call</source>
<target>受けた通話</target>
@ -8383,6 +8472,10 @@ Repeat connection request?</source>
<source>accepted invitation</source>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="accepted you" xml:space="preserve">
<source>accepted you</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="admin" xml:space="preserve">
<source>admin</source>
<target>管理者</target>
@ -8402,6 +8495,10 @@ Repeat connection request?</source>
<target>暗号化に同意しています…</target>
<note>chat item text</note>
</trans-unit>
<trans-unit id="all" xml:space="preserve">
<source>all</source>
<note>member criteria value</note>
</trans-unit>
<trans-unit id="all members" xml:space="preserve">
<source>all members</source>
<note>feature role</note>
@ -8479,6 +8576,10 @@ marked deleted chat item preview text</note>
<target>発信中…</target>
<note>call status</note>
</trans-unit>
<trans-unit id="can't send messages" xml:space="preserve">
<source>can't send messages</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="cancelled %@" xml:space="preserve">
<source>cancelled %@</source>
<target>キャンセルされました %@</target>
@ -8582,6 +8683,14 @@ marked deleted chat item preview text</note>
<source>contact %1$@ changed to %2$@</source>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="contact deleted" xml:space="preserve">
<source>contact deleted</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact disabled" xml:space="preserve">
<source>contact disabled</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact has e2e encryption" xml:space="preserve">
<source>contact has e2e encryption</source>
<target>連絡先はエンドツーエンド暗号化があります</target>
@ -8592,6 +8701,10 @@ marked deleted chat item preview text</note>
<target>連絡先はエンドツーエンド暗号化がありません</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact not ready" xml:space="preserve">
<source>contact not ready</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="creator" xml:space="preserve">
<source>creator</source>
<target>作成者</target>
@ -8758,6 +8871,10 @@ pref value</note>
<target>グループ削除済み</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group is deleted" xml:space="preserve">
<source>group is deleted</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group profile updated" xml:space="preserve">
<source>group profile updated</source>
<target>グループのプロフィールが更新されました</target>
@ -8880,6 +8997,10 @@ pref value</note>
<target>接続中</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="member has old version" xml:space="preserve">
<source>member has old version</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="message" xml:space="preserve">
<source>message</source>
<note>No comment provided by engineer.</note>
@ -8943,6 +9064,10 @@ pref value</note>
<target>テキストなし</target>
<note>copied message info in history</note>
</trans-unit>
<trans-unit id="not synchronized" xml:space="preserve">
<source>not synchronized</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="observer" xml:space="preserve">
<source>observer</source>
<target>オブザーバー</target>
@ -8953,6 +9078,7 @@ pref value</note>
<target>オフ</target>
<note>enabled status
group pref value
member criteria value
time to disappear</note>
</trans-unit>
<trans-unit id="offered %@" xml:space="preserve">
@ -9000,6 +9126,10 @@ time to disappear</note>
<source>pending approval</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="pending review" xml:space="preserve">
<source>pending review</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="quantum resistant e2e encryption" xml:space="preserve">
<source>quantum resistant e2e encryption</source>
<note>chat item text</note>
@ -9037,6 +9167,10 @@ time to disappear</note>
<source>removed contact address</source>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="removed from group" xml:space="preserve">
<source>removed from group</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="removed profile picture" xml:space="preserve">
<source>removed profile picture</source>
<note>profile update event chat item</note>
@ -9046,10 +9180,22 @@ time to disappear</note>
<target>あなたを除名しました</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="request to join rejected" xml:space="preserve">
<source>request to join rejected</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="requested to connect" xml:space="preserve">
<source>requested to connect</source>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="review" xml:space="preserve">
<source>review</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="reviewed by admins" xml:space="preserve">
<source>reviewed by admins</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="saved" xml:space="preserve">
<source>saved</source>
<note>No comment provided by engineer.</note>
@ -9220,6 +9366,10 @@ last received msg: %2$@</source>
<source>you</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="you accepted this member" xml:space="preserve">
<source>you accepted this member</source>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="you are invited to group" xml:space="preserve">
<source>you are invited to group</source>
<target>グループ招待が届きました</target>

View file

@ -565,6 +565,14 @@ time interval</note>
accept incoming call via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept as member" xml:space="preserve">
<source>Accept as member</source>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept as observer" xml:space="preserve">
<source>Accept as observer</source>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept conditions" xml:space="preserve">
<source>Accept conditions</source>
<target>Accepteer voorwaarden</target>
@ -586,6 +594,10 @@ swipe action</note>
<note>accept contact request via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept member" xml:space="preserve">
<source>Accept member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Accepted conditions" xml:space="preserve">
<source>Accepted conditions</source>
<target>Geaccepteerde voorwaarden</target>
@ -1581,11 +1593,23 @@ set passcode view</note>
<target>De chat wordt voor je verwijderd - dit kan niet ongedaan worden gemaakt!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat with admins" xml:space="preserve">
<source>Chat with admins</source>
<note>chat toolbar</note>
</trans-unit>
<trans-unit id="Chat with member" xml:space="preserve">
<source>Chat with member</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats" xml:space="preserve">
<source>Chats</source>
<target>Chats</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats with members" xml:space="preserve">
<source>Chats with members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Check messages every 20 min." xml:space="preserve">
<source>Check messages every 20 min.</source>
<target>Controleer uw berichten elke 20 minuten.</target>
@ -2401,6 +2425,10 @@ swipe action</note>
<target>Chatprofiel verwijderen?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete chat with member?" xml:space="preserve">
<source>Delete chat with member?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Delete chat?" xml:space="preserve">
<source>Delete chat?</source>
<target>Chat verwijderen?</target>
@ -2824,7 +2852,7 @@ swipe action</note>
<trans-unit id="Don't show again" xml:space="preserve">
<source>Don't show again</source>
<target>Niet meer weergeven</target>
<note>No comment provided by engineer.</note>
<note>alert action</note>
</trans-unit>
<trans-unit id="Done" xml:space="preserve">
<source>Done</source>
@ -3142,6 +3170,10 @@ chat item action</note>
<target>Fout bij het accepteren van een contactverzoek</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error accepting member" xml:space="preserve">
<source>Error accepting member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error adding member(s)" xml:space="preserve">
<source>Error adding member(s)</source>
<target>Fout bij het toevoegen van leden</target>
@ -3237,6 +3269,10 @@ chat item action</note>
<target>Fout bij het verwijderen van de chat database</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error deleting chat with member" xml:space="preserve">
<source>Error deleting chat with member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error deleting chat!" xml:space="preserve">
<source>Error deleting chat!</source>
<target>Fout bij verwijderen gesprek!</target>
@ -3345,7 +3381,7 @@ chat item action</note>
<trans-unit id="Error removing member" xml:space="preserve">
<source>Error removing member</source>
<target>Fout bij verwijderen van lid</target>
<note>No comment provided by engineer.</note>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error reordering lists" xml:space="preserve">
<source>Error reordering lists</source>
@ -4729,6 +4765,10 @@ Dit is jouw link voor groep %@!</target>
<target>Lid</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member admission" xml:space="preserve">
<source>Member admission</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member inactive" xml:space="preserve">
<source>Member inactive</source>
<target>Lid inactief</target>
@ -4764,6 +4804,10 @@ Dit is jouw link voor groep %@!</target>
<target>Lid wordt uit de groep verwijderd, dit kan niet ongedaan worden gemaakt!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member will join the group, accept member?" xml:space="preserve">
<source>Member will join the group, accept member?</source>
<note>alert message</note>
</trans-unit>
<trans-unit id="Members can add message reactions." xml:space="preserve">
<source>Members can add message reactions.</source>
<target>Groepsleden kunnen bericht reacties toevoegen.</target>
@ -5184,6 +5228,10 @@ Dit is jouw link voor groep %@!</target>
<target>Nieuwe leden rol</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New member wants to join the group." xml:space="preserve">
<source>New member wants to join the group.</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="New message" xml:space="preserve">
<source>New message</source>
<target>nieuw bericht</target>
@ -5224,6 +5272,10 @@ Dit is jouw link voor groep %@!</target>
<target>Geen chats in lijst %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No chats with members" xml:space="preserve">
<source>No chats with members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No contacts selected" xml:space="preserve">
<source>No contacts selected</source>
<target>Geen contacten geselecteerd</target>
@ -5416,7 +5468,8 @@ Dit is jouw link voor groep %@!</target>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
<target>OK</target>
<note>alert button</note>
<note>alert action
alert button</note>
</trans-unit>
<trans-unit id="Old database" xml:space="preserve">
<source>Old database</source>
@ -5827,6 +5880,10 @@ Fout: %@</target>
<target>Probeer meldingen uit en weer in te schakelen.</target>
<note>token info</note>
</trans-unit>
<trans-unit id="Please wait for group moderators to review your request to join the group." xml:space="preserve">
<source>Please wait for group moderators to review your request to join the group.</source>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="Please wait for token activation to complete." xml:space="preserve">
<source>Please wait for token activation to complete.</source>
<target>Wacht tot de tokenactivering voltooid is.</target>
@ -6280,6 +6337,10 @@ swipe action</note>
<target>Contactverzoek afwijzen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Reject member?" xml:space="preserve">
<source>Reject member?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Relay server is only used if necessary. Another party can observe your IP address." xml:space="preserve">
<source>Relay server is only used if necessary. Another party can observe your IP address.</source>
<target>Relay server wordt alleen gebruikt als dat nodig is. Een andere partij kan uw IP-adres zien.</target>
@ -6390,6 +6451,10 @@ swipe action</note>
<target>Reden melding?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Report sent to moderators" xml:space="preserve">
<source>Report sent to moderators</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Report spam: only group moderators will see it." xml:space="preserve">
<source>Report spam: only group moderators will see it.</source>
<target>Spam melden: alleen groepsmoderators kunnen het zien.</target>
@ -6505,6 +6570,14 @@ swipe action</note>
<target>Voorwaarden bekijken</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Review members" xml:space="preserve">
<source>Review members</source>
<note>admission stage</note>
</trans-unit>
<trans-unit id="Review members before admitting (&quot;knocking&quot;)." xml:space="preserve">
<source>Review members before admitting ("knocking").</source>
<note>admission stage description</note>
</trans-unit>
<trans-unit id="Revoke" xml:space="preserve">
<source>Revoke</source>
<target>Intrekken</target>
@ -6561,6 +6634,10 @@ chat item action</note>
<target>Bewaar (en informeer contacten)</target>
<note>alert button</note>
</trans-unit>
<trans-unit id="Save admission settings?" xml:space="preserve">
<source>Save admission settings?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Save and notify contact" xml:space="preserve">
<source>Save and notify contact</source>
<target>Opslaan en Contact melden</target>
@ -7076,6 +7153,10 @@ chat item action</note>
<target>Stel het in in plaats van systeemverificatie.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set member admission" xml:space="preserve">
<source>Set member admission</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set message expiration in chats." xml:space="preserve">
<source>Set message expiration in chats.</source>
<target>Stel de berichtvervaldatum in chats in.</target>
@ -8832,6 +8913,10 @@ Deelnameverzoek herhalen?</target>
<target>U kunt de uitnodigingslink opnieuw bekijken in de verbindingsdetails.</target>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can view your reports in Chat with admins." xml:space="preserve">
<source>You can view your reports in Chat with admins.</source>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can't send messages!" xml:space="preserve">
<source>You can't send messages!</source>
<target>Je kunt geen berichten versturen!</target>
@ -9134,6 +9219,10 @@ Verbindingsverzoek herhalen?</target>
<target>hier boven, kies dan:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="accepted %@" xml:space="preserve">
<source>accepted %@</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="accepted call" xml:space="preserve">
<source>accepted call</source>
<target>geaccepteerde oproep</target>
@ -9144,6 +9233,10 @@ Verbindingsverzoek herhalen?</target>
<target>geaccepteerde uitnodiging</target>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="accepted you" xml:space="preserve">
<source>accepted you</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="admin" xml:space="preserve">
<source>admin</source>
<target>Beheerder</target>
@ -9164,6 +9257,10 @@ Verbindingsverzoek herhalen?</target>
<target>versleuteling overeenkomen…</target>
<note>chat item text</note>
</trans-unit>
<trans-unit id="all" xml:space="preserve">
<source>all</source>
<note>member criteria value</note>
</trans-unit>
<trans-unit id="all members" xml:space="preserve">
<source>all members</source>
<target>alle leden</target>
@ -9250,6 +9347,10 @@ marked deleted chat item preview text</note>
<target>bellen…</target>
<note>call status</note>
</trans-unit>
<trans-unit id="can't send messages" xml:space="preserve">
<source>can't send messages</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="cancelled %@" xml:space="preserve">
<source>cancelled %@</source>
<target>geannuleerd %@</target>
@ -9355,6 +9456,14 @@ marked deleted chat item preview text</note>
<target>contactpersoon %1$@ gewijzigd in %2$@</target>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="contact deleted" xml:space="preserve">
<source>contact deleted</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact disabled" xml:space="preserve">
<source>contact disabled</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact has e2e encryption" xml:space="preserve">
<source>contact has e2e encryption</source>
<target>contact heeft e2e-codering</target>
@ -9365,6 +9474,10 @@ marked deleted chat item preview text</note>
<target>contact heeft geen e2e versleuteling</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact not ready" xml:space="preserve">
<source>contact not ready</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="creator" xml:space="preserve">
<source>creator</source>
<target>creator</target>
@ -9536,6 +9649,10 @@ pref value</note>
<target>groep verwijderd</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group is deleted" xml:space="preserve">
<source>group is deleted</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group profile updated" xml:space="preserve">
<source>group profile updated</source>
<target>groep profiel bijgewerkt</target>
@ -9661,6 +9778,10 @@ pref value</note>
<target>is toegetreden</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="member has old version" xml:space="preserve">
<source>member has old version</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="message" xml:space="preserve">
<source>message</source>
<target>bericht</target>
@ -9726,6 +9847,10 @@ pref value</note>
<target>geen tekst</target>
<note>copied message info in history</note>
</trans-unit>
<trans-unit id="not synchronized" xml:space="preserve">
<source>not synchronized</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="observer" xml:space="preserve">
<source>observer</source>
<target>Waarnemer</target>
@ -9736,6 +9861,7 @@ pref value</note>
<target>uit</target>
<note>enabled status
group pref value
member criteria value
time to disappear</note>
</trans-unit>
<trans-unit id="offered %@" xml:space="preserve">
@ -9788,6 +9914,10 @@ time to disappear</note>
<target>in afwachting van goedkeuring</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="pending review" xml:space="preserve">
<source>pending review</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="quantum resistant e2e encryption" xml:space="preserve">
<source>quantum resistant e2e encryption</source>
<target>quantum bestendige e2e-codering</target>
@ -9828,6 +9958,10 @@ time to disappear</note>
<target>contactadres verwijderd</target>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="removed from group" xml:space="preserve">
<source>removed from group</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="removed profile picture" xml:space="preserve">
<source>removed profile picture</source>
<target>profielfoto verwijderd</target>
@ -9838,11 +9972,23 @@ time to disappear</note>
<target>heeft je verwijderd</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="request to join rejected" xml:space="preserve">
<source>request to join rejected</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="requested to connect" xml:space="preserve">
<source>requested to connect</source>
<target>verzocht om verbinding te maken</target>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="review" xml:space="preserve">
<source>review</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="reviewed by admins" xml:space="preserve">
<source>reviewed by admins</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="saved" xml:space="preserve">
<source>saved</source>
<target>opgeslagen</target>
@ -10032,6 +10178,10 @@ laatst ontvangen bericht: %2$@</target>
<target>jij</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="you accepted this member" xml:space="preserve">
<source>you accepted this member</source>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="you are invited to group" xml:space="preserve">
<source>you are invited to group</source>
<target>je bent uitgenodigd voor de groep</target>

View file

@ -565,6 +565,14 @@ time interval</note>
accept incoming call via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept as member" xml:space="preserve">
<source>Accept as member</source>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept as observer" xml:space="preserve">
<source>Accept as observer</source>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept conditions" xml:space="preserve">
<source>Accept conditions</source>
<target>Zaakceptuj warunki</target>
@ -586,6 +594,10 @@ swipe action</note>
<note>accept contact request via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept member" xml:space="preserve">
<source>Accept member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Accepted conditions" xml:space="preserve">
<source>Accepted conditions</source>
<target>Zaakceptowano warunki</target>
@ -1575,11 +1587,23 @@ set passcode view</note>
<target>Czat zostanie usunięty dla Ciebie tej operacji nie można cofnąć!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat with admins" xml:space="preserve">
<source>Chat with admins</source>
<note>chat toolbar</note>
</trans-unit>
<trans-unit id="Chat with member" xml:space="preserve">
<source>Chat with member</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats" xml:space="preserve">
<source>Chats</source>
<target>Czaty</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats with members" xml:space="preserve">
<source>Chats with members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Check messages every 20 min." xml:space="preserve">
<source>Check messages every 20 min.</source>
<target>Sprawdzaj wiadomości co 20 min.</target>
@ -2374,6 +2398,10 @@ swipe action</note>
<target>Usunąć profil czatu?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete chat with member?" xml:space="preserve">
<source>Delete chat with member?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Delete chat?" xml:space="preserve">
<source>Delete chat?</source>
<note>No comment provided by engineer.</note>
@ -2787,7 +2815,7 @@ swipe action</note>
<trans-unit id="Don't show again" xml:space="preserve">
<source>Don't show again</source>
<target>Nie pokazuj ponownie</target>
<note>No comment provided by engineer.</note>
<note>alert action</note>
</trans-unit>
<trans-unit id="Done" xml:space="preserve">
<source>Done</source>
@ -3100,6 +3128,10 @@ chat item action</note>
<target>Błąd przyjmowania prośby o kontakt</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error accepting member" xml:space="preserve">
<source>Error accepting member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error adding member(s)" xml:space="preserve">
<source>Error adding member(s)</source>
<target>Błąd dodawania członka(ów)</target>
@ -3191,6 +3223,10 @@ chat item action</note>
<target>Błąd usuwania bazy danych czatu</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error deleting chat with member" xml:space="preserve">
<source>Error deleting chat with member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error deleting chat!" xml:space="preserve">
<source>Error deleting chat!</source>
<target>Błąd usuwania czatu!</target>
@ -3297,7 +3333,7 @@ chat item action</note>
<trans-unit id="Error removing member" xml:space="preserve">
<source>Error removing member</source>
<target>Błąd usuwania członka</target>
<note>No comment provided by engineer.</note>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error reordering lists" xml:space="preserve">
<source>Error reordering lists</source>
@ -4641,6 +4677,10 @@ To jest twój link do grupy %@!</target>
<target>Członek</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member admission" xml:space="preserve">
<source>Member admission</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member inactive" xml:space="preserve">
<source>Member inactive</source>
<target>Członek nieaktywny</target>
@ -4673,6 +4713,10 @@ To jest twój link do grupy %@!</target>
<target>Członek zostanie usunięty z grupy - nie można tego cofnąć!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member will join the group, accept member?" xml:space="preserve">
<source>Member will join the group, accept member?</source>
<note>alert message</note>
</trans-unit>
<trans-unit id="Members can add message reactions." xml:space="preserve">
<source>Members can add message reactions.</source>
<target>Członkowie grupy mogą dodawać reakcje wiadomości.</target>
@ -5083,6 +5127,10 @@ To jest twój link do grupy %@!</target>
<target>Nowa rola członka</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New member wants to join the group." xml:space="preserve">
<source>New member wants to join the group.</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="New message" xml:space="preserve">
<source>New message</source>
<target>Nowa wiadomość</target>
@ -5119,6 +5167,10 @@ To jest twój link do grupy %@!</target>
<source>No chats in list %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No chats with members" xml:space="preserve">
<source>No chats with members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No contacts selected" xml:space="preserve">
<source>No contacts selected</source>
<target>Nie wybrano kontaktów</target>
@ -5298,7 +5350,8 @@ To jest twój link do grupy %@!</target>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
<target>Ok</target>
<note>alert button</note>
<note>alert action
alert button</note>
</trans-unit>
<trans-unit id="Old database" xml:space="preserve">
<source>Old database</source>
@ -5698,6 +5751,10 @@ Błąd: %@</target>
<source>Please try to disable and re-enable notfications.</source>
<note>token info</note>
</trans-unit>
<trans-unit id="Please wait for group moderators to review your request to join the group." xml:space="preserve">
<source>Please wait for group moderators to review your request to join the group.</source>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="Please wait for token activation to complete." xml:space="preserve">
<source>Please wait for token activation to complete.</source>
<note>token info</note>
@ -6140,6 +6197,10 @@ swipe action</note>
<target>Odrzuć prośbę kontaktu</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Reject member?" xml:space="preserve">
<source>Reject member?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Relay server is only used if necessary. Another party can observe your IP address." xml:space="preserve">
<source>Relay server is only used if necessary. Another party can observe your IP address.</source>
<target>Serwer przekaźnikowy jest używany tylko w razie potrzeby. Inna strona może obserwować Twój adres IP.</target>
@ -6245,6 +6306,10 @@ swipe action</note>
<source>Report reason?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Report sent to moderators" xml:space="preserve">
<source>Report sent to moderators</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Report spam: only group moderators will see it." xml:space="preserve">
<source>Report spam: only group moderators will see it.</source>
<note>report reason</note>
@ -6354,6 +6419,14 @@ swipe action</note>
<source>Review conditions</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Review members" xml:space="preserve">
<source>Review members</source>
<note>admission stage</note>
</trans-unit>
<trans-unit id="Review members before admitting (&quot;knocking&quot;)." xml:space="preserve">
<source>Review members before admitting ("knocking").</source>
<note>admission stage description</note>
</trans-unit>
<trans-unit id="Revoke" xml:space="preserve">
<source>Revoke</source>
<target>Odwołaj</target>
@ -6410,6 +6483,10 @@ chat item action</note>
<target>Zapisz (i powiadom kontakty)</target>
<note>alert button</note>
</trans-unit>
<trans-unit id="Save admission settings?" xml:space="preserve">
<source>Save admission settings?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Save and notify contact" xml:space="preserve">
<source>Save and notify contact</source>
<target>Zapisz i powiadom kontakt</target>
@ -6918,6 +6995,10 @@ chat item action</note>
<target>Ustaw go zamiast uwierzytelniania systemowego.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set member admission" xml:space="preserve">
<source>Set member admission</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set message expiration in chats." xml:space="preserve">
<source>Set message expiration in chats.</source>
<note>No comment provided by engineer.</note>
@ -8630,6 +8711,10 @@ Powtórzyć prośbę dołączenia?</target>
<target>Możesz zobaczyć link zaproszenia ponownie w szczegółach połączenia.</target>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can view your reports in Chat with admins." xml:space="preserve">
<source>You can view your reports in Chat with admins.</source>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can't send messages!" xml:space="preserve">
<source>You can't send messages!</source>
<target>Nie możesz wysyłać wiadomości!</target>
@ -8930,6 +9015,10 @@ Powtórzyć prośbę połączenia?</target>
<target>powyżej, a następnie wybierz:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="accepted %@" xml:space="preserve">
<source>accepted %@</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="accepted call" xml:space="preserve">
<source>accepted call</source>
<target>zaakceptowane połączenie</target>
@ -8939,6 +9028,10 @@ Powtórzyć prośbę połączenia?</target>
<source>accepted invitation</source>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="accepted you" xml:space="preserve">
<source>accepted you</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="admin" xml:space="preserve">
<source>admin</source>
<target>administrator</target>
@ -8959,6 +9052,10 @@ Powtórzyć prośbę połączenia?</target>
<target>uzgadnianie szyfrowania…</target>
<note>chat item text</note>
</trans-unit>
<trans-unit id="all" xml:space="preserve">
<source>all</source>
<note>member criteria value</note>
</trans-unit>
<trans-unit id="all members" xml:space="preserve">
<source>all members</source>
<target>wszyscy członkowie</target>
@ -9044,6 +9141,10 @@ marked deleted chat item preview text</note>
<target>dzwonie…</target>
<note>call status</note>
</trans-unit>
<trans-unit id="can't send messages" xml:space="preserve">
<source>can't send messages</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="cancelled %@" xml:space="preserve">
<source>cancelled %@</source>
<target>anulowany %@</target>
@ -9149,6 +9250,14 @@ marked deleted chat item preview text</note>
<target>kontakt %1$@ zmieniony na %2$@</target>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="contact deleted" xml:space="preserve">
<source>contact deleted</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact disabled" xml:space="preserve">
<source>contact disabled</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact has e2e encryption" xml:space="preserve">
<source>contact has e2e encryption</source>
<target>kontakt posiada szyfrowanie e2e</target>
@ -9159,6 +9268,10 @@ marked deleted chat item preview text</note>
<target>kontakt nie posiada szyfrowania e2e</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact not ready" xml:space="preserve">
<source>contact not ready</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="creator" xml:space="preserve">
<source>creator</source>
<target>twórca</target>
@ -9330,6 +9443,10 @@ pref value</note>
<target>grupa usunięta</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group is deleted" xml:space="preserve">
<source>group is deleted</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group profile updated" xml:space="preserve">
<source>group profile updated</source>
<target>zaktualizowano profil grupy</target>
@ -9455,6 +9572,10 @@ pref value</note>
<target>połączony</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="member has old version" xml:space="preserve">
<source>member has old version</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="message" xml:space="preserve">
<source>message</source>
<target>wiadomość</target>
@ -9519,6 +9640,10 @@ pref value</note>
<target>brak tekstu</target>
<note>copied message info in history</note>
</trans-unit>
<trans-unit id="not synchronized" xml:space="preserve">
<source>not synchronized</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="observer" xml:space="preserve">
<source>observer</source>
<target>obserwator</target>
@ -9529,6 +9654,7 @@ pref value</note>
<target>wyłączony</target>
<note>enabled status
group pref value
member criteria value
time to disappear</note>
</trans-unit>
<trans-unit id="offered %@" xml:space="preserve">
@ -9579,6 +9705,10 @@ time to disappear</note>
<source>pending approval</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="pending review" xml:space="preserve">
<source>pending review</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="quantum resistant e2e encryption" xml:space="preserve">
<source>quantum resistant e2e encryption</source>
<target>kwantowo odporne szyfrowanie e2e</target>
@ -9618,6 +9748,10 @@ time to disappear</note>
<target>usunięto adres kontaktu</target>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="removed from group" xml:space="preserve">
<source>removed from group</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="removed profile picture" xml:space="preserve">
<source>removed profile picture</source>
<target>usunięto zdjęcie profilu</target>
@ -9628,10 +9762,22 @@ time to disappear</note>
<target>usunął cię</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="request to join rejected" xml:space="preserve">
<source>request to join rejected</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="requested to connect" xml:space="preserve">
<source>requested to connect</source>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="review" xml:space="preserve">
<source>review</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="reviewed by admins" xml:space="preserve">
<source>reviewed by admins</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="saved" xml:space="preserve">
<source>saved</source>
<target>zapisane</target>
@ -9821,6 +9967,10 @@ ostatnia otrzymana wiadomość: %2$@</target>
<target>Ty</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="you accepted this member" xml:space="preserve">
<source>you accepted this member</source>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="you are invited to group" xml:space="preserve">
<source>you are invited to group</source>
<target>jesteś zaproszony do grupy</target>

View file

@ -167,7 +167,7 @@
</trans-unit>
<trans-unit id="%d hours" xml:space="preserve">
<source>%d hours</source>
<target>%d ч.</target>
<target>%d час.</target>
<note>time interval</note>
</trans-unit>
<trans-unit id="%d messages not forwarded" xml:space="preserve">
@ -565,6 +565,16 @@ time interval</note>
accept incoming call via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept as member" xml:space="preserve">
<source>Accept as member</source>
<target>Принять в группу</target>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept as observer" xml:space="preserve">
<source>Accept as observer</source>
<target>Принять как читателя</target>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept conditions" xml:space="preserve">
<source>Accept conditions</source>
<target>Принять условия</target>
@ -586,6 +596,11 @@ swipe action</note>
<note>accept contact request via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept member" xml:space="preserve">
<source>Accept member</source>
<target>Принять члена</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Accepted conditions" xml:space="preserve">
<source>Accepted conditions</source>
<target>Принятые условия</target>
@ -798,6 +813,7 @@ swipe action</note>
</trans-unit>
<trans-unit id="All servers" xml:space="preserve">
<source>All servers</source>
<target>Все серверы</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="All your contacts will remain connected." xml:space="preserve">
@ -1581,11 +1597,26 @@ set passcode view</note>
<target>Разговор будет удален для Вас - это действие нельзя отменить!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat with admins" xml:space="preserve">
<source>Chat with admins</source>
<target>Чат с админами</target>
<note>chat toolbar</note>
</trans-unit>
<trans-unit id="Chat with member" xml:space="preserve">
<source>Chat with member</source>
<target>Чат с членом группы</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats" xml:space="preserve">
<source>Chats</source>
<target>Чаты</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats with members" xml:space="preserve">
<source>Chats with members</source>
<target>Чаты с членами группы</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Check messages every 20 min." xml:space="preserve">
<source>Check messages every 20 min.</source>
<target>Проверять сообщения каждые 20 минут.</target>
@ -2401,6 +2432,11 @@ swipe action</note>
<target>Удалить профиль?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete chat with member?" xml:space="preserve">
<source>Delete chat with member?</source>
<target>Удалить чат с членом группы?</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Delete chat?" xml:space="preserve">
<source>Delete chat?</source>
<target>Удалить разговор?</target>
@ -2688,6 +2724,7 @@ swipe action</note>
</trans-unit>
<trans-unit id="Direct messages between members are prohibited." xml:space="preserve">
<source>Direct messages between members are prohibited.</source>
<target>Прямые сообщения между членами запрещены.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Disable (keep overrides)" xml:space="preserve">
@ -2792,6 +2829,7 @@ swipe action</note>
</trans-unit>
<trans-unit id="Do not send history to new members." xml:space="preserve">
<source>Do not send history to new members.</source>
<target>Не отправлять историю новым членам.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do not use credentials with proxy." xml:space="preserve">
@ -2822,7 +2860,7 @@ swipe action</note>
<trans-unit id="Don't show again" xml:space="preserve">
<source>Don't show again</source>
<target>Не показывать</target>
<note>No comment provided by engineer.</note>
<note>alert action</note>
</trans-unit>
<trans-unit id="Done" xml:space="preserve">
<source>Done</source>
@ -2917,6 +2955,7 @@ chat item action</note>
</trans-unit>
<trans-unit id="Enable Flux in Network &amp; servers settings for better metadata privacy." xml:space="preserve">
<source>Enable Flux in Network &amp; servers settings for better metadata privacy.</source>
<target>Включите Flux в настройках Сеть и серверы для лучшей конфиденциальности метаданных.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Enable SimpleX Lock" xml:space="preserve">
@ -3139,8 +3178,14 @@ chat item action</note>
<target>Ошибка при принятии запроса на соединение</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error accepting member" xml:space="preserve">
<source>Error accepting member</source>
<target>Ошибка вступления члена группы</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error adding member(s)" xml:space="preserve">
<source>Error adding member(s)</source>
<target>Ошибка при добавлении членов группы</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error adding server" xml:space="preserve">
@ -3205,6 +3250,7 @@ chat item action</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 message" xml:space="preserve">
@ -3232,6 +3278,11 @@ chat item action</note>
<target>Ошибка при удалении данных чата</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error deleting chat with member" xml:space="preserve">
<source>Error deleting chat with member</source>
<target>Ошибка при удалении чата с членом группы</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error deleting chat!" xml:space="preserve">
<source>Error deleting chat!</source>
<target>Ошибка при удалении чата!</target>
@ -3339,7 +3390,8 @@ chat item action</note>
</trans-unit>
<trans-unit id="Error removing member" xml:space="preserve">
<source>Error removing member</source>
<note>No comment provided by engineer.</note>
<target>Ошибка при удалении члена группы</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error reordering lists" xml:space="preserve">
<source>Error reordering lists</source>
@ -3403,6 +3455,7 @@ chat item action</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">
@ -3738,6 +3791,7 @@ snd error text</note>
</trans-unit>
<trans-unit id="Fix not supported by group member" xml:space="preserve">
<source>Fix not supported by group member</source>
<target>Починка не поддерживается членом группы.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="For all moderators" xml:space="preserve">
@ -3871,6 +3925,7 @@ Error: %2$@</source>
</trans-unit>
<trans-unit id="Fully decentralized visible only to members." xml:space="preserve">
<source>Fully decentralized visible only to members.</source>
<target>Группа полностью децентрализована она видна только членам.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Fully re-implemented - work in background!" xml:space="preserve">
@ -3980,6 +4035,7 @@ Error: %2$@</source>
</trans-unit>
<trans-unit id="Group profile is stored on members' devices, not on the servers." xml:space="preserve">
<source>Group profile is stored on members' devices, not on the servers.</source>
<target>Профиль группы хранится на устройствах членов, а не на серверах.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Group welcome message" xml:space="preserve">
@ -3989,6 +4045,7 @@ Error: %2$@</source>
</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>Группа будет удалена для всех членов - это действие нельзя отменить!</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">
@ -4053,6 +4110,7 @@ Error: %2$@</source>
</trans-unit>
<trans-unit id="History is not sent to new members." xml:space="preserve">
<source>History is not sent to new members.</source>
<target>История не отправляется новым членам.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="How SimpleX works" xml:space="preserve">
@ -4400,6 +4458,7 @@ More improvements are coming soon!</source>
</trans-unit>
<trans-unit id="Invite members" xml:space="preserve">
<source>Invite members</source>
<target>Пригласить членов группы</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Invite to chat" xml:space="preserve">
@ -4712,10 +4771,17 @@ This is your link for group %@!</source>
</trans-unit>
<trans-unit id="Member" xml:space="preserve">
<source>Member</source>
<target>Член группы</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member admission" xml:space="preserve">
<source>Member admission</source>
<target>Приём членов в группу</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member inactive" xml:space="preserve">
<source>Member inactive</source>
<target>Член неактивен</target>
<note>item status text</note>
</trans-unit>
<trans-unit id="Member reports" xml:space="preserve">
@ -4730,54 +4796,72 @@ This is your link for group %@!</source>
</trans-unit>
<trans-unit id="Member role will be changed to &quot;%@&quot;. All group members will be notified." xml:space="preserve">
<source>Member role will be changed to "%@". All group members will be notified.</source>
<target>Роль члена будет изменена на "%@". Все члены группы получат уведомление.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member role will be changed to &quot;%@&quot;. The member will receive a new invitation." xml:space="preserve">
<source>Member role will be changed to "%@". The member will receive a new invitation.</source>
<target>Роль члена будет изменена на "%@". Будет отправлено новое приглашение.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member will be removed from chat - this cannot be undone!" xml:space="preserve">
<source>Member will be removed from chat - this cannot be undone!</source>
<target>Член будет удален из разговора - это действие нельзя отменить!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member will be removed from group - this cannot be undone!" xml:space="preserve">
<source>Member will be removed from group - this cannot be undone!</source>
<target>Член группы будет удален - это действие нельзя отменить!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member will join the group, accept member?" xml:space="preserve">
<source>Member will join the group, accept member?</source>
<target>Участник хочет присоединиться к группе. Принять?</target>
<note>alert message</note>
</trans-unit>
<trans-unit id="Members can add message reactions." xml:space="preserve">
<source>Members can add message reactions.</source>
<target>Члены могут добавлять реакции на сообщения.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Members can irreversibly delete sent messages. (24 hours)" xml:space="preserve">
<source>Members can irreversibly delete sent messages. (24 hours)</source>
<target>Члены могут необратимо удалять отправленные сообщения. (24 часа)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Members can report messsages to moderators." xml:space="preserve">
<source>Members can report messsages to moderators.</source>
<target>Члены группы могут пожаловаться модераторам.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Members can send SimpleX links." xml:space="preserve">
<source>Members can send SimpleX links.</source>
<target>Члены могут отправлять ссылки SimpleX.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Members can send direct messages." xml:space="preserve">
<source>Members can send direct messages.</source>
<target>Члены могут посылать прямые сообщения.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Members can send disappearing messages." xml:space="preserve">
<source>Members can send disappearing messages.</source>
<target>Члены могут посылать исчезающие сообщения.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Members can send files and media." xml:space="preserve">
<source>Members can send files and media.</source>
<target>Члены могут слать файлы и медиа.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Members can send voice messages." xml:space="preserve">
<source>Members can send voice messages.</source>
<target>Члены могут отправлять голосовые сообщения.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Mention members 👋" xml:space="preserve">
<source>Mention members 👋</source>
<target>Упоминайте участников 👋</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Menus" xml:space="preserve">
@ -4812,6 +4896,7 @@ This is your link for group %@!</source>
</trans-unit>
<trans-unit id="Message may be delivered later if member becomes active." xml:space="preserve">
<source>Message may be delivered later if member becomes active.</source>
<target>Сообщение может быть доставлено позже, если член группы станет активным.</target>
<note>item status description</note>
</trans-unit>
<trans-unit id="Message queue info" xml:space="preserve">
@ -5151,8 +5236,14 @@ This is your link for group %@!</source>
</trans-unit>
<trans-unit id="New member role" xml:space="preserve">
<source>New member role</source>
<target>Роль члена группы</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New member wants to join the group." xml:space="preserve">
<source>New member wants to join the group.</source>
<target>Новый участник хочет присоединиться к группе.</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="New message" xml:space="preserve">
<source>New message</source>
<target>Новое сообщение</target>
@ -5193,6 +5284,11 @@ This is your link for group %@!</source>
<target>Нет чатов в списке %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No chats with members" xml:space="preserve">
<source>No chats with members</source>
<target>Нет чатов с членами группы</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No contacts selected" xml:space="preserve">
<source>No contacts selected</source>
<target>Контакты не выбраны</target>
@ -5367,6 +5463,9 @@ This is your link for group %@!</source>
<source>Now admins can:
- delete members' messages.
- disable members ("observer" role)</source>
<target>Теперь админы могут:
- удалять сообщения членов.
- приостанавливать членов (роль наблюдатель)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="OK" xml:space="preserve">
@ -5382,7 +5481,8 @@ This is your link for group %@!</source>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
<target>Ок</target>
<note>alert button</note>
<note>alert action
alert button</note>
</trans-unit>
<trans-unit id="Old database" xml:space="preserve">
<source>Old database</source>
@ -5540,6 +5640,7 @@ Requires compatible VPN.</source>
</trans-unit>
<trans-unit id="Open link?" xml:space="preserve">
<source>Open link?</source>
<target>Открыть ссылку?</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Open migration to another device" xml:space="preserve">
@ -5656,6 +5757,7 @@ Requires compatible VPN.</source>
</trans-unit>
<trans-unit id="Past member %@" xml:space="preserve">
<source>Past member %@</source>
<target>Бывший член %@</target>
<note>past/unknown group member</note>
</trans-unit>
<trans-unit id="Paste desktop address" xml:space="preserve">
@ -5792,6 +5894,11 @@ Error: %@</source>
<target>Попробуйте выключить и снова включить уведомления.</target>
<note>token info</note>
</trans-unit>
<trans-unit id="Please wait for group moderators to review your request to join the group." xml:space="preserve">
<source>Please wait for group moderators to review your request to join the group.</source>
<target>Пожалуйста, подождите, пока модераторы группы рассмотрят ваш запрос на вступление.</target>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="Please wait for token activation to complete." xml:space="preserve">
<source>Please wait for token activation to complete.</source>
<target>Пожалуйста, дождитесь завершения активации токена.</target>
@ -5964,6 +6071,7 @@ Error: %@</source>
</trans-unit>
<trans-unit id="Prohibit sending direct messages to members." xml:space="preserve">
<source>Prohibit sending direct messages to members.</source>
<target>Запретить посылать прямые сообщения членам группы.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Prohibit sending disappearing messages." xml:space="preserve">
@ -6244,6 +6352,11 @@ swipe action</note>
<target>Отклонить запрос</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Reject member?" xml:space="preserve">
<source>Reject member?</source>
<target>Отклонить участника?</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Relay server is only used if necessary. Another party can observe your IP address." xml:space="preserve">
<source>Relay server is only used if necessary. Another party can observe your IP address.</source>
<target>Relay сервер используется только при необходимости. Другая сторона может видеть Ваш IP адрес.</target>
@ -6271,10 +6384,12 @@ swipe action</note>
</trans-unit>
<trans-unit id="Remove member" xml:space="preserve">
<source>Remove member</source>
<target>Удалить члена группы</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Remove member?" xml:space="preserve">
<source>Remove member?</source>
<target>Удалить члена группы?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Remove passphrase from keychain?" xml:space="preserve">
@ -6352,6 +6467,11 @@ swipe action</note>
<target>Причина сообщения?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Report sent to moderators" xml:space="preserve">
<source>Report sent to moderators</source>
<target>Жалоба отправлена модераторам</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Report spam: only group moderators will see it." xml:space="preserve">
<source>Report spam: only group moderators will see it.</source>
<target>Пожаловаться на спам: увидят только модераторы группы.</target>
@ -6467,6 +6587,16 @@ swipe action</note>
<target>Посмотреть условия</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Review members" xml:space="preserve">
<source>Review members</source>
<target>Одобрять членов</target>
<note>admission stage</note>
</trans-unit>
<trans-unit id="Review members before admitting (&quot;knocking&quot;)." xml:space="preserve">
<source>Review members before admitting ("knocking").</source>
<target>Одобрять членов для вступления в группу.</target>
<note>admission stage description</note>
</trans-unit>
<trans-unit id="Revoke" xml:space="preserve">
<source>Revoke</source>
<target>Отозвать</target>
@ -6523,6 +6653,11 @@ chat item action</note>
<target>Сохранить (и уведомить контакты)</target>
<note>alert button</note>
</trans-unit>
<trans-unit id="Save admission settings?" xml:space="preserve">
<source>Save admission settings?</source>
<target>Сохранить настройки вступления?</target>
<note>alert title</note>
</trans-unit>
<trans-unit id="Save and notify contact" xml:space="preserve">
<source>Save and notify contact</source>
<target>Сохранить и уведомить контакт</target>
@ -6530,6 +6665,7 @@ chat item action</note>
</trans-unit>
<trans-unit id="Save and notify group members" xml:space="preserve">
<source>Save and notify group members</source>
<target>Сохранить и уведомить членов группы</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Save and reconnect" xml:space="preserve">
@ -6814,6 +6950,7 @@ chat item action</note>
</trans-unit>
<trans-unit id="Send up to 100 last messages to new members." xml:space="preserve">
<source>Send up to 100 last messages to new members.</source>
<target>Отправить до 100 последних сообщений новым членам.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Sender cancelled file transfer." xml:space="preserve">
@ -7036,6 +7173,11 @@ chat item action</note>
<target>Установите код вместо системной аутентификации.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set member admission" xml:space="preserve">
<source>Set member admission</source>
<target>Приём членов в группу</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set message expiration in chats." xml:space="preserve">
<source>Set message expiration in chats.</source>
<target>Установите срок хранения сообщений в чатах.</target>
@ -7058,6 +7200,7 @@ chat item action</note>
</trans-unit>
<trans-unit id="Set the message shown to new members!" xml:space="preserve">
<source>Set the message shown to new members!</source>
<target>Установить сообщение для новых членов группы!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set timeouts for proxy/VPN" xml:space="preserve">
@ -7148,6 +7291,7 @@ chat item action</note>
</trans-unit>
<trans-unit id="Short link" xml:space="preserve">
<source>Short link</source>
<target>Короткая ссылка</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Show QR code" xml:space="preserve">
@ -7252,6 +7396,7 @@ chat item action</note>
</trans-unit>
<trans-unit id="SimpleX channel link" xml:space="preserve">
<source>SimpleX channel link</source>
<target>SimpleX ссылка канала</target>
<note>simplex link type</note>
</trans-unit>
<trans-unit id="SimpleX contact address" xml:space="preserve">
@ -7696,18 +7841,22 @@ It can happen because of some bug or when the connection is compromised.</source
</trans-unit>
<trans-unit id="The message will be deleted for all members." xml:space="preserve">
<source>The message will be deleted for all members.</source>
<target>Сообщение будет удалено для всех членов группы.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The message will be marked as moderated for all members." xml:space="preserve">
<source>The message will be marked as moderated for all members.</source>
<target>Сообщение будет помечено как удаленное для всех членов группы.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The messages will be deleted for all members." xml:space="preserve">
<source>The messages will be deleted for all members.</source>
<target>Сообщения будут удалены для всех членов группы.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The messages will be marked as moderated for all members." xml:space="preserve">
<source>The messages will be marked as moderated for all members.</source>
<target>Сообщения будут помечены как удаленные для всех членов группы.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The old database was not removed during the migration, it can be deleted." xml:space="preserve">
@ -7817,6 +7966,7 @@ It can happen because of some bug or when the connection is compromised.</source
</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>В этой группе более %lld членов, отчёты о доставке не отправляются.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="This group no longer exists." xml:space="preserve">
@ -7836,6 +7986,7 @@ It can happen because of some bug or when the connection is compromised.</source
</trans-unit>
<trans-unit id="This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." xml:space="preserve">
<source>This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link.</source>
<target>Эта ссылка требует новую версию. Обновите приложение или попросите Ваш контакт прислать совместимую ссылку.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="This link was used with another mobile device, please create a new link on the desktop." xml:space="preserve">
@ -8027,14 +8178,17 @@ You will be prompted to complete authentication before this feature is enabled.<
</trans-unit>
<trans-unit id="Unblock member" xml:space="preserve">
<source>Unblock member</source>
<target>Разблокировать члена группы</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unblock member for all?" xml:space="preserve">
<source>Unblock member for all?</source>
<target>Разблокировать члена для всех?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unblock member?" xml:space="preserve">
<source>Unblock member?</source>
<target>Разблокировать члена группы?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Undelivered messages" xml:space="preserve">
@ -8136,10 +8290,12 @@ To connect, please ask your contact to create another connection link and check
</trans-unit>
<trans-unit id="Unsupported connection link" xml:space="preserve">
<source>Unsupported connection link</source>
<target>Ссылка не поддерживается</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Up to 100 last messages are sent to new members." xml:space="preserve">
<source>Up to 100 last messages are sent to new members.</source>
<target>До 100 последних сообщений отправляются новым членам.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Update" xml:space="preserve">
@ -8234,6 +8390,7 @@ To connect, please ask your contact to create another connection link and check
</trans-unit>
<trans-unit id="Use TCP port 443 for preset servers only." xml:space="preserve">
<source>Use TCP port 443 for preset servers only.</source>
<target>Использовать TCP-порт 443 только для серверов по умолчанию.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Use chat" xml:space="preserve">
@ -8303,6 +8460,7 @@ To connect, please ask your contact to create another connection link and check
</trans-unit>
<trans-unit id="Use short links (BETA)" xml:space="preserve">
<source>Use short links (BETA)</source>
<target>Короткие ссылки (БЕТА)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Use the app while in the call." xml:space="preserve">
@ -8749,6 +8907,7 @@ Repeat join request?</source>
</trans-unit>
<trans-unit id="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." xml:space="preserve">
<source>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.</source>
<target>Вы можете поделиться ссылкой или QR кодом - через них можно присоединиться к группе. Вы сможете удалить ссылку, сохранив членов группы, которые через нее соединились.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You can share this address with your contacts to let them connect with **%@**." xml:space="preserve">
@ -8781,6 +8940,11 @@ Repeat join request?</source>
<target>Вы можете увидеть ссылку-приглашение снова открыв соединение.</target>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can view your reports in Chat with admins." xml:space="preserve">
<source>You can view your reports in Chat with admins.</source>
<target>Вы можете найти Ваши жалобы в Чате с админами.</target>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can't send messages!" xml:space="preserve">
<source>You can't send messages!</source>
<target>Вы не можете отправлять сообщения!</target>
@ -8825,6 +8989,7 @@ Repeat connection request?</source>
</trans-unit>
<trans-unit id="You joined this group. Connecting to inviting group member." xml:space="preserve">
<source>You joined this group. Connecting to inviting group member.</source>
<target>Вы вступили в эту группу. Устанавливается соединение с пригласившим членом группы.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You may migrate the exported database." xml:space="preserve">
@ -8894,6 +9059,7 @@ Repeat connection request?</source>
</trans-unit>
<trans-unit id="You will connect to all group members." xml:space="preserve">
<source>You will connect to all group members.</source>
<target>Вы соединитесь со всеми членами группы.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You will still receive calls and notifications from muted profiles when they are active." xml:space="preserve">
@ -9081,6 +9247,11 @@ Repeat connection request?</source>
<target>наверху, затем выберите:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="accepted %@" xml:space="preserve">
<source>accepted %@</source>
<target>принят %@</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="accepted call" xml:space="preserve">
<source>accepted call</source>
<target>принятый звонок</target>
@ -9091,6 +9262,11 @@ Repeat connection request?</source>
<target>принятое приглашение</target>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="accepted you" xml:space="preserve">
<source>accepted you</source>
<target>Вы приняты</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="admin" xml:space="preserve">
<source>admin</source>
<target>админ</target>
@ -9111,8 +9287,14 @@ Repeat connection request?</source>
<target>шифрование согласовывается…</target>
<note>chat item text</note>
</trans-unit>
<trans-unit id="all" xml:space="preserve">
<source>all</source>
<target>все</target>
<note>member criteria value</note>
</trans-unit>
<trans-unit id="all members" xml:space="preserve">
<source>all members</source>
<target>все члены</target>
<note>feature role</note>
</trans-unit>
<trans-unit id="always" xml:space="preserve">
@ -9196,6 +9378,11 @@ marked deleted chat item preview text</note>
<target>входящий звонок…</target>
<note>call status</note>
</trans-unit>
<trans-unit id="can't send messages" xml:space="preserve">
<source>can't send messages</source>
<target>нельзя отправлять</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="cancelled %@" xml:space="preserve">
<source>cancelled %@</source>
<target>отменил(a) %@</target>
@ -9301,6 +9488,16 @@ marked deleted chat item preview text</note>
<target>контакт %1$@ изменён на %2$@</target>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="contact deleted" xml:space="preserve">
<source>contact deleted</source>
<target>контакт удален</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact disabled" xml:space="preserve">
<source>contact disabled</source>
<target>контакт выключен</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact has e2e encryption" xml:space="preserve">
<source>contact has e2e encryption</source>
<target>у контакта есть e2e шифрование</target>
@ -9311,6 +9508,11 @@ marked deleted chat item preview text</note>
<target>у контакта нет e2e шифрования</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact not ready" xml:space="preserve">
<source>contact not ready</source>
<target>контакт не готов</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="creator" xml:space="preserve">
<source>creator</source>
<target>создатель</target>
@ -9482,6 +9684,11 @@ pref value</note>
<target>группа удалена</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group is deleted" xml:space="preserve">
<source>group is deleted</source>
<target>группа удалена</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group profile updated" xml:space="preserve">
<source>group profile updated</source>
<target>профиль группы обновлен</target>
@ -9594,10 +9801,12 @@ pref value</note>
</trans-unit>
<trans-unit id="member" xml:space="preserve">
<source>member</source>
<target>член группы</target>
<note>member role</note>
</trans-unit>
<trans-unit id="member %@ changed to %@" xml:space="preserve">
<source>member %1$@ changed to %2$@</source>
<target>член %1$@ изменился на %2$@</target>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="member connected" xml:space="preserve">
@ -9605,6 +9814,11 @@ pref value</note>
<target>соединен(а)</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="member has old version" xml:space="preserve">
<source>member has old version</source>
<target>член имеет старую версию</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="message" xml:space="preserve">
<source>message</source>
<target>написать</target>
@ -9670,6 +9884,11 @@ pref value</note>
<target>нет текста</target>
<note>copied message info in history</note>
</trans-unit>
<trans-unit id="not synchronized" xml:space="preserve">
<source>not synchronized</source>
<target>не синхронизирован</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="observer" xml:space="preserve">
<source>observer</source>
<target>читатель</target>
@ -9680,6 +9899,7 @@ pref value</note>
<target>нет</target>
<note>enabled status
group pref value
member criteria value
time to disappear</note>
</trans-unit>
<trans-unit id="offered %@" xml:space="preserve">
@ -9732,6 +9952,11 @@ time to disappear</note>
<target>ожидает утверждения</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="pending review" xml:space="preserve">
<source>pending review</source>
<target>ожидает одобрения</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="quantum resistant e2e encryption" xml:space="preserve">
<source>quantum resistant e2e encryption</source>
<target>квантово-устойчивое e2e шифрование</target>
@ -9772,6 +9997,11 @@ time to disappear</note>
<target>удалён адрес контакта</target>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="removed from group" xml:space="preserve">
<source>removed from group</source>
<target>удален из группы</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="removed profile picture" xml:space="preserve">
<source>removed profile picture</source>
<target>удалена картинка профиля</target>
@ -9782,11 +10012,26 @@ time to disappear</note>
<target>удалил(а) Вас из группы</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="request to join rejected" xml:space="preserve">
<source>request to join rejected</source>
<target>запрос на вступление отклонён</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="requested to connect" xml:space="preserve">
<source>requested to connect</source>
<target>запрошено соединение</target>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="review" xml:space="preserve">
<source>review</source>
<target>рассмотрение</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="reviewed by admins" xml:space="preserve">
<source>reviewed by admins</source>
<target>одобрен админами</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="saved" xml:space="preserve">
<source>saved</source>
<target>сохранено</target>
@ -9976,6 +10221,11 @@ last received msg: %2$@</source>
<target>Вы</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="you accepted this member" xml:space="preserve">
<source>you accepted this member</source>
<target>Вы приняли этого члена</target>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="you are invited to group" xml:space="preserve">
<source>you are invited to group</source>
<target>Вы приглашены в группу</target>
@ -10119,6 +10369,7 @@ last received msg: %2$@</source>
</trans-unit>
<trans-unit id="From %d chat(s)" xml:space="preserve">
<source>From %d chat(s)</source>
<target>Из %d чатов</target>
<note>notification body</note>
</trans-unit>
<trans-unit id="From: %@" xml:space="preserve">

View file

@ -520,6 +520,14 @@ time interval</note>
accept incoming call via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept as member" xml:space="preserve">
<source>Accept as member</source>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept as observer" xml:space="preserve">
<source>Accept as observer</source>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept conditions" xml:space="preserve">
<source>Accept conditions</source>
<note>No comment provided by engineer.</note>
@ -539,6 +547,10 @@ swipe action</note>
<note>accept contact request via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept member" xml:space="preserve">
<source>Accept member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Accepted conditions" xml:space="preserve">
<source>Accepted conditions</source>
<note>No comment provided by engineer.</note>
@ -1429,11 +1441,23 @@ set passcode view</note>
<source>Chat will be deleted for you - this cannot be undone!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat with admins" xml:space="preserve">
<source>Chat with admins</source>
<note>chat toolbar</note>
</trans-unit>
<trans-unit id="Chat with member" xml:space="preserve">
<source>Chat with member</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats" xml:space="preserve">
<source>Chats</source>
<target>แชท</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats with members" xml:space="preserve">
<source>Chats with members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Check messages every 20 min." xml:space="preserve">
<source>Check messages every 20 min.</source>
<note>No comment provided by engineer.</note>
@ -2162,6 +2186,10 @@ swipe action</note>
<target>ลบโปรไฟล์แชทไหม?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete chat with member?" xml:space="preserve">
<source>Delete chat with member?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Delete chat?" xml:space="preserve">
<source>Delete chat?</source>
<note>No comment provided by engineer.</note>
@ -2551,7 +2579,7 @@ swipe action</note>
<trans-unit id="Don't show again" xml:space="preserve">
<source>Don't show again</source>
<target>ไม่ต้องแสดงอีก</target>
<note>No comment provided by engineer.</note>
<note>alert action</note>
</trans-unit>
<trans-unit id="Done" xml:space="preserve">
<source>Done</source>
@ -2843,6 +2871,10 @@ chat item action</note>
<target>เกิดข้อผิดพลาดในการรับคำขอติดต่อ</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error accepting member" xml:space="preserve">
<source>Error accepting member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error adding member(s)" xml:space="preserve">
<source>Error adding member(s)</source>
<target>เกิดข้อผิดพลาดในการเพิ่มสมาชิก</target>
@ -2928,6 +2960,10 @@ chat item action</note>
<target>เกิดข้อผิดพลาดในการลบฐานข้อมูลแชท</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error deleting chat with member" xml:space="preserve">
<source>Error deleting chat with member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error deleting chat!" xml:space="preserve">
<source>Error deleting chat!</source>
<target>เกิดข้อผิดพลาดในการลบแชท!</target>
@ -3028,7 +3064,7 @@ chat item action</note>
<trans-unit id="Error removing member" xml:space="preserve">
<source>Error removing member</source>
<target>เกิดข้อผิดพลาดในการลบสมาชิก</target>
<note>No comment provided by engineer.</note>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error reordering lists" xml:space="preserve">
<source>Error reordering lists</source>
@ -4293,6 +4329,10 @@ This is your link for group %@!</source>
<target>สมาชิก</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member admission" xml:space="preserve">
<source>Member admission</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member inactive" xml:space="preserve">
<source>Member inactive</source>
<note>item status text</note>
@ -4324,6 +4364,10 @@ This is your link for group %@!</source>
<target>สมาชิกจะถูกลบออกจากกลุ่ม - ไม่สามารถยกเลิกได้!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member will join the group, accept member?" xml:space="preserve">
<source>Member will join the group, accept member?</source>
<note>alert message</note>
</trans-unit>
<trans-unit id="Members can add message reactions." xml:space="preserve">
<source>Members can add message reactions.</source>
<target>สมาชิกกลุ่มสามารถเพิ่มการแสดงปฏิกิริยาต่อข้อความได้</target>
@ -4697,6 +4741,10 @@ This is your link for group %@!</source>
<target>บทบาทของสมาชิกใหม่</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New member wants to join the group." xml:space="preserve">
<source>New member wants to join the group.</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="New message" xml:space="preserve">
<source>New message</source>
<target>ข้อความใหม่</target>
@ -4733,6 +4781,10 @@ This is your link for group %@!</source>
<source>No chats in list %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No chats with members" xml:space="preserve">
<source>No chats with members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No contacts selected" xml:space="preserve">
<source>No contacts selected</source>
<target>ไม่ได้เลือกผู้ติดต่อ</target>
@ -4902,7 +4954,8 @@ This is your link for group %@!</source>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
<target>ตกลง</target>
<note>alert button</note>
<note>alert action
alert button</note>
</trans-unit>
<trans-unit id="Old database" xml:space="preserve">
<source>Old database</source>
@ -5274,6 +5327,10 @@ Error: %@</source>
<source>Please try to disable and re-enable notfications.</source>
<note>token info</note>
</trans-unit>
<trans-unit id="Please wait for group moderators to review your request to join the group." xml:space="preserve">
<source>Please wait for group moderators to review your request to join the group.</source>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="Please wait for token activation to complete." xml:space="preserve">
<source>Please wait for token activation to complete.</source>
<note>token info</note>
@ -5683,6 +5740,10 @@ swipe action</note>
<target>ปฏิเสธคำขอติดต่อ</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Reject member?" xml:space="preserve">
<source>Reject member?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Relay server is only used if necessary. Another party can observe your IP address." xml:space="preserve">
<source>Relay server is only used if necessary. Another party can observe your IP address.</source>
<target>ใช้เซิร์ฟเวอร์รีเลย์ในกรณีที่จำเป็นเท่านั้น บุคคลอื่นสามารถสังเกตที่อยู่ IP ของคุณได้</target>
@ -5781,6 +5842,10 @@ swipe action</note>
<source>Report reason?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Report sent to moderators" xml:space="preserve">
<source>Report sent to moderators</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Report spam: only group moderators will see it." xml:space="preserve">
<source>Report spam: only group moderators will see it.</source>
<note>report reason</note>
@ -5884,6 +5949,14 @@ swipe action</note>
<source>Review conditions</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Review members" xml:space="preserve">
<source>Review members</source>
<note>admission stage</note>
</trans-unit>
<trans-unit id="Review members before admitting (&quot;knocking&quot;)." xml:space="preserve">
<source>Review members before admitting ("knocking").</source>
<note>admission stage description</note>
</trans-unit>
<trans-unit id="Revoke" xml:space="preserve">
<source>Revoke</source>
<target>ถอน</target>
@ -5936,6 +6009,10 @@ chat item action</note>
<target>บันทึก (และแจ้งผู้ติดต่อ)</target>
<note>alert button</note>
</trans-unit>
<trans-unit id="Save admission settings?" xml:space="preserve">
<source>Save admission settings?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Save and notify contact" xml:space="preserve">
<source>Save and notify contact</source>
<target>บันทึกและแจ้งผู้ติดต่อ</target>
@ -6404,6 +6481,10 @@ chat item action</note>
<target>ตั้งแทนการรับรองความถูกต้องของระบบ</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set member admission" xml:space="preserve">
<source>Set member admission</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set message expiration in chats." xml:space="preserve">
<source>Set message expiration in chats.</source>
<note>No comment provided by engineer.</note>
@ -7986,6 +8067,10 @@ Repeat join request?</source>
<source>You can view invitation link again in connection details.</source>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can view your reports in Chat with admins." xml:space="preserve">
<source>You can view your reports in Chat with admins.</source>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can't send messages!" xml:space="preserve">
<source>You can't send messages!</source>
<target>คุณไม่สามารถส่งข้อความได้!</target>
@ -8270,6 +8355,10 @@ Repeat connection request?</source>
<target>ด้านบน จากนั้นเลือก:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="accepted %@" xml:space="preserve">
<source>accepted %@</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="accepted call" xml:space="preserve">
<source>accepted call</source>
<target>รับสายแล้ว</target>
@ -8279,6 +8368,10 @@ Repeat connection request?</source>
<source>accepted invitation</source>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="accepted you" xml:space="preserve">
<source>accepted you</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="admin" xml:space="preserve">
<source>admin</source>
<target>ผู้ดูแลระบบ</target>
@ -8298,6 +8391,10 @@ Repeat connection request?</source>
<target>เห็นด้วยกับการ encryption…</target>
<note>chat item text</note>
</trans-unit>
<trans-unit id="all" xml:space="preserve">
<source>all</source>
<note>member criteria value</note>
</trans-unit>
<trans-unit id="all members" xml:space="preserve">
<source>all members</source>
<note>feature role</note>
@ -8375,6 +8472,10 @@ marked deleted chat item preview text</note>
<target>กำลังโทร…</target>
<note>call status</note>
</trans-unit>
<trans-unit id="can't send messages" xml:space="preserve">
<source>can't send messages</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="cancelled %@" xml:space="preserve">
<source>cancelled %@</source>
<target>ยกเลิก %@</target>
@ -8478,6 +8579,14 @@ marked deleted chat item preview text</note>
<source>contact %1$@ changed to %2$@</source>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="contact deleted" xml:space="preserve">
<source>contact deleted</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact disabled" xml:space="preserve">
<source>contact disabled</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact has e2e encryption" xml:space="preserve">
<source>contact has e2e encryption</source>
<target>ผู้ติดต่อมีการ encrypt จากต้นจนจบ</target>
@ -8488,6 +8597,10 @@ marked deleted chat item preview text</note>
<target>ผู้ติดต่อไม่มีการ encrypt จากต้นจนจบ</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact not ready" xml:space="preserve">
<source>contact not ready</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="creator" xml:space="preserve">
<source>creator</source>
<target>ผู้สร้าง</target>
@ -8653,6 +8766,10 @@ pref value</note>
<target>ลบกลุ่มแล้ว</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group is deleted" xml:space="preserve">
<source>group is deleted</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group profile updated" xml:space="preserve">
<source>group profile updated</source>
<target>อัปเดตโปรไฟล์กลุ่มแล้ว</target>
@ -8775,6 +8892,10 @@ pref value</note>
<target>เชื่อมต่อสำเร็จ</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="member has old version" xml:space="preserve">
<source>member has old version</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="message" xml:space="preserve">
<source>message</source>
<note>No comment provided by engineer.</note>
@ -8838,6 +8959,10 @@ pref value</note>
<target>ไม่มีข้อความ</target>
<note>copied message info in history</note>
</trans-unit>
<trans-unit id="not synchronized" xml:space="preserve">
<source>not synchronized</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="observer" xml:space="preserve">
<source>observer</source>
<target>ผู้สังเกตการณ์</target>
@ -8848,6 +8973,7 @@ pref value</note>
<target>ปิด</target>
<note>enabled status
group pref value
member criteria value
time to disappear</note>
</trans-unit>
<trans-unit id="offered %@" xml:space="preserve">
@ -8895,6 +9021,10 @@ time to disappear</note>
<source>pending approval</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="pending review" xml:space="preserve">
<source>pending review</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="quantum resistant e2e encryption" xml:space="preserve">
<source>quantum resistant e2e encryption</source>
<note>chat item text</note>
@ -8932,6 +9062,10 @@ time to disappear</note>
<source>removed contact address</source>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="removed from group" xml:space="preserve">
<source>removed from group</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="removed profile picture" xml:space="preserve">
<source>removed profile picture</source>
<note>profile update event chat item</note>
@ -8941,10 +9075,22 @@ time to disappear</note>
<target>ลบคุณออกแล้ว</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="request to join rejected" xml:space="preserve">
<source>request to join rejected</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="requested to connect" xml:space="preserve">
<source>requested to connect</source>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="review" xml:space="preserve">
<source>review</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="reviewed by admins" xml:space="preserve">
<source>reviewed by admins</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="saved" xml:space="preserve">
<source>saved</source>
<note>No comment provided by engineer.</note>
@ -9115,6 +9261,10 @@ last received msg: %2$@</source>
<source>you</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="you accepted this member" xml:space="preserve">
<source>you accepted this member</source>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="you are invited to group" xml:space="preserve">
<source>you are invited to group</source>
<target>คุณได้รับเชิญให้เข้าร่วมกลุ่ม</target>

View file

@ -563,6 +563,14 @@ time interval</note>
accept incoming call via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept as member" xml:space="preserve">
<source>Accept as member</source>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept as observer" xml:space="preserve">
<source>Accept as observer</source>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept conditions" xml:space="preserve">
<source>Accept conditions</source>
<target>Koşulları kabul et</target>
@ -584,6 +592,10 @@ swipe action</note>
<note>accept contact request via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept member" xml:space="preserve">
<source>Accept member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Accepted conditions" xml:space="preserve">
<source>Accepted conditions</source>
<target>Kabul edilmiş koşullar</target>
@ -1557,11 +1569,23 @@ set passcode view</note>
<target>Sohbet senden silinecek - bu geri alınamaz!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat with admins" xml:space="preserve">
<source>Chat with admins</source>
<note>chat toolbar</note>
</trans-unit>
<trans-unit id="Chat with member" xml:space="preserve">
<source>Chat with member</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats" xml:space="preserve">
<source>Chats</source>
<target>Sohbetler</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats with members" xml:space="preserve">
<source>Chats with members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Check messages every 20 min." xml:space="preserve">
<source>Check messages every 20 min.</source>
<target>Her 20 dakikada mesajları kontrol et.</target>
@ -2364,6 +2388,10 @@ swipe action</note>
<target>Sohbet profili silinsin mi?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete chat with member?" xml:space="preserve">
<source>Delete chat with member?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Delete chat?" xml:space="preserve">
<source>Delete chat?</source>
<target>Sohbet silinsin mi?</target>
@ -2780,7 +2808,7 @@ swipe action</note>
<trans-unit id="Don't show again" xml:space="preserve">
<source>Don't show again</source>
<target>Yeniden gösterme</target>
<note>No comment provided by engineer.</note>
<note>alert action</note>
</trans-unit>
<trans-unit id="Done" xml:space="preserve">
<source>Done</source>
@ -3095,6 +3123,10 @@ chat item action</note>
<target>Bağlantı isteği kabul edilirken hata oluştu</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error accepting member" xml:space="preserve">
<source>Error accepting member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error adding member(s)" xml:space="preserve">
<source>Error adding member(s)</source>
<target>Üye(ler) eklenirken hata oluştu</target>
@ -3187,6 +3219,10 @@ chat item action</note>
<target>Sohbet veritabanı silinirken sorun oluştu</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error deleting chat with member" xml:space="preserve">
<source>Error deleting chat with member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error deleting chat!" xml:space="preserve">
<source>Error deleting chat!</source>
<target>Sohbet silinirken hata oluştu!</target>
@ -3294,7 +3330,7 @@ chat item action</note>
<trans-unit id="Error removing member" xml:space="preserve">
<source>Error removing member</source>
<target>Kişiyi silerken sorun oluştu</target>
<note>No comment provided by engineer.</note>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error reordering lists" xml:space="preserve">
<source>Error reordering lists</source>
@ -4653,6 +4689,10 @@ Bu senin grup için bağlantın %@!</target>
<target>Kişi</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member admission" xml:space="preserve">
<source>Member admission</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member inactive" xml:space="preserve">
<source>Member inactive</source>
<target>Üye inaktif</target>
@ -4686,6 +4726,10 @@ Bu senin grup için bağlantın %@!</target>
<target>Üye gruptan çıkarılacaktır - bu geri alınamaz!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member will join the group, accept member?" xml:space="preserve">
<source>Member will join the group, accept member?</source>
<note>alert message</note>
</trans-unit>
<trans-unit id="Members can add message reactions." xml:space="preserve">
<source>Members can add message reactions.</source>
<target>Grup üyeleri mesaj tepkileri ekleyebilir.</target>
@ -5096,6 +5140,10 @@ Bu senin grup için bağlantın %@!</target>
<target>Yeni üye rolü</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New member wants to join the group." xml:space="preserve">
<source>New member wants to join the group.</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="New message" xml:space="preserve">
<source>New message</source>
<target>Yeni mesaj</target>
@ -5132,6 +5180,10 @@ Bu senin grup için bağlantın %@!</target>
<source>No chats in list %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No chats with members" xml:space="preserve">
<source>No chats with members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No contacts selected" xml:space="preserve">
<source>No contacts selected</source>
<target>Hiçbir kişi seçilmedi</target>
@ -5311,7 +5363,8 @@ Bu senin grup için bağlantın %@!</target>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
<target>Tamam</target>
<note>alert button</note>
<note>alert action
alert button</note>
</trans-unit>
<trans-unit id="Old database" xml:space="preserve">
<source>Old database</source>
@ -5711,6 +5764,10 @@ Hata: %@</target>
<source>Please try to disable and re-enable notfications.</source>
<note>token info</note>
</trans-unit>
<trans-unit id="Please wait for group moderators to review your request to join the group." xml:space="preserve">
<source>Please wait for group moderators to review your request to join the group.</source>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="Please wait for token activation to complete." xml:space="preserve">
<source>Please wait for token activation to complete.</source>
<note>token info</note>
@ -6153,6 +6210,10 @@ swipe action</note>
<target>Bağlanma isteğini reddet</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Reject member?" xml:space="preserve">
<source>Reject member?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Relay server is only used if necessary. Another party can observe your IP address." xml:space="preserve">
<source>Relay server is only used if necessary. Another party can observe your IP address.</source>
<target>Yönlendirici sunucusu yalnızca gerekli olduğunda kullanılır. Başka bir taraf IP adresinizi gözlemleyebilir.</target>
@ -6258,6 +6319,10 @@ swipe action</note>
<source>Report reason?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Report sent to moderators" xml:space="preserve">
<source>Report sent to moderators</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Report spam: only group moderators will see it." xml:space="preserve">
<source>Report spam: only group moderators will see it.</source>
<note>report reason</note>
@ -6367,6 +6432,14 @@ swipe action</note>
<source>Review conditions</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Review members" xml:space="preserve">
<source>Review members</source>
<note>admission stage</note>
</trans-unit>
<trans-unit id="Review members before admitting (&quot;knocking&quot;)." xml:space="preserve">
<source>Review members before admitting ("knocking").</source>
<note>admission stage description</note>
</trans-unit>
<trans-unit id="Revoke" xml:space="preserve">
<source>Revoke</source>
<target>İptal et</target>
@ -6423,6 +6496,10 @@ chat item action</note>
<target>Kaydet (ve kişilere bildir)</target>
<note>alert button</note>
</trans-unit>
<trans-unit id="Save admission settings?" xml:space="preserve">
<source>Save admission settings?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Save and notify contact" xml:space="preserve">
<source>Save and notify contact</source>
<target>Kaydet ve kişilere bildir</target>
@ -6931,6 +7008,10 @@ chat item action</note>
<target>Sistem kimlik doğrulaması yerine ayarla.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set member admission" xml:space="preserve">
<source>Set member admission</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set message expiration in chats." xml:space="preserve">
<source>Set message expiration in chats.</source>
<note>No comment provided by engineer.</note>
@ -8646,6 +8727,10 @@ Katılma isteği tekrarlansın mı?</target>
<target>Bağlantı detaylarından davet bağlantısını yeniden görüntüleyebilirsin.</target>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can view your reports in Chat with admins." xml:space="preserve">
<source>You can view your reports in Chat with admins.</source>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can't send messages!" xml:space="preserve">
<source>You can't send messages!</source>
<target>Mesajlar gönderemezsiniz!</target>
@ -8945,6 +9030,10 @@ Bağlantı isteği tekrarlansın mı?</target>
<target>yukarı çıkın, ardından seçin:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="accepted %@" xml:space="preserve">
<source>accepted %@</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="accepted call" xml:space="preserve">
<source>accepted call</source>
<target>kabul edilen arama</target>
@ -8954,6 +9043,10 @@ Bağlantı isteği tekrarlansın mı?</target>
<source>accepted invitation</source>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="accepted you" xml:space="preserve">
<source>accepted you</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="admin" xml:space="preserve">
<source>admin</source>
<target>yönetici</target>
@ -8974,6 +9067,10 @@ Bağlantı isteği tekrarlansın mı?</target>
<target>şifreleme kabul ediliyor…</target>
<note>chat item text</note>
</trans-unit>
<trans-unit id="all" xml:space="preserve">
<source>all</source>
<note>member criteria value</note>
</trans-unit>
<trans-unit id="all members" xml:space="preserve">
<source>all members</source>
<target>bütün üyeler</target>
@ -9059,6 +9156,10 @@ marked deleted chat item preview text</note>
<target>aranıyor…</target>
<note>call status</note>
</trans-unit>
<trans-unit id="can't send messages" xml:space="preserve">
<source>can't send messages</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="cancelled %@" xml:space="preserve">
<source>cancelled %@</source>
<target>%@ iptal edildi</target>
@ -9164,6 +9265,14 @@ marked deleted chat item preview text</note>
<target>%1$@ kişisi %2$@ olarak değişti</target>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="contact deleted" xml:space="preserve">
<source>contact deleted</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact disabled" xml:space="preserve">
<source>contact disabled</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact has e2e encryption" xml:space="preserve">
<source>contact has e2e encryption</source>
<target>kişi uçtan uca şifrelemeye sahiptir</target>
@ -9174,6 +9283,10 @@ marked deleted chat item preview text</note>
<target>kişi uçtan uca şifrelemeye sahip değildir</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact not ready" xml:space="preserve">
<source>contact not ready</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="creator" xml:space="preserve">
<source>creator</source>
<target>oluşturan</target>
@ -9345,6 +9458,10 @@ pref value</note>
<target>grup silindi</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group is deleted" xml:space="preserve">
<source>group is deleted</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group profile updated" xml:space="preserve">
<source>group profile updated</source>
<target>grup profili güncellendi</target>
@ -9470,6 +9587,10 @@ pref value</note>
<target>bağlanıldı</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="member has old version" xml:space="preserve">
<source>member has old version</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="message" xml:space="preserve">
<source>message</source>
<target>mesaj</target>
@ -9534,6 +9655,10 @@ pref value</note>
<target>metin yok</target>
<note>copied message info in history</note>
</trans-unit>
<trans-unit id="not synchronized" xml:space="preserve">
<source>not synchronized</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="observer" xml:space="preserve">
<source>observer</source>
<target>gözlemci</target>
@ -9544,6 +9669,7 @@ pref value</note>
<target>kapalı</target>
<note>enabled status
group pref value
member criteria value
time to disappear</note>
</trans-unit>
<trans-unit id="offered %@" xml:space="preserve">
@ -9594,6 +9720,10 @@ time to disappear</note>
<source>pending approval</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="pending review" xml:space="preserve">
<source>pending review</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="quantum resistant e2e encryption" xml:space="preserve">
<source>quantum resistant e2e encryption</source>
<target>kuantuma dayanıklı e2e şifreleme</target>
@ -9633,6 +9763,10 @@ time to disappear</note>
<target>kişi adresi silindi</target>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="removed from group" xml:space="preserve">
<source>removed from group</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="removed profile picture" xml:space="preserve">
<source>removed profile picture</source>
<target>profil fotoğrafı silindi</target>
@ -9643,10 +9777,22 @@ time to disappear</note>
<target>sen kaldırıldın</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="request to join rejected" xml:space="preserve">
<source>request to join rejected</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="requested to connect" xml:space="preserve">
<source>requested to connect</source>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="review" xml:space="preserve">
<source>review</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="reviewed by admins" xml:space="preserve">
<source>reviewed by admins</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="saved" xml:space="preserve">
<source>saved</source>
<target>kaydedildi</target>
@ -9836,6 +9982,10 @@ son alınan msj: %2$@</target>
<target>sen</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="you accepted this member" xml:space="preserve">
<source>you accepted this member</source>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="you are invited to group" xml:space="preserve">
<source>you are invited to group</source>
<target>gruba davet edildiniz</target>

View file

@ -563,6 +563,14 @@ time interval</note>
accept incoming call via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept as member" xml:space="preserve">
<source>Accept as member</source>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept as observer" xml:space="preserve">
<source>Accept as observer</source>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept conditions" xml:space="preserve">
<source>Accept conditions</source>
<target>Прийняти умови</target>
@ -584,6 +592,10 @@ swipe action</note>
<note>accept contact request via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept member" xml:space="preserve">
<source>Accept member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Accepted conditions" xml:space="preserve">
<source>Accepted conditions</source>
<target>Прийняті умови</target>
@ -1557,11 +1569,23 @@ set passcode view</note>
<target>Чат буде видалено для вас - цю дію неможливо скасувати!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat with admins" xml:space="preserve">
<source>Chat with admins</source>
<note>chat toolbar</note>
</trans-unit>
<trans-unit id="Chat with member" xml:space="preserve">
<source>Chat with member</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats" xml:space="preserve">
<source>Chats</source>
<target>Чати</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats with members" xml:space="preserve">
<source>Chats with members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Check messages every 20 min." xml:space="preserve">
<source>Check messages every 20 min.</source>
<target>Перевіряйте повідомлення кожні 20 хв.</target>
@ -2364,6 +2388,10 @@ swipe action</note>
<target>Видалити профіль чату?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete chat with member?" xml:space="preserve">
<source>Delete chat with member?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Delete chat?" xml:space="preserve">
<source>Delete chat?</source>
<target>Видалити чат?</target>
@ -2781,7 +2809,7 @@ swipe action</note>
<trans-unit id="Don't show again" xml:space="preserve">
<source>Don't show again</source>
<target>Більше не показувати</target>
<note>No comment provided by engineer.</note>
<note>alert action</note>
</trans-unit>
<trans-unit id="Done" xml:space="preserve">
<source>Done</source>
@ -3096,6 +3124,10 @@ chat item action</note>
<target>Помилка при прийнятті запиту на контакт</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error accepting member" xml:space="preserve">
<source>Error accepting member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error adding member(s)" xml:space="preserve">
<source>Error adding member(s)</source>
<target>Помилка додавання користувача(ів)</target>
@ -3188,6 +3220,10 @@ chat item action</note>
<target>Помилка видалення бази даних чату</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error deleting chat with member" xml:space="preserve">
<source>Error deleting chat with member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error deleting chat!" xml:space="preserve">
<source>Error deleting chat!</source>
<target>Помилка видалення чату!</target>
@ -3295,7 +3331,7 @@ chat item action</note>
<trans-unit id="Error removing member" xml:space="preserve">
<source>Error removing member</source>
<target>Помилка видалення учасника</target>
<note>No comment provided by engineer.</note>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error reordering lists" xml:space="preserve">
<source>Error reordering lists</source>
@ -4654,6 +4690,10 @@ This is your link for group %@!</source>
<target>Учасник</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member admission" xml:space="preserve">
<source>Member admission</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member inactive" xml:space="preserve">
<source>Member inactive</source>
<target>Користувач неактивний</target>
@ -4688,6 +4728,10 @@ This is your link for group %@!</source>
<target>Учасник буде видалений з групи - це неможливо скасувати!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member will join the group, accept member?" xml:space="preserve">
<source>Member will join the group, accept member?</source>
<note>alert message</note>
</trans-unit>
<trans-unit id="Members can add message reactions." xml:space="preserve">
<source>Members can add message reactions.</source>
<target>Учасники групи можуть додавати реакції на повідомлення.</target>
@ -5102,6 +5146,10 @@ This is your link for group %@!</source>
<target>Нова роль учасника</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New member wants to join the group." xml:space="preserve">
<source>New member wants to join the group.</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="New message" xml:space="preserve">
<source>New message</source>
<target>Нове повідомлення</target>
@ -5139,6 +5187,10 @@ This is your link for group %@!</source>
<source>No chats in list %@</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No chats with members" xml:space="preserve">
<source>No chats with members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No contacts selected" xml:space="preserve">
<source>No contacts selected</source>
<target>Не вибрано жодного контакту</target>
@ -5325,7 +5377,8 @@ This is your link for group %@!</source>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
<target>Гаразд</target>
<note>alert button</note>
<note>alert action
alert button</note>
</trans-unit>
<trans-unit id="Old database" xml:space="preserve">
<source>Old database</source>
@ -5732,6 +5785,10 @@ Error: %@</source>
<source>Please try to disable and re-enable notfications.</source>
<note>token info</note>
</trans-unit>
<trans-unit id="Please wait for group moderators to review your request to join the group." xml:space="preserve">
<source>Please wait for group moderators to review your request to join the group.</source>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="Please wait for token activation to complete." xml:space="preserve">
<source>Please wait for token activation to complete.</source>
<note>token info</note>
@ -6176,6 +6233,10 @@ swipe action</note>
<target>Відхилити запит на контакт</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Reject member?" xml:space="preserve">
<source>Reject member?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Relay server is only used if necessary. Another party can observe your IP address." xml:space="preserve">
<source>Relay server is only used if necessary. Another party can observe your IP address.</source>
<target>Релейний сервер використовується тільки в разі потреби. Інша сторона може бачити вашу IP-адресу.</target>
@ -6281,6 +6342,10 @@ swipe action</note>
<source>Report reason?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Report sent to moderators" xml:space="preserve">
<source>Report sent to moderators</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Report spam: only group moderators will see it." xml:space="preserve">
<source>Report spam: only group moderators will see it.</source>
<note>report reason</note>
@ -6391,6 +6456,14 @@ swipe action</note>
<target>Умови перегляду</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Review members" xml:space="preserve">
<source>Review members</source>
<note>admission stage</note>
</trans-unit>
<trans-unit id="Review members before admitting (&quot;knocking&quot;)." xml:space="preserve">
<source>Review members before admitting ("knocking").</source>
<note>admission stage description</note>
</trans-unit>
<trans-unit id="Revoke" xml:space="preserve">
<source>Revoke</source>
<target>Відкликати</target>
@ -6447,6 +6520,10 @@ chat item action</note>
<target>Зберегти (і повідомити контактам)</target>
<note>alert button</note>
</trans-unit>
<trans-unit id="Save admission settings?" xml:space="preserve">
<source>Save admission settings?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Save and notify contact" xml:space="preserve">
<source>Save and notify contact</source>
<target>Зберегти та повідомити контакт</target>
@ -6959,6 +7036,10 @@ chat item action</note>
<target>Встановіть його замість аутентифікації системи.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set member admission" xml:space="preserve">
<source>Set member admission</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set message expiration in chats." xml:space="preserve">
<source>Set message expiration in chats.</source>
<note>No comment provided by engineer.</note>
@ -8704,6 +8785,10 @@ Repeat join request?</source>
<target>Ви можете переглянути посилання на запрошення ще раз у деталях підключення.</target>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can view your reports in Chat with admins." xml:space="preserve">
<source>You can view your reports in Chat with admins.</source>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can't send messages!" xml:space="preserve">
<source>You can't send messages!</source>
<target>Ви не можете надсилати повідомлення!</target>
@ -9005,6 +9090,10 @@ Repeat connection request?</source>
<target>вище, а потім обирайте:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="accepted %@" xml:space="preserve">
<source>accepted %@</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="accepted call" xml:space="preserve">
<source>accepted call</source>
<target>прийнято виклик</target>
@ -9015,6 +9104,10 @@ Repeat connection request?</source>
<target>прийняте запрошення</target>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="accepted you" xml:space="preserve">
<source>accepted you</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="admin" xml:space="preserve">
<source>admin</source>
<target>адмін</target>
@ -9035,6 +9128,10 @@ Repeat connection request?</source>
<target>узгодження шифрування…</target>
<note>chat item text</note>
</trans-unit>
<trans-unit id="all" xml:space="preserve">
<source>all</source>
<note>member criteria value</note>
</trans-unit>
<trans-unit id="all members" xml:space="preserve">
<source>all members</source>
<target>всі учасники</target>
@ -9120,6 +9217,10 @@ marked deleted chat item preview text</note>
<target>дзвоніть…</target>
<note>call status</note>
</trans-unit>
<trans-unit id="can't send messages" xml:space="preserve">
<source>can't send messages</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="cancelled %@" xml:space="preserve">
<source>cancelled %@</source>
<target>скасовано %@</target>
@ -9225,6 +9326,14 @@ marked deleted chat item preview text</note>
<target>контакт %1$@ змінено на %2$@</target>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="contact deleted" xml:space="preserve">
<source>contact deleted</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact disabled" xml:space="preserve">
<source>contact disabled</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact has e2e encryption" xml:space="preserve">
<source>contact has e2e encryption</source>
<target>контакт має шифрування e2e</target>
@ -9235,6 +9344,10 @@ marked deleted chat item preview text</note>
<target>контакт не має шифрування e2e</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact not ready" xml:space="preserve">
<source>contact not ready</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="creator" xml:space="preserve">
<source>creator</source>
<target>творець</target>
@ -9406,6 +9519,10 @@ pref value</note>
<target>групу видалено</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group is deleted" xml:space="preserve">
<source>group is deleted</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group profile updated" xml:space="preserve">
<source>group profile updated</source>
<target>оновлено профіль групи</target>
@ -9531,6 +9648,10 @@ pref value</note>
<target>з'єднаний</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="member has old version" xml:space="preserve">
<source>member has old version</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="message" xml:space="preserve">
<source>message</source>
<target>повідомлення</target>
@ -9595,6 +9716,10 @@ pref value</note>
<target>без тексту</target>
<note>copied message info in history</note>
</trans-unit>
<trans-unit id="not synchronized" xml:space="preserve">
<source>not synchronized</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="observer" xml:space="preserve">
<source>observer</source>
<target>спостерігач</target>
@ -9605,6 +9730,7 @@ pref value</note>
<target>вимкнено</target>
<note>enabled status
group pref value
member criteria value
time to disappear</note>
</trans-unit>
<trans-unit id="offered %@" xml:space="preserve">
@ -9655,6 +9781,10 @@ time to disappear</note>
<source>pending approval</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="pending review" xml:space="preserve">
<source>pending review</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="quantum resistant e2e encryption" xml:space="preserve">
<source>quantum resistant e2e encryption</source>
<target>квантово-стійке шифрування e2e</target>
@ -9694,6 +9824,10 @@ time to disappear</note>
<target>видалено контактну адресу</target>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="removed from group" xml:space="preserve">
<source>removed from group</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="removed profile picture" xml:space="preserve">
<source>removed profile picture</source>
<target>видалено зображення профілю</target>
@ -9704,11 +9838,23 @@ time to disappear</note>
<target>прибрали вас</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="request to join rejected" xml:space="preserve">
<source>request to join rejected</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="requested to connect" xml:space="preserve">
<source>requested to connect</source>
<target>запит на підключення</target>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="review" xml:space="preserve">
<source>review</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="reviewed by admins" xml:space="preserve">
<source>reviewed by admins</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="saved" xml:space="preserve">
<source>saved</source>
<target>збережено</target>
@ -9898,6 +10044,10 @@ last received msg: %2$@</source>
<target>ти</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="you accepted this member" xml:space="preserve">
<source>you accepted this member</source>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="you are invited to group" xml:space="preserve">
<source>you are invited to group</source>
<target>вас запрошують до групи</target>

View file

@ -565,6 +565,14 @@ time interval</note>
accept incoming call via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept as member" xml:space="preserve">
<source>Accept as member</source>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept as observer" xml:space="preserve">
<source>Accept as observer</source>
<note>alert action</note>
</trans-unit>
<trans-unit id="Accept conditions" xml:space="preserve">
<source>Accept conditions</source>
<target>接受条款</target>
@ -586,6 +594,10 @@ swipe action</note>
<note>accept contact request via notification
swipe action</note>
</trans-unit>
<trans-unit id="Accept member" xml:space="preserve">
<source>Accept member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Accepted conditions" xml:space="preserve">
<source>Accepted conditions</source>
<target>已接受的条款</target>
@ -1582,11 +1594,23 @@ set passcode view</note>
<target>将为你删除聊天 - 此操作无法撤销!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chat with admins" xml:space="preserve">
<source>Chat with admins</source>
<note>chat toolbar</note>
</trans-unit>
<trans-unit id="Chat with member" xml:space="preserve">
<source>Chat with member</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats" xml:space="preserve">
<source>Chats</source>
<target>聊天</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Chats with members" xml:space="preserve">
<source>Chats with members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Check messages every 20 min." xml:space="preserve">
<source>Check messages every 20 min.</source>
<target>每 20 分钟检查消息。</target>
@ -2401,6 +2425,10 @@ swipe action</note>
<target>删除聊天资料?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete chat with member?" xml:space="preserve">
<source>Delete chat with member?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Delete chat?" xml:space="preserve">
<source>Delete chat?</source>
<target>删除聊天?</target>
@ -2824,7 +2852,7 @@ swipe action</note>
<trans-unit id="Don't show again" xml:space="preserve">
<source>Don't show again</source>
<target>不再显示</target>
<note>No comment provided by engineer.</note>
<note>alert action</note>
</trans-unit>
<trans-unit id="Done" xml:space="preserve">
<source>Done</source>
@ -3142,6 +3170,10 @@ chat item action</note>
<target>接受联系人请求错误</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error accepting member" xml:space="preserve">
<source>Error accepting member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error adding member(s)" xml:space="preserve">
<source>Error adding member(s)</source>
<target>添加成员错误</target>
@ -3236,6 +3268,10 @@ chat item action</note>
<target>删除聊天数据库错误</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error deleting chat with member" xml:space="preserve">
<source>Error deleting chat with member</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error deleting chat!" xml:space="preserve">
<source>Error deleting chat!</source>
<target>删除聊天错误!</target>
@ -3344,7 +3380,7 @@ chat item action</note>
<trans-unit id="Error removing member" xml:space="preserve">
<source>Error removing member</source>
<target>删除成员错误</target>
<note>No comment provided by engineer.</note>
<note>alert title</note>
</trans-unit>
<trans-unit id="Error reordering lists" xml:space="preserve">
<source>Error reordering lists</source>
@ -4728,6 +4764,10 @@ This is your link for group %@!</source>
<target>成员</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member admission" xml:space="preserve">
<source>Member admission</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member inactive" xml:space="preserve">
<source>Member inactive</source>
<target>成员不活跃</target>
@ -4763,6 +4803,10 @@ This is your link for group %@!</source>
<target>成员将被移出群组——此操作无法撤消!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Member will join the group, accept member?" xml:space="preserve">
<source>Member will join the group, accept member?</source>
<note>alert message</note>
</trans-unit>
<trans-unit id="Members can add message reactions." xml:space="preserve">
<source>Members can add message reactions.</source>
<target>群组成员可以添加信息回应。</target>
@ -5183,6 +5227,10 @@ This is your link for group %@!</source>
<target>新成员角色</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="New member wants to join the group." xml:space="preserve">
<source>New member wants to join the group.</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="New message" xml:space="preserve">
<source>New message</source>
<target>新消息</target>
@ -5223,6 +5271,10 @@ This is your link for group %@!</source>
<target>列表 %@ 中无聊天</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No chats with members" xml:space="preserve">
<source>No chats with members</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="No contacts selected" xml:space="preserve">
<source>No contacts selected</source>
<target>未选择联系人</target>
@ -5415,7 +5467,8 @@ This is your link for group %@!</source>
<trans-unit id="Ok" xml:space="preserve">
<source>Ok</source>
<target>好的</target>
<note>alert button</note>
<note>alert action
alert button</note>
</trans-unit>
<trans-unit id="Old database" xml:space="preserve">
<source>Old database</source>
@ -5823,6 +5876,10 @@ Error: %@</source>
<source>Please try to disable and re-enable notfications.</source>
<note>token info</note>
</trans-unit>
<trans-unit id="Please wait for group moderators to review your request to join the group." xml:space="preserve">
<source>Please wait for group moderators to review your request to join the group.</source>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="Please wait for token activation to complete." xml:space="preserve">
<source>Please wait for token activation to complete.</source>
<note>token info</note>
@ -6265,6 +6322,10 @@ swipe action</note>
<target>拒绝联系人请求</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Reject member?" xml:space="preserve">
<source>Reject member?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Relay server is only used if necessary. Another party can observe your IP address." xml:space="preserve">
<source>Relay server is only used if necessary. Another party can observe your IP address.</source>
<target>中继服务器仅在必要时使用。其他人可能会观察到您的IP地址。</target>
@ -6369,6 +6430,10 @@ swipe action</note>
<source>Report reason?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Report sent to moderators" xml:space="preserve">
<source>Report sent to moderators</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Report spam: only group moderators will see it." xml:space="preserve">
<source>Report spam: only group moderators will see it.</source>
<note>report reason</note>
@ -6479,6 +6544,14 @@ swipe action</note>
<target>审阅条款</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Review members" xml:space="preserve">
<source>Review members</source>
<note>admission stage</note>
</trans-unit>
<trans-unit id="Review members before admitting (&quot;knocking&quot;)." xml:space="preserve">
<source>Review members before admitting ("knocking").</source>
<note>admission stage description</note>
</trans-unit>
<trans-unit id="Revoke" xml:space="preserve">
<source>Revoke</source>
<target>吊销</target>
@ -6534,6 +6607,10 @@ chat item action</note>
<target>保存(并通知联系人)</target>
<note>alert button</note>
</trans-unit>
<trans-unit id="Save admission settings?" xml:space="preserve">
<source>Save admission settings?</source>
<note>alert title</note>
</trans-unit>
<trans-unit id="Save and notify contact" xml:space="preserve">
<source>Save and notify contact</source>
<target>保存并通知联系人</target>
@ -7041,6 +7118,10 @@ chat item action</note>
<target>设置它以代替系统身份验证。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set member admission" xml:space="preserve">
<source>Set member admission</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Set message expiration in chats." xml:space="preserve">
<source>Set message expiration in chats.</source>
<note>No comment provided by engineer.</note>
@ -8750,6 +8831,10 @@ Repeat join request?</source>
<target>您可以在连接详情中再次查看邀请链接。</target>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can view your reports in Chat with admins." xml:space="preserve">
<source>You can view your reports in Chat with admins.</source>
<note>alert message</note>
</trans-unit>
<trans-unit id="You can't send messages!" xml:space="preserve">
<source>You can't send messages!</source>
<target>您无法发送消息!</target>
@ -9045,6 +9130,10 @@ Repeat connection request?</source>
<target>上面,然后选择:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="accepted %@" xml:space="preserve">
<source>accepted %@</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="accepted call" xml:space="preserve">
<source>accepted call</source>
<target>已接受通话</target>
@ -9054,6 +9143,10 @@ Repeat connection request?</source>
<source>accepted invitation</source>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="accepted you" xml:space="preserve">
<source>accepted you</source>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="admin" xml:space="preserve">
<source>admin</source>
<target>管理员</target>
@ -9074,6 +9167,10 @@ Repeat connection request?</source>
<target>同意加密…</target>
<note>chat item text</note>
</trans-unit>
<trans-unit id="all" xml:space="preserve">
<source>all</source>
<note>member criteria value</note>
</trans-unit>
<trans-unit id="all members" xml:space="preserve">
<source>all members</source>
<target>所有成员</target>
@ -9159,6 +9256,10 @@ marked deleted chat item preview text</note>
<target>呼叫中……</target>
<note>call status</note>
</trans-unit>
<trans-unit id="can't send messages" xml:space="preserve">
<source>can't send messages</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="cancelled %@" xml:space="preserve">
<source>cancelled %@</source>
<target>已取消 %@</target>
@ -9264,6 +9365,14 @@ marked deleted chat item preview text</note>
<target>联系人 %1$@ 已更改为 %2$@</target>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="contact deleted" xml:space="preserve">
<source>contact deleted</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact disabled" xml:space="preserve">
<source>contact disabled</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact has e2e encryption" xml:space="preserve">
<source>contact has e2e encryption</source>
<target>联系人具有端到端加密</target>
@ -9274,6 +9383,10 @@ marked deleted chat item preview text</note>
<target>联系人没有端到端加密</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="contact not ready" xml:space="preserve">
<source>contact not ready</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="creator" xml:space="preserve">
<source>creator</source>
<target>创建者</target>
@ -9445,6 +9558,10 @@ pref value</note>
<target>群组已删除</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group is deleted" xml:space="preserve">
<source>group is deleted</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="group profile updated" xml:space="preserve">
<source>group profile updated</source>
<target>群组资料已更新</target>
@ -9570,6 +9687,10 @@ pref value</note>
<target>已连接</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="member has old version" xml:space="preserve">
<source>member has old version</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="message" xml:space="preserve">
<source>message</source>
<target>消息</target>
@ -9634,6 +9755,10 @@ pref value</note>
<target>无文本</target>
<note>copied message info in history</note>
</trans-unit>
<trans-unit id="not synchronized" xml:space="preserve">
<source>not synchronized</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="observer" xml:space="preserve">
<source>observer</source>
<target>观察者</target>
@ -9644,6 +9769,7 @@ pref value</note>
<target>关闭</target>
<note>enabled status
group pref value
member criteria value
time to disappear</note>
</trans-unit>
<trans-unit id="offered %@" xml:space="preserve">
@ -9694,6 +9820,10 @@ time to disappear</note>
<source>pending approval</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="pending review" xml:space="preserve">
<source>pending review</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="quantum resistant e2e encryption" xml:space="preserve">
<source>quantum resistant e2e encryption</source>
<target>抗量子端到端加密</target>
@ -9733,6 +9863,10 @@ time to disappear</note>
<target>删除了联系地址</target>
<note>profile update event chat item</note>
</trans-unit>
<trans-unit id="removed from group" xml:space="preserve">
<source>removed from group</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="removed profile picture" xml:space="preserve">
<source>removed profile picture</source>
<target>删除了资料图片</target>
@ -9743,10 +9877,22 @@ time to disappear</note>
<target>已将您移除</target>
<note>rcv group event chat item</note>
</trans-unit>
<trans-unit id="request to join rejected" xml:space="preserve">
<source>request to join rejected</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="requested to connect" xml:space="preserve">
<source>requested to connect</source>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="review" xml:space="preserve">
<source>review</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="reviewed by admins" xml:space="preserve">
<source>reviewed by admins</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="saved" xml:space="preserve">
<source>saved</source>
<target>已保存</target>
@ -9936,6 +10082,10 @@ last received msg: %2$@</source>
<target>您</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="you accepted this member" xml:space="preserve">
<source>you accepted this member</source>
<note>snd group event chat item</note>
</trans-unit>
<trans-unit id="you are invited to group" xml:space="preserve">
<source>you are invited to group</source>
<target>您被邀请加入群组</target>

View file

@ -1,6 +1,9 @@
/* notification body */
"%d new events" = "%d новых сообщений";
/* notification body */
"From %d chat(s)" = "Из %d чатов";
/* notification body */
"From: %@" = "От: %@";

View file

@ -67,6 +67,7 @@ func apiSendMessages(
: SEChatCommand.apiSendMessages(
type: chatInfo.chatType,
id: chatInfo.apiId,
scope: chatInfo.groupChatScope(),
live: false,
ttl: nil,
composedMessages: composedMessages
@ -123,7 +124,7 @@ enum SEChatCommand: ChatCmdProtocol {
case apiSetEncryptLocalFiles(enable: Bool)
case apiGetChats(userId: Int64)
case apiCreateChatItems(noteFolderId: Int64, composedMessages: [ComposedMessage])
case apiSendMessages(type: ChatType, id: Int64, live: Bool, ttl: Int?, composedMessages: [ComposedMessage])
case apiSendMessages(type: ChatType, id: Int64, scope: GroupChatScope?, live: Bool, ttl: Int?, composedMessages: [ComposedMessage])
var cmdString: String {
switch self {
@ -139,15 +140,27 @@ enum SEChatCommand: ChatCmdProtocol {
case let .apiCreateChatItems(noteFolderId, composedMessages):
let msgs = encodeJSON(composedMessages)
return "/_create *\(noteFolderId) json \(msgs)"
case let .apiSendMessages(type, id, live, ttl, composedMessages):
case let .apiSendMessages(type, id, scope, live, ttl, composedMessages):
let msgs = encodeJSON(composedMessages)
let ttlStr = ttl != nil ? "\(ttl!)" : "default"
return "/_send \(ref(type, id)) live=\(onOff(live)) ttl=\(ttlStr) json \(msgs)"
return "/_send \(ref(type, id, scope: scope)) live=\(onOff(live)) ttl=\(ttlStr) json \(msgs)"
}
}
func ref(_ type: ChatType, _ id: Int64) -> String {
"\(type.rawValue)\(id)"
func ref(_ type: ChatType, _ id: Int64, scope: GroupChatScope?) -> String {
"\(type.rawValue)\(id)\(scopeRef(scope: scope))"
}
func scopeRef(scope: GroupChatScope?) -> String {
switch (scope) {
case .none: ""
case let .memberSupport(groupMemberId_):
if let groupMemberId = groupMemberId_ {
"(_support:\(groupMemberId))"
} else {
"(_support)"
}
}
}
}

View file

@ -168,14 +168,19 @@
648679AB2BC96A74006456E7 /* ChatItemForwardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648679AA2BC96A74006456E7 /* ChatItemForwardingView.swift */; };
649BCDA0280460FD00C3A862 /* ComposeImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */; };
649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCDA12805D6EF00C3A862 /* CIImageView.swift */; };
64A779F62DBFB9F200FDEF2F /* MemberAdmissionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A779F52DBFB9F200FDEF2F /* MemberAdmissionView.swift */; };
64A779F82DBFDBF200FDEF2F /* MemberSupportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A779F72DBFDBF200FDEF2F /* MemberSupportView.swift */; };
64A779FC2DC1040000FDEF2F /* SecondaryChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A779FB2DC1040000FDEF2F /* SecondaryChatView.swift */; };
64A779FE2DC3AFF200FDEF2F /* MemberSupportChatToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A779FD2DC3AFF200FDEF2F /* MemberSupportChatToolbar.swift */; };
64A77A022DC4AD6100FDEF2F /* ContextPendingMemberActionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A77A012DC4AD6100FDEF2F /* ContextPendingMemberActionsView.swift */; };
64AA1C6927EE10C800AC7277 /* ContextItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6827EE10C800AC7277 /* ContextItemView.swift */; };
64AA1C6C27F3537400AC7277 /* DeletedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6B27F3537400AC7277 /* DeletedItemView.swift */; };
64C06EB52A0A4A7C00792D4D /* ChatItemInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C06EB42A0A4A7C00792D4D /* ChatItemInfoView.swift */; };
64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; };
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; };
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; };
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a */; };
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.a */; };
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F-ghc9.6.3.a */; };
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F.a */; };
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; };
64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; };
64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; };
@ -527,14 +532,19 @@
6493D667280ED77F007A76FB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
649BCD9F280460FD00C3A862 /* ComposeImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeImageView.swift; sourceTree = "<group>"; };
649BCDA12805D6EF00C3A862 /* CIImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIImageView.swift; sourceTree = "<group>"; };
64A779F52DBFB9F200FDEF2F /* MemberAdmissionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberAdmissionView.swift; sourceTree = "<group>"; };
64A779F72DBFDBF200FDEF2F /* MemberSupportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberSupportView.swift; sourceTree = "<group>"; };
64A779FB2DC1040000FDEF2F /* SecondaryChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondaryChatView.swift; sourceTree = "<group>"; };
64A779FD2DC3AFF200FDEF2F /* MemberSupportChatToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberSupportChatToolbar.swift; sourceTree = "<group>"; };
64A77A012DC4AD6100FDEF2F /* ContextPendingMemberActionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextPendingMemberActionsView.swift; sourceTree = "<group>"; };
64AA1C6827EE10C800AC7277 /* ContextItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextItemView.swift; sourceTree = "<group>"; };
64AA1C6B27F3537400AC7277 /* DeletedItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedItemView.swift; sourceTree = "<group>"; };
64C06EB42A0A4A7C00792D4D /* ChatItemInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemInfoView.swift; sourceTree = "<group>"; };
64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = "<group>"; };
64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a"; sourceTree = "<group>"; };
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.a"; sourceTree = "<group>"; };
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F-ghc9.6.3.a"; sourceTree = "<group>"; };
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F.a"; sourceTree = "<group>"; };
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = "<group>"; };
64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = "<group>"; };
@ -692,8 +702,8 @@
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */,
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */,
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */,
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a in Frameworks */,
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.a in Frameworks */,
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F-ghc9.6.3.a in Frameworks */,
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F.a in Frameworks */,
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -778,8 +788,8 @@
64C829992D54AEEE006B9E89 /* libffi.a */,
64C829982D54AEED006B9E89 /* libgmp.a */,
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */,
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a */,
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.a */,
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F-ghc9.6.3.a */,
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F.a */,
);
path = Libraries;
sourceTree = "<group>";
@ -1076,6 +1086,7 @@
644EFFDD292BCD9D00525D5B /* ComposeVoiceView.swift */,
D72A9087294BD7A70047C86D /* NativeTextEditor.swift */,
6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */,
64A77A012DC4AD6100FDEF2F /* ContextPendingMemberActionsView.swift */,
);
path = ComposeMessage;
sourceTree = "<group>";
@ -1117,6 +1128,10 @@
6448BBB528FA9D56000D2AB9 /* GroupLinkView.swift */,
1841516F0CE5992B0EDFB377 /* GroupWelcomeView.swift */,
B70CE9E52D4BE5930080F36D /* GroupMentions.swift */,
64A779F52DBFB9F200FDEF2F /* MemberAdmissionView.swift */,
64A779F72DBFDBF200FDEF2F /* MemberSupportView.swift */,
64A779FB2DC1040000FDEF2F /* SecondaryChatView.swift */,
64A779FD2DC3AFF200FDEF2F /* MemberSupportChatToolbar.swift */,
);
path = Group;
sourceTree = "<group>";
@ -1431,8 +1446,10 @@
640417CE2B29B8C200CCB412 /* NewChatView.swift in Sources */,
6440CA03288AECA70062C672 /* AddGroupMembersView.swift in Sources */,
640743612CD360E600158442 /* ChooseServerOperators.swift in Sources */,
64A779FE2DC3AFF200FDEF2F /* MemberSupportChatToolbar.swift in Sources */,
5C3F1D58284363C400EC8A82 /* PrivacySettings.swift in Sources */,
5C55A923283CEDE600C4E99E /* SoundPlayer.swift in Sources */,
64A779F82DBFDBF200FDEF2F /* MemberSupportView.swift in Sources */,
5C93292F29239A170090FFF9 /* ProtocolServersView.swift in Sources */,
5CB924D727A8563F00ACCCDD /* SettingsView.swift in Sources */,
B79ADAFF2CE4EF930083DFFD /* AddressCreationCard.swift in Sources */,
@ -1467,6 +1484,7 @@
5CB634AF29E4BB7D0066AD6B /* SetAppPasscodeView.swift in Sources */,
5C10D88828EED12E00E58BF0 /* ContactConnectionInfo.swift in Sources */,
5CBE6C12294487F7002D9531 /* VerifyCodeView.swift in Sources */,
64A779FC2DC1040000FDEF2F /* SecondaryChatView.swift in Sources */,
3CDBCF4227FAE51000354CDD /* ComposeLinkView.swift in Sources */,
648679AB2BC96A74006456E7 /* ChatItemForwardingView.swift in Sources */,
3CDBCF4827FF621E00354CDD /* CILinkView.swift in Sources */,
@ -1509,6 +1527,7 @@
5CB346E72868D76D001FD2EF /* NotificationsView.swift in Sources */,
647F090E288EA27B00644C40 /* GroupMemberInfoView.swift in Sources */,
646BB38E283FDB6D001CE359 /* LocalAuthenticationUtils.swift in Sources */,
64A77A022DC4AD6100FDEF2F /* ContextPendingMemberActionsView.swift in Sources */,
8C74C3EA2C1B90AF00039E77 /* ThemeManager.swift in Sources */,
5C7505A227B65FDB00BE3227 /* CIMetaView.swift in Sources */,
5C35CFC827B2782E00FB6C6D /* BGManager.swift in Sources */,
@ -1587,6 +1606,7 @@
1841560FD1CD447955474C1D /* UserProfilesView.swift in Sources */,
64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */,
8CC4ED902BD7B8530078AEE8 /* CallAudioDeviceManager.swift in Sources */,
64A779F62DBFB9F200FDEF2F /* MemberAdmissionView.swift in Sources */,
18415C6C56DBCEC2CBBD2F11 /* WebRTCClient.swift in Sources */,
8CB15EA02CFDA30600C28209 /* ChatItemsMerger.swift in Sources */,
184152CEF68D2336FC2EBCB0 /* CallViewRenderers.swift in Sources */,
@ -1971,7 +1991,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 282;
CURRENT_PROJECT_VERSION = 279;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
@ -1996,7 +2016,7 @@
"@executable_path/Frameworks",
);
LLVM_LTO = YES_THIN;
MARKETING_VERSION = 6.3.6;
MARKETING_VERSION = 6.4;
OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000";
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
PRODUCT_NAME = SimpleX;
@ -2021,7 +2041,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 282;
CURRENT_PROJECT_VERSION = 279;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
@ -2046,7 +2066,7 @@
"@executable_path/Frameworks",
);
LLVM_LTO = YES;
MARKETING_VERSION = 6.3.6;
MARKETING_VERSION = 6.4;
OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000";
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
PRODUCT_NAME = SimpleX;
@ -2063,11 +2083,11 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 282;
CURRENT_PROJECT_VERSION = 279;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 6.3.6;
MARKETING_VERSION = 6.4;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@ -2083,11 +2103,11 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 282;
CURRENT_PROJECT_VERSION = 279;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 6.3.6;
MARKETING_VERSION = 6.4;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@ -2108,7 +2128,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 282;
CURRENT_PROJECT_VERSION = 279;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
GCC_OPTIMIZATION_LEVEL = s;
@ -2123,7 +2143,7 @@
"@executable_path/../../Frameworks",
);
LLVM_LTO = YES;
MARKETING_VERSION = 6.3.6;
MARKETING_VERSION = 6.4;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -2145,7 +2165,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 282;
CURRENT_PROJECT_VERSION = 279;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
ENABLE_CODE_COVERAGE = NO;
@ -2160,7 +2180,7 @@
"@executable_path/../../Frameworks",
);
LLVM_LTO = YES;
MARKETING_VERSION = 6.3.6;
MARKETING_VERSION = 6.4;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -2182,7 +2202,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 282;
CURRENT_PROJECT_VERSION = 279;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1;
@ -2208,7 +2228,7 @@
"$(PROJECT_DIR)/Libraries/sim",
);
LLVM_LTO = YES;
MARKETING_VERSION = 6.3.6;
MARKETING_VERSION = 6.4;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SDKROOT = iphoneos;
@ -2233,7 +2253,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 282;
CURRENT_PROJECT_VERSION = 279;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1;
@ -2259,7 +2279,7 @@
"$(PROJECT_DIR)/Libraries/sim",
);
LLVM_LTO = YES;
MARKETING_VERSION = 6.3.6;
MARKETING_VERSION = 6.4;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SDKROOT = iphoneos;
@ -2284,7 +2304,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 282;
CURRENT_PROJECT_VERSION = 279;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@ -2299,7 +2319,7 @@
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 6.3.6;
MARKETING_VERSION = 6.4;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@ -2318,7 +2338,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 282;
CURRENT_PROJECT_VERSION = 279;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@ -2333,7 +2353,7 @@
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 6.3.6;
MARKETING_VERSION = 6.4;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;

View file

@ -15,6 +15,9 @@ public let CREATE_MEMBER_CONTACT_VERSION = 2
// version to receive reports (MCReport)
public let REPORTS_VERSION = 12
// support group knocking (MsgScope)
public let GROUP_KNOCKING_VERSION = 15
public let contentModerationPostLink = URL(string: "https://simplex.chat/blog/20250114-simplex-network-large-groups-privacy-preserving-content-moderation.html#preventing-server-abuse-without-compromising-e2e-encryption")!
public struct User: Identifiable, Decodable, UserLike, NamedChat, Hashable {
@ -1197,7 +1200,7 @@ public enum GroupFeatureEnabled: String, Codable, Identifiable, Hashable {
public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
case direct(contact: Contact)
case group(groupInfo: GroupInfo)
case group(groupInfo: GroupInfo, groupChatScope: GroupChatScopeInfo?)
case local(noteFolder: NoteFolder)
case contactRequest(contactRequest: UserContactRequest)
case contactConnection(contactConnection: PendingContactConnection)
@ -1211,7 +1214,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
get {
switch self {
case let .direct(contact): return contact.localDisplayName
case let .group(groupInfo): return groupInfo.localDisplayName
case let .group(groupInfo, _): return groupInfo.localDisplayName
case .local: return ""
case let .contactRequest(contactRequest): return contactRequest.localDisplayName
case let .contactConnection(contactConnection): return contactConnection.localDisplayName
@ -1224,7 +1227,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
get {
switch self {
case let .direct(contact): return contact.displayName
case let .group(groupInfo): return groupInfo.displayName
case let .group(groupInfo, _): return groupInfo.displayName
case .local: return ChatInfo.privateNotesChatName
case let .contactRequest(contactRequest): return contactRequest.displayName
case let .contactConnection(contactConnection): return contactConnection.displayName
@ -1237,7 +1240,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
get {
switch self {
case let .direct(contact): return contact.fullName
case let .group(groupInfo): return groupInfo.fullName
case let .group(groupInfo, _): return groupInfo.fullName
case .local: return ""
case let .contactRequest(contactRequest): return contactRequest.fullName
case let .contactConnection(contactConnection): return contactConnection.fullName
@ -1250,7 +1253,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
get {
switch self {
case let .direct(contact): return contact.image
case let .group(groupInfo): return groupInfo.image
case let .group(groupInfo, _): return groupInfo.image
case .local: return nil
case let .contactRequest(contactRequest): return contactRequest.image
case let .contactConnection(contactConnection): return contactConnection.image
@ -1263,7 +1266,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
get {
switch self {
case let .direct(contact): return contact.localAlias
case let .group(groupInfo): return groupInfo.localAlias
case let .group(groupInfo, _): return groupInfo.localAlias
case .local: return ""
case let .contactRequest(contactRequest): return contactRequest.localAlias
case let .contactConnection(contactConnection): return contactConnection.localAlias
@ -1276,7 +1279,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
get {
switch self {
case let .direct(contact): return contact.id
case let .group(groupInfo): return groupInfo.id
case let .group(groupInfo, _): return groupInfo.id
case let .local(noteFolder): return noteFolder.id
case let .contactRequest(contactRequest): return contactRequest.id
case let .contactConnection(contactConnection): return contactConnection.id
@ -1302,7 +1305,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
get {
switch self {
case let .direct(contact): return contact.apiId
case let .group(groupInfo): return groupInfo.apiId
case let .group(groupInfo, _): return groupInfo.apiId
case let .local(noteFolder): return noteFolder.apiId
case let .contactRequest(contactRequest): return contactRequest.apiId
case let .contactConnection(contactConnection): return contactConnection.apiId
@ -1315,7 +1318,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
get {
switch self {
case let .direct(contact): return contact.ready
case let .group(groupInfo): return groupInfo.ready
case let .group(groupInfo, _): return groupInfo.ready
case let .local(noteFolder): return noteFolder.ready
case let .contactRequest(contactRequest): return contactRequest.ready
case let .contactConnection(contactConnection): return contactConnection.ready
@ -1336,34 +1339,57 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? {
get {
switch self {
case let .direct(contact): return contact.userCantSendReason
case let .group(groupInfo): return groupInfo.userCantSendReason
case let .local(noteFolder): return noteFolder.userCantSendReason
case let .contactRequest(contactRequest): return contactRequest.userCantSendReason
case let .contactConnection(contactConnection): return contactConnection.userCantSendReason
case .invalidJSON: return ("can't send messages", nil)
case let .direct(contact):
// TODO [short links] this will have additional statuses for pending contact requests before they are accepted
if contact.nextSendGrpInv { return nil }
if !contact.active { return ("contact deleted", nil) }
if !contact.sndReady { return ("contact not ready", nil) }
if contact.activeConn?.connectionStats?.ratchetSyncSendProhibited ?? false { return ("not synchronized", nil) }
if contact.activeConn?.connDisabled ?? true { return ("contact disabled", nil) }
return nil
case let .group(groupInfo, groupChatScope):
if groupInfo.membership.memberActive {
switch(groupChatScope) {
case .none:
if groupInfo.membership.memberPending { return ("reviewed by admins", "Please contact group admin.") }
if groupInfo.membership.memberRole == .observer { return ("you are observer", "Please contact group admin.") }
return nil
case let .some(.memberSupport(groupMember_: .some(supportMember))):
if supportMember.versionRange.maxVersion < GROUP_KNOCKING_VERSION && !supportMember.memberPending {
return ("member has old version", nil)
}
return nil
case .some(.memberSupport(groupMember_: .none)):
return nil
}
} else {
switch groupInfo.membership.memberStatus {
case .memRejected: return ("request to join rejected", nil)
case .memGroupDeleted: return ("group is deleted", nil)
case .memRemoved: return ("removed from group", nil)
case .memLeft: return ("you left", nil)
default: return ("can't send messages", nil)
}
}
case .local:
return nil
case .contactRequest:
return ("can't send messages", nil)
case .contactConnection:
return ("can't send messages", nil)
case .invalidJSON:
return ("can't send messages", nil)
}
}
}
public var sendMsgEnabled: Bool {
get {
switch self {
case let .direct(contact): return contact.sendMsgEnabled
case let .group(groupInfo): return groupInfo.sendMsgEnabled
case let .local(noteFolder): return noteFolder.sendMsgEnabled
case let .contactRequest(contactRequest): return contactRequest.sendMsgEnabled
case let .contactConnection(contactConnection): return contactConnection.sendMsgEnabled
case .invalidJSON: return false
}
}
}
public var sendMsgEnabled: Bool { userCantSendReason == nil }
public var incognito: Bool {
get {
switch self {
case let .direct(contact): return contact.contactConnIncognito
case let .group(groupInfo): return groupInfo.membership.memberIncognito
case let .group(groupInfo, _): return groupInfo.membership.memberIncognito
case .local: return false
case .contactRequest: return false
case let .contactConnection(contactConnection): return contactConnection.incognito
@ -1388,7 +1414,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
public var groupInfo: GroupInfo? {
switch self {
case let .group(groupInfo): return groupInfo
case let .group(groupInfo, _): return groupInfo
default: return nil
}
}
@ -1405,7 +1431,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
case .voice: return cups.voice.enabled.forUser
case .calls: return cups.calls.enabled.forUser
}
case let .group(groupInfo):
case let .group(groupInfo, _):
let prefs = groupInfo.fullGroupPreferences
switch feature {
case .timedMessages: return prefs.timedMessages.on
@ -1428,7 +1454,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
case let .direct(contact):
let pref = contact.mergedPreferences.timedMessages
return pref.enabled.forUser ? pref.userPreference.preference.ttl : nil
case let .group(groupInfo):
case let .group(groupInfo, _):
let pref = groupInfo.fullGroupPreferences.timedMessages
return pref.on ? pref.ttl : nil
default:
@ -1453,7 +1479,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
} else {
return .other
}
case let .group(groupInfo):
case let .group(groupInfo, _):
if !groupInfo.fullGroupPreferences.voice.on(for: groupInfo.membership) {
return .groupOwnerCan
} else {
@ -1484,7 +1510,14 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
return .other
}
}
public func groupChatScope() -> GroupChatScope? {
switch self {
case let .group(_, groupChatScope): groupChatScope?.toChatScope()
default: nil
}
}
public func ntfsEnabled(chatItem: ChatItem) -> Bool {
ntfsEnabled(chatItem.meta.userMention)
}
@ -1500,7 +1533,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
public var chatSettings: ChatSettings? {
switch self {
case let .direct(contact): return contact.chatSettings
case let .group(groupInfo): return groupInfo.chatSettings
case let .group(groupInfo, _): return groupInfo.chatSettings
default: return nil
}
}
@ -1516,7 +1549,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
public var chatTags: [Int64]? {
switch self {
case let .direct(contact): return contact.chatTags
case let .group(groupInfo): return groupInfo.chatTags
case let .group(groupInfo, _): return groupInfo.chatTags
default: return nil
}
}
@ -1524,7 +1557,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
var createdAt: Date {
switch self {
case let .direct(contact): return contact.createdAt
case let .group(groupInfo): return groupInfo.createdAt
case let .group(groupInfo, _): return groupInfo.createdAt
case let .local(noteFolder): return noteFolder.createdAt
case let .contactRequest(contactRequest): return contactRequest.createdAt
case let .contactConnection(contactConnection): return contactConnection.createdAt
@ -1535,7 +1568,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
public var updatedAt: Date {
switch self {
case let .direct(contact): return contact.updatedAt
case let .group(groupInfo): return groupInfo.updatedAt
case let .group(groupInfo, _): return groupInfo.updatedAt
case let .local(noteFolder): return noteFolder.updatedAt
case let .contactRequest(contactRequest): return contactRequest.updatedAt
case let .contactConnection(contactConnection): return contactConnection.updatedAt
@ -1546,7 +1579,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
public var chatTs: Date {
switch self {
case let .direct(contact): return contact.chatTs ?? contact.updatedAt
case let .group(groupInfo): return groupInfo.chatTs ?? groupInfo.updatedAt
case let .group(groupInfo, _): return groupInfo.chatTs ?? groupInfo.updatedAt
case let .local(noteFolder): return noteFolder.chatTs
case let .contactRequest(contactRequest): return contactRequest.updatedAt
case let .contactConnection(contactConnection): return contactConnection.updatedAt
@ -1562,7 +1595,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
} else {
ChatTTL.userDefault(globalTTL)
}
case let .group(groupInfo):
case let .group(groupInfo, _):
return if let ciTTL = groupInfo.chatItemTTL {
ChatTTL.chat(ChatItemTTL(ciTTL))
} else {
@ -1582,7 +1615,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
public static var sampleData: ChatInfo.SampleData = SampleData(
direct: ChatInfo.direct(contact: Contact.sampleData),
group: ChatInfo.group(groupInfo: GroupInfo.sampleData),
group: ChatInfo.group(groupInfo: GroupInfo.sampleData, groupChatScope: nil),
local: ChatInfo.local(noteFolder: NoteFolder.sampleData),
contactRequest: ChatInfo.contactRequest(contactRequest: UserContactRequest.sampleData),
contactConnection: ChatInfo.contactConnection(contactConnection: PendingContactConnection.getSampleData())
@ -1612,7 +1645,13 @@ public struct ChatData: Decodable, Identifiable, Hashable, ChatLike {
}
public struct ChatStats: Decodable, Hashable {
public init(unreadCount: Int = 0, unreadMentions: Int = 0, reportsCount: Int = 0, minUnreadItemId: Int64 = 0, unreadChat: Bool = false) {
public init(
unreadCount: Int = 0,
unreadMentions: Int = 0,
reportsCount: Int = 0,
minUnreadItemId: Int64 = 0,
unreadChat: Bool = false
) {
self.unreadCount = unreadCount
self.unreadMentions = unreadMentions
self.reportsCount = reportsCount
@ -1629,6 +1668,32 @@ public struct ChatStats: Decodable, Hashable {
public var unreadChat: Bool = false
}
public enum GroupChatScope: Decodable {
case memberSupport(groupMemberId_: Int64?)
}
public func sameChatScope(_ scope1: GroupChatScope, _ scope2: GroupChatScope) -> Bool {
switch (scope1, scope2) {
case let (.memberSupport(groupMemberId1_), .memberSupport(groupMemberId2_)):
return groupMemberId1_ == groupMemberId2_
}
}
public enum GroupChatScopeInfo: Decodable, Hashable {
case memberSupport(groupMember_: GroupMember?)
public func toChatScope() -> GroupChatScope {
switch self {
case let .memberSupport(groupMember_):
if let groupMember = groupMember_ {
return .memberSupport(groupMemberId_: groupMember.groupMemberId)
} else {
return .memberSupport(groupMemberId_: nil)
}
}
}
}
public struct Contact: Identifiable, Decodable, NamedChat, Hashable {
public var contactId: Int64
var localDisplayName: ContactName
@ -1655,16 +1720,6 @@ public struct Contact: Identifiable, Decodable, NamedChat, Hashable {
public var ready: Bool { get { activeConn?.connStatus == .ready } }
public var sndReady: Bool { get { ready || activeConn?.connStatus == .sndReady } }
public var active: Bool { get { contactStatus == .active } }
public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? {
// TODO [short links] this will have additional statuses for pending contact requests before they are accepted
if nextSendGrpInv { return nil }
if !active { return ("contact deleted", nil) }
if !sndReady { return ("contact not ready", nil) }
if activeConn?.connectionStats?.ratchetSyncSendProhibited ?? false { return ("not synchronized", nil) }
if activeConn?.connDisabled ?? true { return ("contact disabled", nil) }
return nil
}
public var sendMsgEnabled: Bool { userCantSendReason == nil }
public var nextSendGrpInv: Bool { get { contactGroupMemberId != nil && !contactGrpInvSent } }
public var displayName: String { localAlias == "" ? profile.displayName : localAlias }
public var fullName: String { get { profile.fullName } }
@ -1843,8 +1898,6 @@ public struct UserContactRequest: Decodable, NamedChat, Hashable {
public var id: ChatId { get { "<@\(contactRequestId)" } }
public var apiId: Int64 { get { contactRequestId } }
var ready: Bool { get { true } }
public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { ("can't send messages", nil) }
public var sendMsgEnabled: Bool { get { false } }
public var displayName: String { get { profile.displayName } }
public var fullName: String { get { profile.fullName } }
public var image: String? { get { profile.image } }
@ -1876,8 +1929,6 @@ public struct PendingContactConnection: Decodable, NamedChat, Hashable {
public var id: ChatId { get { ":\(pccConnId)" } }
public var apiId: Int64 { get { pccConnId } }
var ready: Bool { get { false } }
public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { ("can't send messages", nil) }
public var sendMsgEnabled: Bool { get { false } }
var localDisplayName: String {
get { String.localizedStringWithFormat(NSLocalizedString("connection:%@", comment: "connection information"), pccConnId) }
}
@ -2002,24 +2053,11 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable {
var updatedAt: Date
var chatTs: Date?
public var uiThemes: ThemeModeOverrides?
public var membersRequireAttention: Int
public var id: ChatId { get { "#\(groupId)" } }
public var apiId: Int64 { get { groupId } }
public var ready: Bool { get { true } }
public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? {
return if membership.memberActive {
membership.memberRole == .observer ? ("you are observer", "Please contact group admin.") : nil
} else {
switch membership.memberStatus {
case .memRejected: ("request to join rejected", nil)
case .memGroupDeleted: ("group is deleted", nil)
case .memRemoved: ("removed from group", nil)
case .memLeft: ("you left", nil)
default: ("can't send messages", nil)
}
}
}
public var sendMsgEnabled: Bool { userCantSendReason == nil }
public var displayName: String { localAlias == "" ? groupProfile.displayName : localAlias }
public var fullName: String { get { groupProfile.fullName } }
public var image: String? { get { groupProfile.image } }
@ -2032,13 +2070,17 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable {
}
public var canDelete: Bool {
return membership.memberRole == .owner || !membership.memberCurrent
return membership.memberRole == .owner || !membership.memberCurrentOrPending
}
public var canAddMembers: Bool {
return membership.memberRole >= .admin && membership.memberActive
}
public var canModerate: Bool {
return membership.memberRole >= .moderator && membership.memberActive
}
public static let sampleData = GroupInfo(
groupId: 1,
localDisplayName: "team",
@ -2048,6 +2090,7 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable {
chatSettings: ChatSettings.defaults,
createdAt: .now,
updatedAt: .now,
membersRequireAttention: 0,
chatTags: [],
localAlias: ""
)
@ -2059,12 +2102,20 @@ public struct GroupRef: Decodable, Hashable {
}
public struct GroupProfile: Codable, NamedChat, Hashable {
public init(displayName: String, fullName: String, description: String? = nil, image: String? = nil, groupPreferences: GroupPreferences? = nil) {
public init(
displayName: String,
fullName: String,
description: String? = nil,
image: String? = nil,
groupPreferences: GroupPreferences? = nil,
memberAdmission: GroupMemberAdmission? = nil
) {
self.displayName = displayName
self.fullName = fullName
self.description = description
self.image = image
self.groupPreferences = groupPreferences
self.memberAdmission = memberAdmission
}
public var displayName: String
@ -2072,14 +2123,48 @@ public struct GroupProfile: Codable, NamedChat, Hashable {
public var description: String?
public var image: String?
public var groupPreferences: GroupPreferences?
public var memberAdmission: GroupMemberAdmission?
public var localAlias: String { "" }
public var memberAdmission_: GroupMemberAdmission {
get { self.memberAdmission ?? GroupMemberAdmission() }
set { memberAdmission = newValue }
}
public static let sampleData = GroupProfile(
displayName: "team",
fullName: "My Team"
)
}
public struct GroupMemberAdmission: Codable, Hashable {
public var review: MemberCriteria?
public init(
review: MemberCriteria? = nil
) {
self.review = review
}
public static let sampleData = GroupMemberAdmission(
review: .all
)
}
public enum MemberCriteria: String, Codable, Identifiable, Hashable {
case all
public static var values: [MemberCriteria] { [.all] }
public var id: Self { self }
public var text: String {
switch self {
case .all: return NSLocalizedString("all", comment: "member criteria value")
}
}
}
public struct BusinessChatInfo: Decodable, Hashable {
public var chatType: BusinessChatType
public var businessId: String
@ -2106,6 +2191,7 @@ public struct GroupMember: Identifiable, Decodable, Hashable {
public var memberContactId: Int64?
public var memberContactProfileId: Int64
public var activeConn: Connection?
public var supportChat: GroupSupportChat?
public var memberChatVRange: VersionRange
public var id: String { "#\(groupId) @\(groupMemberId)" }
@ -2177,6 +2263,7 @@ public struct GroupMember: Identifiable, Decodable, Hashable {
case .memUnknown: return false
case .memInvited: return false
case .memPendingApproval: return true
case .memPendingReview: return true
case .memIntroduced: return false
case .memIntroInvited: return false
case .memAccepted: return false
@ -2196,6 +2283,7 @@ public struct GroupMember: Identifiable, Decodable, Hashable {
case .memUnknown: return false
case .memInvited: return false
case .memPendingApproval: return false
case .memPendingReview: return false
case .memIntroduced: return true
case .memIntroInvited: return true
case .memAccepted: return true
@ -2206,6 +2294,18 @@ public struct GroupMember: Identifiable, Decodable, Hashable {
}
}
public var memberPending: Bool {
switch memberStatus {
case .memPendingApproval: return true
case .memPendingReview: return true
default: return false
}
}
public var memberCurrentOrPending: Bool {
memberCurrent || memberPending
}
public func canBeRemoved(groupInfo: GroupInfo) -> Bool {
let userRole = groupInfo.membership.memberRole
return memberStatus != .memRemoved && memberStatus != .memLeft
@ -2259,6 +2359,13 @@ public struct GroupMember: Identifiable, Decodable, Hashable {
)
}
public struct GroupSupportChat: Codable, Hashable {
public var chatTs: Date
public var unread: Int
public var memberAttention: Int
public var mentions: Int
}
public struct GroupMemberSettings: Codable, Hashable {
public var showMessages: Bool
}
@ -2328,6 +2435,7 @@ public enum GroupMemberStatus: String, Decodable, Hashable {
case memUnknown = "unknown"
case memInvited = "invited"
case memPendingApproval = "pending_approval"
case memPendingReview = "pending_review"
case memIntroduced = "introduced"
case memIntroInvited = "intro-inv"
case memAccepted = "accepted"
@ -2345,6 +2453,7 @@ public enum GroupMemberStatus: String, Decodable, Hashable {
case .memUnknown: return "unknown status"
case .memInvited: return "invited"
case .memPendingApproval: return "pending approval"
case .memPendingReview: return "pending review"
case .memIntroduced: return "connecting (introduced)"
case .memIntroInvited: return "connecting (introduction invitation)"
case .memAccepted: return "connecting (accepted)"
@ -2364,6 +2473,7 @@ public enum GroupMemberStatus: String, Decodable, Hashable {
case .memUnknown: return "unknown"
case .memInvited: return "invited"
case .memPendingApproval: return "pending"
case .memPendingReview: return "review"
case .memIntroduced: return "connecting"
case .memIntroInvited: return "connecting"
case .memAccepted: return "connecting"
@ -2386,8 +2496,6 @@ public struct NoteFolder: Identifiable, Decodable, NamedChat, Hashable {
public var id: ChatId { get { "*\(noteFolderId)" } }
public var apiId: Int64 { get { noteFolderId } }
public var ready: Bool { get { true } }
public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { nil }
public var sendMsgEnabled: Bool { get { true } }
public var displayName: String { get { ChatInfo.privateNotesChatName } }
public var fullName: String { get { "" } }
public var image: String? { get { nil } }
@ -2647,12 +2755,15 @@ public struct ChatItem: Identifiable, Decodable, Hashable {
case .userDeleted: nil
case .groupDeleted: nil
case .memberCreatedContact: nil
case .newMemberPendingReview: nil
default: .rcvGroupEvent
}
case let .sndGroupEvent(event):
switch event {
case .userRole: nil
case .userLeft: nil
case .memberAccepted: nil
case .userPendingReview: nil
default: .sndGroupEvent
}
default:
@ -2685,6 +2796,8 @@ public struct ChatItem: Identifiable, Decodable, Hashable {
switch rcvGroupEvent {
case .groupUpdated: return false
case .memberConnected: return false
case .memberAccepted: return false
case .userAccepted: return false
case .memberRole: return false
case .memberBlocked: return false
case .userRole: return true
@ -2696,6 +2809,7 @@ public struct ChatItem: Identifiable, Decodable, Hashable {
case .invitedViaGroupLink: return false
case .memberCreatedContact: return false
case .memberProfileUpdated: return false
case .newMemberPendingReview: return true
}
case .sndGroupEvent: return false
case .rcvConnEvent: return false
@ -2764,12 +2878,12 @@ public struct ChatItem: Identifiable, Decodable, Hashable {
public func memberToModerate(_ chatInfo: ChatInfo) -> (GroupInfo, GroupMember?)? {
switch (chatInfo, chatDir) {
case let (.group(groupInfo), .groupRcv(groupMember)):
case let (.group(groupInfo, _), .groupRcv(groupMember)):
let m = groupInfo.membership
return m.memberRole >= .admin && m.memberRole >= groupMember.memberRole && meta.itemDeleted == nil
? (groupInfo, groupMember)
: nil
case let (.group(groupInfo), .groupSnd):
case let (.group(groupInfo, _), .groupSnd):
let m = groupInfo.membership
return m.memberRole >= .admin ? (groupInfo, nil) : nil
default: return nil
@ -3151,6 +3265,21 @@ public enum CIStatus: Decodable, Hashable {
}
}
// as in corresponds to SENT response from agent, opposed to `sent` which means snd status
public var isSent: Bool {
switch self {
case .sndNew: false
case .sndSent: true
case .sndRcvd: false
case .sndErrorAuth: true
case .sndError: true
case .sndWarning: true
case .rcvNew: false
case .rcvRead: false
case .invalid: false
}
}
public func statusIcon(_ metaColor: Color, _ paleMetaColor: Color, _ primaryColor: Color = .accentColor) -> (Image, Color)? {
switch self {
case .sndNew: nil
@ -3204,6 +3333,17 @@ public enum CIStatus: Decodable, Hashable {
}
}
public func shouldKeepOldSndCIStatus(oldStatus: CIStatus, newStatus: CIStatus) -> Bool {
switch (oldStatus, newStatus) {
case (.sndRcvd, let new) where !new.isSndRcvd:
return true
case (let old, .sndNew) where old.isSent:
return true
default:
return false
}
}
public enum SndError: Decodable, Hashable {
case auth
case quota
@ -4103,6 +4243,16 @@ extension MsgContent: Encodable {
}
}
public enum MsgContentTag: String {
case text
case link
case image
case video
case voice
case file
case report
}
public struct FormattedText: Decodable, Hashable {
public var text: String
public var format: Format?
@ -4440,6 +4590,8 @@ public enum RcvDirectEvent: Decodable, Hashable {
public enum RcvGroupEvent: Decodable, Hashable {
case memberAdded(groupMemberId: Int64, profile: Profile)
case memberConnected
case memberAccepted(groupMemberId: Int64, profile: Profile)
case userAccepted
case memberLeft
case memberRole(groupMemberId: Int64, profile: Profile, role: GroupMemberRole)
case memberBlocked(groupMemberId: Int64, profile: Profile, blocked: Bool)
@ -4451,12 +4603,16 @@ public enum RcvGroupEvent: Decodable, Hashable {
case invitedViaGroupLink
case memberCreatedContact
case memberProfileUpdated(fromProfile: Profile, toProfile: Profile)
case newMemberPendingReview
var text: String {
switch self {
case let .memberAdded(_, profile):
return String.localizedStringWithFormat(NSLocalizedString("invited %@", comment: "rcv group event chat item"), profile.profileViewName)
case .memberConnected: return NSLocalizedString("member connected", comment: "rcv group event chat item")
case let .memberAccepted(_, profile):
return String.localizedStringWithFormat(NSLocalizedString("accepted %@", comment: "rcv group event chat item"), profile.profileViewName)
case .userAccepted: return NSLocalizedString("accepted you", comment: "rcv group event chat item")
case .memberLeft: return NSLocalizedString("left", comment: "rcv group event chat item")
case let .memberRole(_, profile, role):
return String.localizedStringWithFormat(NSLocalizedString("changed role of %@ to %@", comment: "rcv group event chat item"), profile.profileViewName, role.text)
@ -4476,6 +4632,7 @@ public enum RcvGroupEvent: Decodable, Hashable {
case .invitedViaGroupLink: return NSLocalizedString("invited via your group link", comment: "rcv group event chat item")
case .memberCreatedContact: return NSLocalizedString("connected directly", comment: "rcv group event chat item")
case let .memberProfileUpdated(fromProfile, toProfile): return profileUpdatedText(fromProfile, toProfile)
case .newMemberPendingReview: return NSLocalizedString("New member wants to join the group.", comment: "rcv group event chat item")
}
}
@ -4500,6 +4657,8 @@ public enum SndGroupEvent: Decodable, Hashable {
case memberDeleted(groupMemberId: Int64, profile: Profile)
case userLeft
case groupUpdated(groupProfile: GroupProfile)
case memberAccepted(groupMemberId: Int64, profile: Profile)
case userPendingReview
var text: String {
switch self {
@ -4517,6 +4676,9 @@ public enum SndGroupEvent: Decodable, Hashable {
return String.localizedStringWithFormat(NSLocalizedString("you removed %@", comment: "snd group event chat item"), profile.profileViewName)
case .userLeft: return NSLocalizedString("you left", comment: "snd group event chat item")
case .groupUpdated: return NSLocalizedString("group profile updated", comment: "snd group event chat item")
case .memberAccepted: return NSLocalizedString("you accepted this member", comment: "snd group event chat item")
case .userPendingReview:
return NSLocalizedString("Please wait for group moderators to review your request to join the group.", comment: "snd group event chat item")
}
}
}

View file

@ -16,7 +16,7 @@ public protocol ChatLike {
extension ChatLike {
public func groupFeatureEnabled(_ feature: GroupFeature) -> Bool {
if case let .group(groupInfo) = self.chatInfo {
if case let .group(groupInfo, _) = self.chatInfo {
let p = groupInfo.fullGroupPreferences
return switch feature {
case .timedMessages: p.timedMessages.on
@ -82,9 +82,9 @@ public func foundChat(_ chat: ChatLike, _ searchStr: String) -> Bool {
private func canForwardToChat(_ cInfo: ChatInfo) -> Bool {
switch cInfo {
case let .direct(contact): contact.sendMsgEnabled && !contact.nextSendGrpInv
case let .group(groupInfo): groupInfo.sendMsgEnabled
case let .local(noteFolder): noteFolder.sendMsgEnabled
case let .direct(contact): cInfo.sendMsgEnabled && !contact.nextSendGrpInv
case .group: cInfo.sendMsgEnabled
case .local: cInfo.sendMsgEnabled
case .contactRequest: false
case .contactConnection: false
case .invalidJSON: false
@ -94,7 +94,7 @@ private func canForwardToChat(_ cInfo: ChatInfo) -> Bool {
public func chatIconName(_ cInfo: ChatInfo) -> String {
switch cInfo {
case .direct: "person.crop.circle.fill"
case let .group(groupInfo):
case let .group(groupInfo, _):
switch groupInfo.businessChat?.chatType {
case .none: "person.2.circle.fill"
case .business: "briefcase.circle.fill"

View file

@ -62,7 +62,7 @@ public func createContactConnectedNtf(_ user: any UserLike, _ contact: Contact,
public func createMessageReceivedNtf(_ user: any UserLike, _ cInfo: ChatInfo, _ cItem: ChatItem, _ badgeCount: Int) -> UNMutableNotificationContent {
let previewMode = ntfPreviewModeGroupDefault.get()
var title: String
if case let .group(groupInfo) = cInfo, case let .groupRcv(groupMember) = cItem.chatDir {
if case let .group(groupInfo, _) = cInfo, case let .groupRcv(groupMember) = cItem.chatDir {
title = groupMsgNtfTitle(groupInfo, groupMember, hideContent: previewMode == .hidden)
} else {
title = previewMode == .hidden ? contactHidden : "\(cInfo.chatViewName):"

View file

@ -1485,7 +1485,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't enable" = "Не активирай";
/* No comment provided by engineer. */
/* alert action */
"Don't show again" = "Не показвай отново";
/* No comment provided by engineer. */
@ -1777,7 +1777,7 @@ chat item action */
/* alert title */
"Error receiving file" = "Грешка при получаване на файл";
/* No comment provided by engineer. */
/* alert title */
"Error removing member" = "Грешка при отстраняване на член";
/* No comment provided by engineer. */
@ -2747,6 +2747,7 @@ snd error text */
/* enabled status
group pref value
member criteria value
time to disappear */
"off" = "изключено";
@ -2759,7 +2760,8 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "предлага %1$@: %2$@";
/* alert button */
/* alert action
alert button */
"Ok" = "Ок";
/* No comment provided by engineer. */
@ -3776,9 +3778,6 @@ chat item action */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "Старата база данни не бе премахната по време на миграцията, тя може да бъде изтрита.";
/* No comment provided by engineer. */
"Your profile is stored on your device and only shared with your contacts." = "Профилът се споделя само с вашите контакти.";
/* No comment provided by engineer. */
"The second tick we missed! ✅" = "Втората отметка, която пропуснахме! ✅";
@ -4446,10 +4445,10 @@ chat item action */
"Your profile **%@** will be shared." = "Вашият профил **%@** ще бъде споделен.";
/* No comment provided by engineer. */
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Вашият профил се съхранява на вашето устройство и се споделя само с вашите контакти. SimpleX сървърите не могат да видят вашия профил.";
"Your profile is stored on your device and only shared with your contacts." = "Профилът се споделя само с вашите контакти.";
/* No comment provided by engineer. */
"Your profile, contacts and delivered messages are stored on your device." = "Вашият профил, контакти и доставени съобщения се съхраняват на вашето устройство.";
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Вашият профил се съхранява на вашето устройство и се споделя само с вашите контакти. SimpleX сървърите не могат да видят вашия профил.";
/* No comment provided by engineer. */
"Your random profile" = "Вашият автоматично генериран профил";

View file

@ -1127,7 +1127,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't enable" = "Nepovolovat";
/* No comment provided by engineer. */
/* alert action */
"Don't show again" = "Znovu neukazuj";
/* No comment provided by engineer. */
@ -1367,7 +1367,7 @@ swipe action */
/* alert title */
"Error receiving file" = "Chyba při příjmu souboru";
/* No comment provided by engineer. */
/* alert title */
"Error removing member" = "Chyba při odebrání člena";
/* No comment provided by engineer. */
@ -2145,6 +2145,7 @@ snd error text */
/* enabled status
group pref value
member criteria value
time to disappear */
"off" = "vypnuto";
@ -2157,7 +2158,8 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "nabídl %1$@: %2$@";
/* alert button */
/* alert action
alert button */
"Ok" = "Ok";
/* No comment provided by engineer. */
@ -2988,9 +2990,6 @@ chat item action */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "Stará databáze nebyla během přenášení odstraněna, lze ji smazat.";
/* No comment provided by engineer. */
"Your profile is stored on your device and only shared with your contacts." = "Profil je sdílen pouze s vašimi kontakty.";
/* No comment provided by engineer. */
"The second tick we missed! ✅" = "Druhé zaškrtnutí jsme přehlédli! ✅";
@ -3472,10 +3471,10 @@ chat item action */
"Your profile **%@** will be shared." = "Váš profil **%@** bude sdílen.";
/* No comment provided by engineer. */
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Váš profil je uložen ve vašem zařízení a sdílen pouze s vašimi kontakty. Servery SimpleX nevidí váš profil.";
"Your profile is stored on your device and only shared with your contacts." = "Profil je sdílen pouze s vašimi kontakty.";
/* No comment provided by engineer. */
"Your profile, contacts and delivered messages are stored on your device." = "Váš profil, kontakty a doručené zprávy jsou uloženy ve vašem zařízení.";
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Váš profil je uložen ve vašem zařízení a sdílen pouze s vašimi kontakty. Servery SimpleX nevidí váš profil.";
/* No comment provided by engineer. */
"Your random profile" = "Váš náhodný profil";

View file

@ -345,6 +345,12 @@ accept incoming call via notification
swipe action */
"Accept" = "Annehmen";
/* alert action */
"Accept as member" = "Als Mitglied übernehmen";
/* alert action */
"Accept as observer" = "Als Beobachter übernehmen";
/* No comment provided by engineer. */
"Accept conditions" = "Nutzungsbedingungen akzeptieren";
@ -358,6 +364,12 @@ swipe action */
swipe action */
"Accept incognito" = "Inkognito akzeptieren";
/* alert title */
"Accept member" = "Mitglied übernehmen";
/* rcv group event chat item */
"accepted %@" = "%@ übernommen";
/* call status */
"accepted call" = "Anruf angenommen";
@ -367,6 +379,9 @@ swipe action */
/* chat list item title */
"accepted invitation" = "Einladung angenommen";
/* rcv group event chat item */
"accepted you" = "hat Sie übernommen";
/* No comment provided by engineer. */
"Acknowledged" = "Bestätigt";
@ -463,6 +478,9 @@ swipe action */
/* chat item text */
"agreeing encryption…" = "Verschlüsselung zustimmen…";
/* member criteria value */
"all" = "alle";
/* No comment provided by engineer. */
"All" = "Alle";
@ -905,6 +923,9 @@ marked deleted chat item preview text */
/* No comment provided by engineer. */
"Can't message member" = "Mitglied kann nicht benachrichtigt werden";
/* No comment provided by engineer. */
"can't send messages" = "Es können keine Nachrichten gesendet werden";
/* alert action
alert button */
"Cancel" = "Abbrechen";
@ -1042,9 +1063,18 @@ set passcode view */
/* No comment provided by engineer. */
"Chat will be deleted for you - this cannot be undone!" = "Der Chat wird für Sie gelöscht. Dies kann nicht rückgängig gemacht werden!";
/* chat toolbar */
"Chat with admins" = "Chat mit Administratoren";
/* No comment provided by engineer. */
"Chat with member" = "Chat mit einem Mitglied";
/* No comment provided by engineer. */
"Chats" = "Chats";
/* No comment provided by engineer. */
"Chats with members" = "Chats mit Mitgliedern";
/* No comment provided by engineer. */
"Check messages every 20 min." = "Alle 20min Nachrichten überprüfen.";
@ -1333,9 +1363,15 @@ set passcode view */
/* No comment provided by engineer. */
"Contact already exists" = "Der Kontakt ist bereits vorhanden";
/* No comment provided by engineer. */
"contact deleted" = "Kontakt gelöscht";
/* No comment provided by engineer. */
"Contact deleted!" = "Kontakt gelöscht!";
/* No comment provided by engineer. */
"contact disabled" = "Kontakt deaktiviert";
/* No comment provided by engineer. */
"contact has e2e encryption" = "Kontakt nutzt E2E-Verschlüsselung";
@ -1354,6 +1390,9 @@ set passcode view */
/* No comment provided by engineer. */
"Contact name" = "Kontaktname";
/* No comment provided by engineer. */
"contact not ready" = "Kontakt nicht bereit";
/* No comment provided by engineer. */
"Contact preferences" = "Kontakt-Präferenzen";
@ -1602,6 +1641,9 @@ swipe action */
/* No comment provided by engineer. */
"Delete chat profile?" = "Chat-Profil löschen?";
/* alert title */
"Delete chat with member?" = "Chat mit dem Mitglied löschen?";
/* No comment provided by engineer. */
"Delete chat?" = "Chat löschen?";
@ -1872,7 +1914,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't miss important messages." = "Verpassen Sie keine wichtigen Nachrichten.";
/* No comment provided by engineer. */
/* alert action */
"Don't show again" = "Nicht nochmals anzeigen";
/* No comment provided by engineer. */
@ -2116,6 +2158,9 @@ chat item action */
/* No comment provided by engineer. */
"Error accepting contact request" = "Fehler beim Annehmen der Kontaktanfrage";
/* alert title */
"Error accepting member" = "Fehler beim Übernehmen des Mitglieds";
/* No comment provided by engineer. */
"Error adding member(s)" = "Fehler beim Hinzufügen von Mitgliedern";
@ -2173,6 +2218,9 @@ chat item action */
/* No comment provided by engineer. */
"Error deleting chat database" = "Fehler beim Löschen der Chat-Datenbank";
/* alert title */
"Error deleting chat with member" = "Fehler beim Löschen des Chats mit dem Mitglied";
/* No comment provided by engineer. */
"Error deleting chat!" = "Fehler beim Löschen des Chats!";
@ -2236,7 +2284,7 @@ chat item action */
/* alert title */
"Error registering for notifications" = "Fehler beim Registrieren für Benachrichtigungen";
/* No comment provided by engineer. */
/* alert title */
"Error removing member" = "Fehler beim Entfernen des Mitglieds";
/* alert title */
@ -2610,6 +2658,9 @@ snd error text */
/* No comment provided by engineer. */
"Group invitation is no longer valid, it was removed by sender." = "Die Gruppeneinladung ist nicht mehr gültig, da sie vom Absender entfernt wurde.";
/* No comment provided by engineer. */
"group is deleted" = "Gruppe wird gelöscht";
/* No comment provided by engineer. */
"Group link" = "Gruppen-Link";
@ -3138,9 +3189,15 @@ snd error text */
/* profile update event chat item */
"member %@ changed to %@" = "Der Mitgliedsname von %1$@ wurde auf %2$@ geändert";
/* No comment provided by engineer. */
"Member admission" = "Aufnahme von Mitgliedern";
/* rcv group event chat item */
"member connected" = "ist der Gruppe beigetreten";
/* No comment provided by engineer. */
"member has old version" = "Das Mitglied hat eine alte App-Version";
/* item status text */
"Member inactive" = "Mitglied inaktiv";
@ -3162,6 +3219,9 @@ snd error text */
/* No comment provided by engineer. */
"Member will be removed from group - this cannot be undone!" = "Das Mitglied wird aus der Gruppe entfernt. Dies kann nicht rückgängig gemacht werden!";
/* alert message */
"Member will join the group, accept member?" = "Ein Mitglied wird der Gruppe beitreten. Übernehmen?";
/* No comment provided by engineer. */
"Members can add message reactions." = "Gruppenmitglieder können eine Reaktion auf Nachrichten geben.";
@ -3432,6 +3492,9 @@ snd error text */
/* No comment provided by engineer. */
"New member role" = "Neue Mitgliedsrolle";
/* rcv group event chat item */
"New member wants to join the group." = "Ein neues Mitglied will der Gruppe beitreten.";
/* notification */
"new message" = "Neue Nachricht";
@ -3471,6 +3534,9 @@ snd error text */
/* No comment provided by engineer. */
"No chats in list %@" = "Keine Chats in der Liste %@";
/* No comment provided by engineer. */
"No chats with members" = "Keine Chats mit Mitgliedern";
/* No comment provided by engineer. */
"No contacts selected" = "Keine Kontakte ausgewählt";
@ -3555,6 +3621,9 @@ snd error text */
/* No comment provided by engineer. */
"Not compatible!" = "Nicht kompatibel!";
/* No comment provided by engineer. */
"not synchronized" = "Nicht synchronisiert";
/* No comment provided by engineer. */
"Notes" = "Anmerkungen";
@ -3587,6 +3656,7 @@ snd error text */
/* enabled status
group pref value
member criteria value
time to disappear */
"off" = "Aus";
@ -3599,7 +3669,8 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "angeboten %1$@: %2$@";
/* alert button */
/* alert action
alert button */
"Ok" = "Ok";
/* No comment provided by engineer. */
@ -3695,6 +3766,9 @@ time to disappear */
/* No comment provided by engineer. */
"Open group" = "Gruppe öffnen";
/* alert title */
"Open link?" = "Link öffnen?";
/* authentication reason */
"Open migration to another device" = "Migration auf ein anderes Gerät öffnen";
@ -3797,6 +3871,9 @@ time to disappear */
/* No comment provided by engineer. */
"pending approval" = "ausstehende Genehmigung";
/* No comment provided by engineer. */
"pending review" = "Ausstehende Überprüfung";
/* No comment provided by engineer. */
"Periodic" = "Periodisch";
@ -3866,6 +3943,9 @@ time to disappear */
/* token info */
"Please try to disable and re-enable notfications." = "Bitte versuchen Sie, die Benachrichtigungen zu deaktivieren und wieder zu aktivieren.";
/* snd group event chat item */
"Please wait for group moderators to review your request to join the group." = "Bitte warten Sie auf die Überprüfung Ihrer Anfrage durch die Gruppen-Moderatoren, um der Gruppe beitreten zu können.";
/* token info */
"Please wait for token activation to complete." = "Bitte warten Sie, bis die Token-Aktivierung abgeschlossen ist.";
@ -4146,6 +4226,9 @@ swipe action */
/* No comment provided by engineer. */
"Reject contact request" = "Kontaktanfrage ablehnen";
/* alert title */
"Reject member?" = "Mitglied ablehnen?";
/* No comment provided by engineer. */
"rejected" = "abgelehnt";
@ -4185,6 +4268,9 @@ swipe action */
/* profile update event chat item */
"removed contact address" = "Die Kontaktadresse wurde entfernt";
/* No comment provided by engineer. */
"removed from group" = "Von der Gruppe entfernt";
/* profile update event chat item */
"removed profile picture" = "Das Profil-Bild wurde entfernt";
@ -4233,6 +4319,9 @@ swipe action */
/* No comment provided by engineer. */
"Report reason?" = "Grund der Meldung?";
/* alert title */
"Report sent to moderators" = "Meldung wurde an die Moderatoren gesendet";
/* report reason */
"Report spam: only group moderators will see it." = "Spam melden: Nur Gruppenmoderatoren werden es sehen.";
@ -4248,6 +4337,9 @@ swipe action */
/* No comment provided by engineer. */
"Reports" = "Meldungen";
/* No comment provided by engineer. */
"request to join rejected" = "Beitrittsanfrage abgelehnt";
/* chat list item title */
"requested to connect" = "Zur Verbindung aufgefordert";
@ -4302,9 +4394,21 @@ swipe action */
/* chat item action */
"Reveal" = "Aufdecken";
/* No comment provided by engineer. */
"review" = "Überprüfung";
/* No comment provided by engineer. */
"Review conditions" = "Nutzungsbedingungen einsehen";
/* admission stage */
"Review members" = "Überprüfung der Mitglieder";
/* admission stage description */
"Review members before admitting (\"knocking\")." = "Überprüfung der Mitglieder vor der Aufnahme (\"Anklopfen\").";
/* No comment provided by engineer. */
"reviewed by admins" = "Von Administratoren überprüft";
/* No comment provided by engineer. */
"Revoke" = "Widerrufen";
@ -4333,6 +4437,9 @@ chat item action */
/* alert button */
"Save (and notify contacts)" = "Speichern (und Kontakte benachrichtigen)";
/* alert title */
"Save admission settings?" = "Speichern der Aufnahme-Einstellungen?";
/* alert button */
"Save and notify contact" = "Speichern und Kontakt benachrichtigen";
@ -4669,6 +4776,9 @@ chat item action */
/* No comment provided by engineer. */
"Set it instead of system authentication." = "Anstelle der System-Authentifizierung festlegen.";
/* No comment provided by engineer. */
"Set member admission" = "Aufnahme von Mitgliedern festlegen";
/* No comment provided by engineer. */
"Set message expiration in chats." = "Verfallsdatum von Nachrichten in Chats festlegen.";
@ -5100,9 +5210,6 @@ report reason */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "Die alte Datenbank wurde während der Migration nicht entfernt. Sie kann gelöscht werden.";
/* No comment provided by engineer. */
"Your profile is stored on your device and only shared with your contacts." = "Das Profil wird nur mit Ihren Kontakten geteilt.";
/* No comment provided by engineer. */
"The same conditions will apply to operator **%@**." = "Dieselben Nutzungsbedingungen gelten auch für den Betreiber **%@**.";
@ -5712,6 +5819,9 @@ report reason */
/* No comment provided by engineer. */
"You accepted connection" = "Sie haben die Verbindung akzeptiert";
/* snd group event chat item */
"you accepted this member" = "Sie haben dieses Mitglied übernommen";
/* No comment provided by engineer. */
"You allow" = "Sie erlauben";
@ -5823,6 +5933,9 @@ report reason */
/* alert message */
"You can view invitation link again in connection details." = "Den Einladungslink können Sie in den Details der Verbindung nochmals sehen.";
/* alert message */
"You can view your reports in Chat with admins." = "Sie können Ihre Meldungen im Chat mit den Administratoren sehen.";
/* No comment provided by engineer. */
"You can't send messages!" = "Sie können keine Nachrichten versenden!";
@ -5991,15 +6104,15 @@ report reason */
/* No comment provided by engineer. */
"Your profile **%@** will be shared." = "Ihr Profil **%@** wird geteilt.";
/* No comment provided by engineer. */
"Your profile is stored on your device and only shared with your contacts." = "Ihr Profil wird auf Ihrem Gerät gespeichert und nur mit Ihren Kontakten geteilt.";
/* No comment provided by engineer. */
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Ihr Profil wird auf Ihrem Gerät gespeichert und nur mit Ihren Kontakten geteilt. SimpleX-Server können Ihr Profil nicht einsehen.";
/* alert message */
"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Ihr Profil wurde geändert. Wenn Sie es speichern, wird das aktualisierte Profil an alle Ihre Kontakte gesendet.";
/* No comment provided by engineer. */
"Your profile, contacts and delivered messages are stored on your device." = "Ihr Profil, Ihre Kontakte und zugestellten Nachrichten werden auf Ihrem Gerät gespeichert.";
/* No comment provided by engineer. */
"Your random profile" = "Ihr Zufallsprofil";

View file

@ -1872,7 +1872,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't miss important messages." = "No pierdas los mensajes importantes.";
/* No comment provided by engineer. */
/* alert action */
"Don't show again" = "No volver a mostrar";
/* No comment provided by engineer. */
@ -2236,7 +2236,7 @@ chat item action */
/* alert title */
"Error registering for notifications" = "Error al registrarse para notificaciones";
/* No comment provided by engineer. */
/* alert title */
"Error removing member" = "Error al expulsar miembro";
/* alert title */
@ -3587,6 +3587,7 @@ snd error text */
/* enabled status
group pref value
member criteria value
time to disappear */
"off" = "desactivado";
@ -3599,7 +3600,8 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "ofrecido %1$@: %2$@";
/* alert button */
/* alert action
alert button */
"Ok" = "Ok";
/* No comment provided by engineer. */
@ -5100,9 +5102,6 @@ report reason */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "La base de datos antigua no se eliminó durante la migración, puede eliminarse.";
/* No comment provided by engineer. */
"Your profile is stored on your device and only shared with your contacts." = "El perfil sólo se comparte con tus contactos.";
/* No comment provided by engineer. */
"The same conditions will apply to operator **%@**." = "Las mismas condiciones se aplicarán al operador **%@**.";
@ -5991,15 +5990,15 @@ report reason */
/* No comment provided by engineer. */
"Your profile **%@** will be shared." = "El perfil **%@** será compartido.";
/* No comment provided by engineer. */
"Your profile is stored on your device and only shared with your contacts." = "El perfil sólo se comparte con tus contactos.";
/* No comment provided by engineer. */
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Tu perfil es almacenado en tu dispositivo y solamente se comparte con tus contactos. Los servidores SimpleX no pueden ver tu perfil.";
/* alert message */
"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Tu perfil ha sido modificado. Si lo guardas la actualización será enviada a todos tus contactos.";
/* No comment provided by engineer. */
"Your profile, contacts and delivered messages are stored on your device." = "Tu perfil, contactos y mensajes se almacenan en tu dispositivo.";
/* No comment provided by engineer. */
"Your random profile" = "Tu perfil aleatorio";

View file

@ -1073,7 +1073,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't enable" = "Älä salli";
/* No comment provided by engineer. */
/* alert action */
"Don't show again" = "Älä näytä uudelleen";
/* No comment provided by engineer. */
@ -1307,7 +1307,7 @@ swipe action */
/* alert title */
"Error receiving file" = "Virhe tiedoston vastaanottamisessa";
/* No comment provided by engineer. */
/* alert title */
"Error removing member" = "Virhe poistettaessa jäsentä";
/* No comment provided by engineer. */
@ -2079,6 +2079,7 @@ snd error text */
/* enabled status
group pref value
member criteria value
time to disappear */
"off" = "pois";
@ -2091,7 +2092,8 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "tarjottu %1$@: %2$@";
/* alert button */
/* alert action
alert button */
"Ok" = "Ok";
/* No comment provided by engineer. */
@ -2910,9 +2912,6 @@ chat item action */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "Vanhaa tietokantaa ei poistettu siirron aikana, se voidaan kuitenkin poistaa.";
/* No comment provided by engineer. */
"Your profile is stored on your device and only shared with your contacts." = "Profiili jaetaan vain kontaktiesi kanssa.";
/* No comment provided by engineer. */
"The second tick we missed! ✅" = "Toinen kuittaus, joka uupui! ✅";
@ -3391,10 +3390,10 @@ chat item action */
"Your profile **%@** will be shared." = "Profiilisi **%@** jaetaan.";
/* No comment provided by engineer. */
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Profiilisi tallennetaan laitteeseesi ja jaetaan vain yhteystietojesi kanssa. SimpleX-palvelimet eivät näe profiiliasi.";
"Your profile is stored on your device and only shared with your contacts." = "Profiili jaetaan vain kontaktiesi kanssa.";
/* No comment provided by engineer. */
"Your profile, contacts and delivered messages are stored on your device." = "Profiilisi, kontaktisi ja toimitetut viestit tallennetaan laitteellesi.";
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Profiilisi tallennetaan laitteeseesi ja jaetaan vain yhteystietojesi kanssa. SimpleX-palvelimet eivät näe profiiliasi.";
/* No comment provided by engineer. */
"Your random profile" = "Satunnainen profiilisi";

View file

@ -1863,7 +1863,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't miss important messages." = "Ne manquez pas les messages importants.";
/* No comment provided by engineer. */
/* alert action */
"Don't show again" = "Ne plus afficher";
/* No comment provided by engineer. */
@ -2227,7 +2227,7 @@ chat item action */
/* alert title */
"Error registering for notifications" = "Erreur lors de l'inscription aux notifications";
/* No comment provided by engineer. */
/* alert title */
"Error removing member" = "Erreur lors de la suppression d'un membre";
/* alert title */
@ -3479,6 +3479,7 @@ snd error text */
/* enabled status
group pref value
member criteria value
time to disappear */
"off" = "off";
@ -3491,7 +3492,8 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "propose %1$@: %2$@";
/* alert button */
/* alert action
alert button */
"Ok" = "Ok";
/* No comment provided by engineer. */
@ -4883,9 +4885,6 @@ chat item action */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "L'ancienne base de données n'a pas été supprimée lors de la migration, elle peut être supprimée.";
/* No comment provided by engineer. */
"Your profile is stored on your device and only shared with your contacts." = "Le profil n'est partagé qu'avec vos contacts.";
/* No comment provided by engineer. */
"The same conditions will apply to operator **%@**." = "Les mêmes conditions s'appliquent à l'opérateur **%@**.";
@ -5741,15 +5740,15 @@ chat item action */
/* No comment provided by engineer. */
"Your profile **%@** will be shared." = "Votre profil **%@** sera partagé.";
/* No comment provided by engineer. */
"Your profile is stored on your device and only shared with your contacts." = "Le profil n'est partagé qu'avec vos contacts.";
/* No comment provided by engineer. */
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Votre profil est stocké sur votre appareil et est seulement partagé avec vos contacts. Les serveurs SimpleX ne peuvent pas voir votre profil.";
/* alert message */
"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Votre profil a été modifié. Si vous l'enregistrez, le profil mis à jour sera envoyé à tous vos contacts.";
/* No comment provided by engineer. */
"Your profile, contacts and delivered messages are stored on your device." = "Votre profil, vos contacts et les messages reçus sont stockés sur votre appareil.";
/* No comment provided by engineer. */
"Your random profile" = "Votre profil aléatoire";

View file

@ -345,6 +345,12 @@ accept incoming call via notification
swipe action */
"Accept" = "Elfogadás";
/* alert action */
"Accept as member" = "Befogadás tagként";
/* alert action */
"Accept as observer" = "Befogadás megfigyelőként";
/* No comment provided by engineer. */
"Accept conditions" = "Feltételek elfogadása";
@ -358,6 +364,12 @@ swipe action */
swipe action */
"Accept incognito" = "Elfogadás inkognitóban";
/* alert title */
"Accept member" = "Tag befogadása";
/* rcv group event chat item */
"accepted %@" = "befogadta őt: %@";
/* call status */
"accepted call" = "fogadott hívás";
@ -367,6 +379,9 @@ swipe action */
/* chat list item title */
"accepted invitation" = "elfogadott meghívó";
/* rcv group event chat item */
"accepted you" = "befogadta Önt";
/* No comment provided by engineer. */
"Acknowledged" = "Visszaigazolt";
@ -463,6 +478,9 @@ swipe action */
/* chat item text */
"agreeing encryption…" = "titkosítás elfogadása…";
/* member criteria value */
"all" = "összes";
/* No comment provided by engineer. */
"All" = "Összes";
@ -905,6 +923,9 @@ marked deleted chat item preview text */
/* No comment provided by engineer. */
"Can't message member" = "Nem lehet üzenetet küldeni a tagnak";
/* No comment provided by engineer. */
"can't send messages" = "nem lehet üzeneteket küldeni";
/* alert action
alert button */
"Cancel" = "Mégse";
@ -1042,9 +1063,18 @@ set passcode view */
/* No comment provided by engineer. */
"Chat will be deleted for you - this cannot be undone!" = "A csevegés törölve lesz az Ön számára ez a művelet nem vonható vissza!";
/* chat toolbar */
"Chat with admins" = "Csevegés az adminisztrátorokkal";
/* No comment provided by engineer. */
"Chat with member" = "Csevegés a taggal";
/* No comment provided by engineer. */
"Chats" = "Csevegések";
/* No comment provided by engineer. */
"Chats with members" = "Csevegés a tagokkal";
/* No comment provided by engineer. */
"Check messages every 20 min." = "Üzenetek ellenőrzése 20 percenként.";
@ -1333,9 +1363,15 @@ set passcode view */
/* No comment provided by engineer. */
"Contact already exists" = "A partner már létezik";
/* No comment provided by engineer. */
"contact deleted" = "partner törölve";
/* No comment provided by engineer. */
"Contact deleted!" = "Partner törölve!";
/* No comment provided by engineer. */
"contact disabled" = "partner letiltva";
/* No comment provided by engineer. */
"contact has e2e encryption" = "a partner e2e titkosítással rendelkezik";
@ -1354,6 +1390,9 @@ set passcode view */
/* No comment provided by engineer. */
"Contact name" = "Csak név";
/* No comment provided by engineer. */
"contact not ready" = "a kapcsolat nem áll készen";
/* No comment provided by engineer. */
"Contact preferences" = "Partnerbeállítások";
@ -1505,7 +1544,7 @@ set passcode view */
"Database ID: %d" = "Adatbázis-azonosító: %d";
/* No comment provided by engineer. */
"Database IDs and Transport isolation option." = "Adatbázis-azonosítók és átvitel-izolációs beállítások.";
"Database IDs and Transport isolation option." = "Adatbázis-azonosítók és átvitelelkülönítési beállítások.";
/* No comment provided by engineer. */
"Database is encrypted using a random passphrase, you can change it." = "Az adatbázis egy véletlenszerű jelmondattal van titkosítva, amelyet szabadon módosíthat.";
@ -1550,7 +1589,7 @@ set passcode view */
"Decentralized" = "Decentralizált";
/* message decrypt error item */
"Decryption error" = "Titkosítás visszafejtési hiba";
"Decryption error" = "Titkosításvisszafejtési hiba";
/* No comment provided by engineer. */
"decryption errors" = "visszafejtési hibák";
@ -1602,6 +1641,9 @@ swipe action */
/* No comment provided by engineer. */
"Delete chat profile?" = "Törli a csevegési profilt?";
/* alert title */
"Delete chat with member?" = "Törli a taggal való csevegést?";
/* No comment provided by engineer. */
"Delete chat?" = "Törli a csevegést?";
@ -1777,7 +1819,7 @@ swipe action */
"different migration in the app/database: %@ / %@" = "különböző átköltöztetés az alkalmazásban/adatbázisban: %@ / %@";
/* No comment provided by engineer. */
"Different names, avatars and transport isolation." = "Különböző nevek, profilképek és átvitel-izoláció.";
"Different names, avatars and transport isolation." = "Különböző nevek, profilképek és átvitelizoláció.";
/* connection level description */
"direct" = "közvetlen";
@ -1872,7 +1914,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't miss important messages." = "Ne maradjon le a fontos üzenetekről.";
/* No comment provided by engineer. */
/* alert action */
"Don't show again" = "Ne mutasd újra";
/* No comment provided by engineer. */
@ -2116,6 +2158,9 @@ chat item action */
/* No comment provided by engineer. */
"Error accepting contact request" = "Hiba történt a meghívási kérés elfogadásakor";
/* alert title */
"Error accepting member" = "Hiba a tag befogadásakor";
/* No comment provided by engineer. */
"Error adding member(s)" = "Hiba történt a tag(ok) hozzáadásakor";
@ -2173,6 +2218,9 @@ chat item action */
/* No comment provided by engineer. */
"Error deleting chat database" = "Hiba történt a csevegési adatbázis törlésekor";
/* alert title */
"Error deleting chat with member" = "Hiba a taggal való csevegés törlésekor";
/* No comment provided by engineer. */
"Error deleting chat!" = "Hiba történt a csevegés törlésekor!";
@ -2189,7 +2237,7 @@ chat item action */
"Error deleting token" = "Hiba történt a token törlésekor";
/* No comment provided by engineer. */
"Error deleting user profile" = "Hiba történt a felhasználó-profil törlésekor";
"Error deleting user profile" = "Hiba történt a felhasználói profil törlésekor";
/* No comment provided by engineer. */
"Error downloading the archive" = "Hiba történt az archívum letöltésekor";
@ -2236,7 +2284,7 @@ chat item action */
/* alert title */
"Error registering for notifications" = "Hiba történt az értesítések regisztrálásakor";
/* No comment provided by engineer. */
/* alert title */
"Error removing member" = "Hiba történt a tag eltávolításakor";
/* alert title */
@ -2610,6 +2658,9 @@ snd error text */
/* No comment provided by engineer. */
"Group invitation is no longer valid, it was removed by sender." = "A csoportmeghívó már nem érvényes, a küldője eltávolította.";
/* No comment provided by engineer. */
"group is deleted" = "csoport törölve";
/* No comment provided by engineer. */
"Group link" = "Csoporthivatkozás";
@ -2956,10 +3007,10 @@ snd error text */
"It allows having many anonymous connections without any shared data between them in a single chat profile." = "Lehetővé teszi, hogy egyetlen csevegési profilon belül több névtelen kapcsolat legyen, anélkül, hogy megosztott adatok lennének közöttük.";
/* No comment provided by engineer. */
"It can happen when you or your connection used the old database backup." = "Ez akkor fordulhat elő, ha Ön vagy a partnere régi adatbázis biztonsági mentést használt.";
"It can happen when you or your connection used the old database backup." = "Ez akkor fordulhat elő, ha Ön vagy a partnere egy régi adatbázis biztonsági mentését használta.";
/* No comment provided by engineer. */
"It can happen when:\n1. The messages expired in the sending client after 2 days or on the server after 30 days.\n2. Message decryption failed, because you or your contact used old database backup.\n3. The connection was compromised." = "Ez akkor fordulhat elő, ha:\n1. Az üzenetek 2 nap után, vagy a kiszolgálón 30 nap után lejártak.\n2. Nem sikerült az üzenetet visszafejteni, mert Ön, vagy a partnere régebbi adatbázis biztonsági mentést használt.\n3. A kapcsolat sérült.";
"It can happen when:\n1. The messages expired in the sending client after 2 days or on the server after 30 days.\n2. Message decryption failed, because you or your contact used old database backup.\n3. The connection was compromised." = "Ez akkor fordulhat elő, ha:\n1. Az üzenetek 2 nap után, vagy a kiszolgálón 30 nap után lejártak.\n2. Nem sikerült az üzenetet visszafejteni, mert Ön, vagy a partnere egy régi adatbázis biztonsági mentését használta.\n3. A kapcsolat sérült.";
/* No comment provided by engineer. */
"It protects your IP address and connections." = "Védi az IP-címét és a kapcsolatait.";
@ -3138,9 +3189,15 @@ snd error text */
/* profile update event chat item */
"member %@ changed to %@" = "%1$@ a következőre módosította a nevét: %2$@";
/* No comment provided by engineer. */
"Member admission" = "Tagbefogadás";
/* rcv group event chat item */
"member connected" = "kapcsolódott";
/* No comment provided by engineer. */
"member has old version" = "a tag régi verziót használ";
/* item status text */
"Member inactive" = "Inaktív tag";
@ -3162,6 +3219,9 @@ snd error text */
/* No comment provided by engineer. */
"Member will be removed from group - this cannot be undone!" = "A tag el lesz távolítva a csoportból ez a művelet nem vonható vissza!";
/* alert message */
"Member will join the group, accept member?" = "A tag csatlakozni akar a csoporthoz, befogadja a tagot?";
/* No comment provided by engineer. */
"Members can add message reactions." = "A tagok reakciókat adhatnak hozzá az üzenetekhez.";
@ -3274,10 +3334,10 @@ snd error text */
"Messages were deleted after you selected them." = "Az üzeneteket törölték miután kijelölte őket.";
/* No comment provided by engineer. */
"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Az üzenetek, a fájlok és a hívások **végpontok közötti titkosítással**, sérülés utáni titkosságvédelemmel és -helyreállítással, továbbá letagadhatósággal vannak védve.";
"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Az üzenetek, a fájlok és a hívások **végpontok közötti titkosítással**, kompromittálás előtti és utáni titkosságvédelemmel, illetve letagadhatósággal vannak védve.";
/* No comment provided by engineer. */
"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Az üzenetek, a fájlok és a hívások **végpontok közötti kvantumbiztos titkosítással**, sérülés utáni titkosságvédelemmel és -helyreállítással, továbbá letagadhatósággal vannak védve.";
"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Az üzenetek, a fájlok és a hívások **végpontok közötti kvantumbiztos titkosítással**, kompromittálás előtti és utáni titkosságvédelemmel, illetve letagadhatósággal vannak védve.";
/* No comment provided by engineer. */
"Migrate device" = "Eszköz átköltöztetése";
@ -3432,6 +3492,9 @@ snd error text */
/* No comment provided by engineer. */
"New member role" = "Új tag szerepköre";
/* rcv group event chat item */
"New member wants to join the group." = "Új tag szeretne csatlakozni a csoporthoz.";
/* notification */
"new message" = "új üzenet";
@ -3471,6 +3534,9 @@ snd error text */
/* No comment provided by engineer. */
"No chats in list %@" = "Nincsenek csevegések a(z) %@ nevű listában";
/* No comment provided by engineer. */
"No chats with members" = "Nincsenek csevegések a tagokkal";
/* No comment provided by engineer. */
"No contacts selected" = "Nincs partner kijelölve";
@ -3550,11 +3616,14 @@ snd error text */
"No unread chats" = "Nincsenek olvasatlan csevegések";
/* No comment provided by engineer. */
"No user identifiers." = "Nincsenek felhasználó-azonosítók.";
"No user identifiers." = "Nincsenek felhasználói azonosítók.";
/* No comment provided by engineer. */
"Not compatible!" = "Nem kompatibilis!";
/* No comment provided by engineer. */
"not synchronized" = "nincs szinkronizálva";
/* No comment provided by engineer. */
"Notes" = "Jegyzetek";
@ -3587,6 +3656,7 @@ snd error text */
/* enabled status
group pref value
member criteria value
time to disappear */
"off" = "kikapcsolva";
@ -3599,7 +3669,8 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "ajánlotta: %1$@, ekkor: %2$@";
/* alert button */
/* alert action
alert button */
"Ok" = "Rendben";
/* No comment provided by engineer. */
@ -3695,6 +3766,9 @@ time to disappear */
/* No comment provided by engineer. */
"Open group" = "Csoport megnyitása";
/* alert title */
"Open link?" = "Megnyitja a hivatkozást?";
/* authentication reason */
"Open migration to another device" = "Átköltöztetés indítása egy másik eszközre";
@ -3797,6 +3871,9 @@ time to disappear */
/* No comment provided by engineer. */
"pending approval" = "jóváhagyásra vár";
/* No comment provided by engineer. */
"pending review" = "függőben lévő áttekintés";
/* No comment provided by engineer. */
"Periodic" = "Időszakos";
@ -3866,6 +3943,9 @@ time to disappear */
/* token info */
"Please try to disable and re-enable notfications." = "Próbálja meg letiltani és újra engedélyezni az értesítéseket.";
/* snd group event chat item */
"Please wait for group moderators to review your request to join the group." = "Várja meg, amíg a csoport moderátorai áttekintik a csoporthoz való csatlakozási kérelmét.";
/* token info */
"Please wait for token activation to complete." = "Várjon, amíg a token aktiválása befejeződik.";
@ -4146,6 +4226,9 @@ swipe action */
/* No comment provided by engineer. */
"Reject contact request" = "Meghívási kérés elutasítása";
/* alert title */
"Reject member?" = "Elutasítja a tagot?";
/* No comment provided by engineer. */
"rejected" = "elutasítva";
@ -4153,10 +4236,10 @@ swipe action */
"rejected call" = "elutasított hívás";
/* No comment provided by engineer. */
"Relay server is only used if necessary. Another party can observe your IP address." = "A továbbítókiszolgáló csak szükség esetén lesz használva. Egy másik fél megfigyelheti az IP-címet.";
"Relay server is only used if necessary. Another party can observe your IP address." = "A továbbítókiszolgáló csak szükség esetén lesz használva. Egy másik fél megfigyelheti az IP-címét.";
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "A továbbítókiszolgáló megvédi az Ön IP-címét, de megfigyelheti a hívás időtartamát.";
"Relay server protects your IP address, but it can observe the duration of the call." = "A továbbítókiszolgáló megvédi az IP-címét, de megfigyelheti a hívás időtartamát.";
/* No comment provided by engineer. */
"Remove" = "Eltávolítás";
@ -4185,6 +4268,9 @@ swipe action */
/* profile update event chat item */
"removed contact address" = "eltávolította a kapcsolattartási címet";
/* No comment provided by engineer. */
"removed from group" = "eltávolítva a csoportból";
/* profile update event chat item */
"removed profile picture" = "eltávolította a profilképét";
@ -4233,6 +4319,9 @@ swipe action */
/* No comment provided by engineer. */
"Report reason?" = "Jelentés indoklása?";
/* alert title */
"Report sent to moderators" = "A jelentés el lett küldve a moderátoroknak";
/* report reason */
"Report spam: only group moderators will see it." = "Kéretlen tartalom jelentése: csak a csoport moderátorai látják.";
@ -4248,6 +4337,9 @@ swipe action */
/* No comment provided by engineer. */
"Reports" = "Jelentések";
/* No comment provided by engineer. */
"request to join rejected" = "csatlakozási kérelem elutasítva";
/* chat list item title */
"requested to connect" = "Függőben lévő meghívási kérelem";
@ -4302,9 +4394,21 @@ swipe action */
/* chat item action */
"Reveal" = "Felfedés";
/* No comment provided by engineer. */
"review" = "áttekintés";
/* No comment provided by engineer. */
"Review conditions" = "Feltételek felülvizsgálata";
/* admission stage */
"Review members" = "Tagok áttekintése";
/* admission stage description */
"Review members before admitting (\"knocking\")." = "Tagok áttekintése a befogadás előtt (kopogtatás).";
/* No comment provided by engineer. */
"reviewed by admins" = "áttekintve a moderátorok által";
/* No comment provided by engineer. */
"Revoke" = "Visszavonás";
@ -4333,6 +4437,9 @@ chat item action */
/* alert button */
"Save (and notify contacts)" = "Mentés (és a partnerek értesítése)";
/* alert title */
"Save admission settings?" = "Elmenti a befogadási beállításokat?";
/* alert button */
"Save and notify contact" = "Mentés és a partner értesítése";
@ -4669,6 +4776,9 @@ chat item action */
/* No comment provided by engineer. */
"Set it instead of system authentication." = "Beállítás a rendszer-hitelesítés helyett.";
/* No comment provided by engineer. */
"Set member admission" = "Tagbefogadás beállítása";
/* No comment provided by engineer. */
"Set message expiration in chats." = "Üzenetek eltűnési idejének módosítása a csevegésekben.";
@ -4830,7 +4940,7 @@ chat item action */
"SimpleX one-time invitation" = "Egyszer használható SimpleX-meghívó";
/* No comment provided by engineer. */
"SimpleX protocols reviewed by Trail of Bits." = "A SimpleX Chat biztonsága a Trail of Bits által lett felülvizsgálva.";
"SimpleX protocols reviewed by Trail of Bits." = "A SimpleX-protokollokat a Trail of Bits auditálta.";
/* No comment provided by engineer. */
"Simplified incognito mode" = "Egyszerűsített inkognitómód";
@ -5100,9 +5210,6 @@ report reason */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "A régi adatbázis nem lett eltávolítva az átköltöztetéskor, ezért törölhető.";
/* No comment provided by engineer. */
"Your profile is stored on your device and only shared with your contacts." = "A profilja csak a partnereivel van megosztva.";
/* No comment provided by engineer. */
"The same conditions will apply to operator **%@**." = "Ugyanezek a feltételek lesznek elfogadva a következő üzemeltető számára is: **%@**.";
@ -5263,7 +5370,7 @@ report reason */
"Total" = "Összes kapcsolat";
/* No comment provided by engineer. */
"Transport isolation" = "Átvitel-izoláció";
"Transport isolation" = "Átvitelelkülönítés";
/* No comment provided by engineer. */
"Transport sessions" = "Munkamenetek átvitele";
@ -5458,7 +5565,7 @@ report reason */
"Use only local notifications?" = "Csak helyi értesítések használata?";
/* No comment provided by engineer. */
"Use private routing with unknown servers when IP address is not protected." = "Használjon privát útválasztást ismeretlen kiszolgálókkal, ha az IP-cím nem védett.";
"Use private routing with unknown servers when IP address is not protected." = "Használjon privát útválasztást az ismeretlen kiszolgálókkal, ha az IP-cím nem védett.";
/* No comment provided by engineer. */
"Use private routing with unknown servers." = "Használjon privát útválasztást ismeretlen kiszolgálókkal.";
@ -5680,10 +5787,10 @@ report reason */
"With reduced battery usage." = "Csökkentett akkumulátor-használattal.";
/* No comment provided by engineer. */
"Without Tor or VPN, your IP address will be visible to file servers." = "Tor vagy VPN nélkül az Ön IP-címe látható lesz a fájlkiszolgálók számára.";
"Without Tor or VPN, your IP address will be visible to file servers." = "Tor vagy VPN nélkül az IP-címe láthatóvá válik a fájlkiszolgálók számára.";
/* alert message */
"Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Tor vagy VPN nélkül az Ön IP-címe látható lesz a következő XFTP-továbbítókiszolgálók számára: %@.";
"Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Tor vagy VPN nélkül az IP-címe láthatóvá válik a következő XFTP-továbbítókiszolgálók számára: %@.";
/* No comment provided by engineer. */
"Wrong database passphrase" = "Érvénytelen adatbázis-jelmondat";
@ -5712,6 +5819,9 @@ report reason */
/* No comment provided by engineer. */
"You accepted connection" = "Kapcsolat létrehozása";
/* snd group event chat item */
"you accepted this member" = "Ön befogadta ezt a tagot";
/* No comment provided by engineer. */
"You allow" = "Ön engedélyezi";
@ -5823,6 +5933,9 @@ report reason */
/* alert message */
"You can view invitation link again in connection details." = "A meghívási hivatkozást újra megtekintheti a kapcsolat részleteinél.";
/* alert message */
"You can view your reports in Chat with admins." = "A jelentéseket megtekintheti a „Csevegés az adminisztrátorokkal” menüben.";
/* No comment provided by engineer. */
"You can't send messages!" = "Nem lehet üzeneteket küldeni!";
@ -5991,15 +6104,15 @@ report reason */
/* No comment provided by engineer. */
"Your profile **%@** will be shared." = "A(z) **%@** nevű profilja meg lesz osztva.";
/* No comment provided by engineer. */
"Your profile is stored on your device and only shared with your contacts." = "A profilja az eszközén van tárolva és csak a partnereivel van megosztva.";
/* No comment provided by engineer. */
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "A profilja az eszközén van tárolva és csak a partnereivel van megosztva. A SimpleX-kiszolgálók nem láthatják a profilját.";
/* alert message */
"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "A profilja módosult. Ha elmenti, a profilfrissítés el lesz küldve a partnerei számára.";
/* No comment provided by engineer. */
"Your profile, contacts and delivered messages are stored on your device." = "A profilja, a partnerei és az elküldött üzenetei a saját eszközén vannak tárolva.";
/* No comment provided by engineer. */
"Your random profile" = "Véletlenszerű profil";

View file

@ -345,6 +345,12 @@ accept incoming call via notification
swipe action */
"Accept" = "Accetta";
/* alert action */
"Accept as member" = "Accetta come membro";
/* alert action */
"Accept as observer" = "Accetta come osservatore";
/* No comment provided by engineer. */
"Accept conditions" = "Accetta le condizioni";
@ -358,6 +364,12 @@ swipe action */
swipe action */
"Accept incognito" = "Accetta in incognito";
/* alert title */
"Accept member" = "Accetta membro";
/* rcv group event chat item */
"accepted %@" = "%@ accettato";
/* call status */
"accepted call" = "chiamata accettata";
@ -367,6 +379,9 @@ swipe action */
/* chat list item title */
"accepted invitation" = "invito accettato";
/* rcv group event chat item */
"accepted you" = "ti ha accettato/a";
/* No comment provided by engineer. */
"Acknowledged" = "Riconosciuto";
@ -463,6 +478,9 @@ swipe action */
/* chat item text */
"agreeing encryption…" = "concordando la crittografia…";
/* member criteria value */
"all" = "tutti";
/* No comment provided by engineer. */
"All" = "Tutte";
@ -905,6 +923,9 @@ marked deleted chat item preview text */
/* No comment provided by engineer. */
"Can't message member" = "Impossibile inviare un messaggio al membro";
/* No comment provided by engineer. */
"can't send messages" = "impossibile inviare messaggi";
/* alert action
alert button */
"Cancel" = "Annulla";
@ -1042,9 +1063,18 @@ set passcode view */
/* No comment provided by engineer. */
"Chat will be deleted for you - this cannot be undone!" = "La chat verrà eliminata solo per te, non è reversibile!";
/* chat toolbar */
"Chat with admins" = "Chat con amministratori";
/* No comment provided by engineer. */
"Chat with member" = "Chatta con il membro";
/* No comment provided by engineer. */
"Chats" = "Chat";
/* No comment provided by engineer. */
"Chats with members" = "Chat con membri";
/* No comment provided by engineer. */
"Check messages every 20 min." = "Controlla i messaggi ogni 20 min.";
@ -1333,9 +1363,15 @@ set passcode view */
/* No comment provided by engineer. */
"Contact already exists" = "Il contatto esiste già";
/* No comment provided by engineer. */
"contact deleted" = "contatto eliminato";
/* No comment provided by engineer. */
"Contact deleted!" = "Contatto eliminato!";
/* No comment provided by engineer. */
"contact disabled" = "contatto disattivato";
/* No comment provided by engineer. */
"contact has e2e encryption" = "il contatto ha la crittografia e2e";
@ -1354,6 +1390,9 @@ set passcode view */
/* No comment provided by engineer. */
"Contact name" = "Nome del contatto";
/* No comment provided by engineer. */
"contact not ready" = "contatto non pronto";
/* No comment provided by engineer. */
"Contact preferences" = "Preferenze del contatto";
@ -1602,6 +1641,9 @@ swipe action */
/* No comment provided by engineer. */
"Delete chat profile?" = "Eliminare il profilo di chat?";
/* alert title */
"Delete chat with member?" = "Eliminare la chat con il membro?";
/* No comment provided by engineer. */
"Delete chat?" = "Eliminare la chat?";
@ -1872,7 +1914,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't miss important messages." = "Non perdere messaggi importanti.";
/* No comment provided by engineer. */
/* alert action */
"Don't show again" = "Non mostrare più";
/* No comment provided by engineer. */
@ -2116,6 +2158,9 @@ chat item action */
/* No comment provided by engineer. */
"Error accepting contact request" = "Errore nell'accettazione della richiesta di contatto";
/* alert title */
"Error accepting member" = "Errore di accettazione del membro";
/* No comment provided by engineer. */
"Error adding member(s)" = "Errore di aggiunta membro/i";
@ -2173,6 +2218,9 @@ chat item action */
/* No comment provided by engineer. */
"Error deleting chat database" = "Errore nell'eliminazione del database della chat";
/* alert title */
"Error deleting chat with member" = "Errore di eliminazione della chat con il membro";
/* No comment provided by engineer. */
"Error deleting chat!" = "Errore nell'eliminazione della chat!";
@ -2236,7 +2284,7 @@ chat item action */
/* alert title */
"Error registering for notifications" = "Errore di registrazione per le notifiche";
/* No comment provided by engineer. */
/* alert title */
"Error removing member" = "Errore nella rimozione del membro";
/* alert title */
@ -2610,6 +2658,9 @@ snd error text */
/* No comment provided by engineer. */
"Group invitation is no longer valid, it was removed by sender." = "L'invito al gruppo non è più valido, è stato rimosso dal mittente.";
/* No comment provided by engineer. */
"group is deleted" = "il gruppo è eliminato";
/* No comment provided by engineer. */
"Group link" = "Link del gruppo";
@ -3138,9 +3189,15 @@ snd error text */
/* profile update event chat item */
"member %@ changed to %@" = "il membro %1$@ è diventato %2$@";
/* No comment provided by engineer. */
"Member admission" = "Ammissione del membro";
/* rcv group event chat item */
"member connected" = "si è connesso/a";
/* No comment provided by engineer. */
"member has old version" = "il membro ha una versione vecchia";
/* item status text */
"Member inactive" = "Membro inattivo";
@ -3162,6 +3219,9 @@ snd error text */
/* No comment provided by engineer. */
"Member will be removed from group - this cannot be undone!" = "Il membro verrà rimosso dal gruppo, non è reversibile!";
/* alert message */
"Member will join the group, accept member?" = "Il membro entrerà nel gruppo, accettarlo?";
/* No comment provided by engineer. */
"Members can add message reactions." = "I membri del gruppo possono aggiungere reazioni ai messaggi.";
@ -3432,6 +3492,9 @@ snd error text */
/* No comment provided by engineer. */
"New member role" = "Nuovo ruolo del membro";
/* rcv group event chat item */
"New member wants to join the group." = "Un nuovo membro vuole entrare nel gruppo.";
/* notification */
"new message" = "messaggio nuovo";
@ -3471,6 +3534,9 @@ snd error text */
/* No comment provided by engineer. */
"No chats in list %@" = "Nessuna chat nell'elenco %@";
/* No comment provided by engineer. */
"No chats with members" = "Nessuna chat con membri";
/* No comment provided by engineer. */
"No contacts selected" = "Nessun contatto selezionato";
@ -3555,6 +3621,9 @@ snd error text */
/* No comment provided by engineer. */
"Not compatible!" = "Non compatibile!";
/* No comment provided by engineer. */
"not synchronized" = "non sincronizzato";
/* No comment provided by engineer. */
"Notes" = "Note";
@ -3587,6 +3656,7 @@ snd error text */
/* enabled status
group pref value
member criteria value
time to disappear */
"off" = "off";
@ -3599,7 +3669,8 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "offerto %1$@: %2$@";
/* alert button */
/* alert action
alert button */
"Ok" = "Ok";
/* No comment provided by engineer. */
@ -3695,6 +3766,9 @@ time to disappear */
/* No comment provided by engineer. */
"Open group" = "Apri gruppo";
/* alert title */
"Open link?" = "Aprire il link?";
/* authentication reason */
"Open migration to another device" = "Apri migrazione ad un altro dispositivo";
@ -3797,6 +3871,9 @@ time to disappear */
/* No comment provided by engineer. */
"pending approval" = "in attesa di approvazione";
/* No comment provided by engineer. */
"pending review" = "in attesa di revisione";
/* No comment provided by engineer. */
"Periodic" = "Periodicamente";
@ -3866,6 +3943,9 @@ time to disappear */
/* token info */
"Please try to disable and re-enable notfications." = "Prova a disattivare e riattivare le notifiche.";
/* snd group event chat item */
"Please wait for group moderators to review your request to join the group." = "Attendi che i moderatori del gruppo revisionino la tua richiesta di entrare nel gruppo.";
/* token info */
"Please wait for token activation to complete." = "Attendi il completamento dell'attivazione del token.";
@ -4146,6 +4226,9 @@ swipe action */
/* No comment provided by engineer. */
"Reject contact request" = "Rifiuta la richiesta di contatto";
/* alert title */
"Reject member?" = "Rifiutare il membro?";
/* No comment provided by engineer. */
"rejected" = "rifiutato";
@ -4185,6 +4268,9 @@ swipe action */
/* profile update event chat item */
"removed contact address" = "indirizzo di contatto rimosso";
/* No comment provided by engineer. */
"removed from group" = "rimosso dal gruppo";
/* profile update event chat item */
"removed profile picture" = "immagine del profilo rimossa";
@ -4233,6 +4319,9 @@ swipe action */
/* No comment provided by engineer. */
"Report reason?" = "Motivo della segnalazione?";
/* alert title */
"Report sent to moderators" = "Segnalazione inviata ai moderatori";
/* report reason */
"Report spam: only group moderators will see it." = "Segnala spam: solo i moderatori del gruppo lo vedranno.";
@ -4248,6 +4337,9 @@ swipe action */
/* No comment provided by engineer. */
"Reports" = "Segnalazioni";
/* No comment provided by engineer. */
"request to join rejected" = "richiesta di entrare rifiutata";
/* chat list item title */
"requested to connect" = "richiesto di connettersi";
@ -4302,9 +4394,21 @@ swipe action */
/* chat item action */
"Reveal" = "Rivela";
/* No comment provided by engineer. */
"review" = "revisiona";
/* No comment provided by engineer. */
"Review conditions" = "Leggi le condizioni";
/* admission stage */
"Review members" = "Revisiona i membri";
/* admission stage description */
"Review members before admitting (\"knocking\")." = "Revisiona i membri prima di ammetterli (\"bussare\").";
/* No comment provided by engineer. */
"reviewed by admins" = "revisionato dagli amministratori";
/* No comment provided by engineer. */
"Revoke" = "Revoca";
@ -4333,6 +4437,9 @@ chat item action */
/* alert button */
"Save (and notify contacts)" = "Salva (e avvisa i contatti)";
/* alert title */
"Save admission settings?" = "Salvare le impostazioni di ammissione?";
/* alert button */
"Save and notify contact" = "Salva e avvisa il contatto";
@ -4669,6 +4776,9 @@ chat item action */
/* No comment provided by engineer. */
"Set it instead of system authentication." = "Impostalo al posto dell'autenticazione di sistema.";
/* No comment provided by engineer. */
"Set member admission" = "Imposta l'ammissione del membro";
/* No comment provided by engineer. */
"Set message expiration in chats." = "Imposta la scadenza dei messaggi nelle chat.";
@ -5100,9 +5210,6 @@ report reason */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "Il database vecchio non è stato rimosso durante la migrazione, può essere eliminato.";
/* No comment provided by engineer. */
"Your profile is stored on your device and only shared with your contacts." = "Il profilo è condiviso solo con i tuoi contatti.";
/* No comment provided by engineer. */
"The same conditions will apply to operator **%@**." = "Le stesse condizioni si applicheranno all'operatore **%@**.";
@ -5712,6 +5819,9 @@ report reason */
/* No comment provided by engineer. */
"You accepted connection" = "Hai accettato la connessione";
/* snd group event chat item */
"you accepted this member" = "hai accettato questo membro";
/* No comment provided by engineer. */
"You allow" = "Lo consenti";
@ -5823,6 +5933,9 @@ report reason */
/* alert message */
"You can view invitation link again in connection details." = "Puoi vedere di nuovo il link di invito nei dettagli di connessione.";
/* alert message */
"You can view your reports in Chat with admins." = "Puoi vedere le tue segnalazioni nella chat con gli amministratori.";
/* No comment provided by engineer. */
"You can't send messages!" = "Non puoi inviare messaggi!";
@ -5991,15 +6104,15 @@ report reason */
/* No comment provided by engineer. */
"Your profile **%@** will be shared." = "Verrà condiviso il tuo profilo **%@**.";
/* No comment provided by engineer. */
"Your profile is stored on your device and only shared with your contacts." = "Il profilo è condiviso solo con i tuoi contatti.";
/* No comment provided by engineer. */
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Il tuo profilo è memorizzato sul tuo dispositivo e condiviso solo con i tuoi contatti. I server di SimpleX non possono vedere il tuo profilo.";
/* alert message */
"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Il tuo profilo è stato cambiato. Se lo salvi, il profilo aggiornato verrà inviato a tutti i tuoi contatti.";
/* No comment provided by engineer. */
"Your profile, contacts and delivered messages are stored on your device." = "Il tuo profilo, i contatti e i messaggi recapitati sono memorizzati sul tuo dispositivo.";
/* No comment provided by engineer. */
"Your random profile" = "Il tuo profilo casuale";

View file

@ -1277,7 +1277,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't enable" = "有効にしない";
/* No comment provided by engineer. */
/* alert action */
"Don't show again" = "次から表示しない";
/* No comment provided by engineer. */
@ -1514,7 +1514,7 @@ swipe action */
/* alert title */
"Error receiving file" = "ファイル受信にエラー発生";
/* No comment provided by engineer. */
/* alert title */
"Error removing member" = "メンバー除名にエラー発生";
/* No comment provided by engineer. */
@ -2295,6 +2295,7 @@ snd error text */
/* enabled status
group pref value
member criteria value
time to disappear */
"off" = "オフ";
@ -2307,7 +2308,8 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "提供された %1$@: %2$@";
/* alert button */
/* alert action
alert button */
"Ok" = "OK";
/* No comment provided by engineer. */
@ -3111,9 +3113,6 @@ chat item action */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "古いデータベースは移行時に削除されなかったので、削除することができます。";
/* No comment provided by engineer. */
"Your profile is stored on your device and only shared with your contacts." = "プロフィールは連絡先にしか共有されません。";
/* No comment provided by engineer. */
"The second tick we missed! ✅" = "長らくお待たせしました! ✅";
@ -3592,10 +3591,10 @@ chat item action */
"Your profile **%@** will be shared." = "あなたのプロファイル **%@** が共有されます。";
/* No comment provided by engineer. */
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "プロフィールはデバイスに保存され、連絡先とのみ共有されます。 SimpleX サーバーはあなたのプロファイルを参照できません。";
"Your profile is stored on your device and only shared with your contacts." = "プロフィールは連絡先にしか共有されません。";
/* No comment provided by engineer. */
"Your profile, contacts and delivered messages are stored on your device." = "あなたのプロフィール、連絡先、送信したメッセージがご自分の端末に保存されます。";
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "プロフィールはデバイスに保存され、連絡先とのみ共有されます。 SimpleX サーバーはあなたのプロファイルを参照できません。";
/* No comment provided by engineer. */
"Your random profile" = "あなたのランダム・プロフィール";

View file

@ -1869,7 +1869,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't miss important messages." = "Mis geen belangrijke berichten.";
/* No comment provided by engineer. */
/* alert action */
"Don't show again" = "Niet meer weergeven";
/* No comment provided by engineer. */
@ -2233,7 +2233,7 @@ chat item action */
/* alert title */
"Error registering for notifications" = "Fout bij registreren voor meldingen";
/* No comment provided by engineer. */
/* alert title */
"Error removing member" = "Fout bij verwijderen van lid";
/* alert title */
@ -3584,6 +3584,7 @@ snd error text */
/* enabled status
group pref value
member criteria value
time to disappear */
"off" = "uit";
@ -3596,7 +3597,8 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "voorgesteld %1$@: %2$@";
/* alert button */
/* alert action
alert button */
"Ok" = "OK";
/* No comment provided by engineer. */
@ -5091,9 +5093,6 @@ report reason */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "De oude database is niet verwijderd tijdens de migratie, deze kan worden verwijderd.";
/* No comment provided by engineer. */
"Your profile is stored on your device and only shared with your contacts." = "Het profiel wordt alleen gedeeld met uw contacten.";
/* No comment provided by engineer. */
"The same conditions will apply to operator **%@**." = "Dezelfde voorwaarden gelden voor operator **%@**.";
@ -5970,15 +5969,15 @@ report reason */
/* No comment provided by engineer. */
"Your profile **%@** will be shared." = "Uw profiel **%@** wordt gedeeld.";
/* No comment provided by engineer. */
"Your profile is stored on your device and only shared with your contacts." = "Het profiel wordt alleen gedeeld met uw contacten.";
/* No comment provided by engineer. */
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Uw profiel wordt op uw apparaat opgeslagen en alleen gedeeld met uw contacten. SimpleX servers kunnen uw profiel niet zien.";
/* alert message */
"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Je profiel is gewijzigd. Als je het opslaat, wordt het bijgewerkte profiel naar al je contacten verzonden.";
/* No comment provided by engineer. */
"Your profile, contacts and delivered messages are stored on your device." = "Uw profiel, contacten en afgeleverde berichten worden op uw apparaat opgeslagen.";
/* No comment provided by engineer. */
"Your random profile" = "Je willekeurige profiel";

View file

@ -1761,7 +1761,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't enable" = "Nie włączaj";
/* No comment provided by engineer. */
/* alert action */
"Don't show again" = "Nie pokazuj ponownie";
/* No comment provided by engineer. */
@ -2092,7 +2092,7 @@ chat item action */
/* No comment provided by engineer. */
"Error reconnecting servers" = "Błąd ponownego łączenia serwerów";
/* No comment provided by engineer. */
/* alert title */
"Error removing member" = "Błąd usuwania członka";
/* No comment provided by engineer. */
@ -3236,6 +3236,7 @@ snd error text */
/* enabled status
group pref value
member criteria value
time to disappear */
"off" = "wyłączony";
@ -3248,7 +3249,8 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "zaoferował %1$@: %2$@";
/* alert button */
/* alert action
alert button */
"Ok" = "Ok";
/* No comment provided by engineer. */
@ -4556,9 +4558,6 @@ chat item action */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "Stara baza danych nie została usunięta podczas migracji, można ją usunąć.";
/* No comment provided by engineer. */
"Your profile is stored on your device and only shared with your contacts." = "Profil jest udostępniany tylko Twoim kontaktom.";
/* No comment provided by engineer. */
"The second tick we missed! ✅" = "Drugi tik, który przegapiliśmy! ✅";
@ -5354,15 +5353,15 @@ chat item action */
/* No comment provided by engineer. */
"Your profile **%@** will be shared." = "Twój profil **%@** zostanie udostępniony.";
/* No comment provided by engineer. */
"Your profile is stored on your device and only shared with your contacts." = "Profil jest udostępniany tylko Twoim kontaktom.";
/* No comment provided by engineer. */
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Twój profil jest przechowywany na urządzeniu i udostępniany tylko Twoim kontaktom. Serwery SimpleX nie mogą zobaczyć Twojego profilu.";
/* alert message */
"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Twój profil został zmieniony. Jeśli go zapiszesz, zaktualizowany profil zostanie wysłany do wszystkich kontaktów.";
/* No comment provided by engineer. */
"Your profile, contacts and delivered messages are stored on your device." = "Twój profil, kontakty i dostarczone wiadomości są przechowywane na Twoim urządzeniu.";
/* No comment provided by engineer. */
"Your random profile" = "Twój losowy profil";

View file

@ -164,7 +164,7 @@
"%d file(s) were not downloaded." = "%d файлов не было загружено.";
/* time interval */
"%d hours" = "%d ч.";
"%d hours" = "%d час.";
/* alert title */
"%d messages not forwarded" = "%d сообщений не переслано";
@ -345,6 +345,12 @@ accept incoming call via notification
swipe action */
"Accept" = "Принять";
/* alert action */
"Accept as member" = "Принять в группу";
/* alert action */
"Accept as observer" = "Принять как читателя";
/* No comment provided by engineer. */
"Accept conditions" = "Принять условия";
@ -358,6 +364,12 @@ swipe action */
swipe action */
"Accept incognito" = "Принять инкогнито";
/* alert title */
"Accept member" = "Принять члена";
/* rcv group event chat item */
"accepted %@" = "принят %@";
/* call status */
"accepted call" = "принятый звонок";
@ -367,6 +379,9 @@ swipe action */
/* chat list item title */
"accepted invitation" = "принятое приглашение";
/* rcv group event chat item */
"accepted you" = "Вы приняты";
/* No comment provided by engineer. */
"Acknowledged" = "Подтверждено";
@ -463,6 +478,9 @@ swipe action */
/* chat item text */
"agreeing encryption…" = "шифрование согласовывается…";
/* member criteria value */
"all" = "все";
/* No comment provided by engineer. */
"All" = "Все";
@ -484,6 +502,9 @@ swipe action */
/* No comment provided by engineer. */
"All group members will remain connected." = "Все члены группы останутся соединены.";
/* feature role */
"all members" = "все члены";
/* No comment provided by engineer. */
"All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "Все сообщения и файлы отправляются с **end-to-end шифрованием**, с постквантовой безопасностью в прямых разговорах.";
@ -502,6 +523,9 @@ swipe action */
/* No comment provided by engineer. */
"All reports will be archived for you." = "Все сообщения о нарушениях будут заархивированы для вас.";
/* No comment provided by engineer. */
"All servers" = "Все серверы";
/* No comment provided by engineer. */
"All your contacts will remain connected." = "Все контакты, которые соединились через этот адрес, сохранятся.";
@ -899,6 +923,9 @@ marked deleted chat item preview text */
/* No comment provided by engineer. */
"Can't message member" = "Не удаётся отправить сообщение члену группы";
/* No comment provided by engineer. */
"can't send messages" = "нельзя отправлять";
/* alert action
alert button */
"Cancel" = "Отменить";
@ -1036,9 +1063,18 @@ set passcode view */
/* No comment provided by engineer. */
"Chat will be deleted for you - this cannot be undone!" = "Разговор будет удален для Вас - это действие нельзя отменить!";
/* chat toolbar */
"Chat with admins" = "Чат с админами";
/* No comment provided by engineer. */
"Chat with member" = "Чат с членом группы";
/* No comment provided by engineer. */
"Chats" = "Чаты";
/* No comment provided by engineer. */
"Chats with members" = "Чаты с членами группы";
/* No comment provided by engineer. */
"Check messages every 20 min." = "Проверять сообщения каждые 20 минут.";
@ -1327,9 +1363,15 @@ set passcode view */
/* No comment provided by engineer. */
"Contact already exists" = "Существующий контакт";
/* No comment provided by engineer. */
"contact deleted" = "контакт удален";
/* No comment provided by engineer. */
"Contact deleted!" = "Контакт удален!";
/* No comment provided by engineer. */
"contact disabled" = "контакт выключен";
/* No comment provided by engineer. */
"contact has e2e encryption" = "у контакта есть e2e шифрование";
@ -1348,6 +1390,9 @@ set passcode view */
/* No comment provided by engineer. */
"Contact name" = "Имена контактов";
/* No comment provided by engineer. */
"contact not ready" = "контакт не готов";
/* No comment provided by engineer. */
"Contact preferences" = "Предпочтения контакта";
@ -1596,6 +1641,9 @@ swipe action */
/* No comment provided by engineer. */
"Delete chat profile?" = "Удалить профиль?";
/* alert title */
"Delete chat with member?" = "Удалить чат с членом группы?";
/* No comment provided by engineer. */
"Delete chat?" = "Удалить разговор?";
@ -1782,6 +1830,9 @@ swipe action */
/* No comment provided by engineer. */
"Direct messages between members are prohibited in this chat." = "Личные сообщения запрещены в этой группе.";
/* No comment provided by engineer. */
"Direct messages between members are prohibited." = "Прямые сообщения между членами запрещены.";
/* No comment provided by engineer. */
"Disable (keep overrides)" = "Выключить (кроме исключений)";
@ -1836,6 +1887,9 @@ swipe action */
/* No comment provided by engineer. */
"Do it later" = "Отложить";
/* No comment provided by engineer. */
"Do not send history to new members." = "Не отправлять историю новым членам.";
/* No comment provided by engineer. */
"Do NOT send messages directly, even if your or destination server does not support private routing." = "Не отправлять сообщения напрямую, даже если сервер получателя не поддерживает конфиденциальную доставку.";
@ -1860,7 +1914,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't miss important messages." = "Не пропустите важные сообщения.";
/* No comment provided by engineer. */
/* alert action */
"Don't show again" = "Не показывать";
/* No comment provided by engineer. */
@ -1933,6 +1987,9 @@ chat item action */
/* No comment provided by engineer. */
"Enable camera access" = "Включить доступ к камере";
/* No comment provided by engineer. */
"Enable Flux in Network & servers settings for better metadata privacy." = "Включите Flux в настройках Сеть и серверы для лучшей конфиденциальности метаданных.";
/* No comment provided by engineer. */
"Enable for all" = "Включить для всех";
@ -2101,6 +2158,12 @@ chat item action */
/* No comment provided by engineer. */
"Error accepting contact request" = "Ошибка при принятии запроса на соединение";
/* alert title */
"Error accepting member" = "Ошибка вступления члена группы";
/* No comment provided by engineer. */
"Error adding member(s)" = "Ошибка при добавлении членов группы";
/* alert title */
"Error adding server" = "Ошибка добавления сервера";
@ -2137,6 +2200,9 @@ chat item action */
/* alert title */
"Error creating list" = "Ошибка создания списка";
/* No comment provided by engineer. */
"Error creating member contact" = "Ошибка при создании контакта";
/* No comment provided by engineer. */
"Error creating message" = "Ошибка создания сообщения";
@ -2152,6 +2218,9 @@ chat item action */
/* No comment provided by engineer. */
"Error deleting chat database" = "Ошибка при удалении данных чата";
/* alert title */
"Error deleting chat with member" = "Ошибка при удалении чата с членом группы";
/* No comment provided by engineer. */
"Error deleting chat!" = "Ошибка при удалении чата!";
@ -2215,6 +2284,9 @@ chat item action */
/* alert title */
"Error registering for notifications" = "Ошибка регистрации для уведомлений";
/* alert title */
"Error removing member" = "Ошибка при удалении члена группы";
/* alert title */
"Error reordering lists" = "Ошибка сортировки списков";
@ -2251,6 +2323,9 @@ chat item action */
/* No comment provided by engineer. */
"Error sending email" = "Ошибка отправки email";
/* No comment provided by engineer. */
"Error sending member contact invitation" = "Ошибка при отправке приглашения члену";
/* No comment provided by engineer. */
"Error sending message" = "Ошибка при отправке сообщения";
@ -2451,6 +2526,9 @@ snd error text */
/* No comment provided by engineer. */
"Fix not supported by contact" = "Починка не поддерживается контактом";
/* No comment provided by engineer. */
"Fix not supported by group member" = "Починка не поддерживается членом группы.";
/* No comment provided by engineer. */
"For all moderators" = "Для всех модераторов";
@ -2529,6 +2607,9 @@ snd error text */
/* No comment provided by engineer. */
"Full name (optional)" = "Полное имя (не обязательно)";
/* No comment provided by engineer. */
"Fully decentralized visible only to members." = "Группа полностью децентрализована она видна только членам.";
/* No comment provided by engineer. */
"Fully re-implemented - work in background!" = "Полностью обновлены - работают в фоне!";
@ -2577,6 +2658,9 @@ snd error text */
/* No comment provided by engineer. */
"Group invitation is no longer valid, it was removed by sender." = "Приглашение в группу больше не действительно, оно было удалено отправителем.";
/* No comment provided by engineer. */
"group is deleted" = "группа удалена";
/* No comment provided by engineer. */
"Group link" = "Ссылка группы";
@ -2595,12 +2679,18 @@ snd error text */
/* No comment provided by engineer. */
"Group profile" = "Профиль группы";
/* No comment provided by engineer. */
"Group profile is stored on members' devices, not on the servers." = "Профиль группы хранится на устройствах членов, а не на серверах.";
/* snd group event chat item */
"group profile updated" = "профиль группы обновлен";
/* No comment provided by engineer. */
"Group welcome message" = "Приветственное сообщение группы";
/* No comment provided by engineer. */
"Group will be deleted for all members - this cannot be undone!" = "Группа будет удалена для всех членов - это действие нельзя отменить!";
/* No comment provided by engineer. */
"Group will be deleted for you - this cannot be undone!" = "Группа будет удалена для Вас - это действие нельзя отменить!";
@ -2637,6 +2727,9 @@ snd error text */
/* No comment provided by engineer. */
"History" = "История";
/* No comment provided by engineer. */
"History is not sent to new members." = "История не отправляется новым членам.";
/* time unit */
"hours" = "часов";
@ -2871,6 +2964,9 @@ snd error text */
/* No comment provided by engineer. */
"Invite friends" = "Пригласить друзей";
/* No comment provided by engineer. */
"Invite members" = "Пригласить членов группы";
/* No comment provided by engineer. */
"Invite to chat" = "Пригласить в разговор";
@ -3084,15 +3180,75 @@ snd error text */
/* blur media */
"Medium" = "Среднее";
/* member role */
"member" = "член группы";
/* No comment provided by engineer. */
"Member" = "Член группы";
/* profile update event chat item */
"member %@ changed to %@" = "член %1$@ изменился на %2$@";
/* No comment provided by engineer. */
"Member admission" = "Приём членов в группу";
/* rcv group event chat item */
"member connected" = "соединен(а)";
/* No comment provided by engineer. */
"member has old version" = "член имеет старую версию";
/* item status text */
"Member inactive" = "Член неактивен";
/* chat feature */
"Member reports" = "Сообщения о нарушениях";
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". All chat members will be notified." = "Роль участника будет изменена на \"%@\". Все участники разговора получат уведомление.";
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". All group members will be notified." = "Роль члена будет изменена на \"%@\". Все члены группы получат уведомление.";
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "Роль члена будет изменена на \"%@\". Будет отправлено новое приглашение.";
/* No comment provided by engineer. */
"Member will be removed from chat - this cannot be undone!" = "Член будет удален из разговора - это действие нельзя отменить!";
/* No comment provided by engineer. */
"Member will be removed from group - this cannot be undone!" = "Член группы будет удален - это действие нельзя отменить!";
/* alert message */
"Member will join the group, accept member?" = "Участник хочет присоединиться к группе. Принять?";
/* No comment provided by engineer. */
"Members can add message reactions." = "Члены могут добавлять реакции на сообщения.";
/* No comment provided by engineer. */
"Members can irreversibly delete sent messages. (24 hours)" = "Члены могут необратимо удалять отправленные сообщения. (24 часа)";
/* No comment provided by engineer. */
"Members can report messsages to moderators." = "Члены группы могут пожаловаться модераторам.";
/* No comment provided by engineer. */
"Members can send direct messages." = "Члены могут посылать прямые сообщения.";
/* No comment provided by engineer. */
"Members can send disappearing messages." = "Члены могут посылать исчезающие сообщения.";
/* No comment provided by engineer. */
"Members can send files and media." = "Члены могут слать файлы и медиа.";
/* No comment provided by engineer. */
"Members can send SimpleX links." = "Члены могут отправлять ссылки SimpleX.";
/* No comment provided by engineer. */
"Members can send voice messages." = "Члены могут отправлять голосовые сообщения.";
/* No comment provided by engineer. */
"Mention members 👋" = "Упоминайте участников 👋";
/* No comment provided by engineer. */
"Menus" = "Меню";
@ -3114,6 +3270,9 @@ snd error text */
/* item status text */
"Message forwarded" = "Сообщение переслано";
/* item status description */
"Message may be delivered later if member becomes active." = "Сообщение может быть доставлено позже, если член группы станет активным.";
/* No comment provided by engineer. */
"Message queue info" = "Информация об очереди сообщений";
@ -3330,6 +3489,12 @@ snd error text */
/* No comment provided by engineer. */
"New media options" = "Новые медиа-опции";
/* No comment provided by engineer. */
"New member role" = "Роль члена группы";
/* rcv group event chat item */
"New member wants to join the group." = "Новый участник хочет присоединиться к группе.";
/* notification */
"new message" = "новое сообщение";
@ -3369,6 +3534,9 @@ snd error text */
/* No comment provided by engineer. */
"No chats in list %@" = "Нет чатов в списке %@";
/* No comment provided by engineer. */
"No chats with members" = "Нет чатов с членами группы";
/* No comment provided by engineer. */
"No contacts selected" = "Контакты не выбраны";
@ -3453,6 +3621,9 @@ snd error text */
/* No comment provided by engineer. */
"Not compatible!" = "Несовместимая версия!";
/* No comment provided by engineer. */
"not synchronized" = "не синхронизирован";
/* No comment provided by engineer. */
"Notes" = "Заметки";
@ -3477,11 +3648,15 @@ snd error text */
/* alert title */
"Notifications status" = "Статус уведомлений";
/* No comment provided by engineer. */
"Now admins can:\n- delete members' messages.\n- disable members (\"observer\" role)" = "Теперь админы могут:\n- удалять сообщения членов.\n- приостанавливать членов (роль наблюдатель)";
/* member role */
"observer" = "читатель";
/* enabled status
group pref value
member criteria value
time to disappear */
"off" = "нет";
@ -3494,7 +3669,8 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "предложил(a) %1$@: %2$@";
/* alert button */
/* alert action
alert button */
"Ok" = "Ок";
/* No comment provided by engineer. */
@ -3590,6 +3766,9 @@ time to disappear */
/* No comment provided by engineer. */
"Open group" = "Открыть группу";
/* alert title */
"Open link?" = "Открыть ссылку?";
/* authentication reason */
"Open migration to another device" = "Открытие миграции на другое устройство";
@ -3665,6 +3844,9 @@ time to disappear */
/* No comment provided by engineer. */
"Password to show" = "Пароль чтобы раскрыть";
/* past/unknown group member */
"Past member %@" = "Бывший член %@";
/* No comment provided by engineer. */
"Paste desktop address" = "Вставить адрес компьютера";
@ -3689,6 +3871,9 @@ time to disappear */
/* No comment provided by engineer. */
"pending approval" = "ожидает утверждения";
/* No comment provided by engineer. */
"pending review" = "ожидает одобрения";
/* No comment provided by engineer. */
"Periodic" = "Периодически";
@ -3758,6 +3943,9 @@ time to disappear */
/* token info */
"Please try to disable and re-enable notfications." = "Попробуйте выключить и снова включить уведомления.";
/* snd group event chat item */
"Please wait for group moderators to review your request to join the group." = "Пожалуйста, подождите, пока модераторы группы рассмотрят ваш запрос на вступление.";
/* token info */
"Please wait for token activation to complete." = "Пожалуйста, дождитесь завершения активации токена.";
@ -3857,6 +4045,9 @@ time to disappear */
/* No comment provided by engineer. */
"Prohibit reporting messages to moderators." = "Запретить жаловаться модераторам группы.";
/* No comment provided by engineer. */
"Prohibit sending direct messages to members." = "Запретить посылать прямые сообщения членам группы.";
/* No comment provided by engineer. */
"Prohibit sending disappearing messages." = "Запретить посылать исчезающие сообщения.";
@ -4035,6 +4226,9 @@ swipe action */
/* No comment provided by engineer. */
"Reject contact request" = "Отклонить запрос";
/* alert title */
"Reject member?" = "Отклонить участника?";
/* No comment provided by engineer. */
"rejected" = "отклонён";
@ -4056,6 +4250,12 @@ swipe action */
/* No comment provided by engineer. */
"Remove image" = "Удалить изображение";
/* No comment provided by engineer. */
"Remove member" = "Удалить члена группы";
/* No comment provided by engineer. */
"Remove member?" = "Удалить члена группы?";
/* No comment provided by engineer. */
"Remove passphrase from keychain?" = "Удалить пароль из Keychain?";
@ -4068,6 +4268,9 @@ swipe action */
/* profile update event chat item */
"removed contact address" = "удалён адрес контакта";
/* No comment provided by engineer. */
"removed from group" = "удален из группы";
/* profile update event chat item */
"removed profile picture" = "удалена картинка профиля";
@ -4116,6 +4319,9 @@ swipe action */
/* No comment provided by engineer. */
"Report reason?" = "Причина сообщения?";
/* alert title */
"Report sent to moderators" = "Жалоба отправлена модераторам";
/* report reason */
"Report spam: only group moderators will see it." = "Пожаловаться на спам: увидят только модераторы группы.";
@ -4131,6 +4337,9 @@ swipe action */
/* No comment provided by engineer. */
"Reports" = "Сообщения о нарушениях";
/* No comment provided by engineer. */
"request to join rejected" = "запрос на вступление отклонён";
/* chat list item title */
"requested to connect" = "запрошено соединение";
@ -4185,9 +4394,21 @@ swipe action */
/* chat item action */
"Reveal" = "Показать";
/* No comment provided by engineer. */
"review" = "рассмотрение";
/* No comment provided by engineer. */
"Review conditions" = "Посмотреть условия";
/* admission stage */
"Review members" = "Одобрять членов";
/* admission stage description */
"Review members before admitting (\"knocking\")." = "Одобрять членов для вступления в группу.";
/* No comment provided by engineer. */
"reviewed by admins" = "одобрен админами";
/* No comment provided by engineer. */
"Revoke" = "Отозвать";
@ -4216,9 +4437,15 @@ chat item action */
/* alert button */
"Save (and notify contacts)" = "Сохранить (и уведомить контакты)";
/* alert title */
"Save admission settings?" = "Сохранить настройки вступления?";
/* alert button */
"Save and notify contact" = "Сохранить и уведомить контакт";
/* No comment provided by engineer. */
"Save and notify group members" = "Сохранить и уведомить членов группы";
/* No comment provided by engineer. */
"Save and reconnect" = "Сохранить и переподключиться";
@ -4411,6 +4638,9 @@ chat item action */
/* No comment provided by engineer. */
"Send them from gallery or custom keyboards." = "Отправьте из галереи или из дополнительных клавиатур.";
/* No comment provided by engineer. */
"Send up to 100 last messages to new members." = "Отправить до 100 последних сообщений новым членам.";
/* alert message */
"Sender cancelled file transfer." = "Отправитель отменил передачу файла.";
@ -4546,6 +4776,9 @@ chat item action */
/* No comment provided by engineer. */
"Set it instead of system authentication." = "Установите код вместо системной аутентификации.";
/* No comment provided by engineer. */
"Set member admission" = "Приём членов в группу";
/* No comment provided by engineer. */
"Set message expiration in chats." = "Установите срок хранения сообщений в чатах.";
@ -4564,6 +4797,9 @@ chat item action */
/* No comment provided by engineer. */
"Set passphrase to export" = "Установите пароль";
/* No comment provided by engineer. */
"Set the message shown to new members!" = "Установить сообщение для новых членов группы!";
/* No comment provided by engineer. */
"Set timeouts for proxy/VPN" = "Установить таймауты для прокси/VPN";
@ -4616,6 +4852,9 @@ chat item action */
/* No comment provided by engineer. */
"Share with contacts" = "Поделиться с контактами";
/* No comment provided by engineer. */
"Short link" = "Короткая ссылка";
/* No comment provided by engineer. */
"Show → on messages sent via private routing." = "Показать → на сообщениях доставленных конфиденциально.";
@ -4658,6 +4897,9 @@ chat item action */
/* No comment provided by engineer. */
"SimpleX address or 1-time link?" = "Адрес SimpleX или одноразовая ссылка?";
/* simplex link type */
"SimpleX channel link" = "SimpleX ссылка канала";
/* No comment provided by engineer. */
"SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app." = "SimpleX Chat и Flux заключили соглашение добавить серверы под управлением Flux в приложение.";
@ -4954,10 +5196,19 @@ report reason */
"The ID of the next message is incorrect (less or equal to the previous).\nIt can happen because of some bug or when the connection is compromised." = "Неправильный ID предыдущего сообщения (меньше или равен предыдущему).\nЭто может произойти из-за ошибки программы, или когда соединение компроментировано.";
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "Предыдущая версия данных чата не удалена при перемещении, её можно удалить.";
"The message will be deleted for all members." = "Сообщение будет удалено для всех членов группы.";
/* No comment provided by engineer. */
"Your profile is stored on your device and only shared with your contacts." = "Ваш профиль храниться на Вашем устройстве и отправляется только контактам.";
"The message will be marked as moderated for all members." = "Сообщение будет помечено как удаленное для всех членов группы.";
/* No comment provided by engineer. */
"The messages will be deleted for all members." = "Сообщения будут удалены для всех членов группы.";
/* No comment provided by engineer. */
"The messages will be marked as moderated for all members." = "Сообщения будут помечены как удаленные для всех членов группы.";
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "Предыдущая версия данных чата не удалена при перемещении, её можно удалить.";
/* No comment provided by engineer. */
"The same conditions will apply to operator **%@**." = "Те же самые условия будут приняты для оператора **%@**.";
@ -5022,6 +5273,9 @@ report reason */
/* No comment provided by engineer. */
"This display name is invalid. Please choose another name." = "Ошибка имени профиля. Пожалуйста, выберите другое имя.";
/* No comment provided by engineer. */
"This group has over %lld members, delivery receipts are not sent." = "В этой группе более %lld членов, отчёты о доставке не отправляются.";
/* No comment provided by engineer. */
"This group no longer exists." = "Эта группа больше не существует.";
@ -5031,6 +5285,9 @@ report reason */
/* No comment provided by engineer. */
"This is your own SimpleX address!" = "Это ваш собственный адрес SimpleX!";
/* No comment provided by engineer. */
"This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." = "Эта ссылка требует новую версию. Обновите приложение или попросите Ваш контакт прислать совместимую ссылку.";
/* No comment provided by engineer. */
"This link was used with another mobile device, please create a new link on the desktop." = "Эта ссылка была использована на другом мобильном, пожалуйста, создайте новую ссылку на компьютере.";
@ -5142,6 +5399,15 @@ report reason */
/* No comment provided by engineer. */
"Unblock for all" = "Разблокировать для всех";
/* No comment provided by engineer. */
"Unblock member" = "Разблокировать члена группы";
/* No comment provided by engineer. */
"Unblock member for all?" = "Разблокировать члена для всех?";
/* No comment provided by engineer. */
"Unblock member?" = "Разблокировать члена группы?";
/* rcv group event chat item */
"unblocked %@" = "%@ разблокирован";
@ -5214,6 +5480,12 @@ report reason */
/* swipe action */
"Unread" = "Не прочитано";
/* No comment provided by engineer. */
"Unsupported connection link" = "Ссылка не поддерживается";
/* No comment provided by engineer. */
"Up to 100 last messages are sent to new members." = "До 100 последних сообщений отправляются новым членам.";
/* No comment provided by engineer. */
"Update" = "Обновить";
@ -5304,6 +5576,9 @@ report reason */
/* No comment provided by engineer. */
"Use servers" = "Использовать серверы";
/* No comment provided by engineer. */
"Use short links (BETA)" = "Короткие ссылки (БЕТА)";
/* No comment provided by engineer. */
"Use SimpleX Chat servers?" = "Использовать серверы предосталенные SimpleX Chat?";
@ -5313,6 +5588,9 @@ report reason */
/* No comment provided by engineer. */
"Use TCP port %@ when no port is specified." = "Использовать TCP-порт %@, когда порт не указан.";
/* No comment provided by engineer. */
"Use TCP port 443 for preset servers only." = "Использовать TCP-порт 443 только для серверов по умолчанию.";
/* No comment provided by engineer. */
"Use the app while in the call." = "Используйте приложение во время звонка.";
@ -5541,6 +5819,9 @@ report reason */
/* No comment provided by engineer. */
"You accepted connection" = "Вы приняли приглашение соединиться";
/* snd group event chat item */
"you accepted this member" = "Вы приняли этого члена";
/* No comment provided by engineer. */
"You allow" = "Вы разрешаете";
@ -5631,6 +5912,9 @@ report reason */
/* No comment provided by engineer. */
"You can set lock screen notification preview via settings." = "Вы можете установить просмотр уведомлений на экране блокировки в настройках.";
/* No comment provided by engineer. */
"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." = "Вы можете поделиться ссылкой или QR кодом - через них можно присоединиться к группе. Вы сможете удалить ссылку, сохранив членов группы, которые через нее соединились.";
/* No comment provided by engineer. */
"You can share this address with your contacts to let them connect with **%@**." = "Вы можете поделиться этим адресом с Вашими контактами, чтобы они могли соединиться с **%@**.";
@ -5649,6 +5933,9 @@ report reason */
/* alert message */
"You can view invitation link again in connection details." = "Вы можете увидеть ссылку-приглашение снова открыв соединение.";
/* alert message */
"You can view your reports in Chat with admins." = "Вы можете найти Ваши жалобы в Чате с админами.";
/* No comment provided by engineer. */
"You can't send messages!" = "Вы не можете отправлять сообщения!";
@ -5685,6 +5972,9 @@ report reason */
/* No comment provided by engineer. */
"You joined this group" = "Вы вступили в эту группу";
/* No comment provided by engineer. */
"You joined this group. Connecting to inviting group member." = "Вы вступили в эту группу. Устанавливается соединение с пригласившим членом группы.";
/* snd group event chat item */
"you left" = "Вы покинули группу";
@ -5739,6 +6029,9 @@ report reason */
/* No comment provided by engineer. */
"You will be required to authenticate when you start or resume the app after 30 seconds in background." = "Вы будете аутентифицированы при запуске и возобновлении приложения, которое было 30 секунд в фоновом режиме.";
/* No comment provided by engineer. */
"You will connect to all group members." = "Вы соединитесь со всеми членами группы.";
/* No comment provided by engineer. */
"You will still receive calls and notifications from muted profiles when they are active." = "Вы все равно получите звонки и уведомления в профилях без звука, когда они активные.";
@ -5811,15 +6104,15 @@ report reason */
/* No comment provided by engineer. */
"Your profile **%@** will be shared." = "Будет отправлен Ваш профиль **%@**.";
/* No comment provided by engineer. */
"Your profile is stored on your device and only shared with your contacts." = "Ваш профиль храниться на Вашем устройстве и отправляется только контактам.";
/* No comment provided by engineer. */
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Ваш профиль хранится на Вашем устройстве и отправляется только Вашим контактам. SimpleX серверы не могут получить доступ к Вашему профилю.";
/* alert message */
"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Ваш профиль был изменен. Если вы сохраните его, обновленный профиль будет отправлен всем вашим контактам.";
/* No comment provided by engineer. */
"Your profile, contacts and delivered messages are stored on your device." = "Ваш профиль, контакты и доставленные сообщения хранятся на Вашем устройстве.";
/* No comment provided by engineer. */
"Your random profile" = "Случайный профиль";
@ -5834,4 +6127,3 @@ report reason */
/* No comment provided by engineer. */
"Your SimpleX address" = "Ваш адрес SimpleX";

View file

@ -1028,7 +1028,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't enable" = "อย่าเปิดใช้งาน";
/* No comment provided by engineer. */
/* alert action */
"Don't show again" = "ไม่ต้องแสดงอีก";
/* No comment provided by engineer. */
@ -1256,7 +1256,7 @@ swipe action */
/* alert title */
"Error receiving file" = "เกิดข้อผิดพลาดในการรับไฟล์";
/* No comment provided by engineer. */
/* alert title */
"Error removing member" = "เกิดข้อผิดพลาดในการลบสมาชิก";
/* No comment provided by engineer. */
@ -2016,6 +2016,7 @@ snd error text */
/* enabled status
group pref value
member criteria value
time to disappear */
"off" = "ปิด";
@ -2028,7 +2029,8 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "เสนอแล้ว %1$@: %2$@";
/* alert button */
/* alert action
alert button */
"Ok" = "ตกลง";
/* No comment provided by engineer. */
@ -2829,9 +2831,6 @@ chat item action */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "ฐานข้อมูลเก่าไม่ได้ถูกลบในระหว่างการย้ายข้อมูล แต่สามารถลบได้";
/* No comment provided by engineer. */
"Your profile is stored on your device and only shared with your contacts." = "โปรไฟล์นี้แชร์กับผู้ติดต่อของคุณเท่านั้น";
/* No comment provided by engineer. */
"The second tick we missed! ✅" = "ขีดที่สองที่เราพลาด! ✅";
@ -3292,10 +3291,10 @@ chat item action */
"Your privacy" = "ความเป็นส่วนตัวของคุณ";
/* No comment provided by engineer. */
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "โปรไฟล์ของคุณจะถูกจัดเก็บไว้ในอุปกรณ์ของคุณและแชร์กับผู้ติดต่อของคุณเท่านั้น เซิร์ฟเวอร์ SimpleX ไม่สามารถดูโปรไฟล์ของคุณได้";
"Your profile is stored on your device and only shared with your contacts." = "โปรไฟล์นี้แชร์กับผู้ติดต่อของคุณเท่านั้น";
/* No comment provided by engineer. */
"Your profile, contacts and delivered messages are stored on your device." = "โปรไฟล์ รายชื่อผู้ติดต่อ และข้อความที่ส่งของคุณจะถูกจัดเก็บไว้ในอุปกรณ์ของคุณ";
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "โปรไฟล์ของคุณจะถูกจัดเก็บไว้ในอุปกรณ์ของคุณและแชร์กับผู้ติดต่อของคุณเท่านั้น เซิร์ฟเวอร์ SimpleX ไม่สามารถดูโปรไฟล์ของคุณได้";
/* No comment provided by engineer. */
"Your random profile" = "โปรไฟล์แบบสุ่มของคุณ";

View file

@ -1740,7 +1740,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't enable" = "Etkinleştirme";
/* No comment provided by engineer. */
/* alert action */
"Don't show again" = "Yeniden gösterme";
/* No comment provided by engineer. */
@ -2083,7 +2083,7 @@ chat item action */
/* No comment provided by engineer. */
"Error reconnecting servers" = "Hata sunuculara yeniden bağlanılıyor";
/* No comment provided by engineer. */
/* alert title */
"Error removing member" = "Kişiyi silerken sorun oluştu";
/* No comment provided by engineer. */
@ -3272,6 +3272,7 @@ snd error text */
/* enabled status
group pref value
member criteria value
time to disappear */
"off" = "kapalı";
@ -3284,7 +3285,8 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "%1$@: %2$@ teklif etti";
/* alert button */
/* alert action
alert button */
"Ok" = "Tamam";
/* No comment provided by engineer. */
@ -4601,9 +4603,6 @@ chat item action */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "Eski veritabanı geçiş sırasında kaldırılmadı, silinebilir.";
/* No comment provided by engineer. */
"Your profile is stored on your device and only shared with your contacts." = "Profil sadece kişilerinle paylaşılacak.";
/* No comment provided by engineer. */
"The second tick we missed! ✅" = "Özlediğimiz ikinci tik! ✅";
@ -5399,15 +5398,15 @@ chat item action */
/* No comment provided by engineer. */
"Your profile **%@** will be shared." = "Profiliniz **%@** paylaşılacaktır.";
/* No comment provided by engineer. */
"Your profile is stored on your device and only shared with your contacts." = "Profil sadece kişilerinle paylaşılacak.";
/* No comment provided by engineer. */
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Profiliniz cihazınızda saklanır ve sadece kişilerinizle paylaşılır. SimpleX sunucuları profilinizi göremez.";
/* alert message */
"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Profiliniz değiştirildi. Kaydederseniz, güncellenmiş profil tüm kişilerinize gönderilecektir.";
/* No comment provided by engineer. */
"Your profile, contacts and delivered messages are stored on your device." = "Profiliniz, kişileriniz ve gönderilmiş mesajlar cihazınızda saklanır.";
/* No comment provided by engineer. */
"Your random profile" = "Rasgele profiliniz";

View file

@ -1746,7 +1746,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't enable" = "Не вмикати";
/* No comment provided by engineer. */
/* alert action */
"Don't show again" = "Більше не показувати";
/* No comment provided by engineer. */
@ -2089,7 +2089,7 @@ chat item action */
/* No comment provided by engineer. */
"Error reconnecting servers" = "Помилка перепідключення серверів";
/* No comment provided by engineer. */
/* alert title */
"Error removing member" = "Помилка видалення учасника";
/* No comment provided by engineer. */
@ -3317,6 +3317,7 @@ snd error text */
/* enabled status
group pref value
member criteria value
time to disappear */
"off" = "вимкнено";
@ -3329,7 +3330,8 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "запропонував %1$@: %2$@";
/* alert button */
/* alert action
alert button */
"Ok" = "Гаразд";
/* No comment provided by engineer. */
@ -4721,9 +4723,6 @@ chat item action */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "Стара база даних не була видалена під час міграції, її можна видалити.";
/* No comment provided by engineer. */
"Your profile is stored on your device and only shared with your contacts." = "Профіль доступний лише вашим контактам.";
/* No comment provided by engineer. */
"The same conditions will apply to operator **%@**." = "Такі ж умови діятимуть і для оператора **%@**.";
@ -5579,15 +5578,15 @@ chat item action */
/* No comment provided by engineer. */
"Your profile **%@** will be shared." = "Ваш профіль **%@** буде опублікований.";
/* No comment provided by engineer. */
"Your profile is stored on your device and only shared with your contacts." = "Профіль доступний лише вашим контактам.";
/* No comment provided by engineer. */
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Ваш профіль зберігається на вашому пристрої і доступний лише вашим контактам. Сервери SimpleX не бачать ваш профіль.";
/* alert message */
"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Ваш профіль було змінено. Якщо ви збережете його, оновлений профіль буде надіслано всім вашим контактам.";
/* No comment provided by engineer. */
"Your profile, contacts and delivered messages are stored on your device." = "Ваш профіль, контакти та доставлені повідомлення зберігаються на вашому пристрої.";
/* No comment provided by engineer. */
"Your random profile" = "Ваш випадковий профіль";

View file

@ -1866,7 +1866,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't miss important messages." = "不错过重要消息。";
/* No comment provided by engineer. */
/* alert action */
"Don't show again" = "不再显示";
/* No comment provided by engineer. */
@ -2227,7 +2227,7 @@ chat item action */
/* alert title */
"Error registering for notifications" = "注册消息推送出错";
/* No comment provided by engineer. */
/* alert title */
"Error removing member" = "删除成员错误";
/* alert title */
@ -3575,6 +3575,7 @@ snd error text */
/* enabled status
group pref value
member criteria value
time to disappear */
"off" = "关闭";
@ -3587,7 +3588,8 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "已提供 %1$@%2$@";
/* alert button */
/* alert action
alert button */
"Ok" = "好的";
/* No comment provided by engineer. */
@ -4922,9 +4924,6 @@ chat item action */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "旧数据库在迁移过程中没有被移除,可以删除。";
/* No comment provided by engineer. */
"Your profile is stored on your device and only shared with your contacts." = "该资料仅与您的联系人共享。";
/* No comment provided by engineer. */
"The second tick we missed! ✅" = "我们错过的第二个\"√\"!✅";
@ -5697,10 +5696,10 @@ chat item action */
"Your profile **%@** will be shared." = "您的个人资料 **%@** 将被共享。";
/* No comment provided by engineer. */
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "您的资料存储在您的设备上并仅与您的联系人共享。 SimpleX 服务器无法看到您的资料。";
"Your profile is stored on your device and only shared with your contacts." = "该资料仅与您的联系人共享。";
/* No comment provided by engineer. */
"Your profile, contacts and delivered messages are stored on your device." = "您的资料、联系人和发送的消息存储在您的设备上。";
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "您的资料存储在您的设备上并仅与您的联系人共享。 SimpleX 服务器无法看到您的资料。";
/* No comment provided by engineer. */
"Your random profile" = "您的随机资料";

View file

@ -155,7 +155,6 @@ buildConfig {
buildConfigField("String", "DESKTOP_VERSION_NAME", "\"${extra["desktop.version_name"]}\"")
buildConfigField("int", "DESKTOP_VERSION_CODE", "${extra["desktop.version_code"]}")
buildConfigField("String", "DATABASE_BACKEND", "\"${extra["database.backend"]}\"")
buildConfigField("Boolean", "ANDROID_BUNDLE", "${extra["android.bundle"]}")
}
}

View file

@ -91,7 +91,7 @@ fun UserPickerUserBox(
ProfileImageForActiveCall(size = USER_PICKER_IMAGE_SIZE, image = userInfo.user.profile.image, color = MaterialTheme.colors.secondaryVariant)
if (userInfo.unreadCount > 0 && !userInfo.user.activeUser) {
unreadBadge(userInfo.unreadCount, userInfo.user.showNtfs, false)
userUnreadBadge(userInfo.unreadCount, userInfo.user.showNtfs, false)
}
}
val user = userInfo.user

View file

@ -295,14 +295,13 @@ object ChatModel {
}
}
class ChatsContext(val contentTag: MsgContentTag?) {
class ChatsContext(val secondaryContextFilter: SecondaryContextFilter?) {
val chats = mutableStateOf(SnapshotStateList<Chat>())
/** if you modify the items by adding/removing them, use helpers methods like [addToChatItems], [removeLastChatItems], [removeAllAndNotify], [clearAndNotify] and so on.
* If some helper is missing, create it. Notify is needed to track state of items that we added manually (not via api call). See [apiLoadMessages].
* If you use api call to get the items, use just [add] instead of [addToChatItems].
* Never modify underlying list directly because it produces unexpected results in ChatView's LazyColumn (setting by index is ok) */
val chatItems = mutableStateOf(SnapshotStateList<ChatItem>())
val chatItemStatuses = mutableMapOf<Long, CIStatus>()
// set listener here that will be notified on every add/delete of a chat item
val chatState = ActiveChatState()
@ -310,6 +309,26 @@ object ChatModel {
fun getChat(id: String): Chat? = chats.value.firstOrNull { it.id == id }
private fun getChatIndex(rhId: Long?, id: String): Int = chats.value.indexOfFirst { it.id == id && it.remoteHostId == rhId }
val contentTag: MsgContentTag? =
when (secondaryContextFilter) {
null -> null
is SecondaryContextFilter.GroupChatScopeContext -> null
is SecondaryContextFilter.MsgContentTagContext -> secondaryContextFilter.contentTag
}
val groupScopeInfo: GroupChatScopeInfo? =
when (secondaryContextFilter) {
null -> null
is SecondaryContextFilter.GroupChatScopeContext -> secondaryContextFilter.groupScopeInfo
is SecondaryContextFilter.MsgContentTagContext -> null
}
val isUserSupportChat: Boolean =
when (groupScopeInfo) {
null -> false
is GroupChatScopeInfo.MemberSupport -> groupScopeInfo.groupMember_ == null
}
suspend fun addChat(chat: Chat) {
chats.add(index = 0, chat)
popChatCollector.throttlePopChat(chat.remoteHostId, chat.id, currentPosition = 0)
@ -342,6 +361,8 @@ object ChatModel {
)
)
}
} else if (currentCInfo is ChatInfo.Group && newCInfo is ChatInfo.Group && newCInfo.groupChatScope != null) {
newCInfo = newCInfo.copy(groupInfo = newCInfo.groupInfo, groupChatScope = null)
}
chats[i] = chats[i].copy(chatInfo = newCInfo)
}
@ -364,7 +385,7 @@ object ChatModel {
updateContact(rhId, updatedContact)
}
suspend fun updateGroup(rhId: Long?, groupInfo: GroupInfo) = updateChat(rhId, ChatInfo.Group(groupInfo))
suspend fun updateGroup(rhId: Long?, groupInfo: GroupInfo) = updateChat(rhId, ChatInfo.Group(groupInfo, groupChatScope = null))
private suspend fun updateChat(rhId: Long?, cInfo: ChatInfo, addMissing: Boolean = true) {
if (hasChat(rhId, cInfo.id)) {
@ -416,55 +437,64 @@ object ChatModel {
}
suspend fun addChatItem(rhId: Long?, cInfo: ChatInfo, cItem: ChatItem) {
// updates membersRequireAttention
updateChatInfo(rhId, cInfo)
// mark chat non deleted
if (cInfo is ChatInfo.Direct && cInfo.chatDeleted) {
val updatedContact = cInfo.contact.copy(chatDeleted = false)
updateContact(rhId, updatedContact)
}
// update previews
// update chat list
val i = getChatIndex(rhId, cInfo.id)
val chat: Chat
if (i >= 0) {
chat = chats[i]
val newPreviewItem = when (cInfo) {
is ChatInfo.Group -> {
val currentPreviewItem = chat.chatItems.firstOrNull()
if (currentPreviewItem != null) {
if (cItem.meta.itemTs >= currentPreviewItem.meta.itemTs) {
cItem
chat = chatsContext.chats[i]
// update preview (for chat from main scope to show new items for invitee in pending status)
if (cInfo.groupChatScope() == null || cInfo.groupInfo_?.membership?.memberPending == true) {
val newPreviewItem = when (cInfo) {
is ChatInfo.Group -> {
val currentPreviewItem = chat.chatItems.firstOrNull()
if (currentPreviewItem != null) {
if (cItem.meta.itemTs >= currentPreviewItem.meta.itemTs) {
cItem
} else {
currentPreviewItem
}
} else {
currentPreviewItem
cItem
}
} else {
cItem
}
}
else -> cItem
}
val wasUnread = chat.unreadTag
chats[i] = chat.copy(
chatItems = arrayListOf(newPreviewItem),
chatStats =
if (cItem.meta.itemStatus is CIStatus.RcvNew) {
increaseUnreadCounter(rhId, currentUser.value!!)
chat.chatStats.copy(unreadCount = chat.chatStats.unreadCount + 1, unreadMentions = if (cItem.meta.userMention) chat.chatStats.unreadMentions + 1 else chat.chatStats.unreadMentions)
}
else
chat.chatStats
)
updateChatTagReadNoContentTag(chats[i], wasUnread)
else -> cItem
}
val wasUnread = chat.unreadTag
chatsContext.chats[i] = chat.copy(
chatItems = arrayListOf(newPreviewItem),
chatStats =
if (cItem.meta.itemStatus is CIStatus.RcvNew) {
increaseUnreadCounter(rhId, currentUser.value!!)
chat.chatStats.copy(unreadCount = chat.chatStats.unreadCount + 1, unreadMentions = if (cItem.meta.userMention) chat.chatStats.unreadMentions + 1 else chat.chatStats.unreadMentions)
} else
chat.chatStats
)
updateChatTagReadInPrimaryContext(chatsContext.chats[i], wasUnread)
}
// pop chat
if (appPlatform.isDesktop && cItem.chatDir.sent) {
reorderChat(chats[i], 0)
reorderChat(chatsContext.chats[i], 0)
} else {
popChatCollector.throttlePopChat(chat.remoteHostId, chat.id, currentPosition = i)
}
} else {
addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = arrayListOf(cItem)))
if (cInfo.groupChatScope() == null) {
addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = arrayListOf(cItem)))
} else {
addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = emptyList()))
}
}
// add to current scope
withContext(Dispatchers.Main) {
// add to current chat
if (chatId.value == cInfo.id) {
if (chatItemBelongsToScope(cInfo, cItem)) {
// Prevent situation when chat item already in the list received from backend
if (chatItems.value.none { it.id == cItem.id }) {
if (chatItems.value.lastOrNull()?.id == ChatItem.TEMP_LIVE_CHAT_ITEM_ID) {
@ -476,84 +506,101 @@ object ChatModel {
}
}
}
suspend fun upsertChatItem(rhId: Long?, cInfo: ChatInfo, cItem: ChatItem): Boolean {
// update previews
val i = getChatIndex(rhId, cInfo.id)
val chat: Chat
val res: Boolean
if (i >= 0) {
chat = chats[i]
val pItem = chat.chatItems.lastOrNull()
if (pItem?.id == cItem.id) {
chats[i] = chat.copy(chatItems = arrayListOf(cItem))
if (pItem.isRcvNew && !cItem.isRcvNew) {
// status changed from New to Read, update counter
decreaseCounterInChatNoContentTag(rhId, cInfo.id)
private fun chatItemBelongsToScope(cInfo: ChatInfo, cItem: ChatItem): Boolean =
when (secondaryContextFilter) {
null ->
chatId.value == cInfo.id && cInfo.groupChatScope() == null
is SecondaryContextFilter.GroupChatScopeContext -> {
val cInfoScope = cInfo.groupChatScope()
if (cInfoScope != null) {
chatId.value == cInfo.id && sameChatScope(cInfoScope, secondaryContextFilter.groupScopeInfo.toChatScope())
} else {
false
}
}
res = false
} else {
addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = arrayListOf(cItem)))
res = true
is SecondaryContextFilter.MsgContentTagContext ->
chatId.value == cInfo.id && cItem.isReport
}
return withContext(Dispatchers.Main) {
// update current chat
if (chatId.value == cInfo.id) {
suspend fun upsertChatItem(rhId: Long?, cInfo: ChatInfo, cItem: ChatItem): Boolean {
var itemAdded = false
// update chat list
if (cInfo.groupChatScope() == null) {
val i = getChatIndex(rhId, cInfo.id)
val chat: Chat
if (i >= 0) {
chat = chats[i]
val pItem = chat.chatItems.lastOrNull()
if (pItem?.id == cItem.id) {
chats[i] = chat.copy(chatItems = arrayListOf(cItem))
if (pItem.isRcvNew && !cItem.isRcvNew) {
// status changed from New to Read, update counter
decreaseCounterInPrimaryContext(rhId, cInfo.id)
}
}
} else {
addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = arrayListOf(cItem)))
itemAdded = true
}
}
// update current scope
withContext(Dispatchers.Main) {
if (chatItemBelongsToScope(cInfo, cItem)) {
if (cItem.isDeletedContent || cItem.meta.itemDeleted != null) {
AudioPlayer.stop(cItem)
}
val items = chatItems.value
val itemIndex = items.indexOfFirst { it.id == cItem.id }
if (itemIndex >= 0) {
items[itemIndex] = cItem
false
} else {
val status = chatItemStatuses.remove(cItem.id)
val ci = if (status != null && cItem.meta.itemStatus is CIStatus.SndNew) {
cItem.copy(meta = cItem.meta.copy(itemStatus = status))
val oldStatus = items[itemIndex].meta.itemStatus
val newStatus = cItem.meta.itemStatus
val ci = if (shouldKeepOldSndCIStatus(oldStatus, newStatus)) {
cItem.copy(meta = cItem.meta.copy(itemStatus = oldStatus))
} else {
cItem
}
addToChatItems(ci)
true
items[itemIndex] = ci
} else {
addToChatItems(cItem)
itemAdded = true
}
} else {
res
}
}
return itemAdded
}
suspend fun updateChatItem(cInfo: ChatInfo, cItem: ChatItem, status: CIStatus? = null, atIndex: Int? = null) {
withContext(Dispatchers.Main) {
if (chatId.value == cInfo.id) {
if (chatItemBelongsToScope(cInfo, cItem)) {
val items = chatItems.value
val itemIndex = atIndex ?: items.indexOfFirst { it.id == cItem.id }
if (itemIndex >= 0) {
items[itemIndex] = cItem
}
} else if (status != null) {
chatItemStatuses[cItem.id] = status
}
}
}
fun removeChatItem(rhId: Long?, cInfo: ChatInfo, cItem: ChatItem) {
if (cItem.isRcvNew) {
decreaseCounterInChatNoContentTag(rhId, cInfo.id)
}
// update previews
val i = getChatIndex(rhId, cInfo.id)
val chat: Chat
if (i >= 0) {
chat = chats[i]
val pItem = chat.chatItems.lastOrNull()
if (pItem?.id == cItem.id) {
chats[i] = chat.copy(chatItems = arrayListOf(ChatItem.deletedItemDummy))
// update chat list
if (cInfo.groupChatScope() == null) {
if (cItem.isRcvNew) {
decreaseCounterInPrimaryContext(rhId, cInfo.id)
}
// update preview
val i = getChatIndex(rhId, cInfo.id)
val chat: Chat
if (i >= 0) {
chat = chats[i]
val pItem = chat.chatItems.lastOrNull()
if (pItem?.id == cItem.id) {
chats[i] = chat.copy(chatItems = arrayListOf(ChatItem.deletedItemDummy))
}
}
}
// remove from current chat
if (chatId.value == cInfo.id) {
// remove from current scope
if (chatItemBelongsToScope(cInfo, cItem)) {
chatItems.removeAllAndNotify {
// We delete taking into account meta.createdAt to make sure we will not be in situation when two items with the same id will be deleted
// (it can happen if already deleted chat item in backend still in the list and new one came with the same (re-used) chat item id)
@ -586,7 +633,7 @@ object ChatModel {
Log.d(TAG, "exiting removeMemberItems")
return
}
val cInfo = ChatInfo.Group(groupInfo)
val cInfo = ChatInfo.Group(groupInfo, groupChatScope = null) // TODO [knocking] review
if (chatId.value == groupInfo.id) {
for (i in 0 until chatItems.value.size) {
val updatedItem = removedUpdatedItem(chatItems.value[i])
@ -617,7 +664,6 @@ object ChatModel {
}
// clear current chat
if (chatId.value == cInfo.id) {
chatItemStatuses.clear()
chatItems.clearAndNotify()
}
}
@ -688,7 +734,7 @@ object ChatModel {
chats[chatIdx] = chat.copy(
chatStats = chat.chatStats.copy(unreadCount = unreadCount, unreadMentions = unreadMentions)
)
updateChatTagReadNoContentTag(chats[chatIdx], wasUnread)
updateChatTagReadInPrimaryContext(chats[chatIdx], wasUnread)
}
}
}
@ -729,9 +775,9 @@ object ChatModel {
return markedRead to mentionsMarkedRead
}
private fun decreaseCounterInChatNoContentTag(rhId: Long?, chatId: ChatId) {
private fun decreaseCounterInPrimaryContext(rhId: Long?, chatId: ChatId) {
// updates anything only in main ChatView, not GroupReportsView or anything else from the future
if (contentTag != null) return
if (secondaryContextFilter != null) return
val chatIndex = getChatIndex(rhId, chatId)
if (chatIndex == -1) return
@ -745,7 +791,7 @@ object ChatModel {
unreadCount = unreadCount,
)
)
updateChatTagReadNoContentTag(chats[chatIndex], wasUnread)
updateChatTagReadInPrimaryContext(chats[chatIndex], wasUnread)
}
fun removeChat(rhId: Long?, id: String) {
@ -814,16 +860,16 @@ object ChatModel {
}
fun increaseUnreadCounter(rhId: Long?, user: UserLike) {
changeUnreadCounterNoContentTag(rhId, user, 1)
changeUnreadCounterInPrimaryContext(rhId, user, 1)
}
fun decreaseUnreadCounter(rhId: Long?, user: UserLike, by: Int = 1) {
changeUnreadCounterNoContentTag(rhId, user, -by)
changeUnreadCounterInPrimaryContext(rhId, user, -by)
}
private fun changeUnreadCounterNoContentTag(rhId: Long?, user: UserLike, by: Int) {
private fun changeUnreadCounterInPrimaryContext(rhId: Long?, user: UserLike, by: Int) {
// updates anything only in main ChatView, not GroupReportsView or anything else from the future
if (contentTag != null) return
if (secondaryContextFilter != null) return
val i = users.indexOfFirst { it.user.userId == user.userId && it.user.remoteHostId == rhId }
if (i != -1) {
@ -831,9 +877,9 @@ object ChatModel {
}
}
fun updateChatTagReadNoContentTag(chat: Chat, wasUnread: Boolean) {
fun updateChatTagReadInPrimaryContext(chat: Chat, wasUnread: Boolean) {
// updates anything only in main ChatView, not GroupReportsView or anything else from the future
if (contentTag != null) return
if (secondaryContextFilter != null) return
val tags = chat.chatInfo.chatTags ?: return
val nowUnread = chat.unreadTag
@ -843,21 +889,21 @@ object ChatModel {
unreadTags[tag] = (unreadTags[tag] ?: 0) + 1
}
} else if (!nowUnread && wasUnread) {
markChatTagReadNoContentTag_(chat, tags)
markChatTagReadInPrimaryContext_(chat, tags)
}
}
fun markChatTagRead(chat: Chat) {
if (chat.unreadTag) {
chat.chatInfo.chatTags?.let { tags ->
markChatTagReadNoContentTag_(chat, tags)
markChatTagReadInPrimaryContext_(chat, tags)
}
}
}
private fun markChatTagReadNoContentTag_(chat: Chat, tags: List<Long>) {
private fun markChatTagReadInPrimaryContext_(chat: Chat, tags: List<Long>) {
// updates anything only in main ChatView, not GroupReportsView or anything else from the future
if (contentTag != null) return
if (secondaryContextFilter != null) return
for (tag in tags) {
val count = unreadTags[tag]
@ -889,12 +935,12 @@ object ChatModel {
val wasReportsCount = chat.chatStats.reportsCount
val nowReportsCount = chats[i].chatStats.reportsCount
val by = if (wasReportsCount == 0 && nowReportsCount > 0) 1 else if (wasReportsCount > 0 && nowReportsCount == 0) -1 else 0
changeGroupReportsTagNoContentTag(by)
changeGroupReportsTagInPrimaryContext(by)
}
}
private fun changeGroupReportsTagNoContentTag(by: Int = 0) {
if (by == 0 || contentTag != null) return
private fun changeGroupReportsTagInPrimaryContext(by: Int = 0) {
if (by == 0 || secondaryContextFilter != null) return
presetTags[PresetTagKind.GROUP_REPORTS] = kotlin.math.max(0, (presetTags[PresetTagKind.GROUP_REPORTS] ?: 0) + by)
clearActiveChatFilterIfNeeded()
}
@ -1099,6 +1145,28 @@ enum class ChatType(val type: String) {
ContactConnection(":");
}
sealed class GroupChatScope {
class MemberSupport(val groupMemberId_: Long?): GroupChatScope()
}
fun sameChatScope(scope1: GroupChatScope, scope2: GroupChatScope) =
scope1 is GroupChatScope.MemberSupport
&& scope2 is GroupChatScope.MemberSupport
&& scope1.groupMemberId_ == scope2.groupMemberId_
@Serializable
sealed class GroupChatScopeInfo {
@Serializable @SerialName("memberSupport") data class MemberSupport(val groupMember_: GroupMember?) : GroupChatScopeInfo()
fun toChatScope(): GroupChatScope =
when (this) {
is MemberSupport -> when (groupMember_) {
null -> GroupChatScope.MemberSupport(groupMemberId_ = null)
else -> GroupChatScope.MemberSupport(groupMemberId_ = groupMember_.groupMemberId)
}
}
}
@Serializable
data class User(
val remoteHostId: Long?,
@ -1204,8 +1272,6 @@ interface SomeChat {
val apiId: Long
val ready: Boolean
val chatDeleted: Boolean
val userCantSendReason: Pair<String, String?>?
val sendMsgEnabled: Boolean
val incognito: Boolean
fun featureEnabled(feature: ChatFeature): Boolean
val timedMessagesTTL: Int?
@ -1237,6 +1303,16 @@ data class Chat(
val id: String get() = chatInfo.id
val supportUnreadCount: Int get() = when (chatInfo) {
is ChatInfo.Group ->
if (chatInfo.groupInfo.canModerate) {
chatInfo.groupInfo.membersRequireAttention
} else {
chatInfo.groupInfo.membership.supportChat?.unread ?: 0
}
else -> 0
}
fun groupFeatureEnabled(feature: GroupFeature): Boolean =
if (chatInfo is ChatInfo.Group) {
chatInfo.groupInfo.groupFeatureEnabled(feature)
@ -1275,8 +1351,6 @@ sealed class ChatInfo: SomeChat, NamedChat {
override val apiId get() = contact.apiId
override val ready get() = contact.ready
override val chatDeleted get() = contact.chatDeleted
override val userCantSendReason get() = contact.userCantSendReason
override val sendMsgEnabled get() = contact.sendMsgEnabled
override val incognito get() = contact.incognito
override fun featureEnabled(feature: ChatFeature) = contact.featureEnabled(feature)
override val timedMessagesTTL: Int? get() = contact.timedMessagesTTL
@ -1294,15 +1368,13 @@ sealed class ChatInfo: SomeChat, NamedChat {
}
@Serializable @SerialName("group")
data class Group(val groupInfo: GroupInfo): ChatInfo() {
data class Group(val groupInfo: GroupInfo, val groupChatScope: GroupChatScopeInfo?): ChatInfo() {
override val chatType get() = ChatType.Group
override val localDisplayName get() = groupInfo.localDisplayName
override val id get() = groupInfo.id
override val apiId get() = groupInfo.apiId
override val ready get() = groupInfo.ready
override val chatDeleted get() = groupInfo.chatDeleted
override val userCantSendReason get() = groupInfo.userCantSendReason
override val sendMsgEnabled get() = groupInfo.sendMsgEnabled
override val incognito get() = groupInfo.incognito
override fun featureEnabled(feature: ChatFeature) = groupInfo.featureEnabled(feature)
override val timedMessagesTTL: Int? get() = groupInfo.timedMessagesTTL
@ -1314,7 +1386,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
override val localAlias get() = groupInfo.localAlias
companion object {
val sampleData = Group(GroupInfo.sampleData)
val sampleData = Group(GroupInfo.sampleData, groupChatScope = null)
}
}
@ -1326,8 +1398,6 @@ sealed class ChatInfo: SomeChat, NamedChat {
override val apiId get() = noteFolder.apiId
override val ready get() = noteFolder.ready
override val chatDeleted get() = noteFolder.chatDeleted
override val userCantSendReason get() = noteFolder.userCantSendReason
override val sendMsgEnabled get() = noteFolder.sendMsgEnabled
override val incognito get() = noteFolder.incognito
override fun featureEnabled(feature: ChatFeature) = noteFolder.featureEnabled(feature)
override val timedMessagesTTL: Int? get() = noteFolder.timedMessagesTTL
@ -1351,8 +1421,6 @@ sealed class ChatInfo: SomeChat, NamedChat {
override val apiId get() = contactRequest.apiId
override val ready get() = contactRequest.ready
override val chatDeleted get() = contactRequest.chatDeleted
override val userCantSendReason get() = contactRequest.userCantSendReason
override val sendMsgEnabled get() = contactRequest.sendMsgEnabled
override val incognito get() = contactRequest.incognito
override fun featureEnabled(feature: ChatFeature) = contactRequest.featureEnabled(feature)
override val timedMessagesTTL: Int? get() = contactRequest.timedMessagesTTL
@ -1376,8 +1444,6 @@ sealed class ChatInfo: SomeChat, NamedChat {
override val apiId get() = contactConnection.apiId
override val ready get() = contactConnection.ready
override val chatDeleted get() = contactConnection.chatDeleted
override val userCantSendReason get() = contactConnection.userCantSendReason
override val sendMsgEnabled get() = contactConnection.sendMsgEnabled
override val incognito get() = contactConnection.incognito
override fun featureEnabled(feature: ChatFeature) = contactConnection.featureEnabled(feature)
override val timedMessagesTTL: Int? get() = contactConnection.timedMessagesTTL
@ -1406,8 +1472,6 @@ sealed class ChatInfo: SomeChat, NamedChat {
override val id get() = "?$apiId"
override val ready get() = false
override val chatDeleted get() = false
override val userCantSendReason get() = generalGetString(MR.strings.cant_send_message_generic) to null
override val sendMsgEnabled get() = false
override val incognito get() = false
override fun featureEnabled(feature: ChatFeature) = false
override val timedMessagesTTL: Int? get() = null
@ -1422,6 +1486,71 @@ sealed class ChatInfo: SomeChat, NamedChat {
}
}
val userCantSendReason: Pair<String, String?>?
get() {
when (this) {
is Direct -> {
// TODO [short links] this will have additional statuses for pending contact requests before they are accepted
if (contact.nextSendGrpInv) return null
if (!contact.active) return generalGetString(MR.strings.cant_send_message_contact_deleted) to null
if (!contact.sndReady) return generalGetString(MR.strings.cant_send_message_contact_not_ready) to null
if (contact.activeConn?.connectionStats?.ratchetSyncSendProhibited == true) return generalGetString(MR.strings.cant_send_message_contact_not_synchronized) to null
if (contact.activeConn?.connDisabled == true) return generalGetString(MR.strings.cant_send_message_contact_disabled) to null
return null
}
is Group -> {
if (groupInfo.membership.memberActive) {
when (groupChatScope) {
null -> {
if (groupInfo.membership.memberPending) {
return generalGetString(MR.strings.reviewed_by_admins) to generalGetString(MR.strings.observer_cant_send_message_desc)
}
if (groupInfo.membership.memberRole == GroupMemberRole.Observer) {
return generalGetString(MR.strings.observer_cant_send_message_title) to generalGetString(MR.strings.observer_cant_send_message_desc)
}
return null
}
is GroupChatScopeInfo.MemberSupport ->
if (groupChatScope.groupMember_ != null) {
if (
groupChatScope.groupMember_.versionRange.maxVersion < GROUP_KNOCKING_VERSION
&& !groupChatScope.groupMember_.memberPending
) {
return generalGetString(MR.strings.cant_send_message_member_has_old_version) to null
}
return null
} else {
return null
}
}
} else {
return when (groupInfo.membership.memberStatus) {
GroupMemberStatus.MemRejected -> generalGetString(MR.strings.cant_send_message_rejected) to null
GroupMemberStatus.MemGroupDeleted -> generalGetString(MR.strings.cant_send_message_group_deleted) to null
GroupMemberStatus.MemRemoved -> generalGetString(MR.strings.cant_send_message_mem_removed) to null
GroupMemberStatus.MemLeft -> generalGetString(MR.strings.cant_send_message_you_left) to null
else -> generalGetString(MR.strings.cant_send_message_generic) to null
}
}
}
is Local ->
return null
is ContactRequest ->
return generalGetString(MR.strings.cant_send_message_generic) to null
is ContactConnection ->
return generalGetString(MR.strings.cant_send_message_generic) to null
is InvalidJSON ->
return generalGetString(MR.strings.cant_send_message_generic) to null
}
}
val sendMsgEnabled get() = userCantSendReason == null
fun groupChatScope(): GroupChatScope? = when (this) {
is Group -> groupChatScope?.toChatScope()
else -> null
}
fun ntfsEnabled(ci: ChatItem): Boolean =
ntfsEnabled(ci.meta.userMention)
@ -1465,7 +1594,13 @@ sealed class ChatInfo: SomeChat, NamedChat {
is Direct -> contact.activeConn == null && contact.profile.contactLink != null && contact.active
else -> false
}
}
val groupInfo_: GroupInfo?
get() = when (this) {
is Group -> groupInfo
else -> null
}
}
@Serializable
sealed class NetworkStatus {
@ -1519,17 +1654,6 @@ data class Contact(
override val ready get() = activeConn?.connStatus == ConnStatus.Ready
val sndReady get() = ready || activeConn?.connStatus == ConnStatus.SndReady
val active get() = contactStatus == ContactStatus.Active
override val userCantSendReason: Pair<String, String?>?
get() {
// TODO [short links] this will have additional statuses for pending contact requests before they are accepted
if (nextSendGrpInv) return null
if (!active) return generalGetString(MR.strings.cant_send_message_contact_deleted) to null
if (!sndReady) return generalGetString(MR.strings.cant_send_message_contact_not_ready) to null
if (activeConn?.connectionStats?.ratchetSyncSendProhibited == true) return generalGetString(MR.strings.cant_send_message_contact_not_synchronized) to null
if (activeConn?.connDisabled == true) return generalGetString(MR.strings.cant_send_message_contact_disabled) to null
return null
}
override val sendMsgEnabled get() = userCantSendReason == null
val nextSendGrpInv get() = contactGroupMemberId != null && !contactGrpInvSent
override val incognito get() = contactConnIncognito
override fun featureEnabled(feature: ChatFeature) = when (feature) {
@ -1754,6 +1878,7 @@ data class GroupInfo (
override val updatedAt: Instant,
val chatTs: Instant?,
val uiThemes: ThemeModeOverrides? = null,
val membersRequireAttention: Int,
val chatTags: List<Long>,
val chatItemTTL: Long?,
override val localAlias: String,
@ -1763,23 +1888,6 @@ data class GroupInfo (
override val apiId get() = groupId
override val ready get() = membership.memberActive
override val chatDeleted get() = false
override val userCantSendReason: Pair<String, String?>? get() =
if (membership.memberActive) {
if (membership.memberRole == GroupMemberRole.Observer) {
generalGetString(MR.strings.observer_cant_send_message_title) to generalGetString(MR.strings.observer_cant_send_message_desc)
} else {
null
}
} else {
when (membership.memberStatus) {
GroupMemberStatus.MemRejected -> generalGetString(MR.strings.cant_send_message_rejected) to null
GroupMemberStatus.MemGroupDeleted -> generalGetString(MR.strings.cant_send_message_group_deleted) to null
GroupMemberStatus.MemRemoved -> generalGetString(MR.strings.cant_send_message_mem_removed) to null
GroupMemberStatus.MemLeft -> generalGetString(MR.strings.cant_send_message_you_left) to null
else -> generalGetString(MR.strings.cant_send_message_generic) to null
}
}
override val sendMsgEnabled get() = userCantSendReason == null
override val incognito get() = membership.memberIncognito
override fun featureEnabled(feature: ChatFeature) = when (feature) {
ChatFeature.TimedMessages -> fullGroupPreferences.timedMessages.on
@ -1797,7 +1905,7 @@ data class GroupInfo (
get() = membership.memberRole == GroupMemberRole.Owner && membership.memberCurrent
val canDelete: Boolean
get() = membership.memberRole == GroupMemberRole.Owner || !membership.memberCurrent
get() = membership.memberRole == GroupMemberRole.Owner || !membership.memberCurrentOrPending
val canAddMembers: Boolean
get() = membership.memberRole >= GroupMemberRole.Admin && membership.memberActive
@ -1832,6 +1940,7 @@ data class GroupInfo (
updatedAt = Clock.System.now(),
chatTs = Clock.System.now(),
uiThemes = null,
membersRequireAttention = 0,
chatTags = emptyList(),
localAlias = "",
chatItemTTL = null
@ -1849,7 +1958,8 @@ data class GroupProfile (
val description: String? = null,
override val image: String? = null,
override val localAlias: String = "",
val groupPreferences: GroupPreferences? = null
val groupPreferences: GroupPreferences? = null,
val memberAdmission: GroupMemberAdmission? = null
): NamedChat {
companion object {
val sampleData = GroupProfile(
@ -1859,6 +1969,27 @@ data class GroupProfile (
}
}
@Serializable
data class GroupMemberAdmission(
val review: MemberCriteria? = null,
) {
companion object {
val sampleData = GroupMemberAdmission(
review = null,
)
}
}
@Serializable
enum class MemberCriteria {
@SerialName("all") All;
val text: String
get() = when(this) {
MemberCriteria.All -> generalGetString(MR.strings.member_criteria_all)
}
}
@Serializable
data class BusinessChatInfo (
val chatType: BusinessChatType,
@ -1887,7 +2018,9 @@ data class GroupMember (
val memberProfile: LocalProfile,
val memberContactId: Long? = null,
val memberContactProfileId: Long,
var activeConn: Connection? = null
var activeConn: Connection? = null,
val supportChat: GroupSupportChat? = null,
val memberChatVRange: VersionRange
): NamedChat {
val id: String get() = "#$groupId @$groupMemberId"
val ready get() = activeConn?.connStatus == ConnStatus.Ready
@ -1946,6 +2079,7 @@ data class GroupMember (
GroupMemberStatus.MemUnknown -> false
GroupMemberStatus.MemInvited -> false
GroupMemberStatus.MemPendingApproval -> true
GroupMemberStatus.MemPendingReview -> true
GroupMemberStatus.MemIntroduced -> false
GroupMemberStatus.MemIntroInvited -> false
GroupMemberStatus.MemAccepted -> false
@ -1963,6 +2097,7 @@ data class GroupMember (
GroupMemberStatus.MemUnknown -> false
GroupMemberStatus.MemInvited -> false
GroupMemberStatus.MemPendingApproval -> false
GroupMemberStatus.MemPendingReview -> false
GroupMemberStatus.MemIntroduced -> true
GroupMemberStatus.MemIntroInvited -> true
GroupMemberStatus.MemAccepted -> true
@ -1972,6 +2107,15 @@ data class GroupMember (
GroupMemberStatus.MemCreator -> true
}
val memberPending: Boolean get() = when (this.memberStatus) {
GroupMemberStatus.MemPendingApproval -> true
GroupMemberStatus.MemPendingReview -> true
else -> false
}
val memberCurrentOrPending: Boolean get() =
memberCurrent || memberPending
fun canBeRemoved(groupInfo: GroupInfo): Boolean {
val userRole = groupInfo.membership.memberRole
return memberStatus != GroupMemberStatus.MemRemoved && memberStatus != GroupMemberStatus.MemLeft
@ -1990,6 +2134,8 @@ data class GroupMember (
&& userRole >= GroupMemberRole.Moderator && userRole >= memberRole && groupInfo.membership.memberActive
}
val versionRange: VersionRange = activeConn?.peerChatVRange ?: memberChatVRange
val memberIncognito = memberProfile.profileId != memberContactProfileId
companion object {
@ -2007,11 +2153,20 @@ data class GroupMember (
memberProfile = LocalProfile.sampleData,
memberContactId = 1,
memberContactProfileId = 1L,
activeConn = Connection.sampleData
activeConn = Connection.sampleData,
memberChatVRange = VersionRange(minVersion = 1, maxVersion = 15)
)
}
}
@Serializable
class GroupSupportChat (
val chatTs: Instant,
val unread: Int,
val memberAttention: Int,
val mentions: Int
)
@Serializable
data class GroupMemberSettings(val showMessages: Boolean) {}
@ -2068,6 +2223,7 @@ enum class GroupMemberStatus {
@SerialName("unknown") MemUnknown,
@SerialName("invited") MemInvited,
@SerialName("pending_approval") MemPendingApproval,
@SerialName("pending_review") MemPendingReview,
@SerialName("introduced") MemIntroduced,
@SerialName("intro-inv") MemIntroInvited,
@SerialName("accepted") MemAccepted,
@ -2084,6 +2240,7 @@ enum class GroupMemberStatus {
MemUnknown -> generalGetString(MR.strings.group_member_status_unknown)
MemInvited -> generalGetString(MR.strings.group_member_status_invited)
MemPendingApproval -> generalGetString(MR.strings.group_member_status_pending_approval)
MemPendingReview -> generalGetString(MR.strings.group_member_status_pending_review)
MemIntroduced -> generalGetString(MR.strings.group_member_status_introduced)
MemIntroInvited -> generalGetString(MR.strings.group_member_status_intro_invitation)
MemAccepted -> generalGetString(MR.strings.group_member_status_accepted)
@ -2101,6 +2258,7 @@ enum class GroupMemberStatus {
MemUnknown -> generalGetString(MR.strings.group_member_status_unknown_short)
MemInvited -> generalGetString(MR.strings.group_member_status_invited)
MemPendingApproval -> generalGetString(MR.strings.group_member_status_pending_approval_short)
MemPendingReview -> generalGetString(MR.strings.group_member_status_pending_review_short)
MemIntroduced -> generalGetString(MR.strings.group_member_status_connecting)
MemIntroInvited -> generalGetString(MR.strings.group_member_status_connecting)
MemAccepted -> generalGetString(MR.strings.group_member_status_connecting)
@ -2155,8 +2313,6 @@ class NoteFolder(
override val apiId get() = noteFolderId
override val chatDeleted get() = false
override val ready get() = true
override val userCantSendReason: Pair<String, String?>? = null
override val sendMsgEnabled get() = true
override val incognito get() = false
override fun featureEnabled(feature: ChatFeature) = feature == ChatFeature.Voice
override val timedMessagesTTL: Int? get() = null
@ -2192,8 +2348,6 @@ class UserContactRequest (
override val apiId get() = contactRequestId
override val chatDeleted get() = false
override val ready get() = true
override val userCantSendReason = generalGetString(MR.strings.cant_send_message_generic) to null
override val sendMsgEnabled get() = false
override val incognito get() = false
override fun featureEnabled(feature: ChatFeature) = false
override val timedMessagesTTL: Int? get() = null
@ -2232,8 +2386,6 @@ class PendingContactConnection(
override val apiId get() = pccConnId
override val chatDeleted get() = false
override val ready get() = false
override val userCantSendReason = generalGetString(MR.strings.cant_send_message_generic) to null
override val sendMsgEnabled get() = false
override val incognito get() = customUserProfileId != null
override fun featureEnabled(feature: ChatFeature) = false
override val timedMessagesTTL: Int? get() = null
@ -2451,11 +2603,16 @@ data class ChatItem (
is CIContent.RcvGroupFeature,
is CIContent.SndGroupFeature -> CIMergeCategory.ChatFeature
is CIContent.RcvGroupEventContent -> when (content.rcvGroupEvent) {
is RcvGroupEvent.UserRole, is RcvGroupEvent.UserDeleted, is RcvGroupEvent.GroupDeleted, is RcvGroupEvent.MemberCreatedContact -> null
is RcvGroupEvent.UserRole,
is RcvGroupEvent.UserDeleted,
is RcvGroupEvent.GroupDeleted,
is RcvGroupEvent.MemberCreatedContact,
is RcvGroupEvent.NewMemberPendingReview ->
null
else -> CIMergeCategory.RcvGroupEvent
}
is CIContent.SndGroupEventContent -> when (content.sndGroupEvent) {
is SndGroupEvent.UserRole, is SndGroupEvent.UserLeft -> null
is SndGroupEvent.UserRole, is SndGroupEvent.UserLeft, is SndGroupEvent.MemberAccepted, is SndGroupEvent.UserPendingReview -> null
else -> CIMergeCategory.SndGroupEvent
}
else -> {
@ -2526,6 +2683,8 @@ data class ChatItem (
is CIContent.RcvGroupEventContent -> when (content.rcvGroupEvent) {
is RcvGroupEvent.MemberAdded -> false
is RcvGroupEvent.MemberConnected -> false
is RcvGroupEvent.MemberAccepted -> false
is RcvGroupEvent.UserAccepted -> false
is RcvGroupEvent.MemberLeft -> false
is RcvGroupEvent.MemberRole -> false
is RcvGroupEvent.MemberBlocked -> false
@ -2537,6 +2696,7 @@ data class ChatItem (
is RcvGroupEvent.InvitedViaGroupLink -> false
is RcvGroupEvent.MemberCreatedContact -> false
is RcvGroupEvent.MemberProfileUpdated -> false
is RcvGroupEvent.NewMemberPendingReview -> true
}
is CIContent.SndGroupEventContent -> false
is CIContent.RcvConnEventContent -> false
@ -2718,6 +2878,11 @@ data class ChatItem (
}
}
sealed class SecondaryContextFilter {
class GroupChatScopeContext(val groupScopeInfo: GroupChatScopeInfo): SecondaryContextFilter()
class MsgContentTagContext(val contentTag: MsgContentTag): SecondaryContextFilter()
}
fun MutableState<SnapshotStateList<Chat>>.add(index: Int, elem: Chat) {
value = SnapshotStateList<Chat>().apply { addAll(value); add(index, elem) }
}
@ -2976,6 +3141,19 @@ sealed class CIStatus {
@Serializable @SerialName("rcvRead") class RcvRead: CIStatus()
@Serializable @SerialName("invalid") class Invalid(val text: String): CIStatus()
// as in corresponds to SENT response from agent
fun isSent(): Boolean = when(this) {
is SndNew -> false
is SndSent -> true
is SndRcvd -> false
is SndErrorAuth -> true
is CISSndError -> true
is SndWarning -> true
is RcvNew -> false
is RcvRead -> false
is Invalid -> false
}
fun statusIcon(
primaryColor: Color,
metaColor: Color = CurrentColors.value.colors.secondary,
@ -3015,6 +3193,13 @@ sealed class CIStatus {
}
}
fun shouldKeepOldSndCIStatus(oldStatus: CIStatus, newStatus: CIStatus): Boolean =
when {
oldStatus is CIStatus.SndRcvd && newStatus !is CIStatus.SndRcvd -> true
oldStatus.isSent() && newStatus is CIStatus.SndNew -> true
else -> false
}
@Serializable
sealed class SndError {
@Serializable @SerialName("auth") class Auth: SndError()
@ -4153,6 +4338,8 @@ sealed class RcvDirectEvent() {
sealed class RcvGroupEvent() {
@Serializable @SerialName("memberAdded") class MemberAdded(val groupMemberId: Long, val profile: Profile): RcvGroupEvent()
@Serializable @SerialName("memberConnected") class MemberConnected(): RcvGroupEvent()
@Serializable @SerialName("memberAccepted") class MemberAccepted(val groupMemberId: Long, val profile: Profile): RcvGroupEvent()
@Serializable @SerialName("userAccepted") class UserAccepted(): RcvGroupEvent()
@Serializable @SerialName("memberLeft") class MemberLeft(): RcvGroupEvent()
@Serializable @SerialName("memberRole") class MemberRole(val groupMemberId: Long, val profile: Profile, val role: GroupMemberRole): RcvGroupEvent()
@Serializable @SerialName("memberBlocked") class MemberBlocked(val groupMemberId: Long, val profile: Profile, val blocked: Boolean): RcvGroupEvent()
@ -4164,10 +4351,13 @@ sealed class RcvGroupEvent() {
@Serializable @SerialName("invitedViaGroupLink") class InvitedViaGroupLink(): RcvGroupEvent()
@Serializable @SerialName("memberCreatedContact") class MemberCreatedContact(): RcvGroupEvent()
@Serializable @SerialName("memberProfileUpdated") class MemberProfileUpdated(val fromProfile: Profile, val toProfile: Profile): RcvGroupEvent()
@Serializable @SerialName("newMemberPendingReview") class NewMemberPendingReview(): RcvGroupEvent()
val text: String get() = when (this) {
is MemberAdded -> String.format(generalGetString(MR.strings.rcv_group_event_member_added), profile.profileViewName)
is MemberConnected -> generalGetString(MR.strings.rcv_group_event_member_connected)
is MemberAccepted -> String.format(generalGetString(MR.strings.rcv_group_event_member_accepted), profile.profileViewName)
is UserAccepted -> generalGetString(MR.strings.rcv_group_event_user_accepted)
is MemberLeft -> generalGetString(MR.strings.rcv_group_event_member_left)
is MemberRole -> String.format(generalGetString(MR.strings.rcv_group_event_changed_member_role), profile.profileViewName, role.text)
is MemberBlocked -> if (blocked) {
@ -4183,6 +4373,7 @@ sealed class RcvGroupEvent() {
is InvitedViaGroupLink -> generalGetString(MR.strings.rcv_group_event_invited_via_your_group_link)
is MemberCreatedContact -> generalGetString(MR.strings.rcv_group_event_member_created_contact)
is MemberProfileUpdated -> profileUpdatedText(fromProfile, toProfile)
is NewMemberPendingReview -> generalGetString(MR.strings.rcv_group_event_new_member_pending_review)
}
private fun profileUpdatedText(from: Profile, to: Profile): String =
@ -4207,6 +4398,8 @@ sealed class SndGroupEvent() {
@Serializable @SerialName("memberDeleted") class MemberDeleted(val groupMemberId: Long, val profile: Profile): SndGroupEvent()
@Serializable @SerialName("userLeft") class UserLeft(): SndGroupEvent()
@Serializable @SerialName("groupUpdated") class GroupUpdated(val groupProfile: GroupProfile): SndGroupEvent()
@Serializable @SerialName("memberAccepted") class MemberAccepted(val groupMemberId: Long, val profile: Profile): SndGroupEvent()
@Serializable @SerialName("userPendingReview") class UserPendingReview(): SndGroupEvent()
val text: String get() = when (this) {
is MemberRole -> String.format(generalGetString(MR.strings.snd_group_event_changed_member_role), profile.profileViewName, role.text)
@ -4219,6 +4412,8 @@ sealed class SndGroupEvent() {
is MemberDeleted -> String.format(generalGetString(MR.strings.snd_group_event_member_deleted), profile.profileViewName)
is UserLeft -> generalGetString(MR.strings.snd_group_event_user_left)
is GroupUpdated -> generalGetString(MR.strings.snd_group_event_group_profile_updated)
is MemberAccepted -> generalGetString(MR.strings.snd_group_event_member_accepted)
is UserPendingReview -> generalGetString(MR.strings.snd_group_event_user_pending_review)
}
}

View file

@ -58,6 +58,9 @@ typealias ChatCtrl = Long
// version range that supports establishing direct connection with a group member (xGrpDirectInvVRange in core)
val CREATE_MEMBER_CONTACT_VERSION = 2
// support group knocking (MsgScope)
val GROUP_KNOCKING_VERSION = 15
enum class CallOnLockScreen {
DISABLE,
SHOW,
@ -163,6 +166,7 @@ class AppPreferences {
val oneHandUICardShown = mkBoolPreference(SHARED_PREFS_ONE_HAND_UI_CARD_SHOWN, false)
val addressCreationCardShown = mkBoolPreference(SHARED_PREFS_ADDRESS_CREATION_CARD_SHOWN, false)
val showMuteProfileAlert = mkBoolPreference(SHARED_PREFS_SHOW_MUTE_PROFILE_ALERT, true)
val showReportsInSupportChatAlert = mkBoolPreference(SHARED_PREFS_SHOW_REPORTS_IN_SUPPORT_CHAT_ALERT, true)
val appLanguage = mkStrPreference(SHARED_PREFS_APP_LANGUAGE, null)
val appUpdateChannel = mkEnumPreference(SHARED_PREFS_APP_UPDATE_CHANNEL, AppUpdatesChannel.DISABLED) { AppUpdatesChannel.entries.firstOrNull { it.name == this } }
val appSkippedUpdate = mkStrPreference(SHARED_PREFS_APP_SKIPPED_UPDATE, "")
@ -245,6 +249,7 @@ class AppPreferences {
liveMessageAlertShown to false,
showHiddenProfilesNotice to true,
showMuteProfileAlert to true,
showReportsInSupportChatAlert to true,
showDeleteConversationNotice to true,
showDeleteContactNotice to true,
)
@ -415,6 +420,7 @@ class AppPreferences {
private const val SHARED_PREFS_ONE_HAND_UI_CARD_SHOWN = "OneHandUICardShown"
private const val SHARED_PREFS_ADDRESS_CREATION_CARD_SHOWN = "AddressCreationCardShown"
private const val SHARED_PREFS_SHOW_MUTE_PROFILE_ALERT = "ShowMuteProfileAlert"
private const val SHARED_PREFS_SHOW_REPORTS_IN_SUPPORT_CHAT_ALERT = "ShowReportsInSupportChatAlert"
private const val SHARED_PREFS_STORE_DB_PASSPHRASE = "StoreDBPassphrase"
private const val SHARED_PREFS_INITIAL_RANDOM_DB_PASSPHRASE = "InitialRandomDBPassphrase"
private const val SHARED_PREFS_ENCRYPTED_DB_PASSPHRASE = "EncryptedDBPassphrase"
@ -882,8 +888,8 @@ object ChatController {
return null
}
suspend fun apiGetChat(rh: Long?, type: ChatType, id: Long, contentTag: MsgContentTag? = null, pagination: ChatPagination, search: String = ""): Pair<Chat, NavigationInfo>? {
val r = sendCmd(rh, CC.ApiGetChat(type, id, contentTag, pagination, search))
suspend fun apiGetChat(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?, contentTag: MsgContentTag? = null, pagination: ChatPagination, search: String = ""): Pair<Chat, NavigationInfo>? {
val r = sendCmd(rh, CC.ApiGetChat(type, id, scope, contentTag, pagination, search))
if (r is API.Result && r.res is CR.ApiChat) return if (rh == null) r.res.chat to r.res.navInfo else r.res.chat.copy(remoteHostId = rh) to r.res.navInfo
Log.e(TAG, "apiGetChat bad response: ${r.responseType} ${r.details}")
val e = (r as? API.Error)?.err
@ -917,8 +923,8 @@ object ChatController {
suspend fun apiReorderChatTags(rh: Long?, tagIds: List<Long>) = sendCommandOkResp(rh, CC.ApiReorderChatTags(tagIds))
suspend fun apiSendMessages(rh: Long?, type: ChatType, id: Long, live: Boolean = false, ttl: Int? = null, composedMessages: List<ComposedMessage>): List<AChatItem>? {
val cmd = CC.ApiSendMessages(type, id, live, ttl, composedMessages)
suspend fun apiSendMessages(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?, live: Boolean = false, ttl: Int? = null, composedMessages: List<ComposedMessage>): List<AChatItem>? {
val cmd = CC.ApiSendMessages(type, id, scope, live, ttl, composedMessages)
return processSendMessageCmd(rh, cmd)
}
@ -969,27 +975,27 @@ object ChatController {
return null
}
suspend fun apiGetChatItemInfo(rh: Long?, type: ChatType, id: Long, itemId: Long): ChatItemInfo? {
val r = sendCmd(rh, CC.ApiGetChatItemInfo(type, id, itemId))
suspend fun apiGetChatItemInfo(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?, itemId: Long): ChatItemInfo? {
val r = sendCmd(rh, CC.ApiGetChatItemInfo(type, id, scope, itemId))
if (r is API.Result && r.res is CR.ApiChatItemInfo) return r.res.chatItemInfo
apiErrorAlert("apiGetChatItemInfo", generalGetString(MR.strings.error_loading_details), r)
return null
}
suspend fun apiForwardChatItems(rh: Long?, toChatType: ChatType, toChatId: Long, fromChatType: ChatType, fromChatId: Long, itemIds: List<Long>, ttl: Int?): List<ChatItem>? {
val cmd = CC.ApiForwardChatItems(toChatType, toChatId, fromChatType, fromChatId, itemIds, ttl)
suspend fun apiForwardChatItems(rh: Long?, toChatType: ChatType, toChatId: Long, toScope: GroupChatScope?, fromChatType: ChatType, fromChatId: Long, fromScope: GroupChatScope?, itemIds: List<Long>, ttl: Int?): List<ChatItem>? {
val cmd = CC.ApiForwardChatItems(toChatType, toChatId, toScope, fromChatType, fromChatId, fromScope, itemIds, ttl)
return processSendMessageCmd(rh, cmd)?.map { it.chatItem }
}
suspend fun apiPlanForwardChatItems(rh: Long?, fromChatType: ChatType, fromChatId: Long, chatItemIds: List<Long>): CR.ForwardPlan? {
val r = sendCmd(rh, CC.ApiPlanForwardChatItems(fromChatType, fromChatId, chatItemIds))
suspend fun apiPlanForwardChatItems(rh: Long?, fromChatType: ChatType, fromChatId: Long, fromScope: GroupChatScope?, chatItemIds: List<Long>): CR.ForwardPlan? {
val r = sendCmd(rh, CC.ApiPlanForwardChatItems(fromChatType, fromChatId, fromScope, chatItemIds))
if (r is API.Result && r.res is CR.ForwardPlan) return r.res
apiErrorAlert("apiPlanForwardChatItems", generalGetString(MR.strings.error_forwarding_messages), r)
return null
}
suspend fun apiUpdateChatItem(rh: Long?, type: ChatType, id: Long, itemId: Long, updatedMessage: UpdatedMessage, live: Boolean = false): AChatItem? {
val r = sendCmd(rh, CC.ApiUpdateChatItem(type, id, itemId, updatedMessage, live))
suspend fun apiUpdateChatItem(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?, itemId: Long, updatedMessage: UpdatedMessage, live: Boolean = false): AChatItem? {
val r = sendCmd(rh, CC.ApiUpdateChatItem(type, id, scope, itemId, updatedMessage, live))
when {
r is API.Result && r.res is CR.ChatItemUpdated -> return r.res.chatItem
r is API.Result && r.res is CR.ChatItemNotChanged -> return r.res.chatItem
@ -1011,8 +1017,8 @@ object ChatController {
return null
}
suspend fun apiChatItemReaction(rh: Long?, type: ChatType, id: Long, itemId: Long, add: Boolean, reaction: MsgReaction): ChatItem? {
val r = sendCmd(rh, CC.ApiChatItemReaction(type, id, itemId, add, reaction))
suspend fun apiChatItemReaction(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?, itemId: Long, add: Boolean, reaction: MsgReaction): ChatItem? {
val r = sendCmd(rh, CC.ApiChatItemReaction(type, id, scope, itemId, add, reaction))
if (r is API.Result && r.res is CR.ChatItemReaction) return r.res.reaction.chatReaction.chatItem
Log.e(TAG, "apiUpdateChatItem bad response: ${r.responseType} ${r.details}")
return null
@ -1026,8 +1032,8 @@ object ChatController {
return null
}
suspend fun apiDeleteChatItems(rh: Long?, type: ChatType, id: Long, itemIds: List<Long>, mode: CIDeleteMode): List<ChatItemDeletion>? {
val r = sendCmd(rh, CC.ApiDeleteChatItem(type, id, itemIds, mode))
suspend fun apiDeleteChatItems(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?, itemIds: List<Long>, mode: CIDeleteMode): List<ChatItemDeletion>? {
val r = sendCmd(rh, CC.ApiDeleteChatItem(type, id, scope, itemIds, mode))
if (r is API.Result && r.res is CR.ChatItemsDeleted) return r.res.chatItemDeletions
Log.e(TAG, "apiDeleteChatItem bad response: ${r.responseType} ${r.details}")
return null
@ -1659,18 +1665,18 @@ object ChatController {
return null
}
suspend fun apiChatRead(rh: Long?, type: ChatType, id: Long): Boolean {
val r = sendCmd(rh, CC.ApiChatRead(type, id))
suspend fun apiChatRead(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?): Boolean {
val r = sendCmd(rh, CC.ApiChatRead(type, id, scope))
if (r.result is CR.CmdOk) return true
Log.e(TAG, "apiChatRead bad response: ${r.responseType} ${r.details}")
return false
}
suspend fun apiChatItemsRead(rh: Long?, type: ChatType, id: Long, itemIds: List<Long>): Boolean {
val r = sendCmd(rh, CC.ApiChatItemsRead(type, id, itemIds))
if (r.result is CR.CmdOk) return true
suspend fun apiChatItemsRead(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?, itemIds: List<Long>): ChatInfo? {
val r = sendCmd(rh, CC.ApiChatItemsRead(type, id, scope, itemIds))
if (r is API.Result && r.res is CR.ItemsReadForChat) return r.res.chatInfo
Log.e(TAG, "apiChatItemsRead bad response: ${r.responseType} ${r.details}")
return false
return null
}
suspend fun apiChatUnread(rh: Long?, type: ChatType, id: Long, unreadChat: Boolean): Boolean {
@ -1899,9 +1905,25 @@ object ChatController {
}
}
suspend fun apiRemoveMembers(rh: Long?, groupId: Long, memberIds: List<Long>, withMessages: Boolean = false): List<GroupMember>? {
suspend fun apiAcceptMember(rh: Long?, groupId: Long, groupMemberId: Long, memberRole: GroupMemberRole): Pair<GroupInfo, GroupMember>? {
val r = sendCmd(rh, CC.ApiAcceptMember(groupId, groupMemberId, memberRole))
if (r is API.Result && r.res is CR.MemberAccepted) return r.res.groupInfo to r.res.member
if (!(networkErrorAlert(r))) {
apiErrorAlert("apiAcceptMember", generalGetString(MR.strings.error_accepting_member), r)
}
return null
}
suspend fun apiDeleteMemberSupportChat(rh: Long?, groupId: Long, groupMemberId: Long): Pair<GroupInfo, GroupMember>? {
val r = sendCmd(rh, CC.ApiDeleteMemberSupportChat(groupId, groupMemberId))
if (r is API.Result && r.res is CR.MemberSupportChatDeleted) return r.res.groupInfo to r.res.member
apiErrorAlert("apiDeleteMemberSupportChat", generalGetString(MR.strings.error_deleting_member_support_chat), r)
return null
}
suspend fun apiRemoveMembers(rh: Long?, groupId: Long, memberIds: List<Long>, withMessages: Boolean = false): Pair<GroupInfo, List<GroupMember>>? {
val r = sendCmd(rh, CC.ApiRemoveMembers(groupId, memberIds, withMessages))
if (r is API.Result && r.res is CR.UserDeletedMembers) return r.res.members
if (r is API.Result && r.res is CR.UserDeletedMembers) return r.res.groupInfo to r.res.members
if (!(networkErrorAlert(r))) {
apiErrorAlert("apiRemoveMembers", generalGetString(MR.strings.error_removing_member), r)
}
@ -2420,9 +2442,7 @@ object ChatController {
}
}
withContext(Dispatchers.Main) {
if (cItem.isReport) {
chatModel.secondaryChatsContext.value?.addChatItem(rhId, cInfo, cItem)
}
chatModel.secondaryChatsContext.value?.addChatItem(rhId, cInfo, cItem)
}
} else if (cItem.isRcvNew && cInfo.ntfsEnabled(cItem)) {
withContext(Dispatchers.Main) {
@ -2448,12 +2468,10 @@ object ChatController {
val cItem = chatItem.chatItem
if (!cItem.isDeletedContent && active(r.user)) {
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateChatItem(cInfo, cItem, status = cItem.meta.itemStatus)
chatModel.chatsContext.upsertChatItem(rhId, cInfo, cItem)
}
withContext(Dispatchers.Main) {
if (cItem.isReport) {
chatModel.secondaryChatsContext.value?.updateChatItem(cInfo, cItem, status = cItem.meta.itemStatus)
}
chatModel.secondaryChatsContext.value?.upsertChatItem(rhId, cInfo, cItem)
}
}
}
@ -2465,9 +2483,7 @@ object ChatController {
chatModel.chatsContext.updateChatItem(r.reaction.chatInfo, r.reaction.chatReaction.chatItem)
}
withContext(Dispatchers.Main) {
if (r.reaction.chatReaction.chatItem.isReport) {
chatModel.secondaryChatsContext.value?.updateChatItem(r.reaction.chatInfo, r.reaction.chatReaction.chatItem)
}
chatModel.secondaryChatsContext.value?.updateChatItem(r.reaction.chatInfo, r.reaction.chatReaction.chatItem)
}
}
}
@ -2510,15 +2526,18 @@ object ChatController {
}
}
withContext(Dispatchers.Main) {
if (cItem.isReport) {
if (toChatItem == null) {
chatModel.secondaryChatsContext.value?.removeChatItem(rhId, cInfo, cItem)
} else {
chatModel.secondaryChatsContext.value?.upsertChatItem(rhId, cInfo, toChatItem.chatItem)
}
if (toChatItem == null) {
chatModel.secondaryChatsContext.value?.removeChatItem(rhId, cInfo, cItem)
} else {
chatModel.secondaryChatsContext.value?.upsertChatItem(rhId, cInfo, toChatItem.chatItem)
}
}
}
r.chatItemDeletions.lastOrNull()?.deletedChatItem?.chatInfo?.let { updatedChatInfo ->
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateChatInfo(rhId, updatedChatInfo)
}
}
}
is CR.GroupChatItemsDeleted -> {
groupChatItemsDeleted(rhId, r)
@ -2575,6 +2594,13 @@ object ChatController {
chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.member)
}
}
is CR.MemberAcceptedByOther ->
if (active(r.user)) {
withContext(Dispatchers.Main) {
chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.member)
chatModel.chatsContext.updateGroup(rhId, r.groupInfo)
}
}
is CR.DeletedMemberUser -> // TODO update user member
if (active(r.user)) {
withContext(Dispatchers.Main) {
@ -2592,6 +2618,7 @@ object ChatController {
is CR.DeletedMember ->
if (active(r.user)) {
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(rhId, r.groupInfo)
chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.deletedMember)
if (r.withMessages) {
chatModel.chatsContext.removeMemberItems(rhId, r.deletedMember, byMember = r.byMember, r.groupInfo)
@ -2607,6 +2634,7 @@ object ChatController {
is CR.LeftMember ->
if (active(r.user)) {
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(rhId, r.groupInfo)
chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.member)
}
withContext(Dispatchers.Main) {
@ -2655,6 +2683,16 @@ object ChatController {
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(rhId, r.groupInfo)
}
if (
chatModel.chatId.value == r.groupInfo.id
&& ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)
&& chatModel.secondaryChatsContext.value?.secondaryContextFilter is SecondaryContextFilter.GroupChatScopeContext
) {
withContext(Dispatchers.Main) {
chatModel.secondaryChatsContext.value = null
}
ModalManager.end.closeModals()
}
}
is CR.JoinedGroupMember ->
if (active(r.user)) {
@ -3016,11 +3054,11 @@ object ChatController {
if (activeUser(rh, user)) {
val cInfo = aChatItem.chatInfo
val cItem = aChatItem.chatItem
withContext(Dispatchers.Main) { chatModel.chatsContext.upsertChatItem(rh, cInfo, cItem) }
withContext(Dispatchers.Main) {
if (cItem.isReport) {
chatModel.secondaryChatsContext.value?.upsertChatItem(rh, cInfo, cItem)
}
chatModel.chatsContext.upsertChatItem(rh, cInfo, cItem)
}
withContext(Dispatchers.Main) {
chatModel.secondaryChatsContext.value?.upsertChatItem(rh, cInfo, cItem)
}
}
}
@ -3032,7 +3070,7 @@ object ChatController {
chatModel.users.addAll(users)
return
}
val cInfo = ChatInfo.Group(r.groupInfo)
val cInfo = ChatInfo.Group(r.groupInfo, groupChatScope = null) // TODO [knocking] get scope from items?
withContext(Dispatchers.Main) {
val chatsCtx = chatModel.chatsContext
r.chatItemIDs.forEach { itemId ->
@ -3087,11 +3125,11 @@ object ChatController {
if (!activeUser(rh, user)) {
notify()
} else {
val createdChat = withContext(Dispatchers.Main) { chatModel.chatsContext.upsertChatItem(rh, cInfo, cItem) }
val createdChat = withContext(Dispatchers.Main) {
chatModel.chatsContext.upsertChatItem(rh, cInfo, cItem)
}
withContext(Dispatchers.Main) {
if (cItem.content.msgContent is MsgContent.MCReport) {
chatModel.secondaryChatsContext.value?.upsertChatItem(rh, cInfo, cItem)
}
chatModel.secondaryChatsContext.value?.upsertChatItem(rh, cInfo, cItem)
}
if (createdChat) {
notify()
@ -3296,9 +3334,9 @@ sealed class CC {
class ApiGetSettings(val settings: AppSettings): CC()
class ApiGetChatTags(val userId: Long): CC()
class ApiGetChats(val userId: Long): CC()
class ApiGetChat(val type: ChatType, val id: Long, val contentTag: MsgContentTag?, val pagination: ChatPagination, val search: String = ""): CC()
class ApiGetChatItemInfo(val type: ChatType, val id: Long, val itemId: Long): CC()
class ApiSendMessages(val type: ChatType, val id: Long, val live: Boolean, val ttl: Int?, val composedMessages: List<ComposedMessage>): CC()
class ApiGetChat(val type: ChatType, val id: Long, val scope: GroupChatScope?, val contentTag: MsgContentTag?, val pagination: ChatPagination, val search: String = ""): CC()
class ApiGetChatItemInfo(val type: ChatType, val id: Long, val scope: GroupChatScope?, val itemId: Long): CC()
class ApiSendMessages(val type: ChatType, val id: Long, val scope: GroupChatScope?, val live: Boolean, val ttl: Int?, val composedMessages: List<ComposedMessage>): CC()
class ApiCreateChatTag(val tag: ChatTagData): CC()
class ApiSetChatTags(val type: ChatType, val id: Long, val tagIds: List<Long>): CC()
class ApiDeleteChatTag(val tagId: Long): CC()
@ -3306,18 +3344,20 @@ sealed class CC {
class ApiReorderChatTags(val tagIds: List<Long>): CC()
class ApiCreateChatItems(val noteFolderId: Long, val composedMessages: List<ComposedMessage>): CC()
class ApiReportMessage(val groupId: Long, val chatItemId: Long, val reportReason: ReportReason, val reportText: String): CC()
class ApiUpdateChatItem(val type: ChatType, val id: Long, val itemId: Long, val updatedMessage: UpdatedMessage, val live: Boolean): CC()
class ApiDeleteChatItem(val type: ChatType, val id: Long, val itemIds: List<Long>, val mode: CIDeleteMode): CC()
class ApiUpdateChatItem(val type: ChatType, val id: Long, val scope: GroupChatScope?, val itemId: Long, val updatedMessage: UpdatedMessage, val live: Boolean): CC()
class ApiDeleteChatItem(val type: ChatType, val id: Long, val scope: GroupChatScope?, val itemIds: List<Long>, val mode: CIDeleteMode): CC()
class ApiDeleteMemberChatItem(val groupId: Long, val itemIds: List<Long>): CC()
class ApiArchiveReceivedReports(val groupId: Long): CC()
class ApiDeleteReceivedReports(val groupId: Long, val itemIds: List<Long>, val mode: CIDeleteMode): CC()
class ApiChatItemReaction(val type: ChatType, val id: Long, val itemId: Long, val add: Boolean, val reaction: MsgReaction): CC()
class ApiChatItemReaction(val type: ChatType, val id: Long, val scope: GroupChatScope?, val itemId: Long, val add: Boolean, val reaction: MsgReaction): CC()
class ApiGetReactionMembers(val userId: Long, val groupId: Long, val itemId: Long, val reaction: MsgReaction): CC()
class ApiPlanForwardChatItems(val fromChatType: ChatType, val fromChatId: Long, val chatItemIds: List<Long>): CC()
class ApiForwardChatItems(val toChatType: ChatType, val toChatId: Long, val fromChatType: ChatType, val fromChatId: Long, val itemIds: List<Long>, val ttl: Int?): CC()
class ApiPlanForwardChatItems(val fromChatType: ChatType, val fromChatId: Long, val fromScope: GroupChatScope?, val chatItemIds: List<Long>): CC()
class ApiForwardChatItems(val toChatType: ChatType, val toChatId: Long, val toScope: GroupChatScope?, val fromChatType: ChatType, val fromChatId: Long, val fromScope: GroupChatScope?, val itemIds: List<Long>, val ttl: Int?): CC()
class ApiNewGroup(val userId: Long, val incognito: Boolean, val groupProfile: GroupProfile): CC()
class ApiAddMember(val groupId: Long, val contactId: Long, val memberRole: GroupMemberRole): CC()
class ApiJoinGroup(val groupId: Long): CC()
class ApiAcceptMember(val groupId: Long, val groupMemberId: Long, val memberRole: GroupMemberRole): CC()
class ApiDeleteMemberSupportChat(val groupId: Long, val groupMemberId: Long): CC()
class ApiMembersRole(val groupId: Long, val memberIds: List<Long>, val memberRole: GroupMemberRole): CC()
class ApiBlockMembersForAll(val groupId: Long, val memberIds: List<Long>, val blocked: Boolean): CC()
class ApiRemoveMembers(val groupId: Long, val memberIds: List<Long>, val withMessages: Boolean): CC()
@ -3395,8 +3435,8 @@ sealed class CC {
class ApiGetNetworkStatuses(): CC()
class ApiAcceptContact(val incognito: Boolean, val contactReqId: Long): CC()
class ApiRejectContact(val contactReqId: Long): CC()
class ApiChatRead(val type: ChatType, val id: Long): CC()
class ApiChatItemsRead(val type: ChatType, val id: Long, val itemIds: List<Long>): CC()
class ApiChatRead(val type: ChatType, val id: Long, val scope: GroupChatScope?): CC()
class ApiChatItemsRead(val type: ChatType, val id: Long, val scope: GroupChatScope?, val itemIds: List<Long>): CC()
class ApiChatUnread(val type: ChatType, val id: Long, val unreadChat: Boolean): CC()
class ReceiveFile(val fileId: Long, val userApprovedRelays: Boolean, val encrypt: Boolean, val inline: Boolean?): CC()
class CancelFile(val fileId: Long): CC()
@ -3468,16 +3508,16 @@ sealed class CC {
} else {
" content=${contentTag.name.lowercase()}"
}
"/_get chat ${chatRef(type, id)}$tag ${pagination.cmdString}" + (if (search == "") "" else " search=$search")
"/_get chat ${chatRef(type, id, scope)}$tag ${pagination.cmdString}" + (if (search == "") "" else " search=$search")
}
is ApiGetChatItemInfo -> "/_get item info ${chatRef(type, id)} $itemId"
is ApiGetChatItemInfo -> "/_get item info ${chatRef(type, id, scope)} $itemId"
is ApiSendMessages -> {
val msgs = json.encodeToString(composedMessages)
val ttlStr = if (ttl != null) "$ttl" else "default"
"/_send ${chatRef(type, id)} live=${onOff(live)} ttl=${ttlStr} json $msgs"
"/_send ${chatRef(type, id, scope)} live=${onOff(live)} ttl=${ttlStr} json $msgs"
}
is ApiCreateChatTag -> "/_create tag ${json.encodeToString(tag)}"
is ApiSetChatTags -> "/_tags ${chatRef(type, id)} ${tagIds.joinToString(",")}"
is ApiSetChatTags -> "/_tags ${chatRef(type, id, scope = null)} ${tagIds.joinToString(",")}"
is ApiDeleteChatTag -> "/_delete tag $tagId"
is ApiUpdateChatTag -> "/_update tag $tagId ${json.encodeToString(tagData)}"
is ApiReorderChatTags -> "/_reorder tags ${tagIds.joinToString(",")}"
@ -3486,23 +3526,25 @@ sealed class CC {
"/_create *$noteFolderId json $msgs"
}
is ApiReportMessage -> "/_report #$groupId $chatItemId reason=${json.encodeToString(reportReason).trim('"')} $reportText"
is ApiUpdateChatItem -> "/_update item ${chatRef(type, id)} $itemId live=${onOff(live)} ${updatedMessage.cmdString}"
is ApiDeleteChatItem -> "/_delete item ${chatRef(type, id)} ${itemIds.joinToString(",")} ${mode.deleteMode}"
is ApiUpdateChatItem -> "/_update item ${chatRef(type, id, scope)} $itemId live=${onOff(live)} ${updatedMessage.cmdString}"
is ApiDeleteChatItem -> "/_delete item ${chatRef(type, id, scope)} ${itemIds.joinToString(",")} ${mode.deleteMode}"
is ApiDeleteMemberChatItem -> "/_delete member item #$groupId ${itemIds.joinToString(",")}"
is ApiArchiveReceivedReports -> "/_archive reports #$groupId"
is ApiDeleteReceivedReports -> "/_delete reports #$groupId ${itemIds.joinToString(",")} ${mode.deleteMode}"
is ApiChatItemReaction -> "/_reaction ${chatRef(type, id)} $itemId ${onOff(add)} ${json.encodeToString(reaction)}"
is ApiChatItemReaction -> "/_reaction ${chatRef(type, id, scope)} $itemId ${onOff(add)} ${json.encodeToString(reaction)}"
is ApiGetReactionMembers -> "/_reaction members $userId #$groupId $itemId ${json.encodeToString(reaction)}"
is ApiForwardChatItems -> {
val ttlStr = if (ttl != null) "$ttl" else "default"
"/_forward ${chatRef(toChatType, toChatId)} ${chatRef(fromChatType, fromChatId)} ${itemIds.joinToString(",")} ttl=${ttlStr}"
"/_forward ${chatRef(toChatType, toChatId, toScope)} ${chatRef(fromChatType, fromChatId, fromScope)} ${itemIds.joinToString(",")} ttl=${ttlStr}"
}
is ApiPlanForwardChatItems -> {
"/_forward plan ${chatRef(fromChatType, fromChatId)} ${chatItemIds.joinToString(",")}"
"/_forward plan ${chatRef(fromChatType, fromChatId, fromScope)} ${chatItemIds.joinToString(",")}"
}
is ApiNewGroup -> "/_group $userId incognito=${onOff(incognito)} ${json.encodeToString(groupProfile)}"
is ApiAddMember -> "/_add #$groupId $contactId ${memberRole.memberRole}"
is ApiJoinGroup -> "/_join #$groupId"
is ApiAcceptMember -> "/_accept member #$groupId $groupMemberId ${memberRole.memberRole}"
is ApiDeleteMemberSupportChat -> "/_delete member chat #$groupId $groupMemberId"
is ApiMembersRole -> "/_member role #$groupId ${memberIds.joinToString(",")} ${memberRole.memberRole}"
is ApiBlockMembersForAll -> "/_block #$groupId ${memberIds.joinToString(",")} blocked=${onOff(blocked)}"
is ApiRemoveMembers -> "/_remove #$groupId ${memberIds.joinToString(",")} messages=${onOff(withMessages)}"
@ -3526,13 +3568,13 @@ sealed class CC {
is ApiAcceptConditions -> "/_accept_conditions ${conditionsId} ${operatorIds.joinToString(",")}"
is APISetChatItemTTL -> "/_ttl $userId ${chatItemTTLStr(seconds)}"
is APIGetChatItemTTL -> "/_ttl $userId"
is APISetChatTTL -> "/_ttl $userId ${chatRef(chatType, id)} ${chatItemTTLStr(seconds)}"
is APISetChatTTL -> "/_ttl $userId ${chatRef(chatType, id, scope = null)} ${chatItemTTLStr(seconds)}"
is APISetNetworkConfig -> "/_network ${json.encodeToString(networkConfig)}"
is APIGetNetworkConfig -> "/network"
is APISetNetworkInfo -> "/_network info ${json.encodeToString(networkInfo)}"
is ReconnectServer -> "/reconnect $userId $server"
is ReconnectAllServers -> "/reconnect"
is APISetChatSettings -> "/_settings ${chatRef(type, id)} ${json.encodeToString(chatSettings)}"
is APISetChatSettings -> "/_settings ${chatRef(type, id, scope = null)} ${json.encodeToString(chatSettings)}"
is ApiSetMemberSettings -> "/_member settings #$groupId $groupMemberId ${json.encodeToString(memberSettings)}"
is APIContactInfo -> "/_info @$contactId"
is APIGroupMemberInfo -> "/_info #$groupId $groupMemberId"
@ -3554,8 +3596,8 @@ sealed class CC {
is APIConnectPlan -> "/_connect plan $userId $connLink"
is APIConnect -> "/_connect $userId incognito=${onOff(incognito)} ${connLink.connFullLink} ${connLink.connShortLink ?: ""}"
is ApiConnectContactViaAddress -> "/_connect contact $userId incognito=${onOff(incognito)} $contactId"
is ApiDeleteChat -> "/_delete ${chatRef(type, id)} ${chatDeleteMode.cmdString}"
is ApiClearChat -> "/_clear chat ${chatRef(type, id)}"
is ApiDeleteChat -> "/_delete ${chatRef(type, id, scope = null)} ${chatDeleteMode.cmdString}"
is ApiClearChat -> "/_clear chat ${chatRef(type, id, scope = null)}"
is ApiListContacts -> "/_contacts $userId"
is ApiUpdateProfile -> "/_profile $userId ${json.encodeToString(profile)}"
is ApiSetContactPrefs -> "/_set prefs @$contactId ${json.encodeToString(prefs)}"
@ -3580,9 +3622,9 @@ sealed class CC {
is ApiEndCall -> "/_call end @${contact.apiId}"
is ApiCallStatus -> "/_call status @${contact.apiId} ${callStatus.value}"
is ApiGetNetworkStatuses -> "/_network_statuses"
is ApiChatRead -> "/_read chat ${chatRef(type, id)}"
is ApiChatItemsRead -> "/_read chat items ${chatRef(type, id)} ${itemIds.joinToString(",")}"
is ApiChatUnread -> "/_unread chat ${chatRef(type, id)} ${onOff(unreadChat)}"
is ApiChatRead -> "/_read chat ${chatRef(type, id, scope)}"
is ApiChatItemsRead -> "/_read chat items ${chatRef(type, id, scope)} ${itemIds.joinToString(",")}"
is ApiChatUnread -> "/_unread chat ${chatRef(type, id, scope = null)} ${onOff(unreadChat)}"
is ReceiveFile ->
"/freceive $fileId" +
" approved_relays=${onOff(userApprovedRelays)}" +
@ -3666,6 +3708,8 @@ sealed class CC {
is ApiNewGroup -> "apiNewGroup"
is ApiAddMember -> "apiAddMember"
is ApiJoinGroup -> "apiJoinGroup"
is ApiAcceptMember -> "apiAcceptMember"
is ApiDeleteMemberSupportChat -> "apiDeleteMemberSupportChat"
is ApiMembersRole -> "apiMembersRole"
is ApiBlockMembersForAll -> "apiBlockMembersForAll"
is ApiRemoveMembers -> "apiRemoveMembers"
@ -3802,7 +3846,13 @@ sealed class CC {
private fun maybePwd(pwd: String?): String = if (pwd == "" || pwd == null) "" else " " + json.encodeToString(pwd)
companion object {
fun chatRef(chatType: ChatType, id: Long) = "${chatType.type}${id}"
fun chatRef(chatType: ChatType, id: Long, scope: GroupChatScope?) = when (scope) {
null -> "${chatType.type}${id}"
is GroupChatScope.MemberSupport -> when (scope.groupMemberId_) {
null -> "${chatType.type}${id}(_support)"
else -> "${chatType.type}${id}(_support:${scope.groupMemberId_})"
}
}
}
}
@ -5764,6 +5814,7 @@ sealed class CR {
@Serializable @SerialName("contactAlreadyExists") class ContactAlreadyExists(val user: UserRef, val contact: Contact): CR()
@Serializable @SerialName("contactDeleted") class ContactDeleted(val user: UserRef, val contact: Contact): CR()
@Serializable @SerialName("contactDeletedByContact") class ContactDeletedByContact(val user: UserRef, val contact: Contact): CR()
@Serializable @SerialName("itemsReadForChat") class ItemsReadForChat(val user: UserRef, val chatInfo: ChatInfo): CR()
@Serializable @SerialName("chatCleared") class ChatCleared(val user: UserRef, val chatInfo: ChatInfo): CR()
@Serializable @SerialName("userProfileNoChange") class UserProfileNoChange(val user: User): CR()
@Serializable @SerialName("userProfileUpdated") class UserProfileUpdated(val user: User, val fromProfile: Profile, val toProfile: Profile, val updateSummary: UserProfileUpdateSummary): CR()
@ -5812,6 +5863,9 @@ sealed class CR {
@Serializable @SerialName("receivedGroupInvitation") class ReceivedGroupInvitation(val user: UserRef, val groupInfo: GroupInfo, val contact: Contact, val memberRole: GroupMemberRole): CR()
@Serializable @SerialName("groupDeletedUser") class GroupDeletedUser(val user: UserRef, val groupInfo: GroupInfo): CR()
@Serializable @SerialName("joinedGroupMemberConnecting") class JoinedGroupMemberConnecting(val user: UserRef, val groupInfo: GroupInfo, val hostMember: GroupMember, val member: GroupMember): CR()
@Serializable @SerialName("memberAccepted") class MemberAccepted(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember): CR()
@Serializable @SerialName("memberSupportChatDeleted") class MemberSupportChatDeleted(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember): CR()
@Serializable @SerialName("memberAcceptedByOther") class MemberAcceptedByOther(val user: UserRef, val groupInfo: GroupInfo, val acceptingMember: GroupMember, val member: GroupMember): CR()
@Serializable @SerialName("memberRole") class MemberRole(val user: UserRef, val groupInfo: GroupInfo, val byMember: GroupMember, val member: GroupMember, val fromRole: GroupMemberRole, val toRole: GroupMemberRole): CR()
@Serializable @SerialName("membersRoleUser") class MembersRoleUser(val user: UserRef, val groupInfo: GroupInfo, val members: List<GroupMember>, val toRole: GroupMemberRole): CR()
@Serializable @SerialName("memberBlockedForAll") class MemberBlockedForAll(val user: UserRef, val groupInfo: GroupInfo, val byMember: GroupMember, val member: GroupMember, val blocked: Boolean): CR()
@ -5942,6 +5996,7 @@ sealed class CR {
is ContactAlreadyExists -> "contactAlreadyExists"
is ContactDeleted -> "contactDeleted"
is ContactDeletedByContact -> "contactDeletedByContact"
is ItemsReadForChat -> "itemsReadForChat"
is ChatCleared -> "chatCleared"
is UserProfileNoChange -> "userProfileNoChange"
is UserProfileUpdated -> "userProfileUpdated"
@ -5987,6 +6042,9 @@ sealed class CR {
is ReceivedGroupInvitation -> "receivedGroupInvitation"
is GroupDeletedUser -> "groupDeletedUser"
is JoinedGroupMemberConnecting -> "joinedGroupMemberConnecting"
is MemberAccepted -> "memberAccepted"
is MemberSupportChatDeleted -> "memberSupportChatDeleted"
is MemberAcceptedByOther -> "memberAcceptedByOther"
is MemberRole -> "memberRole"
is MembersRoleUser -> "membersRoleUser"
is MemberBlockedForAll -> "memberBlockedForAll"
@ -6110,6 +6168,7 @@ sealed class CR {
is ContactAlreadyExists -> withUser(user, json.encodeToString(contact))
is ContactDeleted -> withUser(user, json.encodeToString(contact))
is ContactDeletedByContact -> withUser(user, json.encodeToString(contact))
is ItemsReadForChat -> withUser(user, json.encodeToString(chatInfo))
is ChatCleared -> withUser(user, json.encodeToString(chatInfo))
is UserProfileNoChange -> withUser(user, noDetails())
is UserProfileUpdated -> withUser(user, json.encodeToString(toProfile))
@ -6155,6 +6214,9 @@ sealed class CR {
is ReceivedGroupInvitation -> withUser(user, "groupInfo: $groupInfo\ncontact: $contact\nmemberRole: $memberRole")
is GroupDeletedUser -> withUser(user, json.encodeToString(groupInfo))
is JoinedGroupMemberConnecting -> withUser(user, "groupInfo: $groupInfo\nhostMember: $hostMember\nmember: $member")
is MemberAccepted -> withUser(user, "groupInfo: $groupInfo\nmember: $member")
is MemberSupportChatDeleted -> withUser(user, "groupInfo: $groupInfo\nmember: $member")
is MemberAcceptedByOther -> withUser(user, "groupInfo: $groupInfo\nacceptingMember: $acceptingMember\nmember: $member")
is MemberRole -> withUser(user, "groupInfo: $groupInfo\nbyMember: $byMember\nmember: $member\nfromRole: $fromRole\ntoRole: $toRole")
is MembersRoleUser -> withUser(user, "groupInfo: $groupInfo\nmembers: $members\ntoRole: $toRole")
is MemberBlockedForAll -> withUser(user, "groupInfo: $groupInfo\nbyMember: $byMember\nmember: $member\nblocked: $blocked")

View file

@ -40,9 +40,8 @@ import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.helpers.*
import chat.simplex.common.views.usersettings.*
import chat.simplex.common.platform.*
import chat.simplex.common.views.chat.group.ChatTTLSection
import chat.simplex.common.views.chat.group.ChatTTLOption
import chat.simplex.common.views.chatlist.updateChatSettings
import chat.simplex.common.views.database.*
import chat.simplex.common.views.newchat.*
import chat.simplex.res.MR
import kotlinx.coroutines.*
@ -617,7 +616,10 @@ fun ChatInfoLayout(
}
SectionDividerSpaced(maxBottomPadding = false)
ChatTTLSection(chatItemTTL, setChatItemTTL, deletingItems)
SectionView {
ChatTTLOption(chatItemTTL, setChatItemTTL, deletingItems)
SectionTextFooter(stringResource(MR.strings.chat_ttl_options_footer))
}
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
val conn = contact.activeConn
@ -1384,7 +1386,7 @@ private fun setChatTTL(
private suspend fun afterSetChatTTL(chatsCtx: ChatModel.ChatsContext, rhId: Long?, chatInfo: ChatInfo, progressIndicator: MutableState<Boolean>) {
try {
val pagination = ChatPagination.Initial(ChatPagination.INITIAL_COUNT)
val (chat, navInfo) = controller.apiGetChat(rhId, chatInfo.chatType, chatInfo.apiId, null, pagination) ?: return
val (chat, navInfo) = controller.apiGetChat(rhId, chatInfo.chatType, chatInfo.apiId, scope = null, contentTag = null, pagination) ?: return
if (chat.chatItems.isEmpty()) {
// replacing old chat with the same old chat but without items. Less intrusive way of clearing a preview
withContext(Dispatchers.Main) {

View file

@ -17,7 +17,7 @@ suspend fun apiLoadSingleMessage(
apiId: Long,
itemId: Long
): ChatItem? = coroutineScope {
val (chat, _) = chatModel.controller.apiGetChat(rhId, chatType, apiId, chatsCtx.contentTag, ChatPagination.Around(itemId, 0), "") ?: return@coroutineScope null
val (chat, _) = chatModel.controller.apiGetChat(rhId, chatType, apiId, chatsCtx.groupScopeInfo?.toChatScope(), chatsCtx.contentTag, ChatPagination.Around(itemId, 0), "") ?: return@coroutineScope null
chat.chatItems.firstOrNull()
}
@ -31,7 +31,7 @@ suspend fun apiLoadMessages(
openAroundItemId: Long? = null,
visibleItemIndexesNonReversed: () -> IntRange = { 0 .. 0 }
) = coroutineScope {
val (chat, navInfo) = chatModel.controller.apiGetChat(rhId, chatType, apiId, chatsCtx.contentTag, pagination, search) ?: return@coroutineScope
val (chat, navInfo) = chatModel.controller.apiGetChat(rhId, chatType, apiId, chatsCtx.groupScopeInfo?.toChatScope(), chatsCtx.contentTag, pagination, search) ?: return@coroutineScope
// For .initial allow the chatItems to be empty as well as chatModel.chatId to not match this chat because these values become set after .initial finishes
/** When [openAroundItemId] is provided, chatId can be different too */
if (((chatModel.chatId.value != chat.id || chat.chatItems.isEmpty()) && pagination !is ChatPagination.Initial && pagination !is ChatPagination.Last && openAroundItemId == null)
@ -54,7 +54,7 @@ suspend fun processLoadedChat(
when (pagination) {
is ChatPagination.Initial -> {
val newSplits = if (chat.chatItems.isNotEmpty() && navInfo.afterTotal > 0) listOf(chat.chatItems.last().id) else emptyList()
if (chatsCtx.contentTag == null) {
if (chatsCtx.secondaryContextFilter == null) {
// update main chats, not content tagged
withContext(Dispatchers.Main) {
val oldChat = chatModel.chatsContext.getChat(chat.id)
@ -68,7 +68,6 @@ suspend fun processLoadedChat(
}
}
withContext(Dispatchers.Main) {
chatsCtx.chatItemStatuses.clear()
chatsCtx.chatItems.replaceAll(chat.chatItems)
chatModel.chatId.value = chat.id
splits.value = newSplits

View file

@ -0,0 +1,126 @@
package chat.simplex.common.views.chat
import SectionItemView
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import chat.simplex.common.model.*
import chat.simplex.common.platform.chatModel
import chat.simplex.common.views.chat.group.removeMember
import chat.simplex.common.views.chat.group.removeMemberDialog
import chat.simplex.common.views.helpers.*
import chat.simplex.res.MR
import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Composable
fun ComposeContextPendingMemberActionsView(
rhId: Long?,
groupInfo: GroupInfo,
member: GroupMember
) {
Column(
Modifier
.height(60.dp)
.background(MaterialTheme.colors.surface)
) {
Divider()
Row(
Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
) {
Column(
Modifier
.fillMaxWidth()
.fillMaxHeight()
.weight(1F)
.clickable {
rejectMemberDialog(rhId, member, chatModel, close = { ModalManager.end.closeModal() })
},
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(stringResource(MR.strings.reject_pending_member_button), color = Color.Red)
}
Column(
Modifier
.fillMaxWidth()
.fillMaxHeight()
.weight(1F)
.clickable {
acceptMemberDialog(rhId, groupInfo, member, close = { ModalManager.end.closeModal() })
},
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(stringResource(MR.strings.accept_pending_member_button), color = MaterialTheme.colors.primary)
}
}
}
}
fun rejectMemberDialog(rhId: Long?, member: GroupMember, chatModel: ChatModel, close: (() -> Unit)? = null) {
AlertManager.shared.showAlertDialog(
title = generalGetString(MR.strings.reject_pending_member_alert_title),
confirmText = generalGetString(MR.strings.reject_pending_member_button),
onConfirm = {
removeMember(rhId, member, chatModel, close)
},
destructive = true,
)
}
fun acceptMemberDialog(rhId: Long?, groupInfo: GroupInfo, member: GroupMember, close: (() -> Unit)? = null) {
AlertManager.shared.showAlertDialogButtonsColumn(
title = generalGetString(MR.strings.accept_pending_member_alert_title),
text = generalGetString(MR.strings.accept_pending_member_alert_question),
buttons = {
Column {
// Accept as member
SectionItemView({
AlertManager.shared.hideAlert()
acceptMember(rhId, groupInfo, member, GroupMemberRole.Member, close)
}) {
Text(generalGetString(MR.strings.accept_pending_member_alert_confirmation_as_member), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
}
// Accept as observer
SectionItemView({
AlertManager.shared.hideAlert()
acceptMember(rhId, groupInfo, member, GroupMemberRole.Observer, close)
}) {
Text(generalGetString(MR.strings.accept_pending_member_alert_confirmation_as_observer), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
}
// Cancel
SectionItemView({
AlertManager.shared.hideAlert()
}) {
Text(stringResource(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
}
}
}
)
}
private fun acceptMember(rhId: Long?, groupInfo: GroupInfo, member: GroupMember, role: GroupMemberRole, close: (() -> Unit)?) {
withBGApi {
val r = chatModel.controller.apiAcceptMember(rhId, groupInfo.groupId, member.groupMemberId, role)
if (r != null) {
withContext(Dispatchers.Main) {
chatModel.chatsContext.upsertGroupMember(rhId, r.first, r.second)
chatModel.chatsContext.updateGroup(rhId, r.first)
}
}
close?.invoke()
}
}

View file

@ -333,7 +333,9 @@ suspend fun MutableState<ComposeState>.processPickedMedia(uris: List<URI>, text:
@Composable
fun ComposeView(
rhId: Long?,
chatModel: ChatModel,
chatsCtx: ChatModel.ChatsContext,
chat: Chat,
composeState: MutableState<ComposeState>,
attachmentOption: MutableState<AttachmentOption?>,
@ -466,6 +468,7 @@ fun ComposeView(
rh = chat.remoteHostId,
type = cInfo.chatType,
id = cInfo.apiId,
scope = cInfo.groupChatScope(),
live = live,
ttl = ttl,
composedMessages = listOf(ComposedMessage(file, quoted, mc, mentions))
@ -473,7 +476,7 @@ fun ComposeView(
if (!chatItems.isNullOrEmpty()) {
chatItems.forEach { aChatItem ->
withContext(Dispatchers.Main) {
chatModel.chatsContext.addChatItem(chat.remoteHostId, cInfo, aChatItem.chatItem)
chatsCtx.addChatItem(chat.remoteHostId, cInfo, aChatItem.chatItem)
}
}
return chatItems.first().chatItem
@ -498,15 +501,17 @@ fun ComposeView(
rh = rhId,
toChatType = chat.chatInfo.chatType,
toChatId = chat.chatInfo.apiId,
toScope = chat.chatInfo.groupChatScope(),
fromChatType = fromChatInfo.chatType,
fromChatId = fromChatInfo.apiId,
fromScope = fromChatInfo.groupChatScope(),
itemIds = forwardedItem.map { it.id },
ttl = ttl
)
withContext(Dispatchers.Main) {
chatItems?.forEach { chatItem ->
chatModel.chatsContext.addChatItem(rhId, chat.chatInfo, chatItem)
chatsCtx.addChatItem(rhId, chat.chatInfo, chatItem)
}
}
@ -563,16 +568,21 @@ fun ComposeView(
}
}
fun showReportsInSupportChatAlert() {
AlertManager.shared.showAlertDialog(
title = generalGetString(MR.strings.report_sent_alert_title),
text = generalGetString(MR.strings.report_sent_alert_msg_view_in_support_chat),
confirmText = generalGetString(MR.strings.ok),
dismissText = generalGetString(MR.strings.dont_show_again),
onDismiss = {
chatModel.controller.appPrefs.showReportsInSupportChatAlert.set(false)
},
)
}
suspend fun sendReport(reportReason: ReportReason, chatItemId: Long): List<ChatItem>? {
val cItems = chatModel.controller.apiReportMessage(chat.remoteHostId, chat.chatInfo.apiId, chatItemId, reportReason, msgText)
if (cItems != null) {
withContext(Dispatchers.Main) {
cItems.forEach { chatItem ->
chatModel.chatsContext.addChatItem(chat.remoteHostId, chat.chatInfo, chatItem.chatItem)
}
}
}
if (chatModel.controller.appPrefs.showReportsInSupportChatAlert.get()) showReportsInSupportChatAlert()
return cItems?.map { it.chatItem }
}
@ -581,7 +591,7 @@ fun ComposeView(
val contact = chatModel.controller.apiSendMemberContactInvitation(chat.remoteHostId, chat.chatInfo.apiId, mc)
if (contact != null) {
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContact(chat.remoteHostId, contact)
chatsCtx.updateContact(chat.remoteHostId, contact)
}
}
}
@ -594,13 +604,14 @@ fun ComposeView(
rh = chat.remoteHostId,
type = cInfo.chatType,
id = cInfo.apiId,
scope = cInfo.groupChatScope(),
itemId = ei.meta.itemId,
updatedMessage = UpdatedMessage(updateMsgContent(oldMsgContent), cs.memberMentions),
live = live
)
if (updatedItem != null) {
withContext(Dispatchers.Main) {
chatModel.chatsContext.upsertChatItem(chat.remoteHostId, cInfo, updatedItem.chatItem)
chatsCtx.upsertChatItem(chat.remoteHostId, cInfo, updatedItem.chatItem)
}
}
return updatedItem?.chatItem
@ -891,7 +902,7 @@ fun ComposeView(
fun editPrevMessage() {
if (composeState.value.contextItem != ComposeContextItem.NoContextItem || composeState.value.preview != ComposePreview.NoPreview) return
val lastEditable = chatModel.chatsContext.chatItems.value.findLast { it.meta.editable }
val lastEditable = chatsCtx.chatItems.value.findLast { it.meta.editable }
if (lastEditable != null) {
composeState.value = ComposeState(editingItem = lastEditable, useLinkPreviews = useLinkPreviews)
}
@ -1004,6 +1015,21 @@ fun ComposeView(
val nextSendGrpInv = rememberUpdatedState(chat.nextSendGrpInv)
Column {
if (
chat.chatInfo is ChatInfo.Group
&& chatsCtx.secondaryContextFilter is SecondaryContextFilter.GroupChatScopeContext
&& chatsCtx.secondaryContextFilter.groupScopeInfo is GroupChatScopeInfo.MemberSupport
&& chatsCtx.secondaryContextFilter.groupScopeInfo.groupMember_ != null
&& chatsCtx.secondaryContextFilter.groupScopeInfo.groupMember_.memberPending
&& composeState.value.contextItem == ComposeContextItem.NoContextItem
&& composeState.value.preview == ComposePreview.NoPreview
) {
ComposeContextPendingMemberActionsView(
rhId = rhId,
groupInfo = chat.chatInfo.groupInfo,
member = chatsCtx.secondaryContextFilter.groupScopeInfo.groupMember_
)
}
if (nextSendGrpInv.value) {
ComposeContextInvitingContactMemberView()
}
@ -1011,8 +1037,8 @@ fun ComposeView(
if (ctx is ComposeContextItem.ReportedItem) {
ReportReasonView(ctx.reason)
}
val simplexLinkProhibited = hasSimplexLink.value && !chat.groupFeatureEnabled(GroupFeature.SimplexLinks)
val fileProhibited = composeState.value.attachmentPreview && !chat.groupFeatureEnabled(GroupFeature.Files)
val simplexLinkProhibited = chatsCtx.secondaryContextFilter == null && hasSimplexLink.value && !chat.groupFeatureEnabled(GroupFeature.SimplexLinks)
val fileProhibited = chatsCtx.secondaryContextFilter == null && composeState.value.attachmentPreview && !chat.groupFeatureEnabled(GroupFeature.Files)
val voiceProhibited = composeState.value.preview is ComposePreview.VoicePreview && !chat.chatInfo.featureEnabled(ChatFeature.Voice)
if (composeState.value.preview !is ComposePreview.VoicePreview || composeState.value.editing) {
if (simplexLinkProhibited) {
@ -1041,7 +1067,10 @@ fun ComposeView(
Surface(color = MaterialTheme.colors.background, contentColor = MaterialTheme.colors.onBackground) {
Divider()
Row(Modifier.padding(end = 8.dp), verticalAlignment = Alignment.Bottom) {
val isGroupAndProhibitedFiles = chat.chatInfo is ChatInfo.Group && !chat.chatInfo.groupInfo.fullGroupPreferences.files.on(chat.chatInfo.groupInfo.membership)
val isGroupAndProhibitedFiles =
chatsCtx.secondaryContextFilter == null
&& chat.chatInfo is ChatInfo.Group
&& !chat.chatInfo.groupInfo.fullGroupPreferences.files.on(chat.chatInfo.groupInfo.membership)
val attachmentClicked = if (isGroupAndProhibitedFiles) {
{
AlertManager.shared.showAlertMsg(

View file

@ -77,7 +77,7 @@ fun SelectedItemsButtonsToolbar(
val forwardCountProhibited = remember { mutableStateOf(false) }
Box {
// It's hard to measure exact height of ComposeView with different fontSizes. Better to depend on actual ComposeView, even empty
ComposeView(chatModel = chatModel, Chat.sampleData, remember { mutableStateOf(ComposeState(useLinkPreviews = false)) }, remember { mutableStateOf(null) }, {}, remember { FocusRequester() })
ComposeView(rhId = null, chatModel = chatModel, chatModel.chatsContext, Chat.sampleData, remember { mutableStateOf(ComposeState(useLinkPreviews = false)) }, remember { mutableStateOf(null) }, {}, remember { FocusRequester() })
Row(
Modifier
.matchParentSize()
@ -101,21 +101,21 @@ fun SelectedItemsButtonsToolbar(
)
}
IconButton({ moderateItems() }, Modifier.alpha(if (canModerate.value) 1f else 0f), enabled = moderateEnabled.value && !deleteCountProhibited.value) {
IconButton({ moderateItems() }, Modifier.alpha(if (canModerate.value) 1f else 0f), enabled = moderateEnabled.value && !deleteCountProhibited.value && chatsCtx.secondaryContextFilter == null) {
Icon(
painterResource(MR.images.ic_flag),
null,
Modifier.size(22.dp),
tint = if (!moderateEnabled.value || deleteCountProhibited.value) MaterialTheme.colors.secondary else MaterialTheme.colors.error
tint = if (!moderateEnabled.value || deleteCountProhibited.value || chatsCtx.secondaryContextFilter != null) MaterialTheme.colors.secondary else MaterialTheme.colors.error
)
}
IconButton({ forwardItems() }, enabled = forwardEnabled.value && !forwardCountProhibited.value) {
IconButton({ forwardItems() }, enabled = forwardEnabled.value && !forwardCountProhibited.value && chatsCtx.secondaryContextFilter == null) {
Icon(
painterResource(MR.images.ic_forward),
null,
Modifier.size(22.dp),
tint = if (!forwardEnabled.value || forwardCountProhibited.value) MaterialTheme.colors.secondary else MaterialTheme.colors.primary
tint = if (!forwardEnabled.value || forwardCountProhibited.value || chatsCtx.secondaryContextFilter != null) MaterialTheme.colors.secondary else MaterialTheme.colors.primary
)
}
}

View file

@ -55,6 +55,16 @@ fun AddGroupMembersView(rhId: Long?, groupInfo: GroupInfo, creatingGroup: Boolea
GroupPreferencesView(chatModel, rhId, groupInfo.id, close)
}
},
openMemberAdmission = {
ModalManager.end.showCustomModal { close ->
MemberAdmissionView(
chat.simplex.common.platform.chatModel,
rhId,
groupInfo.id,
close
)
}
},
inviteMembers = {
allowModifyMembers = false
withLongRunningApi(slow = 120_000) {
@ -93,8 +103,9 @@ fun getContactsToAdd(chatModel: ChatModel, search: String): List<Contact> {
.asSequence()
.map { it.chatInfo }
.filterIsInstance<ChatInfo.Direct>()
.filter { it.sendMsgEnabled }
.map { it.contact }
.filter { c -> c.sendMsgEnabled && !c.nextSendGrpInv && c.contactId !in memberContactIds && c.anyNameContains(s)
.filter { c -> !c.nextSendGrpInv && c.contactId !in memberContactIds && c.anyNameContains(s)
}
.sortedBy { it.displayName.lowercase() }
.toList()
@ -110,6 +121,7 @@ fun AddGroupMembersLayout(
allowModifyMembers: Boolean,
searchText: MutableState<TextFieldValue>,
openPreferences: () -> Unit,
openMemberAdmission: () -> Unit,
inviteMembers: () -> Unit,
clearSelection: () -> Unit,
addContact: (Long) -> Unit,
@ -144,7 +156,7 @@ fun AddGroupMembersLayout(
horizontalArrangement = Arrangement.Center
) {
ChatInfoToolbarTitle(
ChatInfo.Group(groupInfo),
ChatInfo.Group(groupInfo, groupChatScope = null),
imageSize = 60.dp,
iconColor = if (isInDarkTheme()) GroupDark else SettingsSecondaryLight
)
@ -165,6 +177,9 @@ fun AddGroupMembersLayout(
} else {
SectionView {
if (creatingGroup) {
SectionItemView(openMemberAdmission) {
Text(stringResource(MR.strings.set_member_admission))
}
SectionItemView(openPreferences) {
Text(stringResource(MR.strings.set_group_preferences))
}
@ -376,6 +391,7 @@ fun PreviewAddGroupMembersLayout() {
allowModifyMembers = true,
searchText = remember { mutableStateOf(TextFieldValue("")) },
openPreferences = {},
openMemberAdmission = {},
inviteMembers = {},
clearSelection = {},
addContact = {},

View file

@ -74,6 +74,9 @@ fun ModalData.GroupChatInfoView(
val chatItemTTL = remember(groupInfo.id) { mutableStateOf(if (groupInfo.chatItemTTL != null) ChatItemTTL.fromSeconds(groupInfo.chatItemTTL) else null) }
val deletingItems = rememberSaveable(groupInfo.id) { mutableStateOf(false) }
val scope = rememberCoroutineScope()
val activeSortedMembers = remember { chatModel.groupMembers }.value
.filter { it.memberStatus != GroupMemberStatus.MemLeft && it.memberStatus != GroupMemberStatus.MemRemoved }
.sortedByDescending { it.memberRole }
GroupChatInfoLayout(
chat,
@ -95,9 +98,7 @@ fun ModalData.GroupChatInfoView(
setChatTTLAlert(chatsCtx, chat.remoteHostId, chat.chatInfo, chatItemTTL, previousChatTTL, deletingItems)
},
activeSortedMembers = remember { chatModel.groupMembers }.value
.filter { it.memberStatus != GroupMemberStatus.MemLeft && it.memberStatus != GroupMemberStatus.MemRemoved }
.sortedByDescending { it.memberRole },
activeSortedMembers = activeSortedMembers,
developerTools,
onLocalAliasChanged = { setGroupAlias(chat, it, chatModel) },
groupLink,
@ -126,7 +127,7 @@ fun ModalData.GroupChatInfoView(
}
ModalManager.end.showModalCloseable(true) { closeCurrent ->
remember { derivedStateOf { chatModel.getGroupMember(member.groupMemberId) } }.value?.let { mem ->
GroupMemberInfoView(rhId, groupInfo, mem, stats, code, chatModel, closeCurrent) {
GroupMemberInfoView(rhId, groupInfo, mem, scrollToItemId, stats, code, chatModel, closeCurrent) {
closeCurrent()
close()
}
@ -140,6 +141,17 @@ fun ModalData.GroupChatInfoView(
addOrEditWelcomeMessage = {
ModalManager.end.showCustomModal { close -> GroupWelcomeView(chatModel, rhId, groupInfo, close) }
},
openMemberSupport = {
ModalManager.end.showCustomModal { close ->
MemberSupportView(
rhId,
chat,
groupInfo,
scrollToItemId,
close
)
}
},
openPreferences = {
ModalManager.end.showCustomModal { close ->
GroupPreferencesView(
@ -317,6 +329,40 @@ fun AddGroupMembersButton(
)
}
@Composable
fun UserSupportChatButton(
chat: Chat,
groupInfo: GroupInfo,
scrollToItemId: MutableState<Long?>
) {
val scope = rememberCoroutineScope()
SettingsActionItemWithContent(
painterResource(if (chat.supportUnreadCount > 0) MR.images.ic_flag_filled else MR.images.ic_flag),
stringResource(MR.strings.button_support_chat),
click = {
val scopeInfo = GroupChatScopeInfo.MemberSupport(groupMember_ = null)
val supportChatInfo = ChatInfo.Group(groupInfo, groupChatScope = scopeInfo)
scope.launch {
showMemberSupportChatView(
chatModel.chatId,
scrollToItemId = scrollToItemId,
supportChatInfo,
scopeInfo
)
}
},
iconColor = (if (chat.supportUnreadCount > 0) MaterialTheme.colors.primary else MaterialTheme.colors.secondary),
) {
if (chat.supportUnreadCount > 0) {
UnreadBadge(
text = unreadCountStr(chat.supportUnreadCount),
backgroundColor = MaterialTheme.colors.primary
)
}
}
}
@Composable
fun ModalData.GroupChatInfoLayout(
chat: Chat,
@ -337,6 +383,7 @@ fun ModalData.GroupChatInfoLayout(
showMemberInfo: (GroupMember) -> Unit,
editGroupProfile: () -> Unit,
addOrEditWelcomeMessage: () -> Unit,
openMemberSupport: () -> Unit,
openPreferences: () -> Unit,
deleteGroup: () -> Unit,
clearChat: () -> Unit,
@ -422,6 +469,40 @@ fun ModalData.GroupChatInfoLayout(
SectionSpacer()
var anyTopSectionRowShow = false
SectionView {
if (groupInfo.canAddMembers && groupInfo.businessChat == null) {
anyTopSectionRowShow = true
if (groupLink == null) {
CreateGroupLinkButton(manageGroupLink)
} else {
GroupLinkButton(manageGroupLink)
}
}
if (groupInfo.businessChat == null && groupInfo.membership.memberRole >= GroupMemberRole.Moderator) {
anyTopSectionRowShow = true
MemberSupportButton(chat, openMemberSupport)
}
if (groupInfo.canModerate) {
anyTopSectionRowShow = true
GroupReportsButton(chat) {
scope.launch {
showGroupReportsView(chatModel.chatId, scrollToItemId, chat.chatInfo)
}
}
}
if (
groupInfo.membership.memberActive &&
(groupInfo.membership.memberRole < GroupMemberRole.Moderator || groupInfo.membership.supportChat != null)
) {
anyTopSectionRowShow = true
UserSupportChatButton(chat, groupInfo, scrollToItemId)
}
}
if (anyTopSectionRowShow) {
SectionDividerSpaced(maxBottomPadding = false)
}
SectionView {
if (groupInfo.isOwner && groupInfo.businessChat?.chatType == null) {
EditGroupProfileButton(editGroupProfile)
@ -431,19 +512,17 @@ fun ModalData.GroupChatInfoLayout(
}
val prefsTitleId = if (groupInfo.businessChat == null) MR.strings.group_preferences else MR.strings.chat_preferences
GroupPreferencesButton(prefsTitleId, openPreferences)
if (groupInfo.canModerate) {
GroupReportsButton {
scope.launch {
showGroupReportsView(chatModel.chatId, scrollToItemId, chat.chatInfo)
}
}
}
}
val footerId = if (groupInfo.businessChat == null) MR.strings.only_group_owners_can_change_prefs else MR.strings.only_chat_owners_can_change_prefs
SectionTextFooter(stringResource(footerId))
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
SectionView {
if (activeSortedMembers.filter { it.memberCurrent }.size <= SMALL_GROUPS_RCPS_MEM_LIMIT) {
SendReceiptsOption(currentUser, sendReceipts, setSendReceipts)
} else {
SendReceiptsOptionDisabled()
}
WallpaperButton {
ModalManager.end.showModal {
val chat = remember { derivedStateOf { chatModel.chats.value.firstOrNull { it.id == chat.id } } }
@ -453,23 +532,13 @@ fun ModalData.GroupChatInfoLayout(
}
}
}
ChatTTLOption(chatItemTTL, setChatItemTTL, deletingItems)
SectionTextFooter(stringResource(MR.strings.chat_ttl_options_footer))
}
val footerId = if (groupInfo.businessChat == null) MR.strings.only_group_owners_can_change_prefs else MR.strings.only_chat_owners_can_change_prefs
SectionTextFooter(stringResource(footerId))
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
ChatTTLSection(chatItemTTL, setChatItemTTL, deletingItems)
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = true)
SectionView(title = String.format(generalGetString(MR.strings.group_info_section_title_num_members), activeSortedMembers.count() + 1)) {
if (groupInfo.canAddMembers) {
if (groupInfo.businessChat == null) {
if (groupLink == null) {
CreateGroupLinkButton(manageGroupLink)
} else {
GroupLinkButton(manageGroupLink)
}
}
val onAddMembersClick = if (chat.chatInfo.incognito) ::cantInviteIncognitoAlert else addMembers
val tint = if (chat.chatInfo.incognito) MaterialTheme.colors.secondary else MaterialTheme.colors.primary
val addMembersTitleId = when (groupInfo.businessChat?.chatType) {
@ -481,7 +550,7 @@ fun ModalData.GroupChatInfoLayout(
}
if (activeSortedMembers.size > 8) {
SectionItemView(padding = PaddingValues(start = 14.dp, end = DEFAULT_PADDING_HALF)) {
SearchRowView(searchText)
MemberListSearchRowView(searchText)
}
}
SectionItemView(minHeight = 54.dp, padding = PaddingValues(horizontal = DEFAULT_PADDING)) {
@ -527,7 +596,7 @@ fun ModalData.GroupChatInfoLayout(
val titleId = if (groupInfo.businessChat == null) MR.strings.button_delete_group else MR.strings.button_delete_chat
DeleteGroupButton(titleId, deleteGroup)
}
if (groupInfo.membership.memberCurrent) {
if (groupInfo.membership.memberCurrentOrPending) {
val titleId = if (groupInfo.businessChat == null) MR.strings.button_leave_group else MR.strings.button_leave_chat
LeaveGroupButton(titleId, leaveGroup)
}
@ -631,17 +700,14 @@ private fun SelectedItemsCounterToolbarSetter(
}
@Composable
fun ChatTTLSection(chatItemTTL: State<ChatItemTTL?>, setChatItemTTL: (ChatItemTTL?) -> Unit, deletingItems: State<Boolean>) {
fun ChatTTLOption(chatItemTTL: State<ChatItemTTL?>, setChatItemTTL: (ChatItemTTL?) -> Unit, deletingItems: State<Boolean>) {
Box {
SectionView {
TtlOptions(
chatItemTTL,
enabled = remember { derivedStateOf { !deletingItems.value } },
onSelected = setChatItemTTL,
default = chatModel.chatItemTTL
)
SectionTextFooter(stringResource(MR.strings.chat_ttl_options_footer))
}
TtlOptions(
chatItemTTL,
enabled = remember { derivedStateOf { !deletingItems.value } },
onSelected = setChatItemTTL,
default = chatModel.chatItemTTL
)
if (deletingItems.value) {
Box(Modifier.matchParentSize()) {
ProgressIndicator()
@ -683,6 +749,23 @@ private fun GroupChatInfoHeader(cInfo: ChatInfo, groupInfo: GroupInfo) {
}
}
@Composable
private fun MemberSupportButton(chat: Chat, onClick: () -> Unit) {
SettingsActionItemWithContent(
painterResource(if (chat.supportUnreadCount > 0) MR.images.ic_flag_filled else MR.images.ic_flag),
stringResource(MR.strings.member_support),
click = onClick,
iconColor = (if (chat.supportUnreadCount > 0) MaterialTheme.colors.primary else MaterialTheme.colors.secondary)
) {
if (chat.supportUnreadCount > 0) {
UnreadBadge(
text = unreadCountStr(chat.supportUnreadCount),
backgroundColor = MaterialTheme.colors.primary
)
}
}
}
@Composable
private fun GroupPreferencesButton(titleId: StringResource, onClick: () -> Unit) {
SettingsActionItem(
@ -693,12 +776,20 @@ private fun GroupPreferencesButton(titleId: StringResource, onClick: () -> Unit)
}
@Composable
private fun GroupReportsButton(onClick: () -> Unit) {
SettingsActionItem(
painterResource(MR.images.ic_flag),
private fun GroupReportsButton(chat: Chat, onClick: () -> Unit) {
SettingsActionItemWithContent(
painterResource(if (chat.chatStats.reportsCount > 0) MR.images.ic_flag_filled else MR.images.ic_flag),
stringResource(MR.strings.group_reports_member_reports),
click = onClick
)
click = onClick,
iconColor = (if (chat.chatStats.reportsCount > 0) Color.Red else MaterialTheme.colors.secondary)
) {
if (chat.chatStats.reportsCount > 0) {
UnreadBadge(
text = unreadCountStr(chat.chatStats.reportsCount),
backgroundColor = Color.Red
)
}
}
}
@Composable
@ -820,7 +911,7 @@ fun MemberRow(member: GroupMember, user: Boolean = false, infoPage: Boolean = tr
}
@Composable
private fun MemberVerifiedShield() {
fun MemberVerifiedShield() {
Icon(painterResource(MR.images.ic_verified_user), null, Modifier.padding(end = 3.dp).size(16.dp), tint = MaterialTheme.colors.secondary)
}
@ -941,7 +1032,7 @@ private fun DeleteGroupButton(titleId: StringResource, onClick: () -> Unit) {
}
@Composable
private fun SearchRowView(
fun MemberListSearchRowView(
searchText: MutableState<TextFieldValue> = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue()) }
) {
Box(Modifier.width(36.dp), contentAlignment = Alignment.Center) {
@ -964,16 +1055,18 @@ private fun setGroupAlias(chat: Chat, localAlias: String, chatModel: ChatModel)
fun removeMembers(rhId: Long?, groupInfo: GroupInfo, memberIds: List<Long>, onSuccess: () -> Unit = {}) {
withBGApi {
val updatedMembers = chatModel.controller.apiRemoveMembers(rhId, groupInfo.groupId, memberIds)
if (updatedMembers != null) {
val r = chatModel.controller.apiRemoveMembers(rhId, groupInfo.groupId, memberIds)
if (r != null) {
val (updatedGroupInfo, updatedMembers) = r
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(rhId, updatedGroupInfo)
updatedMembers.forEach { updatedMember ->
chatModel.chatsContext.upsertGroupMember(rhId, groupInfo, updatedMember)
chatModel.chatsContext.upsertGroupMember(rhId, updatedGroupInfo, updatedMember)
}
}
withContext(Dispatchers.Main) {
updatedMembers.forEach { updatedMember ->
chatModel.secondaryChatsContext.value?.upsertGroupMember(rhId, groupInfo, updatedMember)
chatModel.secondaryChatsContext.value?.upsertGroupMember(rhId, updatedGroupInfo, updatedMember)
}
}
onSuccess()
@ -1016,7 +1109,18 @@ fun PreviewGroupChatInfoLayout() {
selectedItems = remember { mutableStateOf(null) },
appBar = remember { mutableStateOf(null) },
scrollToItemId = remember { mutableStateOf(null) },
addMembers = {}, showMemberInfo = {}, editGroupProfile = {}, addOrEditWelcomeMessage = {}, openPreferences = {}, deleteGroup = {}, clearChat = {}, leaveGroup = {}, manageGroupLink = {}, onSearchClicked = {}, deletingItems = remember { mutableStateOf(true) }
addMembers = {},
showMemberInfo = {},
editGroupProfile = {},
addOrEditWelcomeMessage = {},
openMemberSupport = {},
openPreferences = {},
deleteGroup = {},
clearChat = {},
leaveGroup = {},
manageGroupLink = {},
onSearchClicked = {},
deletingItems = remember { mutableStateOf(true) }
)
}
}

View file

@ -45,6 +45,7 @@ fun GroupMemberInfoView(
rhId: Long?,
groupInfo: GroupInfo,
member: GroupMember,
scrollToItemId: MutableState<Long?>,
connectionStats: ConnectionStats?,
connectionCode: String?,
chatModel: ChatModel,
@ -79,6 +80,7 @@ fun GroupMemberInfoView(
rhId = rhId,
groupInfo,
member,
scrollToItemId,
connStats,
newRole,
developerTools,
@ -243,32 +245,34 @@ fun removeMemberDialog(rhId: Long?, groupInfo: GroupInfo, member: GroupMember, c
text = generalGetString(messageId),
confirmText = generalGetString(MR.strings.remove_member_confirmation),
onConfirm = {
withBGApi {
val removedMembers = chatModel.controller.apiRemoveMembers(rhId, member.groupId, listOf(member.groupMemberId))
if (removedMembers != null) {
withContext(Dispatchers.Main) {
removedMembers.forEach { removedMember ->
chatModel.chatsContext.upsertGroupMember(rhId, groupInfo, removedMember)
}
}
withContext(Dispatchers.Main) {
removedMembers.forEach { removedMember ->
chatModel.secondaryChatsContext.value?.upsertGroupMember(rhId, groupInfo, removedMember)
}
}
}
close?.invoke()
}
removeMember(rhId, member, chatModel, close)
},
destructive = true,
)
}
fun removeMember(rhId: Long?, member: GroupMember, chatModel: ChatModel, close: (() -> Unit)? = null) {
withBGApi {
val r = chatModel.controller.apiRemoveMembers(rhId, member.groupId, listOf(member.groupMemberId))
if (r != null) {
val (updatedGroupInfo, removedMembers) = r
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(rhId, updatedGroupInfo)
removedMembers.forEach { removedMember ->
chatModel.chatsContext.upsertGroupMember(rhId, updatedGroupInfo, removedMember)
}
}
}
close?.invoke()
}
}
@Composable
fun GroupMemberInfoLayout(
rhId: Long?,
groupInfo: GroupInfo,
member: GroupMember,
scrollToItemId: MutableState<Long?>,
connStats: MutableState<ConnectionStats?>,
newRole: MutableState<GroupMemberRole>,
developerTools: Boolean,
@ -299,6 +303,29 @@ fun GroupMemberInfoLayout(
}
}
@Composable
fun SupportChatButton() {
val scope = rememberCoroutineScope()
SettingsActionItem(
painterResource(MR.images.ic_flag),
stringResource(MR.strings.button_support_chat_member),
click = {
val scopeInfo = GroupChatScopeInfo.MemberSupport(groupMember_ = member)
val supportChatInfo = ChatInfo.Group(groupInfo, groupChatScope = scopeInfo)
scope.launch {
showMemberSupportChatView(
chatModel.chatId,
scrollToItemId = scrollToItemId,
supportChatInfo,
scopeInfo
)
}
},
iconColor = MaterialTheme.colors.secondary,
)
}
@Composable
fun ModeratorDestructiveSection() {
val canBlockForAll = member.canBlockForAll(groupInfo)
@ -413,6 +440,12 @@ fun GroupMemberInfoLayout(
if (member.memberActive) {
SectionView {
if (
groupInfo.membership.memberRole >= GroupMemberRole.Moderator &&
(member.memberRole < GroupMemberRole.Moderator || member.supportChat != null)
) {
SupportChatButton()
}
if (connectionCode != null) {
VerifyCodeButton(member.verified, verifyClicked)
}
@ -878,6 +911,7 @@ fun PreviewGroupMemberInfoLayout() {
rhId = null,
groupInfo = GroupInfo.sampleData,
member = GroupMember.sampleData,
scrollToItemId = remember { mutableStateOf(null) },
connStats = remember { mutableStateOf(null) },
newRole = remember { mutableStateOf(GroupMemberRole.Member) },
developerTools = false,

View file

@ -44,7 +44,7 @@ fun SelectedItemsMembersToolbar(
) {
// It's hard to measure exact height of ComposeView with different fontSizes. Better to depend on actual ComposeView, even empty
Box(Modifier.alpha(0f)) {
ComposeView(chatModel = chatModel, Chat.sampleData, remember { mutableStateOf(ComposeState(useLinkPreviews = false)) }, remember { mutableStateOf(null) }, {}, remember { FocusRequester() })
ComposeView(rhId = null, chatModel = chatModel, chatModel.chatsContext, Chat.sampleData, remember { mutableStateOf(ComposeState(useLinkPreviews = false)) }, remember { mutableStateOf(null) }, {}, remember { FocusRequester() })
}
Row(
Modifier

View file

@ -35,6 +35,7 @@ private val MAX_PICKER_HEIGHT = (PICKER_ROW_SIZE * 4) + (MEMBER_ROW_AVATAR_SIZE
@Composable
fun GroupMentions(
chatsCtx: ChatModel.ChatsContext,
rhId: Long?,
composeState: MutableState<ComposeState>,
composeViewFocusRequester: FocusRequester?,
@ -48,12 +49,31 @@ fun GroupMentions(
val mentionName = remember { mutableStateOf("") }
val mentionRange = remember { mutableStateOf<TextRange?>(null) }
val mentionMemberId = remember { mutableStateOf<String?>(null) }
fun contextMemberFilter(member: GroupMember): Boolean =
when (chatsCtx.secondaryContextFilter) {
null -> true
is SecondaryContextFilter.GroupChatScopeContext ->
when (chatsCtx.secondaryContextFilter.groupScopeInfo) {
is GroupChatScopeInfo.MemberSupport -> {
val scopeMember = chatsCtx.secondaryContextFilter.groupScopeInfo.groupMember_
if (scopeMember != null) {
member.memberRole >= GroupMemberRole.Moderator || member.groupMemberId == scopeMember.groupMemberId
} else {
member.memberRole >= GroupMemberRole.Moderator
}
}
}
is SecondaryContextFilter.MsgContentTagContext -> false
}
val filteredMembers = remember {
derivedStateOf {
val members = chatModel.groupMembers.value
.filter {
val status = it.memberStatus
status != GroupMemberStatus.MemLeft && status != GroupMemberStatus.MemRemoved && status != GroupMemberStatus.MemInvited
&& contextMemberFilter(it)
}
.sortedByDescending { it.memberRole }

View file

@ -17,7 +17,9 @@ import chat.simplex.common.views.usersettings.PreferenceToggleWithIcon
import chat.simplex.common.model.*
import chat.simplex.common.platform.ColumnWithScrollBar
import chat.simplex.common.platform.chatModel
import chat.simplex.common.views.usersettings.SettingsActionItem
import chat.simplex.res.MR
import dev.icerock.moko.resources.compose.painterResource
import kotlinx.coroutines.*
private val featureRoles: List<Pair<GroupMemberRole?, String>> = listOf(
@ -71,6 +73,16 @@ fun GroupPreferencesView(m: ChatModel, rhId: Long?, chatId: String, close: () ->
preferences = currentPreferences
},
savePrefs = ::savePrefs,
openMemberAdmission = {
ModalManager.end.showCustomModal { close ->
MemberAdmissionView(
chatModel,
rhId,
chatId,
close
)
}
}
)
}
}
@ -83,10 +95,15 @@ private fun GroupPreferencesLayout(
applyPrefs: (FullGroupPreferences) -> Unit,
reset: () -> Unit,
savePrefs: () -> Unit,
openMemberAdmission: () -> Unit,
) {
ColumnWithScrollBar {
val titleId = if (groupInfo.businessChat == null) MR.strings.group_preferences else MR.strings.chat_preferences
AppBarTitle(stringResource(titleId))
if (groupInfo.businessChat == null) {
MemberAdmissionButton(openMemberAdmission)
SectionDividerSpaced(maxBottomPadding = false)
}
val timedMessages = remember(preferences) { mutableStateOf(preferences.timedMessages.enable) }
val onTTLUpdated = { ttl: Int? ->
applyPrefs(preferences.copy(timedMessages = preferences.timedMessages.copy(ttl = ttl)))
@ -156,6 +173,15 @@ private fun GroupPreferencesLayout(
}
}
@Composable
private fun MemberAdmissionButton(onClick: () -> Unit) {
SettingsActionItem(
painterResource(MR.images.ic_toggle_on),
stringResource(MR.strings.member_admission),
click = onClick
)
}
@Composable
private fun FeatureSection(
feature: GroupFeature,

View file

@ -15,7 +15,15 @@ import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.flow.*
@Composable
private fun GroupReportsView(reportsChatsCtx: ChatModel.ChatsContext, staleChatId: State<String?>, scrollToItemId: MutableState<Long?>) {
private fun GroupReportsView(
reportsChatsCtx: ChatModel.ChatsContext,
staleChatId: State<String?>,
scrollToItemId: MutableState<Long?>,
close: () -> Unit
) {
KeyChangeEffect(chatModel.chatId.value) {
close()
}
ChatView(reportsChatsCtx, staleChatId, scrollToItemId, onComposed = {})
}
@ -53,7 +61,7 @@ fun GroupReportsAppBar(
}
@Composable
private fun ItemsReload(chatsCtx: ChatModel.ChatsContext,) {
fun ItemsReload(chatsCtx: ChatModel.ChatsContext,) {
LaunchedEffect(Unit) {
snapshotFlow { chatModel.chatId.value }
.distinctUntilChanged()
@ -69,13 +77,13 @@ private fun ItemsReload(chatsCtx: ChatModel.ChatsContext,) {
}
suspend fun showGroupReportsView(staleChatId: State<String?>, scrollToItemId: MutableState<Long?>, chatInfo: ChatInfo) {
val reportsChatsCtx = ChatModel.ChatsContext(contentTag = MsgContentTag.Report)
val reportsChatsCtx = ChatModel.ChatsContext(secondaryContextFilter = SecondaryContextFilter.MsgContentTagContext(MsgContentTag.Report))
openChat(secondaryChatsCtx = reportsChatsCtx, chatModel.remoteHostId(), chatInfo)
ModalManager.end.showCustomModal(true, id = ModalViewId.SECONDARY_CHAT) { close ->
ModalView({}, showAppBar = false) {
val chatInfo = remember { derivedStateOf { chatModel.chats.value.firstOrNull { it.id == chatModel.chatId.value }?.chatInfo } }.value
if (chatInfo is ChatInfo.Group && chatInfo.groupInfo.canModerate) {
GroupReportsView(reportsChatsCtx, staleChatId, scrollToItemId)
GroupReportsView(reportsChatsCtx, staleChatId, scrollToItemId, close)
} else {
LaunchedEffect(Unit) {
close()

View file

@ -0,0 +1,152 @@
package chat.simplex.common.views.chat.group
import InfoRow
import SectionBottomSpacer
import SectionDividerSpaced
import SectionItemView
import SectionTextFooter
import SectionView
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import dev.icerock.moko.resources.compose.stringResource
import chat.simplex.common.views.helpers.*
import chat.simplex.common.model.*
import chat.simplex.common.platform.ColumnWithScrollBar
import chat.simplex.common.platform.chatModel
import chat.simplex.res.MR
import dev.icerock.moko.resources.StringResource
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Composable
fun MemberAdmissionView(m: ChatModel, rhId: Long?, chatId: String, close: () -> Unit) {
val groupInfo = remember { derivedStateOf {
val ch = m.getChat(chatId)
val g = (ch?.chatInfo as? ChatInfo.Group)?.groupInfo
if (g == null || ch.remoteHostId != rhId) null else g
}}
val gInfo = groupInfo.value ?: return
var admission by rememberSaveable(gInfo, stateSaver = serializableSaver()) { mutableStateOf(gInfo.groupProfile.memberAdmission) }
var currentAdmission by rememberSaveable(gInfo, stateSaver = serializableSaver()) { mutableStateOf(admission) }
fun saveAdmission(afterSave: () -> Unit = {}) {
withBGApi {
val gp = gInfo.groupProfile.copy(memberAdmission = admission)
val g = m.controller.apiUpdateGroup(rhId, gInfo.groupId, gp)
if (g != null) {
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(rhId, g)
currentAdmission = admission
}
}
afterSave()
}
}
ModalView(
close = {
if (admission == currentAdmission) close()
else showUnsavedChangesAlert({ saveAdmission(close) }, close)
},
) {
MemberAdmissionLayout(
admission,
currentAdmission,
gInfo,
applyAdmission = { admsn ->
admission = admsn
},
reset = {
admission = currentAdmission
},
saveAdmission = ::saveAdmission,
)
}
}
@Composable
private fun MemberAdmissionLayout(
admission: GroupMemberAdmission?,
currentAdmission: GroupMemberAdmission?,
groupInfo: GroupInfo,
applyAdmission: (GroupMemberAdmission) -> Unit,
reset: () -> Unit,
saveAdmission: () -> Unit,
) {
ColumnWithScrollBar {
AppBarTitle(stringResource(MR.strings.member_admission))
val review = remember(admission) { mutableStateOf(admission?.review) }
AdmissionSection(MR.strings.admission_stage_review, MR.strings.admission_stage_review_descr, review, groupInfo) { criteria ->
if (admission != null) {
applyAdmission(admission.copy(review = criteria))
} else {
applyAdmission(GroupMemberAdmission(review = criteria))
}
}
if (groupInfo.isOwner) {
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
ResetSaveButtons(
reset = reset,
save = saveAdmission,
disabled = admission == currentAdmission
)
}
SectionBottomSpacer()
}
}
private val memberCriterias: List<Pair<MemberCriteria?, String>> = listOf(
null to generalGetString(MR.strings.member_criteria_off),
MemberCriteria.All to generalGetString(MR.strings.member_criteria_all)
)
@Composable
private fun AdmissionSection(
admissionStageStrId: StringResource,
admissionStageDescrStrId: StringResource,
memberCriteria: State<MemberCriteria?>,
groupInfo: GroupInfo,
onSelected: (MemberCriteria?) -> Unit
) {
SectionView {
if (groupInfo.isOwner) {
ExposedDropDownSettingRow(
generalGetString(admissionStageStrId),
memberCriterias,
memberCriteria,
onSelected = { value ->
onSelected(value)
}
)
} else {
InfoRow(
stringResource(admissionStageStrId),
memberCriteria.value?.text ?: generalGetString(MR.strings.member_criteria_off)
)
}
}
SectionTextFooter(stringResource( admissionStageDescrStrId))
}
@Composable
private fun ResetSaveButtons(reset: () -> Unit, save: () -> Unit, disabled: Boolean) {
SectionView {
SectionItemView(reset, disabled = disabled) {
Text(stringResource(MR.strings.reset_verb), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary)
}
SectionItemView(save, disabled = disabled) {
Text(stringResource(MR.strings.save_and_notify_group_members), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary)
}
}
}
private fun showUnsavedChangesAlert(save: () -> Unit, revert: () -> Unit) {
AlertManager.shared.showAlertDialogStacked(
title = generalGetString(MR.strings.save_admission_question),
confirmText = generalGetString(MR.strings.save_and_notify_group_members),
dismissText = generalGetString(MR.strings.exit_without_saving),
onConfirm = save,
onDismiss = revert,
)
}

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