Compare commits

...

115 Commits

Author SHA1 Message Date
Evgeny Poberezkin 0d62d7ddfb Merge branch 'master' into ab/diff-subs 2024-05-28 08:06:25 +01:00
Stanislav Dmitrenko b58ccedf23 android, desktop: enhancements to wallpapers (#4234)
* android, desktop: enhancements to wallpapers

* fixes

* unneeded
2024-05-28 08:02:45 +01:00
Evgeny Poberezkin a78aeb47c5 Merge branch 'master' into ab/diff-subs 2024-05-27 16:00:55 +01:00
spaced4ndy 01cadefde7 core: avoid creating duplicate introductions between group members connecting concurrently (#4235) 2024-05-27 18:42:39 +04:00
Evgeny Poberezkin c70e7223d9 core: delete connection records when connecting (JOIN) fails (#4233)
* core: delete connection records when connecting (JOIN) fails

* show errors in commands
2024-05-27 15:32:09 +01:00
Evgeny Poberezkin a1d4f52185 5.8-beta.3: ios 220, android 214, desktop 49 2024-05-25 20:57:07 +01:00
Evgeny Poberezkin 373070f388 ui: show UI for additional group preferences (SimpleX links and per-role permissions) 2024-05-25 19:54:05 +01:00
Evgeny Poberezkin a03ccd0ba6 ios: update core library 5.8.0.3 2024-05-25 18:55:15 +01:00
Evgeny Poberezkin a7982cccf9 Merge branch 'stable' 2024-05-25 17:22:25 +01:00
Evgeny Poberezkin dccd6631af 5.7.4: ios 219, android 212, desktop 48 2024-05-25 17:06:11 +01:00
Evgeny Poberezkin b0516b6440 core: 5.7.4.1 2024-05-25 11:18:49 +01:00
Stanislav Dmitrenko bf91666f07 android, desktop: wallpapers (#4109)
* android, desktop: wallpapers

* changes

* removed unused

* changes

* backgrounds moved to themes. can be exported/imported

* changes and refactoring

* scrollable

* change

* reorder

* theme per background

* changes

* don't drop customization in same cases

* changes

* changes

* fix state update

* different defaults

* fix Android

* removed migration

* temporary

* returned theme name as uppercase back

* resilient to wrong preset name

* more

* live color change without button

* fix

* Revert "temporary"

This reverts commit 705c92ee56.

* changes

* changes

* almost candy

* old picker and ability to specify hex color

* different image

* picker

* user theme override, fixes, import/export theme with image

* revert when about to merge: useful layout for theming

* custom image displaying and deletion work correctly

* performance

* moved code up

* changes

* color changes in picker when changing themes

* reset color without closing modal

* unused

* log

* currentTheme instead of MaterialTheme

* different inheritance logic

* fix and move sent/received message colors to top

* remove background

* performance

* fix wrong base

* changes

* fix

* small

* custom image selection made normally

* better color picker

* better picker

* refactor, fixes, optimizations

* working with unknown base theme + tests

* change

* delete wallpaper dir when importing archive

* function rename

* fix bug with state update

* update colors

* fix text colors

* update images

* changes

* remote desktop and theme mode dropdown

* default theme

* additional accent 2 changes

* fix default wallpaper

* removed ChatListAlwaysVisible

* renaming

* update messages

* changes

* default theme

* unused

* member icon background color

* image quality

* fix wallpaper on empty notes screen

* update colors

* change

* update colors

* fix update on android

* fix update on both platforms

* update colors, remove serializable

* fix color

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-05-25 09:52:16 +01:00
Evgeny Poberezkin ab47a5a27e Revert "ui: comment smp proxy ui (#4204)"
This reverts commit a0d6ae15ab.
2024-05-24 22:59:37 +01:00
Evgeny Poberezkin 23581f59ae core: 5.8.0.3 2024-05-24 21:33:45 +01:00
Evgeny Poberezkin 291df6e9d0 core: logging of chat events (#4216)
* core: update simplexmq (persist server errors)

* fix

* same config

* logging

* logging 2

* log

* log 2

* finally

* catch better

* more logs

* logs

* fix

* more logging, context from PROHIBITED

* warning

* more logs

* logs3

* logs4

* logs in simplexmq

* log locks from simplemq

* log queue size

* log sendMessagesB in simplexmq

* update simplexmq

* logs5

* logs6

* logs7

* logs8

* logs8

* logs9

* logs10

* log11

* log12

* fix test

* more logs

* logging

* clean up

* refactor

* simplify

* tags

* log level

* remove network errors from the log

* rename
2024-05-24 21:09:21 +01:00
Evgeny Poberezkin dae0b63c22 Merge branch 'stable' 2024-05-23 22:56:04 +01:00
Evgeny Poberezkin 0964982782 core: ensure that messages are sent to different members (#4221)
* core: ensure that messages are sent to different members

* update simplexmq
2024-05-23 22:50:23 +01:00
Evgeny Poberezkin 9eb6034e1a core: 5.7.4.0 (simplexmq 5.7.4.1: networking improvements) 2024-05-22 20:30:24 +01:00
Evgeny Poberezkin c59eb06b8a 5.8-beta.2: ios 218, android 210, desktop 47 2024-05-20 21:23:40 +01:00
Evgeny Poberezkin 423fc96638 core: 5.8.0.2 (simplexmq 5.8.0.2) 2024-05-20 18:38:46 +01:00
spaced4ndy ba203faad4 android: check known relays before file reception, support user approval of unknown relays (#4196) 2024-05-20 17:58:30 +04:00
spaced4ndy ec7b35adb9 ios: check known relays before file reception, support user approval of unknown relays (#4044)
* ios: check known relays before file reception, support user approval of unknown relays

* add privacyAskToApproveRelays to AppSettings

* less text

* ui improvements, fixes

* fix ntf

* shorter alert

* simplify settings

* rename

* icon

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-05-20 17:49:19 +04:00
spaced4ndy d2d450d1d7 core: check known relays before file reception, support user approval of unknown relays (#4043)
* core: check known relays before file reception, support user approval of unknown relays

* comment

* reset on not approved agent error

* add privacyAskToApproveRelays to AppSettings

* filter distinct servers

* update simplexmq

* remember user_approved_relays

* refactor

* rename

* update simplexmq

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-05-20 17:23:29 +04:00
IC Rainbow 94df5d47d1 Merge remote-tracking branch 'origin/master' into ab/diff-subs 2024-05-20 13:11:55 +03:00
Evgeny Poberezkin b4caefb17c 5.8-beta.1: ios 217, android 209, desktop 46 2024-05-18 08:20:38 +01:00
Evgeny Poberezkin fd791f3fb8 ios: update core library 2024-05-17 15:54:51 +01:00
Evgeny Poberezkin 0720292ebf core: 5.8.0.1 (simplexmq: 5.8.0.1, faster reconnection when switching networks) 2024-05-17 15:39:58 +01:00
spaced4ndy a0d6ae15ab ui: comment smp proxy ui (#4204) 2024-05-17 16:36:25 +04:00
Evgeny Poberezkin 3e76a2d49c blog: image 2024-05-16 19:47:24 +01:00
Evgeny Poberezkin f9cba3f8e0 Merge branch 'master' into ab/diff-subs 2024-05-16 19:11:49 +01:00
Evgeny Poberezkin ed3365bb6d website: fix links 2024-05-16 18:16:00 +01:00
Evgeny Poberezkin b2a8797ed4 blog: Redefining Privacy by Making Hard Choices (#4195)
* blog: Redefining Privacy by Making Hard Choices

* update

* update

* revert

* update

* corrections

Co-authored-by: Esra'a Al Shafei <alshafei@users.noreply.github.com>

* update

---------

Co-authored-by: Esra'a Al Shafei <alshafei@users.noreply.github.com>
2024-05-16 18:02:37 +01:00
spaced4ndy d3e522e169 ui: disable send field for disabled connection (based on authErrCounter) (#4193) 2024-05-16 17:08:31 +04:00
spaced4ndy 49e09ca449 core: contact disabled event (#4194) 2024-05-16 17:08:13 +04:00
spaced4ndy d1dde3d0c8 ui: support message forwarding with custom ttl (#4192) 2024-05-16 14:12:58 +04:00
spaced4ndy ef75eceb40 core: support message forwarding with custom ttl (#4191) 2024-05-16 14:11:55 +04:00
spaced4ndy 96ce59f330 multiplatform: SMP proxy configuration buttons (#4188)
* multiplatform: SMP proxy configuration buttons

* fix

* icons

* icon

* icon
2024-05-15 20:09:51 +01:00
Evgeny Poberezkin 3655971aec Merge branch 'master' into ab/diff-subs 2024-05-15 13:57:39 +01:00
spaced4ndy 98a67fa2e4 multiplatform: SMP proxy support (#4179)
* android: SMP proxy support

* android types

* show in meta

* reserve

* remove comment

* texts
2024-05-15 13:56:29 +01:00
spaced4ndy 4b13512950 ios: SMP proxy support (#4178)
* ios: SMP proxy support

* statuses

* group statuses

* error texts

* update

* change icon

* texts

* texts

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-05-15 13:09:42 +01:00
Alexander Bondarenko dea45e3228 Merge remote-tracking branch 'origin/master' into ab/diff-subs 2024-05-15 14:58:08 +03:00
spaced4ndy 4c0d47bbd4 core: message statuses for sending proxies (#4161)
* core: delivery path

* update simplexmq

* via proxy snd flags

* error statuses

* rework errors

* proxy expired errors

* corrections

* move backwards compatibile parser to new type

* update simplexmq

* names

* refactor, style

* simplexmq

* refactor

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-05-15 12:30:05 +01:00
Evgeny Poberezkin 3129602a78 Merge branch 'stable' 2024-05-15 11:44:22 +01:00
Stanislav Dmitrenko a0c257962e android, desktop: fix file menu and icon (#4185)
* android, desktop: fix file menu and icon

* simplify

* space
2024-05-15 11:43:20 +01:00
Evgeny Poberezkin 93ae1145bc core: update simplexmq (use MVar for better DB connection concurrency) (#4183)
* core: update simplexmq (use MVar for better DB connection concurrency)

* focus failing tests

* add timeouts to test

* fix tests

* more delays

* increase timeouts

* prints

* delay

* delay

* empty

* more delays

* enable all

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2024-05-15 11:16:38 +01:00
Evgeny Poberezkin d02668386f directory: debug logging (#4104) 2024-05-14 21:32:53 +01:00
M. Sarmad Qadeer bd8c8b5a8c web: fix the links of faq & some other (#4144)
* web: fix the links of faq & some other

* web: fix links

* web: update .eleventy.js to fix links

* web: fix some links and improve the logic

* web: fix links & improve the logic

* remove semicolon

* update links

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-05-14 19:59:51 +01:00
Alexander Bondarenko 4f2102ac6e use user and diff filters 2024-05-14 16:56:28 +03:00
IC Rainbow 17bcdaf6c4 Merge remote-tracking branch 'origin/master' into ab/diff-subs 2024-05-14 13:41:38 +03:00
Evgeny Poberezkin e359ec02f5 5.8-beta.0: ios 216, android 208, desktop 45 2024-05-13 23:34:14 +01:00
Evgeny Poberezkin a1948d549b ios: update core library 2024-05-13 22:24:10 +01:00
Evgeny Poberezkin 396b73b480 core: 5.8.0.0 2024-05-13 21:13:07 +01:00
Evgeny Poberezkin 331b4633fc core: platform independent encoding for delete command (#4176) 2024-05-13 20:39:13 +01:00
Evgeny Poberezkin 29db2b4e9b core: command to show duplicate message count (#4172)
* core: command to show duplicate message count

* update

* updated

* update

* update

* simplexmq
2024-05-13 19:55:57 +01:00
spaced4ndy f091384aa7 ui: apply model updates only for active user for switch and ratchet sync events (#4175) 2024-05-13 17:25:24 +01:00
IC Rainbow 1a2fad070e Merge remote-tracking branch 'origin/master' into ab/diff-subs 2024-05-13 15:56:29 +03:00
spaced4ndy f40ba6f04d core: api to differentiate contacts and conversations (#4111) 2024-05-13 16:51:54 +04:00
Evgeny Poberezkin 06d61ea73e core: update simplexmq 2024-05-13 08:19:40 +01:00
Evgeny Poberezkin 84d7a77a9f core: update simplexmq (better subscription management) (#4131)
* core: update simplexmq (better subscription management)

* ui: update settings

* update simplexmq

* simplexmq

* simplexmq

* test output

* delay

* disable test
2024-05-13 08:04:12 +01:00
Evgeny Poberezkin 878eea774d core: save app themes as map with any text key (#4159)
* core: save app themes as map with any text key

* remove theme preset name

* list of themes

* theme id

* theme IDs

* moar

* colors

* default to dark

* fix
2024-05-12 23:35:14 +01:00
IC Rainbow 6101be5657 update mq 2024-05-12 21:55:35 +03:00
Narasimha-sc 4575dc6e30 langs: change 1-2 days to several days on fdroid (#4170) 2024-05-12 18:19:27 +01:00
Evgeny Poberezkin a10dd81a1d Merge branch 'master' into ab/diff-subs 2024-05-12 12:07:26 +01:00
Evgeny Poberezkin 554ef4eefc 5.7.3: ios 215, android 206, desktop 44 2024-05-12 11:36:30 +01:00
Evgeny Poberezkin 289130832a Merge branch 'master' into ab/diff-subs 2024-05-11 23:59:15 +01:00
Evgeny Poberezkin 567e9981be core: 5.7.3.0 2024-05-11 23:48:39 +01:00
Evgeny Poberezkin 224cfe9e02 Merge branch 'master' into ab/diff-subs 2024-05-11 13:07:05 +01:00
Evgeny Poberezkin 1183fe427f Merge branch 'stable' 2024-05-10 20:30:42 +01:00
Alexander Bondarenko 0679d023e2 Merge remote-tracking branch 'origin/master' into ab/diff-subs 2024-05-10 21:01:33 +03:00
Evgeny Poberezkin c49a1f42e7 5.7.2: ios 214, android 204, desktop 43 2024-05-10 18:32:07 +01:00
Narasimha-sc 4f173215c6 docs: fix broken simplex group invite links (#4155)
* Fix non-working #simplex-devs invite

* Fix #simplex-devs link in pl

* replace SimpleX-Group-2

* replace simplex-group-2 link

* Update README.md
2024-05-10 17:43:06 +01:00
Evgeny Poberezkin 41597e563e website: translations (#4165)
* Translated using Weblate (French)

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

* Translated using Weblate (Czech)

Currently translated at 98.0% (252 of 257 strings)

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

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

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

* Translated using Weblate (Czech)

Currently translated at 98.0% (252 of 257 strings)

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

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

---------

Co-authored-by: J'informatique <hello@jinformatique.net>
Co-authored-by: zenobit <zen@osowoso.xyz>
Co-authored-by: link2xt <link2xt@testrun.org>
2024-05-10 17:39:51 +01:00
Evgeny Poberezkin aa127f0cea ui: translations (#4164)
* Translated using Weblate (Japanese)

Currently translated at 100.0% (1741 of 1741 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 100.0% (1515 of 1515 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 100.0% (1741 of 1741 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1741 of 1741 strings)

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

* Translated using Weblate (Persian)

Currently translated at 25.3% (441 of 1741 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1515 of 1515 strings)

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

* Translated using Weblate (Vietnamese)

Currently translated at 10.1% (176 of 1741 strings)

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

* Translated using Weblate (Persian)

Currently translated at 26.8% (467 of 1741 strings)

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

* Translated using Weblate (Persian)

Currently translated at 33.6% (586 of 1741 strings)

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

* Translated using Weblate (Romanian)

Currently translated at 6.0% (105 of 1741 strings)

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

* Translated using Weblate (Persian)

Currently translated at 36.8% (642 of 1741 strings)

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

* Translated using Weblate (Romanian)

Currently translated at 9.1% (159 of 1741 strings)

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

* Translated using Weblate (Persian)

Currently translated at 39.0% (679 of 1741 strings)

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

* Translated using Weblate (Persian)

Currently translated at 39.0% (680 of 1741 strings)

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

* import/export localizations

---------

Co-authored-by: Miyu Sakatsuki <miyu-sakatsuki@outlook.jp>
Co-authored-by: z0rden <kaanpeker196@gmail.com>
Co-authored-by: summoner001 <summoner@vivaldi.net>
Co-authored-by: Siavash <ciavash@proton.me>
Co-authored-by: tuananh-ng <158744840+tuananh-ng@users.noreply.github.com>
Co-authored-by: Flavian <3zorro.1@gmail.com>
2024-05-10 17:38:42 +01:00
Evgeny Poberezkin f9c64ba39c ios: update core library 2024-05-10 17:22:39 +01:00
Evgeny Poberezkin 1025fa94a1 core: 5.7.2.0 (simplexmq 5.7.3.0: update protocol versions, fix file downloads) 2024-05-10 15:18:42 +01:00
Stanislav Dmitrenko a59769b91d android, desktop: fix crash in contact preferences when updating user profile (#4158) 2024-05-10 09:45:44 +01:00
spaced4ndy 76248b4878 core: on receive set file crypto args only for new files (#4152) 2024-05-09 15:40:23 +04:00
Stanislav Dmitrenko 93a7ddb128 android, desktop: fix presenting large images (#4139)
* android, desktop: fix presenting large images

* 4320 px limitation

* revert line

* onClick

* onClick
2024-05-09 10:39:49 +01:00
Evgeny Poberezkin bc5af35a3e core: wallpapers api (#4110)
* core: wallpapers api

* optional colors

* update

* api

* update

* whitespace

* typo

* test, fix

* fix color parsing

* separate UI and Theme color schemes

* update

* enable test

* multiple themes, one per color scheme

* theme overrides as a separate type

* rename

---------

Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
2024-05-08 15:36:20 +01:00
Alexander Bondarenko 6d5869e974 Merge remote-tracking branch 'origin/master' into ab/diff-subs 2024-05-08 16:42:54 +03:00
Alexander Bondarenko f0974af03a sync 2024-05-08 16:13:42 +03:00
Alexander Bondarenko da70aa30b6 debug: use client diffs in /get subs 2024-05-08 16:09:05 +03:00
Evgeny Poberezkin e38d5bd885 core: update simplexmq (upgrade client protocol versions SMP v7, NTF v2) 2024-05-08 13:40:25 +01:00
M. Sarmad Qadeer 10f41aa1c9 web: add scroll to navbar (#4100)
* web: add scroll to navbar

* web: add scroll to whole navbar instead of scrolling the opened menu
2024-05-08 09:31:02 +01:00
spaced4ndy 1b081197d4 multiplatform: add error to response (#4140) 2024-05-06 20:27:35 +04:00
M. Sarmad Qadeer 9615e525c0 web: fix list numbering for safari (#4106)
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-05-06 08:36:13 +01:00
M. Sarmad Qadeer db136116d3 web: make image of full width for mobile screen (#4101) 2024-05-06 08:31:34 +01:00
Evgeny Poberezkin 98f5e5d22b 5.7.1: ios 213, android 202, desktop 42 2024-05-05 21:01:57 +01:00
Evgeny Poberezkin 9f33e5d35e website: translations (#4136)
* Translated using Weblate (German)

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

* Translated using Weblate (Dutch)

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

* Translated using Weblate (Arabic)

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

* Translated using Weblate (Italian)

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

* Translated using Weblate (Polish)

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

* Translated using Weblate (German)

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

* Translated using Weblate (Dutch)

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

* Translated using Weblate (Arabic)

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

* Translated using Weblate (Italian)

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

* Translated using Weblate (Polish)

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

* Translated using Weblate (Arabic)

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

* Translated using Weblate (Chinese (Simplified))

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 (Japanese)

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

---------

Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: M1K4 <oomikaoo@gmail.com>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: Miyu Sakatsuki <miyu-sakatsuki@outlook.jp>
2024-05-05 19:00:16 +01:00
Evgeny Poberezkin b8e8124faf ui: translations (#4135)
* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1515 of 1515 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (1741 of 1741 strings)

Co-authored-by: summoner001 <summoner@vivaldi.net>
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/
Translation: SimpleX Chat/SimpleX Chat Android
Translation: SimpleX Chat/SimpleX Chat iOS

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1515 of 1515 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (1741 of 1741 strings)

Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/
Translation: SimpleX Chat/SimpleX Chat Android
Translation: SimpleX Chat/SimpleX Chat iOS

* Translated using Weblate (Polish)

Currently translated at 100.0% (1515 of 1515 strings)

Translated using Weblate (Polish)

Currently translated at 99.9% (1740 of 1741 strings)

Translated using Weblate (Polish)

Currently translated at 99.1% (1727 of 1741 strings)

Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pl/
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/pl/
Translation: SimpleX Chat/SimpleX Chat Android
Translation: SimpleX Chat/SimpleX Chat iOS

* Translated using Weblate (German)

Currently translated at 100.0% (1515 of 1515 strings)

Translated using Weblate (German)

Currently translated at 100.0% (1741 of 1741 strings)

Co-authored-by: mlanp <github@lang.xyz>
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/
Translation: SimpleX Chat/SimpleX Chat Android
Translation: SimpleX Chat/SimpleX Chat iOS

* Translated using Weblate (Italian)

Currently translated at 100.0% (1515 of 1515 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (1741 of 1741 strings)

Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/
Translation: SimpleX Chat/SimpleX Chat Android
Translation: SimpleX Chat/SimpleX Chat iOS

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1515 of 1515 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (1741 of 1741 strings)

Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/
Translation: SimpleX Chat/SimpleX Chat Android
Translation: SimpleX Chat/SimpleX Chat iOS

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1515 of 1515 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (1741 of 1741 strings)

Co-authored-by: M1K4 <oomikaoo@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/
Translation: SimpleX Chat/SimpleX Chat Android
Translation: SimpleX Chat/SimpleX Chat iOS

* Translated using Weblate (Arabic)

Currently translated at 100.0% (1741 of 1741 strings)

Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/
Translation: SimpleX Chat/SimpleX Chat Android

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1741 of 1741 strings)

Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/
Translation: SimpleX Chat/SimpleX Chat Android

* Translated using Weblate (Japanese)

Currently translated at 100.0% (1741 of 1741 strings)

Co-authored-by: Miyu Sakatsuki <miyu-sakatsuki@outlook.jp>
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/
Translation: SimpleX Chat/SimpleX Chat Android

* Translated using Weblate (Czech)

Currently translated at 98.1% (1709 of 1741 strings)

Co-authored-by: zenobit <zen@osowoso.xyz>
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/
Translation: SimpleX Chat/SimpleX Chat Android

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1741 of 1741 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1515 of 1515 strings)

Co-authored-by: elgratea <weblate@fastmail.com>
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/bg/
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/bg/
Translation: SimpleX Chat/SimpleX Chat Android
Translation: SimpleX Chat/SimpleX Chat iOS

* Translated using Weblate (Turkish)

Currently translated at 100.0% (1741 of 1741 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (1515 of 1515 strings)

Co-authored-by: z0rden <kaanpeker196@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/tr/
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/tr/
Translation: SimpleX Chat/SimpleX Chat Android
Translation: SimpleX Chat/SimpleX Chat iOS

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1515 of 1515 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (1741 of 1741 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (1515 of 1515 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (1741 of 1741 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (1515 of 1515 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (1741 of 1741 strings)

Co-authored-by: summoner001 <summoner@vivaldi.net>
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/
Translation: SimpleX Chat/SimpleX Chat Android
Translation: SimpleX Chat/SimpleX Chat iOS

* Translated using Weblate (Japanese)

Currently translated at 80.7% (1224 of 1515 strings)

Translated using Weblate (Japanese)

Currently translated at 80.3% (1218 of 1515 strings)

Co-authored-by: koreirozzu <trueworldfacts+asjfsajfhajfh@proton.me>
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ja/
Translation: SimpleX Chat/SimpleX Chat iOS

* Translated using Weblate (Japanese)

Currently translated at 80.3% (1218 of 1515 strings)

Co-authored-by: a4318 <dalse.077@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ja/
Translation: SimpleX Chat/SimpleX Chat iOS

* Translated using Weblate (Turkish)

Currently translated at 100.0% (1515 of 1515 strings)

Co-authored-by: xe1st <dnzkckali@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/tr/
Translation: SimpleX Chat/SimpleX Chat iOS

* Translated using Weblate (Turkish)

Currently translated at 100.0% (1741 of 1741 strings)

Co-authored-by: J R <jr@simplex.chat>
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/tr/
Translation: SimpleX Chat/SimpleX Chat Android

* Translated using Weblate (Persian)

Currently translated at 9.8% (171 of 1741 strings)

Translated using Weblate (Persian)

Currently translated at 9.4% (164 of 1741 strings)

Co-authored-by: Siavash <ciavash@proton.me>
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fa/
Translation: SimpleX Chat/SimpleX Chat Android

* Translated using Weblate (Persian)

Currently translated at 10.1% (176 of 1741 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1741 of 1741 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1515 of 1515 strings)

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

* Translated using Weblate (Persian)

Currently translated at 11.9% (208 of 1741 strings)

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

* Translated using Weblate (Vietnamese)

Currently translated at 8.5% (148 of 1741 strings)

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

* import/export localizations

* tr revert

---------

Co-authored-by: summoner001 <summoner@vivaldi.net>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: M1K4 <oomikaoo@gmail.com>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: Miyu Sakatsuki <miyu-sakatsuki@outlook.jp>
Co-authored-by: zenobit <zen@osowoso.xyz>
Co-authored-by: elgratea <weblate@fastmail.com>
Co-authored-by: z0rden <kaanpeker196@gmail.com>
Co-authored-by: koreirozzu <trueworldfacts+asjfsajfhajfh@proton.me>
Co-authored-by: a4318 <dalse.077@gmail.com>
Co-authored-by: xe1st <dnzkckali@gmail.com>
Co-authored-by: J R <jr@simplex.chat>
Co-authored-by: Siavash <ciavash@proton.me>
Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: tuananh-ng <158744840+tuananh-ng@users.noreply.github.com>
2024-05-05 18:57:52 +01:00
Evgeny Poberezkin 156e41950a ios: update library 2024-05-05 18:01:14 +01:00
Evgeny Poberezkin 26c5ed4caf core: 5.7.1.0 (simplexmq 5.7.2.0: reduce "ping" traffic, remove TLS timeouts) 2024-05-05 17:43:09 +01:00
Evgeny Poberezkin d832afa7e8 docs: calls without e2e encryption (#4134) 2024-05-05 16:56:34 +01:00
Stanislav Dmitrenko 2e18b97077 android: enable Android 8 support (API 26) (#4132) 2024-05-05 16:30:17 +01:00
Evgeny Poberezkin b6e57c0fa2 core: fix connection failure because of race condition when connecting via link or joining group via invitation (#4133)
* core: fix connection failure because of race condition when connecting via link or joining group via invitation

* fix race for connection via contact address

* simplexmq
2024-05-05 13:11:30 +01:00
spaced4ndy 15a226cfd1 ui: limit length of member names in chat view (#4122) 2024-05-03 12:34:15 +01:00
Evgeny Poberezkin c4cbf751b9 Merge branch 'stable' 2024-05-02 23:28:59 +01:00
Stanislav Dmitrenko 535ff1d836 actions: add macos aarch64 build and fix openssl include path (#4107) 2024-05-02 21:41:15 +01:00
Evgeny Poberezkin fb0718adac core: forward agent errors without connections to UI (to show CRITICAL alerts) (#4120) 2024-05-02 21:40:08 +01:00
Stanislav Dmitrenko 5f501f1cb9 android, desktop: fix android video calls (#4118) 2024-05-02 16:47:52 +01:00
Evgeny Poberezkin 7cc86574fe core: update simplexmq to 5.7.1.0 2024-05-02 16:43:08 +01:00
Evgeny Poberezkin 19fc44efae rfc: commercial model for infrastructure operators (#4102) 2024-04-30 07:53:27 +01:00
spaced4ndy 37e03a838c core: forward group message before ack (fixes forwarding message that deleted connection causing error in ackMsg) (#4108) 2024-04-29 19:49:04 +04:00
spaced4ndy aeb28400e9 core: take entity lock before processing file (#4105) 2024-04-29 19:32:53 +04:00
sh d5a0c5e56a scripts/build-android: update nix and refactor (#4099) 2024-04-27 09:23:14 +01:00
Evgeny Poberezkin a28d965a65 website: fix privacy page 2024-04-26 23:24:54 +01:00
Evgeny Poberezkin 9148425eba blog: legally binding transparency, v5.7 announcement (#4097)
* blog: legally binding transparency, v5.7 announcement

* update

* update, images

* readme

* update
2024-04-26 23:06:40 +01:00
M. Sarmad Qadeer 406c35958e web: add privacy page to website (#4096)
* web: add privacy page to website

* web: fix a blog frontmatter

* web: fix privacy link from other docs

* update

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-04-26 23:00:49 +01:00
Evgeny Poberezkin be9ae61a32 5.7: ios 212, android 200, desktop 41 2024-04-26 20:59:21 +01:00
Stanislav Dmitrenko 9db65a1775 ios: audio device picker (#4091)
* ios: audio device picker

* removed unused

* removed logs

* correct routing

* Revert "removed unused"

This reverts commit d883d7a719.

* changes

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-04-26 19:59:00 +01:00
Evgeny Poberezkin 1033f55597 ios: fix sound in outgoing calls in iOS 15 2024-04-26 19:41:44 +01:00
Stanislav Dmitrenko 7f2462c936 android: black background in call (#4098) 2024-04-26 18:31:52 +01:00
Evgeny Poberezkin e675efe6f3 ios: update library 2024-04-26 17:38:57 +01:00
Evgeny Poberezkin b3f8068446 core: 5.7.0.5, increase min version of remote host/controller to 5.7.0.3 2024-04-26 15:42:13 +01:00
Evgeny Poberezkin 957eda8a78 ui: variable profile image size (#4094)
* ui: variable profile image size

* changes

* fix

* clean up

---------

Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
2024-04-25 22:09:06 +01:00
271 changed files with 8920 additions and 2369 deletions
+32 -9
View File
@@ -65,6 +65,11 @@ jobs:
asset_name: simplex-chat-ubuntu-22_04-x86-64 asset_name: simplex-chat-ubuntu-22_04-x86-64
desktop_asset_name: simplex-desktop-ubuntu-22_04-x86_64.deb desktop_asset_name: simplex-desktop-ubuntu-22_04-x86_64.deb
- os: macos-latest - os: macos-latest
ghc: "9.6.3"
cache_path: ~/.cabal/store
asset_name: simplex-chat-macos-aarch64
desktop_asset_name: simplex-desktop-macos-aarch64.dmg
- os: macos-13
ghc: "9.6.3" ghc: "9.6.3"
cache_path: ~/.cabal/store cache_path: ~/.cabal/store
asset_name: simplex-chat-macos-x86-64 asset_name: simplex-chat-macos-x86-64
@@ -107,18 +112,36 @@ jobs:
if: matrix.os == 'macos-latest' if: matrix.os == 'macos-latest'
shell: bash shell: bash
run: | run: |
echo "ignore-project: False" >> cabal.project.local echo "ignore-project: False" >> cabal.project.local
echo "package direct-sqlcipher" >> cabal.project.local echo "package simplexmq" >> cabal.project.local
echo " extra-include-dirs: /usr/local/opt/openssl@1.1/include" >> cabal.project.local echo " extra-include-dirs: /opt/homebrew/opt/openssl@1.1/include" >> cabal.project.local
echo " extra-lib-dirs: /usr/local/opt/openssl@1.1/lib" >> cabal.project.local echo " extra-lib-dirs: /opt/homebrew/opt/openssl@1.1/lib" >> cabal.project.local
echo " flags: +openssl" >> cabal.project.local echo "" >> cabal.project.local
echo "package direct-sqlcipher" >> cabal.project.local
echo " extra-include-dirs: /opt/homebrew/opt/openssl@1.1/include" >> cabal.project.local
echo " extra-lib-dirs: /opt/homebrew/opt/openssl@1.1/lib" >> cabal.project.local
echo " flags: +openssl" >> cabal.project.local
- name: Unix prepare cabal.project.local for Mac
if: matrix.os == 'macos-13'
shell: bash
run: |
echo "ignore-project: False" >> cabal.project.local
echo "package simplexmq" >> cabal.project.local
echo " extra-include-dirs: /usr/local/opt/openssl@1.1/include" >> cabal.project.local
echo " extra-lib-dirs: /usr/local/opt/openssl@1.1/lib" >> cabal.project.local
echo "" >> cabal.project.local
echo "package direct-sqlcipher" >> cabal.project.local
echo " extra-include-dirs: /usr/local/opt/openssl@1.1/include" >> cabal.project.local
echo " extra-lib-dirs: /usr/local/opt/openssl@1.1/lib" >> cabal.project.local
echo " flags: +openssl" >> cabal.project.local
- name: Install AppImage dependencies - name: Install AppImage dependencies
if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os == 'ubuntu-20.04' if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os == 'ubuntu-20.04'
run: sudo apt install -y desktop-file-utils run: sudo apt install -y desktop-file-utils
- name: Install pkg-config for Mac - name: Install pkg-config for Mac
if: matrix.os == 'macos-latest' if: matrix.os == 'macos-latest' || matrix.os == 'macos-13'
run: brew install pkg-config run: brew install pkg-config
- name: Unix prepare cabal.project.local for Ubuntu - name: Unix prepare cabal.project.local for Ubuntu
@@ -190,7 +213,7 @@ jobs:
- name: Mac build desktop - name: Mac build desktop
id: mac_desktop_build id: mac_desktop_build
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'macos-latest' if: startsWith(github.ref, 'refs/tags/v') && (matrix.os == 'macos-latest' || matrix.os == 'macos-13')
shell: bash shell: bash
env: env:
APPLE_SIMPLEX_SIGNING_KEYCHAIN: ${{ secrets.APPLE_SIMPLEX_SIGNING_KEYCHAIN }} APPLE_SIMPLEX_SIGNING_KEYCHAIN: ${{ secrets.APPLE_SIMPLEX_SIGNING_KEYCHAIN }}
@@ -241,7 +264,7 @@ jobs:
${{ steps.linux_appimage_build.outputs.appimage_hash }} ${{ steps.linux_appimage_build.outputs.appimage_hash }}
- name: Mac upload desktop package to release - name: Mac upload desktop package to release
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'macos-latest' if: startsWith(github.ref, 'refs/tags/v') && (matrix.os == 'macos-latest' || matrix.os == 'macos-13')
uses: svenstaro/upload-release-action@v2 uses: svenstaro/upload-release-action@v2
with: with:
repo_token: ${{ secrets.GITHUB_TOKEN }} repo_token: ${{ secrets.GITHUB_TOKEN }}
@@ -250,7 +273,7 @@ jobs:
tag: ${{ github.ref }} tag: ${{ github.ref }}
- name: Mac update desktop package hash - name: Mac update desktop package hash
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'macos-latest' if: startsWith(github.ref, 'refs/tags/v') && (matrix.os == 'macos-latest' || matrix.os == 'macos-13')
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+1
View File
@@ -54,6 +54,7 @@ website/translations.json
website/src/img/images/ website/src/img/images/
website/src/images/ website/src/images/
website/src/js/lottie.min.js website/src/js/lottie.min.js
website/src/privacy.md
# Generated files # Generated files
website/package/generated* website/package/generated*
+13 -8
View File
@@ -1,16 +1,21 @@
---
layout: layouts/privacy.html
permalink: /privacy/index.html
---
# SimpleX Chat Privacy Policy and Conditions of Use # SimpleX Chat Privacy Policy and Conditions of Use
SimpleX Chat is the first communication network based on a new protocol stack that builds on the same ideas of complete openness and decentralization as email and web, with the focus on providing security and privacy of communications, and without compromising on usability. SimpleX Chat is the first communication network based on a new protocol stack that builds on the same ideas of complete openness and decentralization as email and web, with the focus on providing security and privacy of communications, and without compromising on usability.
SimpleX Chat communication protocol is the first protocol that has no user profile IDs of any kind, not even random numbers, cryptographic keys or hashes that identify the users. SimpleX Chat apps allow their users to send messages and files via relay server infrastructure. Relay server owners and providers do not have any access to your messages, thanks to double-ratchet end-to-end encryption algorithm (also known as Signal algorithm - do not confuse with Signal protocols or platform) and additional encryption layers, and they also have no access to your profile and contacts - as they do not provide any user accounts. SimpleX Chat communication protocol is the first protocol that has no user profile IDs of any kind, not even random numbers, cryptographic keys or hashes that identify the users. SimpleX Chat apps allow their users to send messages and files via relay server infrastructure. Relay server owners and providers do not have any access to your messages, thanks to double-ratchet end-to-end encryption algorithm (also known as Signal algorithm - do not confuse with Signal protocols or platform) and additional encryption layers, and they also have no access to your profile and contacts - as they do not provide any user accounts.
Double ratchet algorithm has such important properties as [forward secrecy](./docs/GLOSSARY.md#forward-secrecy), sender [repudiation](./docs/GLOSSARY.md#) and break-in recovery (also known as [post-compromise security](./docs/GLOSSARY.md#post-compromise-security)). Double ratchet algorithm has such important properties as [forward secrecy](/docs/GLOSSARY.md#forward-secrecy), sender [repudiation](/docs/GLOSSARY.md#) and break-in recovery (also known as [post-compromise security](/docs/GLOSSARY.md#post-compromise-security)).
If you believe that any part of this document is not aligned with our mission or values, please raise it with us via [email](chat@simplex.chat) or [chat](https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23%2F%3Fv%3D1%26dh%3DMCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion). If you believe that any part of this document is not aligned with our mission or values, please raise it with us via [email](mailto:chat@simplex.chat) or [chat](https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23%2F%3Fv%3D1%26dh%3DMCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion).
## Privacy Policy ## Privacy Policy
SimpleX Chat Ltd uses the best industry practices for security and encryption to provide client and server software for secure [end-to-end encrypted](./docs/GLOSSARY.md#end-to-end-encryption) messaging via private connections. This encryption cannot be compromised by the relays servers, even if they are modified or compromised, via [man-in-the-middle attack](./docs/GLOSSARY.md#man-in-the-middle-attack), unlike most other communication platforms, services and networks. SimpleX Chat Ltd uses the best industry practices for security and encryption to provide client and server software for secure [end-to-end encrypted](/docs/GLOSSARY.md#end-to-end-encryption) messaging via private connections. This encryption cannot be compromised by the relays servers, even if they are modified or compromised, via [man-in-the-middle attack](/docs/GLOSSARY.md#man-in-the-middle-attack), unlike most other communication platforms, services and networks.
SimpleX Chat software is built on top of SimpleX messaging and application protocols, based on a new message routing protocol allowing to establish private connections without having any kind of addresses or other identifiers assigned to its users - it does not use emails, phone numbers, usernames, identity keys or any other user profile identifiers to pass messages between the user applications. SimpleX Chat software is built on top of SimpleX messaging and application protocols, based on a new message routing protocol allowing to establish private connections without having any kind of addresses or other identifiers assigned to its users - it does not use emails, phone numbers, usernames, identity keys or any other user profile identifiers to pass messages between the user applications.
@@ -20,7 +25,7 @@ While SimpleX Chat Ltd is not a communication service provider, and provide publ
We see users and data sovereignty, and device and provider portability as critically important properties for any communication system. We see users and data sovereignty, and device and provider portability as critically important properties for any communication system.
SimpleX Chat security assessment was done in October 2022 by [Trail of Bits](https://www.trailofbits.com/about), and most fixes were released in v4.2 see [the announcement](./blog/20221108-simplex-chat-v4.2-security-audit-new-website.md). SimpleX Chat security assessment was done in October 2022 by [Trail of Bits](https://www.trailofbits.com/about), and most fixes were released in v4.2 see [the announcement](/blog/20221108-simplex-chat-v4.2-security-audit-new-website.md).
### Your information ### Your information
@@ -34,7 +39,7 @@ You can transfer the profile to another device by creating a backup of the app d
#### Messages and Files #### Messages and Files
SimpleX relay servers cannot decrypt or otherwise access the content or even the size of your messages and files you send or receive. Each message is padded to a fixed size of 16kb. Each file is sent in chunks of 64kb, 256kb, 1mb or 8mb via all or some of the configured file relay servers. Both messages and files are sent end-to-end encrypted, and the servers do not have technical means to compromise this encryption, because part of the [key exchange](./docs/GLOSSARY.md#key-exchange) happens out-of-band. SimpleX relay servers cannot decrypt or otherwise access the content or even the size of your messages and files you send or receive. Each message is padded to a fixed size of 16kb. Each file is sent in chunks of 64kb, 256kb, 1mb or 8mb via all or some of the configured file relay servers. Both messages and files are sent end-to-end encrypted, and the servers do not have technical means to compromise this encryption, because part of the [key exchange](/docs/GLOSSARY.md#key-exchange) happens out-of-band.
Your message history is stored only on your own device and the devices of your contacts. While the recipients' devices are offline, messaging relay servers temporarily store end-to-end encrypted messages you can configure which relay servers are used to receive the messages from the new contacts, and you can manually change them for the existing contacts too. Your message history is stored only on your own device and the devices of your contacts. While the recipients' devices are offline, messaging relay servers temporarily store end-to-end encrypted messages you can configure which relay servers are used to receive the messages from the new contacts, and you can manually change them for the existing contacts too.
@@ -74,7 +79,7 @@ When you choose to use instant push notifications in SimpleX iOS app, because th
Preset notification server cannot observe the actual addresses of these queues, as a separate address is used to subscribe to the notifications. It also cannot observe who sends messages to you. Apple push notifications servers can only observe how many notifications are sent to you, but not from how many contacts, or from which messaging relays, as notifications are delivered to your device end-to-end encrypted by one of the preset notification servers - these notifications only contain end-to-end encrypted metadata, not even encrypted message content, and they look completely random to Apple push notification servers. Preset notification server cannot observe the actual addresses of these queues, as a separate address is used to subscribe to the notifications. It also cannot observe who sends messages to you. Apple push notifications servers can only observe how many notifications are sent to you, but not from how many contacts, or from which messaging relays, as notifications are delivered to your device end-to-end encrypted by one of the preset notification servers - these notifications only contain end-to-end encrypted metadata, not even encrypted message content, and they look completely random to Apple push notification servers.
You can read more about the design of iOS push notifications [here](https://simplex.chat/blog/20220404-simplex-chat-instant-notifications.html#our-ios-approach-has-one-trade-off). You can read more about the design of iOS push notifications [here](./blog/20220404-simplex-chat-instant-notifications.md#our-ios-approach-has-one-trade-off).
#### Another information stored on the servers #### Another information stored on the servers
@@ -82,7 +87,7 @@ Additional technical information can be stored on our servers, including randoml
#### SimpleX Directory #### SimpleX Directory
[SimpleX Directory](./docs/DIRECTORY.md) stores: your search requests, the messages and the members profiles in the registered groups. You can connect to SimpleX Directory via [this address](https://simplex.chat/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion). [SimpleX Directory](/docs/DIRECTORY.md) stores: your search requests, the messages and the members profiles in the registered groups. You can connect to SimpleX Directory via [this address](https://simplex.chat/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion).
#### User Support #### User Support
@@ -111,7 +116,7 @@ We will update this Privacy Policy as needed so that it is current, accurate, an
Please also read our Conditions of Use of Software and Infrastructure below. Please also read our Conditions of Use of Software and Infrastructure below.
If you have questions about our Privacy Policy please contact us via [email](chat@simplex.chat) or [chat](https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23%2F%3Fv%3D1%26dh%3DMCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion). If you have questions about our Privacy Policy please contact us via [email](mailto:chat@simplex.chat) or [chat](https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23%2F%3Fv%3D1%26dh%3DMCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion).
## Conditions of Use of Software and Infrastructure ## Conditions of Use of Software and Infrastructure
+6 -4
View File
@@ -74,7 +74,7 @@ Messages not following these rules will be deleted, the right to send messages m
You can join an English-speaking users group if you want to ask any questions: [#SimpleX users group](https://simplex.chat/contact#/?v=1-4&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2Fos8FftfoV8zjb2T89fUEjJtF7y64p5av%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAQqMgh0fw2lPhjn3PDIEfAKA_E0-gf8Hr8zzhYnDivRs%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22lBPiveK2mjfUH43SN77R0w%3D%3D%22%7D) You can join an English-speaking users group if you want to ask any questions: [#SimpleX users group](https://simplex.chat/contact#/?v=1-4&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2Fos8FftfoV8zjb2T89fUEjJtF7y64p5av%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAQqMgh0fw2lPhjn3PDIEfAKA_E0-gf8Hr8zzhYnDivRs%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22lBPiveK2mjfUH43SN77R0w%3D%3D%22%7D)
There is also a group [#simplex-devs](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2F6eHqy7uAbZPOcA6qBtrQgQquVlt4Ll91%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAqV_pg3FF00L98aCXp4D3bOs4Sxv_UmSd-gb0juVoQVs%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22XonlixcHBIb2ijCehbZoiw%3D%3D%22%7D) for developers who build on SimpleX platform: There is also a group [#simplex-devs](https://simplex.chat/contact#/?v=1-4&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FvYCRjIflKNMGYlfTkuHe4B40qSlQ0439%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAHNdcqNbzXZhyMoSBjT2R0-Eb1EPaLyUg3KZjn-kmM1w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22PD20tcXjw7IpkkMCfR6HLA%3D%3D%22%7D) for developers who build on SimpleX platform:
- chat bots and automations - chat bots and automations
- integrations with other apps - integrations with other apps
@@ -234,6 +234,8 @@ You can use SimpleX with your own servers and still communicate with people usin
Recent and important updates: Recent and important updates:
[Apr 26, 2024. SimpleX network: legally binding transparency, v5.7 released with better calls and messages.](./blog/20240426-simplex-legally-binding-transparency-v5-7-better-user-experience.md)
[Mar 23, 2024. SimpleX network: real privacy and stable profits, non-profits for protocols, v5.6 released with quantum resistant e2e encryption and simple profile migration.](./blog/20240323-simplex-network-privacy-non-profit-v5-6-quantum-resistant-e2e-encryption-simple-migration.md) [Mar 23, 2024. SimpleX network: real privacy and stable profits, non-profits for protocols, v5.6 released with quantum resistant e2e encryption and simple profile migration.](./blog/20240323-simplex-network-privacy-non-profit-v5-6-quantum-resistant-e2e-encryption-simple-migration.md)
[Mar 14, 2024. SimpleX Chat v5.6 beta: adding quantum resistance to Signal double ratchet algorithm.](./blog/20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md) [Mar 14, 2024. SimpleX Chat v5.6 beta: adding quantum resistance to Signal double ratchet algorithm.](./blog/20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md)
@@ -412,13 +414,13 @@ We have never provided or have been requested access to our servers or any infor
We do not log IP addresses of the users and we do not perform any traffic correlation on our servers. If transport level security is critical you must use Tor or some other similar network to access messaging servers. We will be improving the client applications to reduce the opportunities for traffic correlation. We do not log IP addresses of the users and we do not perform any traffic correlation on our servers. If transport level security is critical you must use Tor or some other similar network to access messaging servers. We will be improving the client applications to reduce the opportunities for traffic correlation.
Please read more in [Terms & privacy policy](./PRIVACY.md). Please read more in [Privacy Policy](./PRIVACY.md).
## Security contact ## Security contact
To report a security vulnerability, please send us email to chat@simplex.chat. We will coordinate the fix and disclosure. Please do NOT report security vulnerabilities via GitHub issues. Please see our [Security Policy](./docs/SECURITY.md) on how to report security vulnerabilities to us. We will coordinate the fix and disclosure.
Please treat any findings of possible traffic correlation attacks allowing to correlate two different conversations to the same user, other than covered in [the threat model](https://github.com/simplex-chat/simplexmq/blob/stable/protocol/overview-tjr.md#threat-model), as security vulnerabilities, and follow this disclosure process. Please do NOT report security vulnerabilities via GitHub issues.
## License ## License
+1 -1
View File
@@ -179,7 +179,7 @@ class AudioPlayer: NSObject, AVAudioPlayerDelegate {
if playback { if playback {
if AVAudioSession.sharedInstance().category != .playback { if AVAudioSession.sharedInstance().category != .playback {
logger.log("AudioSession: playback") logger.log("AudioSession: playback")
try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, options: .duckOthers) try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, options: [.duckOthers, .allowBluetooth, .allowAirPlay, .allowBluetoothA2DP])
} }
} else { } else {
if AVAudioSession.sharedInstance().category != .soloAmbient { if AVAudioSession.sharedInstance().category != .soloAmbient {
+73 -23
View File
@@ -341,8 +341,8 @@ func apiGetChatItemInfo(type: ChatType, id: Int64, itemId: Int64) async throws -
throw r throw r
} }
func apiForwardChatItem(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemId: Int64) async -> ChatItem? { func apiForwardChatItem(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemId: Int64, ttl: Int?) async -> ChatItem? {
let cmd: ChatCommand = .apiForwardChatItem(toChatType: toChatType, toChatId: toChatId, fromChatType: fromChatType, fromChatId: fromChatId, itemId: itemId) let cmd: ChatCommand = .apiForwardChatItem(toChatType: toChatType, toChatId: toChatId, fromChatType: fromChatType, fromChatId: fromChatId, itemId: itemId, ttl: ttl)
return await processSendMessageCmd(toChatType: toChatType, cmd: cmd) return await processSendMessageCmd(toChatType: toChatType, cmd: cmd)
} }
@@ -927,14 +927,19 @@ func standaloneFileInfo(url: String, ctrl: chat_ctrl? = nil) async -> MigrationF
} }
} }
func receiveFile(user: any UserLike, fileId: Int64, auto: Bool = false) async { func receiveFile(user: any UserLike, fileId: Int64, userApprovedRelays: Bool = false, auto: Bool = false) async {
if let chatItem = await apiReceiveFile(fileId: fileId, encrypted: privacyEncryptLocalFilesGroupDefault.get(), auto: auto) { if let chatItem = await apiReceiveFile(
fileId: fileId,
userApprovedRelays: userApprovedRelays || !privacyAskToApproveRelaysGroupDefault.get(),
encrypted: privacyEncryptLocalFilesGroupDefault.get(),
auto: auto
) {
await chatItemSimpleUpdate(user, chatItem) await chatItemSimpleUpdate(user, chatItem)
} }
} }
func apiReceiveFile(fileId: Int64, encrypted: Bool, inline: Bool? = nil, auto: Bool = false) async -> AChatItem? { func apiReceiveFile(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool, inline: Bool? = nil, auto: Bool = false) async -> AChatItem? {
let r = await chatSendCmd(.receiveFile(fileId: fileId, encrypted: encrypted, inline: inline)) let r = await chatSendCmd(.receiveFile(fileId: fileId, userApprovedRelays: userApprovedRelays, encrypted: encrypted, inline: inline))
let am = AlertManager.shared let am = AlertManager.shared
if case let .rcvFileAccepted(_, chatItem) = r { return chatItem } if case let .rcvFileAccepted(_, chatItem) = r { return chatItem }
if case .rcvFileAcceptedSndCancelled = r { if case .rcvFileAcceptedSndCancelled = r {
@@ -947,19 +952,50 @@ func apiReceiveFile(fileId: Int64, encrypted: Bool, inline: Bool? = nil, auto: B
} }
} else if let networkErrorAlert = networkErrorAlert(r) { } else if let networkErrorAlert = networkErrorAlert(r) {
logger.error("apiReceiveFile network error: \(String(describing: r))") logger.error("apiReceiveFile network error: \(String(describing: r))")
am.showAlert(networkErrorAlert) if !auto {
am.showAlert(networkErrorAlert)
}
} else { } else {
switch chatError(r) { switch chatError(r) {
case .fileCancelled: case .fileCancelled:
logger.debug("apiReceiveFile ignoring fileCancelled error") logger.debug("apiReceiveFile ignoring fileCancelled error")
case .fileAlreadyReceiving: case .fileAlreadyReceiving:
logger.debug("apiReceiveFile ignoring fileAlreadyReceiving error") logger.debug("apiReceiveFile ignoring fileAlreadyReceiving error")
case let .fileNotApproved(fileId, unknownServers):
logger.debug("apiReceiveFile fileNotApproved error")
if !auto {
let srvs = unknownServers.map { s in
if let srv = parseServerAddress(s), !srv.hostnames.isEmpty {
srv.hostnames[0]
} else {
serverHost(s)
}
}
am.showAlert(Alert(
title: Text("Unknown servers!"),
message: Text("Without Tor or VPN, your IP address will be visible to these XFTP relays: \(srvs.sorted().joined(separator: ", "))."),
primaryButton: .default(
Text("Download"),
action: {
Task {
logger.debug("apiReceiveFile fileNotApproved alert - in Task")
if let user = ChatModel.shared.currentUser {
await receiveFile(user: user, fileId: fileId, userApprovedRelays: true)
}
}
}
),
secondaryButton: .cancel()
))
}
default: default:
logger.error("apiReceiveFile error: \(String(describing: r))") logger.error("apiReceiveFile error: \(String(describing: r))")
am.showAlertMsg( if !auto {
title: "Error receiving file", am.showAlertMsg(
message: "Error: \(String(describing: r))" title: "Error receiving file",
) message: "Error: \(String(describing: r))"
)
}
} }
} }
return nil return nil
@@ -1849,21 +1885,35 @@ func processReceivedMsg(_ res: ChatResponse) async {
} }
case .chatSuspended: case .chatSuspended:
chatSuspended() chatSuspended()
case let .contactSwitch(_, contact, switchProgress): case let .contactSwitch(user, contact, switchProgress):
await MainActor.run { if active(user) {
m.updateContactConnectionStats(contact, switchProgress.connectionStats) await MainActor.run {
m.updateContactConnectionStats(contact, switchProgress.connectionStats)
}
} }
case let .groupMemberSwitch(_, groupInfo, member, switchProgress): case let .groupMemberSwitch(user, groupInfo, member, switchProgress):
await MainActor.run { if active(user) {
m.updateGroupMemberConnectionStats(groupInfo, member, switchProgress.connectionStats) await MainActor.run {
m.updateGroupMemberConnectionStats(groupInfo, member, switchProgress.connectionStats)
}
} }
case let .contactRatchetSync(_, contact, ratchetSyncProgress): case let .contactRatchetSync(user, contact, ratchetSyncProgress):
await MainActor.run { if active(user) {
m.updateContactConnectionStats(contact, ratchetSyncProgress.connectionStats) await MainActor.run {
m.updateContactConnectionStats(contact, ratchetSyncProgress.connectionStats)
}
} }
case let .groupMemberRatchetSync(_, groupInfo, member, ratchetSyncProgress): case let .groupMemberRatchetSync(user, groupInfo, member, ratchetSyncProgress):
await MainActor.run { if active(user) {
m.updateGroupMemberConnectionStats(groupInfo, member, ratchetSyncProgress.connectionStats) await MainActor.run {
m.updateGroupMemberConnectionStats(groupInfo, member, ratchetSyncProgress.connectionStats)
}
}
case let .contactDisabled(user, contact):
if active(user) {
await MainActor.run {
m.updateContact(contact)
}
} }
case let .remoteCtrlFound(remoteCtrl, ctrlAppInfo_, appVersion, compatible): case let .remoteCtrlFound(remoteCtrl, ctrlAppInfo_, appVersion, compatible):
await MainActor.run { await MainActor.run {
@@ -110,8 +110,8 @@ struct ActiveCallView: View {
call.callState = .invitationSent call.callState = .invitationSent
call.localCapabilities = capabilities call.localCapabilities = capabilities
} }
if call.supportsVideo { if call.supportsVideo && !AVAudioSession.sharedInstance().hasExternalAudioDevice() {
try? AVAudioSession.sharedInstance().setCategory(.playback, options: .defaultToSpeaker) try? AVAudioSession.sharedInstance().setCategory(.playback, options: [.allowBluetooth, .allowAirPlay, .allowBluetoothA2DP])
} }
CallSoundsPlayer.shared.startConnectingCallSound() CallSoundsPlayer.shared.startConnectingCallSound()
activeCallWaitDeliveryReceipt() activeCallWaitDeliveryReceipt()
@@ -139,7 +139,6 @@ struct ActiveCallView: View {
await MainActor.run { await MainActor.run {
call.callState = .negotiated call.callState = .negotiated
CallSoundsPlayer.shared.stop() CallSoundsPlayer.shared.stop()
try? AVAudioSession.sharedInstance().setCategory(.soloAmbient)
} }
} }
case let .ice(iceCandidates): case let .ice(iceCandidates):
@@ -236,6 +235,7 @@ struct ActiveCallOverlay: View {
@EnvironmentObject var chatModel: ChatModel @EnvironmentObject var chatModel: ChatModel
@ObservedObject var call: Call @ObservedObject var call: Call
var client: WebRTCClient var client: WebRTCClient
@ObservedObject private var deviceManager = CallAudioDeviceManager.shared
var body: some View { var body: some View {
VStack { VStack {
@@ -251,7 +251,16 @@ struct ActiveCallOverlay: View {
HStack { HStack {
toggleAudioButton() toggleAudioButton()
Spacer() Spacer()
Color.clear.frame(width: 40, height: 40) if deviceManager.availableInputs.allSatisfy({ $0.portType == .builtInMic }) {
toggleSpeakerButton()
.frame(width: 40, height: 40)
} else if call.hasMedia {
AudioDevicePicker()
.scaleEffect(2)
.frame(maxWidth: 40, maxHeight: 40)
} else {
Color.clear.frame(width: 40, height: 40)
}
Spacer() Spacer()
endCallButton() endCallButton()
Spacer() Spacer()
@@ -292,14 +301,33 @@ struct ActiveCallOverlay: View {
toggleAudioButton() toggleAudioButton()
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
endCallButton() endCallButton()
toggleSpeakerButton() // Check if the only input is microphone. And in this case show toggle button,
.frame(maxWidth: .infinity, alignment: .trailing) // If there are more inputs, it probably means something like bluetooth headphones are available
// and in this case show iOS button for choosing different output.
// There is no way to get available outputs, only inputs
if deviceManager.availableInputs.allSatisfy({ $0.portType == .builtInMic }) {
toggleSpeakerButton()
.frame(maxWidth: .infinity, alignment: .trailing)
} else if call.hasMedia {
AudioDevicePicker()
.scaleEffect(2)
.frame(maxWidth: 50, maxHeight: 40)
.frame(maxWidth: .infinity, alignment: .trailing)
} else {
Color.clear.frame(width: 50, height: 40)
}
} }
.padding(.bottom, 60) .padding(.bottom, 60)
.padding(.horizontal, 48) .padding(.horizontal, 48)
} }
} }
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.onAppear {
deviceManager.start()
}
.onDisappear {
deviceManager.stop()
}
} }
private func audioCallInfoView(_ call: Call) -> some View { private func audioCallInfoView(_ call: Call) -> some View {
@@ -377,12 +405,17 @@ struct ActiveCallOverlay: View {
private func toggleSpeakerButton() -> some View { private func toggleSpeakerButton() -> some View {
controlButton(call, call.speakerEnabled ? "speaker.wave.2.fill" : "speaker.wave.1.fill") { controlButton(call, call.speakerEnabled ? "speaker.wave.2.fill" : "speaker.wave.1.fill") {
Task { Task {
client.setSpeakerEnabledAndConfigureSession(!call.speakerEnabled) let speakerEnabled = AVAudioSession.sharedInstance().currentRoute.outputs.first?.portType == .builtInSpeaker
client.setSpeakerEnabledAndConfigureSession(!speakerEnabled)
DispatchQueue.main.async { DispatchQueue.main.async {
call.speakerEnabled = !call.speakerEnabled call.speakerEnabled = !speakerEnabled
} }
} }
} }
.onAppear {
deviceManager.call = call
//call.speakerEnabled = AVAudioSession.sharedInstance().currentRoute.outputs.first?.portType == .builtInSpeaker
}
} }
private func toggleVideoButton() -> some View { private func toggleVideoButton() -> some View {
@@ -0,0 +1,25 @@
//
// MPVolumeView.swift
// SimpleX (iOS)
//
// Created by Stanislav on 24.04.2024.
// Copyright © 2024 SimpleX Chat. All rights reserved.
//
import Foundation
import SwiftUI
import UIKit
import AVKit
struct AudioDevicePicker: UIViewRepresentable {
func makeUIView(context: Context) -> some UIView {
let v = AVRoutePickerView(frame: .zero)
v.activeTintColor = .white
v.tintColor = .white
return v
}
func updateUIView(_ uiView: UIViewType, context: Context) {
}
}
@@ -0,0 +1,67 @@
//
// CallAudioDeviceManager.swift
// SimpleX (iOS)
//
// Created by Avently on 23.04.2024.
// Copyright © 2024 SimpleX Chat. All rights reserved.
//
import Foundation
import SwiftUI
import SimpleXChat
import AVKit
import WebRTC
class CallAudioDeviceManager: ObservableObject {
static let shared = CallAudioDeviceManager()
let audioSession: AVAudioSession
let nc = NotificationCenter.default
var call: Call?
var timer: Timer? = nil
// Actually, only one output
@Published var outputs: [AVAudioSessionPortDescription]
@Published var currentDevice: AVAudioSessionPortDescription? = nil
// All devices that can record audio (the ones that can play audio are not included)
@Published var availableInputs: [AVAudioSessionPortDescription] = []
init(_ audioSession: AVAudioSession? = nil) {
self.audioSession = audioSession ?? RTCAudioSession.sharedInstance().session
self.outputs = self.audioSession.currentRoute.outputs
self.availableInputs = self.audioSession.availableInputs ?? []
}
func reloadDevices() {
outputs = audioSession.currentRoute.outputs
currentDevice = audioSession.currentRoute.outputs.first
availableInputs = audioSession.availableInputs ?? []
call?.speakerEnabled = currentDevice?.portType == .builtInSpeaker
// Workaround situation:
// have bluetooth device connected, choosing speaker, disconnecting bluetooth device. In this case iOS will not post notification, so do it manually
timer?.invalidate()
if availableInputs.contains(where: { $0.portType != .builtInReceiver && $0.portType != .builtInSpeaker }) {
timer = Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { t in
self.reloadDevices()
}
}
}
@objc func audioCallback(notification: Notification) {
reloadDevices()
logger.debug("Changes in devices, current audio devices: \(String(describing: self.availableInputs.map({ $0.portType.rawValue }))), output: \(String(describing: self.currentDevice?.portType.rawValue))")
}
func start() {
nc.addObserver(self, selector: #selector(audioCallback), name: AVAudioSession.routeChangeNotification, object: nil)
}
func stop() {
nc.removeObserver(self, name: AVAudioSession.routeChangeNotification, object: nil)
timer?.invalidate()
}
}
@@ -103,7 +103,23 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
RTCAudioSession.sharedInstance().audioSessionDidActivate(audioSession) RTCAudioSession.sharedInstance().audioSessionDidActivate(audioSession)
RTCAudioSession.sharedInstance().isAudioEnabled = true RTCAudioSession.sharedInstance().isAudioEnabled = true
do { do {
try audioSession.setCategory(.playAndRecord, mode: .voiceChat, options: .mixWithOthers) let supportsVideo = ChatModel.shared.activeCall?.supportsVideo == true
if supportsVideo {
try audioSession.setCategory(.playAndRecord, mode: .videoChat, options: [.defaultToSpeaker, .mixWithOthers, .allowBluetooth, .allowAirPlay, .allowBluetoothA2DP])
} else {
try audioSession.setCategory(.playAndRecord, mode: .voiceChat, options: [.mixWithOthers, .allowBluetooth, .allowAirPlay, .allowBluetoothA2DP])
}
// Without any delay sound is not playing from speaker or external device in incoming call
Task {
for i in 0 ... 3 {
try? await Task.sleep(nanoseconds: UInt64(i) * 300_000000)
if let preferred = audioSession.preferredInputDevice() {
await MainActor.run { try? audioSession.setPreferredInput(preferred) }
} else if supportsVideo {
await MainActor.run { try? audioSession.overrideOutputAudioPort(.speaker) }
}
}
}
logger.debug("audioSession category set") logger.debug("audioSession category set")
try audioSession.setActive(true) try audioSession.setActive(true)
logger.debug("audioSession activated") logger.debug("audioSession activated")
+28 -3
View File
@@ -605,9 +605,23 @@ extension WebRTCClient {
self.rtcAudioSession.unlockForConfiguration() self.rtcAudioSession.unlockForConfiguration()
} }
do { do {
try self.rtcAudioSession.setCategory(AVAudioSession.Category.playAndRecord.rawValue) let hasExternalAudioDevice = self.rtcAudioSession.session.hasExternalAudioDevice()
try self.rtcAudioSession.setMode(AVAudioSession.Mode.voiceChat.rawValue) if enabled {
try self.rtcAudioSession.overrideOutputAudioPort(enabled ? .speaker : .none) try self.rtcAudioSession.setCategory(AVAudioSession.Category.playAndRecord.rawValue, with: [.defaultToSpeaker, .allowBluetooth, .allowAirPlay, .allowBluetoothA2DP])
try self.rtcAudioSession.setMode(AVAudioSession.Mode.videoChat.rawValue)
if hasExternalAudioDevice, let preferred = self.rtcAudioSession.session.preferredInputDevice() {
try self.rtcAudioSession.setPreferredInput(preferred)
} else {
try self.rtcAudioSession.overrideOutputAudioPort(.speaker)
}
} else {
try self.rtcAudioSession.setCategory(AVAudioSession.Category.playAndRecord.rawValue, with: [.allowBluetooth, .allowAirPlay, .allowBluetoothA2DP])
try self.rtcAudioSession.setMode(AVAudioSession.Mode.voiceChat.rawValue)
try self.rtcAudioSession.overrideOutputAudioPort(.none)
}
if hasExternalAudioDevice {
logger.debug("WebRTCClient: configuring session with external device available, skip configuring speaker")
}
try self.rtcAudioSession.setActive(true) try self.rtcAudioSession.setActive(true)
logger.debug("WebRTCClient: configuring session with speaker enabled \(enabled) success") logger.debug("WebRTCClient: configuring session with speaker enabled \(enabled) success")
} catch let error { } catch let error {
@@ -658,6 +672,17 @@ extension WebRTCClient {
} }
} }
extension AVAudioSession {
func hasExternalAudioDevice() -> Bool {
availableInputs?.allSatisfy({ $0.portType == .builtInMic }) != true
}
func preferredInputDevice() -> AVAudioSessionPortDescription? {
// logger.debug("Preferred input device: \(String(describing: self.availableInputs?.filter({ $0.portType != .builtInMic })))")
return availableInputs?.filter({ $0.portType != .builtInMic }).last
}
}
struct CustomRTCSessionDescription: Codable { struct CustomRTCSessionDescription: Codable {
public var type: RTCSdpType? public var type: RTCSdpType?
public var sdp: String? public var sdp: String?
@@ -49,7 +49,7 @@ func localizedInfoRow(_ title: LocalizedStringKey, _ value: LocalizedStringKey)
} }
} }
private func serverHost(_ s: String) -> String { func serverHost(_ s: String) -> String {
if let i = s.range(of: "@")?.lowerBound { if let i = s.range(of: "@")?.lowerBound {
return String(s[i...].dropFirst()) return String(s[i...].dropFirst())
} else { } else {
@@ -60,6 +60,7 @@ struct CIFileView: View {
case .rcvInvitation: return true case .rcvInvitation: return true
case .rcvAccepted: return true case .rcvAccepted: return true
case .rcvTransfer: return false case .rcvTransfer: return false
case .rcvAborted: return true
case .rcvComplete: return true case .rcvComplete: return true
case .rcvCancelled: return false case .rcvCancelled: return false
case .rcvError: return false case .rcvError: return false
@@ -73,10 +74,10 @@ struct CIFileView: View {
logger.debug("CIFileView fileAction") logger.debug("CIFileView fileAction")
if let file = file { if let file = file {
switch (file.fileStatus) { switch (file.fileStatus) {
case .rcvInvitation: case .rcvInvitation, .rcvAborted:
if fileSizeValid(file) { if fileSizeValid(file) {
Task { Task {
logger.debug("CIFileView fileAction - in .rcvInvitation, in Task") logger.debug("CIFileView fileAction - in .rcvInvitation, .rcvAborted, in Task")
if let user = m.currentUser { if let user = m.currentUser {
await receiveFile(user: user, fileId: file.fileId) await receiveFile(user: user, fileId: file.fileId)
} }
@@ -148,6 +149,8 @@ struct CIFileView: View {
} else { } else {
progressView() progressView()
} }
case .rcvAborted:
fileIcon("doc.fill", color: .accentColor, innerIcon: "exclamationmark.arrow.circlepath", innerIconSize: 12)
case .rcvComplete: fileIcon("doc.fill") case .rcvComplete: fileIcon("doc.fill")
case .rcvCancelled: fileIcon("doc.fill", innerIcon: "xmark", innerIconSize: 10) case .rcvCancelled: fileIcon("doc.fill", innerIcon: "xmark", innerIconSize: 10)
case .rcvError: fileIcon("doc.fill", innerIcon: "xmark", innerIconSize: 10) case .rcvError: fileIcon("doc.fill", innerIcon: "xmark", innerIconSize: 10)
@@ -21,6 +21,8 @@ struct CIGroupInvitationView: View {
@State private var inProgress = false @State private var inProgress = false
@State private var progressByTimeout = false @State private var progressByTimeout = false
@AppStorage(DEFAULT_SHOW_SENT_VIA_RPOXY) private var showSentViaProxy = false
var body: some View { var body: some View {
let action = !chatItem.chatDir.sent && groupInvitation.status == .pending let action = !chatItem.chatDir.sent && groupInvitation.status == .pending
let v = ZStack(alignment: .bottomTrailing) { let v = ZStack(alignment: .bottomTrailing) {
@@ -43,7 +45,7 @@ struct CIGroupInvitationView: View {
.foregroundColor(inProgress ? .secondary : chatIncognito ? .indigo : .accentColor) .foregroundColor(inProgress ? .secondary : chatIncognito ? .indigo : .accentColor)
.font(.callout) .font(.callout)
+ Text(" ") + Text(" ")
+ ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showStatus: false, showEdited: false) + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showStatus: false, showEdited: false, showViaProxy: showSentViaProxy)
) )
.overlay(DetermineWidth()) .overlay(DetermineWidth())
} }
@@ -51,7 +53,7 @@ struct CIGroupInvitationView: View {
( (
groupInvitationText() groupInvitationText()
+ Text(" ") + Text(" ")
+ ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showStatus: false, showEdited: false) + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showStatus: false, showEdited: false, showViaProxy: showSentViaProxy)
) )
.overlay(DetermineWidth()) .overlay(DetermineWidth())
} }
@@ -17,7 +17,6 @@ struct CIImageView: View {
let maxWidth: CGFloat let maxWidth: CGFloat
@Binding var imgWidth: CGFloat? @Binding var imgWidth: CGFloat?
@State var scrollProxy: ScrollViewProxy? @State var scrollProxy: ScrollViewProxy?
@State var metaColor: Color
@State private var showFullScreenImage = false @State private var showFullScreenImage = false
var body: some View { var body: some View {
@@ -38,7 +37,7 @@ struct CIImageView: View {
.onTapGesture { .onTapGesture {
if let file = file { if let file = file {
switch file.fileStatus { switch file.fileStatus {
case .rcvInvitation: case .rcvInvitation, .rcvAborted:
Task { Task {
if let user = m.currentUser { if let user = m.currentUser {
await receiveFile(user: user, fileId: file.fileId) await receiveFile(user: user, fileId: file.fileId)
@@ -103,6 +102,7 @@ struct CIImageView: View {
case .rcvInvitation: fileIcon("arrow.down", 10, 13) case .rcvInvitation: fileIcon("arrow.down", 10, 13)
case .rcvAccepted: fileIcon("ellipsis", 14, 11) case .rcvAccepted: fileIcon("ellipsis", 14, 11)
case .rcvTransfer: progressView() case .rcvTransfer: progressView()
case .rcvAborted: fileIcon("exclamationmark.arrow.circlepath", 14, 11)
case .rcvCancelled: fileIcon("xmark", 10, 13) case .rcvCancelled: fileIcon("xmark", 10, 13)
case .rcvError: fileIcon("xmark", 10, 13) case .rcvError: fileIcon("xmark", 10, 13)
case .invalid: fileIcon("questionmark", 10, 13) case .invalid: fileIcon("questionmark", 10, 13)
@@ -116,7 +116,7 @@ struct CIImageView: View {
.resizable() .resizable()
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: size, height: size) .frame(width: size, height: size)
.foregroundColor(metaColor) .foregroundColor(.white)
.padding(padding) .padding(padding)
} }
@@ -17,6 +17,8 @@ struct CIMetaView: View {
var showStatus = true var showStatus = true
var showEdited = true var showEdited = true
@AppStorage(DEFAULT_SHOW_SENT_VIA_RPOXY) private var showSentViaProxy = false
var body: some View { var body: some View {
if chatItem.isDeletedContent { if chatItem.isDeletedContent {
chatItem.timestampText.font(.caption).foregroundColor(metaColor) chatItem.timestampText.font(.caption).foregroundColor(metaColor)
@@ -27,24 +29,24 @@ struct CIMetaView: View {
switch meta.itemStatus { switch meta.itemStatus {
case let .sndSent(sndProgress): case let .sndSent(sndProgress):
switch sndProgress { switch sndProgress {
case .complete: ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, sent: .sent, showStatus: showStatus, showEdited: showEdited) case .complete: ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, sent: .sent, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy)
case .partial: ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: paleMetaColor, sent: .sent, showStatus: showStatus, showEdited: showEdited) case .partial: ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: paleMetaColor, sent: .sent, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy)
} }
case let .sndRcvd(_, sndProgress): case let .sndRcvd(_, sndProgress):
switch sndProgress { switch sndProgress {
case .complete: case .complete:
ZStack { ZStack {
ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, sent: .rcvd1, showStatus: showStatus, showEdited: showEdited) ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, sent: .rcvd1, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy)
ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, sent: .rcvd2, showStatus: showStatus, showEdited: showEdited) ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, sent: .rcvd2, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy)
} }
case .partial: case .partial:
ZStack { ZStack {
ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: paleMetaColor, sent: .rcvd1, showStatus: showStatus, showEdited: showEdited) ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: paleMetaColor, sent: .rcvd1, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy)
ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: paleMetaColor, sent: .rcvd2, showStatus: showStatus, showEdited: showEdited) ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: paleMetaColor, sent: .rcvd2, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy)
} }
} }
default: default:
ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, showStatus: showStatus, showEdited: showEdited) ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy)
} }
} }
} }
@@ -64,7 +66,8 @@ func ciMetaText(
transparent: Bool = false, transparent: Bool = false,
sent: SentCheckmark? = nil, sent: SentCheckmark? = nil,
showStatus: Bool = true, showStatus: Bool = true,
showEdited: Bool = true showEdited: Bool = true,
showViaProxy: Bool
) -> Text { ) -> Text {
var r = Text("") var r = Text("")
if showEdited, meta.itemEdited { if showEdited, meta.itemEdited {
@@ -78,6 +81,9 @@ func ciMetaText(
} }
r = r + Text(" ") r = r + Text(" ")
} }
if showViaProxy, meta.sentViaProxy == true {
r = r + statusIconText("arrow.forward", color.opacity(0.67)).font(.caption2)
}
if showStatus { if showStatus {
if let (icon, statusColor) = meta.statusIcon(color) { if let (icon, statusColor) = meta.statusIcon(color) {
let t = Text(Image(systemName: icon)).font(.caption2) let t = Text(Image(systemName: icon)).font(.caption2)
@@ -19,6 +19,8 @@ struct CIRcvDecryptionError: View {
var chatItem: ChatItem var chatItem: ChatItem
@State private var alert: CIRcvDecryptionErrorAlert? @State private var alert: CIRcvDecryptionErrorAlert?
@AppStorage(DEFAULT_SHOW_SENT_VIA_RPOXY) private var showSentViaProxy = false
enum CIRcvDecryptionErrorAlert: Identifiable { enum CIRcvDecryptionErrorAlert: Identifiable {
case syncAllowedAlert(_ syncConnection: () -> Void) case syncAllowedAlert(_ syncConnection: () -> Void)
case syncNotSupportedContactAlert case syncNotSupportedContactAlert
@@ -119,7 +121,7 @@ struct CIRcvDecryptionError: View {
.foregroundColor(syncSupported ? .accentColor : .secondary) .foregroundColor(syncSupported ? .accentColor : .secondary)
.font(.callout) .font(.callout)
+ Text(" ") + Text(" ")
+ ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true) + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showViaProxy: showSentViaProxy)
) )
} }
.padding(.horizontal, 12) .padding(.horizontal, 12)
@@ -140,7 +142,7 @@ struct CIRcvDecryptionError: View {
.foregroundColor(.red) .foregroundColor(.red)
.italic() .italic()
+ Text(" ") + Text(" ")
+ ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true) + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showViaProxy: showSentViaProxy)
} }
.padding(.horizontal, 12) .padding(.horizontal, 12)
CIMetaView(chat: chat, chatItem: chatItem) CIMetaView(chat: chat, chatItem: chatItem)
@@ -69,7 +69,7 @@ struct CIVideoView: View {
.onTapGesture { .onTapGesture {
if let file = file { if let file = file {
switch file.fileStatus { switch file.fileStatus {
case .rcvInvitation: case .rcvInvitation, .rcvAborted:
receiveFileIfValidSize(file: file, receiveFile: receiveFile) receiveFileIfValidSize(file: file, receiveFile: receiveFile)
case .rcvAccepted: case .rcvAccepted:
switch file.fileProtocol { switch file.fileProtocol {
@@ -95,7 +95,7 @@ struct CIVideoView: View {
} }
durationProgress() durationProgress()
} }
if let file = file, case .rcvInvitation = file.fileStatus { if let file = file, showDownloadButton(file.fileStatus) {
Button { Button {
receiveFileIfValidSize(file: file, receiveFile: receiveFile) receiveFileIfValidSize(file: file, receiveFile: receiveFile)
} label: { } label: {
@@ -105,6 +105,14 @@ struct CIVideoView: View {
} }
} }
private func showDownloadButton(_ fileStatus: CIFileStatus) -> Bool {
switch fileStatus {
case .rcvInvitation: true
case .rcvAborted: true
default: false
}
}
private func videoViewEncrypted(_ file: CIFile, _ defaultPreview: UIImage, _ duration: Int) -> some View { private func videoViewEncrypted(_ file: CIFile, _ defaultPreview: UIImage, _ duration: Int) -> some View {
return ZStack(alignment: .topTrailing) { return ZStack(alignment: .topTrailing) {
ZStack(alignment: .center) { ZStack(alignment: .center) {
@@ -280,6 +288,7 @@ struct CIVideoView: View {
} else { } else {
progressView() progressView()
} }
case .rcvAborted: fileIcon("exclamationmark.arrow.circlepath", 14, 11)
case .rcvCancelled: fileIcon("xmark", 10, 13) case .rcvCancelled: fileIcon("xmark", 10, 13)
case .rcvError: fileIcon("xmark", 10, 13) case .rcvError: fileIcon("xmark", 10, 13)
case .invalid: fileIcon("questionmark", 10, 13) case .invalid: fileIcon("questionmark", 10, 13)
@@ -318,10 +327,10 @@ struct CIVideoView: View {
} }
// TODO encrypt: where file size is checked? // TODO encrypt: where file size is checked?
private func receiveFileIfValidSize(file: CIFile, receiveFile: @escaping (User, Int64, Bool) async -> Void) { private func receiveFileIfValidSize(file: CIFile, receiveFile: @escaping (User, Int64, Bool, Bool) async -> Void) {
Task { Task {
if let user = m.currentUser { if let user = m.currentUser {
await receiveFile(user, file.fileId, false) await receiveFile(user, file.fileId, false, false)
} }
} }
} }
@@ -139,9 +139,10 @@ struct VoiceMessagePlayer: View {
case .sndComplete: playbackButton() case .sndComplete: playbackButton()
case .sndCancelled: playbackButton() case .sndCancelled: playbackButton()
case .sndError: playbackButton() case .sndError: playbackButton()
case .rcvInvitation: downloadButton(recordingFile) case .rcvInvitation: downloadButton(recordingFile, "play.fill")
case .rcvAccepted: loadingIcon() case .rcvAccepted: loadingIcon()
case .rcvTransfer: loadingIcon() case .rcvTransfer: loadingIcon()
case .rcvAborted: downloadButton(recordingFile, "exclamationmark.arrow.circlepath")
case .rcvComplete: playbackButton() case .rcvComplete: playbackButton()
case .rcvCancelled: playPauseIcon("play.fill", Color(uiColor: .tertiaryLabel)) case .rcvCancelled: playPauseIcon("play.fill", Color(uiColor: .tertiaryLabel))
case .rcvError: playPauseIcon("play.fill", Color(uiColor: .tertiaryLabel)) case .rcvError: playPauseIcon("play.fill", Color(uiColor: .tertiaryLabel))
@@ -217,7 +218,7 @@ struct VoiceMessagePlayer: View {
} }
} }
private func downloadButton(_ recordingFile: CIFile) -> some View { private func downloadButton(_ recordingFile: CIFile, _ icon: String) -> some View {
Button { Button {
Task { Task {
if let user = chatModel.currentUser { if let user = chatModel.currentUser {
@@ -225,7 +226,7 @@ struct VoiceMessagePlayer: View {
} }
} }
} label: { } label: {
playPauseIcon("play.fill") playPauseIcon(icon)
} }
} }
@@ -87,12 +87,17 @@ struct FramedItemView: View {
.cornerRadius(18) .cornerRadius(18)
.onPreferenceChange(DetermineWidth.Key.self) { msgWidth = $0 } .onPreferenceChange(DetermineWidth.Key.self) { msgWidth = $0 }
switch chatItem.meta.itemStatus { if let (title, text) = chatItem.meta.itemStatus.statusInfo {
case .sndErrorAuth: v.onTapGesture {
v.onTapGesture { msgDeliveryError("Most likely this contact has deleted the connection with you.") } AlertManager.shared.showAlert(
case let .sndError(agentError): Alert(
v.onTapGesture { msgDeliveryError("Unexpected error: \(agentError)") } title: Text(title),
default: v message: Text(text)
)
)
}
} else {
v
} }
} }
@@ -110,7 +115,7 @@ struct FramedItemView: View {
} else { } else {
switch (chatItem.content.msgContent) { switch (chatItem.content.msgContent) {
case let .image(text, image): case let .image(text, image):
CIImageView(chatItem: chatItem, image: image, maxWidth: maxWidth, imgWidth: $imgWidth, scrollProxy: scrollProxy, metaColor: metaColor) CIImageView(chatItem: chatItem, image: image, maxWidth: maxWidth, imgWidth: $imgWidth, scrollProxy: scrollProxy)
.overlay(DetermineWidth()) .overlay(DetermineWidth())
if text == "" && !chatItem.meta.isLive { if text == "" && !chatItem.meta.isLive {
Color.clear Color.clear
@@ -157,13 +162,6 @@ struct FramedItemView: View {
} }
} }
} }
private func msgDeliveryError(_ err: LocalizedStringKey) {
AlertManager.shared.showAlertMsg(
title: "Message delivery error",
message: err
)
}
@ViewBuilder func framedItemHeader(icon: String? = nil, caption: Text, pad: Bool = false) -> some View { @ViewBuilder func framedItemHeader(icon: String? = nil, caption: Text, pad: Bool = false) -> some View {
let v = HStack(spacing: 6) { let v = HStack(spacing: 6) {
@@ -248,7 +246,10 @@ struct FramedItemView: View {
Group { Group {
if let sender = qi.getSender(membership()) { if let sender = qi.getSender(membership()) {
VStack(alignment: .leading, spacing: 2) { VStack(alignment: .leading, spacing: 2) {
Text(sender).font(.caption).foregroundColor(.secondary) Text(sender)
.font(.caption)
.foregroundColor(.secondary)
.lineLimit(1)
ciQuotedMsgTextView(qi, lines: 2) ciQuotedMsgTextView(qi, lines: 2)
} }
} else { } else {
@@ -35,6 +35,8 @@ struct MsgContentView: View {
@State private var typingIdx = 0 @State private var typingIdx = 0
@State private var timer: Timer? @State private var timer: Timer?
@AppStorage(DEFAULT_SHOW_SENT_VIA_RPOXY) private var showSentViaProxy = false
var body: some View { var body: some View {
if meta?.isLive == true { if meta?.isLive == true {
msgContentView() msgContentView()
@@ -81,7 +83,7 @@ struct MsgContentView: View {
} }
private func reserveSpaceForMeta(_ mt: CIMeta) -> Text { private func reserveSpaceForMeta(_ mt: CIMeta) -> Text {
(rightToLeft ? Text("\n") : Text(" ")) + ciMetaText(mt, chatTTL: chat.chatInfo.timedMessagesTTL, encrypted: nil, transparent: true) (rightToLeft ? Text("\n") : Text(" ")) + ciMetaText(mt, chatTTL: chat.chatInfo.timedMessagesTTL, encrypted: nil, transparent: true, showViaProxy: showSentViaProxy)
} }
} }
@@ -383,7 +383,7 @@ struct ChatItemInfoView: View {
let mss = membersStatuses(memberDeliveryStatuses) let mss = membersStatuses(memberDeliveryStatuses)
if !mss.isEmpty { if !mss.isEmpty {
ForEach(mss, id: \.0.groupMemberId) { memberStatus in ForEach(mss, id: \.0.groupMemberId) { memberStatus in
memberDeliveryStatusView(memberStatus.0, memberStatus.1) memberDeliveryStatusView(memberStatus.0, memberStatus.1, memberStatus.2)
} }
} else { } else {
Text("No delivery information") Text("No delivery information")
@@ -392,23 +392,27 @@ struct ChatItemInfoView: View {
} }
} }
private func membersStatuses(_ memberDeliveryStatuses: [MemberDeliveryStatus]) -> [(GroupMember, CIStatus)] { private func membersStatuses(_ memberDeliveryStatuses: [MemberDeliveryStatus]) -> [(GroupMember, CIStatus, Bool?)] {
memberDeliveryStatuses.compactMap({ mds in memberDeliveryStatuses.compactMap({ mds in
if let mem = chatModel.getGroupMember(mds.groupMemberId) { if let mem = chatModel.getGroupMember(mds.groupMemberId) {
return (mem.wrapped, mds.memberDeliveryStatus) return (mem.wrapped, mds.memberDeliveryStatus, mds.sentViaProxy)
} else { } else {
return nil return nil
} }
}) })
} }
private func memberDeliveryStatusView(_ member: GroupMember, _ status: CIStatus) -> some View { private func memberDeliveryStatusView(_ member: GroupMember, _ status: CIStatus, _ sentViaProxy: Bool?) -> some View {
HStack{ HStack{
ProfileImage(imageStr: member.image, size: 30) ProfileImage(imageStr: member.image, size: 30)
.padding(.trailing, 2) .padding(.trailing, 2)
Text(member.chatViewName) Text(member.chatViewName)
.lineLimit(1) .lineLimit(1)
Spacer() Spacer()
if sentViaProxy == true {
Image(systemName: "arrow.forward")
.foregroundColor(.secondary).opacity(0.67)
}
let v = Group { let v = Group {
if let (icon, statusColor) = status.statusIcon(Color.secondary) { if let (icon, statusColor) = status.statusIcon(Color.secondary) {
switch status { switch status {
@@ -593,6 +593,7 @@ struct ChatView: View {
Text(memberNames(member, prevMember, memCount)) Text(memberNames(member, prevMember, memCount))
.font(.caption) .font(.caption)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.lineLimit(2)
.padding(.leading, memberImageSize + 14) .padding(.leading, memberImageSize + 14)
.padding(.top, 7) .padding(.top, 7)
} }
@@ -712,9 +712,9 @@ struct ComposeView: View {
if chat.chatInfo.contact?.nextSendGrpInv ?? false { if chat.chatInfo.contact?.nextSendGrpInv ?? false {
await sendMemberContactInvitation() await sendMemberContactInvitation()
} else if case let .forwardingItem(ci, fromChatInfo) = composeState.contextItem { } else if case let .forwardingItem(ci, fromChatInfo) = composeState.contextItem {
sent = await forwardItem(ci, fromChatInfo) sent = await forwardItem(ci, fromChatInfo, ttl)
if !composeState.message.isEmpty { if !composeState.message.isEmpty {
sent = await send(checkLinkPreview(), quoted: sent?.id, live: false, ttl: nil) sent = await send(checkLinkPreview(), quoted: sent?.id, live: false, ttl: ttl)
} }
} else if case let .editingItem(ci) = composeState.contextItem { } else if case let .editingItem(ci) = composeState.contextItem {
sent = await updateMessage(ci, live: live) sent = await updateMessage(ci, live: live)
@@ -890,13 +890,14 @@ struct ComposeView: View {
return nil return nil
} }
func forwardItem(_ forwardedItem: ChatItem, _ fromChatInfo: ChatInfo) async -> ChatItem? { func forwardItem(_ forwardedItem: ChatItem, _ fromChatInfo: ChatInfo, _ ttl: Int?) async -> ChatItem? {
if let chatItem = await apiForwardChatItem( if let chatItem = await apiForwardChatItem(
toChatType: chat.chatInfo.chatType, toChatType: chat.chatInfo.chatType,
toChatId: chat.chatInfo.apiId, toChatId: chat.chatInfo.apiId,
fromChatType: fromChatInfo.chatType, fromChatType: fromChatInfo.chatType,
fromChatId: fromChatInfo.apiId, fromChatId: fromChatInfo.apiId,
itemId: forwardedItem.id itemId: forwardedItem.id,
ttl: ttl
) { ) {
await MainActor.run { await MainActor.run {
chatModel.addChatItem(chat.chatInfo, chatItem) chatModel.addChatItem(chat.chatInfo, chatItem)
@@ -224,8 +224,7 @@ struct SendMessageView: View {
@ViewBuilder private func sendButtonContextMenuItems() -> some View { @ViewBuilder private func sendButtonContextMenuItems() -> some View {
if composeState.liveMessage == nil, if composeState.liveMessage == nil,
!composeState.editing, !composeState.editing {
!composeState.forwarding {
if case .noContextItem = composeState.contextItem, if case .noContextItem = composeState.contextItem,
!composeState.voicePreview, !composeState.voicePreview,
let send = sendLiveMessage, let send = sendLiveMessage,
@@ -34,8 +34,7 @@ struct GroupPreferencesView: View {
featureSection(.reactions, $preferences.reactions.enable) featureSection(.reactions, $preferences.reactions.enable)
featureSection(.voice, $preferences.voice.enable, $preferences.voice.role) featureSection(.voice, $preferences.voice.enable, $preferences.voice.role)
featureSection(.files, $preferences.files.enable, $preferences.files.role) featureSection(.files, $preferences.files.enable, $preferences.files.role)
// TODO enable simplexLinks preference in 5.8 featureSection(.simplexLinks, $preferences.simplexLinks.enable, $preferences.simplexLinks.role)
// featureSection(.simplexLinks, $preferences.simplexLinks.enable, $preferences.simplexLinks.role)
featureSection(.history, $preferences.history.enable) featureSection(.history, $preferences.history.enable)
if groupInfo.canEdit { if groupInfo.canEdit {
@@ -102,8 +101,6 @@ struct GroupPreferencesView: View {
} }
} }
.frame(height: 36) .frame(height: 36)
// remove in v5.8
.disabled(true)
} }
} else { } else {
settingsRow(icon, color: color) { settingsRow(icon, color: color) {
@@ -240,14 +240,14 @@ struct ChatPreviewView: View {
private func itemStatusMark(_ cItem: ChatItem) -> Text { private func itemStatusMark(_ cItem: ChatItem) -> Text {
switch cItem.meta.itemStatus { switch cItem.meta.itemStatus {
case .sndErrorAuth: case .sndErrorAuth, .sndError:
return Text(Image(systemName: "multiply")) return Text(Image(systemName: "multiply"))
.font(.caption) .font(.caption)
.foregroundColor(.red) + Text(" ") .foregroundColor(.red) + Text(" ")
case .sndError: case .sndWarning:
return Text(Image(systemName: "exclamationmark.triangle.fill")) return Text(Image(systemName: "exclamationmark.triangle.fill"))
.font(.caption) .font(.caption)
.foregroundColor(.yellow) + Text(" ") .foregroundColor(.orange) + Text(" ")
default: return Text("") default: return Text("")
} }
} }
@@ -32,14 +32,22 @@ struct ProfileImage: View {
} }
} }
private let squareToCircleRatio = 0.935
private let radiusFactor = (1 - squareToCircleRatio) / 50
@ViewBuilder func clipProfileImage(_ img: Image, size: CGFloat, radius: Double) -> some View { @ViewBuilder func clipProfileImage(_ img: Image, size: CGFloat, radius: Double) -> some View {
let v = img.resizable().frame(width: size, height: size) let v = img.resizable()
if radius <= 0 { if radius >= 50 {
v.clipShape(Rectangle()) v.frame(width: size, height: size).clipShape(Circle())
} else if radius >= 50 { } else if radius <= 0 {
v.clipShape(Circle()) let sz = size * squareToCircleRatio
v.frame(width: sz, height: sz).padding((size - sz) / 2)
} else { } else {
v.clipShape(RoundedRectangle(cornerRadius: size * radius / 100, style: .continuous)) let sz = size * (squareToCircleRatio + radius * radiusFactor)
v.frame(width: sz, height: sz)
.clipShape(RoundedRectangle(cornerRadius: sz * radius / 100, style: .continuous))
.padding((size - sz) / 2)
} }
} }
@@ -51,10 +51,10 @@ struct AdvancedNetworkSettings: View {
} }
.disabled(currentNetCfg == NetCfg.proxyDefaults) .disabled(currentNetCfg == NetCfg.proxyDefaults)
timeoutSettingPicker("TCP connection timeout", selection: $netCfg.tcpConnectTimeout, values: [10_000000, 15_000000, 20_000000, 25_000000, 35_000000, 50_000000], label: secondsLabel) timeoutSettingPicker("TCP connection timeout", selection: $netCfg.tcpConnectTimeout, values: [10_000000, 15_000000, 20_000000, 30_000000, 45_000000, 60_000000, 90_000000], label: secondsLabel)
timeoutSettingPicker("Protocol timeout", selection: $netCfg.tcpTimeout, values: [5_000000, 7_000000, 10_000000, 15_000000, 20_000000, 30_000000], label: secondsLabel) timeoutSettingPicker("Protocol timeout", selection: $netCfg.tcpTimeout, values: [5_000000, 7_000000, 10_000000, 15_000000, 20_000000, 30_000000], label: secondsLabel)
timeoutSettingPicker("Protocol timeout per KB", selection: $netCfg.tcpTimeoutPerKb, values: [2_500, 5_000, 10_000, 15_000, 20_000, 30_000], label: secondsLabel) timeoutSettingPicker("Protocol timeout per KB", selection: $netCfg.tcpTimeoutPerKb, values: [2_500, 5_000, 10_000, 15_000, 20_000, 30_000], label: secondsLabel)
intSettingPicker("Receiving concurrency", selection: $netCfg.rcvConcurrency, values: [1, 2, 4, 8, 12, 16, 24], label: "") // intSettingPicker("Receiving concurrency", selection: $netCfg.rcvConcurrency, values: [1, 2, 4, 8, 12, 16, 24], label: "")
timeoutSettingPicker("PING interval", selection: $netCfg.smpPingInterval, values: [120_000000, 300_000000, 600_000000, 1200_000000, 2400_000000, 3600_000000], label: secondsLabel) timeoutSettingPicker("PING interval", selection: $netCfg.smpPingInterval, values: [120_000000, 300_000000, 600_000000, 1200_000000, 2400_000000, 3600_000000], label: secondsLabel)
intSettingPicker("PING count", selection: $netCfg.smpPingCount, values: [1, 2, 3, 5, 8], label: "") intSettingPicker("PING count", selection: $netCfg.smpPingCount, values: [1, 2, 3, 5, 8], label: "")
Toggle("Enable TCP keep-alive", isOn: $enableKeepAlive) Toggle("Enable TCP keep-alive", isOn: $enableKeepAlive)
@@ -23,6 +23,7 @@ extension AppSettings {
setNetCfg(val) setNetCfg(val)
} }
if let val = privacyEncryptLocalFiles { privacyEncryptLocalFilesGroupDefault.set(val) } if let val = privacyEncryptLocalFiles { privacyEncryptLocalFilesGroupDefault.set(val) }
if let val = privacyAskToApproveRelays { privacyAskToApproveRelaysGroupDefault.set(val) }
if let val = privacyAcceptImages { if let val = privacyAcceptImages {
privacyAcceptImagesGroupDefault.set(val) privacyAcceptImagesGroupDefault.set(val)
def.setValue(val, forKey: DEFAULT_PRIVACY_ACCEPT_IMAGES) def.setValue(val, forKey: DEFAULT_PRIVACY_ACCEPT_IMAGES)
@@ -50,6 +51,7 @@ extension AppSettings {
var c = AppSettings.defaults var c = AppSettings.defaults
c.networkConfig = getNetCfg() c.networkConfig = getNetCfg()
c.privacyEncryptLocalFiles = privacyEncryptLocalFilesGroupDefault.get() c.privacyEncryptLocalFiles = privacyEncryptLocalFilesGroupDefault.get()
c.privacyAskToApproveRelays = privacyAskToApproveRelaysGroupDefault.get()
c.privacyAcceptImages = privacyAcceptImagesGroupDefault.get() c.privacyAcceptImages = privacyAcceptImagesGroupDefault.get()
c.privacyLinkPreviews = def.bool(forKey: DEFAULT_PRIVACY_LINK_PREVIEWS) c.privacyLinkPreviews = def.bool(forKey: DEFAULT_PRIVACY_LINK_PREVIEWS)
c.privacyShowChatPreviews = def.bool(forKey: DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS) c.privacyShowChatPreviews = def.bool(forKey: DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS)
@@ -12,12 +12,16 @@ import SimpleXChat
private enum NetworkAlert: Identifiable { private enum NetworkAlert: Identifiable {
case updateOnionHosts(hosts: OnionHosts) case updateOnionHosts(hosts: OnionHosts)
case updateSessionMode(mode: TransportSessionMode) case updateSessionMode(mode: TransportSessionMode)
case updateSMPProxyMode(proxyMode: SMPProxyMode)
case updateSMPProxyFallback(proxyFallback: SMPProxyFallback)
case error(err: String) case error(err: String)
var id: String { var id: String {
switch self { switch self {
case let .updateOnionHosts(hosts): return "updateOnionHosts \(hosts)" case let .updateOnionHosts(hosts): return "updateOnionHosts \(hosts)"
case let .updateSessionMode(mode): return "updateSessionMode \(mode)" case let .updateSessionMode(mode): return "updateSessionMode \(mode)"
case let .updateSMPProxyMode(proxyMode): return "updateSMPProxyMode \(proxyMode)"
case let .updateSMPProxyFallback(proxyFallback): return "updateSMPProxyFallback \(proxyFallback)"
case let .error(err): return "error \(err)" case let .error(err): return "error \(err)"
} }
} }
@@ -26,11 +30,14 @@ private enum NetworkAlert: Identifiable {
struct NetworkAndServers: View { struct NetworkAndServers: View {
@EnvironmentObject var m: ChatModel @EnvironmentObject var m: ChatModel
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
@AppStorage(DEFAULT_SHOW_SENT_VIA_RPOXY) private var showSentViaProxy = true
@State private var cfgLoaded = false @State private var cfgLoaded = false
@State private var currentNetCfg = NetCfg.defaults @State private var currentNetCfg = NetCfg.defaults
@State private var netCfg = NetCfg.defaults @State private var netCfg = NetCfg.defaults
@State private var onionHosts: OnionHosts = .no @State private var onionHosts: OnionHosts = .no
@State private var sessionMode: TransportSessionMode = .user @State private var sessionMode: TransportSessionMode = .user
@State private var proxyMode: SMPProxyMode = .never
@State private var proxyFallback: SMPProxyFallback = .allow
@State private var alert: NetworkAlert? @State private var alert: NetworkAlert?
var body: some View { var body: some View {
@@ -75,6 +82,30 @@ struct NetworkAndServers: View {
Text("Using .onion hosts requires compatible VPN provider.") Text("Using .onion hosts requires compatible VPN provider.")
} }
Section {
Picker("Private routing", selection: $proxyMode) {
ForEach(SMPProxyMode.values, id: \.self) { Text($0.text) }
}
.frame(height: 36)
Picker("Allow downgrade", selection: $proxyFallback) {
ForEach(SMPProxyFallback.values, id: \.self) { Text($0.text) }
}
.disabled(proxyMode == .never)
.frame(height: 36)
Toggle("Show message status", isOn: $showSentViaProxy)
} header: {
Text("Private message routing")
} footer: {
VStack(alignment: .leading) {
Text("To protect your IP address, private routing uses your SMP servers to deliver messages.")
if showSentViaProxy {
Text("Show → on messages sent via private routing.")
}
}
}
Section("Calls") { Section("Calls") {
NavigationLink { NavigationLink {
RTCServers() RTCServers()
@@ -99,14 +130,24 @@ struct NetworkAndServers: View {
currentNetCfg = getNetCfg() currentNetCfg = getNetCfg()
resetNetCfgView() resetNetCfgView()
} }
.onChange(of: onionHosts) { _ in .onChange(of: onionHosts) { hosts in
if onionHosts != OnionHosts(netCfg: currentNetCfg) { if hosts != OnionHosts(netCfg: currentNetCfg) {
alert = .updateOnionHosts(hosts: onionHosts) alert = .updateOnionHosts(hosts: hosts)
} }
} }
.onChange(of: sessionMode) { _ in .onChange(of: sessionMode) { mode in
if sessionMode != netCfg.sessionMode { if mode != netCfg.sessionMode {
alert = .updateSessionMode(mode: sessionMode) alert = .updateSessionMode(mode: mode)
}
}
.onChange(of: proxyMode) { mode in
if mode != netCfg.smpProxyMode {
alert = .updateSMPProxyMode(proxyMode: mode)
}
}
.onChange(of: proxyFallback) { fallbackMode in
if fallbackMode != netCfg.smpProxyFallback {
alert = .updateSMPProxyFallback(proxyFallback: fallbackMode)
} }
} }
.alert(item: $alert) { a in .alert(item: $alert) { a in
@@ -137,6 +178,30 @@ struct NetworkAndServers: View {
resetNetCfgView() resetNetCfgView()
} }
) )
case let .updateSMPProxyMode(proxyMode):
return Alert(
title: Text("Message routing mode"),
message: Text(proxyModeInfo(proxyMode)) + Text("\n") + Text("Updating this setting will re-connect the client to all servers."),
primaryButton: .default(Text("Ok")) {
netCfg.smpProxyMode = proxyMode
saveNetCfg()
},
secondaryButton: .cancel() {
resetNetCfgView()
}
)
case let .updateSMPProxyFallback(proxyFallback):
return Alert(
title: Text("Message routing fallback"),
message: Text(proxyFallbackInfo(proxyFallback)) + Text("\n") + Text("Updating this setting will re-connect the client to all servers."),
primaryButton: .default(Text("Ok")) {
netCfg.smpProxyFallback = proxyFallback
saveNetCfg()
},
secondaryButton: .cancel() {
resetNetCfgView()
}
)
case let .error(err): case let .error(err):
return Alert( return Alert(
title: Text("Error updating settings"), title: Text("Error updating settings"),
@@ -166,6 +231,8 @@ struct NetworkAndServers: View {
netCfg = currentNetCfg netCfg = currentNetCfg
onionHosts = OnionHosts(netCfg: netCfg) onionHosts = OnionHosts(netCfg: netCfg)
sessionMode = netCfg.sessionMode sessionMode = netCfg.sessionMode
proxyMode = netCfg.smpProxyMode
proxyFallback = netCfg.smpProxyFallback
} }
private func onionHostsInfo(_ hosts: OnionHosts) -> LocalizedStringKey { private func onionHostsInfo(_ hosts: OnionHosts) -> LocalizedStringKey {
@@ -182,6 +249,23 @@ struct NetworkAndServers: View {
case .entity: return "A separate TCP connection will be used **for each contact and group member**.\n**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail." case .entity: return "A separate TCP connection will be used **for each contact and group member**.\n**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail."
} }
} }
private func proxyModeInfo(_ mode: SMPProxyMode) -> LocalizedStringKey {
switch mode {
case .always: return "Always use private routing."
case .unknown: return "Use private routing with unknown servers."
case .unprotected: return "Use private routing with unknown servers when IP address is not protected."
case .never: return "Do NOT use private routing."
}
}
private func proxyFallbackInfo(_ proxyFallback: SMPProxyFallback) -> LocalizedStringKey {
switch proxyFallback {
case .allow: return "Send messages directly when your or destination server does not support private routing."
case .allowProtected: return "Send messages directly when IP address is protected and your or destination server does not support private routing."
case .prohibit: return "Do NOT send messages directly, even if your or destination server does not support private routing."
}
}
} }
struct NetworkServersView_Previews: PreviewProvider { struct NetworkServersView_Previews: PreviewProvider {
@@ -16,6 +16,7 @@ struct PrivacySettings: View {
@AppStorage(DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS) private var showChatPreviews = true @AppStorage(DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS) private var showChatPreviews = true
@AppStorage(DEFAULT_PRIVACY_SAVE_LAST_DRAFT) private var saveLastDraft = true @AppStorage(DEFAULT_PRIVACY_SAVE_LAST_DRAFT) private var saveLastDraft = true
@AppStorage(GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES, store: groupDefaults) private var encryptLocalFiles = true @AppStorage(GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES, store: groupDefaults) private var encryptLocalFiles = true
@AppStorage(GROUP_DEFAULT_PRIVACY_ASK_TO_APPROVE_RELAYS, store: groupDefaults) private var askToApproveRelays = true
@State private var simplexLinkMode = privacySimplexLinkModeDefault.get() @State private var simplexLinkMode = privacySimplexLinkModeDefault.get()
@AppStorage(DEFAULT_PRIVACY_PROTECT_SCREEN) private var protectScreen = false @AppStorage(DEFAULT_PRIVACY_PROTECT_SCREEN) private var protectScreen = false
@AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false @AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false
@@ -64,18 +65,6 @@ struct PrivacySettings: View {
} }
Section { Section {
settingsRow("lock.doc") {
Toggle("Encrypt local files", isOn: $encryptLocalFiles)
.onChange(of: encryptLocalFiles) {
setEncryptLocalFiles($0)
}
}
settingsRow("photo") {
Toggle("Auto-accept images", isOn: $autoAcceptImages)
.onChange(of: autoAcceptImages) {
privacyAcceptImagesGroupDefault.set($0)
}
}
settingsRow("network") { settingsRow("network") {
Toggle("Send link previews", isOn: $useLinkPreviews) Toggle("Send link previews", isOn: $useLinkPreviews)
} }
@@ -108,6 +97,32 @@ struct PrivacySettings: View {
Text("Chats") Text("Chats")
} }
Section {
settingsRow("lock.doc") {
Toggle("Encrypt local files", isOn: $encryptLocalFiles)
.onChange(of: encryptLocalFiles) {
setEncryptLocalFiles($0)
}
}
settingsRow("photo") {
Toggle("Auto-accept images", isOn: $autoAcceptImages)
.onChange(of: autoAcceptImages) {
privacyAcceptImagesGroupDefault.set($0)
}
}
settingsRow("network.badge.shield.half.filled") {
Toggle("Protect IP address", isOn: $askToApproveRelays)
}
} header: {
Text("Files")
} footer: {
if askToApproveRelays {
Text("The app will ask to confirm downloads from unknown file servers (except .onion).")
} else {
Text("Without Tor or VPN, your IP address will be visible to file servers.")
}
}
Section { Section {
settingsRow("person") { settingsRow("person") {
Toggle("Contacts", isOn: $contactReceipts) Toggle("Contacts", isOn: $contactReceipts)
@@ -60,6 +60,7 @@ let DEFAULT_DEVICE_NAME_FOR_REMOTE_ACCESS = "deviceNameForRemoteAccess"
let DEFAULT_CONFIRM_REMOTE_SESSIONS = "confirmRemoteSessions" let DEFAULT_CONFIRM_REMOTE_SESSIONS = "confirmRemoteSessions"
let DEFAULT_CONNECT_REMOTE_VIA_MULTICAST = "connectRemoteViaMulticast" let DEFAULT_CONNECT_REMOTE_VIA_MULTICAST = "connectRemoteViaMulticast"
let DEFAULT_CONNECT_REMOTE_VIA_MULTICAST_AUTO = "connectRemoteViaMulticastAuto" let DEFAULT_CONNECT_REMOTE_VIA_MULTICAST_AUTO = "connectRemoteViaMulticastAuto"
let DEFAULT_SHOW_SENT_VIA_RPOXY = "showSentViaProxy"
let ANDROID_DEFAULT_CALL_ON_LOCK_SCREEN = "androidCallOnLockScreen" let ANDROID_DEFAULT_CALL_ON_LOCK_SCREEN = "androidCallOnLockScreen"
@@ -99,6 +100,7 @@ let appDefaults: [String: Any] = [
DEFAULT_CONFIRM_REMOTE_SESSIONS: false, DEFAULT_CONFIRM_REMOTE_SESSIONS: false,
DEFAULT_CONNECT_REMOTE_VIA_MULTICAST: true, DEFAULT_CONNECT_REMOTE_VIA_MULTICAST: true,
DEFAULT_CONNECT_REMOTE_VIA_MULTICAST_AUTO: true, DEFAULT_CONNECT_REMOTE_VIA_MULTICAST_AUTO: true,
DEFAULT_SHOW_SENT_VIA_RPOXY: false,
ANDROID_DEFAULT_CALL_ON_LOCK_SCREEN: AppSettingsLockScreenCalls.show.rawValue ANDROID_DEFAULT_CALL_ON_LOCK_SCREEN: AppSettingsLockScreenCalls.show.rawValue
] ]
@@ -2805,6 +2805,7 @@ This cannot be undone!</source>
</trans-unit> </trans-unit>
<trans-unit id="Forward and save messages" xml:space="preserve"> <trans-unit id="Forward and save messages" xml:space="preserve">
<source>Forward and save messages</source> <source>Forward and save messages</source>
<target>Препращане и запазване на съобщения</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Forwarded" xml:space="preserve"> <trans-unit id="Forwarded" xml:space="preserve">
@@ -3174,6 +3175,7 @@ This cannot be undone!</source>
</trans-unit> </trans-unit>
<trans-unit id="In-call sounds" xml:space="preserve"> <trans-unit id="In-call sounds" xml:space="preserve">
<source>In-call sounds</source> <source>In-call sounds</source>
<target>Звуци по време на разговор</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Incognito" xml:space="preserve"> <trans-unit id="Incognito" xml:space="preserve">
@@ -3653,6 +3655,7 @@ This is your link for group %@!</source>
</trans-unit> </trans-unit>
<trans-unit id="Message source remains private." xml:space="preserve"> <trans-unit id="Message source remains private." xml:space="preserve">
<source>Message source remains private.</source> <source>Message source remains private.</source>
<target>Източникът на съобщението остава скрит.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Message text" xml:space="preserve"> <trans-unit id="Message text" xml:space="preserve">
@@ -3772,6 +3775,7 @@ This is your link for group %@!</source>
</trans-unit> </trans-unit>
<trans-unit id="More reliable network connection." xml:space="preserve"> <trans-unit id="More reliable network connection." xml:space="preserve">
<source>More reliable network connection.</source> <source>More reliable network connection.</source>
<target>По-надеждна мрежова връзка.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Most likely this connection is deleted." xml:space="preserve"> <trans-unit id="Most likely this connection is deleted." xml:space="preserve">
@@ -3816,6 +3820,7 @@ This is your link for group %@!</source>
</trans-unit> </trans-unit>
<trans-unit id="Network management" xml:space="preserve"> <trans-unit id="Network management" xml:space="preserve">
<source>Network management</source> <source>Network management</source>
<target>Управление на мрежата</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Network settings" xml:space="preserve"> <trans-unit id="Network settings" xml:space="preserve">
@@ -4371,6 +4376,7 @@ Error: %@</source>
</trans-unit> </trans-unit>
<trans-unit id="Profile images" xml:space="preserve"> <trans-unit id="Profile images" xml:space="preserve">
<source>Profile images</source> <source>Profile images</source>
<target>Профилни изображения</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Profile name" xml:space="preserve"> <trans-unit id="Profile name" xml:space="preserve">
@@ -5195,6 +5201,7 @@ Error: %@</source>
</trans-unit> </trans-unit>
<trans-unit id="Shape profile images" xml:space="preserve"> <trans-unit id="Shape profile images" xml:space="preserve">
<source>Shape profile images</source> <source>Shape profile images</source>
<target>Променете формата на профилните изображения</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Share" xml:space="preserve"> <trans-unit id="Share" xml:space="preserve">
@@ -5364,6 +5371,7 @@ Error: %@</source>
</trans-unit> </trans-unit>
<trans-unit id="Square, circle, or anything in between." xml:space="preserve"> <trans-unit id="Square, circle, or anything in between." xml:space="preserve">
<source>Square, circle, or anything in between.</source> <source>Square, circle, or anything in between.</source>
<target>Квадрат, кръг или нещо между тях.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Start chat" xml:space="preserve"> <trans-unit id="Start chat" xml:space="preserve">
@@ -6244,6 +6252,7 @@ To connect, please ask your contact to create another connection link and check
</trans-unit> </trans-unit>
<trans-unit id="When connecting audio and video calls." xml:space="preserve"> <trans-unit id="When connecting audio and video calls." xml:space="preserve">
<source>When connecting audio and video calls.</source> <source>When connecting audio and video calls.</source>
<target>При свързване на аудио и видео разговори.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="When people request to connect, you can accept or reject it." xml:space="preserve"> <trans-unit id="When people request to connect, you can accept or reject it." xml:space="preserve">
@@ -6263,6 +6272,7 @@ To connect, please ask your contact to create another connection link and check
</trans-unit> </trans-unit>
<trans-unit id="Will be enabled in direct chats!" xml:space="preserve"> <trans-unit id="Will be enabled in direct chats!" xml:space="preserve">
<source>Will be enabled in direct chats!</source> <source>Will be enabled in direct chats!</source>
<target>Ще бъде активирано в личните чатове!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Wired ethernet" xml:space="preserve"> <trans-unit id="Wired ethernet" xml:space="preserve">
@@ -4376,6 +4376,7 @@ Fehler: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Profile images" xml:space="preserve"> <trans-unit id="Profile images" xml:space="preserve">
<source>Profile images</source> <source>Profile images</source>
<target>Profil-Bilder</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Profile name" xml:space="preserve"> <trans-unit id="Profile name" xml:space="preserve">
@@ -5200,6 +5201,7 @@ Fehler: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Shape profile images" xml:space="preserve"> <trans-unit id="Shape profile images" xml:space="preserve">
<source>Shape profile images</source> <source>Shape profile images</source>
<target>Form der Profil-Bilder</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Share" xml:space="preserve"> <trans-unit id="Share" xml:space="preserve">
@@ -5369,6 +5371,7 @@ Fehler: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Square, circle, or anything in between." xml:space="preserve"> <trans-unit id="Square, circle, or anything in between." xml:space="preserve">
<source>Square, circle, or anything in between.</source> <source>Square, circle, or anything in between.</source>
<target>Quadratisch, kreisförmig oder irgendetwas dazwischen.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Start chat" xml:space="preserve"> <trans-unit id="Start chat" xml:space="preserve">
@@ -6029,7 +6032,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
</trans-unit> </trans-unit>
<trans-unit id="Use current profile" xml:space="preserve"> <trans-unit id="Use current profile" xml:space="preserve">
<source>Use current profile</source> <source>Use current profile</source>
<target>Nutzen Sie das aktuelle Profil</target> <target>Das aktuelle Profil nutzen</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Use for new connections" xml:space="preserve"> <trans-unit id="Use for new connections" xml:space="preserve">
@@ -4376,6 +4376,7 @@ Error: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Profile images" xml:space="preserve"> <trans-unit id="Profile images" xml:space="preserve">
<source>Profile images</source> <source>Profile images</source>
<target>Imágenes del perfil</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Profile name" xml:space="preserve"> <trans-unit id="Profile name" xml:space="preserve">
@@ -5200,6 +5201,7 @@ Error: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Shape profile images" xml:space="preserve"> <trans-unit id="Shape profile images" xml:space="preserve">
<source>Shape profile images</source> <source>Shape profile images</source>
<target>Dar forma a las imágenes de perfil</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Share" xml:space="preserve"> <trans-unit id="Share" xml:space="preserve">
@@ -5369,6 +5371,7 @@ Error: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Square, circle, or anything in between." xml:space="preserve"> <trans-unit id="Square, circle, or anything in between." xml:space="preserve">
<source>Square, circle, or anything in between.</source> <source>Square, circle, or anything in between.</source>
<target>Cuadrada, circular o cualquier forma intermedia.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Start chat" xml:space="preserve"> <trans-unit id="Start chat" xml:space="preserve">
@@ -745,6 +745,7 @@
</trans-unit> </trans-unit>
<trans-unit id="Allow to send SimpleX links." xml:space="preserve"> <trans-unit id="Allow to send SimpleX links." xml:space="preserve">
<source>Allow to send SimpleX links.</source> <source>Allow to send SimpleX links.</source>
<target>Autorise l'envoi de liens SimpleX.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Allow to send files and media." xml:space="preserve"> <trans-unit id="Allow to send files and media." xml:space="preserve">
@@ -1089,6 +1090,7 @@
</trans-unit> </trans-unit>
<trans-unit id="Cellular" xml:space="preserve"> <trans-unit id="Cellular" xml:space="preserve">
<source>Cellular</source> <source>Cellular</source>
<target>Cellulaire</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Change" xml:space="preserve"> <trans-unit id="Change" xml:space="preserve">
@@ -2088,6 +2090,7 @@ Cette opération ne peut être annulée !</target>
</trans-unit> </trans-unit>
<trans-unit id="Download" xml:space="preserve"> <trans-unit id="Download" xml:space="preserve">
<source>Download</source> <source>Download</source>
<target>Télécharger</target>
<note>chat item action</note> <note>chat item action</note>
</trans-unit> </trans-unit>
<trans-unit id="Download failed" xml:space="preserve"> <trans-unit id="Download failed" xml:space="preserve">
@@ -2202,6 +2205,7 @@ Cette opération ne peut être annulée !</target>
</trans-unit> </trans-unit>
<trans-unit id="Enabled for" xml:space="preserve"> <trans-unit id="Enabled for" xml:space="preserve">
<source>Enabled for</source> <source>Enabled for</source>
<target>Activé pour</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Encrypt" xml:space="preserve"> <trans-unit id="Encrypt" xml:space="preserve">
@@ -2726,6 +2730,7 @@ Cette opération ne peut être annulée !</target>
</trans-unit> </trans-unit>
<trans-unit id="Files and media not allowed" xml:space="preserve"> <trans-unit id="Files and media not allowed" xml:space="preserve">
<source>Files and media not allowed</source> <source>Files and media not allowed</source>
<target>Fichiers et médias non autorisés</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Files and media prohibited!" xml:space="preserve"> <trans-unit id="Files and media prohibited!" xml:space="preserve">
@@ -2795,18 +2800,22 @@ Cette opération ne peut être annulée !</target>
</trans-unit> </trans-unit>
<trans-unit id="Forward" xml:space="preserve"> <trans-unit id="Forward" xml:space="preserve">
<source>Forward</source> <source>Forward</source>
<target>Transférer</target>
<note>chat item action</note> <note>chat item action</note>
</trans-unit> </trans-unit>
<trans-unit id="Forward and save messages" xml:space="preserve"> <trans-unit id="Forward and save messages" xml:space="preserve">
<source>Forward and save messages</source> <source>Forward and save messages</source>
<target>Transférer et sauvegarder des messages</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Forwarded" xml:space="preserve"> <trans-unit id="Forwarded" xml:space="preserve">
<source>Forwarded</source> <source>Forwarded</source>
<target>Transféré</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Forwarded from" xml:space="preserve"> <trans-unit id="Forwarded from" xml:space="preserve">
<source>Forwarded from</source> <source>Forwarded from</source>
<target>Transféré depuis</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Found desktop" xml:space="preserve"> <trans-unit id="Found desktop" xml:space="preserve">
@@ -2921,6 +2930,7 @@ Cette opération ne peut être annulée !</target>
</trans-unit> </trans-unit>
<trans-unit id="Group members can send SimpleX links." xml:space="preserve"> <trans-unit id="Group members can send SimpleX links." xml:space="preserve">
<source>Group members can send SimpleX links.</source> <source>Group members can send SimpleX links.</source>
<target>Les membres du groupe peuvent envoyer des liens SimpleX.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Group members can send direct messages." xml:space="preserve"> <trans-unit id="Group members can send direct messages." xml:space="preserve">
@@ -3165,6 +3175,7 @@ Cette opération ne peut être annulée !</target>
</trans-unit> </trans-unit>
<trans-unit id="In-call sounds" xml:space="preserve"> <trans-unit id="In-call sounds" xml:space="preserve">
<source>In-call sounds</source> <source>In-call sounds</source>
<target>Sons d'appel</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Incognito" xml:space="preserve"> <trans-unit id="Incognito" xml:space="preserve">
@@ -3644,6 +3655,7 @@ Voici votre lien pour le groupe %@ !</target>
</trans-unit> </trans-unit>
<trans-unit id="Message source remains private." xml:space="preserve"> <trans-unit id="Message source remains private." xml:space="preserve">
<source>Message source remains private.</source> <source>Message source remains private.</source>
<target>La source du message reste privée.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Message text" xml:space="preserve"> <trans-unit id="Message text" xml:space="preserve">
@@ -3763,6 +3775,7 @@ Voici votre lien pour le groupe %@ !</target>
</trans-unit> </trans-unit>
<trans-unit id="More reliable network connection." xml:space="preserve"> <trans-unit id="More reliable network connection." xml:space="preserve">
<source>More reliable network connection.</source> <source>More reliable network connection.</source>
<target>Connexion réseau plus fiable.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Most likely this connection is deleted." xml:space="preserve"> <trans-unit id="Most likely this connection is deleted." xml:space="preserve">
@@ -3802,10 +3815,12 @@ Voici votre lien pour le groupe %@ !</target>
</trans-unit> </trans-unit>
<trans-unit id="Network connection" xml:space="preserve"> <trans-unit id="Network connection" xml:space="preserve">
<source>Network connection</source> <source>Network connection</source>
<target>Connexion au réseau</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Network management" xml:space="preserve"> <trans-unit id="Network management" xml:space="preserve">
<source>Network management</source> <source>Network management</source>
<target>Gestion du réseau</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Network settings" xml:space="preserve"> <trans-unit id="Network settings" xml:space="preserve">
@@ -3920,6 +3935,7 @@ Voici votre lien pour le groupe %@ !</target>
</trans-unit> </trans-unit>
<trans-unit id="No network connection" xml:space="preserve"> <trans-unit id="No network connection" xml:space="preserve">
<source>No network connection</source> <source>No network connection</source>
<target>Pas de connexion au réseau</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="No permission to record voice message" xml:space="preserve"> <trans-unit id="No permission to record voice message" xml:space="preserve">
@@ -4138,6 +4154,7 @@ Voici votre lien pour le groupe %@ !</target>
</trans-unit> </trans-unit>
<trans-unit id="Other" xml:space="preserve"> <trans-unit id="Other" xml:space="preserve">
<source>Other</source> <source>Other</source>
<target>Autres</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="PING count" xml:space="preserve"> <trans-unit id="PING count" xml:space="preserve">
@@ -4359,6 +4376,7 @@ Erreur: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Profile images" xml:space="preserve"> <trans-unit id="Profile images" xml:space="preserve">
<source>Profile images</source> <source>Profile images</source>
<target>Images de profil</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Profile name" xml:space="preserve"> <trans-unit id="Profile name" xml:space="preserve">
@@ -4403,6 +4421,7 @@ Erreur: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Prohibit sending SimpleX links." xml:space="preserve"> <trans-unit id="Prohibit sending SimpleX links." xml:space="preserve">
<source>Prohibit sending SimpleX links.</source> <source>Prohibit sending SimpleX links.</source>
<target>Interdire l'envoi de liens SimpleX.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Prohibit sending direct messages to members." xml:space="preserve"> <trans-unit id="Prohibit sending direct messages to members." xml:space="preserve">
@@ -4537,6 +4556,7 @@ Erreur: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Receiving concurrency" xml:space="preserve"> <trans-unit id="Receiving concurrency" xml:space="preserve">
<source>Receiving concurrency</source> <source>Receiving concurrency</source>
<target>Réception simultanée</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Receiving file will be stopped." xml:space="preserve"> <trans-unit id="Receiving file will be stopped." xml:space="preserve">
@@ -4556,6 +4576,7 @@ Erreur: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Recipient(s) can't see who this message is from." xml:space="preserve"> <trans-unit id="Recipient(s) can't see who this message is from." xml:space="preserve">
<source>Recipient(s) can't see who this message is from.</source> <source>Recipient(s) can't see who this message is from.</source>
<target>Le(s) destinataire(s) ne peut(vent) pas voir de qui provient ce message.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Recipients see updates as you type them." xml:space="preserve"> <trans-unit id="Recipients see updates as you type them." xml:space="preserve">
@@ -4860,6 +4881,7 @@ Erreur: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Saved" xml:space="preserve"> <trans-unit id="Saved" xml:space="preserve">
<source>Saved</source> <source>Saved</source>
<target>Enregistré</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Saved WebRTC ICE servers will be removed" xml:space="preserve"> <trans-unit id="Saved WebRTC ICE servers will be removed" xml:space="preserve">
@@ -4869,6 +4891,7 @@ Erreur: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Saved from" xml:space="preserve"> <trans-unit id="Saved from" xml:space="preserve">
<source>Saved from</source> <source>Saved from</source>
<target>Enregistré depuis</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Saved message" xml:space="preserve"> <trans-unit id="Saved message" xml:space="preserve">
@@ -5178,6 +5201,7 @@ Erreur: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Shape profile images" xml:space="preserve"> <trans-unit id="Shape profile images" xml:space="preserve">
<source>Shape profile images</source> <source>Shape profile images</source>
<target>Images de profil modelable</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Share" xml:space="preserve"> <trans-unit id="Share" xml:space="preserve">
@@ -5302,10 +5326,12 @@ Erreur: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="SimpleX links are prohibited in this group." xml:space="preserve"> <trans-unit id="SimpleX links are prohibited in this group." xml:space="preserve">
<source>SimpleX links are prohibited in this group.</source> <source>SimpleX links are prohibited in this group.</source>
<target>Les liens SimpleX sont interdits dans ce groupe.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="SimpleX links not allowed" xml:space="preserve"> <trans-unit id="SimpleX links not allowed" xml:space="preserve">
<source>SimpleX links not allowed</source> <source>SimpleX links not allowed</source>
<target>Les liens SimpleX ne sont pas autorisés</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="SimpleX one-time invitation" xml:space="preserve"> <trans-unit id="SimpleX one-time invitation" xml:space="preserve">
@@ -5345,6 +5371,7 @@ Erreur: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Square, circle, or anything in between." xml:space="preserve"> <trans-unit id="Square, circle, or anything in between." xml:space="preserve">
<source>Square, circle, or anything in between.</source> <source>Square, circle, or anything in between.</source>
<target>Carré, circulaire, ou toute autre forme intermédiaire.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Start chat" xml:space="preserve"> <trans-unit id="Start chat" xml:space="preserve">
@@ -6150,6 +6177,7 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien
</trans-unit> </trans-unit>
<trans-unit id="Voice messages not allowed" xml:space="preserve"> <trans-unit id="Voice messages not allowed" xml:space="preserve">
<source>Voice messages not allowed</source> <source>Voice messages not allowed</source>
<target>Les messages vocaux ne sont pas autorisés</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Voice messages prohibited!" xml:space="preserve"> <trans-unit id="Voice messages prohibited!" xml:space="preserve">
@@ -6224,6 +6252,7 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien
</trans-unit> </trans-unit>
<trans-unit id="When connecting audio and video calls." xml:space="preserve"> <trans-unit id="When connecting audio and video calls." xml:space="preserve">
<source>When connecting audio and video calls.</source> <source>When connecting audio and video calls.</source>
<target>Lors des appels audio et vidéo.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="When people request to connect, you can accept or reject it." xml:space="preserve"> <trans-unit id="When people request to connect, you can accept or reject it." xml:space="preserve">
@@ -6238,14 +6267,17 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien
</trans-unit> </trans-unit>
<trans-unit id="WiFi" xml:space="preserve"> <trans-unit id="WiFi" xml:space="preserve">
<source>WiFi</source> <source>WiFi</source>
<target>WiFi</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Will be enabled in direct chats!" xml:space="preserve"> <trans-unit id="Will be enabled in direct chats!" xml:space="preserve">
<source>Will be enabled in direct chats!</source> <source>Will be enabled in direct chats!</source>
<target>Activé dans les discussions directes!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Wired ethernet" xml:space="preserve"> <trans-unit id="Wired ethernet" xml:space="preserve">
<source>Wired ethernet</source> <source>Wired ethernet</source>
<target>Ethernet câblé</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="With encrypted files and media." xml:space="preserve"> <trans-unit id="With encrypted files and media." xml:space="preserve">
@@ -6733,6 +6765,7 @@ Les serveurs SimpleX ne peuvent pas voir votre profil.</target>
</trans-unit> </trans-unit>
<trans-unit id="admins" xml:space="preserve"> <trans-unit id="admins" xml:space="preserve">
<source>admins</source> <source>admins</source>
<target>admins</target>
<note>feature role</note> <note>feature role</note>
</trans-unit> </trans-unit>
<trans-unit id="agreeing encryption for %@…" xml:space="preserve"> <trans-unit id="agreeing encryption for %@…" xml:space="preserve">
@@ -6747,6 +6780,7 @@ Les serveurs SimpleX ne peuvent pas voir votre profil.</target>
</trans-unit> </trans-unit>
<trans-unit id="all members" xml:space="preserve"> <trans-unit id="all members" xml:space="preserve">
<source>all members</source> <source>all members</source>
<target>tous les membres</target>
<note>feature role</note> <note>feature role</note>
</trans-unit> </trans-unit>
<trans-unit id="always" xml:space="preserve"> <trans-unit id="always" xml:space="preserve">
@@ -7081,6 +7115,7 @@ Les serveurs SimpleX ne peuvent pas voir votre profil.</target>
</trans-unit> </trans-unit>
<trans-unit id="forwarded" xml:space="preserve"> <trans-unit id="forwarded" xml:space="preserve">
<source>forwarded</source> <source>forwarded</source>
<target>transféré</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="group deleted" xml:space="preserve"> <trans-unit id="group deleted" xml:space="preserve">
@@ -7292,6 +7327,7 @@ Les serveurs SimpleX ne peuvent pas voir votre profil.</target>
</trans-unit> </trans-unit>
<trans-unit id="owners" xml:space="preserve"> <trans-unit id="owners" xml:space="preserve">
<source>owners</source> <source>owners</source>
<target>propriétaires</target>
<note>feature role</note> <note>feature role</note>
</trans-unit> </trans-unit>
<trans-unit id="peer-to-peer" xml:space="preserve"> <trans-unit id="peer-to-peer" xml:space="preserve">
@@ -7346,10 +7382,12 @@ Les serveurs SimpleX ne peuvent pas voir votre profil.</target>
</trans-unit> </trans-unit>
<trans-unit id="saved" xml:space="preserve"> <trans-unit id="saved" xml:space="preserve">
<source>saved</source> <source>saved</source>
<target>enregistré</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="saved from %@" xml:space="preserve"> <trans-unit id="saved from %@" xml:space="preserve">
<source>saved from %@</source> <source>saved from %@</source>
<target>enregistré à partir de %@</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="sec" xml:space="preserve"> <trans-unit id="sec" xml:space="preserve">
@@ -7494,6 +7532,7 @@ Les serveurs SimpleX ne peuvent pas voir votre profil.</target>
</trans-unit> </trans-unit>
<trans-unit id="you" xml:space="preserve"> <trans-unit id="you" xml:space="preserve">
<source>you</source> <source>you</source>
<target>vous</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="you are invited to group" xml:space="preserve"> <trans-unit id="you are invited to group" xml:space="preserve">
@@ -94,17 +94,17 @@
</trans-unit> </trans-unit>
<trans-unit id="%@ and %@ connected" xml:space="preserve"> <trans-unit id="%@ and %@ connected" xml:space="preserve">
<source>%@ and %@ connected</source> <source>%@ and %@ connected</source>
<target>%@ és %@ csatlakozott</target> <target>%@ és %@ kapcsolódott</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="%@ at %@:" xml:space="preserve"> <trans-unit id="%@ at %@:" xml:space="preserve">
<source>%1$@ at %2$@:</source> <source>%1$@ at %2$@:</source>
<target>%1$@ %2$@-kor:</target> <target>%1$@ ekkor: %2$@</target>
<note>copied message info, &lt;sender&gt; at &lt;time&gt;</note> <note>copied message info, &lt;sender&gt; at &lt;time&gt;</note>
</trans-unit> </trans-unit>
<trans-unit id="%@ connected" xml:space="preserve"> <trans-unit id="%@ connected" xml:space="preserve">
<source>%@ connected</source> <source>%@ connected</source>
<target>%@ csatlakozott</target> <target>%@ kapcsolódott</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="%@ downloaded" xml:space="preserve"> <trans-unit id="%@ downloaded" xml:space="preserve">
@@ -114,7 +114,7 @@
</trans-unit> </trans-unit>
<trans-unit id="%@ is connected!" xml:space="preserve"> <trans-unit id="%@ is connected!" xml:space="preserve">
<source>%@ is connected!</source> <source>%@ is connected!</source>
<target>%@ csatlakozott!</target> <target>%@ kapcsolódott!</target>
<note>notification title</note> <note>notification title</note>
</trans-unit> </trans-unit>
<trans-unit id="%@ is not verified" xml:space="preserve"> <trans-unit id="%@ is not verified" xml:space="preserve">
@@ -139,7 +139,7 @@
</trans-unit> </trans-unit>
<trans-unit id="%@ wants to connect!" xml:space="preserve"> <trans-unit id="%@ wants to connect!" xml:space="preserve">
<source>%@ wants to connect!</source> <source>%@ wants to connect!</source>
<target>%@ csatlakozni szeretne!</target> <target>%@ kapcsolódni szeretne!</target>
<note>notification title</note> <note>notification title</note>
</trans-unit> </trans-unit>
<trans-unit id="%@, %@ and %lld members" xml:space="preserve"> <trans-unit id="%@, %@ and %lld members" xml:space="preserve">
@@ -149,7 +149,7 @@
</trans-unit> </trans-unit>
<trans-unit id="%@, %@ and %lld other members connected" xml:space="preserve"> <trans-unit id="%@, %@ and %lld other members connected" xml:space="preserve">
<source>%@, %@ and %lld other members connected</source> <source>%@, %@ and %lld other members connected</source>
<target>%@, %@ és további %lld tag csatlakozott</target> <target>%@, %@ és további %lld tag kapcsolódott</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="%@:" xml:space="preserve"> <trans-unit id="%@:" xml:space="preserve">
@@ -319,7 +319,7 @@
</trans-unit> </trans-unit>
<trans-unit id="(this device v%@)" xml:space="preserve"> <trans-unit id="(this device v%@)" xml:space="preserve">
<source>(this device v%@)</source> <source>(this device v%@)</source>
<target>(ez az eszköz v%@)</target> <target>(ez az eszköz: v%@)</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id=")" xml:space="preserve"> <trans-unit id=")" xml:space="preserve">
@@ -329,7 +329,7 @@
</trans-unit> </trans-unit>
<trans-unit id="**Add contact**: to create a new invitation link, or connect via a link you received." xml:space="preserve"> <trans-unit id="**Add contact**: to create a new invitation link, or connect via a link you received." xml:space="preserve">
<source>**Add contact**: to create a new invitation link, or connect via a link you received.</source> <source>**Add contact**: to create a new invitation link, or connect via a link you received.</source>
<target>**Ismerős hozzáadása**: új meghívó hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő csatlakozáshoz.</target> <target>**Ismerős hozzáadása**: új meghívó hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő kapcsolódáshoz.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="**Add new contact**: to create your one-time QR Code for your contact." xml:space="preserve"> <trans-unit id="**Add new contact**: to create your one-time QR Code for your contact." xml:space="preserve">
@@ -585,7 +585,7 @@
</trans-unit> </trans-unit>
<trans-unit id="Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." xml:space="preserve"> <trans-unit id="Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." xml:space="preserve">
<source>Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts.</source> <source>Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts.</source>
<target>Azonosító hozzáadása a profilhoz, hogy az ismerősök megoszthassák másokkal. A profilfrissítés elküldésre kerül ismerősők számára.</target> <target>Azonosító hozzáadása a profilhoz, hogy az ismerősei megoszthassák másokkal. A profilfrissítés elküldésre kerül az ismerősei számára.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Add contact" xml:space="preserve"> <trans-unit id="Add contact" xml:space="preserve">
@@ -640,7 +640,7 @@
</trans-unit> </trans-unit>
<trans-unit id="Admins can create the links to join groups." xml:space="preserve"> <trans-unit id="Admins can create the links to join groups." xml:space="preserve">
<source>Admins can create the links to join groups.</source> <source>Admins can create the links to join groups.</source>
<target>Az adminok hivatkozásokat hozhatnak létre a csoportokhoz való csatlakozáshoz.</target> <target>Az adminok hivatkozásokat hozhatnak létre a csoportokhoz való kapcsolódáshoz.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Advanced network settings" xml:space="preserve"> <trans-unit id="Advanced network settings" xml:space="preserve">
@@ -665,7 +665,7 @@
</trans-unit> </trans-unit>
<trans-unit id="All group members will remain connected." xml:space="preserve"> <trans-unit id="All group members will remain connected." xml:space="preserve">
<source>All group members will remain connected.</source> <source>All group members will remain connected.</source>
<target>Minden csoporttag csatlakoztatva marad.</target> <target>Minden csoporttag kapcsolódva marad.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="All messages will be deleted - this cannot be undone!" xml:space="preserve"> <trans-unit id="All messages will be deleted - this cannot be undone!" xml:space="preserve">
@@ -685,7 +685,7 @@
</trans-unit> </trans-unit>
<trans-unit id="All your contacts will remain connected." xml:space="preserve"> <trans-unit id="All your contacts will remain connected." xml:space="preserve">
<source>All your contacts will remain connected.</source> <source>All your contacts will remain connected.</source>
<target>Minden ismerős csatlakoztatva marad.</target> <target>Minden ismerős kapcsolódva marad.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="All your contacts will remain connected. Profile update will be sent to your contacts." xml:space="preserve"> <trans-unit id="All your contacts will remain connected. Profile update will be sent to your contacts." xml:space="preserve">
@@ -795,7 +795,7 @@
</trans-unit> </trans-unit>
<trans-unit id="Already connected?" xml:space="preserve"> <trans-unit id="Already connected?" xml:space="preserve">
<source>Already connected?</source> <source>Already connected?</source>
<target>Csatlakoztatva?</target> <target>Már kapcsolódott?</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Already connecting!" xml:space="preserve"> <trans-unit id="Already connecting!" xml:space="preserve">
@@ -805,7 +805,7 @@
</trans-unit> </trans-unit>
<trans-unit id="Already joining the group!" xml:space="preserve"> <trans-unit id="Already joining the group!" xml:space="preserve">
<source>Already joining the group!</source> <source>Already joining the group!</source>
<target>Csatlakozás folyamatban!</target> <target>A csatlakozás folyamatban van a csoporthoz!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Always use relay" xml:space="preserve"> <trans-unit id="Always use relay" xml:space="preserve">
@@ -920,7 +920,7 @@
</trans-unit> </trans-unit>
<trans-unit id="Authentication is required before the call is connected, but you may miss calls." xml:space="preserve"> <trans-unit id="Authentication is required before the call is connected, but you may miss calls." xml:space="preserve">
<source>Authentication is required before the call is connected, but you may miss calls.</source> <source>Authentication is required before the call is connected, but you may miss calls.</source>
<target>A hívás csatlakoztatása előtt hitelesítésre van szükség, de előfordulhat, hogy nem tud hívásokat fogadni.</target> <target>A hívás összekapcsolása előtt hitelesítésre van szükség, de előfordulhat, hogy lemarad a hívásokról.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Authentication unavailable" xml:space="preserve"> <trans-unit id="Authentication unavailable" xml:space="preserve">
@@ -1070,12 +1070,12 @@
</trans-unit> </trans-unit>
<trans-unit id="Cancel" xml:space="preserve"> <trans-unit id="Cancel" xml:space="preserve">
<source>Cancel</source> <source>Cancel</source>
<target>Megszakítás</target> <target>Mégse</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Cancel migration" xml:space="preserve"> <trans-unit id="Cancel migration" xml:space="preserve">
<source>Cancel migration</source> <source>Cancel migration</source>
<target>Átköltöztetés megszakítása</target> <target>Átköltöztetés visszavonása</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Cannot access keychain to save database password" xml:space="preserve"> <trans-unit id="Cannot access keychain to save database password" xml:space="preserve">
@@ -1350,17 +1350,17 @@ Ez az egyszer használatos hivatkozása!</target>
</trans-unit> </trans-unit>
<trans-unit id="Connect via contact address" xml:space="preserve"> <trans-unit id="Connect via contact address" xml:space="preserve">
<source>Connect via contact address</source> <source>Connect via contact address</source>
<target>Csatlakozás ismerős azonosítója által</target> <target>Kapcsolódás a kapcsolattartási azonosítón keresztül</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Connect via link" xml:space="preserve"> <trans-unit id="Connect via link" xml:space="preserve">
<source>Connect via link</source> <source>Connect via link</source>
<target>Csatlakozás egy hivatkozáson keresztül</target> <target>Kapcsolódás egy hivatkozáson keresztül</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Connect via one-time link" xml:space="preserve"> <trans-unit id="Connect via one-time link" xml:space="preserve">
<source>Connect via one-time link</source> <source>Connect via one-time link</source>
<target>Csatlakozás egyszer használatos hivatkozáson keresztül</target> <target>Kapcsolódás egyszer használatos hivatkozáson keresztül</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Connect with %@" xml:space="preserve"> <trans-unit id="Connect with %@" xml:space="preserve">
@@ -1440,12 +1440,12 @@ Ez az egyszer használatos hivatkozása!</target>
</trans-unit> </trans-unit>
<trans-unit id="Contact is connected" xml:space="preserve"> <trans-unit id="Contact is connected" xml:space="preserve">
<source>Contact is connected</source> <source>Contact is connected</source>
<target>Ismerős csatlakozott</target> <target>Ismerőse kapcsolódott</target>
<note>notification</note> <note>notification</note>
</trans-unit> </trans-unit>
<trans-unit id="Contact is not connected yet!" xml:space="preserve"> <trans-unit id="Contact is not connected yet!" xml:space="preserve">
<source>Contact is not connected yet!</source> <source>Contact is not connected yet!</source>
<target>Az ismerős még nem csatlakozott!</target> <target>Az ismerőse még nem kapcsolódott!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Contact name" xml:space="preserve"> <trans-unit id="Contact name" xml:space="preserve">
@@ -2570,7 +2570,7 @@ Ez a művelet nem vonható vissza!</target>
</trans-unit> </trans-unit>
<trans-unit id="Error stopping chat" xml:space="preserve"> <trans-unit id="Error stopping chat" xml:space="preserve">
<source>Error stopping chat</source> <source>Error stopping chat</source>
<target>Hiba a csevegés megszakításakor</target> <target>Hiba a csevegés megállításakor</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Error switching profile!" xml:space="preserve"> <trans-unit id="Error switching profile!" xml:space="preserve">
@@ -2700,12 +2700,12 @@ Ez a művelet nem vonható vissza!</target>
</trans-unit> </trans-unit>
<trans-unit id="File will be received when your contact completes uploading it." xml:space="preserve"> <trans-unit id="File will be received when your contact completes uploading it." xml:space="preserve">
<source>File will be received when your contact completes uploading it.</source> <source>File will be received when your contact completes uploading it.</source>
<target>A fájl akkor érkezik meg, amikor ismerőse befejezte annak feltöltést.</target> <target>A fájl akkor érkezik meg, amikor a küldője befejezte annak feltöltését.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="File will be received when your contact is online, please wait or check later!" xml:space="preserve"> <trans-unit id="File will be received when your contact is online, please wait or check later!" xml:space="preserve">
<source>File will be received when your contact is online, please wait or check later!</source> <source>File will be received when your contact is online, please wait or check later!</source>
<target>A fájl akkor érkezik meg, amint ismerőse online lesz, várjon, vagy ellenőrizze később!</target> <target>A fájl akkor érkezik meg, amikor a küldője elérhető lesz, várjon, vagy ellenőrizze később!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="File: %@" xml:space="preserve"> <trans-unit id="File: %@" xml:space="preserve">
@@ -3105,12 +3105,12 @@ Ez a művelet nem vonható vissza!</target>
</trans-unit> </trans-unit>
<trans-unit id="Image will be received when your contact completes uploading it." xml:space="preserve"> <trans-unit id="Image will be received when your contact completes uploading it." xml:space="preserve">
<source>Image will be received when your contact completes uploading it.</source> <source>Image will be received when your contact completes uploading it.</source>
<target>A kép akkor érkezik meg, amikor ismerőse befejezte annak feltöltését.</target> <target>A kép akkor érkezik meg, amikor a küldője befejezte annak feltöltését.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Image will be received when your contact is online, please wait or check later!" xml:space="preserve"> <trans-unit id="Image will be received when your contact is online, please wait or check later!" xml:space="preserve">
<source>Image will be received when your contact is online, please wait or check later!</source> <source>Image will be received when your contact is online, please wait or check later!</source>
<target>A kép akkor érkezik meg, amikor ismerős elérhető lesz, várjon vagy ellenőrizze később!</target> <target>A kép akkor érkezik meg, amikor a küldője elérhető lesz, várjon, vagy ellenőrizze később!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Immediately" xml:space="preserve"> <trans-unit id="Immediately" xml:space="preserve">
@@ -3368,7 +3368,7 @@ Ez a művelet nem vonható vissza!</target>
</trans-unit> </trans-unit>
<trans-unit id="It seems like you are already connected via this link. If it is not the case, there was an error (%@)." xml:space="preserve"> <trans-unit id="It seems like you are already connected via this link. If it is not the case, there was an error (%@)." xml:space="preserve">
<source>It seems like you are already connected via this link. If it is not the case, there was an error (%@).</source> <source>It seems like you are already connected via this link. If it is not the case, there was an error (%@).</source>
<target>Úgy tűnik, már csatlakozott ezen a hivatkozáson keresztül. Ha ez nem így van, akkor hiba történt (%@).</target> <target>Úgy tűnik, már kapcsolódott ezen a hivatkozáson keresztül. Ha ez nem így van, akkor hiba történt (%@).</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Italian interface" xml:space="preserve"> <trans-unit id="Italian interface" xml:space="preserve">
@@ -4004,17 +4004,17 @@ Ez az ön hivatkozása a(z) %@ csoporthoz!</target>
</trans-unit> </trans-unit>
<trans-unit id="Onion hosts will be required for connection. Requires enabling VPN." xml:space="preserve"> <trans-unit id="Onion hosts will be required for connection. Requires enabling VPN." xml:space="preserve">
<source>Onion hosts will be required for connection. Requires enabling VPN.</source> <source>Onion hosts will be required for connection. Requires enabling VPN.</source>
<target>A csatlakozáshoz Onion host-okra lesz szükség. VPN engedélyezése szükséges.</target> <target>A kapcsolódáshoz Onion kiszolgálókra lesz szükség. VPN engedélyezése szükséges.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Onion hosts will be used when available. Requires enabling VPN." xml:space="preserve"> <trans-unit id="Onion hosts will be used when available. Requires enabling VPN." xml:space="preserve">
<source>Onion hosts will be used when available. Requires enabling VPN.</source> <source>Onion hosts will be used when available. Requires enabling VPN.</source>
<target>Onion host-ok használata, ha azok rendelkezésre állnak. VPN engedélyezése szükséges.</target> <target>Onion kiszolgálók használata, ha azok rendelkezésre állnak. VPN engedélyezése szükséges.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Onion hosts will not be used." xml:space="preserve"> <trans-unit id="Onion hosts will not be used." xml:space="preserve">
<source>Onion hosts will not be used.</source> <source>Onion hosts will not be used.</source>
<target>Onion host-ok nem lesznek használva.</target> <target>Onion kiszolgálók nem lesznek használva.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." xml:space="preserve"> <trans-unit id="Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." xml:space="preserve">
@@ -4214,7 +4214,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz!</target>
</trans-unit> </trans-unit>
<trans-unit id="Paste link to connect!" xml:space="preserve"> <trans-unit id="Paste link to connect!" xml:space="preserve">
<source>Paste link to connect!</source> <source>Paste link to connect!</source>
<target>Hivatkozás beillesztése a csatlakozáshoz!</target> <target>Hivatkozás beillesztése a kapcsolódáshoz!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Paste the link you received" xml:space="preserve"> <trans-unit id="Paste the link you received" xml:space="preserve">
@@ -4376,6 +4376,7 @@ Hiba: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Profile images" xml:space="preserve"> <trans-unit id="Profile images" xml:space="preserve">
<source>Profile images</source> <source>Profile images</source>
<target>Profilképek</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Profile name" xml:space="preserve"> <trans-unit id="Profile name" xml:space="preserve">
@@ -4585,12 +4586,12 @@ Hiba: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Reconnect all connected servers to force message delivery. It uses additional traffic." xml:space="preserve"> <trans-unit id="Reconnect all connected servers to force message delivery. It uses additional traffic." xml:space="preserve">
<source>Reconnect all connected servers to force message delivery. It uses additional traffic.</source> <source>Reconnect all connected servers to force message delivery. It uses additional traffic.</source>
<target>Az összes csatlakoztatott kiszolgáló újrakapcsolása az üzenetek kézbesítésének kikényszerítéséhez. Ez további forgalmat használ.</target> <target>Újrakapcsolódás az összes kiszolgálóhoz az üzenetek kézbesítésének kikényszerítéséhez. Ez további forgalmat használ.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Reconnect servers?" xml:space="preserve"> <trans-unit id="Reconnect servers?" xml:space="preserve">
<source>Reconnect servers?</source> <source>Reconnect servers?</source>
<target>Kiszolgálók újracsatlakoztatása?</target> <target>Újrakapcsolódás a kiszolgálókhoz?</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Record updated at" xml:space="preserve"> <trans-unit id="Record updated at" xml:space="preserve">
@@ -5000,7 +5001,7 @@ Hiba: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Send direct message to connect" xml:space="preserve"> <trans-unit id="Send direct message to connect" xml:space="preserve">
<source>Send direct message to connect</source> <source>Send direct message to connect</source>
<target>Közvetlen üzenet küldése a csatlakozáshoz</target> <target>Közvetlen üzenet küldése a kapcsolódáshoz</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Send disappearing message" xml:space="preserve"> <trans-unit id="Send disappearing message" xml:space="preserve">
@@ -5200,6 +5201,7 @@ Hiba: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Shape profile images" xml:space="preserve"> <trans-unit id="Shape profile images" xml:space="preserve">
<source>Shape profile images</source> <source>Shape profile images</source>
<target>Profilkép alakzat</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Share" xml:space="preserve"> <trans-unit id="Share" xml:space="preserve">
@@ -5219,7 +5221,7 @@ Hiba: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Share address with contacts?" xml:space="preserve"> <trans-unit id="Share address with contacts?" xml:space="preserve">
<source>Share address with contacts?</source> <source>Share address with contacts?</source>
<target>Megosztja az azonosítót az ismerősökkel?</target> <target>Megosztja az azonosítót az ismerőseivel?</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Share link" xml:space="preserve"> <trans-unit id="Share link" xml:space="preserve">
@@ -5304,7 +5306,7 @@ Hiba: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="SimpleX contact address" xml:space="preserve"> <trans-unit id="SimpleX contact address" xml:space="preserve">
<source>SimpleX contact address</source> <source>SimpleX contact address</source>
<target>SimpleX ismerős azonosítója</target> <target>SimpleX kapcsolattartási azonosító</target>
<note>simplex link type</note> <note>simplex link type</note>
</trans-unit> </trans-unit>
<trans-unit id="SimpleX encrypted message or connection event" xml:space="preserve"> <trans-unit id="SimpleX encrypted message or connection event" xml:space="preserve">
@@ -5369,6 +5371,7 @@ Hiba: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Square, circle, or anything in between." xml:space="preserve"> <trans-unit id="Square, circle, or anything in between." xml:space="preserve">
<source>Square, circle, or anything in between.</source> <source>Square, circle, or anything in between.</source>
<target>Négyzet, kör vagy bármi a kettő között.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Start chat" xml:space="preserve"> <trans-unit id="Start chat" xml:space="preserve">
@@ -5388,62 +5391,62 @@ Hiba: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Stop" xml:space="preserve"> <trans-unit id="Stop" xml:space="preserve">
<source>Stop</source> <source>Stop</source>
<target>Megszakítás</target> <target>Megállítás</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Stop SimpleX" xml:space="preserve"> <trans-unit id="Stop SimpleX" xml:space="preserve">
<source>Stop SimpleX</source> <source>Stop SimpleX</source>
<target>A SimpleX megszakítása</target> <target>SimpleX megállítása</target>
<note>authentication reason</note> <note>authentication reason</note>
</trans-unit> </trans-unit>
<trans-unit id="Stop chat" xml:space="preserve"> <trans-unit id="Stop chat" xml:space="preserve">
<source>Stop chat</source> <source>Stop chat</source>
<target>Csevegési szolgáltatás megszakítása</target> <target>Csevegési szolgáltatás megállítása</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Stop chat to enable database actions" xml:space="preserve"> <trans-unit id="Stop chat to enable database actions" xml:space="preserve">
<source>Stop chat to enable database actions</source> <source>Stop chat to enable database actions</source>
<target>Csevegés megszakítása az adatbázis-műveletek engedélyezéséhez</target> <target>Csevegés megállítása az adatbázis-műveletek engedélyezéséhez</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." xml:space="preserve"> <trans-unit id="Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." xml:space="preserve">
<source>Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped.</source> <source>Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped.</source>
<target>A csevegés megszakítása a csevegőadatbázis exportálásához, importálásához vagy törléséhez. A csevegés megszakítása alatt nem tud üzeneteket fogadni és küldeni.</target> <target>A csevegés megállítása a csevegő adatbázis exportálásához, importálásához vagy törléséhez. A csevegés megállítása alatt nem tud üzeneteket fogadni és küldeni.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Stop chat?" xml:space="preserve"> <trans-unit id="Stop chat?" xml:space="preserve">
<source>Stop chat?</source> <source>Stop chat?</source>
<target>Csevegési szolgáltatás megszakítása?</target> <target>Csevegési szolgáltatás megállítása?</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Stop file" xml:space="preserve"> <trans-unit id="Stop file" xml:space="preserve">
<source>Stop file</source> <source>Stop file</source>
<target>Fájl megszakítása</target> <target>Fájl megállítása</target>
<note>cancel file action</note> <note>cancel file action</note>
</trans-unit> </trans-unit>
<trans-unit id="Stop receiving file?" xml:space="preserve"> <trans-unit id="Stop receiving file?" xml:space="preserve">
<source>Stop receiving file?</source> <source>Stop receiving file?</source>
<target>Fájl fogadás megszakítása?</target> <target>Fájl fogadás megállítása?</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Stop sending file?" xml:space="preserve"> <trans-unit id="Stop sending file?" xml:space="preserve">
<source>Stop sending file?</source> <source>Stop sending file?</source>
<target>Fájl küldés megszakítása?</target> <target>Fájl küldés megállítása?</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Stop sharing" xml:space="preserve"> <trans-unit id="Stop sharing" xml:space="preserve">
<source>Stop sharing</source> <source>Stop sharing</source>
<target>Megosztás megszakítása</target> <target>Megosztás megállítása</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Stop sharing address?" xml:space="preserve"> <trans-unit id="Stop sharing address?" xml:space="preserve">
<source>Stop sharing address?</source> <source>Stop sharing address?</source>
<target>Címmegosztás megszakítása?</target> <target>Címmegosztás megállítása?</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Stopping chat" xml:space="preserve"> <trans-unit id="Stopping chat" xml:space="preserve">
<source>Stopping chat</source> <source>Stopping chat</source>
<target>Csevegés megszakítása</target> <target>Csevegés megállítása folyamatban</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Submit" xml:space="preserve"> <trans-unit id="Submit" xml:space="preserve">
@@ -5498,7 +5501,7 @@ Hiba: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Tap to Connect" xml:space="preserve"> <trans-unit id="Tap to Connect" xml:space="preserve">
<source>Tap to Connect</source> <source>Tap to Connect</source>
<target>Koppintson a csatlakozáshoz</target> <target>Koppintson a kapcsolódáshoz</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Tap to activate profile." xml:space="preserve"> <trans-unit id="Tap to activate profile." xml:space="preserve">
@@ -5600,7 +5603,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő.</target>
</trans-unit> </trans-unit>
<trans-unit id="The contact you shared this link with will NOT be able to connect!" xml:space="preserve"> <trans-unit id="The contact you shared this link with will NOT be able to connect!" xml:space="preserve">
<source>The contact you shared this link with will NOT be able to connect!</source> <source>The contact you shared this link with will NOT be able to connect!</source>
<target>Ismerőse NEM fog tudni csatlakozni, akivel megosztotta ezt a hivatkozást!</target> <target>Ismerőse, akivel megosztotta ezt a hivatkozást, NEM fog tudni kapcsolódni!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="The created archive is available via app Settings / Database / Old database archive." xml:space="preserve"> <trans-unit id="The created archive is available via app Settings / Database / Old database archive." xml:space="preserve">
@@ -5640,7 +5643,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő.</target>
</trans-unit> </trans-unit>
<trans-unit id="The profile is only shared with your contacts." xml:space="preserve"> <trans-unit id="The profile is only shared with your contacts." xml:space="preserve">
<source>The profile is only shared with your contacts.</source> <source>The profile is only shared with your contacts.</source>
<target>Profilja csak az ismerősök számára kerül megosztásra.</target> <target>Profilja csak az ismerőseivel kerül megosztásra.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="The second tick we missed! ✅" xml:space="preserve"> <trans-unit id="The second tick we missed! ✅" xml:space="preserve">
@@ -5745,7 +5748,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő.</target>
</trans-unit> </trans-unit>
<trans-unit id="To connect, your contact can scan QR code or use the link in the app." xml:space="preserve"> <trans-unit id="To connect, your contact can scan QR code or use the link in the app." xml:space="preserve">
<source>To connect, your contact can scan QR code or use the link in the app.</source> <source>To connect, your contact can scan QR code or use the link in the app.</source>
<target>A csatlakozáshoz az ismerős beolvashatja a QR-kódot, vagy használhatja az alkalmazásban található hivatkozást.</target> <target>A kapcsolódáshoz az ismerőse beolvashatja a QR-kódot, vagy használhatja az alkalmazásban található hivatkozást.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="To hide unwanted messages." xml:space="preserve"> <trans-unit id="To hide unwanted messages." xml:space="preserve">
@@ -5797,7 +5800,7 @@ A funkció engedélyezése előtt a rendszer felszólítja a hitelesítés befej
</trans-unit> </trans-unit>
<trans-unit id="Toggle incognito when connecting." xml:space="preserve"> <trans-unit id="Toggle incognito when connecting." xml:space="preserve">
<source>Toggle incognito when connecting.</source> <source>Toggle incognito when connecting.</source>
<target>Inkognító mód csatlakozáskor.</target> <target>Inkognitó mód kapcsolódáskor.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Transport isolation" xml:space="preserve"> <trans-unit id="Transport isolation" xml:space="preserve">
@@ -5807,12 +5810,12 @@ A funkció engedélyezése előtt a rendszer felszólítja a hitelesítés befej
</trans-unit> </trans-unit>
<trans-unit id="Trying to connect to the server used to receive messages from this contact (error: %@)." xml:space="preserve"> <trans-unit id="Trying to connect to the server used to receive messages from this contact (error: %@)." xml:space="preserve">
<source>Trying to connect to the server used to receive messages from this contact (error: %@).</source> <source>Trying to connect to the server used to receive messages from this contact (error: %@).</source>
<target>Csatlakozási kísérlet a kapcsolat üzeneteinek fogadására használt kiszolgálóhoz (hiba: %@).</target> <target>Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott ismerőstől érkező üzenetek fogadására szolgál (hiba: %@).</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Trying to connect to the server used to receive messages from this contact." xml:space="preserve"> <trans-unit id="Trying to connect to the server used to receive messages from this contact." xml:space="preserve">
<source>Trying to connect to the server used to receive messages from this contact.</source> <source>Trying to connect to the server used to receive messages from this contact.</source>
<target>Csatlakozási kísérlet a kapcsolat üzeneteinek fogadására használt kiszolgálóhoz ettől az ismerőstől.</target> <target>Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott ismerőstől érkező üzenetek fogadására szolgál.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Turkish interface" xml:space="preserve"> <trans-unit id="Turkish interface" xml:space="preserve">
@@ -5918,8 +5921,8 @@ A funkció engedélyezése előtt a rendszer felszólítja a hitelesítés befej
<trans-unit id="Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.&#10;To connect, please ask your contact to create another connection link and check that you have a stable network connection." xml:space="preserve"> <trans-unit id="Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.&#10;To connect, please ask your contact to create another connection link and check that you have a stable network connection." xml:space="preserve">
<source>Unless your contact deleted the connection or this link was already used, it might be a bug - please report it. <source>Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.
To connect, please ask your contact to create another connection link and check that you have a stable network connection.</source> To connect, please ask your contact to create another connection link and check that you have a stable network connection.</source>
<target>Hacsak az ismerős nem törölte a kapcsolatot, vagy ez a hivatkozás már használatban volt, hiba lehet kérjük, jelentse. <target>Hacsak az ismerőse nem törölte a kapcsolatot, vagy ez a hivatkozás már használatban volt, lehet hogy ez egy hiba jelentse a problémát.
A csatlakozáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsolati hivatkozást, és ellenőrizze, hogy a hálózati kapcsolat stabil-e.</target> A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsolati hivatkozást, és ellenőrizze, hogy a hálózati kapcsolat stabil-e.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Unlink" xml:space="preserve"> <trans-unit id="Unlink" xml:space="preserve">
@@ -5964,7 +5967,7 @@ A csatlakozáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsola
</trans-unit> </trans-unit>
<trans-unit id="Update .onion hosts setting?" xml:space="preserve"> <trans-unit id="Update .onion hosts setting?" xml:space="preserve">
<source>Update .onion hosts setting?</source> <source>Update .onion hosts setting?</source>
<target>Tor .onion host beállítások frissítése?</target> <target>Tor .onion kiszolgálók beállításainak frissítése?</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Update database passphrase" xml:space="preserve"> <trans-unit id="Update database passphrase" xml:space="preserve">
@@ -5989,7 +5992,7 @@ A csatlakozáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsola
</trans-unit> </trans-unit>
<trans-unit id="Updating this setting will re-connect the client to all servers." xml:space="preserve"> <trans-unit id="Updating this setting will re-connect the client to all servers." xml:space="preserve">
<source>Updating this setting will re-connect the client to all servers.</source> <source>Updating this setting will re-connect the client to all servers.</source>
<target>A beállítás frissítésével a kliens újracsatlakozik az összes kiszolgálóhoz.</target> <target>A beállítás frissítésével a kliens újrakapcsolódik az összes kiszolgálóhoz.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Upgrade and open chat" xml:space="preserve"> <trans-unit id="Upgrade and open chat" xml:space="preserve">
@@ -6014,7 +6017,7 @@ A csatlakozáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsola
</trans-unit> </trans-unit>
<trans-unit id="Use .onion hosts" xml:space="preserve"> <trans-unit id="Use .onion hosts" xml:space="preserve">
<source>Use .onion hosts</source> <source>Use .onion hosts</source>
<target>Tor .onion hostok használata</target> <target>Tor .onion kiszolgálók használata</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Use SimpleX Chat servers?" xml:space="preserve"> <trans-unit id="Use SimpleX Chat servers?" xml:space="preserve">
@@ -6074,7 +6077,7 @@ A csatlakozáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsola
</trans-unit> </trans-unit>
<trans-unit id="Using .onion hosts requires compatible VPN provider." xml:space="preserve"> <trans-unit id="Using .onion hosts requires compatible VPN provider." xml:space="preserve">
<source>Using .onion hosts requires compatible VPN provider.</source> <source>Using .onion hosts requires compatible VPN provider.</source>
<target>A .onion hosztok használatához kompatibilis VPN szolgáltatóra van szükség.</target> <target>A .onion kiszolgálók használatához kompatibilis VPN szolgáltatóra van szükség.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Using SimpleX Chat servers." xml:space="preserve"> <trans-unit id="Using SimpleX Chat servers." xml:space="preserve">
@@ -6134,12 +6137,12 @@ A csatlakozáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsola
</trans-unit> </trans-unit>
<trans-unit id="Video will be received when your contact completes uploading it." xml:space="preserve"> <trans-unit id="Video will be received when your contact completes uploading it." xml:space="preserve">
<source>Video will be received when your contact completes uploading it.</source> <source>Video will be received when your contact completes uploading it.</source>
<target>A videó akkor érkezik meg, amikor az ismerőse befejezte annak feltöltését.</target> <target>A videó akkor érkezik meg, amikor a küldője befejezte annak feltöltését.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Video will be received when your contact is online, please wait or check later!" xml:space="preserve"> <trans-unit id="Video will be received when your contact is online, please wait or check later!" xml:space="preserve">
<source>Video will be received when your contact is online, please wait or check later!</source> <source>Video will be received when your contact is online, please wait or check later!</source>
<target>A videó akkor érkezik meg, amikor az ismerős elérhető, várjon, vagy ellenőrizze később!</target> <target>A videó akkor érkezik meg, amikor a küldője elérhető lesz, várjon, vagy ellenőrizze később!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Videos and files up to 1gb" xml:space="preserve"> <trans-unit id="Videos and files up to 1gb" xml:space="preserve">
@@ -6254,7 +6257,7 @@ A csatlakozáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsola
</trans-unit> </trans-unit>
<trans-unit id="When people request to connect, you can accept or reject it." xml:space="preserve"> <trans-unit id="When people request to connect, you can accept or reject it." xml:space="preserve">
<source>When people request to connect, you can accept or reject it.</source> <source>When people request to connect, you can accept or reject it.</source>
<target>Csatlakozási kérelmek esetében, elfogadhatja vagy elutasíthatja azokat.</target> <target>Amikor az emberek kapcsolódást kérelmeznek, ön elfogadhatja vagy elutasíthatja azokat.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." xml:space="preserve"> <trans-unit id="When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." xml:space="preserve">
@@ -6339,12 +6342,12 @@ A csatlakozáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsola
</trans-unit> </trans-unit>
<trans-unit id="You are already connecting to %@." xml:space="preserve"> <trans-unit id="You are already connecting to %@." xml:space="preserve">
<source>You are already connecting to %@.</source> <source>You are already connecting to %@.</source>
<target>Már folyamatban van a csatlakozás ehhez: %@.</target> <target>Már folyamatban van a kapcsolódás ehhez: %@.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="You are already connecting via this one-time link!" xml:space="preserve"> <trans-unit id="You are already connecting via this one-time link!" xml:space="preserve">
<source>You are already connecting via this one-time link!</source> <source>You are already connecting via this one-time link!</source>
<target>Már csatlakozik ezen az egyszer használatos hivatkozáson keresztül!</target> <target>A kapcsolódás már folyamatban van ezen az egyszer használatos hivatkozáson keresztül!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="You are already in group %@." xml:space="preserve"> <trans-unit id="You are already in group %@." xml:space="preserve">
@@ -6354,17 +6357,17 @@ A csatlakozáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsola
</trans-unit> </trans-unit>
<trans-unit id="You are already joining the group %@." xml:space="preserve"> <trans-unit id="You are already joining the group %@." xml:space="preserve">
<source>You are already joining the group %@.</source> <source>You are already joining the group %@.</source>
<target>Már folyamatban van a csatlakozás a csoporthoz %@.</target> <target>A csatlakozás már folyamatban van a(z) %@ csoporthoz.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="You are already joining the group via this link!" xml:space="preserve"> <trans-unit id="You are already joining the group via this link!" xml:space="preserve">
<source>You are already joining the group via this link!</source> <source>You are already joining the group via this link!</source>
<target>Már csatlakozott a csoporthoz ezen a hivatkozáson keresztül!</target> <target>A csatlakozás már folyamatban van a csoporthoz ezen a hivatkozáson keresztül!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="You are already joining the group via this link." xml:space="preserve"> <trans-unit id="You are already joining the group via this link." xml:space="preserve">
<source>You are already joining the group via this link.</source> <source>You are already joining the group via this link.</source>
<target>Ezen a hivatkozáson keresztül már csatlakozik a csoporthoz.</target> <target>A csatlakozás már folyamatban van a csoporthoz ezen a hivatkozáson keresztül.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="You are already joining the group!&#10;Repeat join request?" xml:space="preserve"> <trans-unit id="You are already joining the group!&#10;Repeat join request?" xml:space="preserve">
@@ -6376,7 +6379,7 @@ Csatlakozási kérés megismétlése?</target>
</trans-unit> </trans-unit>
<trans-unit id="You are connected to the server used to receive messages from this contact." xml:space="preserve"> <trans-unit id="You are connected to the server used to receive messages from this contact." xml:space="preserve">
<source>You are connected to the server used to receive messages from this contact.</source> <source>You are connected to the server used to receive messages from this contact.</source>
<target>Kiszolgálóhoz történő csatlakozás, mely az adott ismerőstől érkező üzenetek fogadására szolgál.</target> <target>Már kapcsolódott ahhoz a kiszolgálóhoz, amely az adott ismerőstől érkező üzenetek fogadására szolgál.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="You are invited to group" xml:space="preserve"> <trans-unit id="You are invited to group" xml:space="preserve">
@@ -6436,12 +6439,12 @@ Csatlakozási kérés megismétlése?</target>
</trans-unit> </trans-unit>
<trans-unit id="You can share this address with your contacts to let them connect with **%@**." xml:space="preserve"> <trans-unit id="You can share this address with your contacts to let them connect with **%@**." xml:space="preserve">
<source>You can share this address with your contacts to let them connect with **%@**.</source> <source>You can share this address with your contacts to let them connect with **%@**.</source>
<target>Megoszthatja ezt a hivatkozást ismerőseivel, hogy kapcsolatba léphessenek önnel a **%@** nevű profilján keresztül.</target> <target>Megoszthatja ezt az azonosítót az ismerőseivel, hogy kapcsolatba léphessenek önnel a **%@** nevű profilján keresztül.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="You can share your address as a link or QR code - anybody can connect to you." xml:space="preserve"> <trans-unit id="You can share your address as a link or QR code - anybody can connect to you." xml:space="preserve">
<source>You can share your address as a link or QR code - anybody can connect to you.</source> <source>You can share your address as a link or QR code - anybody can connect to you.</source>
<target>Megoszthatja azonosítóját hivatkozásként vagy QR-kódként így bárki csatlakozhat önhöz.</target> <target>Megoszthatja azonosítóját hivatkozásként vagy QR-kódként így bárki kapcsolódhat önhöz.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="You can start chat via app Settings / Database or by restarting the app" xml:space="preserve"> <trans-unit id="You can start chat via app Settings / Database or by restarting the app" xml:space="preserve">
@@ -6481,13 +6484,13 @@ Csatlakozási kérés megismétlése?</target>
</trans-unit> </trans-unit>
<trans-unit id="You have already requested connection via this address!" xml:space="preserve"> <trans-unit id="You have already requested connection via this address!" xml:space="preserve">
<source>You have already requested connection via this address!</source> <source>You have already requested connection via this address!</source>
<target>Már kért egy csatlakozást ezen az azonosítón keresztül!</target> <target>Már kért egy kapcsolódási kérelmet ezen az azonosítón keresztül!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="You have already requested connection!&#10;Repeat connection request?" xml:space="preserve"> <trans-unit id="You have already requested connection!&#10;Repeat connection request?" xml:space="preserve">
<source>You have already requested connection! <source>You have already requested connection!
Repeat connection request?</source> Repeat connection request?</source>
<target>Már kérelmezte a csatlakozást! <target>Már kért egy kapcsolódási kérelmet!
Kapcsolódási kérés megismétlése?</target> Kapcsolódási kérés megismétlése?</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
@@ -6538,22 +6541,22 @@ Kapcsolódási kérés megismétlése?</target>
</trans-unit> </trans-unit>
<trans-unit id="You will be connected to group when the group host's device is online, please wait or check later!" xml:space="preserve"> <trans-unit id="You will be connected to group when the group host's device is online, please wait or check later!" xml:space="preserve">
<source>You will be connected to group when the group host's device is online, please wait or check later!</source> <source>You will be connected to group when the group host's device is online, please wait or check later!</source>
<target>Akkor tud csatlakozni a csoporthoz, amikor a csoport tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később!</target> <target>Akkor lesz kapcsolódva a csoporthoz, amikor a csoport tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="You will be connected when group link host's device is online, please wait or check later!" xml:space="preserve"> <trans-unit id="You will be connected when group link host's device is online, please wait or check later!" xml:space="preserve">
<source>You will be connected when group link host's device is online, please wait or check later!</source> <source>You will be connected when group link host's device is online, please wait or check later!</source>
<target>Akkor lesz csatlakoztatva, amikor a csoportos hivatkozás tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később!</target> <target>Akkor lesz kapcsolódva, amikor a csoportos hivatkozás tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="You will be connected when your connection request is accepted, please wait or check later!" xml:space="preserve"> <trans-unit id="You will be connected when your connection request is accepted, please wait or check later!" xml:space="preserve">
<source>You will be connected when your connection request is accepted, please wait or check later!</source> <source>You will be connected when your connection request is accepted, please wait or check later!</source>
<target>Akkor lesz csatlakoztatva, ha a csatlakozási kérelme elfogadásra került, várjon, vagy ellenőrizze később!</target> <target>Akkor lesz kapcsolódva, ha a kapcsolódási kérelme elfogadásra kerül, várjon, vagy ellenőrizze később!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="You will be connected when your contact's device is online, please wait or check later!" xml:space="preserve"> <trans-unit id="You will be connected when your contact's device is online, please wait or check later!" xml:space="preserve">
<source>You will be connected when your contact's device is online, please wait or check later!</source> <source>You will be connected when your contact's device is online, please wait or check later!</source>
<target>Akkor csatlakozik, amikor az ismerős eszköze online lesz, várjon, vagy ellenőrizze később!</target> <target>Akkor le kapcsolódva, amikor az ismerőse eszköze online lesz, várjon, vagy ellenőrizze később!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="You will be required to authenticate when you start or resume the app after 30 seconds in background." xml:space="preserve"> <trans-unit id="You will be required to authenticate when you start or resume the app after 30 seconds in background." xml:space="preserve">
@@ -6563,7 +6566,7 @@ Kapcsolódási kérés megismétlése?</target>
</trans-unit> </trans-unit>
<trans-unit id="You will connect to all group members." xml:space="preserve"> <trans-unit id="You will connect to all group members." xml:space="preserve">
<source>You will connect to all group members.</source> <source>You will connect to all group members.</source>
<target>Csatlakozni fog a csoport összes tagjához.</target> <target>Kapcsolódni fog a csoport összes tagjához.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="You will still receive calls and notifications from muted profiles when they are active." xml:space="preserve"> <trans-unit id="You will still receive calls and notifications from muted profiles when they are active." xml:space="preserve">
@@ -6578,7 +6581,7 @@ Kapcsolódási kérés megismétlése?</target>
</trans-unit> </trans-unit>
<trans-unit id="You won't lose your contacts if you later delete your address." xml:space="preserve"> <trans-unit id="You won't lose your contacts if you later delete your address." xml:space="preserve">
<source>You won't lose your contacts if you later delete your address.</source> <source>You won't lose your contacts if you later delete your address.</source>
<target>Nem veszíti el ismerőseit, ha később törli az azonosítóját.</target> <target>Nem veszíti el az ismerőseit, ha később törli az azonosítóját.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile" xml:space="preserve"> <trans-unit id="You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile" xml:space="preserve">
@@ -6655,7 +6658,7 @@ Megszakíthatja ezt a kapcsolatfelvételt és törölheti az ismerőst (ezt kés
</trans-unit> </trans-unit>
<trans-unit id="Your contacts will remain connected." xml:space="preserve"> <trans-unit id="Your contacts will remain connected." xml:space="preserve">
<source>Your contacts will remain connected.</source> <source>Your contacts will remain connected.</source>
<target>Az ismerősök továbbra is csatlakoztatva maradnak.</target> <target>Az ismerősei továbbra is kapcsolódva maradnak.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Your current chat database will be DELETED and REPLACED with the imported one." xml:space="preserve"> <trans-unit id="Your current chat database will be DELETED and REPLACED with the imported one." xml:space="preserve">
@@ -6857,7 +6860,7 @@ A SimpleX kiszolgálók nem látjhatják profilját.</target>
</trans-unit> </trans-unit>
<trans-unit id="changed role of %@ to %@" xml:space="preserve"> <trans-unit id="changed role of %@ to %@" xml:space="preserve">
<source>changed role of %1$@ to %2$@</source> <source>changed role of %1$@ to %2$@</source>
<target>%1$@ szerepköre megváltozott erre: %2$@</target> <target>%1$@ szerepkörét megváltoztatta erre: %2$@</target>
<note>rcv group event chat item</note> <note>rcv group event chat item</note>
</trans-unit> </trans-unit>
<trans-unit id="changed your role to %@" xml:space="preserve"> <trans-unit id="changed your role to %@" xml:space="preserve">
@@ -6887,7 +6890,7 @@ A SimpleX kiszolgálók nem látjhatják profilját.</target>
</trans-unit> </trans-unit>
<trans-unit id="connect to SimpleX Chat developers." xml:space="preserve"> <trans-unit id="connect to SimpleX Chat developers." xml:space="preserve">
<source>connect to SimpleX Chat developers.</source> <source>connect to SimpleX Chat developers.</source>
<target>Csatlakozás a SimpleX Chat fejlesztőkhöz.</target> <target>Kapcsolódás a SimpleX Chat fejlesztőkhöz.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="connected" xml:space="preserve"> <trans-unit id="connected" xml:space="preserve">
@@ -6922,7 +6925,7 @@ A SimpleX kiszolgálók nem látjhatják profilját.</target>
</trans-unit> </trans-unit>
<trans-unit id="connecting (introduction invitation)" xml:space="preserve"> <trans-unit id="connecting (introduction invitation)" xml:space="preserve">
<source>connecting (introduction invitation)</source> <source>connecting (introduction invitation)</source>
<target>csatlakozás (bemutatkozás meghívás)</target> <target>kapcsolódás (bemutatkozó meghívó)</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="connecting call" xml:space="preserve"> <trans-unit id="connecting call" xml:space="preserve">
@@ -6947,7 +6950,7 @@ A SimpleX kiszolgálók nem látjhatják profilját.</target>
</trans-unit> </trans-unit>
<trans-unit id="contact %@ changed to %@" xml:space="preserve"> <trans-unit id="contact %@ changed to %@" xml:space="preserve">
<source>contact %1$@ changed to %2$@</source> <source>contact %1$@ changed to %2$@</source>
<target>%1$@ ismerősének neve megváltozott erre: %2$@</target> <target>%1$@ megváltoztatta a nevét erre: %2$@</target>
<note>profile update event chat item</note> <note>profile update event chat item</note>
</trans-unit> </trans-unit>
<trans-unit id="contact has e2e encryption" xml:space="preserve"> <trans-unit id="contact has e2e encryption" xml:space="preserve">
@@ -7207,7 +7210,7 @@ A SimpleX kiszolgálók nem látjhatják profilját.</target>
</trans-unit> </trans-unit>
<trans-unit id="join as %@" xml:space="preserve"> <trans-unit id="join as %@" xml:space="preserve">
<source>join as %@</source> <source>join as %@</source>
<target>csatlakozás mint %@</target> <target>csatlakozás mint: %@</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="left" xml:space="preserve"> <trans-unit id="left" xml:space="preserve">
@@ -7227,12 +7230,12 @@ A SimpleX kiszolgálók nem látjhatják profilját.</target>
</trans-unit> </trans-unit>
<trans-unit id="member %@ changed to %@" xml:space="preserve"> <trans-unit id="member %@ changed to %@" xml:space="preserve">
<source>member %1$@ changed to %2$@</source> <source>member %1$@ changed to %2$@</source>
<target>%1$@ tag megváltoztatta a nevét erre: %2$@</target> <target>%1$@ megváltoztatta a nevét erre: %2$@</target>
<note>profile update event chat item</note> <note>profile update event chat item</note>
</trans-unit> </trans-unit>
<trans-unit id="member connected" xml:space="preserve"> <trans-unit id="member connected" xml:space="preserve">
<source>connected</source> <source>connected</source>
<target>kapcsolódva</target> <target>kapcsolódott</target>
<note>rcv group event chat item</note> <note>rcv group event chat item</note>
</trans-unit> </trans-unit>
<trans-unit id="message received" xml:space="preserve"> <trans-unit id="message received" xml:space="preserve">
@@ -7364,7 +7367,7 @@ A SimpleX kiszolgálók nem látjhatják profilját.</target>
</trans-unit> </trans-unit>
<trans-unit id="removed contact address" xml:space="preserve"> <trans-unit id="removed contact address" xml:space="preserve">
<source>removed contact address</source> <source>removed contact address</source>
<target>törölt csatlakozási cím</target> <target>törölt kapcsolattartási azonosító</target>
<note>profile update event chat item</note> <note>profile update event chat item</note>
</trans-unit> </trans-unit>
<trans-unit id="removed profile picture" xml:space="preserve"> <trans-unit id="removed profile picture" xml:space="preserve">
@@ -7404,7 +7407,7 @@ A SimpleX kiszolgálók nem látjhatják profilját.</target>
</trans-unit> </trans-unit>
<trans-unit id="security code changed" xml:space="preserve"> <trans-unit id="security code changed" xml:space="preserve">
<source>security code changed</source> <source>security code changed</source>
<target>biztonsági kód megváltozott</target> <target>a biztonsági kód megváltozott</target>
<note>chat item text</note> <note>chat item text</note>
</trans-unit> </trans-unit>
<trans-unit id="send direct message" xml:space="preserve"> <trans-unit id="send direct message" xml:space="preserve">
@@ -7414,7 +7417,7 @@ A SimpleX kiszolgálók nem látjhatják profilját.</target>
</trans-unit> </trans-unit>
<trans-unit id="set new contact address" xml:space="preserve"> <trans-unit id="set new contact address" xml:space="preserve">
<source>set new contact address</source> <source>set new contact address</source>
<target>új kapcsolattartási cím beállítása</target> <target>új kapcsolattartási azonosító beállítása</target>
<note>profile update event chat item</note> <note>profile update event chat item</note>
</trans-unit> </trans-unit>
<trans-unit id="set new profile picture" xml:space="preserve"> <trans-unit id="set new profile picture" xml:space="preserve">
@@ -7479,7 +7482,7 @@ A SimpleX kiszolgálók nem látjhatják profilját.</target>
</trans-unit> </trans-unit>
<trans-unit id="via contact address link" xml:space="preserve"> <trans-unit id="via contact address link" xml:space="preserve">
<source>via contact address link</source> <source>via contact address link</source>
<target>ismerős azonosítójának hivatkozásán keresztül</target> <target>kapcsolattartási azonosító-hivatkozáson keresztül</target>
<note>chat list item description</note> <note>chat list item description</note>
</trans-unit> </trans-unit>
<trans-unit id="via group link" xml:space="preserve"> <trans-unit id="via group link" xml:space="preserve">
@@ -7564,7 +7567,7 @@ A SimpleX kiszolgálók nem látjhatják profilját.</target>
</trans-unit> </trans-unit>
<trans-unit id="you changed role of %@ to %@" xml:space="preserve"> <trans-unit id="you changed role of %@ to %@" xml:space="preserve">
<source>you changed role of %1$@ to %2$@</source> <source>you changed role of %1$@ to %2$@</source>
<target>megváltoztatta %1$@ szerepkörét erre: %@</target> <target>%1$@ szerepkörét megváltoztatta erre: %@</target>
<note>snd group event chat item</note> <note>snd group event chat item</note>
</trans-unit> </trans-unit>
<trans-unit id="you left" xml:space="preserve"> <trans-unit id="you left" xml:space="preserve">
@@ -7616,7 +7619,7 @@ A SimpleX kiszolgálók nem látjhatják profilját.</target>
</trans-unit> </trans-unit>
<trans-unit id="NSCameraUsageDescription" xml:space="preserve"> <trans-unit id="NSCameraUsageDescription" xml:space="preserve">
<source>SimpleX needs camera access to scan QR codes to connect to other users and for video calls.</source> <source>SimpleX needs camera access to scan QR codes to connect to other users and for video calls.</source>
<target>A SimpleX-nek kamera-hozzáférésre van szüksége a QR-kódok beolvasásához, hogy csatlakozhasson más felhasználókhoz és videohívásokhoz.</target> <target>A SimpleX-nek kamera-hozzáférésre van szüksége a QR-kódok beolvasásához, hogy kapcsolódhasson más felhasználókhoz és videohívásokhoz.</target>
<note>Privacy - Camera Usage Description</note> <note>Privacy - Camera Usage Description</note>
</trans-unit> </trans-unit>
<trans-unit id="NSFaceIDUsageDescription" xml:space="preserve"> <trans-unit id="NSFaceIDUsageDescription" xml:space="preserve">
@@ -2805,6 +2805,7 @@ Non è reversibile!</target>
</trans-unit> </trans-unit>
<trans-unit id="Forward and save messages" xml:space="preserve"> <trans-unit id="Forward and save messages" xml:space="preserve">
<source>Forward and save messages</source> <source>Forward and save messages</source>
<target>Inoltra e salva i messaggi</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Forwarded" xml:space="preserve"> <trans-unit id="Forwarded" xml:space="preserve">
@@ -3174,6 +3175,7 @@ Non è reversibile!</target>
</trans-unit> </trans-unit>
<trans-unit id="In-call sounds" xml:space="preserve"> <trans-unit id="In-call sounds" xml:space="preserve">
<source>In-call sounds</source> <source>In-call sounds</source>
<target>Suoni nelle chiamate</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Incognito" xml:space="preserve"> <trans-unit id="Incognito" xml:space="preserve">
@@ -3653,6 +3655,7 @@ Questo è il tuo link per il gruppo %@!</target>
</trans-unit> </trans-unit>
<trans-unit id="Message source remains private." xml:space="preserve"> <trans-unit id="Message source remains private." xml:space="preserve">
<source>Message source remains private.</source> <source>Message source remains private.</source>
<target>La fonte del messaggio resta privata.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Message text" xml:space="preserve"> <trans-unit id="Message text" xml:space="preserve">
@@ -3772,6 +3775,7 @@ Questo è il tuo link per il gruppo %@!</target>
</trans-unit> </trans-unit>
<trans-unit id="More reliable network connection." xml:space="preserve"> <trans-unit id="More reliable network connection." xml:space="preserve">
<source>More reliable network connection.</source> <source>More reliable network connection.</source>
<target>Connessione di rete più affidabile.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Most likely this connection is deleted." xml:space="preserve"> <trans-unit id="Most likely this connection is deleted." xml:space="preserve">
@@ -3816,6 +3820,7 @@ Questo è il tuo link per il gruppo %@!</target>
</trans-unit> </trans-unit>
<trans-unit id="Network management" xml:space="preserve"> <trans-unit id="Network management" xml:space="preserve">
<source>Network management</source> <source>Network management</source>
<target>Gestione della rete</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Network settings" xml:space="preserve"> <trans-unit id="Network settings" xml:space="preserve">
@@ -4371,6 +4376,7 @@ Errore: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Profile images" xml:space="preserve"> <trans-unit id="Profile images" xml:space="preserve">
<source>Profile images</source> <source>Profile images</source>
<target>Immagini del profilo</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Profile name" xml:space="preserve"> <trans-unit id="Profile name" xml:space="preserve">
@@ -5195,6 +5201,7 @@ Errore: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Shape profile images" xml:space="preserve"> <trans-unit id="Shape profile images" xml:space="preserve">
<source>Shape profile images</source> <source>Shape profile images</source>
<target>Forma delle immagini del profilo</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Share" xml:space="preserve"> <trans-unit id="Share" xml:space="preserve">
@@ -5364,6 +5371,7 @@ Errore: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Square, circle, or anything in between." xml:space="preserve"> <trans-unit id="Square, circle, or anything in between." xml:space="preserve">
<source>Square, circle, or anything in between.</source> <source>Square, circle, or anything in between.</source>
<target>Quadrata, circolare o qualsiasi forma tra le due.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Start chat" xml:space="preserve"> <trans-unit id="Start chat" xml:space="preserve">
@@ -6244,6 +6252,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e
</trans-unit> </trans-unit>
<trans-unit id="When connecting audio and video calls." xml:space="preserve"> <trans-unit id="When connecting audio and video calls." xml:space="preserve">
<source>When connecting audio and video calls.</source> <source>When connecting audio and video calls.</source>
<target>Quando si connettono le chiamate audio e video.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="When people request to connect, you can accept or reject it." xml:space="preserve"> <trans-unit id="When people request to connect, you can accept or reject it." xml:space="preserve">
@@ -6263,6 +6272,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e
</trans-unit> </trans-unit>
<trans-unit id="Will be enabled in direct chats!" xml:space="preserve"> <trans-unit id="Will be enabled in direct chats!" xml:space="preserve">
<source>Will be enabled in direct chats!</source> <source>Will be enabled in direct chats!</source>
<target>Viene attivata nelle chat dirette!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Wired ethernet" xml:space="preserve"> <trans-unit id="Wired ethernet" xml:space="preserve">
@@ -379,12 +379,12 @@
</trans-unit> </trans-unit>
<trans-unit id="**e2e encrypted** audio call" xml:space="preserve"> <trans-unit id="**e2e encrypted** audio call" xml:space="preserve">
<source>**e2e encrypted** audio call</source> <source>**e2e encrypted** audio call</source>
<target>**e2e 暗号化**音声通話</target> <target>**エンドツーエンド暗号化済み**音声通話</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="**e2e encrypted** video call" xml:space="preserve"> <trans-unit id="**e2e encrypted** video call" xml:space="preserve">
<source>**e2e encrypted** video call</source> <source>**e2e encrypted** video call</source>
<target>**e2e暗号化**ビデオ通話</target> <target>**エンドツーエンド暗号化済み**の テレビ電話 通話</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="*bold*" xml:space="preserve"> <trans-unit id="*bold*" xml:space="preserve">
@@ -434,6 +434,7 @@
</trans-unit> </trans-unit>
<trans-unit id="0 sec" xml:space="preserve"> <trans-unit id="0 sec" xml:space="preserve">
<source>0 sec</source> <source>0 sec</source>
<target>0 秒</target>
<note>time to disappear</note> <note>time to disappear</note>
</trans-unit> </trans-unit>
<trans-unit id="0s" xml:space="preserve"> <trans-unit id="0s" xml:space="preserve">
@@ -684,6 +685,7 @@
</trans-unit> </trans-unit>
<trans-unit id="All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." xml:space="preserve"> <trans-unit id="All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." xml:space="preserve">
<source>All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays.</source> <source>All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays.</source>
<target>すべての連絡先、会話、ファイルは安全に暗号化され、設定されたXFTPリレーに分割でアップロードされます。</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Allow" xml:space="preserve"> <trans-unit id="Allow" xml:space="preserve">
@@ -733,6 +735,7 @@
</trans-unit> </trans-unit>
<trans-unit id="Allow to send SimpleX links." xml:space="preserve"> <trans-unit id="Allow to send SimpleX links." xml:space="preserve">
<source>Allow to send SimpleX links.</source> <source>Allow to send SimpleX links.</source>
<target>SimpleXリンクの送信を許可。</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Allow to send files and media." xml:space="preserve"> <trans-unit id="Allow to send files and media." xml:space="preserve">
@@ -787,10 +790,12 @@
</trans-unit> </trans-unit>
<trans-unit id="Already connecting!" xml:space="preserve"> <trans-unit id="Already connecting!" xml:space="preserve">
<source>Already connecting!</source> <source>Already connecting!</source>
<target>既に接続中です!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Already joining the group!" xml:space="preserve"> <trans-unit id="Already joining the group!" xml:space="preserve">
<source>Already joining the group!</source> <source>Already joining the group!</source>
<target>すでにグループに参加しています!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Always use relay" xml:space="preserve"> <trans-unit id="Always use relay" xml:space="preserve">
@@ -815,6 +820,7 @@
</trans-unit> </trans-unit>
<trans-unit id="App data migration" xml:space="preserve"> <trans-unit id="App data migration" xml:space="preserve">
<source>App data migration</source> <source>App data migration</source>
<target>アプリデータの移行</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="App encrypts new local files (except videos)." xml:space="preserve"> <trans-unit id="App encrypts new local files (except videos)." xml:space="preserve">
@@ -4376,6 +4376,7 @@ Fout: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Profile images" xml:space="preserve"> <trans-unit id="Profile images" xml:space="preserve">
<source>Profile images</source> <source>Profile images</source>
<target>Profiel afbeeldingen</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Profile name" xml:space="preserve"> <trans-unit id="Profile name" xml:space="preserve">
@@ -5200,6 +5201,7 @@ Fout: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Shape profile images" xml:space="preserve"> <trans-unit id="Shape profile images" xml:space="preserve">
<source>Shape profile images</source> <source>Shape profile images</source>
<target>Vorm profiel afbeeldingen</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Share" xml:space="preserve"> <trans-unit id="Share" xml:space="preserve">
@@ -5369,6 +5371,7 @@ Fout: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Square, circle, or anything in between." xml:space="preserve"> <trans-unit id="Square, circle, or anything in between." xml:space="preserve">
<source>Square, circle, or anything in between.</source> <source>Square, circle, or anything in between.</source>
<target>Vierkant, cirkel of iets daartussenin.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Start chat" xml:space="preserve"> <trans-unit id="Start chat" xml:space="preserve">
@@ -745,6 +745,7 @@
</trans-unit> </trans-unit>
<trans-unit id="Allow to send SimpleX links." xml:space="preserve"> <trans-unit id="Allow to send SimpleX links." xml:space="preserve">
<source>Allow to send SimpleX links.</source> <source>Allow to send SimpleX links.</source>
<target>Zezwól na wysyłanie linków SimpleX.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Allow to send files and media." xml:space="preserve"> <trans-unit id="Allow to send files and media." xml:space="preserve">
@@ -1089,6 +1090,7 @@
</trans-unit> </trans-unit>
<trans-unit id="Cellular" xml:space="preserve"> <trans-unit id="Cellular" xml:space="preserve">
<source>Cellular</source> <source>Cellular</source>
<target>Sieć komórkowa</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Change" xml:space="preserve"> <trans-unit id="Change" xml:space="preserve">
@@ -2088,6 +2090,7 @@ To nie może być cofnięte!</target>
</trans-unit> </trans-unit>
<trans-unit id="Download" xml:space="preserve"> <trans-unit id="Download" xml:space="preserve">
<source>Download</source> <source>Download</source>
<target>Pobierz</target>
<note>chat item action</note> <note>chat item action</note>
</trans-unit> </trans-unit>
<trans-unit id="Download failed" xml:space="preserve"> <trans-unit id="Download failed" xml:space="preserve">
@@ -2202,6 +2205,7 @@ To nie może być cofnięte!</target>
</trans-unit> </trans-unit>
<trans-unit id="Enabled for" xml:space="preserve"> <trans-unit id="Enabled for" xml:space="preserve">
<source>Enabled for</source> <source>Enabled for</source>
<target>Włączony dla</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Encrypt" xml:space="preserve"> <trans-unit id="Encrypt" xml:space="preserve">
@@ -2726,6 +2730,7 @@ To nie może być cofnięte!</target>
</trans-unit> </trans-unit>
<trans-unit id="Files and media not allowed" xml:space="preserve"> <trans-unit id="Files and media not allowed" xml:space="preserve">
<source>Files and media not allowed</source> <source>Files and media not allowed</source>
<target>Pliki i multimedia nie są dozwolone</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Files and media prohibited!" xml:space="preserve"> <trans-unit id="Files and media prohibited!" xml:space="preserve">
@@ -2795,18 +2800,22 @@ To nie może być cofnięte!</target>
</trans-unit> </trans-unit>
<trans-unit id="Forward" xml:space="preserve"> <trans-unit id="Forward" xml:space="preserve">
<source>Forward</source> <source>Forward</source>
<target>Przekaż dalej</target>
<note>chat item action</note> <note>chat item action</note>
</trans-unit> </trans-unit>
<trans-unit id="Forward and save messages" xml:space="preserve"> <trans-unit id="Forward and save messages" xml:space="preserve">
<source>Forward and save messages</source> <source>Forward and save messages</source>
<target>Przesyłaj dalej i zapisuj wiadomości</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Forwarded" xml:space="preserve"> <trans-unit id="Forwarded" xml:space="preserve">
<source>Forwarded</source> <source>Forwarded</source>
<target>Przekazane dalej</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Forwarded from" xml:space="preserve"> <trans-unit id="Forwarded from" xml:space="preserve">
<source>Forwarded from</source> <source>Forwarded from</source>
<target>Przekazane dalej od</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Found desktop" xml:space="preserve"> <trans-unit id="Found desktop" xml:space="preserve">
@@ -2921,6 +2930,7 @@ To nie może być cofnięte!</target>
</trans-unit> </trans-unit>
<trans-unit id="Group members can send SimpleX links." xml:space="preserve"> <trans-unit id="Group members can send SimpleX links." xml:space="preserve">
<source>Group members can send SimpleX links.</source> <source>Group members can send SimpleX links.</source>
<target>Członkowie grupy mogą wysyłać linki SimpleX.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Group members can send direct messages." xml:space="preserve"> <trans-unit id="Group members can send direct messages." xml:space="preserve">
@@ -3165,6 +3175,7 @@ To nie może być cofnięte!</target>
</trans-unit> </trans-unit>
<trans-unit id="In-call sounds" xml:space="preserve"> <trans-unit id="In-call sounds" xml:space="preserve">
<source>In-call sounds</source> <source>In-call sounds</source>
<target>Dźwięki w rozmowie</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Incognito" xml:space="preserve"> <trans-unit id="Incognito" xml:space="preserve">
@@ -3644,6 +3655,7 @@ To jest twój link do grupy %@!</target>
</trans-unit> </trans-unit>
<trans-unit id="Message source remains private." xml:space="preserve"> <trans-unit id="Message source remains private." xml:space="preserve">
<source>Message source remains private.</source> <source>Message source remains private.</source>
<target>Źródło wiadomości pozostaje prywatne.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Message text" xml:space="preserve"> <trans-unit id="Message text" xml:space="preserve">
@@ -3763,6 +3775,7 @@ To jest twój link do grupy %@!</target>
</trans-unit> </trans-unit>
<trans-unit id="More reliable network connection." xml:space="preserve"> <trans-unit id="More reliable network connection." xml:space="preserve">
<source>More reliable network connection.</source> <source>More reliable network connection.</source>
<target>Bardziej niezawodne połączenia sieciowe.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Most likely this connection is deleted." xml:space="preserve"> <trans-unit id="Most likely this connection is deleted." xml:space="preserve">
@@ -3802,10 +3815,12 @@ To jest twój link do grupy %@!</target>
</trans-unit> </trans-unit>
<trans-unit id="Network connection" xml:space="preserve"> <trans-unit id="Network connection" xml:space="preserve">
<source>Network connection</source> <source>Network connection</source>
<target>Połączenie z siecią</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Network management" xml:space="preserve"> <trans-unit id="Network management" xml:space="preserve">
<source>Network management</source> <source>Network management</source>
<target>Zarządzenie sieciowe</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Network settings" xml:space="preserve"> <trans-unit id="Network settings" xml:space="preserve">
@@ -3920,6 +3935,7 @@ To jest twój link do grupy %@!</target>
</trans-unit> </trans-unit>
<trans-unit id="No network connection" xml:space="preserve"> <trans-unit id="No network connection" xml:space="preserve">
<source>No network connection</source> <source>No network connection</source>
<target>Brak połączenia z siecią</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="No permission to record voice message" xml:space="preserve"> <trans-unit id="No permission to record voice message" xml:space="preserve">
@@ -4138,6 +4154,7 @@ To jest twój link do grupy %@!</target>
</trans-unit> </trans-unit>
<trans-unit id="Other" xml:space="preserve"> <trans-unit id="Other" xml:space="preserve">
<source>Other</source> <source>Other</source>
<target>Inne</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="PING count" xml:space="preserve"> <trans-unit id="PING count" xml:space="preserve">
@@ -4359,6 +4376,7 @@ Błąd: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Profile images" xml:space="preserve"> <trans-unit id="Profile images" xml:space="preserve">
<source>Profile images</source> <source>Profile images</source>
<target>Zdjęcia profilowe</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Profile name" xml:space="preserve"> <trans-unit id="Profile name" xml:space="preserve">
@@ -4403,6 +4421,7 @@ Błąd: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Prohibit sending SimpleX links." xml:space="preserve"> <trans-unit id="Prohibit sending SimpleX links." xml:space="preserve">
<source>Prohibit sending SimpleX links.</source> <source>Prohibit sending SimpleX links.</source>
<target>Zabroń wysyłania linków SimpleX.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Prohibit sending direct messages to members." xml:space="preserve"> <trans-unit id="Prohibit sending direct messages to members." xml:space="preserve">
@@ -4537,6 +4556,7 @@ Błąd: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Receiving concurrency" xml:space="preserve"> <trans-unit id="Receiving concurrency" xml:space="preserve">
<source>Receiving concurrency</source> <source>Receiving concurrency</source>
<target>Konkurencyjne odbieranie</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Receiving file will be stopped." xml:space="preserve"> <trans-unit id="Receiving file will be stopped." xml:space="preserve">
@@ -4556,6 +4576,7 @@ Błąd: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Recipient(s) can't see who this message is from." xml:space="preserve"> <trans-unit id="Recipient(s) can't see who this message is from." xml:space="preserve">
<source>Recipient(s) can't see who this message is from.</source> <source>Recipient(s) can't see who this message is from.</source>
<target>Odbiorca/y nie mogą zobaczyć od kogo jest ta wiadomość.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Recipients see updates as you type them." xml:space="preserve"> <trans-unit id="Recipients see updates as you type them." xml:space="preserve">
@@ -4860,6 +4881,7 @@ Błąd: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Saved" xml:space="preserve"> <trans-unit id="Saved" xml:space="preserve">
<source>Saved</source> <source>Saved</source>
<target>Zapisane</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Saved WebRTC ICE servers will be removed" xml:space="preserve"> <trans-unit id="Saved WebRTC ICE servers will be removed" xml:space="preserve">
@@ -4869,6 +4891,7 @@ Błąd: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Saved from" xml:space="preserve"> <trans-unit id="Saved from" xml:space="preserve">
<source>Saved from</source> <source>Saved from</source>
<target>Zapisane od</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Saved message" xml:space="preserve"> <trans-unit id="Saved message" xml:space="preserve">
@@ -5178,6 +5201,7 @@ Błąd: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Shape profile images" xml:space="preserve"> <trans-unit id="Shape profile images" xml:space="preserve">
<source>Shape profile images</source> <source>Shape profile images</source>
<target>Kształtuj obrazy profilowe</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Share" xml:space="preserve"> <trans-unit id="Share" xml:space="preserve">
@@ -5302,10 +5326,12 @@ Błąd: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="SimpleX links are prohibited in this group." xml:space="preserve"> <trans-unit id="SimpleX links are prohibited in this group." xml:space="preserve">
<source>SimpleX links are prohibited in this group.</source> <source>SimpleX links are prohibited in this group.</source>
<target>Linki SimpleX są zablokowane na tej grupie.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="SimpleX links not allowed" xml:space="preserve"> <trans-unit id="SimpleX links not allowed" xml:space="preserve">
<source>SimpleX links not allowed</source> <source>SimpleX links not allowed</source>
<target>Linki SimpleX są niedozwolone</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="SimpleX one-time invitation" xml:space="preserve"> <trans-unit id="SimpleX one-time invitation" xml:space="preserve">
@@ -5345,6 +5371,7 @@ Błąd: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Square, circle, or anything in between." xml:space="preserve"> <trans-unit id="Square, circle, or anything in between." xml:space="preserve">
<source>Square, circle, or anything in between.</source> <source>Square, circle, or anything in between.</source>
<target>Kwadrat, okrąg lub cokolwiek pomiędzy.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Start chat" xml:space="preserve"> <trans-unit id="Start chat" xml:space="preserve">
@@ -6150,6 +6177,7 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc
</trans-unit> </trans-unit>
<trans-unit id="Voice messages not allowed" xml:space="preserve"> <trans-unit id="Voice messages not allowed" xml:space="preserve">
<source>Voice messages not allowed</source> <source>Voice messages not allowed</source>
<target>Wiadomości głosowe są niedozwolone</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Voice messages prohibited!" xml:space="preserve"> <trans-unit id="Voice messages prohibited!" xml:space="preserve">
@@ -6224,6 +6252,7 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc
</trans-unit> </trans-unit>
<trans-unit id="When connecting audio and video calls." xml:space="preserve"> <trans-unit id="When connecting audio and video calls." xml:space="preserve">
<source>When connecting audio and video calls.</source> <source>When connecting audio and video calls.</source>
<target>Podczas łączenia połączeń audio i wideo.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="When people request to connect, you can accept or reject it." xml:space="preserve"> <trans-unit id="When people request to connect, you can accept or reject it." xml:space="preserve">
@@ -6238,14 +6267,17 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc
</trans-unit> </trans-unit>
<trans-unit id="WiFi" xml:space="preserve"> <trans-unit id="WiFi" xml:space="preserve">
<source>WiFi</source> <source>WiFi</source>
<target>WiFi</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Will be enabled in direct chats!" xml:space="preserve"> <trans-unit id="Will be enabled in direct chats!" xml:space="preserve">
<source>Will be enabled in direct chats!</source> <source>Will be enabled in direct chats!</source>
<target>Zostanie włączone w czatach bezpośrednich!</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Wired ethernet" xml:space="preserve"> <trans-unit id="Wired ethernet" xml:space="preserve">
<source>Wired ethernet</source> <source>Wired ethernet</source>
<target>Połączenie ethernet (po kablu)</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="With encrypted files and media." xml:space="preserve"> <trans-unit id="With encrypted files and media." xml:space="preserve">
@@ -6733,6 +6765,7 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu.</target>
</trans-unit> </trans-unit>
<trans-unit id="admins" xml:space="preserve"> <trans-unit id="admins" xml:space="preserve">
<source>admins</source> <source>admins</source>
<target>administratorzy</target>
<note>feature role</note> <note>feature role</note>
</trans-unit> </trans-unit>
<trans-unit id="agreeing encryption for %@…" xml:space="preserve"> <trans-unit id="agreeing encryption for %@…" xml:space="preserve">
@@ -6747,6 +6780,7 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu.</target>
</trans-unit> </trans-unit>
<trans-unit id="all members" xml:space="preserve"> <trans-unit id="all members" xml:space="preserve">
<source>all members</source> <source>all members</source>
<target>wszyscy członkowie</target>
<note>feature role</note> <note>feature role</note>
</trans-unit> </trans-unit>
<trans-unit id="always" xml:space="preserve"> <trans-unit id="always" xml:space="preserve">
@@ -7081,6 +7115,7 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu.</target>
</trans-unit> </trans-unit>
<trans-unit id="forwarded" xml:space="preserve"> <trans-unit id="forwarded" xml:space="preserve">
<source>forwarded</source> <source>forwarded</source>
<target>przekazane dalej</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="group deleted" xml:space="preserve"> <trans-unit id="group deleted" xml:space="preserve">
@@ -7292,6 +7327,7 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu.</target>
</trans-unit> </trans-unit>
<trans-unit id="owners" xml:space="preserve"> <trans-unit id="owners" xml:space="preserve">
<source>owners</source> <source>owners</source>
<target>właściciele</target>
<note>feature role</note> <note>feature role</note>
</trans-unit> </trans-unit>
<trans-unit id="peer-to-peer" xml:space="preserve"> <trans-unit id="peer-to-peer" xml:space="preserve">
@@ -7346,10 +7382,12 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu.</target>
</trans-unit> </trans-unit>
<trans-unit id="saved" xml:space="preserve"> <trans-unit id="saved" xml:space="preserve">
<source>saved</source> <source>saved</source>
<target>zapisane</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="saved from %@" xml:space="preserve"> <trans-unit id="saved from %@" xml:space="preserve">
<source>saved from %@</source> <source>saved from %@</source>
<target>zapisane od %@</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="sec" xml:space="preserve"> <trans-unit id="sec" xml:space="preserve">
@@ -7494,6 +7532,7 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu.</target>
</trans-unit> </trans-unit>
<trans-unit id="you" xml:space="preserve"> <trans-unit id="you" xml:space="preserve">
<source>you</source> <source>you</source>
<target>Ty</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="you are invited to group" xml:space="preserve"> <trans-unit id="you are invited to group" xml:space="preserve">
@@ -4376,6 +4376,7 @@ Hata: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Profile images" xml:space="preserve"> <trans-unit id="Profile images" xml:space="preserve">
<source>Profile images</source> <source>Profile images</source>
<target>Profil resimleri</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Profile name" xml:space="preserve"> <trans-unit id="Profile name" xml:space="preserve">
@@ -4525,7 +4526,7 @@ Hata: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Receipts are disabled" xml:space="preserve"> <trans-unit id="Receipts are disabled" xml:space="preserve">
<source>Receipts are disabled</source> <source>Receipts are disabled</source>
<target>Makbuzlar devre dışı bırakıldı</target> <target>Gönderildi bilgisi devre dışı bırakıldı</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Received at" xml:space="preserve"> <trans-unit id="Received at" xml:space="preserve">
@@ -5065,7 +5066,7 @@ Hata: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Sending delivery receipts will be enabled for all contacts." xml:space="preserve"> <trans-unit id="Sending delivery receipts will be enabled for all contacts." xml:space="preserve">
<source>Sending delivery receipts will be enabled for all contacts.</source> <source>Sending delivery receipts will be enabled for all contacts.</source>
<target>Teslimat makbuzlarının gönderilmesi tüm kişiler için etkinleştirilecektir.</target> <target>Gönderildi bilgisi tüm kişiler için etkinleştirilecektir.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Sending file will be stopped." xml:space="preserve"> <trans-unit id="Sending file will be stopped." xml:space="preserve">
@@ -5200,6 +5201,7 @@ Hata: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Shape profile images" xml:space="preserve"> <trans-unit id="Shape profile images" xml:space="preserve">
<source>Shape profile images</source> <source>Shape profile images</source>
<target>Profil resimlerini şekillendir</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Share" xml:space="preserve"> <trans-unit id="Share" xml:space="preserve">
@@ -5369,6 +5371,7 @@ Hata: %@</target>
</trans-unit> </trans-unit>
<trans-unit id="Square, circle, or anything in between." xml:space="preserve"> <trans-unit id="Square, circle, or anything in between." xml:space="preserve">
<source>Square, circle, or anything in between.</source> <source>Square, circle, or anything in between.</source>
<target>Kare,daire, veya aralarında herhangi bir şey.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Start chat" xml:space="preserve"> <trans-unit id="Start chat" xml:space="preserve">
@@ -696,14 +696,16 @@ func apiGetNtfMessage(nonce: String, encNtfInfo: String) -> NtfMessages? {
} }
func apiReceiveFile(fileId: Int64, encrypted: Bool, inline: Bool? = nil) -> AChatItem? { func apiReceiveFile(fileId: Int64, encrypted: Bool, inline: Bool? = nil) -> AChatItem? {
let r = sendSimpleXCmd(.receiveFile(fileId: fileId, encrypted: encrypted, inline: inline)) let userApprovedRelays = !privacyAskToApproveRelaysGroupDefault.get()
let r = sendSimpleXCmd(.receiveFile(fileId: fileId, userApprovedRelays: userApprovedRelays, encrypted: encrypted, inline: inline))
if case let .rcvFileAccepted(_, chatItem) = r { return chatItem } if case let .rcvFileAccepted(_, chatItem) = r { return chatItem }
logger.error("receiveFile error: \(responseError(r))") logger.error("receiveFile error: \(responseError(r))")
return nil return nil
} }
func apiSetFileToReceive(fileId: Int64, encrypted: Bool) { func apiSetFileToReceive(fileId: Int64, encrypted: Bool) {
let r = sendSimpleXCmd(.setFileToReceive(fileId: fileId, encrypted: encrypted)) let userApprovedRelays = !privacyAskToApproveRelaysGroupDefault.get()
let r = sendSimpleXCmd(.setFileToReceive(fileId: fileId, userApprovedRelays: userApprovedRelays, encrypted: encrypted))
if case .cmdOk = r { return } if case .cmdOk = r { return }
logger.error("setFileToReceive error: \(responseError(r))") logger.error("setFileToReceive error: \(responseError(r))")
} }
+40 -32
View File
@@ -29,11 +29,6 @@
5C116CDC27AABE0400E66D01 /* ContactRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */; }; 5C116CDC27AABE0400E66D01 /* ContactRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */; };
5C13730B28156D2700F43030 /* ContactConnectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C13730A28156D2700F43030 /* ContactConnectionView.swift */; }; 5C13730B28156D2700F43030 /* ContactConnectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C13730A28156D2700F43030 /* ContactConnectionView.swift */; };
5C1A4C1E27A715B700EAD5AD /* ChatItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */; }; 5C1A4C1E27A715B700EAD5AD /* ChatItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */; };
5C2217B62BDA9B7000A8B0E7 /* libHSsimplex-chat-5.7.0.4-9w48ibH0KftLfWfQjuLznV-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C2217B12BDA9B7000A8B0E7 /* libHSsimplex-chat-5.7.0.4-9w48ibH0KftLfWfQjuLznV-ghc9.6.3.a */; };
5C2217B72BDA9B7000A8B0E7 /* libHSsimplex-chat-5.7.0.4-9w48ibH0KftLfWfQjuLznV.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C2217B22BDA9B7000A8B0E7 /* libHSsimplex-chat-5.7.0.4-9w48ibH0KftLfWfQjuLznV.a */; };
5C2217B82BDA9B7000A8B0E7 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C2217B32BDA9B7000A8B0E7 /* libgmp.a */; };
5C2217B92BDA9B7000A8B0E7 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C2217B42BDA9B7000A8B0E7 /* libgmpxx.a */; };
5C2217BA2BDA9B7000A8B0E7 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C2217B52BDA9B7000A8B0E7 /* libffi.a */; };
5C2E260727A2941F00F70299 /* SimpleXAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260627A2941F00F70299 /* SimpleXAPI.swift */; }; 5C2E260727A2941F00F70299 /* SimpleXAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260627A2941F00F70299 /* SimpleXAPI.swift */; };
5C2E260B27A30CFA00F70299 /* ChatListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260A27A30CFA00F70299 /* ChatListView.swift */; }; 5C2E260B27A30CFA00F70299 /* ChatListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260A27A30CFA00F70299 /* ChatListView.swift */; };
5C2E260F27A30FDC00F70299 /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260E27A30FDC00F70299 /* ChatView.swift */; }; 5C2E260F27A30FDC00F70299 /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260E27A30FDC00F70299 /* ChatView.swift */; };
@@ -144,6 +139,11 @@
5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEACCEC27DEA495000BD591 /* MsgContentView.swift */; }; 5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEACCEC27DEA495000BD591 /* MsgContentView.swift */; };
5CEBD7462A5C0A8F00665FE2 /* KeyboardPadding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */; }; 5CEBD7462A5C0A8F00665FE2 /* KeyboardPadding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */; };
5CEBD7482A5F115D00665FE2 /* SetDeliveryReceiptsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBD7472A5F115D00665FE2 /* SetDeliveryReceiptsView.swift */; }; 5CEBD7482A5F115D00665FE2 /* SetDeliveryReceiptsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBD7472A5F115D00665FE2 /* SetDeliveryReceiptsView.swift */; };
5CEE87942C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CEE878F2C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G-ghc9.6.3.a */; };
5CEE87952C024F4F00583B8A /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CEE87902C024F4F00583B8A /* libgmp.a */; };
5CEE87962C024F4F00583B8A /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CEE87912C024F4F00583B8A /* libgmpxx.a */; };
5CEE87972C024F4F00583B8A /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CEE87922C024F4F00583B8A /* libffi.a */; };
5CEE87982C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CEE87932C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G.a */; };
5CF937202B24DE8C00E1D781 /* SharedFileSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */; }; 5CF937202B24DE8C00E1D781 /* SharedFileSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */; };
5CF937232B2503D000E1D781 /* NSESubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF937212B25034A00E1D781 /* NSESubscriber.swift */; }; 5CF937232B2503D000E1D781 /* NSESubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF937212B25034A00E1D781 /* NSESubscriber.swift */; };
5CFA59C42860BC6200863A68 /* MigrateToAppGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */; }; 5CFA59C42860BC6200863A68 /* MigrateToAppGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */; };
@@ -188,6 +188,8 @@
8C69FE7D2B8C7D2700267E38 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C69FE7C2B8C7D2700267E38 /* AppSettings.swift */; }; 8C69FE7D2B8C7D2700267E38 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C69FE7C2B8C7D2700267E38 /* AppSettings.swift */; };
8C7D949A2B88952700B7B9E1 /* MigrateToDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C7D94992B88952700B7B9E1 /* MigrateToDevice.swift */; }; 8C7D949A2B88952700B7B9E1 /* MigrateToDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C7D94992B88952700B7B9E1 /* MigrateToDevice.swift */; };
8C7DF3202B7CDB0A00C886D0 /* MigrateFromDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C7DF31F2B7CDB0A00C886D0 /* MigrateFromDevice.swift */; }; 8C7DF3202B7CDB0A00C886D0 /* MigrateFromDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C7DF31F2B7CDB0A00C886D0 /* MigrateFromDevice.swift */; };
8C81482C2BD91CD4002CBEC3 /* AudioDevicePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C81482B2BD91CD4002CBEC3 /* AudioDevicePicker.swift */; };
8CC4ED902BD7B8530078AEE8 /* CallAudioDeviceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC4ED8F2BD7B8530078AEE8 /* CallAudioDeviceManager.swift */; };
8CC956EE2BC0041000412A11 /* NetworkObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC956ED2BC0041000412A11 /* NetworkObserver.swift */; }; 8CC956EE2BC0041000412A11 /* NetworkObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC956ED2BC0041000412A11 /* NetworkObserver.swift */; };
D7197A1829AE89660055C05A /* WebRTC in Frameworks */ = {isa = PBXBuildFile; productRef = D7197A1729AE89660055C05A /* WebRTC */; }; D7197A1829AE89660055C05A /* WebRTC in Frameworks */ = {isa = PBXBuildFile; productRef = D7197A1729AE89660055C05A /* WebRTC */; };
D72A9088294BD7A70047C86D /* NativeTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A9087294BD7A70047C86D /* NativeTextEditor.swift */; }; D72A9088294BD7A70047C86D /* NativeTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A9087294BD7A70047C86D /* NativeTextEditor.swift */; };
@@ -279,11 +281,6 @@
5C13730A28156D2700F43030 /* ContactConnectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactConnectionView.swift; sourceTree = "<group>"; }; 5C13730A28156D2700F43030 /* ContactConnectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactConnectionView.swift; sourceTree = "<group>"; };
5C13730C2815740A00F43030 /* DebugJSON.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = DebugJSON.playground; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 5C13730C2815740A00F43030 /* DebugJSON.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = DebugJSON.playground; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemView.swift; sourceTree = "<group>"; }; 5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemView.swift; sourceTree = "<group>"; };
5C2217B12BDA9B7000A8B0E7 /* libHSsimplex-chat-5.7.0.4-9w48ibH0KftLfWfQjuLznV-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.7.0.4-9w48ibH0KftLfWfQjuLznV-ghc9.6.3.a"; sourceTree = "<group>"; };
5C2217B22BDA9B7000A8B0E7 /* libHSsimplex-chat-5.7.0.4-9w48ibH0KftLfWfQjuLznV.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.7.0.4-9w48ibH0KftLfWfQjuLznV.a"; sourceTree = "<group>"; };
5C2217B32BDA9B7000A8B0E7 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
5C2217B42BDA9B7000A8B0E7 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
5C2217B52BDA9B7000A8B0E7 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
5C245F3C2B501E98001CC39F /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = "<group>"; }; 5C245F3C2B501E98001CC39F /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = "<group>"; };
5C245F3D2B501F13001CC39F /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = "tr.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = "<group>"; }; 5C245F3D2B501F13001CC39F /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = "tr.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = "<group>"; };
5C245F3E2B501F13001CC39F /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/InfoPlist.strings; sourceTree = "<group>"; }; 5C245F3E2B501F13001CC39F /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/InfoPlist.strings; sourceTree = "<group>"; };
@@ -438,6 +435,11 @@
5CEACCEC27DEA495000BD591 /* MsgContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MsgContentView.swift; sourceTree = "<group>"; }; 5CEACCEC27DEA495000BD591 /* MsgContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MsgContentView.swift; sourceTree = "<group>"; };
5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardPadding.swift; sourceTree = "<group>"; }; 5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardPadding.swift; sourceTree = "<group>"; };
5CEBD7472A5F115D00665FE2 /* SetDeliveryReceiptsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetDeliveryReceiptsView.swift; sourceTree = "<group>"; }; 5CEBD7472A5F115D00665FE2 /* SetDeliveryReceiptsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetDeliveryReceiptsView.swift; sourceTree = "<group>"; };
5CEE878F2C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G-ghc9.6.3.a"; sourceTree = "<group>"; };
5CEE87902C024F4F00583B8A /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
5CEE87912C024F4F00583B8A /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
5CEE87922C024F4F00583B8A /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
5CEE87932C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G.a"; sourceTree = "<group>"; };
5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedFileSubscriber.swift; sourceTree = "<group>"; }; 5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedFileSubscriber.swift; sourceTree = "<group>"; };
5CF937212B25034A00E1D781 /* NSESubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSESubscriber.swift; sourceTree = "<group>"; }; 5CF937212B25034A00E1D781 /* NSESubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSESubscriber.swift; sourceTree = "<group>"; };
5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateToAppGroupView.swift; sourceTree = "<group>"; }; 5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateToAppGroupView.swift; sourceTree = "<group>"; };
@@ -483,6 +485,8 @@
8C69FE7C2B8C7D2700267E38 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; }; 8C69FE7C2B8C7D2700267E38 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; };
8C7D94992B88952700B7B9E1 /* MigrateToDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateToDevice.swift; sourceTree = "<group>"; }; 8C7D94992B88952700B7B9E1 /* MigrateToDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateToDevice.swift; sourceTree = "<group>"; };
8C7DF31F2B7CDB0A00C886D0 /* MigrateFromDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateFromDevice.swift; sourceTree = "<group>"; }; 8C7DF31F2B7CDB0A00C886D0 /* MigrateFromDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateFromDevice.swift; sourceTree = "<group>"; };
8C81482B2BD91CD4002CBEC3 /* AudioDevicePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioDevicePicker.swift; sourceTree = "<group>"; };
8CC4ED8F2BD7B8530078AEE8 /* CallAudioDeviceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallAudioDeviceManager.swift; sourceTree = "<group>"; };
8CC956ED2BC0041000412A11 /* NetworkObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkObserver.swift; sourceTree = "<group>"; }; 8CC956ED2BC0041000412A11 /* NetworkObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkObserver.swift; sourceTree = "<group>"; };
D72A9087294BD7A70047C86D /* NativeTextEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeTextEditor.swift; sourceTree = "<group>"; }; D72A9087294BD7A70047C86D /* NativeTextEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeTextEditor.swift; sourceTree = "<group>"; };
D741547729AF89AF0022400A /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; }; D741547729AF89AF0022400A /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; };
@@ -525,13 +529,13 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
5C2217B92BDA9B7000A8B0E7 /* libgmpxx.a in Frameworks */, 5CEE87942C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G-ghc9.6.3.a in Frameworks */,
5CEE87972C024F4F00583B8A /* libffi.a in Frameworks */,
5CEE87962C024F4F00583B8A /* libgmpxx.a in Frameworks */,
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
5C2217B82BDA9B7000A8B0E7 /* libgmp.a in Frameworks */, 5CEE87952C024F4F00583B8A /* libgmp.a in Frameworks */,
5C2217B62BDA9B7000A8B0E7 /* libHSsimplex-chat-5.7.0.4-9w48ibH0KftLfWfQjuLznV-ghc9.6.3.a in Frameworks */,
5C2217BA2BDA9B7000A8B0E7 /* libffi.a in Frameworks */,
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
5C2217B72BDA9B7000A8B0E7 /* libHSsimplex-chat-5.7.0.4-9w48ibH0KftLfWfQjuLznV.a in Frameworks */, 5CEE87982C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G.a in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -549,6 +553,8 @@
5C55A922283CEDE600C4E99E /* SoundPlayer.swift */, 5C55A922283CEDE600C4E99E /* SoundPlayer.swift */,
18415323A4082FC92887F906 /* WebRTCClient.swift */, 18415323A4082FC92887F906 /* WebRTCClient.swift */,
18415B08031E8FB0F7FC27F9 /* CallViewRenderers.swift */, 18415B08031E8FB0F7FC27F9 /* CallViewRenderers.swift */,
8CC4ED8F2BD7B8530078AEE8 /* CallAudioDeviceManager.swift */,
8C81482B2BD91CD4002CBEC3 /* AudioDevicePicker.swift */,
); );
path = Call; path = Call;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -595,11 +601,11 @@
5C764E5C279C70B7000C6508 /* Libraries */ = { 5C764E5C279C70B7000C6508 /* Libraries */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
5C2217B52BDA9B7000A8B0E7 /* libffi.a */, 5CEE87922C024F4F00583B8A /* libffi.a */,
5C2217B32BDA9B7000A8B0E7 /* libgmp.a */, 5CEE87902C024F4F00583B8A /* libgmp.a */,
5C2217B42BDA9B7000A8B0E7 /* libgmpxx.a */, 5CEE87912C024F4F00583B8A /* libgmpxx.a */,
5C2217B12BDA9B7000A8B0E7 /* libHSsimplex-chat-5.7.0.4-9w48ibH0KftLfWfQjuLznV-ghc9.6.3.a */, 5CEE878F2C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G-ghc9.6.3.a */,
5C2217B22BDA9B7000A8B0E7 /* libHSsimplex-chat-5.7.0.4-9w48ibH0KftLfWfQjuLznV.a */, 5CEE87932C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G.a */,
); );
path = Libraries; path = Libraries;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1237,6 +1243,7 @@
5CB346E52868AA7F001FD2EF /* SuspendChat.swift in Sources */, 5CB346E52868AA7F001FD2EF /* SuspendChat.swift in Sources */,
5C9C2DA52894777E00CC63B1 /* GroupProfileView.swift in Sources */, 5C9C2DA52894777E00CC63B1 /* GroupProfileView.swift in Sources */,
5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */, 5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */,
8C81482C2BD91CD4002CBEC3 /* AudioDevicePicker.swift in Sources */,
5CCB939C297EFCB100399E78 /* NavStackCompat.swift in Sources */, 5CCB939C297EFCB100399E78 /* NavStackCompat.swift in Sources */,
5C764E89279CBCB3000C6508 /* ChatModel.swift in Sources */, 5C764E89279CBCB3000C6508 /* ChatModel.swift in Sources */,
5C971E1D27AEBEF600C8A3CE /* ChatInfoView.swift in Sources */, 5C971E1D27AEBEF600C8A3CE /* ChatInfoView.swift in Sources */,
@@ -1266,6 +1273,7 @@
18415B0585EB5A9A0A7CA8CD /* PressedButtonStyle.swift in Sources */, 18415B0585EB5A9A0A7CA8CD /* PressedButtonStyle.swift in Sources */,
1841560FD1CD447955474C1D /* UserProfilesView.swift in Sources */, 1841560FD1CD447955474C1D /* UserProfilesView.swift in Sources */,
64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */, 64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */,
8CC4ED902BD7B8530078AEE8 /* CallAudioDeviceManager.swift in Sources */,
18415C6C56DBCEC2CBBD2F11 /* WebRTCClient.swift in Sources */, 18415C6C56DBCEC2CBBD2F11 /* WebRTCClient.swift in Sources */,
184152CEF68D2336FC2EBCB0 /* CallViewRenderers.swift in Sources */, 184152CEF68D2336FC2EBCB0 /* CallViewRenderers.swift in Sources */,
5CB634AD29E46CF70066AD6B /* LocalAuthView.swift in Sources */, 5CB634AD29E46CF70066AD6B /* LocalAuthView.swift in Sources */,
@@ -1544,7 +1552,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 210; CURRENT_PROJECT_VERSION = 220;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -1569,7 +1577,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
LLVM_LTO = YES_THIN; LLVM_LTO = YES_THIN;
MARKETING_VERSION = 5.7; MARKETING_VERSION = 5.8;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
PRODUCT_NAME = SimpleX; PRODUCT_NAME = SimpleX;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -1593,7 +1601,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 210; CURRENT_PROJECT_VERSION = 220;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -1618,7 +1626,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
LLVM_LTO = YES; LLVM_LTO = YES;
MARKETING_VERSION = 5.7; MARKETING_VERSION = 5.8;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
PRODUCT_NAME = SimpleX; PRODUCT_NAME = SimpleX;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -1679,7 +1687,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 210; CURRENT_PROJECT_VERSION = 220;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
GCC_OPTIMIZATION_LEVEL = s; GCC_OPTIMIZATION_LEVEL = s;
@@ -1694,7 +1702,7 @@
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
LLVM_LTO = YES; LLVM_LTO = YES;
MARKETING_VERSION = 5.7; MARKETING_VERSION = 5.8;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@@ -1716,7 +1724,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 210; CURRENT_PROJECT_VERSION = 220;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
ENABLE_CODE_COVERAGE = NO; ENABLE_CODE_COVERAGE = NO;
@@ -1731,7 +1739,7 @@
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
LLVM_LTO = YES; LLVM_LTO = YES;
MARKETING_VERSION = 5.7; MARKETING_VERSION = 5.8;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@@ -1753,7 +1761,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 210; CURRENT_PROJECT_VERSION = 220;
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_COMPATIBILITY_VERSION = 1;
@@ -1779,7 +1787,7 @@
"$(PROJECT_DIR)/Libraries/sim", "$(PROJECT_DIR)/Libraries/sim",
); );
LLVM_LTO = YES; LLVM_LTO = YES;
MARKETING_VERSION = 5.7; MARKETING_VERSION = 5.8;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -1804,7 +1812,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 210; CURRENT_PROJECT_VERSION = 220;
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_COMPATIBILITY_VERSION = 1;
@@ -1830,7 +1838,7 @@
"$(PROJECT_DIR)/Libraries/sim", "$(PROJECT_DIR)/Libraries/sim",
); );
LLVM_LTO = YES; LLVM_LTO = YES;
MARKETING_VERSION = 5.7; MARKETING_VERSION = 5.8;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
+61 -6
View File
@@ -48,7 +48,7 @@ public enum ChatCommand {
case apiDeleteChatItem(type: ChatType, id: Int64, itemId: Int64, mode: CIDeleteMode) case apiDeleteChatItem(type: ChatType, id: Int64, itemId: Int64, mode: CIDeleteMode)
case apiDeleteMemberChatItem(groupId: Int64, groupMemberId: Int64, itemId: Int64) case apiDeleteMemberChatItem(groupId: Int64, groupMemberId: Int64, itemId: Int64)
case apiChatItemReaction(type: ChatType, id: Int64, itemId: Int64, add: Bool, reaction: MsgReaction) case apiChatItemReaction(type: ChatType, id: Int64, itemId: Int64, add: Bool, reaction: MsgReaction)
case apiForwardChatItem(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemId: Int64) case apiForwardChatItem(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemId: Int64, ttl: Int?)
case apiGetNtfToken case apiGetNtfToken
case apiRegisterToken(token: DeviceToken, notificationMode: NotificationsMode) case apiRegisterToken(token: DeviceToken, notificationMode: NotificationsMode)
case apiVerifyToken(token: DeviceToken, nonce: String, code: String) case apiVerifyToken(token: DeviceToken, nonce: String, code: String)
@@ -123,8 +123,8 @@ public enum ChatCommand {
case apiGetNetworkStatuses case apiGetNetworkStatuses
case apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64)) case apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64))
case apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool) case apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool)
case receiveFile(fileId: Int64, encrypted: Bool?, inline: Bool?) case receiveFile(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool?, inline: Bool?)
case setFileToReceive(fileId: Int64, encrypted: Bool?) case setFileToReceive(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool?)
case cancelFile(fileId: Int64) case cancelFile(fileId: Int64)
// remote desktop commands // remote desktop commands
case setLocalDeviceName(displayName: String) case setLocalDeviceName(displayName: String)
@@ -192,7 +192,9 @@ public enum ChatCommand {
case let .apiDeleteChatItem(type, id, itemId, mode): return "/_delete item \(ref(type, id)) \(itemId) \(mode.rawValue)" case let .apiDeleteChatItem(type, id, itemId, mode): return "/_delete item \(ref(type, id)) \(itemId) \(mode.rawValue)"
case let .apiDeleteMemberChatItem(groupId, groupMemberId, itemId): return "/_delete member item #\(groupId) \(groupMemberId) \(itemId)" case let .apiDeleteMemberChatItem(groupId, groupMemberId, itemId): return "/_delete member item #\(groupId) \(groupMemberId) \(itemId)"
case let .apiChatItemReaction(type, id, itemId, add, reaction): return "/_reaction \(ref(type, id)) \(itemId) \(onOff(add)) \(encodeJSON(reaction))" case let .apiChatItemReaction(type, id, itemId, add, reaction): return "/_reaction \(ref(type, id)) \(itemId) \(onOff(add)) \(encodeJSON(reaction))"
case let .apiForwardChatItem(toChatType, toChatId, fromChatType, fromChatId, itemId): return "/_forward \(ref(toChatType, toChatId)) \(ref(fromChatType, fromChatId)) \(itemId)" case let .apiForwardChatItem(toChatType, toChatId, fromChatType, fromChatId, itemId, ttl):
let ttlStr = ttl != nil ? "\(ttl!)" : "default"
return "/_forward \(ref(toChatType, toChatId)) \(ref(fromChatType, fromChatId)) \(itemId) ttl=\(ttlStr)"
case .apiGetNtfToken: return "/_ntf get " case .apiGetNtfToken: return "/_ntf get "
case let .apiRegisterToken(token, notificationMode): return "/_ntf register \(token.cmdString) \(notificationMode.rawValue)" 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)" case let .apiVerifyToken(token, nonce, code): return "/_ntf verify \(token.cmdString) \(nonce) \(code)"
@@ -280,8 +282,8 @@ public enum ChatCommand {
case .apiGetNetworkStatuses: return "/_network_statuses" case .apiGetNetworkStatuses: return "/_network_statuses"
case let .apiChatRead(type, id, itemRange: (from, to)): return "/_read chat \(ref(type, id)) from=\(from) to=\(to)" case let .apiChatRead(type, id, itemRange: (from, to)): return "/_read chat \(ref(type, id)) from=\(from) to=\(to)"
case let .apiChatUnread(type, id, unreadChat): return "/_unread chat \(ref(type, id)) \(onOff(unreadChat))" case let .apiChatUnread(type, id, unreadChat): return "/_unread chat \(ref(type, id)) \(onOff(unreadChat))"
case let .receiveFile(fileId, encrypt, inline): return "/freceive \(fileId)\(onOffParam("encrypt", encrypt))\(onOffParam("inline", inline))" case let .receiveFile(fileId, userApprovedRelays, encrypt, inline): return "/freceive \(fileId)\(onOffParam("approved_relays", userApprovedRelays))\(onOffParam("encrypt", encrypt))\(onOffParam("inline", inline))"
case let .setFileToReceive(fileId, encrypt): return "/_set_file_to_receive \(fileId)\(onOffParam("encrypt", encrypt))" 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)" case let .cancelFile(fileId): return "/fcancel \(fileId)"
case let .setLocalDeviceName(displayName): return "/set device name \(displayName)" case let .setLocalDeviceName(displayName): return "/set device name \(displayName)"
case let .connectRemoteCtrl(xrcpInv): return "/connect remote ctrl \(xrcpInv)" case let .connectRemoteCtrl(xrcpInv): return "/connect remote ctrl \(xrcpInv)"
@@ -639,6 +641,7 @@ public enum ChatResponse: Decodable, Error {
case ntfMessages(user_: User?, connEntity_: ConnectionEntity?, msgTs: Date?, ntfMessages: [NtfMsgInfo]) case ntfMessages(user_: User?, connEntity_: ConnectionEntity?, msgTs: Date?, ntfMessages: [NtfMsgInfo])
case ntfMessage(user: UserRef, connEntity: ConnectionEntity, ntfMessage: NtfMsgInfo) case ntfMessage(user: UserRef, connEntity: ConnectionEntity, ntfMessage: NtfMsgInfo)
case contactConnectionDeleted(user: UserRef, connection: PendingContactConnection) case contactConnectionDeleted(user: UserRef, connection: PendingContactConnection)
case contactDisabled(user: UserRef, contact: Contact)
// remote desktop responses/events // remote desktop responses/events
case remoteCtrlList(remoteCtrls: [RemoteCtrlInfo]) case remoteCtrlList(remoteCtrls: [RemoteCtrlInfo])
case remoteCtrlFound(remoteCtrl: RemoteCtrlInfo, ctrlAppInfo_: CtrlAppInfo?, appVersion: String, compatible: Bool) case remoteCtrlFound(remoteCtrl: RemoteCtrlInfo, ctrlAppInfo_: CtrlAppInfo?, appVersion: String, compatible: Bool)
@@ -796,6 +799,7 @@ public enum ChatResponse: Decodable, Error {
case .ntfMessages: return "ntfMessages" case .ntfMessages: return "ntfMessages"
case .ntfMessage: return "ntfMessage" case .ntfMessage: return "ntfMessage"
case .contactConnectionDeleted: return "contactConnectionDeleted" case .contactConnectionDeleted: return "contactConnectionDeleted"
case .contactDisabled: return "contactDisabled"
case .remoteCtrlList: return "remoteCtrlList" case .remoteCtrlList: return "remoteCtrlList"
case .remoteCtrlFound: return "remoteCtrlFound" case .remoteCtrlFound: return "remoteCtrlFound"
case .remoteCtrlConnecting: return "remoteCtrlConnecting" case .remoteCtrlConnecting: return "remoteCtrlConnecting"
@@ -953,6 +957,7 @@ public enum ChatResponse: Decodable, Error {
case let .ntfMessages(u, connEntity, msgTs, ntfMessages): return withUser(u, "connEntity: \(String(describing: connEntity))\nmsgTs: \(String(describing: msgTs))\nntfMessages: \(String(describing: ntfMessages))") case let .ntfMessages(u, connEntity, msgTs, ntfMessages): return withUser(u, "connEntity: \(String(describing: connEntity))\nmsgTs: \(String(describing: msgTs))\nntfMessages: \(String(describing: ntfMessages))")
case let .ntfMessage(u, connEntity, ntfMessage): return withUser(u, "connEntity: \(String(describing: connEntity))\nntfMessage: \(String(describing: ntfMessage))") case let .ntfMessage(u, connEntity, ntfMessage): return withUser(u, "connEntity: \(String(describing: connEntity))\nntfMessage: \(String(describing: ntfMessage))")
case let .contactConnectionDeleted(u, connection): return withUser(u, String(describing: connection)) case let .contactConnectionDeleted(u, connection): return withUser(u, String(describing: connection))
case let .contactDisabled(u, contact): return withUser(u, String(describing: contact))
case let .remoteCtrlList(remoteCtrls): return String(describing: remoteCtrls) case let .remoteCtrlList(remoteCtrls): return String(describing: remoteCtrls)
case let .remoteCtrlFound(remoteCtrl, ctrlAppInfo_, appVersion, compatible): return "remoteCtrl:\n\(String(describing: remoteCtrl))\nctrlAppInfo_:\n\(String(describing: ctrlAppInfo_))\nappVersion: \(appVersion)\ncompatible: \(compatible)" case let .remoteCtrlFound(remoteCtrl, ctrlAppInfo_, appVersion, compatible): return "remoteCtrl:\n\(String(describing: remoteCtrl))\nctrlAppInfo_:\n\(String(describing: ctrlAppInfo_))\nappVersion: \(appVersion)\ncompatible: \(compatible)"
case let .remoteCtrlConnecting(remoteCtrl_, ctrlAppInfo, appVersion): return "remoteCtrl_:\n\(String(describing: remoteCtrl_))\nctrlAppInfo:\n\(String(describing: ctrlAppInfo))\nappVersion: \(appVersion)" case let .remoteCtrlConnecting(remoteCtrl_, ctrlAppInfo, appVersion): return "remoteCtrl_:\n\(String(describing: remoteCtrl_))\nctrlAppInfo:\n\(String(describing: ctrlAppInfo))\nappVersion: \(appVersion)"
@@ -1242,9 +1247,12 @@ public struct ServerAddress: Decodable {
public struct NetCfg: Codable, Equatable { public struct NetCfg: Codable, Equatable {
public var socksProxy: String? = nil public var socksProxy: String? = nil
var socksMode: SocksMode = .always
public var hostMode: HostMode = .publicHost public var hostMode: HostMode = .publicHost
public var requiredHostMode = true public var requiredHostMode = true
public var sessionMode: TransportSessionMode public var sessionMode: TransportSessionMode
public var smpProxyMode: SMPProxyMode = .never
public var smpProxyFallback: SMPProxyFallback = .allow
public var tcpConnectTimeout: Int // microseconds public var tcpConnectTimeout: Int // microseconds
public var tcpTimeout: Int // microseconds public var tcpTimeout: Int // microseconds
public var tcpTimeoutPerKb: Int // microseconds public var tcpTimeoutPerKb: Int // microseconds
@@ -1289,6 +1297,49 @@ public enum HostMode: String, Codable {
case publicHost = "public" case publicHost = "public"
} }
public enum SocksMode: String, Codable {
case always = "always"
case onion = "onion"
}
public enum SMPProxyMode: String, Codable {
case always = "always"
case unknown = "unknown"
case unprotected = "unprotected"
case never = "never"
public var text: LocalizedStringKey {
switch self {
case .always: return "always"
case .unknown: return "unknown relays"
case .unprotected: return "unprotected"
case .never: return "never"
}
}
public var id: SMPProxyMode { self }
public static let values: [SMPProxyMode] = [.always, .unknown, .unprotected, .never]
}
public enum SMPProxyFallback: String, Codable {
case allow = "allow"
case allowProtected = "allowProtected"
case prohibit = "prohibit"
public var text: LocalizedStringKey {
switch self {
case .allow: return "yes"
case .allowProtected: return "when IP hidden"
case .prohibit: return "no"
}
}
public var id: SMPProxyFallback { self }
public static let values: [SMPProxyFallback] = [.allow, .allowProtected, .prohibit]
}
public enum OnionHosts: String, Identifiable { public enum OnionHosts: String, Identifiable {
case no case no
case prefer case prefer
@@ -1709,6 +1760,7 @@ public enum ChatErrorType: Decodable {
case fileImageType(filePath: String) case fileImageType(filePath: String)
case fileImageSize(filePath: String) case fileImageSize(filePath: String)
case fileNotReceived(fileId: Int64) case fileNotReceived(fileId: Int64)
case fileNotApproved(fileId: Int64, unknownServers: [String])
// case xFTPRcvFile // case xFTPRcvFile
// case xFTPSndFile // case xFTPSndFile
case fallbackToSMPProhibited(fileId: Int64) case fallbackToSMPProhibited(fileId: Int64)
@@ -1987,6 +2039,7 @@ public struct MigrationFileLinkData: Codable {
public struct AppSettings: Codable, Equatable { public struct AppSettings: Codable, Equatable {
public var networkConfig: NetCfg? = nil public var networkConfig: NetCfg? = nil
public var privacyEncryptLocalFiles: Bool? = nil public var privacyEncryptLocalFiles: Bool? = nil
public var privacyAskToApproveRelays: Bool? = nil
public var privacyAcceptImages: Bool? = nil public var privacyAcceptImages: Bool? = nil
public var privacyLinkPreviews: Bool? = nil public var privacyLinkPreviews: Bool? = nil
public var privacyShowChatPreviews: Bool? = nil public var privacyShowChatPreviews: Bool? = nil
@@ -2010,6 +2063,7 @@ public struct AppSettings: Codable, Equatable {
let def = AppSettings.defaults let def = AppSettings.defaults
if networkConfig != def.networkConfig { empty.networkConfig = networkConfig } if networkConfig != def.networkConfig { empty.networkConfig = networkConfig }
if privacyEncryptLocalFiles != def.privacyEncryptLocalFiles { empty.privacyEncryptLocalFiles = privacyEncryptLocalFiles } if privacyEncryptLocalFiles != def.privacyEncryptLocalFiles { empty.privacyEncryptLocalFiles = privacyEncryptLocalFiles }
if privacyAskToApproveRelays != def.privacyAskToApproveRelays { empty.privacyAskToApproveRelays = privacyAskToApproveRelays }
if privacyAcceptImages != def.privacyAcceptImages { empty.privacyAcceptImages = privacyAcceptImages } if privacyAcceptImages != def.privacyAcceptImages { empty.privacyAcceptImages = privacyAcceptImages }
if privacyLinkPreviews != def.privacyLinkPreviews { empty.privacyLinkPreviews = privacyLinkPreviews } if privacyLinkPreviews != def.privacyLinkPreviews { empty.privacyLinkPreviews = privacyLinkPreviews }
if privacyShowChatPreviews != def.privacyShowChatPreviews { empty.privacyShowChatPreviews = privacyShowChatPreviews } if privacyShowChatPreviews != def.privacyShowChatPreviews { empty.privacyShowChatPreviews = privacyShowChatPreviews }
@@ -2034,6 +2088,7 @@ public struct AppSettings: Codable, Equatable {
AppSettings ( AppSettings (
networkConfig: NetCfg.defaults, networkConfig: NetCfg.defaults,
privacyEncryptLocalFiles: true, privacyEncryptLocalFiles: true,
privacyAskToApproveRelays: true,
privacyAcceptImages: true, privacyAcceptImages: true,
privacyLinkPreviews: true, privacyLinkPreviews: true,
privacyShowChatPreviews: true, privacyShowChatPreviews: true,
+26
View File
@@ -23,9 +23,12 @@ public let GROUP_DEFAULT_NTF_ENABLE_PERIODIC = "ntfEnablePeriodic" // no longer
let GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES = "privacyAcceptImages" let GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES = "privacyAcceptImages"
public let GROUP_DEFAULT_PRIVACY_TRANSFER_IMAGES_INLINE = "privacyTransferImagesInline" // no longer used public let GROUP_DEFAULT_PRIVACY_TRANSFER_IMAGES_INLINE = "privacyTransferImagesInline" // no longer used
public let GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES = "privacyEncryptLocalFiles" public let GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES = "privacyEncryptLocalFiles"
public let GROUP_DEFAULT_PRIVACY_ASK_TO_APPROVE_RELAYS = "privacyAskToApproveRelays"
let GROUP_DEFAULT_NTF_BADGE_COUNT = "ntgBadgeCount" let GROUP_DEFAULT_NTF_BADGE_COUNT = "ntgBadgeCount"
let GROUP_DEFAULT_NETWORK_USE_ONION_HOSTS = "networkUseOnionHosts" let GROUP_DEFAULT_NETWORK_USE_ONION_HOSTS = "networkUseOnionHosts"
let GROUP_DEFAULT_NETWORK_SESSION_MODE = "networkSessionMode" let GROUP_DEFAULT_NETWORK_SESSION_MODE = "networkSessionMode"
let GROUP_DEFAULT_NETWORK_SMP_PROXY_MODE = "networkSMPProxyMode"
let GROUP_DEFAULT_NETWORK_SMP_PROXY_FALLBACK = "networkSMPProxyFallback"
let GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT = "networkTCPConnectTimeout" let GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT = "networkTCPConnectTimeout"
let GROUP_DEFAULT_NETWORK_TCP_TIMEOUT = "networkTCPTimeout" let GROUP_DEFAULT_NETWORK_TCP_TIMEOUT = "networkTCPTimeout"
let GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB = "networkTCPTimeoutPerKb" let GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB = "networkTCPTimeoutPerKb"
@@ -53,6 +56,8 @@ public func registerGroupDefaults() {
GROUP_DEFAULT_NTF_ENABLE_PERIODIC: false, GROUP_DEFAULT_NTF_ENABLE_PERIODIC: false,
GROUP_DEFAULT_NETWORK_USE_ONION_HOSTS: OnionHosts.no.rawValue, GROUP_DEFAULT_NETWORK_USE_ONION_HOSTS: OnionHosts.no.rawValue,
GROUP_DEFAULT_NETWORK_SESSION_MODE: TransportSessionMode.user.rawValue, GROUP_DEFAULT_NETWORK_SESSION_MODE: TransportSessionMode.user.rawValue,
GROUP_DEFAULT_NETWORK_SMP_PROXY_MODE: SMPProxyMode.never.rawValue,
GROUP_DEFAULT_NETWORK_SMP_PROXY_FALLBACK: SMPProxyFallback.allow.rawValue,
GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT: NetCfg.defaults.tcpConnectTimeout, GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT: NetCfg.defaults.tcpConnectTimeout,
GROUP_DEFAULT_NETWORK_TCP_TIMEOUT: NetCfg.defaults.tcpTimeout, GROUP_DEFAULT_NETWORK_TCP_TIMEOUT: NetCfg.defaults.tcpTimeout,
GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB: NetCfg.defaults.tcpTimeoutPerKb, GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB: NetCfg.defaults.tcpTimeoutPerKb,
@@ -69,6 +74,7 @@ public func registerGroupDefaults() {
GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES: true, GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES: true,
GROUP_DEFAULT_PRIVACY_TRANSFER_IMAGES_INLINE: false, GROUP_DEFAULT_PRIVACY_TRANSFER_IMAGES_INLINE: false,
GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES: true, GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES: true,
GROUP_DEFAULT_PRIVACY_ASK_TO_APPROVE_RELAYS: true,
GROUP_DEFAULT_CONFIRM_DB_UPGRADES: false, GROUP_DEFAULT_CONFIRM_DB_UPGRADES: false,
GROUP_DEFAULT_CALL_KIT_ENABLED: true, GROUP_DEFAULT_CALL_KIT_ENABLED: true,
GROUP_DEFAULT_PQ_EXPERIMENTAL_ENABLED: false, GROUP_DEFAULT_PQ_EXPERIMENTAL_ENABLED: false,
@@ -177,6 +183,8 @@ public let privacyAcceptImagesGroupDefault = BoolDefault(defaults: groupDefaults
public let privacyEncryptLocalFilesGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES) public let privacyEncryptLocalFilesGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES)
public let privacyAskToApproveRelaysGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_PRIVACY_ASK_TO_APPROVE_RELAYS)
public let ntfBadgeCountGroupDefault = IntDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_NTF_BADGE_COUNT) public let ntfBadgeCountGroupDefault = IntDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_NTF_BADGE_COUNT)
public let networkUseOnionHostsGroupDefault = EnumDefault<OnionHosts>( public let networkUseOnionHostsGroupDefault = EnumDefault<OnionHosts>(
@@ -191,6 +199,18 @@ public let networkSessionModeGroupDefault = EnumDefault<TransportSessionMode>(
withDefault: .user withDefault: .user
) )
public let networkSMPProxyModeGroupDefault = EnumDefault<SMPProxyMode>(
defaults: groupDefaults,
forKey: GROUP_DEFAULT_NETWORK_SMP_PROXY_MODE,
withDefault: .never
)
public let networkSMPProxyFallbackGroupDefault = EnumDefault<SMPProxyFallback>(
defaults: groupDefaults,
forKey: GROUP_DEFAULT_NETWORK_SMP_PROXY_FALLBACK,
withDefault: .allow
)
public let storeDBPassphraseGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_STORE_DB_PASSPHRASE) public let storeDBPassphraseGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_STORE_DB_PASSPHRASE)
public let initialRandomDBPassphraseGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_INITIAL_RANDOM_DB_PASSPHRASE) public let initialRandomDBPassphraseGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_INITIAL_RANDOM_DB_PASSPHRASE)
@@ -275,6 +295,8 @@ public func getNetCfg() -> NetCfg {
let onionHosts = networkUseOnionHostsGroupDefault.get() let onionHosts = networkUseOnionHostsGroupDefault.get()
let (hostMode, requiredHostMode) = onionHosts.hostMode let (hostMode, requiredHostMode) = onionHosts.hostMode
let sessionMode = networkSessionModeGroupDefault.get() let sessionMode = networkSessionModeGroupDefault.get()
let smpProxyMode = networkSMPProxyModeGroupDefault.get()
let smpProxyFallback = networkSMPProxyFallbackGroupDefault.get()
let tcpConnectTimeout = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT) let tcpConnectTimeout = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT)
let tcpTimeout = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT) let tcpTimeout = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT)
let tcpTimeoutPerKb = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB) let tcpTimeoutPerKb = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB)
@@ -295,6 +317,8 @@ public func getNetCfg() -> NetCfg {
hostMode: hostMode, hostMode: hostMode,
requiredHostMode: requiredHostMode, requiredHostMode: requiredHostMode,
sessionMode: sessionMode, sessionMode: sessionMode,
smpProxyMode: smpProxyMode,
smpProxyFallback: smpProxyFallback,
tcpConnectTimeout: tcpConnectTimeout, tcpConnectTimeout: tcpConnectTimeout,
tcpTimeout: tcpTimeout, tcpTimeout: tcpTimeout,
tcpTimeoutPerKb: tcpTimeoutPerKb, tcpTimeoutPerKb: tcpTimeoutPerKb,
@@ -309,6 +333,8 @@ public func getNetCfg() -> NetCfg {
public func setNetCfg(_ cfg: NetCfg) { public func setNetCfg(_ cfg: NetCfg) {
networkUseOnionHostsGroupDefault.set(OnionHosts(netCfg: cfg)) networkUseOnionHostsGroupDefault.set(OnionHosts(netCfg: cfg))
networkSessionModeGroupDefault.set(cfg.sessionMode) networkSessionModeGroupDefault.set(cfg.sessionMode)
networkSMPProxyModeGroupDefault.set(cfg.smpProxyMode)
networkSMPProxyFallbackGroupDefault.set(cfg.smpProxyFallback)
groupDefaults.set(cfg.tcpConnectTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT) groupDefaults.set(cfg.tcpConnectTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT)
groupDefaults.set(cfg.tcpTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT) groupDefaults.set(cfg.tcpTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT)
groupDefaults.set(cfg.tcpTimeoutPerKb, forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB) groupDefaults.set(cfg.tcpTimeoutPerKb, forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB)
+66 -6
View File
@@ -1510,7 +1510,12 @@ public struct Contact: Identifiable, Decodable, NamedChat {
public var ready: Bool { get { activeConn?.connStatus == .ready } } public var ready: Bool { get { activeConn?.connStatus == .ready } }
public var active: Bool { get { contactStatus == .active } } public var active: Bool { get { contactStatus == .active } }
public var sendMsgEnabled: Bool { get { public var sendMsgEnabled: Bool { get {
(ready && active && !(activeConn?.connectionStats?.ratchetSyncSendProhibited ?? false)) (
ready
&& active
&& !(activeConn?.connectionStats?.ratchetSyncSendProhibited ?? false)
&& !(activeConn?.connDisabled ?? true)
)
|| nextSendGrpInv || nextSendGrpInv
} } } }
public var nextSendGrpInv: Bool { get { contactGroupMemberId != nil && !contactGrpInvSent } } public var nextSendGrpInv: Bool { get { contactGroupMemberId != nil && !contactGrpInvSent } }
@@ -1601,15 +1606,20 @@ public struct Connection: Decodable {
public var pqEncryption: Bool public var pqEncryption: Bool
public var pqSndEnabled: Bool? public var pqSndEnabled: Bool?
public var pqRcvEnabled: Bool? public var pqRcvEnabled: Bool?
public var authErrCounter: Int
public var connectionStats: ConnectionStats? = nil public var connectionStats: ConnectionStats? = nil
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case connId, agentConnId, peerChatVRange, connStatus, connLevel, viaGroupLink, customUserProfileId, connectionCode, pqSupport, pqEncryption, pqSndEnabled, pqRcvEnabled case connId, agentConnId, peerChatVRange, connStatus, connLevel, viaGroupLink, customUserProfileId, connectionCode, pqSupport, pqEncryption, pqSndEnabled, pqRcvEnabled, authErrCounter
} }
public var id: ChatId { get { ":\(connId)" } } public var id: ChatId { get { ":\(connId)" } }
public var connDisabled: Bool {
authErrCounter >= 10 // authErrDisableCount in core
}
public var connPQEnabled: Bool { public var connPQEnabled: Bool {
pqSndEnabled == true && pqRcvEnabled == true pqSndEnabled == true && pqRcvEnabled == true
} }
@@ -1622,7 +1632,8 @@ public struct Connection: Decodable {
connLevel: 0, connLevel: 0,
viaGroupLink: false, viaGroupLink: false,
pqSupport: false, pqSupport: false,
pqEncryption: false pqEncryption: false,
authErrCounter: 0
) )
} }
@@ -2621,6 +2632,7 @@ public struct CIMeta: Decodable {
public var itemTs: Date public var itemTs: Date
var itemText: String var itemText: String
public var itemStatus: CIStatus public var itemStatus: CIStatus
public var sentViaProxy: Bool?
public var createdAt: Date public var createdAt: Date
public var updatedAt: Date public var updatedAt: Date
public var itemForwarded: CIForwardedFrom? public var itemForwarded: CIForwardedFrom?
@@ -2710,7 +2722,8 @@ public enum CIStatus: Decodable {
case sndSent(sndProgress: SndCIStatusProgress) case sndSent(sndProgress: SndCIStatusProgress)
case sndRcvd(msgRcptStatus: MsgReceiptStatus, sndProgress: SndCIStatusProgress) case sndRcvd(msgRcptStatus: MsgReceiptStatus, sndProgress: SndCIStatusProgress)
case sndErrorAuth case sndErrorAuth
case sndError(agentError: String) case sndError(agentError: SndError)
case sndWarning(agentError: SndError)
case rcvNew case rcvNew
case rcvRead case rcvRead
case invalid(text: String) case invalid(text: String)
@@ -2722,6 +2735,7 @@ public enum CIStatus: Decodable {
case .sndRcvd: return "sndRcvd" case .sndRcvd: return "sndRcvd"
case .sndErrorAuth: return "sndErrorAuth" case .sndErrorAuth: return "sndErrorAuth"
case .sndError: return "sndError" case .sndError: return "sndError"
case .sndWarning: return "sndWarning"
case .rcvNew: return "rcvNew" case .rcvNew: return "rcvNew"
case .rcvRead: return "rcvRead" case .rcvRead: return "rcvRead"
case .invalid: return "invalid" case .invalid: return "invalid"
@@ -2738,7 +2752,8 @@ public enum CIStatus: Decodable {
case .badMsgHash: return ("checkmark", .red) case .badMsgHash: return ("checkmark", .red)
} }
case .sndErrorAuth: return ("multiply", .red) case .sndErrorAuth: return ("multiply", .red)
case .sndError: return ("exclamationmark.triangle.fill", .yellow) case .sndError: return ("multiply", .red)
case .sndWarning: return ("exclamationmark.triangle.fill", .orange)
case .rcvNew: return ("circlebadge.fill", Color.accentColor) case .rcvNew: return ("circlebadge.fill", Color.accentColor)
case .rcvRead: return nil case .rcvRead: return nil
case .invalid: return ("questionmark", metaColor) case .invalid: return ("questionmark", metaColor)
@@ -2756,7 +2771,11 @@ public enum CIStatus: Decodable {
) )
case let .sndError(agentError): return ( case let .sndError(agentError): return (
NSLocalizedString("Message delivery error", comment: "item status text"), NSLocalizedString("Message delivery error", comment: "item status text"),
String.localizedStringWithFormat(NSLocalizedString("Unexpected error: %@", comment: "item status description"), agentError) agentError.errorInfo
)
case let .sndWarning(agentError): return (
NSLocalizedString("Message delivery warning", comment: "item status text"),
agentError.errorInfo
) )
case .rcvNew: return nil case .rcvNew: return nil
case .rcvRead: return nil case .rcvRead: return nil
@@ -2768,6 +2787,42 @@ public enum CIStatus: Decodable {
} }
} }
public enum SndError: Decodable {
case auth
case quota
case expired
case relay(srvError: SrvError)
case proxy(proxyServer: String, srvError: SrvError)
case proxyRelay(proxyServer: String, srvError: SrvError)
case other(sndError: String)
public var errorInfo: String {
switch self {
case .auth: NSLocalizedString("Wrong key or unknown connection - most likely this connection is deleted.", comment: "snd error text")
case .quota: NSLocalizedString("Capacity exceeded - recipient did not receive previously sent messages.", comment: "snd error text")
case .expired: NSLocalizedString("Network issues - message expired after many attempts to send it.", comment: "snd error text")
case let .relay(srvError): String.localizedStringWithFormat(NSLocalizedString("Destination server error: %@", comment: "snd error text"), srvError.errorInfo)
case let .proxy(proxyServer, srvError): String.localizedStringWithFormat(NSLocalizedString("Forwarding server: %@\nError: %@", comment: "snd error text"), proxyServer, srvError.errorInfo)
case let .proxyRelay(proxyServer, srvError): String.localizedStringWithFormat(NSLocalizedString("Forwarding server: %@\nDestination server error: %@", comment: "snd error text"), proxyServer, srvError.errorInfo)
case let .other(sndError): String.localizedStringWithFormat(NSLocalizedString("Error: %@", comment: "snd error text"), sndError)
}
}
}
public enum SrvError: Decodable {
case host
case version
case other(srvError: String)
public var errorInfo: String {
switch self {
case .host: NSLocalizedString("Server address is incompatible with network settings.", comment: "srv error text.")
case .version: NSLocalizedString("Server version is incompatible with network settings.", comment: "srv error text")
case let .other(srvError): srvError
}
}
}
public enum MsgReceiptStatus: String, Decodable { public enum MsgReceiptStatus: String, Decodable {
case ok case ok
case badMsgHash case badMsgHash
@@ -3119,6 +3174,7 @@ public struct CIFile: Decodable {
case .rcvInvitation: return false case .rcvInvitation: return false
case .rcvAccepted: return false case .rcvAccepted: return false
case .rcvTransfer: return false case .rcvTransfer: return false
case .rcvAborted: return false
case .rcvCancelled: return false case .rcvCancelled: return false
case .rcvComplete: return true case .rcvComplete: return true
case .rcvError: return false case .rcvError: return false
@@ -3143,6 +3199,7 @@ public struct CIFile: Decodable {
case .rcvInvitation: return nil case .rcvInvitation: return nil
case .rcvAccepted: return rcvCancelAction case .rcvAccepted: return rcvCancelAction
case .rcvTransfer: return rcvCancelAction case .rcvTransfer: return rcvCancelAction
case .rcvAborted: return nil
case .rcvCancelled: return nil case .rcvCancelled: return nil
case .rcvComplete: return nil case .rcvComplete: return nil
case .rcvError: return nil case .rcvError: return nil
@@ -3257,6 +3314,7 @@ public enum CIFileStatus: Decodable, Equatable {
case rcvInvitation case rcvInvitation
case rcvAccepted case rcvAccepted
case rcvTransfer(rcvProgress: Int64, rcvTotal: Int64) case rcvTransfer(rcvProgress: Int64, rcvTotal: Int64)
case rcvAborted
case rcvComplete case rcvComplete
case rcvCancelled case rcvCancelled
case rcvError case rcvError
@@ -3272,6 +3330,7 @@ public enum CIFileStatus: Decodable, Equatable {
case .rcvInvitation: return "rcvInvitation" case .rcvInvitation: return "rcvInvitation"
case .rcvAccepted: return "rcvAccepted" case .rcvAccepted: return "rcvAccepted"
case let .rcvTransfer(rcvProgress, rcvTotal): return "rcvTransfer \(rcvProgress) \(rcvTotal)" case let .rcvTransfer(rcvProgress, rcvTotal): return "rcvTransfer \(rcvProgress) \(rcvTotal)"
case .rcvAborted: return "rcvAborted"
case .rcvComplete: return "rcvComplete" case .rcvComplete: return "rcvComplete"
case .rcvCancelled: return "rcvCancelled" case .rcvCancelled: return "rcvCancelled"
case .rcvError: return "rcvError" case .rcvError: return "rcvError"
@@ -3886,4 +3945,5 @@ public struct ChatItemVersion: Decodable {
public struct MemberDeliveryStatus: Decodable { public struct MemberDeliveryStatus: Decodable {
public var groupMemberId: Int64 public var groupMemberId: Int64
public var memberDeliveryStatus: CIStatus public var memberDeliveryStatus: CIStatus
public var sentViaProxy: Bool?
} }
+30
View File
@@ -1893,6 +1893,9 @@
/* chat item action */ /* chat item action */
"Forward" = "Препрати"; "Forward" = "Препрати";
/* No comment provided by engineer. */
"Forward and save messages" = "Препращане и запазване на съобщения";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"forwarded" = "препратено"; "forwarded" = "препратено";
@@ -2124,6 +2127,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"In reply to" = "В отговор на"; "In reply to" = "В отговор на";
/* No comment provided by engineer. */
"In-call sounds" = "Звуци по време на разговор";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Incognito" = "Инкогнито"; "Incognito" = "Инкогнито";
@@ -2469,6 +2475,9 @@
/* notification */ /* notification */
"message received" = "получено съобщение"; "message received" = "получено съобщение";
/* No comment provided by engineer. */
"Message source remains private." = "Източникът на съобщението остава скрит.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Message text" = "Текст на съобщението"; "Message text" = "Текст на съобщението";
@@ -2553,6 +2562,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"More improvements are coming soon!" = "Очаквайте скоро още подобрения!"; "More improvements are coming soon!" = "Очаквайте скоро още подобрения!";
/* No comment provided by engineer. */
"More reliable network connection." = "По-надеждна мрежова връзка.";
/* item status description */ /* item status description */
"Most likely this connection is deleted." = "Най-вероятно тази връзка е изтрита."; "Most likely this connection is deleted." = "Най-вероятно тази връзка е изтрита.";
@@ -2577,6 +2589,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Network connection" = "Мрежова връзка"; "Network connection" = "Мрежова връзка";
/* No comment provided by engineer. */
"Network management" = "Управление на мрежата";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Network settings" = "Мрежови настройки"; "Network settings" = "Мрежови настройки";
@@ -2945,6 +2960,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Profile image" = "Профилно изображение"; "Profile image" = "Профилно изображение";
/* No comment provided by engineer. */
"Profile images" = "Профилни изображения";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Profile name" = "Име на профила"; "Profile name" = "Име на профила";
@@ -3488,6 +3506,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Settings" = "Настройки"; "Settings" = "Настройки";
/* No comment provided by engineer. */
"Shape profile images" = "Променете формата на профилните изображения";
/* chat item action */ /* chat item action */
"Share" = "Сподели"; "Share" = "Сподели";
@@ -3590,6 +3611,9 @@
/* notification title */ /* notification title */
"Somebody" = "Някой"; "Somebody" = "Някой";
/* No comment provided by engineer. */
"Square, circle, or anything in between." = "Квадрат, кръг или нещо между тях.";
/* chat item text */ /* chat item text */
"standard end-to-end encryption" = "стандартно криптиране от край до край"; "standard end-to-end encryption" = "стандартно криптиране от край до край";
@@ -4172,6 +4196,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"When available" = "Когато са налични"; "When available" = "Когато са налични";
/* No comment provided by engineer. */
"When connecting audio and video calls." = "При свързване на аудио и видео разговори.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"When people request to connect, you can accept or reject it." = "Когато хората искат да се свържат с вас, можете да ги приемете или отхвърлите."; "When people request to connect, you can accept or reject it." = "Когато хората искат да се свържат с вас, можете да ги приемете или отхвърлите.";
@@ -4181,6 +4208,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"WiFi" = "WiFi"; "WiFi" = "WiFi";
/* No comment provided by engineer. */
"Will be enabled in direct chats!" = "Ще бъде активирано в личните чатове!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Wired ethernet" = "Кабелен Ethernet"; "Wired ethernet" = "Кабелен Ethernet";
+10 -1
View File
@@ -2960,6 +2960,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Profile image" = "Profilbild"; "Profile image" = "Profilbild";
/* No comment provided by engineer. */
"Profile images" = "Profil-Bilder";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Profile name" = "Profilname"; "Profile name" = "Profilname";
@@ -3503,6 +3506,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Settings" = "Einstellungen"; "Settings" = "Einstellungen";
/* No comment provided by engineer. */
"Shape profile images" = "Form der Profil-Bilder";
/* chat item action */ /* chat item action */
"Share" = "Teilen"; "Share" = "Teilen";
@@ -3605,6 +3611,9 @@
/* notification title */ /* notification title */
"Somebody" = "Jemand"; "Somebody" = "Jemand";
/* No comment provided by engineer. */
"Square, circle, or anything in between." = "Quadratisch, kreisförmig oder irgendetwas dazwischen.";
/* chat item text */ /* chat item text */
"standard end-to-end encryption" = "Standard-Ende-zu-Ende-Verschlüsselung"; "standard end-to-end encryption" = "Standard-Ende-zu-Ende-Verschlüsselung";
@@ -4020,7 +4029,7 @@
"Use chat" = "Verwenden Sie Chat"; "Use chat" = "Verwenden Sie Chat";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Use current profile" = "Nutzen Sie das aktuelle Profil"; "Use current profile" = "Das aktuelle Profil nutzen";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Use for new connections" = "Für neue Verbindungen nutzen"; "Use for new connections" = "Für neue Verbindungen nutzen";
+9
View File
@@ -2960,6 +2960,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Profile image" = "Imagen del perfil"; "Profile image" = "Imagen del perfil";
/* No comment provided by engineer. */
"Profile images" = "Imágenes del perfil";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Profile name" = "Nombre del perfil"; "Profile name" = "Nombre del perfil";
@@ -3503,6 +3506,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Settings" = "Configuración"; "Settings" = "Configuración";
/* No comment provided by engineer. */
"Shape profile images" = "Dar forma a las imágenes de perfil";
/* chat item action */ /* chat item action */
"Share" = "Compartir"; "Share" = "Compartir";
@@ -3605,6 +3611,9 @@
/* notification title */ /* notification title */
"Somebody" = "Alguien"; "Somebody" = "Alguien";
/* No comment provided by engineer. */
"Square, circle, or anything in between." = "Cuadrada, circular o cualquier forma intermedia.";
/* chat item text */ /* chat item text */
"standard end-to-end encryption" = "cifrado estándar de extremo a extremo"; "standard end-to-end encryption" = "cifrado estándar de extremo a extremo";
+117
View File
@@ -389,6 +389,9 @@
/* member role */ /* member role */
"admin" = "admin"; "admin" = "admin";
/* feature role */
"admins" = "admins";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Admins can block a member for all." = "Les admins peuvent bloquer un membre pour tous."; "Admins can block a member for all." = "Les admins peuvent bloquer un membre pour tous.";
@@ -416,6 +419,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"All group members will remain connected." = "Tous les membres du groupe resteront connectés."; "All group members will remain connected." = "Tous les membres du groupe resteront connectés.";
/* feature role */
"all members" = "tous les membres";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"All messages will be deleted - this cannot be undone!" = "Tous les messages seront supprimés - il n'est pas possible de revenir en arrière!"; "All messages will be deleted - this cannot be undone!" = "Tous les messages seront supprimés - il n'est pas possible de revenir en arrière!";
@@ -464,6 +470,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Allow to send files and media." = "Permet l'envoi de fichiers et de médias."; "Allow to send files and media." = "Permet l'envoi de fichiers et de médias.";
/* No comment provided by engineer. */
"Allow to send SimpleX links." = "Autorise l'envoi de liens SimpleX.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Allow to send voice messages." = "Autoriser l'envoi de messages vocaux."; "Allow to send voice messages." = "Autoriser l'envoi de messages vocaux.";
@@ -707,6 +716,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Cannot receive file" = "Impossible de recevoir le fichier"; "Cannot receive file" = "Impossible de recevoir le fichier";
/* No comment provided by engineer. */
"Cellular" = "Cellulaire";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Change" = "Changer"; "Change" = "Changer";
@@ -1401,6 +1413,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Downgrade and open chat" = "Rétrograder et ouvrir le chat"; "Downgrade and open chat" = "Rétrograder et ouvrir le chat";
/* chat item action */
"Download" = "Télécharger";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Download failed" = "Échec du téléchargement"; "Download failed" = "Échec du téléchargement";
@@ -1476,6 +1491,9 @@
/* enabled status */ /* enabled status */
"enabled" = "activé"; "enabled" = "activé";
/* No comment provided by engineer. */
"Enabled for" = "Activé pour";
/* enabled status */ /* enabled status */
"enabled for contact" = "activé pour le contact"; "enabled for contact" = "activé pour le contact";
@@ -1830,6 +1848,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Files and media are prohibited in this group." = "Les fichiers et les médias sont interdits dans ce groupe."; "Files and media are prohibited in this group." = "Les fichiers et les médias sont interdits dans ce groupe.";
/* No comment provided by engineer. */
"Files and media not allowed" = "Fichiers et médias non autorisés";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Files and media prohibited!" = "Fichiers et médias interdits !"; "Files and media prohibited!" = "Fichiers et médias interdits !";
@@ -1869,6 +1890,21 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"For console" = "Pour la console"; "For console" = "Pour la console";
/* chat item action */
"Forward" = "Transférer";
/* No comment provided by engineer. */
"Forward and save messages" = "Transférer et sauvegarder des messages";
/* No comment provided by engineer. */
"forwarded" = "transféré";
/* No comment provided by engineer. */
"Forwarded" = "Transféré";
/* No comment provided by engineer. */
"Forwarded from" = "Transféré depuis";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Found desktop" = "Bureau trouvé"; "Found desktop" = "Bureau trouvé";
@@ -1947,6 +1983,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Group members can send files and media." = "Les membres du groupe peuvent envoyer des fichiers et des médias."; "Group members can send files and media." = "Les membres du groupe peuvent envoyer des fichiers et des médias.";
/* No comment provided by engineer. */
"Group members can send SimpleX links." = "Les membres du groupe peuvent envoyer des liens SimpleX.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Group members can send voice messages." = "Les membres du groupe peuvent envoyer des messages vocaux."; "Group members can send voice messages." = "Les membres du groupe peuvent envoyer des messages vocaux.";
@@ -2088,6 +2127,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"In reply to" = "En réponse à"; "In reply to" = "En réponse à";
/* No comment provided by engineer. */
"In-call sounds" = "Sons d'appel";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Incognito" = "Incognito"; "Incognito" = "Incognito";
@@ -2433,6 +2475,9 @@
/* notification */ /* notification */
"message received" = "message reçu"; "message received" = "message reçu";
/* No comment provided by engineer. */
"Message source remains private." = "La source du message reste privée.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Message text" = "Texte du message"; "Message text" = "Texte du message";
@@ -2517,6 +2562,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"More improvements are coming soon!" = "Plus d'améliorations à venir !"; "More improvements are coming soon!" = "Plus d'améliorations à venir !";
/* No comment provided by engineer. */
"More reliable network connection." = "Connexion réseau plus fiable.";
/* item status description */ /* item status description */
"Most likely this connection is deleted." = "Connexion probablement supprimée."; "Most likely this connection is deleted." = "Connexion probablement supprimée.";
@@ -2538,6 +2586,12 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Network & servers" = "Réseau et serveurs"; "Network & servers" = "Réseau et serveurs";
/* No comment provided by engineer. */
"Network connection" = "Connexion au réseau";
/* No comment provided by engineer. */
"Network management" = "Gestion du réseau";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Network settings" = "Paramètres réseau"; "Network settings" = "Paramètres réseau";
@@ -2616,6 +2670,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"No history" = "Aucun historique"; "No history" = "Aucun historique";
/* No comment provided by engineer. */
"No network connection" = "Pas de connexion au réseau";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"No permission to record voice message" = "Pas l'autorisation d'enregistrer un message vocal"; "No permission to record voice message" = "Pas l'autorisation d'enregistrer un message vocal";
@@ -2762,9 +2819,15 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Or show this code" = "Ou présenter ce code"; "Or show this code" = "Ou présenter ce code";
/* No comment provided by engineer. */
"Other" = "Autres";
/* member role */ /* member role */
"owner" = "propriétaire"; "owner" = "propriétaire";
/* feature role */
"owners" = "propriétaires";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Passcode" = "Code d'accès"; "Passcode" = "Code d'accès";
@@ -2897,6 +2960,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Profile image" = "Image de profil"; "Profile image" = "Image de profil";
/* No comment provided by engineer. */
"Profile images" = "Images de profil";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Profile name" = "Nom du profil"; "Profile name" = "Nom du profil";
@@ -2930,6 +2996,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Prohibit sending files and media." = "Interdire l'envoi de fichiers et de médias."; "Prohibit sending files and media." = "Interdire l'envoi de fichiers et de médias.";
/* No comment provided by engineer. */
"Prohibit sending SimpleX links." = "Interdire l'envoi de liens SimpleX.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Prohibit sending voice messages." = "Interdire l'envoi de messages vocaux."; "Prohibit sending voice messages." = "Interdire l'envoi de messages vocaux.";
@@ -3008,6 +3077,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Receiving address will be changed to a different server. Address change will complete after sender comes online." = "L'adresse de réception sera changée pour un autre serveur. Le changement d'adresse sera terminé lorsque l'expéditeur sera en ligne."; "Receiving address will be changed to a different server. Address change will complete after sender comes online." = "L'adresse de réception sera changée pour un autre serveur. Le changement d'adresse sera terminé lorsque l'expéditeur sera en ligne.";
/* No comment provided by engineer. */
"Receiving concurrency" = "Réception simultanée";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Receiving file will be stopped." = "La réception du fichier sera interrompue."; "Receiving file will be stopped." = "La réception du fichier sera interrompue.";
@@ -3017,6 +3089,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Recent history and improved [directory bot](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion)." = "Historique récent et amélioration du [bot annuaire](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion)."; "Recent history and improved [directory bot](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion)." = "Historique récent et amélioration du [bot annuaire](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion).";
/* No comment provided by engineer. */
"Recipient(s) can't see who this message is from." = "Le(s) destinataire(s) ne peut(vent) pas voir de qui provient ce message.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Recipients see updates as you type them." = "Les destinataires voient les mises à jour au fur et à mesure que vous leur écrivez."; "Recipients see updates as you type them." = "Les destinataires voient les mises à jour au fur et à mesure que vous leur écrivez.";
@@ -3212,6 +3287,18 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Save welcome message?" = "Enregistrer le message d'accueil?"; "Save welcome message?" = "Enregistrer le message d'accueil?";
/* No comment provided by engineer. */
"saved" = "enregistré";
/* No comment provided by engineer. */
"Saved" = "Enregistré";
/* No comment provided by engineer. */
"Saved from" = "Enregistré depuis";
/* No comment provided by engineer. */
"saved from %@" = "enregistré à partir de %@";
/* message info title */ /* message info title */
"Saved message" = "Message enregistré"; "Saved message" = "Message enregistré";
@@ -3419,6 +3506,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Settings" = "Paramètres"; "Settings" = "Paramètres";
/* No comment provided by engineer. */
"Shape profile images" = "Images de profil modelable";
/* chat item action */ /* chat item action */
"Share" = "Partager"; "Share" = "Partager";
@@ -3479,6 +3569,12 @@
/* chat feature */ /* chat feature */
"SimpleX links" = "Liens SimpleX"; "SimpleX links" = "Liens SimpleX";
/* No comment provided by engineer. */
"SimpleX links are prohibited in this group." = "Les liens SimpleX sont interdits dans ce groupe.";
/* No comment provided by engineer. */
"SimpleX links not allowed" = "Les liens SimpleX ne sont pas autorisés";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"SimpleX Lock" = "SimpleX Lock"; "SimpleX Lock" = "SimpleX Lock";
@@ -3515,6 +3611,9 @@
/* notification title */ /* notification title */
"Somebody" = "Quelqu'un"; "Somebody" = "Quelqu'un";
/* No comment provided by engineer. */
"Square, circle, or anything in between." = "Carré, circulaire, ou toute autre forme intermédiaire.";
/* chat item text */ /* chat item text */
"standard end-to-end encryption" = "chiffrement de bout en bout standard"; "standard end-to-end encryption" = "chiffrement de bout en bout standard";
@@ -4043,6 +4142,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Voice messages are prohibited in this group." = "Les messages vocaux sont interdits dans ce groupe."; "Voice messages are prohibited in this group." = "Les messages vocaux sont interdits dans ce groupe.";
/* No comment provided by engineer. */
"Voice messages not allowed" = "Les messages vocaux ne sont pas autorisés";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Voice messages prohibited!" = "Messages vocaux interdits !"; "Voice messages prohibited!" = "Messages vocaux interdits !";
@@ -4094,12 +4196,24 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"When available" = "Quand disponible"; "When available" = "Quand disponible";
/* No comment provided by engineer. */
"When connecting audio and video calls." = "Lors des appels audio et vidéo.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"When people request to connect, you can accept or reject it." = "Vous pouvez accepter ou refuser les demandes de contacts."; "When people request to connect, you can accept or reject it." = "Vous pouvez accepter ou refuser les demandes de contacts.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Lorsque vous partagez un profil incognito avec quelqu'un, ce profil sera utilisé pour les groupes auxquels il vous invite."; "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Lorsque vous partagez un profil incognito avec quelqu'un, ce profil sera utilisé pour les groupes auxquels il vous invite.";
/* No comment provided by engineer. */
"WiFi" = "WiFi";
/* No comment provided by engineer. */
"Will be enabled in direct chats!" = "Activé dans les discussions directes!";
/* No comment provided by engineer. */
"Wired ethernet" = "Ethernet câblé";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"With encrypted files and media." = "Avec les fichiers et les médias chiffrés."; "With encrypted files and media." = "Avec les fichiers et les médias chiffrés.";
@@ -4121,6 +4235,9 @@
/* pref value */ /* pref value */
"yes" = "oui"; "yes" = "oui";
/* No comment provided by engineer. */
"you" = "vous";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You" = "Vous"; "You" = "Vous";
+102 -93
View File
@@ -50,7 +50,7 @@
"(new)" = "(új)"; "(new)" = "(új)";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"(this device v%@)" = "(ez az eszköz v%@)"; "(this device v%@)" = "(ez az eszköz: v%@)";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
")" = ")"; ")" = ")";
@@ -65,7 +65,7 @@
"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Csillag a GitHubon](https://github.com/simplex-chat/simplex-chat)"; "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Csillag a GitHubon](https://github.com/simplex-chat/simplex-chat)";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"**Add contact**: to create a new invitation link, or connect via a link you received." = "**Ismerős hozzáadása**: új meghívó hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő csatlakozáshoz."; "**Add contact**: to create a new invitation link, or connect via a link you received." = "**Ismerős hozzáadása**: új meghívó hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő kapcsolódáshoz.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"**Add new contact**: to create your one-time QR Code for your contact." = "**Új ismerős hozzáadása**: egyszer használatos QR-kód vagy hivatkozás létrehozása a kapcsolattartóhoz."; "**Add new contact**: to create your one-time QR Code for your contact." = "**Új ismerős hozzáadása**: egyszer használatos QR-kód vagy hivatkozás létrehozása a kapcsolattartóhoz.";
@@ -134,19 +134,19 @@
"%@ and %@" = "%@ és %@"; "%@ and %@" = "%@ és %@";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"%@ and %@ connected" = "%@ és %@ csatlakozott"; "%@ and %@ connected" = "%@ és %@ kapcsolódott";
/* copied message info, <sender> at <time> */ /* copied message info, <sender> at <time> */
"%@ at %@:" = "%1$@ %2$@-kor:"; "%@ at %@:" = "%1$@ ekkor: %2$@";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"%@ connected" = "%@ csatlakozott"; "%@ connected" = "%@ kapcsolódott";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"%@ downloaded" = "%@ letöltve"; "%@ downloaded" = "%@ letöltve";
/* notification title */ /* notification title */
"%@ is connected!" = "%@ csatlakozott!"; "%@ is connected!" = "%@ kapcsolódott!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"%@ is not verified" = "%@ nem ellenőrzött"; "%@ is not verified" = "%@ nem ellenőrzött";
@@ -161,13 +161,13 @@
"%@ uploaded" = "%@ feltöltve"; "%@ uploaded" = "%@ feltöltve";
/* notification title */ /* notification title */
"%@ wants to connect!" = "%@ csatlakozni szeretne!"; "%@ wants to connect!" = "%@ kapcsolódni szeretne!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"%@, %@ and %lld members" = "%@, %@ és további %lld tag"; "%@, %@ and %lld members" = "%@, %@ és további %lld tag";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"%@, %@ and %lld other members connected" = "%@, %@ és további %lld tag csatlakozott"; "%@, %@ and %lld other members connected" = "%@, %@ és további %lld tag kapcsolódott";
/* copied message info */ /* copied message info */
"%@:" = "%@:"; "%@:" = "%@:";
@@ -357,7 +357,7 @@
"accepted call" = "elfogadott hívás"; "accepted call" = "elfogadott hívás";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Azonosító hozzáadása a profilhoz, hogy az ismerősök megoszthassák másokkal. A profilfrissítés elküldésre kerül ismerősők számára."; "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Azonosító hozzáadása a profilhoz, hogy az ismerősei megoszthassák másokkal. A profilfrissítés elküldésre kerül az ismerősei számára.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Add contact" = "Ismerős hozzáadása"; "Add contact" = "Ismerős hozzáadása";
@@ -396,7 +396,7 @@
"Admins can block a member for all." = "Az adminok egy tagot mindenki számára letilthatnak."; "Admins can block a member for all." = "Az adminok egy tagot mindenki számára letilthatnak.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Admins can create the links to join groups." = "Az adminok hivatkozásokat hozhatnak létre a csoportokhoz való csatlakozáshoz."; "Admins can create the links to join groups." = "Az adminok hivatkozásokat hozhatnak létre a csoportokhoz való kapcsolódáshoz.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Advanced network settings" = "Speciális hálózati beállítások"; "Advanced network settings" = "Speciális hálózati beállítások";
@@ -417,7 +417,7 @@
"All data is erased when it is entered." = "A jelkód megadása után minden adat törlésre kerül."; "All data is erased when it is entered." = "A jelkód megadása után minden adat törlésre kerül.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"All group members will remain connected." = "Minden csoporttag csatlakoztatva marad."; "All group members will remain connected." = "Minden csoporttag kapcsolódva marad.";
/* feature role */ /* feature role */
"all members" = "minden tag"; "all members" = "minden tag";
@@ -432,7 +432,7 @@
"All new messages from %@ will be hidden!" = "Minden új üzenet elrejtésre kerül tőle: %@!"; "All new messages from %@ will be hidden!" = "Minden új üzenet elrejtésre kerül tőle: %@!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"All your contacts will remain connected." = "Minden ismerős csatlakoztatva marad."; "All your contacts will remain connected." = "Minden ismerős kapcsolódva marad.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"All your contacts will remain connected. Profile update will be sent to your contacts." = "Ismerőseivel kapcsolatban marad. A profil változtatások frissítésre kerülnek az ismerősöknél."; "All your contacts will remain connected. Profile update will be sent to your contacts." = "Ismerőseivel kapcsolatban marad. A profil változtatások frissítésre kerülnek az ismerősöknél.";
@@ -498,13 +498,13 @@
"Allow your contacts to send voice messages." = "Hangüzenetek küldésének engedélyezése ismerősök számára."; "Allow your contacts to send voice messages." = "Hangüzenetek küldésének engedélyezése ismerősök számára.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Already connected?" = "Csatlakoztatva?"; "Already connected?" = "Már kapcsolódott?";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Already connecting!" = "Kapcsolódás folyamatban!"; "Already connecting!" = "Kapcsolódás folyamatban!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Already joining the group!" = "Csatlakozás folyamatban!"; "Already joining the group!" = "A csatlakozás folyamatban van a csoporthoz!";
/* pref value */ /* pref value */
"always" = "mindig"; "always" = "mindig";
@@ -582,7 +582,7 @@
"Authentication failed" = "Sikertelen hitelesítés"; "Authentication failed" = "Sikertelen hitelesítés";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Authentication is required before the call is connected, but you may miss calls." = "A hívás csatlakoztatása előtt hitelesítésre van szükség, de előfordulhat, hogy nem tud hívásokat fogadni."; "Authentication is required before the call is connected, but you may miss calls." = "A hívás összekapcsolása előtt hitelesítésre van szükség, de előfordulhat, hogy lemarad a hívásokról.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Authentication unavailable" = "Hitelesítés elérhetetlen"; "Authentication unavailable" = "Hitelesítés elérhetetlen";
@@ -702,10 +702,10 @@
"Can't invite contacts!" = "Ismerősök meghívása nem lehetséges!"; "Can't invite contacts!" = "Ismerősök meghívása nem lehetséges!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Cancel" = "Megszakítás"; "Cancel" = "Mégse";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Cancel migration" = "Átköltöztetés megszakítása"; "Cancel migration" = "Átköltöztetés visszavonása";
/* feature offered item */ /* feature offered item */
"cancelled %@" = "%@ törölve"; "cancelled %@" = "%@ törölve";
@@ -754,7 +754,7 @@
"changed address for you" = "cím megváltoztatva"; "changed address for you" = "cím megváltoztatva";
/* rcv group event chat item */ /* rcv group event chat item */
"changed role of %@ to %@" = "%1$@ szerepköre megváltozott erre: %2$@"; "changed role of %@ to %@" = "%1$@ szerepkörét megváltoztatta erre: %2$@";
/* rcv group event chat item */ /* rcv group event chat item */
"changed your role to %@" = "megváltoztatta a szerepkörét erre: %@"; "changed your role to %@" = "megváltoztatta a szerepkörét erre: %@";
@@ -883,7 +883,7 @@
"Connect to desktop" = "Kapcsolódás számítógéphez"; "Connect to desktop" = "Kapcsolódás számítógéphez";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"connect to SimpleX Chat developers." = "Csatlakozás a SimpleX Chat fejlesztőkhöz."; "connect to SimpleX Chat developers." = "Kapcsolódás a SimpleX Chat fejlesztőkhöz.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Connect to yourself?" = "Kapcsolódás saját magához?"; "Connect to yourself?" = "Kapcsolódás saját magához?";
@@ -895,13 +895,13 @@
"Connect to yourself?\nThis is your own SimpleX address!" = "Kapcsolódás saját magához?\nEz a SimpleX azonosítója!"; "Connect to yourself?\nThis is your own SimpleX address!" = "Kapcsolódás saját magához?\nEz a SimpleX azonosítója!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Connect via contact address" = "Csatlakozás ismerős azonosítója által"; "Connect via contact address" = "Kapcsolódás a kapcsolattartási azonosítón keresztül";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Connect via link" = "Csatlakozás egy hivatkozáson keresztül"; "Connect via link" = "Kapcsolódás egy hivatkozáson keresztül";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Connect via one-time link" = "Csatlakozás egyszer használatos hivatkozáson keresztül"; "Connect via one-time link" = "Kapcsolódás egyszer használatos hivatkozáson keresztül";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Connect with %@" = "Kapcsolódás ezzel: %@"; "Connect with %@" = "Kapcsolódás ezzel: %@";
@@ -931,7 +931,7 @@
"connecting (introduced)" = "kapcsolódás (bejelentve)"; "connecting (introduced)" = "kapcsolódás (bejelentve)";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"connecting (introduction invitation)" = "csatlakozás (bemutatkozás meghívás)"; "connecting (introduction invitation)" = "kapcsolódás (bemutatkozó meghívó)";
/* call status */ /* call status */
"connecting call" = "hívás kapcsolódik…"; "connecting call" = "hívás kapcsolódik…";
@@ -973,7 +973,7 @@
"connection:%@" = "kapcsolat: %@"; "connection:%@" = "kapcsolat: %@";
/* profile update event chat item */ /* profile update event chat item */
"contact %@ changed to %@" = "%1$@ ismerősének neve megváltozott erre: %2$@"; "contact %@ changed to %@" = "%1$@ megváltoztatta a nevét erre: %2$@";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Contact allows" = "Ismerős engedélyezi"; "Contact allows" = "Ismerős engedélyezi";
@@ -991,10 +991,10 @@
"Contact hidden:" = "Ismerős elrejtve:"; "Contact hidden:" = "Ismerős elrejtve:";
/* notification */ /* notification */
"Contact is connected" = "Ismerős csatlakozott"; "Contact is connected" = "Ismerőse kapcsolódott";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Contact is not connected yet!" = "Az ismerős még nem csatlakozott!"; "Contact is not connected yet!" = "Az ismerőse még nem kapcsolódott!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Contact name" = "Ismerős neve"; "Contact name" = "Ismerős neve";
@@ -1750,7 +1750,7 @@
"Error starting chat" = "Hiba a csevegés elindításakor"; "Error starting chat" = "Hiba a csevegés elindításakor";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Error stopping chat" = "Hiba a csevegés megszakításakor"; "Error stopping chat" = "Hiba a csevegés megállításakor";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Error switching profile!" = "Hiba a profil váltásakor!"; "Error switching profile!" = "Hiba a profil váltásakor!";
@@ -1831,10 +1831,10 @@
"File will be deleted from servers." = "A fájl törölve lesz a kiszolgálóról."; "File will be deleted from servers." = "A fájl törölve lesz a kiszolgálóról.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"File will be received when your contact completes uploading it." = "A fájl akkor érkezik meg, amikor ismerőse befejezte annak feltöltést."; "File will be received when your contact completes uploading it." = "A fájl akkor érkezik meg, amikor a küldője befejezte annak feltöltését.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"File will be received when your contact is online, please wait or check later!" = "A fájl akkor érkezik meg, amint ismerőse online lesz, várjon, vagy ellenőrizze később!"; "File will be received when your contact is online, please wait or check later!" = "A fájl akkor érkezik meg, amikor a küldője elérhető lesz, várjon, vagy ellenőrizze később!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"File: %@" = "Fájl: %@"; "File: %@" = "Fájl: %@";
@@ -2086,10 +2086,10 @@
"Ignore" = "Figyelmen kívül hagyás"; "Ignore" = "Figyelmen kívül hagyás";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Image will be received when your contact completes uploading it." = "A kép akkor érkezik meg, amikor ismerőse befejezte annak feltöltését."; "Image will be received when your contact completes uploading it." = "A kép akkor érkezik meg, amikor a küldője befejezte annak feltöltését.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Image will be received when your contact is online, please wait or check later!" = "A kép akkor érkezik meg, amikor ismerős elérhető lesz, várjon vagy ellenőrizze később!"; "Image will be received when your contact is online, please wait or check later!" = "A kép akkor érkezik meg, amikor a küldője elérhető lesz, várjon, vagy ellenőrizze később!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Immediately" = "Azonnal"; "Immediately" = "Azonnal";
@@ -2281,7 +2281,7 @@
"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. Az üzenet visszafejtése sikertelen volt, mert vagy az ismerőse 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. Az üzenet visszafejtése sikertelen volt, mert vagy az ismerőse régebbi adatbázis biztonsági mentést használt.\n3. A kapcsolat sérült.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"It seems like you are already connected via this link. If it is not the case, there was an error (%@)." = "Úgy tűnik, már csatlakozott ezen a hivatkozáson keresztül. Ha ez nem így van, akkor hiba történt (%@)."; "It seems like you are already connected via this link. If it is not the case, there was an error (%@)." = "Úgy tűnik, már kapcsolódott ezen a hivatkozáson keresztül. Ha ez nem így van, akkor hiba történt (%@).";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Italian interface" = "Olasz kezelőfelület"; "Italian interface" = "Olasz kezelőfelület";
@@ -2296,7 +2296,7 @@
"Join" = "Csatlakozás"; "Join" = "Csatlakozás";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"join as %@" = "csatlakozás mint %@"; "join as %@" = "csatlakozás mint: %@";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Join group" = "Csatlakozás csoporthoz"; "Join group" = "Csatlakozás csoporthoz";
@@ -2440,10 +2440,10 @@
"Member" = "Tag"; "Member" = "Tag";
/* profile update event chat item */ /* profile update event chat item */
"member %@ changed to %@" = "%1$@ tag megváltoztatta a nevét erre: %2$@"; "member %@ changed to %@" = "%1$@ megváltoztatta a nevét erre: %2$@";
/* rcv group event chat item */ /* rcv group event chat item */
"member connected" = "kapcsolódva"; "member connected" = "kapcsolódott";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Member role will be changed to \"%@\". All group members will be notified." = "A tag szerepköre meg fog változni erre: \"%@\". A csoport minden tagja értesítést kap róla."; "Member role will be changed to \"%@\". All group members will be notified." = "A tag szerepköre meg fog változni erre: \"%@\". A csoport minden tagja értesítést kap róla.";
@@ -2730,13 +2730,13 @@
"One-time invitation link" = "Egyszer használatos meghívó hivatkozás"; "One-time invitation link" = "Egyszer használatos meghívó hivatkozás";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Onion hosts will be required for connection. Requires enabling VPN." = "A csatlakozáshoz Onion host-okra lesz szükség. VPN engedélyezése szükséges."; "Onion hosts will be required for connection. Requires enabling VPN." = "A kapcsolódáshoz Onion kiszolgálókra lesz szükség. VPN engedélyezése szükséges.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Onion hosts will be used when available. Requires enabling VPN." = "Onion host-ok használata, ha azok rendelkezésre állnak. VPN engedélyezése szükséges."; "Onion hosts will be used when available. Requires enabling VPN." = "Onion kiszolgálók használata, ha azok rendelkezésre állnak. VPN engedélyezése szükséges.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Onion hosts will not be used." = "Onion host-ok nem lesznek használva."; "Onion hosts will not be used." = "Onion kiszolgálók nem lesznek használva.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Csak a klienseszközök tárolják a felhasználói profilokat, névjegyeket, csoportokat és a **2 rétegű végponttól-végpontig titkosítással** küldött üzeneteket."; "Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Csak a klienseszközök tárolják a felhasználói profilokat, névjegyeket, csoportokat és a **2 rétegű végponttól-végpontig titkosítással** küldött üzeneteket.";
@@ -2856,7 +2856,7 @@
"Paste image" = "Kép beillesztése"; "Paste image" = "Kép beillesztése";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Paste link to connect!" = "Hivatkozás beillesztése a csatlakozáshoz!"; "Paste link to connect!" = "Hivatkozás beillesztése a kapcsolódáshoz!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Paste the link you received" = "Fogadott hivatkozás beillesztése"; "Paste the link you received" = "Fogadott hivatkozás beillesztése";
@@ -2960,6 +2960,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Profile image" = "Profilkép"; "Profile image" = "Profilkép";
/* No comment provided by engineer. */
"Profile images" = "Profilképek";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Profile name" = "Profilnév"; "Profile name" = "Profilnév";
@@ -3093,10 +3096,10 @@
"Recipients see updates as you type them." = "A címzettek a beírás közben látják a frissítéseket."; "Recipients see updates as you type them." = "A címzettek a beírás közben látják a frissítéseket.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Reconnect all connected servers to force message delivery. It uses additional traffic." = "Az összes csatlakoztatott kiszolgáló újrakapcsolása az üzenetek kézbesítésének kikényszerítéséhez. Ez további forgalmat használ."; "Reconnect all connected servers to force message delivery. It uses additional traffic." = "Újrakapcsolódás az összes kiszolgálóhoz az üzenetek kézbesítésének kikényszerítéséhez. Ez további forgalmat használ.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Reconnect servers?" = "Kiszolgálók újracsatlakoztatása?"; "Reconnect servers?" = "Újrakapcsolódás a kiszolgálókhoz?";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Record updated at" = "A bejegyzés frissítve"; "Record updated at" = "A bejegyzés frissítve";
@@ -3144,7 +3147,7 @@
"removed %@" = "%@ eltávolítva"; "removed %@" = "%@ eltávolítva";
/* profile update event chat item */ /* profile update event chat item */
"removed contact address" = "törölt csatlakozási cím"; "removed contact address" = "törölt kapcsolattartási azonosító";
/* profile update event chat item */ /* profile update event chat item */
"removed profile picture" = "törölt profilkép"; "removed profile picture" = "törölt profilkép";
@@ -3345,7 +3348,7 @@
"Security code" = "Biztonsági kód"; "Security code" = "Biztonsági kód";
/* chat item text */ /* chat item text */
"security code changed" = "biztonsági kód megváltozott"; "security code changed" = "a biztonsági kód megváltozott";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Select" = "Választás"; "Select" = "Választás";
@@ -3378,7 +3381,7 @@
"Send direct message" = "Közvetlen üzenet küldése"; "Send direct message" = "Közvetlen üzenet küldése";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Send direct message to connect" = "Közvetlen üzenet küldése a csatlakozáshoz"; "Send direct message to connect" = "Közvetlen üzenet küldése a kapcsolódáshoz";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Send disappearing message" = "Eltűnő üzenet küldése"; "Send disappearing message" = "Eltűnő üzenet küldése";
@@ -3480,7 +3483,7 @@
"Set it instead of system authentication." = "Rendszerhitelesítés helyetti beállítás."; "Set it instead of system authentication." = "Rendszerhitelesítés helyetti beállítás.";
/* profile update event chat item */ /* profile update event chat item */
"set new contact address" = "új kapcsolattartási cím beállítása"; "set new contact address" = "új kapcsolattartási azonosító beállítása";
/* profile update event chat item */ /* profile update event chat item */
"set new profile picture" = "új profilkép beállítása"; "set new profile picture" = "új profilkép beállítása";
@@ -3503,6 +3506,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Settings" = "Beállítások"; "Settings" = "Beállítások";
/* No comment provided by engineer. */
"Shape profile images" = "Profilkép alakzat";
/* chat item action */ /* chat item action */
"Share" = "Megosztás"; "Share" = "Megosztás";
@@ -3513,7 +3519,7 @@
"Share address" = "Azonosító megosztása"; "Share address" = "Azonosító megosztása";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Share address with contacts?" = "Megosztja az azonosítót az ismerősökkel?"; "Share address with contacts?" = "Megosztja az azonosítót az ismerőseivel?";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Share link" = "Hivatkozás megosztása"; "Share link" = "Hivatkozás megosztása";
@@ -3552,7 +3558,7 @@
"SimpleX Chat security was audited by Trail of Bits." = "A SimpleX Chat biztonsága a Trail of Bits által lett auditálva."; "SimpleX Chat security was audited by Trail of Bits." = "A SimpleX Chat biztonsága a Trail of Bits által lett auditálva.";
/* simplex link type */ /* simplex link type */
"SimpleX contact address" = "SimpleX ismerős azonosítója"; "SimpleX contact address" = "SimpleX kapcsolattartási azonosító";
/* notification */ /* notification */
"SimpleX encrypted message or connection event" = "SimpleX titkosított üzenet vagy kapcsolati esemény"; "SimpleX encrypted message or connection event" = "SimpleX titkosított üzenet vagy kapcsolati esemény";
@@ -3605,6 +3611,9 @@
/* notification title */ /* notification title */
"Somebody" = "Valaki"; "Somebody" = "Valaki";
/* No comment provided by engineer. */
"Square, circle, or anything in between." = "Négyzet, kör vagy bármi a kettő között.";
/* chat item text */ /* chat item text */
"standard end-to-end encryption" = "szabványos végpontok közötti titkosítás"; "standard end-to-end encryption" = "szabványos végpontok közötti titkosítás";
@@ -3621,40 +3630,40 @@
"starting…" = "indítás…"; "starting…" = "indítás…";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Stop" = "Megszakítás"; "Stop" = "Megállítás";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Stop chat" = "Csevegési szolgáltatás megszakítása"; "Stop chat" = "Csevegési szolgáltatás megállítása";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Stop chat to enable database actions" = "Csevegés megszakítása az adatbázis-műveletek engedélyezéséhez"; "Stop chat to enable database actions" = "Csevegés megállítása az adatbázis-műveletek engedélyezéséhez";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." = "A csevegés megszakítása a csevegőadatbázis exportálásához, importálásához vagy törléséhez. A csevegés megszakítása alatt nem tud üzeneteket fogadni és küldeni."; "Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." = "A csevegés megállítása a csevegő adatbázis exportálásához, importálásához vagy törléséhez. A csevegés megállítása alatt nem tud üzeneteket fogadni és küldeni.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Stop chat?" = "Csevegési szolgáltatás megszakítása?"; "Stop chat?" = "Csevegési szolgáltatás megállítása?";
/* cancel file action */ /* cancel file action */
"Stop file" = "Fájl megszakítása"; "Stop file" = "Fájl megállítása";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Stop receiving file?" = "Fájl fogadás megszakítása?"; "Stop receiving file?" = "Fájl fogadás megállítása?";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Stop sending file?" = "Fájl küldés megszakítása?"; "Stop sending file?" = "Fájl küldés megállítása?";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Stop sharing" = "Megosztás megszakítása"; "Stop sharing" = "Megosztás megállítása";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Stop sharing address?" = "Címmegosztás megszakítása?"; "Stop sharing address?" = "Címmegosztás megállítása?";
/* authentication reason */ /* authentication reason */
"Stop SimpleX" = "A SimpleX megszakítása"; "Stop SimpleX" = "SimpleX megállítása";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Stopping chat" = "Csevegés megszakítása"; "Stopping chat" = "Csevegés megállítása folyamatban";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"strike" = "áthúzott"; "strike" = "áthúzott";
@@ -3681,7 +3690,7 @@
"Tap to activate profile." = "A profil aktiválásához koppintson az ikonra."; "Tap to activate profile." = "A profil aktiválásához koppintson az ikonra.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Tap to Connect" = "Koppintson a csatlakozáshoz"; "Tap to Connect" = "Koppintson a kapcsolódáshoz";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Tap to join" = "Koppintson a csatlakozáshoz"; "Tap to join" = "Koppintson a csatlakozáshoz";
@@ -3747,7 +3756,7 @@
"The connection you accepted will be cancelled!" = "Az ön által elfogadott kapcsolat megszakad!"; "The connection you accepted will be cancelled!" = "Az ön által elfogadott kapcsolat megszakad!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"The contact you shared this link with will NOT be able to connect!" = "Ismerőse NEM fog tudni csatlakozni, akivel megosztotta ezt a hivatkozást!"; "The contact you shared this link with will NOT be able to connect!" = "Ismerőse, akivel megosztotta ezt a hivatkozást, NEM fog tudni kapcsolódni!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"The created archive is available via app Settings / Database / Old database archive." = "A létrehozott archívum a Beállítások / Adatbázis / Régi adatbázis-archívum menüpontban érhető el."; "The created archive is available via app Settings / Database / Old database archive." = "A létrehozott archívum a Beállítások / Adatbázis / Régi adatbázis-archívum menüpontban érhető el.";
@@ -3774,7 +3783,7 @@
"The old database was not removed during the migration, it can be deleted." = "A régi adatbázis nem került eltávolításra az átköltöztetés során, így törölhető."; "The old database was not removed during the migration, it can be deleted." = "A régi adatbázis nem került eltávolításra az átköltöztetés során, így törölhető.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"The profile is only shared with your contacts." = "Profilja csak az ismerősök számára kerül megosztásra."; "The profile is only shared with your contacts." = "Profilja csak az ismerőseivel kerül megosztásra.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"The second tick we missed! ✅" = "A második jelölés, amit kihagytunk! ✅"; "The second tick we missed! ✅" = "A második jelölés, amit kihagytunk! ✅";
@@ -3840,7 +3849,7 @@
"To ask any questions and to receive updates:" = "Bármilyen kérdés feltevéséhez és a frissítésekért:"; "To ask any questions and to receive updates:" = "Bármilyen kérdés feltevéséhez és a frissítésekért:";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"To connect, your contact can scan QR code or use the link in the app." = "A csatlakozáshoz az ismerős beolvashatja a QR-kódot, vagy használhatja az alkalmazásban található hivatkozást."; "To connect, your contact can scan QR code or use the link in the app." = "A kapcsolódáshoz az ismerőse beolvashatja a QR-kódot, vagy használhatja az alkalmazásban található hivatkozást.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"To hide unwanted messages." = "Kéretlen üzenetek elrejtése."; "To hide unwanted messages." = "Kéretlen üzenetek elrejtése.";
@@ -3870,16 +3879,16 @@
"To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "A végpontok közötti titkosítás ellenőrzéséhez ismerősével hasonlítsa össze (vagy szkennelje be) az eszközén lévő kódot."; "To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "A végpontok közötti titkosítás ellenőrzéséhez ismerősével hasonlítsa össze (vagy szkennelje be) az eszközén lévő kódot.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Toggle incognito when connecting." = "Inkognító mód csatlakozáskor."; "Toggle incognito when connecting." = "Inkognitó mód kapcsolódáskor.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Transport isolation" = "Kapcsolat izolációs mód"; "Transport isolation" = "Kapcsolat izolációs mód";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Trying to connect to the server used to receive messages from this contact (error: %@)." = "Csatlakozási kísérlet a kapcsolat üzeneteinek fogadására használt kiszolgálóhoz (hiba: %@)."; "Trying to connect to the server used to receive messages from this contact (error: %@)." = "Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott ismerőstől érkező üzenetek fogadására szolgál (hiba: %@).";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Trying to connect to the server used to receive messages from this contact." = "Csatlakozási kísérlet a kapcsolat üzeneteinek fogadására használt kiszolgálóhoz ettől az ismerőstől."; "Trying to connect to the server used to receive messages from this contact." = "Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott ismerőstől érkező üzenetek fogadására szolgál.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Turkish interface" = "Török kezelőfelület"; "Turkish interface" = "Török kezelőfelület";
@@ -3951,7 +3960,7 @@
"Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions." = "Hacsak nem az iOS hívási felületét használja, engedélyezze a Ne zavarjanak módot a megszakítások elkerülése érdekében."; "Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions." = "Hacsak nem az iOS hívási felületét használja, engedélyezze a Ne zavarjanak módot a megszakítások elkerülése érdekében.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection." = "Hacsak az ismerős nem törölte a kapcsolatot, vagy ez a hivatkozás már használatban volt, hiba lehet kérjük, jelentse.\nA csatlakozáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsolati hivatkozást, és ellenőrizze, hogy a hálózati kapcsolat stabil-e."; "Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection." = "Hacsak az ismerőse nem törölte a kapcsolatot, vagy ez a hivatkozás már használatban volt, lehet hogy ez egy hiba jelentse a problémát.\nA kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsolati hivatkozást, és ellenőrizze, hogy a hálózati kapcsolat stabil-e.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Unlink" = "Szétkapcsolás"; "Unlink" = "Szétkapcsolás";
@@ -3978,7 +3987,7 @@
"Update" = "Frissítés"; "Update" = "Frissítés";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Update .onion hosts setting?" = "Tor .onion host beállítások frissítése?"; "Update .onion hosts setting?" = "Tor .onion kiszolgálók beállításainak frissítése?";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Update database passphrase" = "Adatbázis jelmondat megváltoztatása"; "Update database passphrase" = "Adatbázis jelmondat megváltoztatása";
@@ -3999,7 +4008,7 @@
"Updating settings will re-connect the client to all servers." = "A beállítások frissítése a szerverekhez újra kapcsolódással jár."; "Updating settings will re-connect the client to all servers." = "A beállítások frissítése a szerverekhez újra kapcsolódással jár.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Updating this setting will re-connect the client to all servers." = "A beállítás frissítésével a kliens újracsatlakozik az összes kiszolgálóhoz."; "Updating this setting will re-connect the client to all servers." = "A beállítás frissítésével a kliens újrakapcsolódik az összes kiszolgálóhoz.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Upgrade and open chat" = "A csevegés frissítése és megnyitása"; "Upgrade and open chat" = "A csevegés frissítése és megnyitása";
@@ -4014,7 +4023,7 @@
"Uploading archive" = "Archívum feltöltése"; "Uploading archive" = "Archívum feltöltése";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Use .onion hosts" = "Tor .onion hostok használata"; "Use .onion hosts" = "Tor .onion kiszolgálók használata";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Use chat" = "Csevegés használata"; "Use chat" = "Csevegés használata";
@@ -4050,7 +4059,7 @@
"User profile" = "Felhasználói profil"; "User profile" = "Felhasználói profil";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Using .onion hosts requires compatible VPN provider." = "A .onion hosztok használatához kompatibilis VPN szolgáltatóra van szükség."; "Using .onion hosts requires compatible VPN provider." = "A .onion kiszolgálók használatához kompatibilis VPN szolgáltatóra van szükség.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Using SimpleX Chat servers." = "SimpleX Chat kiszolgálók használatban."; "Using SimpleX Chat servers." = "SimpleX Chat kiszolgálók használatban.";
@@ -4086,7 +4095,7 @@
"Via browser" = "Böngészőn keresztül"; "Via browser" = "Böngészőn keresztül";
/* chat list item description */ /* chat list item description */
"via contact address link" = "ismerős azonosítójának hivatkozásán keresztül"; "via contact address link" = "kapcsolattartási azonosító-hivatkozáson keresztül";
/* chat list item description */ /* chat list item description */
"via group link" = "csoport hivatkozáson keresztül"; "via group link" = "csoport hivatkozáson keresztül";
@@ -4107,10 +4116,10 @@
"video call (not e2e encrypted)" = "videóhívás (nem e2e titkosított)"; "video call (not e2e encrypted)" = "videóhívás (nem e2e titkosított)";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Video will be received when your contact completes uploading it." = "A videó akkor érkezik meg, amikor az ismerőse befejezte annak feltöltését."; "Video will be received when your contact completes uploading it." = "A videó akkor érkezik meg, amikor a küldője befejezte annak feltöltését.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Video will be received when your contact is online, please wait or check later!" = "A videó akkor érkezik meg, amikor az ismerős elérhető, várjon, vagy ellenőrizze később!"; "Video will be received when your contact is online, please wait or check later!" = "A videó akkor érkezik meg, amikor a küldője elérhető lesz, várjon, vagy ellenőrizze később!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Videos and files up to 1gb" = "Videók és fájlok 1Gb méretig"; "Videos and files up to 1gb" = "Videók és fájlok 1Gb méretig";
@@ -4191,7 +4200,7 @@
"When connecting audio and video calls." = "Amikor egy bejövő hang- vagy videóhívás érkezik."; "When connecting audio and video calls." = "Amikor egy bejövő hang- vagy videóhívás érkezik.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"When people request to connect, you can accept or reject it." = "Csatlakozási kérelmek esetében, elfogadhatja vagy elutasíthatja azokat."; "When people request to connect, you can accept or reject it." = "Amikor az emberek kapcsolódást kérelmeznek, ön elfogadhatja vagy elutasíthatja azokat.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Inkognitóprofil megosztása esetén a rendszer azt a profilt fogja használni azokhoz a csoportokhoz, amelyekbe meghívást kapott."; "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Inkognitóprofil megosztása esetén a rendszer azt a profilt fogja használni azokhoz a csoportokhoz, amelyekbe meghívást kapott.";
@@ -4248,28 +4257,28 @@
"You are already connected to %@." = "Már kapcsolódva van hozzá: %@."; "You are already connected to %@." = "Már kapcsolódva van hozzá: %@.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You are already connecting to %@." = "Már folyamatban van a csatlakozás ehhez: %@."; "You are already connecting to %@." = "Már folyamatban van a kapcsolódás ehhez: %@.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You are already connecting via this one-time link!" = "Már csatlakozik ezen az egyszer használatos hivatkozáson keresztül!"; "You are already connecting via this one-time link!" = "A kapcsolódás már folyamatban van ezen az egyszer használatos hivatkozáson keresztül!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You are already in group %@." = "Már a(z) %@ csoport tagja."; "You are already in group %@." = "Már a(z) %@ csoport tagja.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You are already joining the group %@." = "Már folyamatban van a csatlakozás a csoporthoz %@."; "You are already joining the group %@." = "A csatlakozás már folyamatban van a(z) %@ csoporthoz.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You are already joining the group via this link!" = "Már csatlakozott a csoporthoz ezen a hivatkozáson keresztül!"; "You are already joining the group via this link!" = "A csatlakozás már folyamatban van a csoporthoz ezen a hivatkozáson keresztül!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You are already joining the group via this link." = "Ezen a hivatkozáson keresztül már csatlakozik a csoporthoz."; "You are already joining the group via this link." = "A csatlakozás már folyamatban van a csoporthoz ezen a hivatkozáson keresztül.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You are already joining the group!\nRepeat join request?" = "Csatlakozás folyamatban!\nCsatlakozási kérés megismétlése?"; "You are already joining the group!\nRepeat join request?" = "Csatlakozás folyamatban!\nCsatlakozási kérés megismétlése?";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You are connected to the server used to receive messages from this contact." = "Kiszolgálóhoz történő csatlakozás, mely az adott ismerőstől érkező üzenetek fogadására szolgál."; "You are connected to the server used to receive messages from this contact." = "Már kapcsolódott ahhoz a kiszolgálóhoz, amely az adott ismerőstől érkező üzenetek fogadására szolgál.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"you are invited to group" = "meghívást kapott a csoportba"; "you are invited to group" = "meghívást kapott a csoportba";
@@ -4314,10 +4323,10 @@
"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." = "Megoszthat egy hivatkozást vagy QR-kódot - így bárki csatlakozhat a csoporthoz. Ha a csoport később törlésre kerül, akkor nem fogja elveszíteni annak tagjait."; "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." = "Megoszthat egy hivatkozást vagy QR-kódot - így bárki csatlakozhat a csoporthoz. Ha a csoport később törlésre kerül, akkor nem fogja elveszíteni annak tagjait.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You can share this address with your contacts to let them connect with **%@**." = "Megoszthatja ezt a hivatkozást ismerőseivel, hogy kapcsolatba léphessenek önnel a **%@** nevű profilján keresztül."; "You can share this address with your contacts to let them connect with **%@**." = "Megoszthatja ezt az azonosítót az ismerőseivel, hogy kapcsolatba léphessenek önnel a **%@** nevű profilján keresztül.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You can share your address as a link or QR code - anybody can connect to you." = "Megoszthatja azonosítóját hivatkozásként vagy QR-kódként így bárki csatlakozhat önhöz."; "You can share your address as a link or QR code - anybody can connect to you." = "Megoszthatja azonosítóját hivatkozásként vagy QR-kódként így bárki kapcsolódhat önhöz.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You can start chat via app Settings / Database or by restarting the app" = "A csevegést az alkalmazás Beállítások / Adatbázis menü segítségével vagy az alkalmazás újraindításával indíthatja el"; "You can start chat via app Settings / Database or by restarting the app" = "A csevegést az alkalmazás Beállítások / Adatbázis menü segítségével vagy az alkalmazás újraindításával indíthatja el";
@@ -4344,7 +4353,7 @@
"you changed role for yourself to %@" = "saját szerepkör megváltoztatva erre: %@"; "you changed role for yourself to %@" = "saját szerepkör megváltoztatva erre: %@";
/* snd group event chat item */ /* snd group event chat item */
"you changed role of %@ to %@" = "megváltoztatta %1$@ szerepkörét erre: %@"; "you changed role of %@ to %@" = "%1$@ szerepkörét megváltoztatta erre: %@";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You control through which server(s) **to receive** the messages, your contacts the servers you use to message them." = "Ön szabályozhatja, hogy mely kiszogál(ók)ón keresztül **kapja** az üzeneteket, az ismerősöket - az üzenetküldéshez használt szervereken."; "You control through which server(s) **to receive** the messages, your contacts the servers you use to message them." = "Ön szabályozhatja, hogy mely kiszogál(ók)ón keresztül **kapja** az üzeneteket, az ismerősöket - az üzenetküldéshez használt szervereken.";
@@ -4353,10 +4362,10 @@
"You could not be verified; please try again." = "Nem lehetett ellenőrizni; próbálja meg újra."; "You could not be verified; please try again." = "Nem lehetett ellenőrizni; próbálja meg újra.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You have already requested connection via this address!" = "Már kért egy csatlakozást ezen az azonosítón keresztül!"; "You have already requested connection via this address!" = "Már kért egy kapcsolódási kérelmet ezen az azonosítón keresztül!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You have already requested connection!\nRepeat connection request?" = "Már kérelmezte a csatlakozást!\nKapcsolódási kérés megismétlése?"; "You have already requested connection!\nRepeat connection request?" = "Már kért egy kapcsolódási kérelmet!\nKapcsolódási kérés megismétlése?";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You have no chats" = "Nincsenek csevegési üzenetek"; "You have no chats" = "Nincsenek csevegési üzenetek";
@@ -4401,22 +4410,22 @@
"you unblocked %@" = "feloldotta %@ blokkolását"; "you unblocked %@" = "feloldotta %@ blokkolását";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You will be connected to group when the group host's device is online, please wait or check later!" = "Akkor tud csatlakozni a csoporthoz, amikor a csoport tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később!"; "You will be connected to group when the group host's device is online, please wait or check later!" = "Akkor lesz kapcsolódva a csoporthoz, amikor a csoport tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You will be connected when group link host's device is online, please wait or check later!" = "Akkor lesz csatlakoztatva, amikor a csoportos hivatkozás tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később!"; "You will be connected when group link host's device is online, please wait or check later!" = "Akkor lesz kapcsolódva, amikor a csoportos hivatkozás tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You will be connected when your connection request is accepted, please wait or check later!" = "Akkor lesz csatlakoztatva, ha a csatlakozási kérelme elfogadásra került, várjon, vagy ellenőrizze később!"; "You will be connected when your connection request is accepted, please wait or check later!" = "Akkor lesz kapcsolódva, ha a kapcsolódási kérelme elfogadásra kerül, várjon, vagy ellenőrizze később!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You will be connected when your contact's device is online, please wait or check later!" = "Akkor csatlakozik, amikor az ismerős eszköze online lesz, várjon, vagy ellenőrizze később!"; "You will be connected when your contact's device is online, please wait or check later!" = "Akkor le kapcsolódva, amikor az ismerőse eszköze online lesz, várjon, vagy ellenőrizze később!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You will be required to authenticate when you start or resume the app after 30 seconds in background." = "Az alkalmazás indításakor, vagy 30 másodpercnyi háttérben töltött idő után az alkalmazáshoz visszatérve hitelesítés szükséges."; "You will be required to authenticate when you start or resume the app after 30 seconds in background." = "Az alkalmazás indításakor, vagy 30 másodpercnyi háttérben töltött idő után az alkalmazáshoz visszatérve hitelesítés szükséges.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You will connect to all group members." = "Csatlakozni fog a csoport összes tagjához."; "You will connect to all group members." = "Kapcsolódni fog a csoport összes tagjához.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You will still receive calls and notifications from muted profiles when they are active." = "Továbbra is kap hívásokat és értesítéseket a némított profiloktól, ha azok aktívak."; "You will still receive calls and notifications from muted profiles when they are active." = "Továbbra is kap hívásokat és értesítéseket a némított profiloktól, ha azok aktívak.";
@@ -4425,7 +4434,7 @@
"You will stop receiving messages from this group. Chat history will be preserved." = "Ettől a csoporttól nem fog értesítéseket kapni. A csevegési előzmények megmaradnak."; "You will stop receiving messages from this group. Chat history will be preserved." = "Ettől a csoporttól nem fog értesítéseket kapni. A csevegési előzmények megmaradnak.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You won't lose your contacts if you later delete your address." = "Nem veszíti el ismerőseit, ha később törli az azonosítóját."; "You won't lose your contacts if you later delete your address." = "Nem veszíti el az ismerőseit, ha később törli az azonosítóját.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"you: " = "ön: "; "you: " = "ön: ";
@@ -4461,7 +4470,7 @@
"Your contacts can allow full message deletion." = "Ismerősök engedélyezhetik a teljes üzenet törlést."; "Your contacts can allow full message deletion." = "Ismerősök engedélyezhetik a teljes üzenet törlést.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Your contacts will remain connected." = "Az ismerősök továbbra is csatlakoztatva maradnak."; "Your contacts will remain connected." = "Az ismerősei továbbra is kapcsolódva maradnak.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Your current chat database will be DELETED and REPLACED with the imported one." = "A jelenlegi csevegési adatbázis TÖRLŐDNI FOG, és a HELYÉRE az importált adatbázis kerül."; "Your current chat database will be DELETED and REPLACED with the imported one." = "A jelenlegi csevegési adatbázis TÖRLŐDNI FOG, és a HELYÉRE az importált adatbázis kerül.";
@@ -2,7 +2,7 @@
"CFBundleName" = "SimpleX"; "CFBundleName" = "SimpleX";
/* Privacy - Camera Usage Description */ /* Privacy - Camera Usage Description */
"NSCameraUsageDescription" = "A SimpleX-nek kamera-hozzáférésre van szüksége a QR-kódok beolvasásához, hogy csatlakozhasson más felhasználókhoz és videohívásokhoz."; "NSCameraUsageDescription" = "A SimpleX-nek kamera-hozzáférésre van szüksége a QR-kódok beolvasásához, hogy kapcsolódhasson más felhasználókhoz és videohívásokhoz.";
/* Privacy - Face ID Usage Description */ /* Privacy - Face ID Usage Description */
"NSFaceIDUsageDescription" = "A SimpleX Face ID-t használ a helyi hitelesítéshez"; "NSFaceIDUsageDescription" = "A SimpleX Face ID-t használ a helyi hitelesítéshez";
+30
View File
@@ -1893,6 +1893,9 @@
/* chat item action */ /* chat item action */
"Forward" = "Inoltra"; "Forward" = "Inoltra";
/* No comment provided by engineer. */
"Forward and save messages" = "Inoltra e salva i messaggi";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"forwarded" = "inoltrato"; "forwarded" = "inoltrato";
@@ -2124,6 +2127,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"In reply to" = "In risposta a"; "In reply to" = "In risposta a";
/* No comment provided by engineer. */
"In-call sounds" = "Suoni nelle chiamate";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Incognito" = "Incognito"; "Incognito" = "Incognito";
@@ -2469,6 +2475,9 @@
/* notification */ /* notification */
"message received" = "messaggio ricevuto"; "message received" = "messaggio ricevuto";
/* No comment provided by engineer. */
"Message source remains private." = "La fonte del messaggio resta privata.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Message text" = "Testo del messaggio"; "Message text" = "Testo del messaggio";
@@ -2553,6 +2562,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"More improvements are coming soon!" = "Altri miglioramenti sono in arrivo!"; "More improvements are coming soon!" = "Altri miglioramenti sono in arrivo!";
/* No comment provided by engineer. */
"More reliable network connection." = "Connessione di rete più affidabile.";
/* item status description */ /* item status description */
"Most likely this connection is deleted." = "Probabilmente questa connessione è stata eliminata."; "Most likely this connection is deleted." = "Probabilmente questa connessione è stata eliminata.";
@@ -2577,6 +2589,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Network connection" = "Connessione di rete"; "Network connection" = "Connessione di rete";
/* No comment provided by engineer. */
"Network management" = "Gestione della rete";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Network settings" = "Impostazioni di rete"; "Network settings" = "Impostazioni di rete";
@@ -2945,6 +2960,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Profile image" = "Immagine del profilo"; "Profile image" = "Immagine del profilo";
/* No comment provided by engineer. */
"Profile images" = "Immagini del profilo";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Profile name" = "Nome del profilo"; "Profile name" = "Nome del profilo";
@@ -3488,6 +3506,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Settings" = "Impostazioni"; "Settings" = "Impostazioni";
/* No comment provided by engineer. */
"Shape profile images" = "Forma delle immagini del profilo";
/* chat item action */ /* chat item action */
"Share" = "Condividi"; "Share" = "Condividi";
@@ -3590,6 +3611,9 @@
/* notification title */ /* notification title */
"Somebody" = "Qualcuno"; "Somebody" = "Qualcuno";
/* No comment provided by engineer. */
"Square, circle, or anything in between." = "Quadrata, circolare o qualsiasi forma tra le due.";
/* chat item text */ /* chat item text */
"standard end-to-end encryption" = "crittografia end-to-end standard"; "standard end-to-end encryption" = "crittografia end-to-end standard";
@@ -4172,6 +4196,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"When available" = "Quando disponibili"; "When available" = "Quando disponibili";
/* No comment provided by engineer. */
"When connecting audio and video calls." = "Quando si connettono le chiamate audio e video.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"When people request to connect, you can accept or reject it." = "Quando le persone chiedono di connettersi, puoi accettare o rifiutare."; "When people request to connect, you can accept or reject it." = "Quando le persone chiedono di connettersi, puoi accettare o rifiutare.";
@@ -4181,6 +4208,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"WiFi" = "WiFi"; "WiFi" = "WiFi";
/* No comment provided by engineer. */
"Will be enabled in direct chats!" = "Viene attivata nelle chat dirette!";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Wired ethernet" = "Cavo ethernet"; "Wired ethernet" = "Cavo ethernet";
+20 -2
View File
@@ -68,10 +68,10 @@
"**Create group**: to create a new group." = "**グループ作成**: 新しいグループを作成する。"; "**Create group**: to create a new group." = "**グループ作成**: 新しいグループを作成する。";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"**e2e encrypted** audio call" = "**e2e 暗号化**音声通話"; "**e2e encrypted** audio call" = "**エンドツーエンド暗号化済み**音声通話";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"**e2e encrypted** video call" = "**e2e暗号化**ビデオ通話"; "**e2e encrypted** video call" = "**エンドツーエンド暗号化済み**の テレビ電話 通話";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**よりプライベート**: 20 分ごとに新しいメッセージを確認します。 デバイス トークンは SimpleX Chat サーバーと共有されますが、連絡先やメッセージの数は共有されません。"; "**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**よりプライベート**: 20 分ごとに新しいメッセージを確認します。 デバイス トークンは SimpleX Chat サーバーと共有されますが、連絡先やメッセージの数は共有されません。";
@@ -265,6 +265,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"~strike~" = "\\~取り消し線~"; "~strike~" = "\\~取り消し線~";
/* time to disappear */
"0 sec" = "0 秒";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"0s" = "0秒"; "0s" = "0秒";
@@ -410,6 +413,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"All your contacts will remain connected. Profile update will be sent to your contacts." = "すべての連絡先は維持されます。連絡先に更新されたプロフィールを送信します。"; "All your contacts will remain connected. Profile update will be sent to your contacts." = "すべての連絡先は維持されます。連絡先に更新されたプロフィールを送信します。";
/* No comment provided by engineer. */
"All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." = "すべての連絡先、会話、ファイルは安全に暗号化され、設定されたXFTPリレーに分割でアップロードされます。";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Allow" = "許可"; "Allow" = "許可";
@@ -440,6 +446,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Allow to send files and media." = "ファイルやメディアの送信を許可する。"; "Allow to send files and media." = "ファイルやメディアの送信を許可する。";
/* No comment provided by engineer. */
"Allow to send SimpleX links." = "SimpleXリンクの送信を許可。";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Allow to send voice messages." = "音声メッセージの送信を許可する。"; "Allow to send voice messages." = "音声メッセージの送信を許可する。";
@@ -467,6 +476,12 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Already connected?" = "すでに接続済みですか?"; "Already connected?" = "すでに接続済みですか?";
/* No comment provided by engineer. */
"Already connecting!" = "既に接続中です!";
/* No comment provided by engineer. */
"Already joining the group!" = "すでにグループに参加しています!";
/* pref value */ /* pref value */
"always" = "常に"; "always" = "常に";
@@ -482,6 +497,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"App build: %@" = "アプリのビルド: %@"; "App build: %@" = "アプリのビルド: %@";
/* No comment provided by engineer. */
"App data migration" = "アプリデータの移行";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"App encrypts new local files (except videos)." = "アプリは新しいローカルファイル(ビデオを除く)を暗号化します。"; "App encrypts new local files (except videos)." = "アプリは新しいローカルファイル(ビデオを除く)を暗号化します。";
+9
View File
@@ -2960,6 +2960,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Profile image" = "profielfoto"; "Profile image" = "profielfoto";
/* No comment provided by engineer. */
"Profile images" = "Profiel afbeeldingen";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Profile name" = "Profielnaam"; "Profile name" = "Profielnaam";
@@ -3503,6 +3506,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Settings" = "Instellingen"; "Settings" = "Instellingen";
/* No comment provided by engineer. */
"Shape profile images" = "Vorm profiel afbeeldingen";
/* chat item action */ /* chat item action */
"Share" = "Deel"; "Share" = "Deel";
@@ -3605,6 +3611,9 @@
/* notification title */ /* notification title */
"Somebody" = "Iemand"; "Somebody" = "Iemand";
/* No comment provided by engineer. */
"Square, circle, or anything in between." = "Vierkant, cirkel of iets daartussenin.";
/* chat item text */ /* chat item text */
"standard end-to-end encryption" = "standaard end-to-end encryptie"; "standard end-to-end encryption" = "standaard end-to-end encryptie";
+117
View File
@@ -389,6 +389,9 @@
/* member role */ /* member role */
"admin" = "administrator"; "admin" = "administrator";
/* feature role */
"admins" = "administratorzy";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Admins can block a member for all." = "Administratorzy mogą blokować członka dla wszystkich."; "Admins can block a member for all." = "Administratorzy mogą blokować członka dla wszystkich.";
@@ -416,6 +419,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"All group members will remain connected." = "Wszyscy członkowie grupy pozostaną połączeni."; "All group members will remain connected." = "Wszyscy członkowie grupy pozostaną połączeni.";
/* feature role */
"all members" = "wszyscy członkowie";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"All messages will be deleted - this cannot be undone!" = "Wszystkie wiadomości zostaną usunięte nie można tego cofnąć!"; "All messages will be deleted - this cannot be undone!" = "Wszystkie wiadomości zostaną usunięte nie można tego cofnąć!";
@@ -464,6 +470,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Allow to send files and media." = "Pozwól na wysyłanie plików i mediów."; "Allow to send files and media." = "Pozwól na wysyłanie plików i mediów.";
/* No comment provided by engineer. */
"Allow to send SimpleX links." = "Zezwól na wysyłanie linków SimpleX.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Allow to send voice messages." = "Zezwól na wysyłanie wiadomości głosowych."; "Allow to send voice messages." = "Zezwól na wysyłanie wiadomości głosowych.";
@@ -707,6 +716,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Cannot receive file" = "Nie można odebrać pliku"; "Cannot receive file" = "Nie można odebrać pliku";
/* No comment provided by engineer. */
"Cellular" = "Sieć komórkowa";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Change" = "Zmień"; "Change" = "Zmień";
@@ -1401,6 +1413,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Downgrade and open chat" = "Obniż wersję i otwórz czat"; "Downgrade and open chat" = "Obniż wersję i otwórz czat";
/* chat item action */
"Download" = "Pobierz";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Download failed" = "Pobieranie nie udane"; "Download failed" = "Pobieranie nie udane";
@@ -1476,6 +1491,9 @@
/* enabled status */ /* enabled status */
"enabled" = "włączone"; "enabled" = "włączone";
/* No comment provided by engineer. */
"Enabled for" = "Włączony dla";
/* enabled status */ /* enabled status */
"enabled for contact" = "włączone dla kontaktu"; "enabled for contact" = "włączone dla kontaktu";
@@ -1830,6 +1848,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Files and media are prohibited in this group." = "Pliki i media są zabronione w tej grupie."; "Files and media are prohibited in this group." = "Pliki i media są zabronione w tej grupie.";
/* No comment provided by engineer. */
"Files and media not allowed" = "Pliki i multimedia nie są dozwolone";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Files and media prohibited!" = "Pliki i media zabronione!"; "Files and media prohibited!" = "Pliki i media zabronione!";
@@ -1869,6 +1890,21 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"For console" = "Dla konsoli"; "For console" = "Dla konsoli";
/* chat item action */
"Forward" = "Przekaż dalej";
/* No comment provided by engineer. */
"Forward and save messages" = "Przesyłaj dalej i zapisuj wiadomości";
/* No comment provided by engineer. */
"forwarded" = "przekazane dalej";
/* No comment provided by engineer. */
"Forwarded" = "Przekazane dalej";
/* No comment provided by engineer. */
"Forwarded from" = "Przekazane dalej od";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Found desktop" = "Znaleziono komputer"; "Found desktop" = "Znaleziono komputer";
@@ -1947,6 +1983,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Group members can send files and media." = "Członkowie grupy mogą wysyłać pliki i media."; "Group members can send files and media." = "Członkowie grupy mogą wysyłać pliki i media.";
/* No comment provided by engineer. */
"Group members can send SimpleX links." = "Członkowie grupy mogą wysyłać linki SimpleX.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Group members can send voice messages." = "Członkowie grupy mogą wysyłać wiadomości głosowe."; "Group members can send voice messages." = "Członkowie grupy mogą wysyłać wiadomości głosowe.";
@@ -2088,6 +2127,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"In reply to" = "W odpowiedzi na"; "In reply to" = "W odpowiedzi na";
/* No comment provided by engineer. */
"In-call sounds" = "Dźwięki w rozmowie";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Incognito" = "Incognito"; "Incognito" = "Incognito";
@@ -2433,6 +2475,9 @@
/* notification */ /* notification */
"message received" = "wiadomość otrzymana"; "message received" = "wiadomość otrzymana";
/* No comment provided by engineer. */
"Message source remains private." = "Źródło wiadomości pozostaje prywatne.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Message text" = "Tekst wiadomości"; "Message text" = "Tekst wiadomości";
@@ -2517,6 +2562,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"More improvements are coming soon!" = "Więcej ulepszeń już wkrótce!"; "More improvements are coming soon!" = "Więcej ulepszeń już wkrótce!";
/* No comment provided by engineer. */
"More reliable network connection." = "Bardziej niezawodne połączenia sieciowe.";
/* item status description */ /* item status description */
"Most likely this connection is deleted." = "Najprawdopodobniej to połączenie jest usunięte."; "Most likely this connection is deleted." = "Najprawdopodobniej to połączenie jest usunięte.";
@@ -2538,6 +2586,12 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Network & servers" = "Sieć i serwery"; "Network & servers" = "Sieć i serwery";
/* No comment provided by engineer. */
"Network connection" = "Połączenie z siecią";
/* No comment provided by engineer. */
"Network management" = "Zarządzenie sieciowe";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Network settings" = "Ustawienia sieci"; "Network settings" = "Ustawienia sieci";
@@ -2616,6 +2670,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"No history" = "Brak historii"; "No history" = "Brak historii";
/* No comment provided by engineer. */
"No network connection" = "Brak połączenia z siecią";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"No permission to record voice message" = "Brak uprawnień do nagrywania wiadomości głosowej"; "No permission to record voice message" = "Brak uprawnień do nagrywania wiadomości głosowej";
@@ -2762,9 +2819,15 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Or show this code" = "Lub pokaż ten kod"; "Or show this code" = "Lub pokaż ten kod";
/* No comment provided by engineer. */
"Other" = "Inne";
/* member role */ /* member role */
"owner" = "właściciel"; "owner" = "właściciel";
/* feature role */
"owners" = "właściciele";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Passcode" = "Pin"; "Passcode" = "Pin";
@@ -2897,6 +2960,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Profile image" = "Zdjęcie profilowe"; "Profile image" = "Zdjęcie profilowe";
/* No comment provided by engineer. */
"Profile images" = "Zdjęcia profilowe";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Profile name" = "Nazwa profilu"; "Profile name" = "Nazwa profilu";
@@ -2930,6 +2996,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Prohibit sending files and media." = "Zakaz wysyłania plików i mediów."; "Prohibit sending files and media." = "Zakaz wysyłania plików i mediów.";
/* No comment provided by engineer. */
"Prohibit sending SimpleX links." = "Zabroń wysyłania linków SimpleX.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Prohibit sending voice messages." = "Zabroń wysyłania wiadomości głosowych."; "Prohibit sending voice messages." = "Zabroń wysyłania wiadomości głosowych.";
@@ -3008,6 +3077,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Receiving address will be changed to a different server. Address change will complete after sender comes online." = "Adres odbiorczy zostanie zmieniony na inny serwer. Zmiana adresu zostanie zakończona gdy nadawca będzie online."; "Receiving address will be changed to a different server. Address change will complete after sender comes online." = "Adres odbiorczy zostanie zmieniony na inny serwer. Zmiana adresu zostanie zakończona gdy nadawca będzie online.";
/* No comment provided by engineer. */
"Receiving concurrency" = "Konkurencyjne odbieranie";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Receiving file will be stopped." = "Odbieranie pliku zostanie przerwane."; "Receiving file will be stopped." = "Odbieranie pliku zostanie przerwane.";
@@ -3017,6 +3089,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Recent history and improved [directory bot](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion)." = "Ostania historia i ulepszony [bot adresowy](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion)."; "Recent history and improved [directory bot](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion)." = "Ostania historia i ulepszony [bot adresowy](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion).";
/* No comment provided by engineer. */
"Recipient(s) can't see who this message is from." = "Odbiorca/y nie mogą zobaczyć od kogo jest ta wiadomość.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Recipients see updates as you type them." = "Odbiorcy widzą aktualizacje podczas ich wpisywania."; "Recipients see updates as you type them." = "Odbiorcy widzą aktualizacje podczas ich wpisywania.";
@@ -3212,6 +3287,18 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Save welcome message?" = "Zapisać wiadomość powitalną?"; "Save welcome message?" = "Zapisać wiadomość powitalną?";
/* No comment provided by engineer. */
"saved" = "zapisane";
/* No comment provided by engineer. */
"Saved" = "Zapisane";
/* No comment provided by engineer. */
"Saved from" = "Zapisane od";
/* No comment provided by engineer. */
"saved from %@" = "zapisane od %@";
/* message info title */ /* message info title */
"Saved message" = "Zachowano wiadomość"; "Saved message" = "Zachowano wiadomość";
@@ -3419,6 +3506,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Settings" = "Ustawienia"; "Settings" = "Ustawienia";
/* No comment provided by engineer. */
"Shape profile images" = "Kształtuj obrazy profilowe";
/* chat item action */ /* chat item action */
"Share" = "Udostępnij"; "Share" = "Udostępnij";
@@ -3479,6 +3569,12 @@
/* chat feature */ /* chat feature */
"SimpleX links" = "Linki SimpleX"; "SimpleX links" = "Linki SimpleX";
/* No comment provided by engineer. */
"SimpleX links are prohibited in this group." = "Linki SimpleX są zablokowane na tej grupie.";
/* No comment provided by engineer. */
"SimpleX links not allowed" = "Linki SimpleX są niedozwolone";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"SimpleX Lock" = "Blokada SimpleX"; "SimpleX Lock" = "Blokada SimpleX";
@@ -3515,6 +3611,9 @@
/* notification title */ /* notification title */
"Somebody" = "Ktoś"; "Somebody" = "Ktoś";
/* No comment provided by engineer. */
"Square, circle, or anything in between." = "Kwadrat, okrąg lub cokolwiek pomiędzy.";
/* chat item text */ /* chat item text */
"standard end-to-end encryption" = "standardowe szyfrowanie end-to-end"; "standard end-to-end encryption" = "standardowe szyfrowanie end-to-end";
@@ -4043,6 +4142,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Voice messages are prohibited in this group." = "Wiadomości głosowe są zabronione w tej grupie."; "Voice messages are prohibited in this group." = "Wiadomości głosowe są zabronione w tej grupie.";
/* No comment provided by engineer. */
"Voice messages not allowed" = "Wiadomości głosowe są niedozwolone";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Voice messages prohibited!" = "Wiadomości głosowe zabronione!"; "Voice messages prohibited!" = "Wiadomości głosowe zabronione!";
@@ -4094,12 +4196,24 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"When available" = "Gdy dostępny"; "When available" = "Gdy dostępny";
/* No comment provided by engineer. */
"When connecting audio and video calls." = "Podczas łączenia połączeń audio i wideo.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"When people request to connect, you can accept or reject it." = "Kiedy ludzie proszą o połączenie, możesz je zaakceptować lub odrzucić."; "When people request to connect, you can accept or reject it." = "Kiedy ludzie proszą o połączenie, możesz je zaakceptować lub odrzucić.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Gdy udostępnisz komuś profil incognito, będzie on używany w grupach, do których Cię zaprosi."; "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Gdy udostępnisz komuś profil incognito, będzie on używany w grupach, do których Cię zaprosi.";
/* No comment provided by engineer. */
"WiFi" = "WiFi";
/* No comment provided by engineer. */
"Will be enabled in direct chats!" = "Zostanie włączone w czatach bezpośrednich!";
/* No comment provided by engineer. */
"Wired ethernet" = "Połączenie ethernet (po kablu)";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"With encrypted files and media." = "Z zaszyfrowanymi plikami i multimediami."; "With encrypted files and media." = "Z zaszyfrowanymi plikami i multimediami.";
@@ -4121,6 +4235,9 @@
/* pref value */ /* pref value */
"yes" = "tak"; "yes" = "tak";
/* No comment provided by engineer. */
"you" = "Ty";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"You" = "Ty"; "You" = "Ty";
+11 -2
View File
@@ -2960,6 +2960,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Profile image" = "Profil fotoğrafı"; "Profile image" = "Profil fotoğrafı";
/* No comment provided by engineer. */
"Profile images" = "Profil resimleri";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Profile name" = "Profil ismi"; "Profile name" = "Profil ismi";
@@ -3051,7 +3054,7 @@
"Read more in our GitHub repository." = "Daha fazlasını GitHub depomuzdan oku."; "Read more in our GitHub repository." = "Daha fazlasını GitHub depomuzdan oku.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Receipts are disabled" = "Makbuzlar devre dışı bırakıldı"; "Receipts are disabled" = "Gönderildi bilgisi devre dışı bırakıldı";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"received answer…" = "alınan cevap…"; "received answer…" = "alınan cevap…";
@@ -3417,7 +3420,7 @@
"Sending delivery receipts will be enabled for all contacts in all visible chat profiles." = "Görüldü bilgisi, tüm görünür sohbet profillerindeki tüm kişiler için etkinleştirilecektir."; "Sending delivery receipts will be enabled for all contacts in all visible chat profiles." = "Görüldü bilgisi, tüm görünür sohbet profillerindeki tüm kişiler için etkinleştirilecektir.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Sending delivery receipts will be enabled for all contacts." = "Teslimat makbuzlarının gönderilmesi tüm kişiler için etkinleştirilecektir."; "Sending delivery receipts will be enabled for all contacts." = "Gönderildi bilgisi tüm kişiler için etkinleştirilecektir.";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Sending file will be stopped." = "Dosya gönderimi durdurulacaktır."; "Sending file will be stopped." = "Dosya gönderimi durdurulacaktır.";
@@ -3503,6 +3506,9 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Settings" = "Ayarlar"; "Settings" = "Ayarlar";
/* No comment provided by engineer. */
"Shape profile images" = "Profil resimlerini şekillendir";
/* chat item action */ /* chat item action */
"Share" = "Paylaş"; "Share" = "Paylaş";
@@ -3605,6 +3611,9 @@
/* notification title */ /* notification title */
"Somebody" = "Biri"; "Somebody" = "Biri";
/* No comment provided by engineer. */
"Square, circle, or anything in between." = "Kare,daire, veya aralarında herhangi bir şey.";
/* chat item text */ /* chat item text */
"standard end-to-end encryption" = "standart uçtan uca şifreleme"; "standard end-to-end encryption" = "standart uçtan uca şifreleme";
+1 -1
View File
@@ -13,7 +13,7 @@ android {
defaultConfig { defaultConfig {
applicationId = "chat.simplex.app" applicationId = "chat.simplex.app"
namespace = "chat.simplex.app" namespace = "chat.simplex.app"
minSdk = 28 minSdk = 26
//noinspection OldTargetApi //noinspection OldTargetApi
targetSdk = 33 targetSdk = 33
// !!! // !!!
@@ -7,7 +7,7 @@ import chat.simplex.app.SimplexService.Companion.showPassphraseNotification
import chat.simplex.common.model.ChatController import chat.simplex.common.model.ChatController
import chat.simplex.common.views.helpers.DBMigrationResult import chat.simplex.common.views.helpers.DBMigrationResult
import chat.simplex.common.platform.chatModel import chat.simplex.common.platform.chatModel
import chat.simplex.common.platform.initChatControllerAndRunMigrations import chat.simplex.common.platform.initChatControllerOnStart
import chat.simplex.common.views.helpers.DatabaseUtils import chat.simplex.common.views.helpers.DatabaseUtils
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.util.Date import java.util.Date
@@ -60,7 +60,7 @@ class MessagesFetcherWork(
try { try {
// In case of self-destruct is enabled the initialization process will not start in SimplexApp, Let's start it here // In case of self-destruct is enabled the initialization process will not start in SimplexApp, Let's start it here
if (DatabaseUtils.ksSelfDestructPassword.get() != null && chatModel.chatDbStatus.value == null) { if (DatabaseUtils.ksSelfDestructPassword.get() != null && chatModel.chatDbStatus.value == null) {
initChatControllerAndRunMigrations() initChatControllerOnStart()
} }
withTimeout(durationSeconds * 1000L) { withTimeout(durationSeconds * 1000L) {
val chatController = ChatController val chatController = ChatController
@@ -6,7 +6,6 @@ import android.content.Context
import chat.simplex.common.platform.Log import chat.simplex.common.platform.Log
import android.content.Intent import android.content.Intent
import android.content.pm.ActivityInfo import android.content.pm.ActivityInfo
import android.media.AudioManager
import android.os.* import android.os.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
@@ -66,6 +65,7 @@ class SimplexApp: Application(), LifecycleEventObserver {
context = this context = this
initHaskell() initHaskell()
initMultiplatform() initMultiplatform()
runMigrations()
tmpDir.deleteRecursively() tmpDir.deleteRecursively()
tmpDir.mkdir() tmpDir.mkdir()
@@ -74,7 +74,7 @@ class SimplexApp: Application(), LifecycleEventObserver {
// It's important, otherwise, user may be locked in undefined state // It's important, otherwise, user may be locked in undefined state
appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
} else if (DatabaseUtils.ksAppPassword.get() == null || DatabaseUtils.ksSelfDestructPassword.get() == null) { } else if (DatabaseUtils.ksAppPassword.get() == null || DatabaseUtils.ksSelfDestructPassword.get() == null) {
initChatControllerAndRunMigrations() initChatControllerOnStart()
} }
ProcessLifecycleOwner.get().lifecycle.addObserver(this@SimplexApp) ProcessLifecycleOwner.get().lifecycle.addObserver(this@SimplexApp)
} }
@@ -254,7 +254,7 @@ class SimplexApp: Application(), LifecycleEventObserver {
override fun androidSetNightModeIfSupported() { override fun androidSetNightModeIfSupported() {
if (Build.VERSION.SDK_INT < 31) return if (Build.VERSION.SDK_INT < 31) return
val light = if (CurrentColors.value.name == DefaultTheme.SYSTEM.name) { val light = if (CurrentColors.value.name == DefaultTheme.SYSTEM_THEME_NAME) {
null null
} else { } else {
CurrentColors.value.colors.isLight CurrentColors.value.colors.isLight
@@ -77,7 +77,7 @@ class SimplexService: Service() {
isServiceStarted = true isServiceStarted = true
// In case of self-destruct is enabled the initialization process will not start in SimplexApp, Let's start it here // In case of self-destruct is enabled the initialization process will not start in SimplexApp, Let's start it here
if (DatabaseUtils.ksSelfDestructPassword.get() != null && chatModel.chatDbStatus.value == null) { if (DatabaseUtils.ksSelfDestructPassword.get() != null && chatModel.chatDbStatus.value == null) {
initChatControllerAndRunMigrations() initChatControllerOnStart()
} }
} }
} }
+5 -2
View File
@@ -37,7 +37,7 @@ kotlin {
api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
api("org.jetbrains.kotlinx:kotlinx-datetime:0.5.0") api("org.jetbrains.kotlinx:kotlinx-datetime:0.5.0")
api("com.russhwolf:multiplatform-settings:1.1.1") api("com.russhwolf:multiplatform-settings:1.1.1")
api("com.charleskorn.kaml:kaml:0.58.0") api("com.charleskorn.kaml:kaml:0.59.0")
api("org.jetbrains.compose.ui:ui-text:${rootProject.extra["compose.version"] as String}") api("org.jetbrains.compose.ui:ui-text:${rootProject.extra["compose.version"] as String}")
implementation("org.jetbrains.compose.components:components-animatedimage:${rootProject.extra["compose.version"] as String}") implementation("org.jetbrains.compose.components:components-animatedimage:${rootProject.extra["compose.version"] as String}")
//Barcode //Barcode
@@ -53,6 +53,9 @@ kotlin {
val commonTest by getting { val commonTest by getting {
dependencies { dependencies {
implementation(kotlin("test")) implementation(kotlin("test"))
implementation(kotlin("test-junit"))
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
} }
} }
val androidMain by getting { val androidMain by getting {
@@ -113,7 +116,7 @@ android {
compileSdk = 34 compileSdk = 34
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig { defaultConfig {
minSdk = 28 minSdk = 26
} }
testOptions.targetSdk = 33 testOptions.targetSdk = 33
lint.targetSdk = 33 lint.targetSdk = 33
@@ -15,8 +15,10 @@ actual val dataDir: File = androidAppContext.dataDir
actual val tmpDir: File = androidAppContext.getDir("temp", Application.MODE_PRIVATE) actual val tmpDir: File = androidAppContext.getDir("temp", Application.MODE_PRIVATE)
actual val filesDir: File = File(dataDir.absolutePath + File.separator + "files") actual val filesDir: File = File(dataDir.absolutePath + File.separator + "files")
actual val appFilesDir: File = File(filesDir.absolutePath + File.separator + "app_files") actual val appFilesDir: File = File(filesDir.absolutePath + File.separator + "app_files")
actual val wallpapersDir: File = File(filesDir.absolutePath + File.separator + "assets" + File.separator + "wallpapers").also { it.mkdirs() }
actual val coreTmpDir: File = File(filesDir.absolutePath + File.separator + "temp_files") actual val coreTmpDir: File = File(filesDir.absolutePath + File.separator + "temp_files")
actual val dbAbsolutePrefixPath: String = dataDir.absolutePath + File.separator + "files" actual val dbAbsolutePrefixPath: String = dataDir.absolutePath + File.separator + "files"
actual val preferencesDir = File(dataDir.absolutePath + File.separator + "shared_prefs")
actual val chatDatabaseFileName: String = "files_chat.db" actual val chatDatabaseFileName: String = "files_chat.db"
actual val agentDatabaseFileName: String = "files_agent.db" actual val agentDatabaseFileName: String = "files_agent.db"
@@ -7,6 +7,8 @@ import android.content.SharedPreferences
import android.content.res.Configuration import android.content.res.Configuration
import android.text.BidiFormatter import android.text.BidiFormatter
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.Font
@@ -14,9 +16,11 @@ import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.toBitmap
import chat.simplex.common.model.AppPreferences import chat.simplex.common.model.AppPreferences
import com.russhwolf.settings.Settings import com.russhwolf.settings.Settings
import com.russhwolf.settings.SharedPreferencesSettings import com.russhwolf.settings.SharedPreferencesSettings
import dev.icerock.moko.resources.ImageResource
import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.StringResource
import dev.icerock.moko.resources.desc.desc import dev.icerock.moko.resources.desc.desc
@@ -51,3 +55,6 @@ actual fun windowWidth(): Dp = LocalConfiguration.current.screenWidthDp.dp
actual fun desktopExpandWindowToWidth(width: Dp) {} actual fun desktopExpandWindowToWidth(width: Dp) {}
actual fun isRtl(text: CharSequence): Boolean = BidiFormatter.getInstance().isRtl(text) actual fun isRtl(text: CharSequence): Boolean = BidiFormatter.getInstance().isRtl(text)
actual fun ImageResource.toComposeImageBitmap(): ImageBitmap? =
getDrawable(androidAppContext)?.toBitmap()?.asImageBitmap()
@@ -37,7 +37,7 @@ actual fun ClipboardManager.shareText(text: String) {
} }
} }
actual fun shareFile(text: String, fileSource: CryptoFile) { fun openOrShareFile(text: String, fileSource: CryptoFile, justOpen: Boolean) {
val uri = if (fileSource.cryptoArgs != null) { val uri = if (fileSource.cryptoArgs != null) {
val tmpFile = File(tmpDir, fileSource.filePath) val tmpFile = File(tmpDir, fileSource.filePath)
tmpFile.deleteOnExit() tmpFile.deleteOnExit()
@@ -55,20 +55,31 @@ actual fun shareFile(text: String, fileSource: CryptoFile) {
} }
val ext = fileSource.filePath.substringAfterLast(".") val ext = fileSource.filePath.substringAfterLast(".")
val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext) ?: return val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext) ?: return
val sendIntent: Intent = Intent().apply { val sendIntent: Intent = Intent(if (justOpen) Intent.ACTION_VIEW else Intent.ACTION_SEND).apply {
action = Intent.ACTION_SEND
/*if (text.isNotEmpty()) { /*if (text.isNotEmpty()) {
putExtra(Intent.EXTRA_TEXT, text) putExtra(Intent.EXTRA_TEXT, text)
}*/ }*/
putExtra(Intent.EXTRA_STREAM, uri.toUri()) if (justOpen) {
type = mimeType flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
flags = Intent.FLAG_ACTIVITY_NEW_TASK setDataAndType(uri.toUri(), mimeType)
} else {
putExtra(Intent.EXTRA_STREAM, uri.toUri())
type = mimeType
}
} }
val shareIntent = Intent.createChooser(sendIntent, null) val shareIntent = Intent.createChooser(sendIntent, null)
shareIntent.addFlags(FLAG_ACTIVITY_NEW_TASK) shareIntent.addFlags(FLAG_ACTIVITY_NEW_TASK)
androidAppContext.startActivity(shareIntent) androidAppContext.startActivity(shareIntent)
} }
actual fun shareFile(text: String, fileSource: CryptoFile) {
openOrShareFile(text, fileSource, justOpen = false)
}
actual fun openFile(fileSource: CryptoFile) {
openOrShareFile("", fileSource, justOpen = true)
}
actual fun UriHandler.sendEmail(subject: String, body: CharSequence) { actual fun UriHandler.sendEmail(subject: String, body: CharSequence) {
val emailIntent = Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:")) val emailIntent = Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:"))
emailIntent.putExtra(Intent.EXTRA_SUBJECT, subject) emailIntent.putExtra(Intent.EXTRA_SUBJECT, subject)
@@ -689,6 +689,7 @@ fun WebRTCView(callCommand: SnapshotStateList<WCallCommand>, onResponse: (WVAPIM
this.clearHistory() this.clearHistory()
this.clearCache(true) this.clearCache(true)
this.addJavascriptInterface(WebRTCInterface(onResponse), "WebRTCInterface") this.addJavascriptInterface(WebRTCInterface(onResponse), "WebRTCInterface")
this.setBackgroundColor(android.graphics.Color.BLACK)
val webViewSettings = this.settings val webViewSettings = this.settings
webViewSettings.allowFileAccess = true webViewSettings.allowFileAccess = true
webViewSettings.allowContentAccess = true webViewSettings.allowContentAccess = true
@@ -19,14 +19,10 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.*
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import dev.icerock.moko.resources.compose.stringResource import dev.icerock.moko.resources.compose.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmap
import chat.simplex.common.R
import chat.simplex.common.model.* import chat.simplex.common.model.*
import chat.simplex.common.ui.theme.* import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.helpers.* import chat.simplex.common.views.helpers.*
@@ -34,7 +30,6 @@ import chat.simplex.common.model.ChatModel
import chat.simplex.common.platform.* import chat.simplex.common.platform.*
import chat.simplex.common.helpers.APPLICATION_ID import chat.simplex.common.helpers.APPLICATION_ID
import chat.simplex.common.helpers.saveAppLocale import chat.simplex.common.helpers.saveAppLocale
import chat.simplex.common.views.usersettings.AppearanceScope.ColorEditor
import chat.simplex.res.MR import chat.simplex.res.MR
import dev.icerock.moko.resources.ImageResource import dev.icerock.moko.resources.ImageResource
import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.painterResource
@@ -46,9 +41,8 @@ enum class AppIcon(val image: ImageResource) {
} }
@Composable @Composable
actual fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit)) { actual fun AppearanceView(m: ChatModel) {
val appIcon = remember { mutableStateOf(findEnabledIcon()) } val appIcon = remember { mutableStateOf(findEnabledIcon()) }
fun setAppIcon(newIcon: AppIcon) { fun setAppIcon(newIcon: AppIcon) {
if (appIcon.value == newIcon) return if (appIcon.value == newIcon) return
val newComponent = ComponentName(APPLICATION_ID, "chat.simplex.app.MainActivity_${newIcon.name.lowercase()}") val newComponent = ComponentName(APPLICATION_ID, "chat.simplex.app.MainActivity_${newIcon.name.lowercase()}")
@@ -65,18 +59,11 @@ actual fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatMod
appIcon.value = newIcon appIcon.value = newIcon
} }
AppearanceScope.AppearanceLayout( AppearanceScope.AppearanceLayout(
appIcon, appIcon,
m.controller.appPrefs.appLanguage, m.controller.appPrefs.appLanguage,
m.controller.appPrefs.systemDarkTheme, m.controller.appPrefs.systemDarkTheme,
changeIcon = ::setAppIcon, changeIcon = ::setAppIcon,
showSettingsModal = showSettingsModal,
editColor = { name, initialColor ->
ModalManager.start.showModalCloseable { close ->
ColorEditor(name, initialColor, close)
}
},
) )
} }
@@ -86,8 +73,6 @@ fun AppearanceScope.AppearanceLayout(
languagePref: SharedPreference<String?>, languagePref: SharedPreference<String?>,
systemDarkTheme: SharedPreference<String?>, systemDarkTheme: SharedPreference<String?>,
changeIcon: (AppIcon) -> Unit, changeIcon: (AppIcon) -> Unit,
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
editColor: (ThemeColor, Color) -> Unit,
) { ) {
ColumnWithScrollBar( ColumnWithScrollBar(
Modifier.fillMaxWidth(), Modifier.fillMaxWidth(),
@@ -120,6 +105,13 @@ fun AppearanceScope.AppearanceLayout(
} }
// } // }
} }
SectionDividerSpaced(maxTopPadding = true)
ThemesSection(systemDarkTheme)
SectionDividerSpaced(maxTopPadding = true)
ProfileImageSection()
SectionDividerSpaced() SectionDividerSpaced()
SectionView(stringResource(MR.strings.settings_section_title_icon), padding = PaddingValues(horizontal = DEFAULT_PADDING_HALF)) { SectionView(stringResource(MR.strings.settings_section_title_icon), padding = PaddingValues(horizontal = DEFAULT_PADDING_HALF)) {
@@ -145,11 +137,6 @@ fun AppearanceScope.AppearanceLayout(
} }
} }
SectionDividerSpaced(maxTopPadding = true)
ProfileImageSection()
SectionDividerSpaced(maxTopPadding = true)
ThemesSection(systemDarkTheme, showSettingsModal, editColor)
SectionBottomSpacer() SectionBottomSpacer()
} }
} }
@@ -169,8 +156,6 @@ fun PreviewAppearanceSettings() {
languagePref = SharedPreference({ null }, {}), languagePref = SharedPreference({ null }, {}),
systemDarkTheme = SharedPreference({ null }, {}), systemDarkTheme = SharedPreference({ null }, {}),
changeIcon = {}, changeIcon = {},
showSettingsModal = { {} },
editColor = { _, _ -> },
) )
} }
} }
@@ -4,7 +4,7 @@ import androidx.compose.material.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.runtime.snapshots.SnapshotStateMap import androidx.compose.runtime.snapshots.SnapshotStateMap
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.*
import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.* import androidx.compose.ui.text.font.*
import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextDecoration
@@ -399,6 +399,18 @@ object ChatModel {
currentUser.value = updated currentUser.value = updated
} }
fun updateCurrentUserUiThemes(rhId: Long?, uiThemes: ThemeModeOverrides?) {
val current = currentUser.value ?: return
val updated = current.copy(
uiThemes = uiThemes
)
val i = users.indexOfFirst { it.user.userId == current.userId && it.user.remoteHostId == rhId }
if (i != -1) {
users[i] = users[i].copy(user = updated)
}
currentUser.value = updated
}
suspend fun addLiveDummy(chatInfo: ChatInfo): ChatItem { suspend fun addLiveDummy(chatInfo: ChatInfo): ChatItem {
val cItem = ChatItem.liveDummy(chatInfo is ChatInfo.Direct) val cItem = ChatItem.liveDummy(chatInfo is ChatInfo.Direct)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
@@ -682,7 +694,8 @@ data class User(
override val showNtfs: Boolean, override val showNtfs: Boolean,
val sendRcptsContacts: Boolean, val sendRcptsContacts: Boolean,
val sendRcptsSmallGroups: Boolean, val sendRcptsSmallGroups: Boolean,
val viewPwdHash: UserPwdHash? val viewPwdHash: UserPwdHash?,
val uiThemes: ThemeModeOverrides? = null,
): NamedChat, UserLike { ): NamedChat, UserLike {
override val displayName: String get() = profile.displayName override val displayName: String get() = profile.displayName
override val fullName: String get() = profile.fullName override val fullName: String get() = profile.fullName
@@ -709,6 +722,7 @@ data class User(
sendRcptsContacts = true, sendRcptsContacts = true,
sendRcptsSmallGroups = false, sendRcptsSmallGroups = false,
viewPwdHash = null, viewPwdHash = null,
uiThemes = null,
) )
} }
} }
@@ -1041,16 +1055,21 @@ data class Contact(
override val updatedAt: Instant, override val updatedAt: Instant,
val chatTs: Instant?, val chatTs: Instant?,
val contactGroupMemberId: Long? = null, val contactGroupMemberId: Long? = null,
val contactGrpInvSent: Boolean val contactGrpInvSent: Boolean,
val uiThemes: ThemeModeOverrides? = null,
): SomeChat, NamedChat { ): SomeChat, NamedChat {
override val chatType get() = ChatType.Direct override val chatType get() = ChatType.Direct
override val id get() = "@$contactId" override val id get() = "@$contactId"
override val apiId get() = contactId override val apiId get() = contactId
override val ready get() = activeConn?.connStatus == ConnStatus.Ready override val ready get() = activeConn?.connStatus == ConnStatus.Ready
val active get() = contactStatus == ContactStatus.Active val active get() = contactStatus == ContactStatus.Active
override val sendMsgEnabled get() = override val sendMsgEnabled get() = (
(ready && active && !(activeConn?.connectionStats?.ratchetSyncSendProhibited ?: false)) ready
|| nextSendGrpInv && active
&& !(activeConn?.connectionStats?.ratchetSyncSendProhibited ?: false)
&& !(activeConn?.connDisabled ?: true)
)
|| nextSendGrpInv
val nextSendGrpInv get() = contactGroupMemberId != null && !contactGrpInvSent val nextSendGrpInv get() = contactGroupMemberId != null && !contactGrpInvSent
override val ntfsEnabled get() = chatSettings.enableNtfs == MsgFilter.All override val ntfsEnabled get() = chatSettings.enableNtfs == MsgFilter.All
override val incognito get() = contactConnIncognito override val incognito get() = contactConnIncognito
@@ -1109,7 +1128,8 @@ data class Contact(
createdAt = Clock.System.now(), createdAt = Clock.System.now(),
updatedAt = Clock.System.now(), updatedAt = Clock.System.now(),
chatTs = Clock.System.now(), chatTs = Clock.System.now(),
contactGrpInvSent = false contactGrpInvSent = false,
uiThemes = null,
) )
} }
} }
@@ -1150,15 +1170,19 @@ data class Connection(
val pqEncryption: Boolean, val pqEncryption: Boolean,
val pqSndEnabled: Boolean? = null, val pqSndEnabled: Boolean? = null,
val pqRcvEnabled: Boolean? = null, val pqRcvEnabled: Boolean? = null,
val connectionStats: ConnectionStats? = null val connectionStats: ConnectionStats? = null,
val authErrCounter: Int
) { ) {
val id: ChatId get() = ":$connId" val id: ChatId get() = ":$connId"
val connDisabled: Boolean
get() = authErrCounter >= 10 // authErrDisableCount in core
val connPQEnabled: Boolean val connPQEnabled: Boolean
get() = pqSndEnabled == true && pqRcvEnabled == true get() = pqSndEnabled == true && pqRcvEnabled == true
companion object { companion object {
val sampleData = Connection(connId = 1, agentConnId = "abc", connStatus = ConnStatus.Ready, connLevel = 0, viaGroupLink = false, peerChatVRange = VersionRange(1, 1), customUserProfileId = null, pqSupport = false, pqEncryption = false) val sampleData = Connection(connId = 1, agentConnId = "abc", connStatus = ConnStatus.Ready, connLevel = 0, viaGroupLink = false, peerChatVRange = VersionRange(1, 1), customUserProfileId = null, pqSupport = false, pqEncryption = false, authErrCounter = 0)
} }
} }
@@ -1245,7 +1269,8 @@ data class GroupInfo (
val chatSettings: ChatSettings, val chatSettings: ChatSettings,
override val createdAt: Instant, override val createdAt: Instant,
override val updatedAt: Instant, override val updatedAt: Instant,
val chatTs: Instant? val chatTs: Instant?,
val uiThemes: ThemeModeOverrides? = null,
): SomeChat, NamedChat { ): SomeChat, NamedChat {
override val chatType get() = ChatType.Group override val chatType get() = ChatType.Group
override val id get() = "#$groupId" override val id get() = "#$groupId"
@@ -1287,7 +1312,8 @@ data class GroupInfo (
chatSettings = ChatSettings(enableNtfs = MsgFilter.All, sendRcpts = null, favorite = false), chatSettings = ChatSettings(enableNtfs = MsgFilter.All, sendRcpts = null, favorite = false),
createdAt = Clock.System.now(), createdAt = Clock.System.now(),
updatedAt = Clock.System.now(), updatedAt = Clock.System.now(),
chatTs = Clock.System.now() chatTs = Clock.System.now(),
uiThemes = null,
) )
} }
} }
@@ -1900,18 +1926,20 @@ data class ChatItem (
ts: Instant = Clock.System.now(), ts: Instant = Clock.System.now(),
text: String = "hello\nthere", text: String = "hello\nthere",
status: CIStatus = CIStatus.SndNew(), status: CIStatus = CIStatus.SndNew(),
sentViaProxy: Boolean? = null,
quotedItem: CIQuote? = null, quotedItem: CIQuote? = null,
file: CIFile? = null, file: CIFile? = null,
itemForwarded: CIForwardedFrom? = null, itemForwarded: CIForwardedFrom? = null,
itemDeleted: CIDeleted? = null, itemDeleted: CIDeleted? = null,
itemEdited: Boolean = false, itemEdited: Boolean = false,
itemTimed: CITimed? = null, itemTimed: CITimed? = null,
itemLive: Boolean = false,
deletable: Boolean = true, deletable: Boolean = true,
editable: Boolean = true editable: Boolean = true
) = ) =
ChatItem( ChatItem(
chatDir = dir, chatDir = dir,
meta = CIMeta.getSample(id, ts, text, status, itemForwarded, itemDeleted, itemEdited, itemTimed, deletable, editable), meta = CIMeta.getSample(id, ts, text, status, sentViaProxy, itemForwarded, itemDeleted, itemEdited, itemTimed, itemLive, deletable, editable),
content = CIContent.SndMsgContent(msgContent = MsgContent.MCText(text)), content = CIContent.SndMsgContent(msgContent = MsgContent.MCText(text)),
quotedItem = quotedItem, quotedItem = quotedItem,
reactions = listOf(), reactions = listOf(),
@@ -1993,6 +2021,7 @@ data class ChatItem (
itemTs = Clock.System.now(), itemTs = Clock.System.now(),
itemText = generalGetString(MR.strings.deleted_description), itemText = generalGetString(MR.strings.deleted_description),
itemStatus = CIStatus.RcvRead(), itemStatus = CIStatus.RcvRead(),
sentViaProxy = null,
createdAt = Clock.System.now(), createdAt = Clock.System.now(),
updatedAt = Clock.System.now(), updatedAt = Clock.System.now(),
itemForwarded = null, itemForwarded = null,
@@ -2016,6 +2045,7 @@ data class ChatItem (
itemTs = Clock.System.now(), itemTs = Clock.System.now(),
itemText = "", itemText = "",
itemStatus = CIStatus.RcvRead(), itemStatus = CIStatus.RcvRead(),
sentViaProxy = null,
createdAt = Clock.System.now(), createdAt = Clock.System.now(),
updatedAt = Clock.System.now(), updatedAt = Clock.System.now(),
itemForwarded = null, itemForwarded = null,
@@ -2118,6 +2148,7 @@ data class CIMeta (
val itemTs: Instant, val itemTs: Instant,
val itemText: String, val itemText: String,
val itemStatus: CIStatus, val itemStatus: CIStatus,
val sentViaProxy: Boolean?,
val createdAt: Instant, val createdAt: Instant,
val updatedAt: Instant, val updatedAt: Instant,
val itemForwarded: CIForwardedFrom?, val itemForwarded: CIForwardedFrom?,
@@ -2144,7 +2175,7 @@ data class CIMeta (
companion object { companion object {
fun getSample( fun getSample(
id: Long, ts: Instant, text: String, status: CIStatus = CIStatus.SndNew(), id: Long, ts: Instant, text: String, status: CIStatus = CIStatus.SndNew(), sentViaProxy: Boolean? = null,
itemForwarded: CIForwardedFrom? = null, itemDeleted: CIDeleted? = null, itemEdited: Boolean = false, itemForwarded: CIForwardedFrom? = null, itemDeleted: CIDeleted? = null, itemEdited: Boolean = false,
itemTimed: CITimed? = null, itemLive: Boolean = false, deletable: Boolean = true, editable: Boolean = true itemTimed: CITimed? = null, itemLive: Boolean = false, deletable: Boolean = true, editable: Boolean = true
): CIMeta = ): CIMeta =
@@ -2153,6 +2184,7 @@ data class CIMeta (
itemTs = ts, itemTs = ts,
itemText = text, itemText = text,
itemStatus = status, itemStatus = status,
sentViaProxy = sentViaProxy,
createdAt = ts, createdAt = ts,
updatedAt = ts, updatedAt = ts,
itemForwarded = itemForwarded, itemForwarded = itemForwarded,
@@ -2171,6 +2203,7 @@ data class CIMeta (
itemTs = Clock.System.now(), itemTs = Clock.System.now(),
itemText = "invalid JSON", itemText = "invalid JSON",
itemStatus = CIStatus.SndNew(), itemStatus = CIStatus.SndNew(),
sentViaProxy = null,
createdAt = Clock.System.now(), createdAt = Clock.System.now(),
updatedAt = Clock.System.now(), updatedAt = Clock.System.now(),
itemForwarded = null, itemForwarded = null,
@@ -2227,7 +2260,8 @@ sealed class CIStatus {
@Serializable @SerialName("sndSent") class SndSent(val sndProgress: SndCIStatusProgress): CIStatus() @Serializable @SerialName("sndSent") class SndSent(val sndProgress: SndCIStatusProgress): CIStatus()
@Serializable @SerialName("sndRcvd") class SndRcvd(val msgRcptStatus: MsgReceiptStatus, val sndProgress: SndCIStatusProgress): CIStatus() @Serializable @SerialName("sndRcvd") class SndRcvd(val msgRcptStatus: MsgReceiptStatus, val sndProgress: SndCIStatusProgress): CIStatus()
@Serializable @SerialName("sndErrorAuth") class SndErrorAuth: CIStatus() @Serializable @SerialName("sndErrorAuth") class SndErrorAuth: CIStatus()
@Serializable @SerialName("sndError") class SndError(val agentError: String): CIStatus() @Serializable @SerialName("sndError") class CISSndError(val agentError: SndError): CIStatus()
@Serializable @SerialName("sndWarning") class SndWarning(val agentError: SndError): CIStatus()
@Serializable @SerialName("rcvNew") class RcvNew: CIStatus() @Serializable @SerialName("rcvNew") class RcvNew: CIStatus()
@Serializable @SerialName("rcvRead") class RcvRead: CIStatus() @Serializable @SerialName("rcvRead") class RcvRead: CIStatus()
@Serializable @SerialName("invalid") class Invalid(val text: String): CIStatus() @Serializable @SerialName("invalid") class Invalid(val text: String): CIStatus()
@@ -2251,7 +2285,8 @@ sealed class CIStatus {
MsgReceiptStatus.BadMsgHash -> MR.images.ic_double_check to Color.Red MsgReceiptStatus.BadMsgHash -> MR.images.ic_double_check to Color.Red
} }
is SndErrorAuth -> MR.images.ic_close to Color.Red is SndErrorAuth -> MR.images.ic_close to Color.Red
is SndError -> MR.images.ic_warning_filled to WarningYellow is CISSndError -> MR.images.ic_close to Color.Red
is SndWarning -> MR.images.ic_warning_filled to WarningOrange
is RcvNew -> MR.images.ic_circle_filled to primaryColor is RcvNew -> MR.images.ic_circle_filled to primaryColor
is RcvRead -> null is RcvRead -> null
is CIStatus.Invalid -> MR.images.ic_question_mark to metaColor is CIStatus.Invalid -> MR.images.ic_question_mark to metaColor
@@ -2262,13 +2297,48 @@ sealed class CIStatus {
is SndSent -> null is SndSent -> null
is SndRcvd -> null is SndRcvd -> null
is SndErrorAuth -> generalGetString(MR.strings.message_delivery_error_title) to generalGetString(MR.strings.message_delivery_error_desc) is SndErrorAuth -> generalGetString(MR.strings.message_delivery_error_title) to generalGetString(MR.strings.message_delivery_error_desc)
is SndError -> generalGetString(MR.strings.message_delivery_error_title) to (generalGetString(MR.strings.unknown_error) + ": $agentError") is CISSndError -> generalGetString(MR.strings.message_delivery_error_title) to agentError.errorInfo
is SndWarning -> generalGetString(MR.strings.message_delivery_warning_title) to agentError.errorInfo
is RcvNew -> null is RcvNew -> null
is RcvRead -> null is RcvRead -> null
is Invalid -> "Invalid status" to this.text is Invalid -> "Invalid status" to this.text
} }
} }
@Serializable
sealed class SndError {
@Serializable @SerialName("auth") class Auth: SndError()
@Serializable @SerialName("quota") class Quota: SndError()
@Serializable @SerialName("expired") class Expired: SndError()
@Serializable @SerialName("relay") class Relay(val srvError: SrvError): SndError()
@Serializable @SerialName("proxy") class Proxy(val proxyServer: String, val srvError: SrvError): SndError()
@Serializable @SerialName("proxyRelay") class ProxyRelay(val proxyServer: String, val srvError: SrvError): SndError()
@Serializable @SerialName("other") class Other(val sndError: String): SndError()
val errorInfo: String get() = when (this) {
is SndError.Auth -> generalGetString(MR.strings.snd_error_auth)
is SndError.Quota -> generalGetString(MR.strings.snd_error_quota)
is SndError.Expired -> generalGetString(MR.strings.snd_error_expired)
is SndError.Relay -> generalGetString(MR.strings.snd_error_relay).format(srvError.errorInfo)
is SndError.Proxy -> generalGetString(MR.strings.snd_error_proxy).format(proxyServer, srvError.errorInfo)
is SndError.ProxyRelay -> generalGetString(MR.strings.snd_error_proxy_relay).format(proxyServer, srvError.errorInfo)
is SndError.Other -> generalGetString(MR.strings.ci_status_other_error).format(sndError)
}
}
@Serializable
sealed class SrvError {
@Serializable @SerialName("host") class Host: SrvError()
@Serializable @SerialName("version") class Version: SrvError()
@Serializable @SerialName("other") class Other(val srvError: String): SrvError()
val errorInfo: String get() = when (this) {
is SrvError.Host -> generalGetString(MR.strings.srv_error_host)
is SrvError.Version -> generalGetString(MR.strings.srv_error_version)
is SrvError.Other -> srvError
}
}
@Serializable @Serializable
enum class MsgReceiptStatus { enum class MsgReceiptStatus {
@SerialName("ok") Ok, @SerialName("ok") Ok,
@@ -2593,6 +2663,7 @@ data class CIFile(
is CIFileStatus.RcvInvitation -> false is CIFileStatus.RcvInvitation -> false
is CIFileStatus.RcvAccepted -> false is CIFileStatus.RcvAccepted -> false
is CIFileStatus.RcvTransfer -> false is CIFileStatus.RcvTransfer -> false
is CIFileStatus.RcvAborted -> false
is CIFileStatus.RcvCancelled -> false is CIFileStatus.RcvCancelled -> false
is CIFileStatus.RcvComplete -> true is CIFileStatus.RcvComplete -> true
is CIFileStatus.RcvError -> false is CIFileStatus.RcvError -> false
@@ -2614,6 +2685,7 @@ data class CIFile(
is CIFileStatus.RcvInvitation -> null is CIFileStatus.RcvInvitation -> null
is CIFileStatus.RcvAccepted -> rcvCancelAction is CIFileStatus.RcvAccepted -> rcvCancelAction
is CIFileStatus.RcvTransfer -> rcvCancelAction is CIFileStatus.RcvTransfer -> rcvCancelAction
is CIFileStatus.RcvAborted -> null
is CIFileStatus.RcvCancelled -> null is CIFileStatus.RcvCancelled -> null
is CIFileStatus.RcvComplete -> null is CIFileStatus.RcvComplete -> null
is CIFileStatus.RcvError -> null is CIFileStatus.RcvError -> null
@@ -2653,6 +2725,12 @@ data class CIFile(
return res return res
} }
fun forwardingAllowed(): Boolean = when {
chatModel.connectedToRemote() && cachedRemoteFileRequests[fileSource] != false && loaded -> true
getLoadedFilePath(this) != null -> true
else -> false
}
companion object { companion object {
fun getSample( fun getSample(
fileId: Long = 1, fileId: Long = 1,
@@ -2788,6 +2866,7 @@ sealed class CIFileStatus {
@Serializable @SerialName("rcvInvitation") object RcvInvitation: CIFileStatus() @Serializable @SerialName("rcvInvitation") object RcvInvitation: CIFileStatus()
@Serializable @SerialName("rcvAccepted") object RcvAccepted: CIFileStatus() @Serializable @SerialName("rcvAccepted") object RcvAccepted: CIFileStatus()
@Serializable @SerialName("rcvTransfer") class RcvTransfer(val rcvProgress: Long, val rcvTotal: Long): CIFileStatus() @Serializable @SerialName("rcvTransfer") class RcvTransfer(val rcvProgress: Long, val rcvTotal: Long): CIFileStatus()
@Serializable @SerialName("rcvAborted") object RcvAborted: CIFileStatus()
@Serializable @SerialName("rcvComplete") object RcvComplete: CIFileStatus() @Serializable @SerialName("rcvComplete") object RcvComplete: CIFileStatus()
@Serializable @SerialName("rcvCancelled") object RcvCancelled: CIFileStatus() @Serializable @SerialName("rcvCancelled") object RcvCancelled: CIFileStatus()
@Serializable @SerialName("rcvError") object RcvError: CIFileStatus() @Serializable @SerialName("rcvError") object RcvError: CIFileStatus()
@@ -2802,6 +2881,7 @@ sealed class CIFileStatus {
is RcvInvitation -> false is RcvInvitation -> false
is RcvAccepted -> false is RcvAccepted -> false
is RcvTransfer -> false is RcvTransfer -> false
is RcvAborted -> false
is RcvComplete -> false is RcvComplete -> false
is RcvCancelled -> false is RcvCancelled -> false
is RcvError -> false is RcvError -> false
@@ -3348,7 +3428,8 @@ data class ChatItemVersion(
@Serializable @Serializable
data class MemberDeliveryStatus( data class MemberDeliveryStatus(
val groupMemberId: Long, val groupMemberId: Long,
val memberDeliveryStatus: CIStatus val memberDeliveryStatus: CIStatus,
val sentViaProxy: Boolean?
) )
enum class NotificationPreviewMode { enum class NotificationPreviewMode {
@@ -12,6 +12,7 @@ import dev.icerock.moko.resources.compose.painterResource
import chat.simplex.common.platform.* import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.* import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.call.* import chat.simplex.common.views.call.*
import chat.simplex.common.views.chat.group.toggleShowMemberMessages
import chat.simplex.common.views.migration.MigrationFileLinkData import chat.simplex.common.views.migration.MigrationFileLinkData
import chat.simplex.common.views.onboarding.OnboardingStage import chat.simplex.common.views.onboarding.OnboardingStage
import chat.simplex.common.views.usersettings.* import chat.simplex.common.views.usersettings.*
@@ -25,8 +26,7 @@ import kotlinx.coroutines.sync.withLock
import kotlinx.datetime.Clock import kotlinx.datetime.Clock
import kotlinx.datetime.Instant import kotlinx.datetime.Instant
import kotlinx.serialization.* import kotlinx.serialization.*
import kotlinx.serialization.builtins.MapSerializer import kotlinx.serialization.builtins.*
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
import java.util.Date import java.util.Date
@@ -106,6 +106,7 @@ class AppPreferences {
val privacySaveLastDraft = mkBoolPreference(SHARED_PREFS_PRIVACY_SAVE_LAST_DRAFT, true) val privacySaveLastDraft = mkBoolPreference(SHARED_PREFS_PRIVACY_SAVE_LAST_DRAFT, true)
val privacyDeliveryReceiptsSet = mkBoolPreference(SHARED_PREFS_PRIVACY_DELIVERY_RECEIPTS_SET, false) val privacyDeliveryReceiptsSet = mkBoolPreference(SHARED_PREFS_PRIVACY_DELIVERY_RECEIPTS_SET, false)
val privacyEncryptLocalFiles = mkBoolPreference(SHARED_PREFS_PRIVACY_ENCRYPT_LOCAL_FILES, true) val privacyEncryptLocalFiles = mkBoolPreference(SHARED_PREFS_PRIVACY_ENCRYPT_LOCAL_FILES, true)
val privacyAskToApproveRelays = mkBoolPreference(SHARED_PREFS_PRIVACY_ASK_TO_APPROVE_RELAYS, true)
val experimentalCalls = mkBoolPreference(SHARED_PREFS_EXPERIMENTAL_CALLS, false) val experimentalCalls = mkBoolPreference(SHARED_PREFS_EXPERIMENTAL_CALLS, false)
val showUnreadAndFavorites = mkBoolPreference(SHARED_PREFS_SHOW_UNREAD_AND_FAVORITES, false) val showUnreadAndFavorites = mkBoolPreference(SHARED_PREFS_SHOW_UNREAD_AND_FAVORITES, false)
val chatArchiveName = mkStrPreference(SHARED_PREFS_CHAT_ARCHIVE_NAME, null) val chatArchiveName = mkStrPreference(SHARED_PREFS_CHAT_ARCHIVE_NAME, null)
@@ -130,6 +131,8 @@ class AppPreferences {
}, },
set = fun(mode: TransportSessionMode) { _networkSessionMode.set(mode.name) } set = fun(mode: TransportSessionMode) { _networkSessionMode.set(mode.name) }
) )
val networkSMPProxyMode = mkStrPreference(SHARED_PREFS_NETWORK_SMP_PROXY_MODE, SMPProxyMode.Never.name)
val networkSMPProxyFallback = mkStrPreference(SHARED_PREFS_NETWORK_SMP_PROXY_FALLBACK, SMPProxyFallback.Allow.name)
val networkHostMode = mkStrPreference(SHARED_PREFS_NETWORK_HOST_MODE, HostMode.OnionViaSocks.name) val networkHostMode = mkStrPreference(SHARED_PREFS_NETWORK_HOST_MODE, HostMode.OnionViaSocks.name)
val networkRequiredHostMode = mkBoolPreference(SHARED_PREFS_NETWORK_REQUIRED_HOST_MODE, false) val networkRequiredHostMode = mkBoolPreference(SHARED_PREFS_NETWORK_REQUIRED_HOST_MODE, false)
val networkTCPConnectTimeout = mkTimeoutPreference(SHARED_PREFS_NETWORK_TCP_CONNECT_TIMEOUT, NetCfg.defaults.tcpConnectTimeout, NetCfg.proxyDefaults.tcpConnectTimeout) val networkTCPConnectTimeout = mkTimeoutPreference(SHARED_PREFS_NETWORK_TCP_CONNECT_TIMEOUT, NetCfg.defaults.tcpConnectTimeout, NetCfg.proxyDefaults.tcpConnectTimeout)
@@ -164,13 +167,20 @@ class AppPreferences {
val selfDestruct = mkBoolPreference(SHARED_PREFS_SELF_DESTRUCT, false) val selfDestruct = mkBoolPreference(SHARED_PREFS_SELF_DESTRUCT, false)
val selfDestructDisplayName = mkStrPreference(SHARED_PREFS_SELF_DESTRUCT_DISPLAY_NAME, null) val selfDestructDisplayName = mkStrPreference(SHARED_PREFS_SELF_DESTRUCT_DISPLAY_NAME, null)
val currentTheme = mkStrPreference(SHARED_PREFS_CURRENT_THEME, DefaultTheme.SYSTEM.name) val currentTheme = mkStrPreference(SHARED_PREFS_CURRENT_THEME, DefaultTheme.SYSTEM_THEME_NAME)
val systemDarkTheme = mkStrPreference(SHARED_PREFS_SYSTEM_DARK_THEME, DefaultTheme.SIMPLEX.name) val systemDarkTheme = mkStrPreference(SHARED_PREFS_SYSTEM_DARK_THEME, DefaultTheme.SIMPLEX.themeName)
val themeOverrides = mkMapPreference(SHARED_PREFS_THEMES, mapOf(), encode = { val currentThemeIds = mkMapPreference(SHARED_PREFS_CURRENT_THEME_IDs, mapOf(), encode = {
json.encodeToString(MapSerializer(String.serializer(), String.serializer()), it)
}, decode = {
json.decodeFromString(MapSerializer(String.serializer(), String.serializer()), it)
})
// Deprecated. Remove key from preferences in 2025
val themeOverridesOld = mkMapPreference(SHARED_PREFS_THEMES_OLD, mapOf(), encode = {
json.encodeToString(MapSerializer(String.serializer(), ThemeOverrides.serializer()), it) json.encodeToString(MapSerializer(String.serializer(), ThemeOverrides.serializer()), it)
}, decode = { }, decode = {
json.decodeFromString(MapSerializer(String.serializer(), ThemeOverrides.serializer()), it) jsonCoerceInputValues.decodeFromString(MapSerializer(String.serializer(), ThemeOverrides.serializer()), it)
}, settingsThemes) }, settingsThemes)
val themeOverrides = mkThemeOverridesPreference()
val profileImageCornerRadius = mkFloatPreference(SHARED_PREFS_PROFILE_IMAGE_CORNER_RADIUS, 22.5f) val profileImageCornerRadius = mkFloatPreference(SHARED_PREFS_PROFILE_IMAGE_CORNER_RADIUS, 22.5f)
val whatsNewVersion = mkStrPreference(SHARED_PREFS_WHATS_NEW_VERSION, null) val whatsNewVersion = mkStrPreference(SHARED_PREFS_WHATS_NEW_VERSION, null)
@@ -185,6 +195,8 @@ class AppPreferences {
val desktopWindowState = mkStrPreference(SHARED_PREFS_DESKTOP_WINDOW_STATE, null) val desktopWindowState = mkStrPreference(SHARED_PREFS_DESKTOP_WINDOW_STATE, null)
val showSentViaProxy = mkBoolPreference(SHARED_PREFS_SHOW_SENT_VIA_RPOXY, false)
val iosCallKitEnabled = mkBoolPreference(SHARED_PREFS_IOS_CALL_KIT_ENABLED, true) val iosCallKitEnabled = mkBoolPreference(SHARED_PREFS_IOS_CALL_KIT_ENABLED, true)
val iosCallKitCallsInRecents = mkBoolPreference(SHARED_PREFS_IOS_CALL_KIT_CALLS_IN_RECENTS, false) val iosCallKitCallsInRecents = mkBoolPreference(SHARED_PREFS_IOS_CALL_KIT_CALLS_IN_RECENTS, false)
@@ -263,6 +275,12 @@ class AppPreferences {
set = fun(value) = prefs.putString(prefName, encode(value)) set = fun(value) = prefs.putString(prefName, encode(value))
) )
private fun mkThemeOverridesPreference(): SharedPreference<List<ThemeOverrides>> =
SharedPreference(
get = fun() = themeOverridesStore ?: (readThemeOverrides()).also { themeOverridesStore = it },
set = fun(value) { if (writeThemeOverrides(value)) { themeOverridesStore = value } }
)
companion object { companion object {
const val SHARED_PREFS_ID = "chat.simplex.app.SIMPLEX_APP_PREFS" const val SHARED_PREFS_ID = "chat.simplex.app.SIMPLEX_APP_PREFS"
internal const val SHARED_PREFS_THEMES_ID = "chat.simplex.app.THEMES" internal const val SHARED_PREFS_THEMES_ID = "chat.simplex.app.THEMES"
@@ -288,6 +306,7 @@ class AppPreferences {
private const val SHARED_PREFS_PRIVACY_SAVE_LAST_DRAFT = "PrivacySaveLastDraft" private const val SHARED_PREFS_PRIVACY_SAVE_LAST_DRAFT = "PrivacySaveLastDraft"
private const val SHARED_PREFS_PRIVACY_DELIVERY_RECEIPTS_SET = "PrivacyDeliveryReceiptsSet" private const val SHARED_PREFS_PRIVACY_DELIVERY_RECEIPTS_SET = "PrivacyDeliveryReceiptsSet"
private const val SHARED_PREFS_PRIVACY_ENCRYPT_LOCAL_FILES = "PrivacyEncryptLocalFiles" private const val SHARED_PREFS_PRIVACY_ENCRYPT_LOCAL_FILES = "PrivacyEncryptLocalFiles"
private const val SHARED_PREFS_PRIVACY_ASK_TO_APPROVE_RELAYS = "PrivacyAskToApproveRelays"
const val SHARED_PREFS_PRIVACY_FULL_BACKUP = "FullBackup" const val SHARED_PREFS_PRIVACY_FULL_BACKUP = "FullBackup"
private const val SHARED_PREFS_EXPERIMENTAL_CALLS = "ExperimentalCalls" private const val SHARED_PREFS_EXPERIMENTAL_CALLS = "ExperimentalCalls"
private const val SHARED_PREFS_SHOW_UNREAD_AND_FAVORITES = "ShowUnreadAndFavorites" private const val SHARED_PREFS_SHOW_UNREAD_AND_FAVORITES = "ShowUnreadAndFavorites"
@@ -306,6 +325,8 @@ class AppPreferences {
private const val SHARED_PREFS_NETWORK_USE_SOCKS_PROXY = "NetworkUseSocksProxy" private const val SHARED_PREFS_NETWORK_USE_SOCKS_PROXY = "NetworkUseSocksProxy"
private const val SHARED_PREFS_NETWORK_PROXY_HOST_PORT = "NetworkProxyHostPort" private const val SHARED_PREFS_NETWORK_PROXY_HOST_PORT = "NetworkProxyHostPort"
private const val SHARED_PREFS_NETWORK_SESSION_MODE = "NetworkSessionMode" private const val SHARED_PREFS_NETWORK_SESSION_MODE = "NetworkSessionMode"
private const val SHARED_PREFS_NETWORK_SMP_PROXY_MODE = "NetworkSMPProxyMode"
private const val SHARED_PREFS_NETWORK_SMP_PROXY_FALLBACK = "NetworkSMPProxyFallback"
private const val SHARED_PREFS_NETWORK_HOST_MODE = "NetworkHostMode" private const val SHARED_PREFS_NETWORK_HOST_MODE = "NetworkHostMode"
private const val SHARED_PREFS_NETWORK_REQUIRED_HOST_MODE = "NetworkRequiredHostMode" private const val SHARED_PREFS_NETWORK_REQUIRED_HOST_MODE = "NetworkRequiredHostMode"
private const val SHARED_PREFS_NETWORK_TCP_CONNECT_TIMEOUT = "NetworkTCPConnectTimeout" private const val SHARED_PREFS_NETWORK_TCP_CONNECT_TIMEOUT = "NetworkTCPConnectTimeout"
@@ -336,8 +357,10 @@ class AppPreferences {
private const val SHARED_PREFS_SELF_DESTRUCT_DISPLAY_NAME = "LocalAuthenticationSelfDestructDisplayName" private const val SHARED_PREFS_SELF_DESTRUCT_DISPLAY_NAME = "LocalAuthenticationSelfDestructDisplayName"
private const val SHARED_PREFS_PQ_EXPERIMENTAL_ENABLED = "PQExperimentalEnabled" // no longer used private const val SHARED_PREFS_PQ_EXPERIMENTAL_ENABLED = "PQExperimentalEnabled" // no longer used
private const val SHARED_PREFS_CURRENT_THEME = "CurrentTheme" private const val SHARED_PREFS_CURRENT_THEME = "CurrentTheme"
private const val SHARED_PREFS_CURRENT_THEME_IDs = "CurrentThemeIds"
private const val SHARED_PREFS_SYSTEM_DARK_THEME = "SystemDarkTheme" private const val SHARED_PREFS_SYSTEM_DARK_THEME = "SystemDarkTheme"
private const val SHARED_PREFS_THEMES = "Themes" private const val SHARED_PREFS_THEMES_OLD = "Themes"
private const val SHARED_PREFS_THEME_OVERRIDES = "ThemeOverrides"
private const val SHARED_PREFS_PROFILE_IMAGE_CORNER_RADIUS = "ProfileImageCornerRadius" private const val SHARED_PREFS_PROFILE_IMAGE_CORNER_RADIUS = "ProfileImageCornerRadius"
private const val SHARED_PREFS_WHATS_NEW_VERSION = "WhatsNewVersion" private const val SHARED_PREFS_WHATS_NEW_VERSION = "WhatsNewVersion"
private const val SHARED_PREFS_LAST_MIGRATED_VERSION_CODE = "LastMigratedVersionCode" private const val SHARED_PREFS_LAST_MIGRATED_VERSION_CODE = "LastMigratedVersionCode"
@@ -348,9 +371,12 @@ class AppPreferences {
private const val SHARED_PREFS_CONNECT_REMOTE_VIA_MULTICAST_AUTO = "ConnectRemoteViaMulticastAuto" private const val SHARED_PREFS_CONNECT_REMOTE_VIA_MULTICAST_AUTO = "ConnectRemoteViaMulticastAuto"
private const val SHARED_PREFS_OFFER_REMOTE_MULTICAST = "OfferRemoteMulticast" private const val SHARED_PREFS_OFFER_REMOTE_MULTICAST = "OfferRemoteMulticast"
private const val SHARED_PREFS_DESKTOP_WINDOW_STATE = "DesktopWindowState" private const val SHARED_PREFS_DESKTOP_WINDOW_STATE = "DesktopWindowState"
private const val SHARED_PREFS_SHOW_SENT_VIA_RPOXY = "showSentViaProxy"
private const val SHARED_PREFS_IOS_CALL_KIT_ENABLED = "iOSCallKitEnabled" private const val SHARED_PREFS_IOS_CALL_KIT_ENABLED = "iOSCallKitEnabled"
private const val SHARED_PREFS_IOS_CALL_KIT_CALLS_IN_RECENTS = "iOSCallKitCallsInRecents" private const val SHARED_PREFS_IOS_CALL_KIT_CALLS_IN_RECENTS = "iOSCallKitCallsInRecents"
private var themeOverridesStore: List<ThemeOverrides>? = null
} }
} }
@@ -435,8 +461,13 @@ object ChatController {
Log.d(TAG, "startChatWithTemporaryDatabase") Log.d(TAG, "startChatWithTemporaryDatabase")
val migrationActiveUser = apiGetActiveUser(null, ctrl) ?: apiCreateActiveUser(null, Profile(displayName = "Temp", fullName = ""), ctrl = ctrl) val migrationActiveUser = apiGetActiveUser(null, ctrl) ?: apiCreateActiveUser(null, Profile(displayName = "Temp", fullName = ""), ctrl = ctrl)
apiSetNetworkConfig(netCfg, ctrl) apiSetNetworkConfig(netCfg, ctrl)
apiSetTempFolder(getMigrationTempFilesDirectory().absolutePath, ctrl) apiSetAppFilePaths(
apiSetFilesFolder(getMigrationTempFilesDirectory().absolutePath, ctrl) getMigrationTempFilesDirectory().absolutePath,
getMigrationTempFilesDirectory().absolutePath,
wallpapersDir.parentFile.absolutePath,
remoteHostsDir.absolutePath,
ctrl
)
apiStartChat(ctrl) apiStartChat(ctrl)
return migrationActiveUser return migrationActiveUser
} }
@@ -655,22 +686,10 @@ object ChatController {
} }
} }
suspend fun apiSetTempFolder(tempFolder: String, ctrl: ChatCtrl? = null) { suspend fun apiSetAppFilePaths(filesFolder: String, tempFolder: String, assetsFolder: String, remoteHostsFolder: String, ctrl: ChatCtrl? = null) {
val r = sendCmd(null, CC.SetTempFolder(tempFolder), ctrl) val r = sendCmd(null, CC.ApiSetAppFilePaths(filesFolder, tempFolder, assetsFolder, remoteHostsFolder), ctrl)
if (r is CR.CmdOk) return if (r is CR.CmdOk) return
throw Exception("failed to set temp folder: ${r.responseType} ${r.details}") throw Exception("failed to set app file paths: ${r.responseType} ${r.details}")
}
suspend fun apiSetFilesFolder(filesFolder: String, ctrl: ChatCtrl? = null) {
val r = sendCmd(null, CC.SetFilesFolder(filesFolder), ctrl)
if (r is CR.CmdOk) return
throw Exception("failed to set files folder: ${r.responseType} ${r.details}")
}
suspend fun apiSetRemoteHostsFolder(remoteHostsFolder: String) {
val r = sendCmd(null, CC.SetRemoteHostsFolder(remoteHostsFolder))
if (r is CR.CmdOk) return
throw Exception("failed to set remote hosts folder: ${r.responseType} ${r.details}")
} }
suspend fun apiSetEncryptLocalFiles(enable: Boolean) = sendCommandOkResp(null, CC.ApiSetEncryptLocalFiles(enable)) suspend fun apiSetEncryptLocalFiles(enable: Boolean) = sendCommandOkResp(null, CC.ApiSetEncryptLocalFiles(enable))
@@ -775,8 +794,8 @@ object ChatController {
} }
} }
suspend fun apiForwardChatItem(rh: Long?, toChatType: ChatType, toChatId: Long, fromChatType: ChatType, fromChatId: Long, itemId: Long): ChatItem? { suspend fun apiForwardChatItem(rh: Long?, toChatType: ChatType, toChatId: Long, fromChatType: ChatType, fromChatId: Long, itemId: Long, ttl: Int?): ChatItem? {
val cmd = CC.ApiForwardChatItem(toChatType, toChatId, fromChatType, fromChatId, itemId) val cmd = CC.ApiForwardChatItem(toChatType, toChatId, fromChatType, fromChatId, itemId, ttl)
return processSendMessageCmd(rh, cmd)?.chatItem return processSendMessageCmd(rh, cmd)?.chatItem
} }
@@ -1156,6 +1175,20 @@ object ChatController {
return null return null
} }
suspend fun apiSetUserUIThemes(rh: Long?, userId: Long, themes: ThemeModeOverrides?): Boolean {
val r = sendCmd(rh, CC.ApiSetUserUIThemes(userId, themes))
if (r is CR.CmdOk) return true
Log.e(TAG, "apiSetUserUIThemes bad response: ${r.responseType} ${r.details}")
return false
}
suspend fun apiSetChatUIThemes(rh: Long?, chatId: ChatId, themes: ThemeModeOverrides?): Boolean {
val r = sendCmd(rh, CC.ApiSetChatUIThemes(chatId, themes))
if (r is CR.CmdOk) return true
Log.e(TAG, "apiSetChatUIThemes bad response: ${r.responseType} ${r.details}")
return false
}
suspend fun apiCreateUserAddress(rh: Long?): String? { suspend fun apiCreateUserAddress(rh: Long?): String? {
val userId = kotlin.runCatching { currentUserId("apiCreateUserAddress") }.getOrElse { return null } val userId = kotlin.runCatching { currentUserId("apiCreateUserAddress") }.getOrElse { return null }
val r = sendCmd(rh, CC.ApiCreateMyAddress(userId)) val r = sendCmd(rh, CC.ApiCreateMyAddress(userId))
@@ -1330,9 +1363,9 @@ object ChatController {
} }
} }
suspend fun apiReceiveFile(rh: Long?, fileId: Long, encrypted: Boolean, inline: Boolean? = null, auto: Boolean = false): AChatItem? { suspend fun apiReceiveFile(rh: Long?, fileId: Long, userApprovedRelays: Boolean, encrypted: Boolean, inline: Boolean? = null, auto: Boolean = false): AChatItem? {
// -1 here is to override default behavior of providing current remote host id because file can be asked by local device while remote is connected // -1 here is to override default behavior of providing current remote host id because file can be asked by local device while remote is connected
val r = sendCmd(rh, CC.ReceiveFile(fileId, encrypted, inline)) val r = sendCmd(rh, CC.ReceiveFile(fileId, userApprovedRelays = userApprovedRelays, encrypt = encrypted, inline = inline))
return when (r) { return when (r) {
is CR.RcvFileAccepted -> r.chatItem is CR.RcvFileAccepted -> r.chatItem
is CR.RcvFileAcceptedSndCancelled -> { is CR.RcvFileAcceptedSndCancelled -> {
@@ -1351,7 +1384,23 @@ object ChatController {
val maybeChatError = chatError(r) val maybeChatError = chatError(r)
if (maybeChatError is ChatErrorType.FileCancelled || maybeChatError is ChatErrorType.FileAlreadyReceiving) { if (maybeChatError is ChatErrorType.FileCancelled || maybeChatError is ChatErrorType.FileAlreadyReceiving) {
Log.d(TAG, "apiReceiveFile ignoring FileCancelled or FileAlreadyReceiving error") Log.d(TAG, "apiReceiveFile ignoring FileCancelled or FileAlreadyReceiving error")
} else { } else if (maybeChatError is ChatErrorType.FileNotApproved) {
Log.d(TAG, "apiReceiveFile FileNotApproved error")
if (!auto) {
val srvs = maybeChatError.unknownServers.map{ serverHostname(it) }
AlertManager.shared.showAlertDialog(
title = generalGetString(MR.strings.file_not_approved_title),
text = generalGetString(MR.strings.file_not_approved_descr).format(srvs.sorted().joinToString(separator = ", ")),
confirmText = generalGetString(MR.strings.download_file),
onConfirm = {
val user = chatModel.currentUser.value
if (user != null) {
withBGApi { chatModel.controller.receiveFile(rh, user, fileId, userApprovedRelays = true) }
}
},
)
}
} else if (!auto) {
apiErrorAlert("apiReceiveFile", generalGetString(MR.strings.error_receiving_file), r) apiErrorAlert("apiReceiveFile", generalGetString(MR.strings.error_receiving_file), r)
} }
} }
@@ -2031,13 +2080,21 @@ object ChatController {
} }
} }
is CR.ContactSwitch -> is CR.ContactSwitch ->
chatModel.updateContactConnectionStats(rhId, r.contact, r.switchProgress.connectionStats) if (active(r.user)) {
chatModel.updateContactConnectionStats(rhId, r.contact, r.switchProgress.connectionStats)
}
is CR.GroupMemberSwitch -> is CR.GroupMemberSwitch ->
chatModel.updateGroupMemberConnectionStats(rhId, r.groupInfo, r.member, r.switchProgress.connectionStats) if (active(r.user)) {
chatModel.updateGroupMemberConnectionStats(rhId, r.groupInfo, r.member, r.switchProgress.connectionStats)
}
is CR.ContactRatchetSync -> is CR.ContactRatchetSync ->
chatModel.updateContactConnectionStats(rhId, r.contact, r.ratchetSyncProgress.connectionStats) if (active(r.user)) {
chatModel.updateContactConnectionStats(rhId, r.contact, r.ratchetSyncProgress.connectionStats)
}
is CR.GroupMemberRatchetSync -> is CR.GroupMemberRatchetSync ->
chatModel.updateGroupMemberConnectionStats(rhId, r.groupInfo, r.member, r.ratchetSyncProgress.connectionStats) if (active(r.user)) {
chatModel.updateGroupMemberConnectionStats(rhId, r.groupInfo, r.member, r.ratchetSyncProgress.connectionStats)
}
is CR.RemoteHostSessionCode -> { is CR.RemoteHostSessionCode -> {
chatModel.remoteHostPairing.value = r.remoteHost_ to RemoteHostSessionState.PendingConfirmation(r.sessionCode) chatModel.remoteHostPairing.value = r.remoteHost_ to RemoteHostSessionState.PendingConfirmation(r.sessionCode)
} }
@@ -2046,6 +2103,11 @@ object ChatController {
chatModel.currentRemoteHost.value = r.remoteHost chatModel.currentRemoteHost.value = r.remoteHost
switchUIRemoteHost(r.remoteHost.remoteHostId) switchUIRemoteHost(r.remoteHost.remoteHostId)
} }
is CR.ContactDisabled -> {
if (active(r.user)) {
chatModel.updateContact(rhId, r.contact)
}
}
is CR.RemoteHostStopped -> { is CR.RemoteHostStopped -> {
val disconnectedHost = chatModel.remoteHosts.firstOrNull { it.remoteHostId == r.remoteHostId_ } val disconnectedHost = chatModel.remoteHosts.firstOrNull { it.remoteHostId == r.remoteHostId_ }
chatModel.remoteHostPairing.value = null chatModel.remoteHostPairing.value = null
@@ -2196,9 +2258,14 @@ object ChatController {
} }
} }
suspend fun receiveFile(rhId: Long?, user: UserLike, fileId: Long, auto: Boolean = false) { suspend fun receiveFile(rhId: Long?, user: UserLike, fileId: Long, userApprovedRelays: Boolean = false, auto: Boolean = false) {
val encrypted = appPrefs.privacyEncryptLocalFiles.get() val chatItem = apiReceiveFile(
val chatItem = apiReceiveFile(rhId, fileId, encrypted = encrypted, auto = auto) rhId,
fileId,
userApprovedRelays = userApprovedRelays || !appPrefs.privacyAskToApproveRelays.get(),
encrypted = appPrefs.privacyEncryptLocalFiles.get(),
auto = auto
)
if (chatItem != null) { if (chatItem != null) {
chatItemSimpleUpdate(rhId, user, chatItem) chatItemSimpleUpdate(rhId, user, chatItem)
} }
@@ -2302,6 +2369,8 @@ object ChatController {
val hostMode = HostMode.valueOf(appPrefs.networkHostMode.get()!!) val hostMode = HostMode.valueOf(appPrefs.networkHostMode.get()!!)
val requiredHostMode = appPrefs.networkRequiredHostMode.get() val requiredHostMode = appPrefs.networkRequiredHostMode.get()
val sessionMode = appPrefs.networkSessionMode.get() val sessionMode = appPrefs.networkSessionMode.get()
val smpProxyMode = SMPProxyMode.valueOf(appPrefs.networkSMPProxyMode.get()!!)
val smpProxyFallback = SMPProxyFallback.valueOf(appPrefs.networkSMPProxyFallback.get()!!)
val tcpConnectTimeout = appPrefs.networkTCPConnectTimeout.get() val tcpConnectTimeout = appPrefs.networkTCPConnectTimeout.get()
val tcpTimeout = appPrefs.networkTCPTimeout.get() val tcpTimeout = appPrefs.networkTCPTimeout.get()
val tcpTimeoutPerKb = appPrefs.networkTCPTimeoutPerKb.get() val tcpTimeoutPerKb = appPrefs.networkTCPTimeoutPerKb.get()
@@ -2322,6 +2391,8 @@ object ChatController {
hostMode = hostMode, hostMode = hostMode,
requiredHostMode = requiredHostMode, requiredHostMode = requiredHostMode,
sessionMode = sessionMode, sessionMode = sessionMode,
smpProxyMode = smpProxyMode,
smpProxyFallback = smpProxyFallback,
tcpConnectTimeout = tcpConnectTimeout, tcpConnectTimeout = tcpConnectTimeout,
tcpTimeout = tcpTimeout, tcpTimeout = tcpTimeout,
tcpTimeoutPerKb = tcpTimeoutPerKb, tcpTimeoutPerKb = tcpTimeoutPerKb,
@@ -2340,6 +2411,8 @@ object ChatController {
appPrefs.networkHostMode.set(cfg.hostMode.name) appPrefs.networkHostMode.set(cfg.hostMode.name)
appPrefs.networkRequiredHostMode.set(cfg.requiredHostMode) appPrefs.networkRequiredHostMode.set(cfg.requiredHostMode)
appPrefs.networkSessionMode.set(cfg.sessionMode) appPrefs.networkSessionMode.set(cfg.sessionMode)
appPrefs.networkSMPProxyMode.set(cfg.smpProxyMode.name)
appPrefs.networkSMPProxyFallback.set(cfg.smpProxyFallback.name)
appPrefs.networkTCPConnectTimeout.set(cfg.tcpConnectTimeout) appPrefs.networkTCPConnectTimeout.set(cfg.tcpConnectTimeout)
appPrefs.networkTCPTimeout.set(cfg.tcpTimeout) appPrefs.networkTCPTimeout.set(cfg.tcpTimeout)
appPrefs.networkTCPTimeoutPerKb.set(cfg.tcpTimeoutPerKb) appPrefs.networkTCPTimeoutPerKb.set(cfg.tcpTimeoutPerKb)
@@ -2387,9 +2460,8 @@ sealed class CC {
class ApiDeleteUser(val userId: Long, val delSMPQueues: Boolean, val viewPwd: String?): CC() class ApiDeleteUser(val userId: Long, val delSMPQueues: Boolean, val viewPwd: String?): CC()
class StartChat(val mainApp: Boolean): CC() class StartChat(val mainApp: Boolean): CC()
class ApiStopChat: CC() class ApiStopChat: CC()
class SetTempFolder(val tempFolder: String): CC() @Serializable
class SetFilesFolder(val filesFolder: String): CC() class ApiSetAppFilePaths(val appFilesFolder: String, val appTempFolder: String, val appAssetsFolder: String, val appRemoteHostsFolder: String): CC()
class SetRemoteHostsFolder(val remoteHostsFolder: String): CC()
class ApiSetEncryptLocalFiles(val enable: Boolean): CC() class ApiSetEncryptLocalFiles(val enable: Boolean): CC()
class ApiExportArchive(val config: ArchiveConfig): CC() class ApiExportArchive(val config: ArchiveConfig): CC()
class ApiImportArchive(val config: ArchiveConfig): CC() class ApiImportArchive(val config: ArchiveConfig): CC()
@@ -2407,7 +2479,7 @@ sealed class CC {
class ApiDeleteChatItem(val type: ChatType, val id: Long, val itemId: Long, val mode: CIDeleteMode): CC() class ApiDeleteChatItem(val type: ChatType, val id: Long, val itemId: Long, val mode: CIDeleteMode): CC()
class ApiDeleteMemberChatItem(val groupId: Long, val groupMemberId: Long, val itemId: Long): CC() class ApiDeleteMemberChatItem(val groupId: Long, val groupMemberId: Long, val itemId: Long): 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 itemId: Long, val add: Boolean, val reaction: MsgReaction): CC()
class ApiForwardChatItem(val toChatType: ChatType, val toChatId: Long, val fromChatType: ChatType, val fromChatId: Long, val itemId: Long): CC() class ApiForwardChatItem(val toChatType: ChatType, val toChatId: Long, val fromChatType: ChatType, val fromChatId: Long, val itemId: Long, val ttl: Int?): CC()
class ApiNewGroup(val userId: Long, val incognito: Boolean, val groupProfile: GroupProfile): 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 ApiAddMember(val groupId: Long, val contactId: Long, val memberRole: GroupMemberRole): CC()
class ApiJoinGroup(val groupId: Long): CC() class ApiJoinGroup(val groupId: Long): CC()
@@ -2457,6 +2529,8 @@ sealed class CC {
class ApiSetContactPrefs(val contactId: Long, val prefs: ChatPreferences): CC() class ApiSetContactPrefs(val contactId: Long, val prefs: ChatPreferences): CC()
class ApiSetContactAlias(val contactId: Long, val localAlias: String): CC() class ApiSetContactAlias(val contactId: Long, val localAlias: String): CC()
class ApiSetConnectionAlias(val connId: Long, val localAlias: String): CC() class ApiSetConnectionAlias(val connId: Long, val localAlias: String): CC()
class ApiSetUserUIThemes(val userId: Long, val themes: ThemeModeOverrides?): CC()
class ApiSetChatUIThemes(val chatId: String, val themes: ThemeModeOverrides?): CC()
class ApiCreateMyAddress(val userId: Long): CC() class ApiCreateMyAddress(val userId: Long): CC()
class ApiDeleteMyAddress(val userId: Long): CC() class ApiDeleteMyAddress(val userId: Long): CC()
class ApiShowMyAddress(val userId: Long): CC() class ApiShowMyAddress(val userId: Long): CC()
@@ -2475,7 +2549,7 @@ sealed class CC {
class ApiRejectContact(val contactReqId: Long): CC() class ApiRejectContact(val contactReqId: Long): CC()
class ApiChatRead(val type: ChatType, val id: Long, val range: ItemRange): CC() class ApiChatRead(val type: ChatType, val id: Long, val range: ItemRange): CC()
class ApiChatUnread(val type: ChatType, val id: Long, val unreadChat: Boolean): CC() class ApiChatUnread(val type: ChatType, val id: Long, val unreadChat: Boolean): CC()
class ReceiveFile(val fileId: Long, val encrypt: Boolean, val inline: Boolean?): CC() class ReceiveFile(val fileId: Long, val userApprovedRelays: Boolean, val encrypt: Boolean, val inline: Boolean?): CC()
class CancelFile(val fileId: Long): CC() class CancelFile(val fileId: Long): CC()
// Remote control // Remote control
class SetLocalDeviceName(val displayName: String): CC() class SetLocalDeviceName(val displayName: String): CC()
@@ -2524,9 +2598,7 @@ sealed class CC {
is ApiDeleteUser -> "/_delete user $userId del_smp=${onOff(delSMPQueues)}${maybePwd(viewPwd)}" is ApiDeleteUser -> "/_delete user $userId del_smp=${onOff(delSMPQueues)}${maybePwd(viewPwd)}"
is StartChat -> "/_start main=${onOff(mainApp)}" is StartChat -> "/_start main=${onOff(mainApp)}"
is ApiStopChat -> "/_stop" is ApiStopChat -> "/_stop"
is SetTempFolder -> "/_temp_folder $tempFolder" is ApiSetAppFilePaths -> "/set file paths ${json.encodeToString(this)}"
is SetFilesFolder -> "/_files_folder $filesFolder"
is SetRemoteHostsFolder -> "/remote_hosts_folder $remoteHostsFolder"
is ApiSetEncryptLocalFiles -> "/_files_encrypt ${onOff(enable)}" is ApiSetEncryptLocalFiles -> "/_files_encrypt ${onOff(enable)}"
is ApiExportArchive -> "/_db export ${json.encodeToString(config)}" is ApiExportArchive -> "/_db export ${json.encodeToString(config)}"
is ApiImportArchive -> "/_db import ${json.encodeToString(config)}" is ApiImportArchive -> "/_db import ${json.encodeToString(config)}"
@@ -2549,7 +2621,10 @@ sealed class CC {
is ApiDeleteChatItem -> "/_delete item ${chatRef(type, id)} $itemId ${mode.deleteMode}" is ApiDeleteChatItem -> "/_delete item ${chatRef(type, id)} $itemId ${mode.deleteMode}"
is ApiDeleteMemberChatItem -> "/_delete member item #$groupId $groupMemberId $itemId" is ApiDeleteMemberChatItem -> "/_delete member item #$groupId $groupMemberId $itemId"
is ApiChatItemReaction -> "/_reaction ${chatRef(type, id)} $itemId ${onOff(add)} ${json.encodeToString(reaction)}" is ApiChatItemReaction -> "/_reaction ${chatRef(type, id)} $itemId ${onOff(add)} ${json.encodeToString(reaction)}"
is ApiForwardChatItem -> "/_forward ${chatRef(toChatType, toChatId)} ${chatRef(fromChatType, fromChatId)} $itemId" is ApiForwardChatItem -> {
val ttlStr = if (ttl != null) "$ttl" else "default"
"/_forward ${chatRef(toChatType, toChatId)} ${chatRef(fromChatType, fromChatId)} $itemId ttl=${ttlStr}"
}
is ApiNewGroup -> "/_group $userId incognito=${onOff(incognito)} ${json.encodeToString(groupProfile)}" is ApiNewGroup -> "/_group $userId incognito=${onOff(incognito)} ${json.encodeToString(groupProfile)}"
is ApiAddMember -> "/_add #$groupId $contactId ${memberRole.memberRole}" is ApiAddMember -> "/_add #$groupId $contactId ${memberRole.memberRole}"
is ApiJoinGroup -> "/_join #$groupId" is ApiJoinGroup -> "/_join #$groupId"
@@ -2603,6 +2678,8 @@ sealed class CC {
is ApiSetContactPrefs -> "/_set prefs @$contactId ${json.encodeToString(prefs)}" is ApiSetContactPrefs -> "/_set prefs @$contactId ${json.encodeToString(prefs)}"
is ApiSetContactAlias -> "/_set alias @$contactId ${localAlias.trim()}" is ApiSetContactAlias -> "/_set alias @$contactId ${localAlias.trim()}"
is ApiSetConnectionAlias -> "/_set alias :$connId ${localAlias.trim()}" is ApiSetConnectionAlias -> "/_set alias :$connId ${localAlias.trim()}"
is ApiSetUserUIThemes -> "/_set theme user $userId ${if (themes != null) json.encodeToString(themes) else ""}"
is ApiSetChatUIThemes -> "/_set theme $chatId ${if (themes != null) json.encodeToString(themes) else ""}"
is ApiCreateMyAddress -> "/_address $userId" is ApiCreateMyAddress -> "/_address $userId"
is ApiDeleteMyAddress -> "/_delete_address $userId" is ApiDeleteMyAddress -> "/_delete_address $userId"
is ApiShowMyAddress -> "/_show_address $userId" is ApiShowMyAddress -> "/_show_address $userId"
@@ -2623,6 +2700,7 @@ sealed class CC {
is ApiChatUnread -> "/_unread chat ${chatRef(type, id)} ${onOff(unreadChat)}" is ApiChatUnread -> "/_unread chat ${chatRef(type, id)} ${onOff(unreadChat)}"
is ReceiveFile -> is ReceiveFile ->
"/freceive $fileId" + "/freceive $fileId" +
(" approved_relays=${onOff(userApprovedRelays)}") +
(if (encrypt == null) "" else " encrypt=${onOff(encrypt)}") + (if (encrypt == null) "" else " encrypt=${onOff(encrypt)}") +
(if (inline == null) "" else " inline=${onOff(inline)}") (if (inline == null) "" else " inline=${onOff(inline)}")
is CancelFile -> "/fcancel $fileId" is CancelFile -> "/fcancel $fileId"
@@ -2666,9 +2744,7 @@ sealed class CC {
is ApiDeleteUser -> "apiDeleteUser" is ApiDeleteUser -> "apiDeleteUser"
is StartChat -> "startChat" is StartChat -> "startChat"
is ApiStopChat -> "apiStopChat" is ApiStopChat -> "apiStopChat"
is SetTempFolder -> "setTempFolder" is ApiSetAppFilePaths -> "apiSetAppFilePaths"
is SetFilesFolder -> "setFilesFolder"
is SetRemoteHostsFolder -> "setRemoteHostsFolder"
is ApiSetEncryptLocalFiles -> "apiSetEncryptLocalFiles" is ApiSetEncryptLocalFiles -> "apiSetEncryptLocalFiles"
is ApiExportArchive -> "apiExportArchive" is ApiExportArchive -> "apiExportArchive"
is ApiImportArchive -> "apiImportArchive" is ApiImportArchive -> "apiImportArchive"
@@ -2736,6 +2812,8 @@ sealed class CC {
is ApiSetContactPrefs -> "apiSetContactPrefs" is ApiSetContactPrefs -> "apiSetContactPrefs"
is ApiSetContactAlias -> "apiSetContactAlias" is ApiSetContactAlias -> "apiSetContactAlias"
is ApiSetConnectionAlias -> "apiSetConnectionAlias" is ApiSetConnectionAlias -> "apiSetConnectionAlias"
is ApiSetUserUIThemes -> "apiSetUserUIThemes"
is ApiSetChatUIThemes -> "apiSetChatUIThemes"
is ApiCreateMyAddress -> "apiCreateMyAddress" is ApiCreateMyAddress -> "apiCreateMyAddress"
is ApiDeleteMyAddress -> "apiDeleteMyAddress" is ApiDeleteMyAddress -> "apiDeleteMyAddress"
is ApiShowMyAddress -> "apiShowMyAddress" is ApiShowMyAddress -> "apiShowMyAddress"
@@ -3025,9 +3103,12 @@ data class ParsedServerAddress (
@Serializable @Serializable
data class NetCfg( data class NetCfg(
val socksProxy: String?, val socksProxy: String?,
val socksMode: SocksMode = SocksMode.Always,
val hostMode: HostMode, val hostMode: HostMode,
val requiredHostMode: Boolean, val requiredHostMode: Boolean,
val sessionMode: TransportSessionMode, val sessionMode: TransportSessionMode,
val smpProxyMode: SMPProxyMode,
val smpProxyFallback: SMPProxyFallback,
val tcpConnectTimeout: Long, // microseconds val tcpConnectTimeout: Long, // microseconds
val tcpTimeout: Long, // microseconds val tcpTimeout: Long, // microseconds
val tcpTimeoutPerKb: Long, // microseconds val tcpTimeoutPerKb: Long, // microseconds
@@ -3056,6 +3137,8 @@ data class NetCfg(
hostMode = HostMode.OnionViaSocks, hostMode = HostMode.OnionViaSocks,
requiredHostMode = false, requiredHostMode = false,
sessionMode = TransportSessionMode.User, sessionMode = TransportSessionMode.User,
smpProxyMode = SMPProxyMode.Never,
smpProxyFallback = SMPProxyFallback.Allow,
tcpConnectTimeout = 25_000_000, tcpConnectTimeout = 25_000_000,
tcpTimeout = 15_000_000, tcpTimeout = 15_000_000,
tcpTimeoutPerKb = 10_000, tcpTimeoutPerKb = 10_000,
@@ -3071,6 +3154,8 @@ data class NetCfg(
hostMode = HostMode.OnionViaSocks, hostMode = HostMode.OnionViaSocks,
requiredHostMode = false, requiredHostMode = false,
sessionMode = TransportSessionMode.User, sessionMode = TransportSessionMode.User,
smpProxyMode = SMPProxyMode.Never,
smpProxyFallback = SMPProxyFallback.Allow,
tcpConnectTimeout = 35_000_000, tcpConnectTimeout = 35_000_000,
tcpTimeout = 20_000_000, tcpTimeout = 20_000_000,
tcpTimeoutPerKb = 15_000, tcpTimeoutPerKb = 15_000,
@@ -3109,6 +3194,35 @@ enum class HostMode {
@SerialName("public") Public; @SerialName("public") Public;
} }
@Serializable
enum class SocksMode {
@SerialName("always") Always,
@SerialName("onion") Onion;
}
@Serializable
enum class SMPProxyMode {
@SerialName("always") Always,
@SerialName("unknown") Unknown,
@SerialName("unprotected") Unprotected,
@SerialName("never") Never;
companion object {
val default = Never
}
}
@Serializable
enum class SMPProxyFallback {
@SerialName("allow") Allow,
@SerialName("allowProtected") AllowProtected,
@SerialName("prohibit") Prohibit;
companion object {
val default = Allow
}
}
@Serializable @Serializable
enum class TransportSessionMode { enum class TransportSessionMode {
@SerialName("user") User, @SerialName("user") User,
@@ -3971,6 +4085,15 @@ val json = Json {
explicitNulls = false explicitNulls = false
} }
// Can decode unknown enum to default value specified for this field
val jsonCoerceInputValues = Json {
prettyPrint = true
ignoreUnknownKeys = true
encodeDefaults = true
explicitNulls = false
coerceInputValues = true
}
val jsonShort = Json { val jsonShort = Json {
prettyPrint = false prettyPrint = false
ignoreUnknownKeys = true ignoreUnknownKeys = true
@@ -3981,6 +4104,8 @@ val jsonShort = Json {
val yaml = Yaml(configuration = YamlConfiguration( val yaml = Yaml(configuration = YamlConfiguration(
strictMode = false, strictMode = false,
encodeDefaults = false, encodeDefaults = false,
/** ~5.5 MB limitation since wallpaper is limited by 5 MB, see [saveWallpaperFile] */
codePointLimit = 5500000,
)) ))
@Serializable @Serializable
@@ -4175,7 +4300,7 @@ sealed class CR {
@Serializable @SerialName("rcvStandaloneFileComplete") class RcvStandaloneFileComplete(val user: UserRef, val targetPath: String, val rcvFileTransfer: RcvFileTransfer): CR() @Serializable @SerialName("rcvStandaloneFileComplete") class RcvStandaloneFileComplete(val user: UserRef, val targetPath: String, val rcvFileTransfer: RcvFileTransfer): CR()
@Serializable @SerialName("rcvFileCancelled") class RcvFileCancelled(val user: UserRef, val chatItem_: AChatItem?, val rcvFileTransfer: RcvFileTransfer): CR() @Serializable @SerialName("rcvFileCancelled") class RcvFileCancelled(val user: UserRef, val chatItem_: AChatItem?, val rcvFileTransfer: RcvFileTransfer): CR()
@Serializable @SerialName("rcvFileSndCancelled") class RcvFileSndCancelled(val user: UserRef, val chatItem: AChatItem, val rcvFileTransfer: RcvFileTransfer): CR() @Serializable @SerialName("rcvFileSndCancelled") class RcvFileSndCancelled(val user: UserRef, val chatItem: AChatItem, val rcvFileTransfer: RcvFileTransfer): CR()
@Serializable @SerialName("rcvFileError") class RcvFileError(val user: UserRef, val chatItem_: AChatItem?, val rcvFileTransfer: RcvFileTransfer): CR() @Serializable @SerialName("rcvFileError") class RcvFileError(val user: UserRef, val chatItem_: AChatItem?, val agentError: AgentErrorType, val rcvFileTransfer: RcvFileTransfer): CR()
// sending file events // sending file events
@Serializable @SerialName("sndFileStart") class SndFileStart(val user: UserRef, val chatItem: AChatItem, val sndFileTransfer: SndFileTransfer): CR() @Serializable @SerialName("sndFileStart") class SndFileStart(val user: UserRef, val chatItem: AChatItem, val sndFileTransfer: SndFileTransfer): CR()
@Serializable @SerialName("sndFileComplete") class SndFileComplete(val user: UserRef, val chatItem: AChatItem, val sndFileTransfer: SndFileTransfer): CR() @Serializable @SerialName("sndFileComplete") class SndFileComplete(val user: UserRef, val chatItem: AChatItem, val sndFileTransfer: SndFileTransfer): CR()
@@ -4197,6 +4322,7 @@ sealed class CR {
@Serializable @SerialName("callExtraInfo") class CallExtraInfo(val user: UserRef, val contact: Contact, val extraInfo: WebRTCExtraInfo): CR() @Serializable @SerialName("callExtraInfo") class CallExtraInfo(val user: UserRef, val contact: Contact, val extraInfo: WebRTCExtraInfo): CR()
@Serializable @SerialName("callEnded") class CallEnded(val user: UserRef, val contact: Contact): CR() @Serializable @SerialName("callEnded") class CallEnded(val user: UserRef, val contact: Contact): CR()
@Serializable @SerialName("contactConnectionDeleted") class ContactConnectionDeleted(val user: UserRef, val connection: PendingContactConnection): CR() @Serializable @SerialName("contactConnectionDeleted") class ContactConnectionDeleted(val user: UserRef, val connection: PendingContactConnection): CR()
@Serializable @SerialName("contactDisabled") class ContactDisabled(val user: UserRef, val contact: Contact): CR()
// remote events (desktop) // remote events (desktop)
@Serializable @SerialName("remoteHostList") class RemoteHostList(val remoteHosts: List<RemoteHostInfo>): CR() @Serializable @SerialName("remoteHostList") class RemoteHostList(val remoteHosts: List<RemoteHostInfo>): CR()
@Serializable @SerialName("currentRemoteHost") class CurrentRemoteHost(val remoteHost_: RemoteHostInfo?): CR() @Serializable @SerialName("currentRemoteHost") class CurrentRemoteHost(val remoteHost_: RemoteHostInfo?): CR()
@@ -4361,6 +4487,7 @@ sealed class CR {
is CallExtraInfo -> "callExtraInfo" is CallExtraInfo -> "callExtraInfo"
is CallEnded -> "callEnded" is CallEnded -> "callEnded"
is ContactConnectionDeleted -> "contactConnectionDeleted" is ContactConnectionDeleted -> "contactConnectionDeleted"
is ContactDisabled -> "contactDisabled"
is RemoteHostList -> "remoteHostList" is RemoteHostList -> "remoteHostList"
is CurrentRemoteHost -> "currentRemoteHost" is CurrentRemoteHost -> "currentRemoteHost"
is RemoteHostStarted -> "remoteHostStarted" is RemoteHostStarted -> "remoteHostStarted"
@@ -4501,7 +4628,7 @@ sealed class CR {
is RcvFileSndCancelled -> withUser(user, json.encodeToString(chatItem)) is RcvFileSndCancelled -> withUser(user, json.encodeToString(chatItem))
is RcvFileProgressXFTP -> withUser(user, "chatItem: ${json.encodeToString(chatItem_)}\nreceivedSize: $receivedSize\ntotalSize: $totalSize") is RcvFileProgressXFTP -> withUser(user, "chatItem: ${json.encodeToString(chatItem_)}\nreceivedSize: $receivedSize\ntotalSize: $totalSize")
is RcvStandaloneFileComplete -> withUser(user, targetPath) is RcvStandaloneFileComplete -> withUser(user, targetPath)
is RcvFileError -> withUser(user, json.encodeToString(chatItem_)) is RcvFileError -> withUser(user, "chatItem_: ${json.encodeToString(chatItem_)}\nagentError: ${agentError.string}\nrcvFileTransfer: $rcvFileTransfer")
is SndFileCancelled -> json.encodeToString(chatItem_) is SndFileCancelled -> json.encodeToString(chatItem_)
is SndStandaloneFileCreated -> noDetails() is SndStandaloneFileCreated -> noDetails()
is SndFileStartXFTP -> withUser(user, json.encodeToString(chatItem)) is SndFileStartXFTP -> withUser(user, json.encodeToString(chatItem))
@@ -4521,6 +4648,7 @@ sealed class CR {
is CallExtraInfo -> withUser(user, "contact: ${contact.id}\nextraInfo: ${json.encodeToString(extraInfo)}") is CallExtraInfo -> withUser(user, "contact: ${contact.id}\nextraInfo: ${json.encodeToString(extraInfo)}")
is CallEnded -> withUser(user, "contact: ${contact.id}") is CallEnded -> withUser(user, "contact: ${contact.id}")
is ContactConnectionDeleted -> withUser(user, json.encodeToString(connection)) is ContactConnectionDeleted -> withUser(user, json.encodeToString(connection))
is ContactDisabled -> withUser(user, json.encodeToString(contact))
// remote events (mobile) // remote events (mobile)
is RemoteHostList -> json.encodeToString(remoteHosts) is RemoteHostList -> json.encodeToString(remoteHosts)
is CurrentRemoteHost -> if (remoteHost_ == null) "local" else json.encodeToString(remoteHost_) is CurrentRemoteHost -> if (remoteHost_ == null) "local" else json.encodeToString(remoteHost_)
@@ -4824,13 +4952,14 @@ sealed class ChatErrorType {
is FileCancel -> "fileCancel" is FileCancel -> "fileCancel"
is FileAlreadyExists -> "fileAlreadyExists" is FileAlreadyExists -> "fileAlreadyExists"
is FileRead -> "fileRead" is FileRead -> "fileRead"
is FileWrite -> "fileWrite" is FileWrite -> "fileWrite $message"
is FileSend -> "fileSend" is FileSend -> "fileSend"
is FileRcvChunk -> "fileRcvChunk" is FileRcvChunk -> "fileRcvChunk"
is FileInternal -> "fileInternal" is FileInternal -> "fileInternal"
is FileImageType -> "fileImageType" is FileImageType -> "fileImageType"
is FileImageSize -> "fileImageSize" is FileImageSize -> "fileImageSize"
is FileNotReceived -> "fileNotReceived" is FileNotReceived -> "fileNotReceived"
is FileNotApproved -> "fileNotApproved"
// is XFTPRcvFile -> "xftpRcvFile" // is XFTPRcvFile -> "xftpRcvFile"
// is XFTPSndFile -> "xftpSndFile" // is XFTPSndFile -> "xftpSndFile"
is FallbackToSMPProhibited -> "fallbackToSMPProhibited" is FallbackToSMPProhibited -> "fallbackToSMPProhibited"
@@ -4910,6 +5039,7 @@ sealed class ChatErrorType {
@Serializable @SerialName("fileImageType") class FileImageType(val filePath: String): ChatErrorType() @Serializable @SerialName("fileImageType") class FileImageType(val filePath: String): ChatErrorType()
@Serializable @SerialName("fileImageSize") class FileImageSize(val filePath: String): ChatErrorType() @Serializable @SerialName("fileImageSize") class FileImageSize(val filePath: String): ChatErrorType()
@Serializable @SerialName("fileNotReceived") class FileNotReceived(val fileId: Long): ChatErrorType() @Serializable @SerialName("fileNotReceived") class FileNotReceived(val fileId: Long): ChatErrorType()
@Serializable @SerialName("fileNotApproved") class FileNotApproved(val fileId: Long, val unknownServers: List<String>): ChatErrorType()
// @Serializable @SerialName("xFTPRcvFile") object XFTPRcvFile: ChatErrorType() // @Serializable @SerialName("xFTPRcvFile") object XFTPRcvFile: ChatErrorType()
// @Serializable @SerialName("xFTPSndFile") object XFTPSndFile: ChatErrorType() // @Serializable @SerialName("xFTPSndFile") object XFTPSndFile: ChatErrorType()
@Serializable @SerialName("fallbackToSMPProhibited") class FallbackToSMPProhibited(val fileId: Long): ChatErrorType() @Serializable @SerialName("fallbackToSMPProhibited") class FallbackToSMPProhibited(val fileId: Long): ChatErrorType()
@@ -5408,6 +5538,7 @@ enum class NotificationsMode() {
data class AppSettings( data class AppSettings(
var networkConfig: NetCfg? = null, var networkConfig: NetCfg? = null,
var privacyEncryptLocalFiles: Boolean? = null, var privacyEncryptLocalFiles: Boolean? = null,
var privacyAskToApproveRelays: Boolean? = null,
var privacyAcceptImages: Boolean? = null, var privacyAcceptImages: Boolean? = null,
var privacyLinkPreviews: Boolean? = null, var privacyLinkPreviews: Boolean? = null,
var privacyShowChatPreviews: Boolean? = null, var privacyShowChatPreviews: Boolean? = null,
@@ -5425,12 +5556,18 @@ data class AppSettings(
var androidCallOnLockScreen: AppSettingsLockScreenCalls? = null, var androidCallOnLockScreen: AppSettingsLockScreenCalls? = null,
var iosCallKitEnabled: Boolean? = null, var iosCallKitEnabled: Boolean? = null,
var iosCallKitCallsInRecents: Boolean? = null, var iosCallKitCallsInRecents: Boolean? = null,
var uiProfileImageCornerRadius: Float? = null,
var uiColorScheme: String? = null,
var uiDarkColorScheme: String? = null,
var uiCurrentThemeIds: Map<String, String>? = null,
var uiThemes: List<ThemeOverrides>? = null,
) { ) {
fun prepareForExport(): AppSettings { fun prepareForExport(): AppSettings {
val empty = AppSettings() val empty = AppSettings()
val def = defaults val def = defaults
if (networkConfig != def.networkConfig) { empty.networkConfig = networkConfig } if (networkConfig != def.networkConfig) { empty.networkConfig = networkConfig }
if (privacyEncryptLocalFiles != def.privacyEncryptLocalFiles) { empty.privacyEncryptLocalFiles = privacyEncryptLocalFiles } if (privacyEncryptLocalFiles != def.privacyEncryptLocalFiles) { empty.privacyEncryptLocalFiles = privacyEncryptLocalFiles }
if (privacyAskToApproveRelays != def.privacyAskToApproveRelays) { empty.privacyAskToApproveRelays = privacyAskToApproveRelays }
if (privacyAcceptImages != def.privacyAcceptImages) { empty.privacyAcceptImages = privacyAcceptImages } if (privacyAcceptImages != def.privacyAcceptImages) { empty.privacyAcceptImages = privacyAcceptImages }
if (privacyLinkPreviews != def.privacyLinkPreviews) { empty.privacyLinkPreviews = privacyLinkPreviews } if (privacyLinkPreviews != def.privacyLinkPreviews) { empty.privacyLinkPreviews = privacyLinkPreviews }
if (privacyShowChatPreviews != def.privacyShowChatPreviews) { empty.privacyShowChatPreviews = privacyShowChatPreviews } if (privacyShowChatPreviews != def.privacyShowChatPreviews) { empty.privacyShowChatPreviews = privacyShowChatPreviews }
@@ -5448,6 +5585,11 @@ data class AppSettings(
if (androidCallOnLockScreen != def.androidCallOnLockScreen) { empty.androidCallOnLockScreen = androidCallOnLockScreen } if (androidCallOnLockScreen != def.androidCallOnLockScreen) { empty.androidCallOnLockScreen = androidCallOnLockScreen }
if (iosCallKitEnabled != def.iosCallKitEnabled) { empty.iosCallKitEnabled = iosCallKitEnabled } if (iosCallKitEnabled != def.iosCallKitEnabled) { empty.iosCallKitEnabled = iosCallKitEnabled }
if (iosCallKitCallsInRecents != def.iosCallKitCallsInRecents) { empty.iosCallKitCallsInRecents = iosCallKitCallsInRecents } if (iosCallKitCallsInRecents != def.iosCallKitCallsInRecents) { empty.iosCallKitCallsInRecents = iosCallKitCallsInRecents }
if (uiProfileImageCornerRadius != def.uiProfileImageCornerRadius) { empty.uiProfileImageCornerRadius = uiProfileImageCornerRadius }
if (uiColorScheme != def.uiColorScheme) { empty.uiColorScheme = uiColorScheme }
if (uiDarkColorScheme != def.uiDarkColorScheme) { empty.uiDarkColorScheme = uiDarkColorScheme }
if (uiCurrentThemeIds != def.uiCurrentThemeIds) { empty.uiCurrentThemeIds = uiCurrentThemeIds }
if (uiThemes != def.uiThemes) { empty.uiThemes = uiThemes }
return empty return empty
} }
@@ -5462,6 +5604,7 @@ data class AppSettings(
setNetCfg(net) setNetCfg(net)
} }
privacyEncryptLocalFiles?.let { def.privacyEncryptLocalFiles.set(it) } privacyEncryptLocalFiles?.let { def.privacyEncryptLocalFiles.set(it) }
privacyAskToApproveRelays?.let { def.privacyAskToApproveRelays.set(it) }
privacyAcceptImages?.let { def.privacyAcceptImages.set(it) } privacyAcceptImages?.let { def.privacyAcceptImages.set(it) }
privacyLinkPreviews?.let { def.privacyLinkPreviews.set(it) } privacyLinkPreviews?.let { def.privacyLinkPreviews.set(it) }
privacyShowChatPreviews?.let { def.privacyShowChatPreviews.set(it) } privacyShowChatPreviews?.let { def.privacyShowChatPreviews.set(it) }
@@ -5479,6 +5622,11 @@ data class AppSettings(
androidCallOnLockScreen?.let { def.callOnLockScreen.set(it.toCallOnLockScreen()) } androidCallOnLockScreen?.let { def.callOnLockScreen.set(it.toCallOnLockScreen()) }
iosCallKitEnabled?.let { def.iosCallKitEnabled.set(it) } iosCallKitEnabled?.let { def.iosCallKitEnabled.set(it) }
iosCallKitCallsInRecents?.let { def.iosCallKitCallsInRecents.set(it) } iosCallKitCallsInRecents?.let { def.iosCallKitCallsInRecents.set(it) }
uiProfileImageCornerRadius?.let { def.profileImageCornerRadius.set(it) }
uiColorScheme?.let { def.currentTheme.set(it) }
uiDarkColorScheme?.let { def.systemDarkTheme.set(it) }
uiCurrentThemeIds?.let { def.currentThemeIds.set(it) }
uiThemes?.let { def.themeOverrides.set(it.skipDuplicates()) }
} }
companion object { companion object {
@@ -5486,6 +5634,7 @@ data class AppSettings(
get() = AppSettings( get() = AppSettings(
networkConfig = NetCfg.defaults, networkConfig = NetCfg.defaults,
privacyEncryptLocalFiles = true, privacyEncryptLocalFiles = true,
privacyAskToApproveRelays = true,
privacyAcceptImages = true, privacyAcceptImages = true,
privacyLinkPreviews = true, privacyLinkPreviews = true,
privacyShowChatPreviews = true, privacyShowChatPreviews = true,
@@ -5502,7 +5651,12 @@ data class AppSettings(
confirmDBUpgrades = false, confirmDBUpgrades = false,
androidCallOnLockScreen = AppSettingsLockScreenCalls.SHOW, androidCallOnLockScreen = AppSettingsLockScreenCalls.SHOW,
iosCallKitEnabled = true, iosCallKitEnabled = true,
iosCallKitCallsInRecents = false iosCallKitCallsInRecents = false,
uiProfileImageCornerRadius = 22.5f,
uiColorScheme = DefaultTheme.SYSTEM_THEME_NAME,
uiDarkColorScheme = DefaultTheme.SIMPLEX.themeName,
uiCurrentThemeIds = null,
uiThemes = null,
) )
val current: AppSettings val current: AppSettings
@@ -5511,6 +5665,7 @@ data class AppSettings(
return defaults.copy( return defaults.copy(
networkConfig = getNetCfg(), networkConfig = getNetCfg(),
privacyEncryptLocalFiles = def.privacyEncryptLocalFiles.get(), privacyEncryptLocalFiles = def.privacyEncryptLocalFiles.get(),
privacyAskToApproveRelays = def.privacyAskToApproveRelays.get(),
privacyAcceptImages = def.privacyAcceptImages.get(), privacyAcceptImages = def.privacyAcceptImages.get(),
privacyLinkPreviews = def.privacyLinkPreviews.get(), privacyLinkPreviews = def.privacyLinkPreviews.get(),
privacyShowChatPreviews = def.privacyShowChatPreviews.get(), privacyShowChatPreviews = def.privacyShowChatPreviews.get(),
@@ -5528,6 +5683,11 @@ data class AppSettings(
androidCallOnLockScreen = AppSettingsLockScreenCalls.from(def.callOnLockScreen.get()), androidCallOnLockScreen = AppSettingsLockScreenCalls.from(def.callOnLockScreen.get()),
iosCallKitEnabled = def.iosCallKitEnabled.get(), iosCallKitEnabled = def.iosCallKitEnabled.get(),
iosCallKitCallsInRecents = def.iosCallKitCallsInRecents.get(), iosCallKitCallsInRecents = def.iosCallKitCallsInRecents.get(),
uiProfileImageCornerRadius = def.profileImageCornerRadius.get(),
uiColorScheme = def.currentTheme.get() ?: DefaultTheme.SYSTEM_THEME_NAME,
uiDarkColorScheme = def.systemDarkTheme.get() ?: DefaultTheme.SIMPLEX.themeName,
uiCurrentThemeIds = def.currentThemeIds.get(),
uiThemes = def.themeOverrides.get(),
) )
} }
} }
@@ -2,7 +2,6 @@ package com.sd.lib.compose.wheel_picker
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background import androidx.compose.foundation.background
import chat.simplex.common.ui.theme.isSystemInDarkTheme
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -12,6 +11,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import chat.simplex.common.ui.theme.isInDarkTheme
/** /**
* The default implementation of focus view in vertical. * The default implementation of focus view in vertical.
@@ -76,7 +76,7 @@ fun FWheelPickerFocusHorizontal(
*/ */
private val DefaultDividerColor: Color private val DefaultDividerColor: Color
@Composable @Composable
get() = (if (isSystemInDarkTheme()) { get() = (if (isInDarkTheme()) {
Color.White Color.White
} else { } else {
Color.Black Color.Black
@@ -42,6 +42,12 @@ fun runMigrations() {
ChatController.appPrefs.currentTheme.set(DefaultTheme.SIMPLEX.name) ChatController.appPrefs.currentTheme.set(DefaultTheme.SIMPLEX.name)
} }
lastMigration.set(117) lastMigration.set(117)
} else if (lastMigration.get() < 203) {
// Moving to a different key for storing themes as a List
val oldOverrides = ChatController.appPrefs.themeOverridesOld.get().values.toList()
ChatController.appPrefs.themeOverrides.set(oldOverrides)
ChatController.appPrefs.currentThemeIds.set(oldOverrides.associate { it.base.themeName to it.themeId })
lastMigration.set(203)
} else { } else {
lastMigration.set(BuildConfigCommon.ANDROID_VERSION_CODE) lastMigration.set(BuildConfigCommon.ANDROID_VERSION_CODE)
break break
@@ -43,14 +43,13 @@ val appPreferences: AppPreferences
val chatController: ChatController = ChatController val chatController: ChatController = ChatController
fun initChatControllerAndRunMigrations() { fun initChatControllerOnStart() {
withLongRunningApi { withLongRunningApi {
if (appPreferences.chatStopped.get() && appPreferences.storeDBPassphrase.get() && ksDatabasePassword.get() != null) { if (appPreferences.chatStopped.get() && appPreferences.storeDBPassphrase.get() && ksDatabasePassword.get() != null) {
initChatController(startChat = ::showStartChatAfterRestartAlert) initChatController(startChat = ::showStartChatAfterRestartAlert)
} else { } else {
initChatController() initChatController()
} }
runMigrations()
} }
} }
@@ -88,11 +87,13 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat
return return
} }
platform.androidRestartNetworkObserver() platform.androidRestartNetworkObserver()
controller.apiSetTempFolder(coreTmpDir.absolutePath) controller.apiSetAppFilePaths(
controller.apiSetFilesFolder(appFilesDir.absolutePath) appFilesDir.absolutePath,
if (appPlatform.isDesktop) { coreTmpDir.absolutePath,
controller.apiSetRemoteHostsFolder(remoteHostsDir.absolutePath) wallpapersDir.parentFile.absolutePath,
} remoteHostsDir.absolutePath,
ctrl
)
controller.apiSetEncryptLocalFiles(controller.appPrefs.privacyEncryptLocalFiles.get()) controller.apiSetEncryptLocalFiles(controller.appPrefs.privacyEncryptLocalFiles.get())
// If we migrated successfully means previous re-encryption process on database level finished successfully too // If we migrated successfully means previous re-encryption process on database level finished successfully too
if (appPreferences.encryptionStartedAt.get() != null) appPreferences.encryptionStartedAt.set(null) if (appPreferences.encryptionStartedAt.get() != null) appPreferences.encryptionStartedAt.set(null)
@@ -1,10 +1,12 @@
package chat.simplex.common.platform package chat.simplex.common.platform
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import chat.simplex.common.model.CIFile import chat.simplex.common.model.*
import chat.simplex.common.model.CryptoFile import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.helpers.generalGetString import chat.simplex.common.views.helpers.generalGetString
import chat.simplex.res.MR import chat.simplex.res.MR
import com.charleskorn.kaml.*
import kotlinx.serialization.encodeToString
import java.io.* import java.io.*
import java.net.URI import java.net.URI
import java.net.URLDecoder import java.net.URLDecoder
@@ -14,8 +16,10 @@ expect val dataDir: File
expect val tmpDir: File expect val tmpDir: File
expect val filesDir: File expect val filesDir: File
expect val appFilesDir: File expect val appFilesDir: File
expect val wallpapersDir: File
expect val coreTmpDir: File expect val coreTmpDir: File
expect val dbAbsolutePrefixPath: String expect val dbAbsolutePrefixPath: String
expect val preferencesDir: File
expect val chatDatabaseFileName: String expect val chatDatabaseFileName: String
expect val agentDatabaseFileName: String expect val agentDatabaseFileName: String
@@ -78,6 +82,20 @@ fun getAppFilePath(fileName: String): String {
} }
} }
fun getWallpaperFilePath(fileName: String): String {
val rh = chatModel.currentRemoteHost.value
val s = File.separator
val path = if (rh == null) {
wallpapersDir.absolutePath + s + fileName
} else {
remoteHostsDir.absolutePath + s + rh.storePath + s + "simplex_v1_assets" + s + "wallpapers" + s + fileName
}
File(path).parentFile.mkdirs()
return path
}
fun getPreferenceFilePath(fileName: String = "themes.yaml"): String = preferencesDir.absolutePath + File.separator + fileName
fun getLoadedFilePath(file: CIFile?): String? { fun getLoadedFilePath(file: CIFile?): String? {
val f = file?.fileSource?.filePath val f = file?.fileSource?.filePath
return if (f != null && file.loaded) { return if (f != null && file.loaded) {
@@ -98,6 +116,42 @@ fun getLoadedFileSource(file: CIFile?): CryptoFile? {
} }
} }
fun readThemeOverrides(): List<ThemeOverrides> {
return try {
val file = File(getPreferenceFilePath("themes.yaml"))
if (!file.exists()) return emptyList()
file.inputStream().use {
val map = yaml.parseToYamlNode(it).yamlMap
val list = map.get<YamlList>("themes")
val res = ArrayList<ThemeOverrides>()
list?.items?.forEach {
try {
res.add(yaml.decodeFromYamlNode(ThemeOverrides.serializer(), it))
} catch (e: Throwable) {
Log.e(TAG, "Error while reading specific theme: ${e.stackTraceToString()}")
}
}
res.skipDuplicates()
}
} catch (e: Throwable) {
Log.e(TAG, "Error while reading themes file: ${e.stackTraceToString()}")
emptyList()
}
}
fun writeThemeOverrides(overrides: List<ThemeOverrides>): Boolean =
try {
File(getPreferenceFilePath("themes.yaml")).outputStream().use {
val string = yaml.encodeToString(ThemesFile(themes = overrides))
it.bufferedWriter().use { it.write(string) }
}
true
} catch (e: Throwable) {
Log.e(TAG, "Error while writing themes file: ${e.stackTraceToString()}")
false
}
private fun fileReady(file: CIFile, filePath: String) = private fun fileReady(file: CIFile, filePath: String) =
File(filePath).exists() && File(filePath).exists() &&
CIFile.cachedRemoteFileRequests[file.fileSource] != false CIFile.cachedRemoteFileRequests[file.fileSource] != false
@@ -1,11 +1,13 @@
package chat.simplex.common.platform package chat.simplex.common.platform
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import com.russhwolf.settings.Settings import com.russhwolf.settings.Settings
import dev.icerock.moko.resources.ImageResource
import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.StringResource
@Composable @Composable
@@ -31,3 +33,5 @@ expect fun windowWidth(): Dp
expect fun desktopExpandWindowToWidth(width: Dp) expect fun desktopExpandWindowToWidth(width: Dp)
expect fun isRtl(text: CharSequence): Boolean expect fun isRtl(text: CharSequence): Boolean
expect fun ImageResource.toComposeImageBitmap(): ImageBitmap?
@@ -8,3 +8,4 @@ expect fun UriHandler.sendEmail(subject: String, body: CharSequence)
expect fun ClipboardManager.shareText(text: String) expect fun ClipboardManager.shareText(text: String)
expect fun shareFile(text: String, fileSource: CryptoFile) expect fun shareFile(text: String, fileSource: CryptoFile)
expect fun openFile(fileSource: CryptoFile)
@@ -31,17 +31,6 @@ val WarningOrange = Color(255, 127, 0, 255)
val WarningYellow = Color(255, 192, 0, 255) val WarningYellow = Color(255, 192, 0, 255)
val FileLight = Color(183, 190, 199, 255) val FileLight = Color(183, 190, 199, 255)
val FileDark = Color(101, 101, 106, 255) val FileDark = Color(101, 101, 106, 255)
val SentMessageColor = Color(0x1E45B8FF)
val MenuTextColor: Color @Composable get () = if (isInDarkTheme()) LocalContentColor.current.copy(alpha = 0.8f) else Color.Black val MenuTextColor: Color @Composable get () = if (isInDarkTheme()) LocalContentColor.current.copy(alpha = 0.8f) else Color.Black
val NoteFolderIconColor: Color @Composable get() = with(CurrentColors.collectAsState().value.appColors.sentMessage) { val NoteFolderIconColor: Color @Composable get() = MaterialTheme.appColors.primaryVariant2
// Default color looks too light and better to have it here a little bit brighter
if (alpha == SentMessageColor.alpha) {
copy(min(SentMessageColor.alpha + 0.1f, 1f))
} else {
// Color is non-standard and theme maker can choose color without alpha at all since the theme bound to dark/light variant,
// and it shouldn't be universal
this
}
}
@@ -6,53 +6,136 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.* import androidx.compose.ui.graphics.*
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import chat.simplex.common.model.ChatController import chat.simplex.common.model.ChatController
import chat.simplex.common.platform.isInNightMode import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.ThemeManager.colorFromReadableHex
import chat.simplex.common.ui.theme.ThemeManager.toReadableHex
import chat.simplex.common.views.helpers.* import chat.simplex.common.views.helpers.*
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import chat.simplex.res.MR import chat.simplex.res.MR
import kotlinx.serialization.Transient
import java.util.UUID
enum class DefaultTheme { enum class DefaultTheme {
SYSTEM, LIGHT, DARK, SIMPLEX; LIGHT, DARK, SIMPLEX, BLACK;
companion object {
const val SYSTEM_THEME_NAME: String = "SYSTEM"
}
val themeName: String
get() = name
val mode: DefaultThemeMode get() = if (this == LIGHT) DefaultThemeMode.LIGHT else DefaultThemeMode.DARK
// Call it only with base theme, not SYSTEM // Call it only with base theme, not SYSTEM
fun hasChangedAnyColor(colors: Colors, appColors: AppColors): Boolean { fun hasChangedAnyColor(overrides: ThemeOverrides?): Boolean {
val palette = when (this) { if (overrides == null) return false
SYSTEM -> return false return overrides.colors != ThemeColors() ||
LIGHT -> LightColorPalette overrides.wallpaper != null && (overrides.wallpaper.background != null || overrides.wallpaper.tint != null)
DARK -> DarkColorPalette
SIMPLEX -> SimplexColorPalette
}
val appPalette = when (this) {
SYSTEM -> return false
LIGHT -> LightColorPaletteApp
DARK -> DarkColorPaletteApp
SIMPLEX -> SimplexColorPaletteApp
}
return colors.primary != palette.primary ||
colors.primaryVariant != palette.primaryVariant ||
colors.secondary != palette.secondary ||
colors.secondaryVariant != palette.secondaryVariant ||
colors.background != palette.background ||
colors.surface != palette.surface ||
appColors != appPalette
} }
} }
data class AppColors( @Serializable
val title: Color, enum class DefaultThemeMode {
val sentMessage: Color, @SerialName("light") LIGHT,
val receivedMessage: Color @SerialName("dark") DARK
) }
@Stable
class AppColors(
title: Color,
primaryVariant2: Color,
sentMessage: Color,
sentQuote: Color,
receivedMessage: Color,
receivedQuote: Color,
) {
var title by mutableStateOf(title, structuralEqualityPolicy())
internal set
var primaryVariant2 by mutableStateOf(primaryVariant2, structuralEqualityPolicy())
internal set
var sentMessage by mutableStateOf(sentMessage, structuralEqualityPolicy())
internal set
var sentQuote by mutableStateOf(sentQuote, structuralEqualityPolicy())
internal set
var receivedMessage by mutableStateOf(receivedMessage, structuralEqualityPolicy())
internal set
var receivedQuote by mutableStateOf(receivedQuote, structuralEqualityPolicy())
internal set
fun copy(
title: Color = this.title,
primaryVariant2: Color = this.primaryVariant2,
sentMessage: Color = this.sentMessage,
sentQuote: Color = this.sentQuote,
receivedMessage: Color = this.receivedMessage,
receivedQuote: Color = this.receivedQuote,
): AppColors = AppColors(
title,
primaryVariant2,
sentMessage,
sentQuote,
receivedMessage,
receivedQuote,
)
override fun toString(): String {
return buildString {
append("AppColors(")
append("title=$title, ")
append("primaryVariant2=$primaryVariant2, ")
append("sentMessage=$sentMessage, ")
append("sentQuote=$sentQuote, ")
append("receivedMessage=$receivedMessage, ")
append("receivedQuote=$receivedQuote")
append(")")
}
}
}
@Stable
class AppWallpaper(
background: Color? = null,
tint: Color? = null,
type: WallpaperType = WallpaperType.Empty,
) {
var background by mutableStateOf(background, structuralEqualityPolicy())
internal set
var tint by mutableStateOf(tint, structuralEqualityPolicy())
internal set
var type by mutableStateOf(type, structuralEqualityPolicy())
internal set
fun copy(
background: Color? = this.background,
tint: Color? = this.tint,
type: WallpaperType = this.type,
): AppWallpaper = AppWallpaper(
background,
tint,
type,
)
override fun toString(): String {
return buildString {
append("AppWallpaper(")
append("background=$background, ")
append("tint=$tint, ")
append("type=$type")
append(")")
}
}
}
enum class ThemeColor { enum class ThemeColor {
PRIMARY, PRIMARY_VARIANT, SECONDARY, SECONDARY_VARIANT, BACKGROUND, SURFACE, TITLE, SENT_MESSAGE, RECEIVED_MESSAGE; PRIMARY, PRIMARY_VARIANT, SECONDARY, SECONDARY_VARIANT, BACKGROUND, SURFACE, TITLE, SENT_MESSAGE, SENT_QUOTE, RECEIVED_MESSAGE, RECEIVED_QUOTE, PRIMARY_VARIANT2, WALLPAPER_BACKGROUND, WALLPAPER_TINT;
fun fromColors(colors: Colors, appColors: AppColors): Color { fun fromColors(colors: Colors, appColors: AppColors, appWallpaper: AppWallpaper): Color? {
return when (this) { return when (this) {
PRIMARY -> colors.primary PRIMARY -> colors.primary
PRIMARY_VARIANT -> colors.primaryVariant PRIMARY_VARIANT -> colors.primaryVariant
@@ -61,8 +144,13 @@ enum class ThemeColor {
BACKGROUND -> colors.background BACKGROUND -> colors.background
SURFACE -> colors.surface SURFACE -> colors.surface
TITLE -> appColors.title TITLE -> appColors.title
PRIMARY_VARIANT2 -> appColors.primaryVariant2
SENT_MESSAGE -> appColors.sentMessage SENT_MESSAGE -> appColors.sentMessage
SENT_QUOTE -> appColors.sentQuote
RECEIVED_MESSAGE -> appColors.receivedMessage RECEIVED_MESSAGE -> appColors.receivedMessage
RECEIVED_QUOTE -> appColors.receivedQuote
WALLPAPER_BACKGROUND -> appWallpaper.background
WALLPAPER_TINT -> appWallpaper.tint
} }
} }
@@ -75,8 +163,13 @@ enum class ThemeColor {
BACKGROUND -> generalGetString(MR.strings.color_background) BACKGROUND -> generalGetString(MR.strings.color_background)
SURFACE -> generalGetString(MR.strings.color_surface) SURFACE -> generalGetString(MR.strings.color_surface)
TITLE -> generalGetString(MR.strings.color_title) TITLE -> generalGetString(MR.strings.color_title)
PRIMARY_VARIANT2 -> generalGetString(MR.strings.color_primary_variant2)
SENT_MESSAGE -> generalGetString(MR.strings.color_sent_message) SENT_MESSAGE -> generalGetString(MR.strings.color_sent_message)
SENT_QUOTE -> generalGetString(MR.strings.color_sent_quote)
RECEIVED_MESSAGE -> generalGetString(MR.strings.color_received_message) RECEIVED_MESSAGE -> generalGetString(MR.strings.color_received_message)
RECEIVED_QUOTE -> generalGetString(MR.strings.color_received_quote)
WALLPAPER_BACKGROUND -> generalGetString(MR.strings.color_wallpaper_background)
WALLPAPER_TINT -> generalGetString(MR.strings.color_wallpaper_tint)
} }
} }
@@ -92,45 +185,232 @@ data class ThemeColors(
@SerialName("menus") @SerialName("menus")
val surface: String? = null, val surface: String? = null,
val title: String? = null, val title: String? = null,
@SerialName("accentVariant2")
val primaryVariant2: String? = null,
val sentMessage: String? = null, val sentMessage: String? = null,
@SerialName("sentReply")
val sentQuote: String? = null,
val receivedMessage: String? = null, val receivedMessage: String? = null,
@SerialName("receivedReply")
val receivedQuote: String? = null,
) { ) {
fun toColors(base: DefaultTheme): Colors { companion object {
fun from(colors: Colors, appColors: AppColors): ThemeColors =
ThemeColors(
primary = colors.primary.toReadableHex(),
primaryVariant = colors.primaryVariant.toReadableHex(),
secondary = colors.secondary.toReadableHex(),
secondaryVariant = colors.secondaryVariant.toReadableHex(),
background = colors.background.toReadableHex(),
surface = colors.surface.toReadableHex(),
title = appColors.title.toReadableHex(),
primaryVariant2 = appColors.primaryVariant2.toReadableHex(),
sentMessage = appColors.sentMessage.toReadableHex(),
sentQuote = appColors.sentQuote.toReadableHex(),
receivedMessage = appColors.receivedMessage.toReadableHex(),
receivedQuote = appColors.receivedQuote.toReadableHex(),
)
}
}
@Serializable
data class ThemeWallpaper (
val preset: String? = null,
val scale: Float? = null,
val scaleType: WallpaperScaleType? = null,
val background: String? = null,
val tint: String? = null,
val image: String? = null,
val imageFile: String? = null,
) {
fun toAppWallpaper(): AppWallpaper {
return AppWallpaper(
background = background?.colorFromReadableHex(),
tint = tint?.colorFromReadableHex(),
type = WallpaperType.from(this) ?: WallpaperType.Empty
)
}
fun withFilledWallpaperBase64(): ThemeWallpaper {
val aw = toAppWallpaper()
val type = aw.type
return ThemeWallpaper(
image = if (type is WallpaperType.Image && type.image != null) resizeImageToStrSize(type.image!!, 5_000_000) else null,
imageFile = null,
preset = if (type is WallpaperType.Preset) type.filename else null,
scale = if (type is WallpaperType.Preset) type.scale else if (type is WallpaperType.Image) type.scale else 1f,
scaleType = if (type is WallpaperType.Image) type.scaleType else null,
background = aw.background?.toReadableHex(),
tint = aw.tint?.toReadableHex(),
)
}
fun withFilledWallpaperPath(): ThemeWallpaper {
val aw = toAppWallpaper()
val type = aw.type
return ThemeWallpaper(
image = null,
imageFile = if (type is WallpaperType.Image) type.filename else null,
preset = if (type is WallpaperType.Preset) type.filename else null,
scale = if (scale == null) null else if (type is WallpaperType.Preset) type.scale else if (type is WallpaperType.Image) scale else null,
scaleType = if (scaleType == null) null else if (type is WallpaperType.Image) type.scaleType else null,
background = aw.background?.toReadableHex(),
tint = aw.tint?.toReadableHex(),
)
}
fun importFromString(): ThemeWallpaper =
if (preset == null && image != null) {
// Need to save image from string and to save its path
try {
val parsed = base64ToBitmap(image)
val filename = saveWallpaperFile(parsed)
copy(image = null, imageFile = filename)
} catch (e: Exception) {
Log.e(TAG, "Error while parsing/copying the image: ${e.stackTraceToString()}")
ThemeWallpaper()
}
} else this
companion object {
fun from(type: WallpaperType, background: String?, tint: String?): ThemeWallpaper {
return ThemeWallpaper(
image = null,
imageFile = if (type is WallpaperType.Image) type.filename else null,
preset = if (type is WallpaperType.Preset) type.filename else null,
scale = if (type is WallpaperType.Preset) type.scale else if (type is WallpaperType.Image) type.scale else null,
scaleType = if (type is WallpaperType.Image) type.scaleType else null,
background = background,
tint = tint,
)
}
}
}
@Serializable
data class ThemesFile(
val themes: List<ThemeOverrides> = emptyList()
)
@Serializable
data class ThemeOverrides (
val themeId: String = UUID.randomUUID().toString(),
val base: DefaultTheme,
val colors: ThemeColors = ThemeColors(),
val wallpaper: ThemeWallpaper? = null,
) {
fun isSame(type: WallpaperType?, themeName: String): Boolean =
(
(wallpaper?.preset != null && type is WallpaperType.Preset && wallpaper.preset == type.filename) ||
(wallpaper?.imageFile != null && type is WallpaperType.Image) ||
(wallpaper?.preset == null && wallpaper?.imageFile == null && (type == WallpaperType.Empty || type == null))
) && base.themeName == themeName
fun withUpdatedColor(name: ThemeColor, color: String?): ThemeOverrides {
return copy(
colors = when (name) {
ThemeColor.PRIMARY -> colors.copy(primary = color)
ThemeColor.PRIMARY_VARIANT -> colors.copy(primaryVariant = color)
ThemeColor.SECONDARY -> colors.copy(secondary = color)
ThemeColor.SECONDARY_VARIANT -> colors.copy(secondaryVariant = color)
ThemeColor.BACKGROUND -> colors.copy(background = color)
ThemeColor.SURFACE -> colors.copy(surface = color)
ThemeColor.TITLE -> colors.copy(title = color)
ThemeColor.PRIMARY_VARIANT2 -> colors.copy(primaryVariant2 = color)
ThemeColor.SENT_MESSAGE -> colors.copy(sentMessage = color)
ThemeColor.SENT_QUOTE -> colors.copy(sentQuote = color)
ThemeColor.RECEIVED_MESSAGE -> colors.copy(receivedMessage = color)
ThemeColor.RECEIVED_QUOTE -> colors.copy(receivedQuote = color)
ThemeColor.WALLPAPER_BACKGROUND -> colors.copy()
ThemeColor.WALLPAPER_TINT -> colors.copy()
}, wallpaper = when (name) {
ThemeColor.WALLPAPER_BACKGROUND -> wallpaper?.copy(background = color)
ThemeColor.WALLPAPER_TINT -> wallpaper?.copy(tint = color)
else -> wallpaper?.copy()
}
)
}
fun toColors(base: DefaultTheme, perChatTheme: ThemeColors?, perUserTheme: ThemeColors?, presetWallpaperTheme: ThemeColors?): Colors {
val baseColors = when (base) { val baseColors = when (base) {
DefaultTheme.LIGHT -> LightColorPalette DefaultTheme.LIGHT -> LightColorPalette
DefaultTheme.DARK -> DarkColorPalette DefaultTheme.DARK -> DarkColorPalette
DefaultTheme.SIMPLEX -> SimplexColorPalette DefaultTheme.SIMPLEX -> SimplexColorPalette
// shouldn't be here DefaultTheme.BLACK -> BlackColorPalette
DefaultTheme.SYSTEM -> LightColorPalette
} }
return baseColors.copy( return baseColors.copy(
primary = primary?.colorFromReadableHex() ?: baseColors.primary, primary = perChatTheme?.primary?.colorFromReadableHex() ?: perUserTheme?.primary?.colorFromReadableHex() ?: colors.primary?.colorFromReadableHex() ?: presetWallpaperTheme?.primary?.colorFromReadableHex() ?: baseColors.primary,
primaryVariant = primaryVariant?.colorFromReadableHex() ?: baseColors.primaryVariant, primaryVariant = perChatTheme?.primaryVariant?.colorFromReadableHex() ?: perUserTheme?.primaryVariant?.colorFromReadableHex() ?: colors.primaryVariant?.colorFromReadableHex() ?: presetWallpaperTheme?.primaryVariant?.colorFromReadableHex() ?: baseColors.primaryVariant,
secondary = secondary?.colorFromReadableHex() ?: baseColors.secondary, secondary = perChatTheme?.secondary?.colorFromReadableHex() ?: perUserTheme?.secondary?.colorFromReadableHex() ?: colors.secondary?.colorFromReadableHex() ?: presetWallpaperTheme?.secondary?.colorFromReadableHex() ?: baseColors.secondary,
secondaryVariant = secondaryVariant?.colorFromReadableHex() ?: baseColors.secondaryVariant, secondaryVariant = perChatTheme?.secondaryVariant?.colorFromReadableHex() ?: perUserTheme?.secondaryVariant?.colorFromReadableHex() ?: colors.secondaryVariant?.colorFromReadableHex() ?: presetWallpaperTheme?.secondaryVariant?.colorFromReadableHex() ?: baseColors.secondaryVariant,
background = background?.colorFromReadableHex() ?: baseColors.background, background = perChatTheme?.background?.colorFromReadableHex() ?: perUserTheme?.background?.colorFromReadableHex() ?: colors.background?.colorFromReadableHex() ?: presetWallpaperTheme?.background?.colorFromReadableHex() ?: baseColors.background,
surface = surface?.colorFromReadableHex() ?: baseColors.surface, surface = perChatTheme?.surface?.colorFromReadableHex() ?: perUserTheme?.surface?.colorFromReadableHex() ?: colors.surface?.colorFromReadableHex() ?: presetWallpaperTheme?.surface?.colorFromReadableHex() ?: baseColors.surface,
) )
} }
fun toAppColors(base: DefaultTheme): AppColors { fun toAppColors(base: DefaultTheme, perChatTheme: ThemeColors?, perChatWallpaperType: WallpaperType?, perUserTheme: ThemeColors?, perUserWallpaperType: WallpaperType?, presetWallpaperTheme: ThemeColors?): AppColors {
val baseColors = when (base) { val baseColors = when (base) {
DefaultTheme.LIGHT -> LightColorPaletteApp DefaultTheme.LIGHT -> LightColorPaletteApp
DefaultTheme.DARK -> DarkColorPaletteApp DefaultTheme.DARK -> DarkColorPaletteApp
DefaultTheme.SIMPLEX -> SimplexColorPaletteApp DefaultTheme.SIMPLEX -> SimplexColorPaletteApp
// shouldn't be here DefaultTheme.BLACK -> BlackColorPaletteApp
DefaultTheme.SYSTEM -> LightColorPaletteApp
} }
val sentMessageFallback = colors.sentMessage?.colorFromReadableHex() ?: presetWallpaperTheme?.sentMessage?.colorFromReadableHex() ?: baseColors.sentMessage
val sentQuoteFallback = colors.sentQuote?.colorFromReadableHex() ?: presetWallpaperTheme?.sentQuote?.colorFromReadableHex() ?: baseColors.sentQuote
val receivedMessageFallback = colors.receivedMessage?.colorFromReadableHex() ?: presetWallpaperTheme?.receivedMessage?.colorFromReadableHex() ?: baseColors.receivedMessage
val receivedQuoteFallback = colors.receivedQuote?.colorFromReadableHex() ?: presetWallpaperTheme?.receivedQuote?.colorFromReadableHex() ?: baseColors.receivedQuote
return baseColors.copy( return baseColors.copy(
title = title?.colorFromReadableHex() ?: baseColors.title, title = perChatTheme?.title?.colorFromReadableHex() ?: perUserTheme?.title?.colorFromReadableHex() ?: colors.title?.colorFromReadableHex() ?: presetWallpaperTheme?.title?.colorFromReadableHex() ?: baseColors.title,
sentMessage = sentMessage?.colorFromReadableHex() ?: baseColors.sentMessage, primaryVariant2 = perChatTheme?.primaryVariant2?.colorFromReadableHex() ?: perUserTheme?.primaryVariant2?.colorFromReadableHex() ?: colors.primaryVariant2?.colorFromReadableHex() ?: presetWallpaperTheme?.primaryVariant2?.colorFromReadableHex() ?: baseColors.primaryVariant2,
receivedMessage = receivedMessage?.colorFromReadableHex() ?: baseColors.receivedMessage, sentMessage = if (perChatTheme?.sentMessage != null) perChatTheme.sentMessage.colorFromReadableHex()
else if (perUserTheme != null && (perChatWallpaperType == null || perUserWallpaperType == null || perChatWallpaperType.sameType(perUserWallpaperType))) perUserTheme.sentMessage?.colorFromReadableHex() ?: sentMessageFallback
else sentMessageFallback,
sentQuote = if (perChatTheme?.sentQuote != null) perChatTheme.sentQuote.colorFromReadableHex()
else if (perUserTheme != null && (perChatWallpaperType == null || perUserWallpaperType == null || perChatWallpaperType.sameType(perUserWallpaperType))) perUserTheme.sentQuote?.colorFromReadableHex() ?: sentQuoteFallback
else sentQuoteFallback,
receivedMessage = if (perChatTheme?.receivedMessage != null) perChatTheme.receivedMessage.colorFromReadableHex()
else if (perUserTheme != null && (perChatWallpaperType == null || perUserWallpaperType == null || perChatWallpaperType.sameType(perUserWallpaperType))) perUserTheme.receivedMessage?.colorFromReadableHex() ?: receivedMessageFallback
else receivedMessageFallback,
receivedQuote = if (perChatTheme?.receivedQuote != null) perChatTheme.receivedQuote.colorFromReadableHex()
else if (perUserTheme != null && (perChatWallpaperType == null || perUserWallpaperType == null || perChatWallpaperType.sameType(perUserWallpaperType))) perUserTheme.receivedQuote?.colorFromReadableHex() ?: receivedQuoteFallback
else receivedQuoteFallback,
) )
} }
fun withFilledColors(base: DefaultTheme): ThemeColors { fun toAppWallpaper(themeOverridesForType: WallpaperType?, perChatTheme: ThemeModeOverride?, perUserTheme: ThemeModeOverride?, materialBackgroundColor: Color): AppWallpaper {
val c = toColors(base) val mainType = when {
val ac = toAppColors(base) themeOverridesForType != null -> themeOverridesForType
// type can be null if override is empty `"wallpaper": "{}"`, in this case no wallpaper is needed, empty.
// It's not null to override upper level wallpaper
perChatTheme?.wallpaper != null -> perChatTheme.wallpaper.toAppWallpaper().type
perUserTheme?.wallpaper != null -> perUserTheme.wallpaper.toAppWallpaper().type
else -> wallpaper?.toAppWallpaper()?.type ?: return AppWallpaper()
}
val first: ThemeWallpaper? = if (mainType.sameType(perChatTheme?.wallpaper?.toAppWallpaper()?.type)) perChatTheme?.wallpaper else null
val second: ThemeWallpaper? = if (mainType.sameType(perUserTheme?.wallpaper?.toAppWallpaper()?.type)) perUserTheme?.wallpaper else null
val third: ThemeWallpaper? = if (mainType.sameType(this.wallpaper?.toAppWallpaper()?.type)) this.wallpaper else null
return AppWallpaper(type = when (mainType) {
is WallpaperType.Preset -> mainType.copy(
scale = mainType.scale ?: first?.scale ?: second?.scale ?: third?.scale
)
is WallpaperType.Image -> mainType.copy(
scale = if (themeOverridesForType == null) mainType.scale ?: first?.scale ?: second?.scale ?: third?.scale else second?.scale ?: third?.scale ?: mainType.scale,
scaleType = if (themeOverridesForType == null) mainType.scaleType ?: first?.scaleType ?: second?.scaleType ?: third?.scaleType else second?.scaleType ?: third?.scaleType ?: mainType.scaleType,
filename = if (themeOverridesForType == null) mainType.filename else first?.imageFile ?: second?.imageFile ?: third?.imageFile ?: mainType.filename,
)
is WallpaperType.Empty -> mainType
},
background = (first?.background ?: second?.background ?: third?.background)?.colorFromReadableHex() ?: mainType.defaultBackgroundColor(base, materialBackgroundColor),
tint = (first?.tint ?: second?.tint ?: third?.tint)?.colorFromReadableHex() ?: mainType.defaultTintColor(base)
)
}
fun withFilledColors(base: DefaultTheme, perChatTheme: ThemeColors?, perChatWallpaperType: WallpaperType?, perUserTheme: ThemeColors?, perUserWallpaperType: WallpaperType?, presetWallpaperTheme: ThemeColors?): ThemeColors {
val c = toColors(base, perChatTheme, perUserTheme, presetWallpaperTheme)
val ac = toAppColors(base, perChatTheme, perChatWallpaperType, perUserTheme, perUserWallpaperType, presetWallpaperTheme)
return ThemeColors( return ThemeColors(
primary = c.primary.toReadableHex(), primary = c.primary.toReadableHex(),
primaryVariant = c.primaryVariant.toReadableHex(), primaryVariant = c.primaryVariant.toReadableHex(),
@@ -139,23 +419,71 @@ data class ThemeColors(
background = c.background.toReadableHex(), background = c.background.toReadableHex(),
surface = c.surface.toReadableHex(), surface = c.surface.toReadableHex(),
title = ac.title.toReadableHex(), title = ac.title.toReadableHex(),
primaryVariant2 = ac.primaryVariant2.toReadableHex(),
sentMessage = ac.sentMessage.toReadableHex(), sentMessage = ac.sentMessage.toReadableHex(),
receivedMessage = ac.receivedMessage.toReadableHex() sentQuote = ac.sentQuote.toReadableHex(),
receivedMessage = ac.receivedMessage.toReadableHex(),
receivedQuote = ac.receivedQuote.toReadableHex(),
) )
} }
} }
private fun String.colorFromReadableHex(): Color = fun List<ThemeOverrides>.getTheme(themeId: String?): ThemeOverrides? =
Color(this.replace("#", "").toLongOrNull(16) ?: Color.White.toArgb().toLong()) firstOrNull { it.themeId == themeId }
private fun Color.toReadableHex(): String = "#" + Integer.toHexString(toArgb()) fun List<ThemeOverrides>.getTheme(themeId: String?, type: WallpaperType?, base: DefaultTheme): ThemeOverrides? =
firstOrNull { it.themeId == themeId || it.isSame(type, base.themeName)}
fun List<ThemeOverrides>.replace(theme: ThemeOverrides): List<ThemeOverrides> {
val index = indexOfFirst { it.themeId == theme.themeId ||
// prevent situation when two themes has the same type but different theme id (maybe something was changed in prefs by hand)
it.isSame(WallpaperType.from(theme.wallpaper), theme.base.themeName)
}
return if (index != -1) {
val a = ArrayList(this)
a[index] = theme
a
} else {
this + theme
}
}
fun List<ThemeOverrides>.sameTheme(type: WallpaperType?, themeName: String): ThemeOverrides? = firstOrNull { it.isSame(type, themeName) }
/** See [ThemesTest.testSkipDuplicates] */
fun List<ThemeOverrides>.skipDuplicates(): List<ThemeOverrides> {
val res = ArrayList<ThemeOverrides>()
forEach { theme ->
val themeType = WallpaperType.from(theme.wallpaper)
if (res.none { it.themeId == theme.themeId || it.isSame(themeType, theme.base.themeName) }) {
res.add(theme)
}
}
return res
}
@Serializable @Serializable
data class ThemeOverrides ( data class ThemeModeOverrides (
val base: DefaultTheme, val light: ThemeModeOverride? = null,
val colors: ThemeColors val dark: ThemeModeOverride? = null
) { ) {
fun withUpdatedColor(name: ThemeColor, color: String): ThemeOverrides { fun preferredMode(darkTheme: Boolean): ThemeModeOverride? = when (darkTheme) {
false -> light
else -> dark
}
}
@Serializable
data class ThemeModeOverride (
val mode: DefaultThemeMode = CurrentColors.value.base.mode,
val colors: ThemeColors = ThemeColors(),
val wallpaper: ThemeWallpaper? = null,
) {
@Transient
val type = WallpaperType.from(wallpaper)
fun withUpdatedColor(name: ThemeColor, color: String?): ThemeModeOverride {
return copy(colors = when (name) { return copy(colors = when (name) {
ThemeColor.PRIMARY -> colors.copy(primary = color) ThemeColor.PRIMARY -> colors.copy(primary = color)
ThemeColor.PRIMARY_VARIANT -> colors.copy(primaryVariant = color) ThemeColor.PRIMARY_VARIANT -> colors.copy(primaryVariant = color)
@@ -164,9 +492,97 @@ data class ThemeOverrides (
ThemeColor.BACKGROUND -> colors.copy(background = color) ThemeColor.BACKGROUND -> colors.copy(background = color)
ThemeColor.SURFACE -> colors.copy(surface = color) ThemeColor.SURFACE -> colors.copy(surface = color)
ThemeColor.TITLE -> colors.copy(title = color) ThemeColor.TITLE -> colors.copy(title = color)
ThemeColor.PRIMARY_VARIANT2 -> colors.copy(primaryVariant2 = color)
ThemeColor.SENT_MESSAGE -> colors.copy(sentMessage = color) ThemeColor.SENT_MESSAGE -> colors.copy(sentMessage = color)
ThemeColor.SENT_QUOTE -> colors.copy(sentQuote = color)
ThemeColor.RECEIVED_MESSAGE -> colors.copy(receivedMessage = color) ThemeColor.RECEIVED_MESSAGE -> colors.copy(receivedMessage = color)
}) ThemeColor.RECEIVED_QUOTE -> colors.copy(receivedQuote = color)
ThemeColor.WALLPAPER_BACKGROUND -> colors.copy()
ThemeColor.WALLPAPER_TINT -> colors.copy()
}, wallpaper = when (name) {
ThemeColor.WALLPAPER_BACKGROUND -> wallpaper?.copy(background = color)
ThemeColor.WALLPAPER_TINT -> wallpaper?.copy(tint = color)
else -> wallpaper?.copy()
}
)
}
fun removeSameColors(base: DefaultTheme): ThemeModeOverride {
val c = when (base) {
DefaultTheme.LIGHT -> LightColorPalette
DefaultTheme.DARK -> DarkColorPalette
DefaultTheme.SIMPLEX -> SimplexColorPalette
DefaultTheme.BLACK -> BlackColorPalette
}
val ac = when (base) {
DefaultTheme.LIGHT -> LightColorPaletteApp
DefaultTheme.DARK -> DarkColorPaletteApp
DefaultTheme.SIMPLEX -> SimplexColorPaletteApp
DefaultTheme.BLACK -> BlackColorPaletteApp
}
val w = when (val wallpaperType = WallpaperType.from(wallpaper)) {
is WallpaperType.Preset -> {
val p = PresetWallpaper.from(wallpaperType.filename)
ThemeWallpaper(
preset = wallpaperType.filename,
scale = p?.scale ?: wallpaper?.scale,
scaleType = null,
background = p?.background?.get(base)?.toReadableHex(),
tint = p?.tint?.get(base)?.toReadableHex(),
image = null,
imageFile = null,
)
}
is WallpaperType.Image -> {
ThemeWallpaper(
preset = null,
scale = null,
scaleType = WallpaperScaleType.FILL,
background = Color.Transparent.toReadableHex(),
tint = Color.Transparent.toReadableHex(),
image = null,
imageFile = null,
)
}
else -> {
ThemeWallpaper()
}
}
return copy(
colors = ThemeColors(
primary = if (colors.primary?.colorFromReadableHex() != c.primary) colors.primary else null,
primaryVariant = if (colors.primaryVariant?.colorFromReadableHex() != c.primaryVariant) colors.primaryVariant else null,
secondary = if (colors.secondary?.colorFromReadableHex() != c.secondary) colors.secondary else null,
secondaryVariant = if (colors.secondaryVariant?.colorFromReadableHex() != c.secondaryVariant) colors.secondaryVariant else null,
background = if (colors.background?.colorFromReadableHex() != c.background) colors.background else null,
surface = if (colors.surface?.colorFromReadableHex() != c.surface) colors.surface else null,
title = if (colors.title?.colorFromReadableHex() != ac.title) colors.title else null,
primaryVariant2 = if (colors.primaryVariant2?.colorFromReadableHex() != ac.primaryVariant2) colors.primary else null,
sentMessage = if (colors.sentMessage?.colorFromReadableHex() != ac.sentMessage) colors.sentMessage else null,
sentQuote = if (colors.sentQuote?.colorFromReadableHex() != ac.sentQuote) colors.sentQuote else null,
receivedMessage = if (colors.receivedMessage?.colorFromReadableHex() != ac.receivedMessage) colors.receivedMessage else null,
receivedQuote = if (colors.receivedQuote?.colorFromReadableHex() != ac.receivedQuote) colors.receivedQuote else null,
),
wallpaper = wallpaper?.copy(
preset = wallpaper.preset,
scale = if (wallpaper.scale != w.scale) wallpaper.scale else null,
scaleType = if (wallpaper.scaleType != w.scaleType) wallpaper.scaleType else null,
background = if (wallpaper.background != w.background) wallpaper.background else null,
tint = if (wallpaper.tint != w.tint) wallpaper.tint else null,
image = wallpaper.image,
imageFile = wallpaper.imageFile,
)
)
}
companion object {
fun withFilledAppDefaults(mode: DefaultThemeMode, base: DefaultTheme): ThemeModeOverride =
ThemeModeOverride(
mode = mode,
colors = ThemeOverrides(base = base).withFilledColors(base, null, null, null, null, null),
wallpaper = ThemeWallpaper(preset = PresetWallpaper.SCHOOL.filename)
)
} }
} }
@@ -204,7 +620,6 @@ val DarkColorPalette = darkColors(
// background = Color.Black, // background = Color.Black,
surface = Color(0xFF222222), surface = Color(0xFF222222),
// background = Color(0xFF121212), // background = Color(0xFF121212),
// surface = Color(0xFF121212),
error = Color.Red, error = Color.Red,
onBackground = Color(0xFFFFFBFA), onBackground = Color(0xFFFFFBFA),
onSurface = Color(0xFFFFFBFA), onSurface = Color(0xFFFFFBFA),
@@ -212,8 +627,11 @@ val DarkColorPalette = darkColors(
) )
val DarkColorPaletteApp = AppColors( val DarkColorPaletteApp = AppColors(
title = SimplexBlue, title = SimplexBlue,
sentMessage = SentMessageColor, primaryVariant2 = Color(0xFF18262E),
receivedMessage = Color(0x20B1B0B5) sentMessage = Color(0xFF18262E),
sentQuote = Color(0xFF1D3847),
receivedMessage = Color(0xff262627),
receivedQuote = Color(0xff373739),
) )
val LightColorPalette = lightColors( val LightColorPalette = lightColors(
@@ -231,8 +649,11 @@ val LightColorPalette = lightColors(
) )
val LightColorPaletteApp = AppColors( val LightColorPaletteApp = AppColors(
title = SimplexBlue, title = SimplexBlue,
sentMessage = SentMessageColor, primaryVariant2 = Color(0xFFE9F7FF),
receivedMessage = Color(0x20B1B0B5) sentMessage = Color(0xFFE9F7FF),
sentQuote = Color(0xFFD6F0FF),
receivedMessage = Color(0xfff5f5f6),
receivedQuote = Color(0xffececee),
) )
val SimplexColorPalette = darkColors( val SimplexColorPalette = darkColors(
@@ -251,11 +672,39 @@ val SimplexColorPalette = darkColors(
) )
val SimplexColorPaletteApp = AppColors( val SimplexColorPaletteApp = AppColors(
title = Color(0xFF267BE5), title = Color(0xFF267BE5),
sentMessage = SentMessageColor, primaryVariant2 = Color(0xFF172941),
receivedMessage = Color(0x20B1B0B5) sentMessage = Color(0xFF172941),
sentQuote = Color(0xFF1C3A57),
receivedMessage = Color(0xff25283a),
receivedQuote = Color(0xff36394a),
) )
val CurrentColors: MutableStateFlow<ThemeManager.ActiveTheme> = MutableStateFlow(ThemeManager.currentColors(isInNightMode())) val BlackColorPalette = darkColors(
primary = Color(0xff0077e0), // If this value changes also need to update #0088ff in string resource files
primaryVariant = Color(0xff0077e0),
secondary = HighOrLowlight,
secondaryVariant = DarkGray,
background = Color(0xff070707),
surface = Color(0xff161617),
// background = Color(0xFF121212),
// surface = Color(0xFF121212),
error = Color.Red,
onBackground = Color(0xFFFFFBFA),
onSurface = Color(0xFFFFFBFA),
// onError: Color = Color.Black,
)
val BlackColorPaletteApp = AppColors(
title = Color(0xff0077e0),
primaryVariant2 = Color(0xff243747),
sentMessage = Color(0xFF18262E),
sentQuote = Color(0xFF1D3847),
receivedMessage = Color(0xff1b1b1b),
receivedQuote = Color(0xff29292b),
)
var systemInDarkThemeCurrently: Boolean = isInNightMode()
val CurrentColors: MutableStateFlow<ThemeManager.ActiveTheme> = MutableStateFlow(ThemeManager.currentColors(null, null, chatModel.currentUser.value?.uiThemes, appPreferences.themeOverrides.get()))
@Composable @Composable
fun isInDarkTheme(): Boolean = !CurrentColors.collectAsState().value.colors.isLight fun isInDarkTheme(): Boolean = !CurrentColors.collectAsState().value.colors.isLight
@@ -263,31 +712,113 @@ fun isInDarkTheme(): Boolean = !CurrentColors.collectAsState().value.colors.isLi
@Composable @Composable
expect fun isSystemInDarkTheme(): Boolean expect fun isSystemInDarkTheme(): Boolean
internal val LocalAppColors = staticCompositionLocalOf { LightColorPaletteApp }
internal val LocalAppWallpaper = staticCompositionLocalOf { AppWallpaper() }
val MaterialTheme.appColors: AppColors
@Composable
@ReadOnlyComposable
get() = LocalAppColors.current
fun AppColors.updateColorsFrom(other: AppColors) {
title = other.title
primaryVariant2 = other.primaryVariant2
sentMessage = other.sentMessage
sentQuote = other.sentQuote
receivedMessage = other.receivedMessage
receivedQuote = other.receivedQuote
}
fun AppWallpaper.updateWallpaperFrom(other: AppWallpaper) {
background = other.background
tint = other.tint
type = other.type
}
val MaterialTheme.wallpaper: AppWallpaper
@Composable
@ReadOnlyComposable
get() = LocalAppWallpaper.current
fun reactOnDarkThemeChanges(isDark: Boolean) { fun reactOnDarkThemeChanges(isDark: Boolean) {
if (ChatController.appPrefs.currentTheme.get() == DefaultTheme.SYSTEM.name && CurrentColors.value.colors.isLight == isDark) { systemInDarkThemeCurrently = isDark
if (ChatController.appPrefs.currentTheme.get() == DefaultTheme.SYSTEM_THEME_NAME && CurrentColors.value.colors.isLight == isDark) {
// Change active colors from light to dark and back based on system theme // Change active colors from light to dark and back based on system theme
ThemeManager.applyTheme(DefaultTheme.SYSTEM.name, isDark) ThemeManager.applyTheme(DefaultTheme.SYSTEM_THEME_NAME)
} }
} }
@Composable @Composable
fun SimpleXTheme(darkTheme: Boolean? = null, content: @Composable () -> Unit) { fun SimpleXTheme(darkTheme: Boolean? = null, content: @Composable () -> Unit) {
LaunchedEffect(darkTheme) { // TODO: Fix preview working with dark/light theme
// For preview
if (darkTheme != null) // LaunchedEffect(darkTheme) {
CurrentColors.value = ThemeManager.currentColors(darkTheme) // // For preview
} // if (darkTheme != null)
val systemDark = isSystemInDarkTheme() // CurrentColors.value = ThemeManager.currentColors(darkTheme, null, null, chatModel.currentUser.value?.uiThemes, appPreferences.themeOverrides.get())
LaunchedEffect(systemDark) { // }
reactOnDarkThemeChanges(systemDark) val systemDark = rememberUpdatedState(isSystemInDarkTheme())
LaunchedEffect(Unit) {
// snapshotFlow vs LaunchedEffect reduce number of recomposes
snapshotFlow { systemDark.value }
.collect {
reactOnDarkThemeChanges(systemDark.value)
}
} }
val theme by CurrentColors.collectAsState() val theme by CurrentColors.collectAsState()
LaunchedEffect(Unit) {
// snapshotFlow vs LaunchedEffect reduce number of recomposes when user is changed or it's themes
snapshotFlow { chatModel.currentUser.value?.uiThemes }
.collect {
ThemeManager.applyTheme(appPrefs.currentTheme.get()!!)
}
}
MaterialTheme( MaterialTheme(
colors = theme.colors, colors = theme.colors,
typography = Typography, typography = Typography,
shapes = Shapes, shapes = Shapes,
content = { content = {
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colors.onBackground, content = content) val rememberedAppColors = remember {
// Explicitly creating a new object here so we don't mutate the initial [appColors]
// provided, and overwrite the values set in it.
theme.appColors.copy()
}.apply { updateColorsFrom(theme.appColors) }
val rememberedWallpaper = remember {
// Explicitly creating a new object here so we don't mutate the initial [wallpaper]
// provided, and overwrite the values set in it.
theme.wallpaper.copy()
}.apply { updateWallpaperFrom(theme.wallpaper) }
CompositionLocalProvider(
LocalContentColor provides MaterialTheme.colors.onBackground,
LocalAppColors provides rememberedAppColors,
LocalAppWallpaper provides rememberedWallpaper,
content = content)
}
)
}
@Composable
fun SimpleXThemeOverride(theme: ThemeManager.ActiveTheme, content: @Composable () -> Unit) {
MaterialTheme(
colors = theme.colors,
typography = Typography,
shapes = Shapes,
content = {
val rememberedAppColors = remember {
// Explicitly creating a new object here so we don't mutate the initial [appColors]
// provided, and overwrite the values set in it.
theme.appColors.copy()
}.apply { updateColorsFrom(theme.appColors) }
val rememberedWallpaper = remember {
// Explicitly creating a new object here so we don't mutate the initial [wallpaper]
// provided, and overwrite the values set in it.
theme.wallpaper.copy()
}.apply { updateWallpaperFrom(theme.wallpaper) }
CompositionLocalProvider(
LocalContentColor provides MaterialTheme.colors.onBackground,
LocalAppColors provides rememberedAppColors,
LocalAppWallpaper provides rememberedWallpaper,
content = content)
} }
) )
} }
@@ -1,14 +1,14 @@
package chat.simplex.common.ui.theme package chat.simplex.common.ui.theme
import androidx.compose.material.Colors import androidx.compose.material.Colors
import androidx.compose.runtime.MutableState
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily
import chat.simplex.res.MR import chat.simplex.common.model.*
import chat.simplex.common.model.AppPreferences import chat.simplex.common.platform.*
import chat.simplex.common.model.ChatController import chat.simplex.common.views.helpers.*
import chat.simplex.common.platform.platform import java.io.File
import chat.simplex.common.views.helpers.generalGetString
// https://github.com/rsms/inter // https://github.com/rsms/inter
// I place it here because IDEA shows an error (but still works anyway) when this declaration inside Type.kt // I place it here because IDEA shows an error (but still works anyway) when this declaration inside Type.kt
@@ -18,140 +18,215 @@ expect val EmojiFont: FontFamily
object ThemeManager { object ThemeManager {
private val appPrefs: AppPreferences = ChatController.appPrefs private val appPrefs: AppPreferences = ChatController.appPrefs
data class ActiveTheme(val name: String, val base: DefaultTheme, val colors: Colors, val appColors: AppColors) data class ActiveTheme(val name: String, val base: DefaultTheme, val colors: Colors, val appColors: AppColors, val wallpaper: AppWallpaper = AppWallpaper())
private fun systemDarkThemeColors(): Pair<Colors, DefaultTheme> = when (appPrefs.systemDarkTheme.get()) { private fun systemDarkThemeColors(): Pair<Colors, DefaultTheme> = when (appPrefs.systemDarkTheme.get()) {
DefaultTheme.DARK.name -> DarkColorPalette to DefaultTheme.DARK DefaultTheme.DARK.themeName -> DarkColorPalette to DefaultTheme.DARK
DefaultTheme.SIMPLEX.name -> SimplexColorPalette to DefaultTheme.SIMPLEX DefaultTheme.SIMPLEX.themeName -> SimplexColorPalette to DefaultTheme.SIMPLEX
DefaultTheme.BLACK.themeName -> BlackColorPalette to DefaultTheme.BLACK
else -> SimplexColorPalette to DefaultTheme.SIMPLEX else -> SimplexColorPalette to DefaultTheme.SIMPLEX
} }
fun currentColors(darkForSystemTheme: Boolean): ActiveTheme { private fun nonSystemThemeName(): String {
val themeName = appPrefs.currentTheme.get()!! val themeName = appPrefs.currentTheme.get()!!
val themeOverrides = appPrefs.themeOverrides.get() return if (themeName != DefaultTheme.SYSTEM_THEME_NAME) {
val nonSystemThemeName = if (themeName != DefaultTheme.SYSTEM.name) {
themeName themeName
} else { } else {
if (darkForSystemTheme) appPrefs.systemDarkTheme.get()!! else DefaultTheme.LIGHT.name if (systemInDarkThemeCurrently) appPrefs.systemDarkTheme.get()!! else DefaultTheme.LIGHT.themeName
} }
val theme = themeOverrides[nonSystemThemeName] }
fun defaultActiveTheme(appSettingsTheme: List<ThemeOverrides>): ThemeOverrides? {
val nonSystemThemeName = nonSystemThemeName()
val defaultThemeId = appPrefs.currentThemeIds.get()[nonSystemThemeName]
return appSettingsTheme.getTheme(defaultThemeId)
}
fun defaultActiveTheme(perUserTheme: ThemeModeOverrides?, appSettingsTheme: List<ThemeOverrides>): ThemeModeOverride {
val perUserTheme = if (!CurrentColors.value.colors.isLight) perUserTheme?.dark else perUserTheme?.light
if (perUserTheme != null) {
return perUserTheme
}
val defaultTheme = defaultActiveTheme(appSettingsTheme)
return ThemeModeOverride(colors = defaultTheme?.colors ?: ThemeColors(), wallpaper = defaultTheme?.wallpaper)
}
fun currentColors(themeOverridesForType: WallpaperType?, perChatTheme: ThemeModeOverride?, perUserTheme: ThemeModeOverrides?, appSettingsTheme: List<ThemeOverrides>): ActiveTheme {
val themeName = appPrefs.currentTheme.get()!!
val nonSystemThemeName = nonSystemThemeName()
val defaultTheme = defaultActiveTheme(appSettingsTheme)
val baseTheme = when (nonSystemThemeName) { val baseTheme = when (nonSystemThemeName) {
DefaultTheme.LIGHT.name -> Triple(DefaultTheme.LIGHT, LightColorPalette, LightColorPaletteApp) DefaultTheme.LIGHT.themeName -> ActiveTheme(DefaultTheme.LIGHT.themeName, DefaultTheme.LIGHT, LightColorPalette, LightColorPaletteApp, AppWallpaper(type = PresetWallpaper.SCHOOL.toType(DefaultTheme.LIGHT)))
DefaultTheme.DARK.name -> Triple(DefaultTheme.DARK, DarkColorPalette, DarkColorPaletteApp) DefaultTheme.DARK.themeName -> ActiveTheme(DefaultTheme.DARK.themeName, DefaultTheme.DARK, DarkColorPalette, DarkColorPaletteApp, AppWallpaper(type = PresetWallpaper.SCHOOL.toType(DefaultTheme.DARK)))
DefaultTheme.SIMPLEX.name -> Triple(DefaultTheme.SIMPLEX, SimplexColorPalette, SimplexColorPaletteApp) DefaultTheme.SIMPLEX.themeName -> ActiveTheme(DefaultTheme.SIMPLEX.themeName, DefaultTheme.SIMPLEX, SimplexColorPalette, SimplexColorPaletteApp, AppWallpaper(type = PresetWallpaper.SCHOOL.toType(DefaultTheme.SIMPLEX)))
else -> Triple(DefaultTheme.LIGHT, LightColorPalette, LightColorPaletteApp) DefaultTheme.BLACK.themeName -> ActiveTheme(DefaultTheme.BLACK.themeName, DefaultTheme.BLACK, BlackColorPalette, BlackColorPaletteApp, AppWallpaper(type = PresetWallpaper.SCHOOL.toType(DefaultTheme.BLACK)))
else -> ActiveTheme(DefaultTheme.LIGHT.themeName, DefaultTheme.LIGHT, LightColorPalette, LightColorPaletteApp, AppWallpaper(type = PresetWallpaper.SCHOOL.toType(DefaultTheme.LIGHT)))
} }
if (theme == null) {
return ActiveTheme(themeName, baseTheme.first, baseTheme.second, baseTheme.third) val perUserTheme = if (baseTheme.colors.isLight) perUserTheme?.light else perUserTheme?.dark
val theme = (appSettingsTheme.sameTheme(themeOverridesForType ?: perChatTheme?.type ?: perUserTheme?.type ?: defaultTheme?.wallpaper?.toAppWallpaper()?.type, nonSystemThemeName) ?: defaultTheme)
if (theme == null && perUserTheme == null && perChatTheme == null && themeOverridesForType == null) {
return ActiveTheme(themeName, baseTheme.base, baseTheme.colors, baseTheme.appColors, baseTheme.wallpaper)
} }
return ActiveTheme(themeName, baseTheme.first, theme.colors.toColors(theme.base), theme.colors.toAppColors(theme.base)) val presetWallpaperTheme = when {
perChatTheme?.wallpaper != null -> if (perChatTheme.wallpaper.preset != null) PresetWallpaper.from(perChatTheme.wallpaper.preset)?.colors?.get(baseTheme.base) else null
perUserTheme?.wallpaper != null -> if (perUserTheme.wallpaper.preset != null) PresetWallpaper.from(perUserTheme.wallpaper.preset)?.colors?.get(baseTheme.base) else null
else -> if (theme?.wallpaper?.preset != null) PresetWallpaper.from(theme.wallpaper.preset)?.colors?.get(baseTheme.base) else null
}
val themeOrEmpty = theme ?: ThemeOverrides(base = baseTheme.base)
val colors = themeOrEmpty.toColors(themeOrEmpty.base, perChatTheme?.colors, perUserTheme?.colors, presetWallpaperTheme)
return ActiveTheme(
themeName,
baseTheme.base,
colors,
themeOrEmpty.toAppColors(themeOrEmpty.base, perChatTheme?.colors, perChatTheme?.type, perUserTheme?.colors, perUserTheme?.type, presetWallpaperTheme),
themeOrEmpty.toAppWallpaper(themeOverridesForType, perChatTheme, perUserTheme, colors.background)
)
} }
fun currentThemeOverridesForExport(darkForSystemTheme: Boolean): ThemeOverrides { fun currentThemeOverridesForExport(perChatTheme: ThemeModeOverride?, perUserTheme: ThemeModeOverrides?): ThemeOverrides {
val themeName = appPrefs.currentTheme.get()!! val current = currentColors(null, perChatTheme, perUserTheme, appPrefs.themeOverrides.get())
val nonSystemThemeName = if (themeName != DefaultTheme.SYSTEM.name) { val wType = current.wallpaper.type
themeName val wBackground = current.wallpaper.background
} else { val wTint = current.wallpaper.tint
if (darkForSystemTheme) appPrefs.systemDarkTheme.get()!! else DefaultTheme.LIGHT.name return ThemeOverrides(
} themeId = "",
val overrides = appPrefs.themeOverrides.get().toMutableMap() base = current.base,
val nonFilledTheme = overrides[nonSystemThemeName] ?: ThemeOverrides(base = CurrentColors.value.base, colors = ThemeColors()) colors = ThemeColors.from(current.colors, current.appColors),
return nonFilledTheme.copy(colors = nonFilledTheme.colors.withFilledColors(CurrentColors.value.base)) wallpaper = if (wType !is WallpaperType.Empty) ThemeWallpaper.from(wType, wBackground?.toReadableHex(), wTint?.toReadableHex()).withFilledWallpaperBase64() else null
)
} }
// colors, default theme enum, localized name of theme fun applyTheme(theme: String) {
fun allThemes(darkForSystemTheme: Boolean): List<Triple<Colors, DefaultTheme, String>> {
val allThemes = ArrayList<Triple<Colors, DefaultTheme, String>>()
allThemes.add(
Triple(
if (darkForSystemTheme) systemDarkThemeColors().first else LightColorPalette,
DefaultTheme.SYSTEM,
generalGetString(MR.strings.theme_system)
)
)
allThemes.add(
Triple(
LightColorPalette,
DefaultTheme.LIGHT,
generalGetString(MR.strings.theme_light)
)
)
allThemes.add(
Triple(
DarkColorPalette,
DefaultTheme.DARK,
generalGetString(MR.strings.theme_dark)
)
)
allThemes.add(
Triple(
SimplexColorPalette,
DefaultTheme.SIMPLEX,
generalGetString(MR.strings.theme_simplex)
)
)
return allThemes
}
fun applyTheme(theme: String, darkForSystemTheme: Boolean) {
appPrefs.currentTheme.set(theme) appPrefs.currentTheme.set(theme)
CurrentColors.value = currentColors(darkForSystemTheme) CurrentColors.value = currentColors(null, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
platform.androidSetNightModeIfSupported() platform.androidSetNightModeIfSupported()
} }
fun changeDarkTheme(theme: String, darkForSystemTheme: Boolean) { fun changeDarkTheme(theme: String) {
appPrefs.systemDarkTheme.set(theme) appPrefs.systemDarkTheme.set(theme)
CurrentColors.value = currentColors(darkForSystemTheme) CurrentColors.value = currentColors(null, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
} }
fun saveAndApplyThemeColor(name: ThemeColor, color: Color? = null, darkForSystemTheme: Boolean) { fun saveAndApplyThemeColor(baseTheme: DefaultTheme, name: ThemeColor, color: Color? = null, pref: SharedPreference<List<ThemeOverrides>> = appPrefs.themeOverrides) {
val themeName = appPrefs.currentTheme.get()!! val nonSystemThemeName = baseTheme.themeName
val nonSystemThemeName = if (themeName != DefaultTheme.SYSTEM.name) { val overrides = pref.get()
themeName val themeId = appPrefs.currentThemeIds.get()[nonSystemThemeName]
} else { val prevValue = overrides.getTheme(themeId) ?: ThemeOverrides(base = baseTheme)
if (darkForSystemTheme) appPrefs.systemDarkTheme.get()!! else DefaultTheme.LIGHT.name pref.set(overrides.replace(prevValue.withUpdatedColor(name, color?.toReadableHex())))
val themeIds = appPrefs.currentThemeIds.get().toMutableMap()
themeIds[nonSystemThemeName] = prevValue.themeId
appPrefs.currentThemeIds.set(themeIds)
CurrentColors.value = currentColors(null, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
}
fun applyThemeColor(name: ThemeColor, color: Color? = null, pref: MutableState<ThemeModeOverride>) {
pref.value = pref.value.withUpdatedColor(name, color?.toReadableHex())
}
fun saveAndApplyWallpaper(baseTheme: DefaultTheme, type: WallpaperType?, pref: SharedPreference<List<ThemeOverrides>> = appPrefs.themeOverrides) {
val nonSystemThemeName = baseTheme.themeName
val overrides = pref.get()
val theme = overrides.sameTheme(type, baseTheme.themeName)
val prevValue = theme ?: ThemeOverrides(base = baseTheme)
pref.set(overrides.replace(prevValue.copy(wallpaper = if (type != null && type !is WallpaperType.Empty) ThemeWallpaper.from(type, prevValue.wallpaper?.background, prevValue.wallpaper?.tint) else null)))
val themeIds = appPrefs.currentThemeIds.get().toMutableMap()
themeIds[nonSystemThemeName] = prevValue.themeId
appPrefs.currentThemeIds.set(themeIds)
CurrentColors.value = currentColors( null, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
}
fun copyFromSameThemeOverrides(type: WallpaperType?, lowerLevelOverride: ThemeModeOverride?, pref: MutableState<ThemeModeOverride>): Boolean {
val overrides = appPrefs.themeOverrides.get()
val sameWallpaper = if (lowerLevelOverride?.type?.sameType(type) == true) lowerLevelOverride.wallpaper else overrides.sameTheme(type, CurrentColors.value.base.themeName)?.wallpaper
if (sameWallpaper == null) {
if (type != null) {
pref.value = ThemeModeOverride(wallpaper = ThemeWallpaper.from(type, null, null).copy(scale = null, scaleType = null))
} else {
// Make an empty wallpaper to override any top level ones
pref.value = ThemeModeOverride(wallpaper = ThemeWallpaper())
}
return true
} }
var colorToSet = color var type = sameWallpaper.toAppWallpaper().type
if (colorToSet == null) { if (type is WallpaperType.Image && sameWallpaper.imageFile == type.filename) {
// Setting default color from a base theme // same image file. Needs to be copied first in order to be able to remove the file once it's not needed anymore without affecting main theme override
colorToSet = when(nonSystemThemeName) { val filename = saveWallpaperFile(File(getWallpaperFilePath(type.filename)).toURI())
DefaultTheme.LIGHT.name -> name.fromColors(LightColorPalette, LightColorPaletteApp) if (filename != null) {
DefaultTheme.DARK.name -> name.fromColors(DarkColorPalette, DarkColorPaletteApp) type = WallpaperType.Image(filename, type.scale, type.scaleType)
DefaultTheme.SIMPLEX.name -> name.fromColors(SimplexColorPalette, SimplexColorPaletteApp) } else {
// Will not be here Log.e(TAG, "Error while copying wallpaper from global overrides to chat overrides")
else -> return return false
} }
} }
val overrides = appPrefs.themeOverrides.get().toMutableMap() val prevValue = pref.value
val prevValue = overrides[nonSystemThemeName] ?: ThemeOverrides(base = CurrentColors.value.base, colors = ThemeColors()) pref.value = prevValue.copy(
overrides[nonSystemThemeName] = prevValue.withUpdatedColor(name, colorToSet.toReadableHex()) colors = ThemeColors(),
appPrefs.themeOverrides.set(overrides) wallpaper = ThemeWallpaper.from(type, null, null).copy(scale = null, scaleType = null)
CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight) )
return true
} }
fun saveAndApplyThemeOverrides(theme: ThemeOverrides, darkForSystemTheme: Boolean) { fun applyWallpaper(type: WallpaperType?, pref: MutableState<ThemeModeOverride>) {
val overrides = appPrefs.themeOverrides.get().toMutableMap() val prevValue = pref.value
val prevValue = overrides[theme.base.name] ?: ThemeOverrides(base = CurrentColors.value.base, colors = ThemeColors()) pref.value = prevValue.copy(
overrides[theme.base.name] = prevValue.copy(colors = theme.colors) wallpaper = if (type != null)
appPrefs.themeOverrides.set(overrides) ThemeWallpaper.from(type, prevValue.wallpaper?.background, prevValue.wallpaper?.tint)
appPrefs.currentTheme.set(theme.base.name) else null
CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight) )
} }
fun resetAllThemeColors(darkForSystemTheme: Boolean) { fun saveAndApplyThemeOverrides(theme: ThemeOverrides, pref: SharedPreference<List<ThemeOverrides>> = appPrefs.themeOverrides) {
val themeName = appPrefs.currentTheme.get()!! val wallpaper = theme.wallpaper?.importFromString()
val nonSystemThemeName = if (themeName != DefaultTheme.SYSTEM.name) { val nonSystemThemeName = theme.base.themeName
themeName val overrides = pref.get()
} else { val prevValue = overrides.getTheme(null, wallpaper?.toAppWallpaper()?.type, theme.base) ?: ThemeOverrides(base = theme.base)
if (darkForSystemTheme) appPrefs.systemDarkTheme.get()!! else DefaultTheme.LIGHT.name if (prevValue.wallpaper?.imageFile != null) {
File(getWallpaperFilePath(prevValue.wallpaper.imageFile)).delete()
}
pref.set(overrides.replace(prevValue.copy(base = theme.base, colors = theme.colors, wallpaper = wallpaper)))
appPrefs.currentTheme.set(nonSystemThemeName)
val currentThemeIds = appPrefs.currentThemeIds.get().toMutableMap()
currentThemeIds[nonSystemThemeName] = prevValue.themeId
appPrefs.currentThemeIds.set(currentThemeIds)
CurrentColors.value = currentColors(null, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
}
fun resetAllThemeColors(pref: SharedPreference<List<ThemeOverrides>> = appPrefs.themeOverrides) {
val nonSystemThemeName = nonSystemThemeName()
val themeId = appPrefs.currentThemeIds.get()[nonSystemThemeName] ?: return
val overrides = pref.get()
val prevValue = overrides.getTheme(themeId) ?: return
pref.set(overrides.replace(prevValue.copy(colors = ThemeColors(), wallpaper = prevValue.wallpaper?.copy(background = null, tint = null))))
CurrentColors.value = currentColors(null, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
}
fun resetAllThemeColors(pref: MutableState<ThemeModeOverride>) {
val prevValue = pref.value
pref.value = prevValue.copy(colors = ThemeColors(), wallpaper = prevValue.wallpaper?.copy(background = null, tint = null))
}
fun removeTheme(themeId: String?) {
val themes = ArrayList(appPrefs.themeOverrides.get())
themes.removeAll { it.themeId == themeId }
appPrefs.themeOverrides.set(themes)
}
fun String.colorFromReadableHex(): Color =
Color(this.replace("#", "").toLongOrNull(16) ?: Color.White.toArgb().toLong())
fun Color.toReadableHex(): String {
val s = Integer.toHexString(toArgb())
return when {
this == Color.Transparent -> "#00ffffff"
s.length == 1 -> "#ff$s$s$s$s$s$s"
s.length == 2 -> "#ff$s$s$s"
s.length == 3 -> "#ff$s$s"
s.length == 6 && this.alpha == 0f -> "#00$s"
s.length == 6 -> "#ff$s"
else -> "#$s"
} }
val overrides = appPrefs.themeOverrides.get().toMutableMap()
val prevValue = overrides[nonSystemThemeName] ?: return
overrides[nonSystemThemeName] = prevValue.copy(colors = ThemeColors())
appPrefs.themeOverrides.set(overrides)
CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight)
} }
} }
private fun Color.toReadableHex(): String = "#" + Integer.toHexString(toArgb())
@@ -29,6 +29,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import chat.simplex.common.model.* import chat.simplex.common.model.*
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.model.ChatModel.controller import chat.simplex.common.model.ChatModel.controller
import chat.simplex.common.ui.theme.* import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.helpers.* import chat.simplex.common.views.helpers.*
@@ -41,6 +42,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.datetime.Clock import kotlinx.datetime.Clock
import java.io.File
@Composable @Composable
fun ChatInfoView( fun ChatInfoView(
@@ -337,6 +339,16 @@ fun ChatInfoLayout(
if (cStats != null && cStats.ratchetSyncAllowed) { if (cStats != null && cStats.ratchetSyncAllowed) {
SynchronizeConnectionButton(syncContactConnection) SynchronizeConnectionButton(syncContactConnection)
} }
WallpaperButton {
ModalManager.end.showModal {
val chat = remember { derivedStateOf { chatModel.chats.firstOrNull { it.id == chat.id } } }
val c = chat.value
if (c != null) {
ChatWallpaperEditorModal(c)
}
}
}
// } else if (developerTools) { // } else if (developerTools) {
// SynchronizeConnectionButtonForce(syncContactConnectionForce) // SynchronizeConnectionButtonForce(syncContactConnectionForce)
// } // }
@@ -642,6 +654,15 @@ private fun SendReceiptsOption(currentUser: User, state: State<SendReceipts>, on
) )
} }
@Composable
fun WallpaperButton(onClick: () -> Unit) {
SettingsActionItem(
painterResource(MR.images.ic_image),
stringResource(MR.strings.settings_section_title_chat_theme),
click = onClick
)
}
@Composable @Composable
fun ClearChatButton(onClick: () -> Unit) { fun ClearChatButton(onClick: () -> Unit) {
SettingsActionItem( SettingsActionItem(
@@ -675,6 +696,72 @@ fun ShareAddressButton(onClick: () -> Unit) {
) )
} }
@Composable
fun ModalData.ChatWallpaperEditorModal(chat: Chat) {
val themes = remember(CurrentColors.collectAsState().value.base) {
(chat.chatInfo as? ChatInfo.Direct)?.contact?.uiThemes
?: (chat.chatInfo as? ChatInfo.Group)?.groupInfo?.uiThemes
?: ThemeModeOverrides()
}
val globalThemeUsed = remember { stateGetOrPut("globalThemeUsed") { false } }
val initialTheme = remember(CurrentColors.collectAsState().value.base) {
val preferred = themes.preferredMode(!CurrentColors.value.colors.isLight)
globalThemeUsed.value = preferred == null
preferred ?: ThemeManager.defaultActiveTheme(chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
}
ChatWallpaperEditor(
initialTheme,
applyToMode = if (themes.light == themes.dark) null else initialTheme.mode,
globalThemeUsed = globalThemeUsed,
save = { applyToMode, newTheme ->
save(applyToMode, newTheme, chatModel.getChat(chat.id) ?: chat)
})
}
suspend fun save(applyToMode: DefaultThemeMode?, newTheme: ThemeModeOverride?, chat: Chat) {
val unchangedThemes: ThemeModeOverrides = ((chat.chatInfo as? ChatInfo.Direct)?.contact?.uiThemes ?: (chat.chatInfo as? ChatInfo.Group)?.groupInfo?.uiThemes) ?: ThemeModeOverrides()
val wallpaperFiles = setOf(unchangedThemes.light?.wallpaper?.imageFile, unchangedThemes.dark?.wallpaper?.imageFile)
var changedThemes: ThemeModeOverrides? = unchangedThemes
val changed = newTheme?.copy(wallpaper = newTheme.wallpaper?.withFilledWallpaperPath())
changedThemes = when (applyToMode) {
null -> changedThemes?.copy(light = changed?.copy(mode = DefaultThemeMode.LIGHT), dark = changed?.copy(mode = DefaultThemeMode.DARK))
DefaultThemeMode.LIGHT -> changedThemes?.copy(light = changed?.copy(mode = applyToMode))
DefaultThemeMode.DARK -> changedThemes?.copy(dark = changed?.copy(mode = applyToMode))
}
changedThemes = if (changedThemes?.light != null || changedThemes?.dark != null) {
val light = changedThemes.light
val dark = changedThemes.dark
val currentMode = CurrentColors.value.base.mode
// same image file for both modes, copy image to make them as different files
if (light?.wallpaper?.imageFile != null && dark?.wallpaper?.imageFile != null && light.wallpaper.imageFile == dark.wallpaper.imageFile) {
val imageFile = if (currentMode == DefaultThemeMode.LIGHT) {
dark.wallpaper.imageFile
} else {
light.wallpaper.imageFile
}
val filePath = saveWallpaperFile(File(getWallpaperFilePath(imageFile)).toURI())
changedThemes = if (currentMode == DefaultThemeMode.LIGHT) {
changedThemes.copy(dark = dark.copy(wallpaper = dark.wallpaper.copy(imageFile = filePath)))
} else {
changedThemes.copy(light = light.copy(wallpaper = light.wallpaper.copy(imageFile = filePath)))
}
}
changedThemes
} else {
null
}
val wallpaperFilesToDelete = wallpaperFiles - changedThemes?.light?.wallpaper?.imageFile - changedThemes?.dark?.wallpaper?.imageFile
wallpaperFilesToDelete.forEach(::removeWallpaperFile)
if (controller.apiSetChatUIThemes(chat.remoteHostId, chat.id, changedThemes)) {
if (chat.chatInfo is ChatInfo.Direct) {
chatModel.updateChatInfo(chat.remoteHostId, chat.chatInfo.copy(contact = chat.chatInfo.contact.copy(uiThemes = changedThemes)))
} else if (chat.chatInfo is ChatInfo.Group) {
chatModel.updateChatInfo(chat.remoteHostId, chat.chatInfo.copy(groupInfo = chat.chatInfo.groupInfo.copy(uiThemes = changedThemes)))
}
}
}
private fun setContactAlias(chat: Chat, localAlias: String, chatModel: ChatModel) = withBGApi { private fun setContactAlias(chat: Chat, localAlias: String, chatModel: ChatModel) = withBGApi {
val chatRh = chat.remoteHostId val chatRh = chat.remoteHostId
chatModel.controller.apiSetContactAlias(chatRh, chat.chatInfo.apiId, localAlias)?.let { chatModel.controller.apiSetContactAlias(chatRh, chat.chatInfo.apiId, localAlias)?.let {
@@ -42,7 +42,7 @@ sealed class CIInfoTab {
@Composable @Composable
fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools: Boolean) { fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools: Boolean) {
val sent = ci.chatDir.sent val sent = ci.chatDir.sent
val appColors = CurrentColors.collectAsState().value.appColors val appColors = MaterialTheme.appColors
val uriHandler = LocalUriHandler.current val uriHandler = LocalUriHandler.current
val selection = remember { mutableStateOf<CIInfoTab>(CIInfoTab.History) } val selection = remember { mutableStateOf<CIInfoTab>(CIInfoTab.History) }
@@ -304,7 +304,7 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
} }
@Composable @Composable
fun MemberDeliveryStatusView(member: GroupMember, status: CIStatus) { fun MemberDeliveryStatusView(member: GroupMember, status: CIStatus, sentViaProxy: Boolean?) {
SectionItemView( SectionItemView(
padding = PaddingValues(horizontal = 0.dp) padding = PaddingValues(horizontal = 0.dp)
) { ) {
@@ -317,6 +317,18 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
Spacer(Modifier.fillMaxWidth().weight(1f)) Spacer(Modifier.fillMaxWidth().weight(1f))
if (sentViaProxy == true) {
Box(
Modifier.size(36.dp),
contentAlignment = Alignment.Center
) {
Icon(
painterResource(MR.images.ic_arrow_forward),
contentDescription = null,
tint = CurrentColors.value.colors.secondary
)
}
}
val statusIcon = status.statusIcon(MaterialTheme.colors.primary, CurrentColors.value.colors.secondary) val statusIcon = status.statusIcon(MaterialTheme.colors.primary, CurrentColors.value.colors.secondary)
var modifier = Modifier.size(36.dp).clip(RoundedCornerShape(20.dp)) var modifier = Modifier.size(36.dp).clip(RoundedCornerShape(20.dp))
val info = status.statusInto val info = status.statusInto
@@ -357,8 +369,8 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
if (mss.isNotEmpty()) { if (mss.isNotEmpty()) {
SectionView(padding = PaddingValues(horizontal = DEFAULT_PADDING)) { SectionView(padding = PaddingValues(horizontal = DEFAULT_PADDING)) {
Text(stringResource(MR.strings.delivery), style = MaterialTheme.typography.h2, modifier = Modifier.padding(bottom = DEFAULT_PADDING)) Text(stringResource(MR.strings.delivery), style = MaterialTheme.typography.h2, modifier = Modifier.padding(bottom = DEFAULT_PADDING))
mss.forEach { (member, status) -> mss.forEach { (member, status, sentViaProxy) ->
MemberDeliveryStatusView(member, status) MemberDeliveryStatusView(member, status, sentViaProxy)
} }
} }
} else { } else {
@@ -482,10 +494,10 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
} }
} }
private fun membersStatuses(chatModel: ChatModel, memberDeliveryStatuses: List<MemberDeliveryStatus>): List<Pair<GroupMember, CIStatus>> { private fun membersStatuses(chatModel: ChatModel, memberDeliveryStatuses: List<MemberDeliveryStatus>): List<Triple<GroupMember, CIStatus, Boolean?>> {
return memberDeliveryStatuses.mapNotNull { mds -> return memberDeliveryStatuses.mapNotNull { mds ->
chatModel.getGroupMember(mds.groupMemberId)?.let { mem -> chatModel.getGroupMember(mds.groupMemberId)?.let { mem ->
mem to mds.memberDeliveryStatus Triple(mem, mds.memberDeliveryStatus, mds.sentViaProxy)
} }
} }
} }
@@ -5,25 +5,24 @@ import androidx.compose.foundation.*
import androidx.compose.foundation.gestures.* import androidx.compose.foundation.gestures.*
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.* import androidx.compose.foundation.lazy.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.mapSaver import androidx.compose.runtime.saveable.mapSaver
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.* import androidx.compose.ui.*
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.*
import androidx.compose.ui.platform.* import androidx.compose.ui.platform.*
import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource import dev.icerock.moko.resources.compose.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.text.* import androidx.compose.ui.text.*
import androidx.compose.ui.unit.* import androidx.compose.ui.unit.*
import chat.simplex.common.model.* import chat.simplex.common.model.*
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.model.ChatModel.controller import chat.simplex.common.model.ChatModel.controller
import chat.simplex.common.ui.theme.* import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.call.* import chat.simplex.common.views.call.*
@@ -118,368 +117,377 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
val clipboard = LocalClipboardManager.current val clipboard = LocalClipboardManager.current
when (chat.chatInfo) { when (chat.chatInfo) {
is ChatInfo.Direct, is ChatInfo.Group, is ChatInfo.Local -> { is ChatInfo.Direct, is ChatInfo.Group, is ChatInfo.Local -> {
ChatLayout( val perChatTheme = remember(chat.chatInfo, CurrentColors.value.base) { if (chat.chatInfo is ChatInfo.Direct) chat.chatInfo.contact.uiThemes?.preferredMode(!CurrentColors.value.colors.isLight) else if (chat.chatInfo is ChatInfo.Group) chat.chatInfo.groupInfo.uiThemes?.preferredMode(!CurrentColors.value.colors.isLight) else null }
chat, val overrides = if (perChatTheme != null) ThemeManager.currentColors(null, perChatTheme, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get()) else null
unreadCount, SimpleXThemeOverride(overrides ?: CurrentColors.collectAsState().value) {
composeState, ChatLayout(
composeView = { chat,
Column( unreadCount,
Modifier.fillMaxWidth(), composeState,
horizontalAlignment = Alignment.CenterHorizontally composeView = {
) { Column(
if ( Modifier.fillMaxWidth(),
chat.chatInfo is ChatInfo.Direct horizontalAlignment = Alignment.CenterHorizontally
&& !chat.chatInfo.contact.ready
&& chat.chatInfo.contact.active
&& !chat.chatInfo.contact.nextSendGrpInv
) { ) {
Text( if (
generalGetString(MR.strings.contact_connection_pending), chat.chatInfo is ChatInfo.Direct
Modifier.padding(top = 4.dp), && !chat.chatInfo.contact.ready
fontSize = 14.sp, && chat.chatInfo.contact.active
color = MaterialTheme.colors.secondary && !chat.chatInfo.contact.nextSendGrpInv
) {
Text(
generalGetString(MR.strings.contact_connection_pending),
Modifier.padding(top = 4.dp),
fontSize = 14.sp,
color = MaterialTheme.colors.secondary
)
}
ComposeView(
chatModel, chat, composeState, attachmentOption,
showChooseAttachment = { scope.launch { attachmentBottomSheetState.show() } }
) )
} }
ComposeView( },
chatModel, chat, composeState, attachmentOption, attachmentOption,
showChooseAttachment = { scope.launch { attachmentBottomSheetState.show() } } attachmentBottomSheetState,
) searchText,
} useLinkPreviews = useLinkPreviews,
}, linkMode = chatModel.simplexLinkMode.value,
attachmentOption, back = {
attachmentBottomSheetState, hideKeyboard(view)
searchText, AudioPlayer.stop()
useLinkPreviews = useLinkPreviews, chatModel.chatId.value = null
linkMode = chatModel.simplexLinkMode.value, chatModel.groupMembers.clear()
back = { },
hideKeyboard(view) info = {
AudioPlayer.stop() if (ModalManager.end.hasModalsOpen()) {
chatModel.chatId.value = null ModalManager.end.closeModals()
chatModel.groupMembers.clear() return@ChatLayout
},
info = {
if (ModalManager.end.hasModalsOpen()) {
ModalManager.end.closeModals()
return@ChatLayout
}
hideKeyboard(view)
withBGApi {
// The idea is to preload information before showing a modal because large groups can take time to load all members
var preloadedContactInfo: Pair<ConnectionStats?, Profile?>? = null
var preloadedCode: String? = null
var preloadedLink: Pair<String, GroupMemberRole>? = null
if (chat.chatInfo is ChatInfo.Direct) {
preloadedContactInfo = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId)
preloadedCode = chatModel.controller.apiGetContactCode(chatRh, chat.chatInfo.apiId)?.second
} else if (chat.chatInfo is ChatInfo.Group) {
setGroupMembers(chatRh, chat.chatInfo.groupInfo, chatModel)
preloadedLink = chatModel.controller.apiGetGroupLink(chatRh, chat.chatInfo.groupInfo.groupId)
} }
ModalManager.end.showModalCloseable(true) { close -> hideKeyboard(view)
val chat = remember { activeChat }.value
if (chat?.chatInfo is ChatInfo.Direct) {
var contactInfo: Pair<ConnectionStats?, Profile?>? by remember { mutableStateOf(preloadedContactInfo) }
var code: String? by remember { mutableStateOf(preloadedCode) }
KeyChangeEffect(chat.id, ChatModel.networkStatuses.toMap()) {
contactInfo = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId)
preloadedContactInfo = contactInfo
code = chatModel.controller.apiGetContactCode(chatRh, chat.chatInfo.apiId)?.second
preloadedCode = code
}
ChatInfoView(chatModel, (chat.chatInfo as ChatInfo.Direct).contact, contactInfo?.first, contactInfo?.second, chat.chatInfo.localAlias, code, close)
} else if (chat?.chatInfo is ChatInfo.Group) {
var link: Pair<String, GroupMemberRole>? by remember(chat.id) { mutableStateOf(preloadedLink) }
KeyChangeEffect(chat.id) {
setGroupMembers(chatRh, (chat.chatInfo as ChatInfo.Group).groupInfo, chatModel)
link = chatModel.controller.apiGetGroupLink(chatRh, chat.chatInfo.groupInfo.groupId)
preloadedLink = link
}
GroupChatInfoView(chatModel, chatRh, chat.id, link?.first, link?.second, {
link = it
preloadedLink = it
}, close)
}
}
}
},
showMemberInfo = { groupInfo: GroupInfo, member: GroupMember ->
hideKeyboard(view)
withBGApi {
val r = chatModel.controller.apiGroupMemberInfo(chatRh, groupInfo.groupId, member.groupMemberId)
val stats = r?.second
val (_, code) = if (member.memberActive) {
val memCode = chatModel.controller.apiGetGroupMemberCode(chatRh, groupInfo.apiId, member.groupMemberId)
member to memCode?.second
} else {
member to null
}
setGroupMembers(chatRh, groupInfo, chatModel)
ModalManager.end.closeModals()
ModalManager.end.showModalCloseable(true) { close ->
remember { derivedStateOf { chatModel.getGroupMember(member.groupMemberId) } }.value?.let { mem ->
GroupMemberInfoView(chatRh, groupInfo, mem, stats, code, chatModel, close, close)
}
}
}
},
loadPrevMessages = {
if (chatModel.chatId.value != activeChat.value?.id) return@ChatLayout
val c = chatModel.getChat(chatModel.chatId.value ?: return@ChatLayout)
val firstId = chatModel.chatItems.value.firstOrNull()?.id
if (c != null && firstId != null) {
withBGApi { withBGApi {
apiLoadPrevMessages(c, chatModel, firstId, searchText.value) // The idea is to preload information before showing a modal because large groups can take time to load all members
var preloadedContactInfo: Pair<ConnectionStats?, Profile?>? = null
var preloadedCode: String? = null
var preloadedLink: Pair<String, GroupMemberRole>? = null
if (chat.chatInfo is ChatInfo.Direct) {
preloadedContactInfo = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId)
preloadedCode = chatModel.controller.apiGetContactCode(chatRh, chat.chatInfo.apiId)?.second
} else if (chat.chatInfo is ChatInfo.Group) {
setGroupMembers(chatRh, chat.chatInfo.groupInfo, chatModel)
preloadedLink = chatModel.controller.apiGetGroupLink(chatRh, chat.chatInfo.groupInfo.groupId)
}
ModalManager.end.showModalCloseable(true) { close ->
val chat = remember { activeChat }.value
if (chat?.chatInfo is ChatInfo.Direct) {
var contactInfo: Pair<ConnectionStats?, Profile?>? by remember { mutableStateOf(preloadedContactInfo) }
var code: String? by remember { mutableStateOf(preloadedCode) }
KeyChangeEffect(chat.id, ChatModel.networkStatuses.toMap()) {
contactInfo = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId)
preloadedContactInfo = contactInfo
code = chatModel.controller.apiGetContactCode(chatRh, chat.chatInfo.apiId)?.second
preloadedCode = code
}
ChatInfoView(chatModel, (chat.chatInfo as ChatInfo.Direct).contact, contactInfo?.first, contactInfo?.second, chat.chatInfo.localAlias, code, close)
} else if (chat?.chatInfo is ChatInfo.Group) {
var link: Pair<String, GroupMemberRole>? by remember(chat.id) { mutableStateOf(preloadedLink) }
KeyChangeEffect(chat.id) {
setGroupMembers(chatRh, (chat.chatInfo as ChatInfo.Group).groupInfo, chatModel)
link = chatModel.controller.apiGetGroupLink(chatRh, chat.chatInfo.groupInfo.groupId)
preloadedLink = link
}
GroupChatInfoView(chatModel, chatRh, chat.id, link?.first, link?.second, {
link = it
preloadedLink = it
}, close)
}
}
} }
} },
}, showMemberInfo = { groupInfo: GroupInfo, member: GroupMember ->
deleteMessage = { itemId, mode -> hideKeyboard(view)
withBGApi { withBGApi {
val cInfo = chat.chatInfo val r = chatModel.controller.apiGroupMemberInfo(chatRh, groupInfo.groupId, member.groupMemberId)
val toDeleteItem = chatModel.chatItems.value.firstOrNull { it.id == itemId } val stats = r?.second
val toModerate = toDeleteItem?.memberToModerate(chat.chatInfo) val (_, code) = if (member.memberActive) {
val groupInfo = toModerate?.first val memCode = chatModel.controller.apiGetGroupMemberCode(chatRh, groupInfo.apiId, member.groupMemberId)
val groupMember = toModerate?.second member to memCode?.second
val deletedChatItem: ChatItem? } else {
val toChatItem: ChatItem? member to null
if (mode == CIDeleteMode.cidmBroadcast && groupInfo != null && groupMember != null) { }
val r = chatModel.controller.apiDeleteMemberChatItem( setGroupMembers(chatRh, groupInfo, chatModel)
chatRh, ModalManager.end.closeModals()
groupId = groupInfo.groupId, ModalManager.end.showModalCloseable(true) { close ->
groupMemberId = groupMember.groupMemberId, remember { derivedStateOf { chatModel.getGroupMember(member.groupMemberId) } }.value?.let { mem ->
itemId = itemId GroupMemberInfoView(chatRh, groupInfo, mem, stats, code, chatModel, close, close)
) }
deletedChatItem = r?.first }
toChatItem = r?.second }
} else { },
val r = chatModel.controller.apiDeleteChatItem( loadPrevMessages = {
chatRh, if (chatModel.chatId.value != activeChat.value?.id) return@ChatLayout
val c = chatModel.getChat(chatModel.chatId.value ?: return@ChatLayout)
val firstId = chatModel.chatItems.value.firstOrNull()?.id
if (c != null && firstId != null) {
withBGApi {
apiLoadPrevMessages(c, chatModel, firstId, searchText.value)
}
}
},
deleteMessage = { itemId, mode ->
withBGApi {
val cInfo = chat.chatInfo
val toDeleteItem = chatModel.chatItems.value.firstOrNull { it.id == itemId }
val toModerate = toDeleteItem?.memberToModerate(chat.chatInfo)
val groupInfo = toModerate?.first
val groupMember = toModerate?.second
val deletedChatItem: ChatItem?
val toChatItem: ChatItem?
if (mode == CIDeleteMode.cidmBroadcast && groupInfo != null && groupMember != null) {
val r = chatModel.controller.apiDeleteMemberChatItem(
chatRh,
groupId = groupInfo.groupId,
groupMemberId = groupMember.groupMemberId,
itemId = itemId
)
deletedChatItem = r?.first
toChatItem = r?.second
} else {
val r = chatModel.controller.apiDeleteChatItem(
chatRh,
type = cInfo.chatType,
id = cInfo.apiId,
itemId = itemId,
mode = mode
)
deletedChatItem = r?.deletedChatItem?.chatItem
toChatItem = r?.toChatItem?.chatItem
}
if (toChatItem == null && deletedChatItem != null) {
chatModel.removeChatItem(chatRh, cInfo, deletedChatItem)
} else if (toChatItem != null) {
chatModel.upsertChatItem(chatRh, cInfo, toChatItem)
}
}
},
deleteMessages = { itemIds ->
if (itemIds.isNotEmpty()) {
val chatInfo = chat.chatInfo
withBGApi {
val deletedItems: ArrayList<ChatItem> = arrayListOf()
for (itemId in itemIds) {
val di = chatModel.controller.apiDeleteChatItem(
chatRh, chatInfo.chatType, chatInfo.apiId, itemId, CIDeleteMode.cidmInternal
)?.deletedChatItem?.chatItem
if (di != null) {
deletedItems.add(di)
}
}
for (di in deletedItems) {
chatModel.removeChatItem(chatRh, chatInfo, di)
}
}
}
},
receiveFile = { fileId ->
withBGApi { chatModel.controller.receiveFile(chatRh, user, fileId) }
},
cancelFile = { fileId ->
withBGApi { chatModel.controller.cancelFile(chatRh, user, fileId) }
},
joinGroup = { groupId, onComplete ->
withBGApi {
chatModel.controller.apiJoinGroup(chatRh, groupId)
onComplete.invoke()
}
},
startCall = out@{ media ->
withBGApi {
val cInfo = chat.chatInfo
if (cInfo is ChatInfo.Direct) {
val contactInfo = chatModel.controller.apiContactInfo(chat.remoteHostId, cInfo.contact.contactId)
val profile = contactInfo?.second ?: chatModel.currentUser.value?.profile?.toProfile() ?: return@withBGApi
chatModel.activeCall.value = Call(remoteHostId = chatRh, contact = cInfo.contact, callState = CallState.WaitCapabilities, localMedia = media, userProfile = profile)
chatModel.showCallView.value = true
chatModel.callCommand.add(WCallCommand.Capabilities(media))
}
}
},
endCall = {
val call = chatModel.activeCall.value
if (call != null) withBGApi { chatModel.callManager.endCall(call) }
},
acceptCall = { contact ->
hideKeyboard(view)
withBGApi {
val invitation = chatModel.callInvitations.remove(contact.id)
?: controller.apiGetCallInvitations(chatModel.remoteHostId()).firstOrNull { it.contact.id == contact.id }
if (invitation == null) {
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.call_already_ended))
} else {
chatModel.callManager.acceptIncomingCall(invitation = invitation)
}
}
},
acceptFeature = { contact, feature, param ->
withBGApi {
chatModel.controller.allowFeatureToContact(chatRh, contact, feature, param)
}
},
openDirectChat = { contactId ->
withBGApi {
openDirectChat(chatRh, contactId, chatModel)
}
},
forwardItem = { cItem, cInfo ->
chatModel.chatId.value = null
chatModel.sharedContent.value = SharedContent.Forward(cInfo, cItem)
},
updateContactStats = { contact ->
withBGApi {
val r = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId)
if (r != null) {
val contactStats = r.first
if (contactStats != null)
chatModel.updateContactConnectionStats(chatRh, contact, contactStats)
}
}
},
updateMemberStats = { groupInfo, member ->
withBGApi {
val r = chatModel.controller.apiGroupMemberInfo(chatRh, groupInfo.groupId, member.groupMemberId)
if (r != null) {
val memStats = r.second
if (memStats != null) {
chatModel.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, memStats)
}
}
}
},
syncContactConnection = { contact ->
withBGApi {
val cStats = chatModel.controller.apiSyncContactRatchet(chatRh, contact.contactId, force = false)
if (cStats != null) {
chatModel.updateContactConnectionStats(chatRh, contact, cStats)
}
}
},
syncMemberConnection = { groupInfo, member ->
withBGApi {
val r = chatModel.controller.apiSyncGroupMemberRatchet(chatRh, groupInfo.apiId, member.groupMemberId, force = false)
if (r != null) {
chatModel.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, r.second)
}
}
},
findModelChat = { chatId ->
chatModel.getChat(chatId)
},
findModelMember = { memberId ->
chatModel.groupMembers.find { it.id == memberId }
},
setReaction = { cInfo, cItem, add, reaction ->
withBGApi {
val updatedCI = chatModel.controller.apiChatItemReaction(
rh = chatRh,
type = cInfo.chatType, type = cInfo.chatType,
id = cInfo.apiId, id = cInfo.apiId,
itemId = itemId, itemId = cItem.id,
mode = mode add = add,
reaction = reaction
) )
deletedChatItem = r?.deletedChatItem?.chatItem if (updatedCI != null) {
toChatItem = r?.toChatItem?.chatItem chatModel.updateChatItem(cInfo, updatedCI)
}
} }
if (toChatItem == null && deletedChatItem != null) { },
chatModel.removeChatItem(chatRh, cInfo, deletedChatItem) showItemDetails = { cInfo, cItem ->
} else if (toChatItem != null) { suspend fun loadChatItemInfo(): ChatItemInfo? {
chatModel.upsertChatItem(chatRh, cInfo, toChatItem) val ciInfo = chatModel.controller.apiGetChatItemInfo(chatRh, cInfo.chatType, cInfo.apiId, cItem.id)
} if (ciInfo != null) {
} if (chat.chatInfo is ChatInfo.Group) {
}, setGroupMembers(chatRh, chat.chatInfo.groupInfo, chatModel)
deleteMessages = { itemIds ->
if (itemIds.isNotEmpty()) {
val chatInfo = chat.chatInfo
withBGApi {
val deletedItems: ArrayList<ChatItem> = arrayListOf()
for (itemId in itemIds) {
val di = chatModel.controller.apiDeleteChatItem(
chatRh, chatInfo.chatType, chatInfo.apiId, itemId, CIDeleteMode.cidmInternal
)?.deletedChatItem?.chatItem
if (di != null) {
deletedItems.add(di)
} }
} }
for (di in deletedItems) { return ciInfo
chatModel.removeChatItem(chatRh, chatInfo, di)
}
} }
} withBGApi {
}, var initialCiInfo = loadChatItemInfo() ?: return@withBGApi
receiveFile = { fileId -> ModalManager.end.closeModals()
withBGApi { chatModel.controller.receiveFile(chatRh, user, fileId) } ModalManager.end.showModalCloseable(endButtons = {
}, ShareButton {
cancelFile = { fileId -> clipboard.shareText(itemInfoShareText(chatModel, cItem, initialCiInfo, chatModel.controller.appPrefs.developerTools.get()))
withBGApi { chatModel.controller.cancelFile(chatRh, user, fileId) } }
}, }) { close ->
joinGroup = { groupId, onComplete -> var ciInfo by remember(cItem.id) { mutableStateOf(initialCiInfo) }
withBGApi { ChatItemInfoView(chatRh, cItem, ciInfo, devTools = chatModel.controller.appPrefs.developerTools.get())
chatModel.controller.apiJoinGroup(chatRh, groupId) LaunchedEffect(cItem.id) {
onComplete.invoke() withContext(Dispatchers.Default) {
} for (apiResp in controller.messagesChannel) {
}, val msg = apiResp.resp
startCall = out@{ media -> if (apiResp.remoteHostId == chatRh &&
withBGApi { msg is CR.ChatItemStatusUpdated &&
val cInfo = chat.chatInfo msg.chatItem.chatItem.id == cItem.id
if (cInfo is ChatInfo.Direct) { ) {
val contactInfo = chatModel.controller.apiContactInfo(chat.remoteHostId, cInfo.contact.contactId) ciInfo = loadChatItemInfo() ?: return@withContext
val profile = contactInfo?.second ?: chatModel.currentUser.value?.profile?.toProfile() ?: return@withBGApi initialCiInfo = ciInfo
chatModel.activeCall.value = Call(remoteHostId = chatRh, contact = cInfo.contact, callState = CallState.WaitCapabilities, localMedia = media, userProfile = profile) }
chatModel.showCallView.value = true
chatModel.callCommand.add(WCallCommand.Capabilities(media))
}
}
},
endCall = {
val call = chatModel.activeCall.value
if (call != null) withBGApi { chatModel.callManager.endCall(call) }
},
acceptCall = { contact ->
hideKeyboard(view)
withBGApi {
val invitation = chatModel.callInvitations.remove(contact.id)
?: controller.apiGetCallInvitations(chatModel.remoteHostId()).firstOrNull { it.contact.id == contact.id }
if (invitation == null) {
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.call_already_ended))
} else {
chatModel.callManager.acceptIncomingCall(invitation = invitation)
}
}
},
acceptFeature = { contact, feature, param ->
withBGApi {
chatModel.controller.allowFeatureToContact(chatRh, contact, feature, param)
}
},
openDirectChat = { contactId ->
withBGApi {
openDirectChat(chatRh, contactId, chatModel)
}
},
updateContactStats = { contact ->
withBGApi {
val r = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId)
if (r != null) {
val contactStats = r.first
if (contactStats != null)
chatModel.updateContactConnectionStats(chatRh, contact, contactStats)
}
}
},
updateMemberStats = { groupInfo, member ->
withBGApi {
val r = chatModel.controller.apiGroupMemberInfo(chatRh, groupInfo.groupId, member.groupMemberId)
if (r != null) {
val memStats = r.second
if (memStats != null) {
chatModel.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, memStats)
}
}
}
},
syncContactConnection = { contact ->
withBGApi {
val cStats = chatModel.controller.apiSyncContactRatchet(chatRh, contact.contactId, force = false)
if (cStats != null) {
chatModel.updateContactConnectionStats(chatRh, contact, cStats)
}
}
},
syncMemberConnection = { groupInfo, member ->
withBGApi {
val r = chatModel.controller.apiSyncGroupMemberRatchet(chatRh, groupInfo.apiId, member.groupMemberId, force = false)
if (r != null) {
chatModel.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, r.second)
}
}
},
findModelChat = { chatId ->
chatModel.getChat(chatId)
},
findModelMember = { memberId ->
chatModel.groupMembers.find { it.id == memberId }
},
setReaction = { cInfo, cItem, add, reaction ->
withBGApi {
val updatedCI = chatModel.controller.apiChatItemReaction(
rh = chatRh,
type = cInfo.chatType,
id = cInfo.apiId,
itemId = cItem.id,
add = add,
reaction = reaction
)
if (updatedCI != null) {
chatModel.updateChatItem(cInfo, updatedCI)
}
}
},
showItemDetails = { cInfo, cItem ->
suspend fun loadChatItemInfo(): ChatItemInfo? {
val ciInfo = chatModel.controller.apiGetChatItemInfo(chatRh, cInfo.chatType, cInfo.apiId, cItem.id)
if (ciInfo != null) {
if (chat.chatInfo is ChatInfo.Group) {
setGroupMembers(chatRh, chat.chatInfo.groupInfo, chatModel)
}
}
return ciInfo
}
withBGApi {
var initialCiInfo = loadChatItemInfo() ?: return@withBGApi
ModalManager.end.closeModals()
ModalManager.end.showModalCloseable(endButtons = {
ShareButton {
clipboard.shareText(itemInfoShareText(chatModel, cItem, initialCiInfo, chatModel.controller.appPrefs.developerTools.get()))
}
}) { close ->
var ciInfo by remember(cItem.id) { mutableStateOf(initialCiInfo) }
ChatItemInfoView(chatRh, cItem, ciInfo, devTools = chatModel.controller.appPrefs.developerTools.get())
LaunchedEffect(cItem.id) {
withContext(Dispatchers.Default) {
for (apiResp in controller.messagesChannel) {
val msg = apiResp.resp
if (apiResp.remoteHostId == chatRh &&
msg is CR.ChatItemStatusUpdated &&
msg.chatItem.chatItem.id == cItem.id
) {
ciInfo = loadChatItemInfo() ?: return@withContext
initialCiInfo = ciInfo
} }
} }
} }
} KeyChangeEffect(chatModel.chatId.value) {
KeyChangeEffect(chatModel.chatId.value) { close()
close() }
} }
} }
} },
}, addMembers = { groupInfo ->
addMembers = { groupInfo -> hideKeyboard(view)
hideKeyboard(view) withBGApi {
withBGApi { setGroupMembers(chatRh, groupInfo, chatModel)
setGroupMembers(chatRh, groupInfo, chatModel) ModalManager.end.closeModals()
ModalManager.end.closeModals() ModalManager.end.showModalCloseable(true) { close ->
ModalManager.end.showModalCloseable(true) { close -> AddGroupMembersView(chatRh, groupInfo, false, chatModel, close)
AddGroupMembersView(chatRh, groupInfo, false, chatModel, close) }
} }
} },
}, openGroupLink = { groupInfo ->
openGroupLink = { groupInfo -> hideKeyboard(view)
hideKeyboard(view) withBGApi {
withBGApi { val link = chatModel.controller.apiGetGroupLink(chatRh, groupInfo.groupId)
val link = chatModel.controller.apiGetGroupLink(chatRh, groupInfo.groupId) ModalManager.end.closeModals()
ModalManager.end.closeModals() ModalManager.end.showModalCloseable(true) {
ModalManager.end.showModalCloseable(true) { GroupLinkView(chatModel, chatRh, groupInfo, link?.first, link?.second, onGroupLinkUpdated = null)
GroupLinkView(chatModel, chatRh, groupInfo, link?.first, link?.second, onGroupLinkUpdated = null) }
} }
} },
}, markRead = { range, unreadCountAfter ->
markRead = { range, unreadCountAfter -> chatModel.markChatItemsRead(chat, range, unreadCountAfter)
chatModel.markChatItemsRead(chat, range, unreadCountAfter) ntfManager.cancelNotificationsForChat(chat.id)
ntfManager.cancelNotificationsForChat(chat.id) withBGApi {
withBGApi { chatModel.controller.apiChatRead(
chatModel.controller.apiChatRead( chatRh,
chatRh, chat.chatInfo.chatType,
chat.chatInfo.chatType, chat.chatInfo.apiId,
chat.chatInfo.apiId, range
range )
) }
} },
}, changeNtfsState = { enabled, currentValue -> toggleNotifications(chat, enabled, chatModel, currentValue) },
changeNtfsState = { enabled, currentValue -> toggleNotifications(chat, enabled, chatModel, currentValue) }, onSearchValueChanged = { value ->
onSearchValueChanged = { value -> if (searchText.value == value) return@ChatLayout
if (searchText.value == value) return@ChatLayout if (chatModel.chatId.value != activeChat.value?.id) return@ChatLayout
if (chatModel.chatId.value != activeChat.value?.id) return@ChatLayout val c = chatModel.getChat(chatModel.chatId.value ?: return@ChatLayout) ?: return@ChatLayout
val c = chatModel.getChat(chatModel.chatId.value ?: return@ChatLayout) ?: return@ChatLayout withBGApi {
withBGApi { apiFindMessages(c, chatModel, value)
apiFindMessages(c, chatModel, value) searchText.value = value
searchText.value = value }
} },
}, onComposed,
onComposed, developerTools = chatModel.controller.appPrefs.developerTools.get(),
developerTools = chatModel.controller.appPrefs.developerTools.get(), showViaProxy = chatModel.controller.appPrefs.showSentViaProxy.get(),
) )
}
} }
is ChatInfo.ContactConnection -> { is ChatInfo.ContactConnection -> {
val close = { chatModel.chatId.value = null } val close = { chatModel.chatId.value = null }
@@ -533,6 +541,7 @@ fun ChatLayout(
acceptCall: (Contact) -> Unit, acceptCall: (Contact) -> Unit,
acceptFeature: (Contact, ChatFeature, Int?) -> Unit, acceptFeature: (Contact, ChatFeature, Int?) -> Unit,
openDirectChat: (Long) -> Unit, openDirectChat: (Long) -> Unit,
forwardItem: (ChatInfo, ChatItem) -> Unit,
updateContactStats: (Contact) -> Unit, updateContactStats: (Contact) -> Unit,
updateMemberStats: (GroupInfo, GroupMember) -> Unit, updateMemberStats: (GroupInfo, GroupMember) -> Unit,
syncContactConnection: (Contact) -> Unit, syncContactConnection: (Contact) -> Unit,
@@ -548,6 +557,7 @@ fun ChatLayout(
onSearchValueChanged: (String) -> Unit, onSearchValueChanged: (String) -> Unit,
onComposed: suspend (chatId: String) -> Unit, onComposed: suspend (chatId: String) -> Unit,
developerTools: Boolean, developerTools: Boolean,
showViaProxy: Boolean
) { ) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val attachmentDisabled = remember { derivedStateOf { composeState.value.attachmentDisabled } } val attachmentDisabled = remember { derivedStateOf { composeState.value.attachmentDisabled } }
@@ -596,17 +606,27 @@ fun ChatLayout(
floatingActionButton = { floatingButton.value() }, floatingActionButton = { floatingButton.value() },
contentColor = LocalContentColor.current, contentColor = LocalContentColor.current,
drawerContentColor = LocalContentColor.current, drawerContentColor = LocalContentColor.current,
backgroundColor = Color.Unspecified
) { contentPadding -> ) { contentPadding ->
val wallpaperImage = MaterialTheme.wallpaper.type.image
val wallpaperType = MaterialTheme.wallpaper.type
val backgroundColor = MaterialTheme.wallpaper.background ?: wallpaperType.defaultBackgroundColor(CurrentColors.value.base, MaterialTheme.colors.background)
val tintColor = MaterialTheme.wallpaper.tint ?: wallpaperType.defaultTintColor(CurrentColors.value.base)
BoxWithConstraints(Modifier BoxWithConstraints(Modifier
.fillMaxHeight() .fillMaxSize()
.background(MaterialTheme.colors.background)
.then(if (wallpaperImage != null)
Modifier.drawBehind { chatViewBackground(wallpaperImage, wallpaperType, backgroundColor, tintColor) }
else
Modifier)
.padding(contentPadding) .padding(contentPadding)
) { ) {
ChatItemsList( ChatItemsList(
chat, unreadCount, composeState, searchValue, chat, unreadCount, composeState, searchValue,
useLinkPreviews, linkMode, showMemberInfo, loadPrevMessages, deleteMessage, deleteMessages, useLinkPreviews, linkMode, showMemberInfo, loadPrevMessages, deleteMessage, deleteMessages,
receiveFile, cancelFile, joinGroup, acceptCall, acceptFeature, openDirectChat, receiveFile, cancelFile, joinGroup, acceptCall, acceptFeature, openDirectChat, forwardItem,
updateContactStats, updateMemberStats, syncContactConnection, syncMemberConnection, findModelChat, findModelMember, updateContactStats, updateMemberStats, syncContactConnection, syncMemberConnection, findModelChat, findModelMember,
setReaction, showItemDetails, markRead, setFloatingButton, onComposed, developerTools, setReaction, showItemDetails, markRead, setFloatingButton, onComposed, developerTools, showViaProxy,
) )
} }
} }
@@ -873,6 +893,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
acceptCall: (Contact) -> Unit, acceptCall: (Contact) -> Unit,
acceptFeature: (Contact, ChatFeature, Int?) -> Unit, acceptFeature: (Contact, ChatFeature, Int?) -> Unit,
openDirectChat: (Long) -> Unit, openDirectChat: (Long) -> Unit,
forwardItem: (ChatInfo, ChatItem) -> Unit,
updateContactStats: (Contact) -> Unit, updateContactStats: (Contact) -> Unit,
updateMemberStats: (GroupInfo, GroupMember) -> Unit, updateMemberStats: (GroupInfo, GroupMember) -> Unit,
syncContactConnection: (Contact) -> Unit, syncContactConnection: (Contact) -> Unit,
@@ -885,6 +906,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
setFloatingButton: (@Composable () -> Unit) -> Unit, setFloatingButton: (@Composable () -> Unit) -> Unit,
onComposed: suspend (chatId: String) -> Unit, onComposed: suspend (chatId: String) -> Unit,
developerTools: Boolean, developerTools: Boolean,
showViaProxy: Boolean
) { ) {
val listState = rememberLazyListState() val listState = rememberLazyListState()
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
@@ -975,7 +997,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
tryOrShowError("${cItem.id}ChatItem", error = { tryOrShowError("${cItem.id}ChatItem", error = {
CIBrokenComposableView(if (cItem.chatDir.sent) Alignment.CenterEnd else Alignment.CenterStart) CIBrokenComposableView(if (cItem.chatDir.sent) Alignment.CenterEnd else Alignment.CenterStart)
}) { }) {
ChatItemView(chat.remoteHostId, chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, range = range, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, developerTools = developerTools) ChatItemView(chat.remoteHostId, chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, range = range, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, developerTools = developerTools, showViaProxy = showViaProxy)
} }
} }
@@ -1003,7 +1025,8 @@ fun BoxWithConstraintsScope.ChatItemsList(
Text( Text(
memberNames(member, prevMember, memCount), memberNames(member, prevMember, memCount),
Modifier.padding(start = MEMBER_IMAGE_SIZE + 10.dp), Modifier.padding(start = MEMBER_IMAGE_SIZE + 10.dp),
style = TextStyle(fontSize = 13.5.sp, color = CurrentColors.value.colors.secondary) style = TextStyle(fontSize = 13.5.sp, color = CurrentColors.value.colors.secondary),
maxLines = 2
) )
} }
Row( Row(
@@ -1258,7 +1281,7 @@ val MEMBER_IMAGE_SIZE: Dp = 38.dp
@Composable @Composable
fun MemberImage(member: GroupMember) { fun MemberImage(member: GroupMember) {
ProfileImage(MEMBER_IMAGE_SIZE, member.memberProfile.image) ProfileImage(MEMBER_IMAGE_SIZE, member.memberProfile.image, backgroundColor = MaterialTheme.colors.background)
} }
@Composable @Composable
@@ -1527,6 +1550,7 @@ fun PreviewChatLayout() {
acceptCall = { _ -> }, acceptCall = { _ -> },
acceptFeature = { _, _, _ -> }, acceptFeature = { _, _, _ -> },
openDirectChat = { _ -> }, openDirectChat = { _ -> },
forwardItem = { _, _ -> },
updateContactStats = { }, updateContactStats = { },
updateMemberStats = { _, _ -> }, updateMemberStats = { _, _ -> },
syncContactConnection = { }, syncContactConnection = { },
@@ -1542,6 +1566,7 @@ fun PreviewChatLayout() {
onSearchValueChanged = {}, onSearchValueChanged = {},
onComposed = {}, onComposed = {},
developerTools = false, developerTools = false,
showViaProxy = false,
) )
} }
} }
@@ -1599,6 +1624,7 @@ fun PreviewGroupChatLayout() {
acceptCall = { _ -> }, acceptCall = { _ -> },
acceptFeature = { _, _, _ -> }, acceptFeature = { _, _, _ -> },
openDirectChat = { _ -> }, openDirectChat = { _ -> },
forwardItem = { _, _ -> },
updateContactStats = { }, updateContactStats = { },
updateMemberStats = { _, _ -> }, updateMemberStats = { _, _ -> },
syncContactConnection = { }, syncContactConnection = { },
@@ -1614,6 +1640,7 @@ fun PreviewGroupChatLayout() {
onSearchValueChanged = {}, onSearchValueChanged = {},
onComposed = {}, onComposed = {},
developerTools = false, developerTools = false,
showViaProxy = false,
) )
} }
} }
@@ -16,7 +16,7 @@ import dev.icerock.moko.resources.compose.stringResource
@Composable @Composable
fun ComposeContextInvitingContactMemberView() { fun ComposeContextInvitingContactMemberView() {
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage val sentColor = MaterialTheme.appColors.sentMessage
Row( Row(
Modifier Modifier
.height(60.dp) .height(60.dp)
@@ -16,7 +16,7 @@ import chat.simplex.res.MR
@Composable @Composable
fun ComposeFileView(fileName: String, cancelFile: () -> Unit, cancelEnabled: Boolean) { fun ComposeFileView(fileName: String, cancelFile: () -> Unit, cancelEnabled: Boolean) {
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage val sentColor = MaterialTheme.appColors.sentMessage
Row( Row(
Modifier Modifier
.height(60.dp) .height(60.dp)
@@ -20,7 +20,7 @@ import chat.simplex.common.views.helpers.UploadContent
@Composable @Composable
fun ComposeImageView(media: ComposePreview.MediaPreview, cancelImages: () -> Unit, cancelEnabled: Boolean) { fun ComposeImageView(media: ComposePreview.MediaPreview, cancelImages: () -> Unit, cancelEnabled: Boolean) {
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage val sentColor = MaterialTheme.appColors.sentMessage
Row( Row(
Modifier Modifier
.padding(top = 8.dp) .padding(top = 8.dp)
@@ -408,14 +408,15 @@ fun ComposeView(
composeState.value = composeState.value.copy(inProgress = true) composeState.value = composeState.value.copy(inProgress = true)
} }
suspend fun forwardItem(rhId: Long?, forwardedItem: ChatItem, fromChatInfo: ChatInfo): ChatItem? { suspend fun forwardItem(rhId: Long?, forwardedItem: ChatItem, fromChatInfo: ChatInfo, ttl: Int?): ChatItem? {
val chatItem = controller.apiForwardChatItem( val chatItem = controller.apiForwardChatItem(
rh = rhId, rh = rhId,
toChatType = chat.chatInfo.chatType, toChatType = chat.chatInfo.chatType,
toChatId = chat.chatInfo.apiId, toChatId = chat.chatInfo.apiId,
fromChatType = fromChatInfo.chatType, fromChatType = fromChatInfo.chatType,
fromChatId = fromChatInfo.apiId, fromChatId = fromChatInfo.apiId,
itemId = forwardedItem.id itemId = forwardedItem.id,
ttl = ttl
) )
if (chatItem != null) { if (chatItem != null) {
chatModel.addChatItem(rhId, chat.chatInfo, chatItem) chatModel.addChatItem(rhId, chat.chatInfo, chatItem)
@@ -490,9 +491,9 @@ fun ComposeView(
sendMemberContactInvitation() sendMemberContactInvitation()
sent = null sent = null
} else if (cs.contextItem is ComposeContextItem.ForwardingItem) { } else if (cs.contextItem is ComposeContextItem.ForwardingItem) {
sent = forwardItem(chat.remoteHostId, cs.contextItem.chatItem, cs.contextItem.fromChatInfo) sent = forwardItem(chat.remoteHostId, cs.contextItem.chatItem, cs.contextItem.fromChatInfo, ttl = ttl)
if (cs.message.isNotEmpty()) { if (cs.message.isNotEmpty()) {
sent = send(chat, checkLinkPreview(), quoted = sent?.id, live = false, ttl = null) sent = send(chat, checkLinkPreview(), quoted = sent?.id, live = false, ttl = ttl)
} }
} else if (cs.contextItem is ComposeContextItem.EditingItem) { } else if (cs.contextItem is ComposeContextItem.EditingItem) {
val ei = cs.contextItem.chatItem val ei = cs.contextItem.chatItem
@@ -775,7 +776,7 @@ fun ComposeView(
@Composable @Composable
fun MsgNotAllowedView(reason: String, icon: Painter) { fun MsgNotAllowedView(reason: String, icon: Painter) {
val color = CurrentColors.collectAsState().value.appColors.receivedMessage val color = MaterialTheme.appColors.receivedMessage
Row(Modifier.padding(top = 5.dp).fillMaxWidth().background(color).padding(horizontal = DEFAULT_PADDING_HALF, vertical = DEFAULT_PADDING_HALF * 1.5f), verticalAlignment = Alignment.CenterVertically) { Row(Modifier.padding(top = 5.dp).fillMaxWidth().background(color).padding(horizontal = DEFAULT_PADDING_HALF, vertical = DEFAULT_PADDING_HALF * 1.5f), verticalAlignment = Alignment.CenterVertically) {
Icon(icon, null, tint = MaterialTheme.colors.secondary) Icon(icon, null, tint = MaterialTheme.colors.secondary)
Spacer(Modifier.width(DEFAULT_PADDING_HALF)) Spacer(Modifier.width(DEFAULT_PADDING_HALF))
@@ -861,7 +862,7 @@ fun ComposeView(
} }
} }
Row( Row(
modifier = Modifier.padding(end = 8.dp), modifier = Modifier.background(MaterialTheme.colors.background).padding(end = 8.dp),
verticalAlignment = Alignment.Bottom, verticalAlignment = Alignment.Bottom,
) { ) {
val isGroupAndProhibitedFiles = chat.chatInfo is ChatInfo.Group && !chat.chatInfo.groupInfo.fullGroupPreferences.files.on(chat.chatInfo.groupInfo.membership) val isGroupAndProhibitedFiles = chat.chatInfo is ChatInfo.Group && !chat.chatInfo.groupInfo.fullGroupPreferences.files.on(chat.chatInfo.groupInfo.membership)
@@ -973,7 +974,7 @@ fun ComposeView(
val timedMessageAllowed = remember(chat.chatInfo) { chat.chatInfo.featureEnabled(ChatFeature.TimedMessages) } val timedMessageAllowed = remember(chat.chatInfo) { chat.chatInfo.featureEnabled(ChatFeature.TimedMessages) }
val sendButtonColor = val sendButtonColor =
if (chat.chatInfo.incognito) if (chat.chatInfo.incognito)
if (isSystemInDarkTheme()) Indigo else Indigo.copy(alpha = 0.7F) if (isInDarkTheme()) Indigo else Indigo.copy(alpha = 0.7F)
else MaterialTheme.colors.primary else MaterialTheme.colors.primary
SendMsgView( SendMsgView(
composeState, composeState,
@@ -35,7 +35,7 @@ fun ComposeVoiceView(
) { ) {
val progress = rememberSaveable { mutableStateOf(0) } val progress = rememberSaveable { mutableStateOf(0) }
val duration = rememberSaveable(recordedDurationMs) { mutableStateOf(recordedDurationMs) } val duration = rememberSaveable(recordedDurationMs) { mutableStateOf(recordedDurationMs) }
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage val sentColor = MaterialTheme.appColors.sentMessage
Box { Box {
Box( Box(
Modifier Modifier
@@ -32,8 +32,8 @@ fun ContactPreferencesView(
) { ) {
val contact = remember { derivedStateOf { (m.getContactChat(contactId)?.chatInfo as? ChatInfo.Direct)?.contact } } val contact = remember { derivedStateOf { (m.getContactChat(contactId)?.chatInfo as? ChatInfo.Direct)?.contact } }
val ct = contact.value ?: return val ct = contact.value ?: return
var featuresAllowed by rememberSaveable(ct, stateSaver = serializableSaver()) { mutableStateOf(contactUserPrefsToFeaturesAllowed(ct.mergedPreferences)) } var featuresAllowed by rememberSaveable(ct, user, stateSaver = serializableSaver()) { mutableStateOf(contactUserPrefsToFeaturesAllowed(ct.mergedPreferences)) }
var currentFeaturesAllowed by rememberSaveable(ct, stateSaver = serializableSaver()) { mutableStateOf(featuresAllowed) } var currentFeaturesAllowed by rememberSaveable(ct, user, stateSaver = serializableSaver()) { mutableStateOf(featuresAllowed) }
fun savePrefs(afterSave: () -> Unit = {}) { fun savePrefs(afterSave: () -> Unit = {}) {
withBGApi { withBGApi {
@@ -30,8 +30,8 @@ fun ContextItemView(
cancelContextItem: () -> Unit cancelContextItem: () -> Unit
) { ) {
val sent = contextItem.chatDir.sent val sent = contextItem.chatDir.sent
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage val sentColor = MaterialTheme.appColors.sentMessage
val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage val receivedColor = MaterialTheme.appColors.receivedMessage
@Composable @Composable
fun MessageText(attachment: ImageResource?, lines: Int) { fun MessageText(attachment: ImageResource?, lines: Int) {
@@ -157,7 +157,7 @@ fun SendMsgView(
fun MenuItems(): List<@Composable () -> Unit> { fun MenuItems(): List<@Composable () -> Unit> {
val menuItems = mutableListOf<@Composable () -> Unit>() val menuItems = mutableListOf<@Composable () -> Unit>()
if (cs.liveMessage == null && !cs.editing && !cs.forwarding && !nextSendGrpInv || sendMsgEnabled) { if (cs.liveMessage == null && !cs.editing && !nextSendGrpInv || sendMsgEnabled) {
if ( if (
cs.preview !is ComposePreview.VoicePreview && cs.preview !is ComposePreview.VoicePreview &&
cs.contextItem is ComposeContextItem.NoContextItem && cs.contextItem is ComposeContextItem.NoContextItem &&
@@ -9,7 +9,6 @@ import SectionSpacer
import SectionTextFooter import SectionTextFooter
import SectionView import SectionView
import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.* import androidx.compose.foundation.lazy.*
import androidx.compose.material.* import androidx.compose.material.*
@@ -233,6 +232,16 @@ fun GroupChatInfoLayout(
} else { } else {
SendReceiptsOptionDisabled() SendReceiptsOptionDisabled()
} }
WallpaperButton {
ModalManager.end.showModal {
val chat = remember { derivedStateOf { chatModel.chats.firstOrNull { it.id == chat.id } } }
val c = chat.value
if (c != null) {
ChatWallpaperEditorModal(c)
}
}
}
} }
SectionTextFooter(stringResource(MR.strings.only_group_owners_can_change_prefs)) SectionTextFooter(stringResource(MR.strings.only_group_owners_can_change_prefs))
SectionDividerSpaced(maxTopPadding = true) SectionDividerSpaced(maxTopPadding = true)
@@ -123,13 +123,12 @@ private fun GroupPreferencesLayout(
applyPrefs(preferences.copy(files = RoleGroupPreference(enable = enable, role))) applyPrefs(preferences.copy(files = RoleGroupPreference(enable = enable, role)))
} }
// TODO enable simplexLinks preference in 5.8 SectionDividerSpaced(true, maxBottomPadding = false)
// SectionDividerSpaced(true, maxBottomPadding = false) val allowSimplexLinks = remember(preferences) { mutableStateOf(preferences.simplexLinks.enable) }
// val allowSimplexLinks = remember(preferences) { mutableStateOf(preferences.simplexLinks.enable) } val simplexLinksRole = remember(preferences) { mutableStateOf(preferences.simplexLinks.role) }
// val simplexLinksRole = remember(preferences) { mutableStateOf(preferences.simplexLinks.role) } FeatureSection(GroupFeature.SimplexLinks, allowSimplexLinks, simplexLinksRole, groupInfo, preferences, onTTLUpdated) { enable, role ->
// FeatureSection(GroupFeature.SimplexLinks, allowSimplexLinks, simplexLinksRole, groupInfo, preferences, onTTLUpdated) { enable, role -> applyPrefs(preferences.copy(simplexLinks = RoleGroupPreference(enable = enable, role)))
// applyPrefs(preferences.copy(simplexLinks = RoleGroupPreference(enable = enable, role))) }
// }
SectionDividerSpaced(true, maxBottomPadding = false) SectionDividerSpaced(true, maxBottomPadding = false)
val enableHistory = remember(preferences) { mutableStateOf(preferences.history.enable) } val enableHistory = remember(preferences) { mutableStateOf(preferences.history.enable) }
@@ -189,8 +188,6 @@ private fun FeatureSection(
generalGetString(MR.strings.feature_enabled_for), generalGetString(MR.strings.feature_enabled_for),
featureRoles, featureRoles,
enableForRole, enableForRole,
// remove in v5.8
enabled = remember { mutableStateOf(false) },
onSelected = { value -> onSelected = { value ->
onSelected(enableFeature.value, value) onSelected(enableFeature.value, value)
} }
@@ -47,7 +47,7 @@ fun CICallItemView(
CICallStatus.Error -> {} CICallStatus.Error -> {}
} }
CIMetaView(cItem, timedMessagesTTL, showStatus = false, showEdited = false) CIMetaView(cItem, timedMessagesTTL, showStatus = false, showEdited = false, showViaProxy = false)
} }
} }
@@ -1,6 +1,6 @@
package chat.simplex.common.views.chat.item package chat.simplex.common.views.chat.item
import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CornerSize import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
@@ -28,6 +28,7 @@ import java.net.URI
fun CIFileView( fun CIFileView(
file: CIFile?, file: CIFile?,
edited: Boolean, edited: Boolean,
showMenu: MutableState<Boolean>,
receiveFile: (Long) -> Unit receiveFile: (Long) -> Unit
) { ) {
val saveFileLauncher = rememberSaveFileLauncher(ciFile = file) val saveFileLauncher = rememberSaveFileLauncher(ciFile = file)
@@ -62,7 +63,7 @@ fun CIFileView(
fun fileAction() { fun fileAction() {
if (file != null) { if (file != null) {
when { when {
file.fileStatus is CIFileStatus.RcvInvitation -> { file.fileStatus is CIFileStatus.RcvInvitation || file.fileStatus is CIFileStatus.RcvAborted -> {
if (fileSizeValid(file)) { if (fileSizeValid(file)) {
receiveFile(file.fileId) receiveFile(file.fileId)
} else { } else {
@@ -86,7 +87,7 @@ fun CIFileView(
) )
FileProtocol.LOCAL -> {} FileProtocol.LOCAL -> {}
} }
file.fileStatus is CIFileStatus.RcvComplete || (file.fileStatus is CIFileStatus.SndStored && file.fileProtocol == FileProtocol.LOCAL) -> { file.forwardingAllowed() -> {
withLongRunningApi(slow = 600_000) { withLongRunningApi(slow = 600_000) {
var filePath = getLoadedFilePath(file) var filePath = getLoadedFilePath(file)
if (chatModel.connectedToRemote() && filePath == null) { if (chatModel.connectedToRemote() && filePath == null) {
@@ -136,8 +137,7 @@ fun CIFileView(
Box( Box(
Modifier Modifier
.size(42.dp) .size(42.dp)
.clip(RoundedCornerShape(4.dp)) .clip(RoundedCornerShape(4.dp)),
.clickable(onClick = { fileAction() }),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
if (file != null) { if (file != null) {
@@ -154,7 +154,13 @@ fun CIFileView(
FileProtocol.SMP -> progressIndicator() FileProtocol.SMP -> progressIndicator()
FileProtocol.LOCAL -> {} FileProtocol.LOCAL -> {}
} }
is CIFileStatus.SndComplete -> fileIcon(innerIcon = painterResource(MR.images.ic_check_filled)) is CIFileStatus.SndComplete -> {
if ((file.forwardingAllowed() || (chatModel.connectedToRemote() && CIFile.cachedRemoteFileRequests[file.fileSource] == true))) {
fileIcon()
} else {
fileIcon(innerIcon = painterResource(MR.images.ic_check_filled))
}
}
is CIFileStatus.SndCancelled -> fileIcon(innerIcon = painterResource(MR.images.ic_close)) is CIFileStatus.SndCancelled -> fileIcon(innerIcon = painterResource(MR.images.ic_close))
is CIFileStatus.SndError -> fileIcon(innerIcon = painterResource(MR.images.ic_close)) is CIFileStatus.SndError -> fileIcon(innerIcon = painterResource(MR.images.ic_close))
is CIFileStatus.RcvInvitation -> is CIFileStatus.RcvInvitation ->
@@ -169,6 +175,8 @@ fun CIFileView(
} else { } else {
progressIndicator() progressIndicator()
} }
is CIFileStatus.RcvAborted ->
fileIcon(innerIcon = painterResource(MR.images.ic_sync_problem), color = MaterialTheme.colors.primary)
is CIFileStatus.RcvComplete -> fileIcon() is CIFileStatus.RcvComplete -> fileIcon()
is CIFileStatus.RcvCancelled -> fileIcon(innerIcon = painterResource(MR.images.ic_close)) is CIFileStatus.RcvCancelled -> fileIcon(innerIcon = painterResource(MR.images.ic_close))
is CIFileStatus.RcvError -> fileIcon(innerIcon = painterResource(MR.images.ic_close)) is CIFileStatus.RcvError -> fileIcon(innerIcon = painterResource(MR.images.ic_close))
@@ -181,7 +189,13 @@ fun CIFileView(
} }
Row( Row(
Modifier.padding(top = 4.dp, bottom = 6.dp, start = 6.dp, end = 12.dp), Modifier
.combinedClickable(
onClick = { fileAction() },
onLongClick = { showMenu.value = true }
)
.padding(top = 4.dp, bottom = 6.dp, start = 6.dp, end = 12.dp),
//Modifier.clickable(enabled = file?.fileSource != null) { if (file?.fileSource != null && getLoadedFilePath(file) != null) openFile(file.fileSource) }.padding(top = 4.dp, bottom = 6.dp, start = 6.dp, end = 12.dp),
verticalAlignment = Alignment.Bottom, verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.spacedBy(2.dp) horizontalArrangement = Arrangement.spacedBy(2.dp)
) { ) {
@@ -83,8 +83,8 @@ fun CIGroupInvitationView(
} }
} }
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage val sentColor = MaterialTheme.appColors.sentMessage
val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage val receivedColor = MaterialTheme.appColors.receivedMessage
Surface( Surface(
modifier = if (action && !inProgress.value) Modifier.clickable(onClick = { modifier = if (action && !inProgress.value) Modifier.clickable(onClick = {
inProgress.value = true inProgress.value = true
@@ -110,6 +110,7 @@ fun CIGroupInvitationView(
.padding(bottom = 4.dp), .padding(bottom = 4.dp),
) { ) {
groupInfoView() groupInfoView()
val secondaryColor = MaterialTheme.colors.secondary
Column(Modifier.padding(top = 2.dp, start = 5.dp)) { Column(Modifier.padding(top = 2.dp, start = 5.dp)) {
Divider(Modifier.fillMaxWidth().padding(bottom = 4.dp)) Divider(Modifier.fillMaxWidth().padding(bottom = 4.dp))
if (action) { if (action) {
@@ -117,7 +118,7 @@ fun CIGroupInvitationView(
Text( Text(
buildAnnotatedString { buildAnnotatedString {
append(generalGetString(if (chatIncognito) MR.strings.group_invitation_tap_to_join_incognito else MR.strings.group_invitation_tap_to_join)) append(generalGetString(if (chatIncognito) MR.strings.group_invitation_tap_to_join_incognito else MR.strings.group_invitation_tap_to_join))
withStyle(reserveTimestampStyle) { append(reserveSpaceForMeta(ci.meta, timedMessagesTTL, encrypted = null, showStatus = false, showEdited = false)) } withStyle(reserveTimestampStyle) { append(reserveSpaceForMeta(ci.meta, timedMessagesTTL, encrypted = null, showStatus = false, showEdited = false, secondaryColor = secondaryColor)) }
}, },
color = if (inProgress.value) color = if (inProgress.value)
MaterialTheme.colors.secondary MaterialTheme.colors.secondary
@@ -128,7 +129,7 @@ fun CIGroupInvitationView(
Text( Text(
buildAnnotatedString { buildAnnotatedString {
append(groupInvitationStr()) append(groupInvitationStr())
withStyle(reserveTimestampStyle) { append(reserveSpaceForMeta(ci.meta, timedMessagesTTL, encrypted = null, showStatus = false, showEdited = false)) } withStyle(reserveTimestampStyle) { append(reserveSpaceForMeta(ci.meta, timedMessagesTTL, encrypted = null, showStatus = false, showEdited = false, secondaryColor = secondaryColor)) }
} }
) )
} }
@@ -144,7 +145,7 @@ fun CIGroupInvitationView(
} }
} }
CIMetaView(ci, timedMessagesTTL, showStatus = false, showEdited = false) CIMetaView(ci, timedMessagesTTL, showStatus = false, showEdited = false, showViaProxy = false)
} }
} }
} }
@@ -2,8 +2,7 @@ package chat.simplex.common.views.chat.item
import androidx.compose.foundation.* import androidx.compose.foundation.*
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.*
import androidx.compose.material.Icon
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -22,17 +21,12 @@ import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.DEFAULT_MAX_IMAGE_WIDTH import chat.simplex.common.ui.theme.DEFAULT_MAX_IMAGE_WIDTH
import chat.simplex.res.MR import chat.simplex.res.MR
import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.StringResource
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import java.io.File
import java.net.URI
@Composable @Composable
fun CIImageView( fun CIImageView(
image: String, image: String,
file: CIFile?, file: CIFile?,
metaColor: Color,
imageProvider: () -> ImageGalleryProvider, imageProvider: () -> ImageGalleryProvider,
showMenu: MutableState<Boolean>, showMenu: MutableState<Boolean>,
receiveFile: (Long) -> Unit receiveFile: (Long) -> Unit
@@ -52,7 +46,7 @@ fun CIImageView(
icon, icon,
stringResource(stringId), stringResource(stringId),
Modifier.fillMaxSize(), Modifier.fillMaxSize(),
tint = metaColor tint = Color.White
) )
} }
@@ -79,6 +73,7 @@ fun CIImageView(
is CIFileStatus.RcvInvitation -> fileIcon(painterResource(MR.images.ic_arrow_downward), MR.strings.icon_descr_asked_to_receive) is CIFileStatus.RcvInvitation -> fileIcon(painterResource(MR.images.ic_arrow_downward), MR.strings.icon_descr_asked_to_receive)
is CIFileStatus.RcvAccepted -> fileIcon(painterResource(MR.images.ic_more_horiz), MR.strings.icon_descr_waiting_for_image) is CIFileStatus.RcvAccepted -> fileIcon(painterResource(MR.images.ic_more_horiz), MR.strings.icon_descr_waiting_for_image)
is CIFileStatus.RcvTransfer -> progressIndicator() is CIFileStatus.RcvTransfer -> progressIndicator()
is CIFileStatus.RcvAborted -> fileIcon(painterResource(MR.images.ic_sync_problem), MR.strings.icon_descr_file)
is CIFileStatus.RcvCancelled -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file) is CIFileStatus.RcvCancelled -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file)
is CIFileStatus.RcvError -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file) is CIFileStatus.RcvError -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file)
is CIFileStatus.Invalid -> fileIcon(painterResource(MR.images.ic_question_mark), MR.strings.icon_descr_file) is CIFileStatus.Invalid -> fileIcon(painterResource(MR.images.ic_question_mark), MR.strings.icon_descr_file)
@@ -113,21 +108,49 @@ fun CIImageView(
} }
@Composable @Composable
fun ImageView(painter: Painter, onClick: () -> Unit) { fun ImageView(painter: Painter, image: String, fileSource: CryptoFile?, onClick: () -> Unit) {
Image( // On my Android device Compose fails to display 6000x6000 px WebP image with exception:
painter, // IllegalStateException: Recording currently in progress - missing #endRecording() call?
contentDescription = stringResource(MR.strings.image_descr), // but can display 5000px image. Using even lower value here just to feel safer.
// .width(DEFAULT_MAX_IMAGE_WIDTH) is a hack for image to increase IntrinsicSize of FramedItemView // It happens to WebP because it's not compressed while sending since it can be animated.
// if text is short and take all available width if text is long if (painter.intrinsicSize.width <= 4320 && painter.intrinsicSize.height <= 4320) {
modifier = Modifier Image(
painter,
contentDescription = stringResource(MR.strings.image_descr),
// .width(DEFAULT_MAX_IMAGE_WIDTH) is a hack for image to increase IntrinsicSize of FramedItemView
// if text is short and take all available width if text is long
modifier = Modifier
.width(if (painter.intrinsicSize.width * 0.97 <= painter.intrinsicSize.height) imageViewFullWidth() * 0.75f else DEFAULT_MAX_IMAGE_WIDTH)
.combinedClickable(
onLongClick = { showMenu.value = true },
onClick = onClick
)
.onRightClick { showMenu.value = true },
contentScale = ContentScale.FillWidth,
)
} else {
Box(Modifier
.width(if (painter.intrinsicSize.width * 0.97 <= painter.intrinsicSize.height) imageViewFullWidth() * 0.75f else DEFAULT_MAX_IMAGE_WIDTH) .width(if (painter.intrinsicSize.width * 0.97 <= painter.intrinsicSize.height) imageViewFullWidth() * 0.75f else DEFAULT_MAX_IMAGE_WIDTH)
.combinedClickable( .combinedClickable(
onLongClick = { showMenu.value = true }, onLongClick = { showMenu.value = true },
onClick = onClick onClick = {}
) )
.onRightClick { showMenu.value = true }, .onRightClick { showMenu.value = true },
contentScale = ContentScale.FillWidth, contentAlignment = Alignment.Center
) ) {
imageView(base64ToBitmap(image), onClick = {
if (fileSource != null) {
openFile(fileSource)
}
})
Icon(
painterResource(MR.images.ic_open_in_new),
contentDescription = stringResource(MR.strings.image_descr),
modifier = Modifier.size(30.dp),
tint = MaterialTheme.colors.primary,
)
}
}
} }
fun fileSizeValid(): Boolean { fun fileSizeValid(): Boolean {
@@ -172,14 +195,14 @@ fun CIImageView(
} }
} }
val loaded = res.value val loaded = res.value
if (loaded != null) { if (loaded != null && file != null) {
val (imageBitmap, data, _) = loaded val (imageBitmap, data, _) = loaded
SimpleAndAnimatedImageView(data, imageBitmap, file, imageProvider, @Composable { painter, onClick -> ImageView(painter, onClick) }) SimpleAndAnimatedImageView(data, imageBitmap, file, imageProvider, @Composable { painter, onClick -> ImageView(painter, image, file.fileSource, onClick) })
} else { } else {
imageView(base64ToBitmap(image), onClick = { imageView(base64ToBitmap(image), onClick = {
if (file != null) { if (file != null) {
when (file.fileStatus) { when (file.fileStatus) {
CIFileStatus.RcvInvitation -> CIFileStatus.RcvInvitation, CIFileStatus.RcvAborted ->
if (fileSizeValid()) { if (fileSizeValid()) {
receiveFile(file.fileId) receiveFile(file.fileId)
} else { } else {
@@ -12,7 +12,6 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import chat.simplex.common.ui.theme.CurrentColors
import chat.simplex.common.model.* import chat.simplex.common.model.*
import chat.simplex.common.ui.theme.isInDarkTheme import chat.simplex.common.ui.theme.isInDarkTheme
import chat.simplex.res.MR import chat.simplex.res.MR
@@ -35,7 +34,8 @@ fun CIMetaView(
blue = minOf(metaColor.red * 1.33F, 1F)) blue = minOf(metaColor.red * 1.33F, 1F))
}, },
showStatus: Boolean = true, showStatus: Boolean = true,
showEdited: Boolean = true showEdited: Boolean = true,
showViaProxy: Boolean
) { ) {
Row(Modifier.padding(start = 3.dp), verticalAlignment = Alignment.CenterVertically) { Row(Modifier.padding(start = 3.dp), verticalAlignment = Alignment.CenterVertically) {
if (chatItem.isDeletedContent) { if (chatItem.isDeletedContent) {
@@ -53,7 +53,8 @@ fun CIMetaView(
metaColor, metaColor,
paleMetaColor, paleMetaColor,
showStatus = showStatus, showStatus = showStatus,
showEdited = showEdited showEdited = showEdited,
showViaProxy = showViaProxy
) )
} }
} }
@@ -68,7 +69,8 @@ private fun CIMetaText(
color: Color, color: Color,
paleColor: Color, paleColor: Color,
showStatus: Boolean = true, showStatus: Boolean = true,
showEdited: Boolean = true showEdited: Boolean = true,
showViaProxy: Boolean
) { ) {
if (showEdited && meta.itemEdited) { if (showEdited && meta.itemEdited) {
StatusIconText(painterResource(MR.images.ic_edit), color) StatusIconText(painterResource(MR.images.ic_edit), color)
@@ -82,6 +84,9 @@ private fun CIMetaText(
} }
Spacer(Modifier.width(4.dp)) Spacer(Modifier.width(4.dp))
} }
if (showViaProxy && meta.sentViaProxy == true) {
Icon(painterResource(MR.images.ic_arrow_forward), null, Modifier.height(17.dp), tint = MaterialTheme.colors.secondary)
}
if (showStatus) { if (showStatus) {
val statusIcon = meta.statusIcon(MaterialTheme.colors.primary, color, paleColor) val statusIcon = meta.statusIcon(MaterialTheme.colors.primary, color, paleColor)
if (statusIcon != null) { if (statusIcon != null) {
@@ -105,7 +110,15 @@ private fun CIMetaText(
} }
// the conditions in this function should match CIMetaText // the conditions in this function should match CIMetaText
fun reserveSpaceForMeta(meta: CIMeta, chatTTL: Int?, encrypted: Boolean?, showStatus: Boolean = true, showEdited: Boolean = true): String { fun reserveSpaceForMeta(
meta: CIMeta,
chatTTL: Int?,
encrypted: Boolean?,
secondaryColor: Color,
showStatus: Boolean = true,
showEdited: Boolean = true,
showViaProxy: Boolean = false
): String {
val iconSpace = " " val iconSpace = " "
var res = "" var res = ""
if (showEdited && meta.itemEdited) res += iconSpace if (showEdited && meta.itemEdited) res += iconSpace
@@ -116,7 +129,10 @@ fun reserveSpaceForMeta(meta: CIMeta, chatTTL: Int?, encrypted: Boolean?, showSt
res += shortTimeText(ttl) res += shortTimeText(ttl)
} }
} }
if (showStatus && (meta.statusIcon(CurrentColors.value.colors.secondary) != null || !meta.disappearing)) { if (showViaProxy && meta.sentViaProxy == true) {
res += iconSpace
}
if (showStatus && (meta.statusIcon(secondaryColor) != null || !meta.disappearing)) {
res += iconSpace res += iconSpace
} }
if (encrypted != null) { if (encrypted != null) {
@@ -137,7 +153,8 @@ fun PreviewCIMetaView() {
chatItem = ChatItem.getSampleData( chatItem = ChatItem.getSampleData(
1, CIDirection.DirectSnd(), Clock.System.now(), "hello" 1, CIDirection.DirectSnd(), Clock.System.now(), "hello"
), ),
null null,
showViaProxy = false
) )
} }
@@ -149,7 +166,8 @@ fun PreviewCIMetaViewUnread() {
1, CIDirection.DirectSnd(), Clock.System.now(), "hello", 1, CIDirection.DirectSnd(), Clock.System.now(), "hello",
status = CIStatus.RcvNew() status = CIStatus.RcvNew()
), ),
null null,
showViaProxy = false
) )
} }
@@ -159,9 +177,10 @@ fun PreviewCIMetaViewSendFailed() {
CIMetaView( CIMetaView(
chatItem = ChatItem.getSampleData( chatItem = ChatItem.getSampleData(
1, CIDirection.DirectSnd(), Clock.System.now(), "hello", 1, CIDirection.DirectSnd(), Clock.System.now(), "hello",
status = CIStatus.SndError("CMD SYNTAX") status = CIStatus.CISSndError(SndError.Other("CMD SYNTAX"))
), ),
null null,
showViaProxy = false
) )
} }
@@ -172,7 +191,8 @@ fun PreviewCIMetaViewSendNoAuth() {
chatItem = ChatItem.getSampleData( chatItem = ChatItem.getSampleData(
1, CIDirection.DirectSnd(), Clock.System.now(), "hello", status = CIStatus.SndErrorAuth() 1, CIDirection.DirectSnd(), Clock.System.now(), "hello", status = CIStatus.SndErrorAuth()
), ),
null null,
showViaProxy = false
) )
} }
@@ -183,7 +203,8 @@ fun PreviewCIMetaViewSendSent() {
chatItem = ChatItem.getSampleData( chatItem = ChatItem.getSampleData(
1, CIDirection.DirectSnd(), Clock.System.now(), "hello", status = CIStatus.SndSent(SndCIStatusProgress.Complete) 1, CIDirection.DirectSnd(), Clock.System.now(), "hello", status = CIStatus.SndSent(SndCIStatusProgress.Complete)
), ),
null null,
showViaProxy = false
) )
} }
@@ -195,7 +216,8 @@ fun PreviewCIMetaViewEdited() {
1, CIDirection.DirectSnd(), Clock.System.now(), "hello", 1, CIDirection.DirectSnd(), Clock.System.now(), "hello",
itemEdited = true itemEdited = true
), ),
null null,
showViaProxy = false
) )
} }
@@ -208,7 +230,8 @@ fun PreviewCIMetaViewEditedUnread() {
itemEdited = true, itemEdited = true,
status= CIStatus.RcvNew() status= CIStatus.RcvNew()
), ),
null null,
showViaProxy = false
) )
} }
@@ -221,7 +244,8 @@ fun PreviewCIMetaViewEditedSent() {
itemEdited = true, itemEdited = true,
status= CIStatus.SndSent(SndCIStatusProgress.Complete) status= CIStatus.SndSent(SndCIStatusProgress.Complete)
), ),
null null,
showViaProxy = false
) )
} }
@@ -230,6 +254,7 @@ fun PreviewCIMetaViewEditedSent() {
fun PreviewCIMetaViewDeletedContent() { fun PreviewCIMetaViewDeletedContent() {
CIMetaView( CIMetaView(
chatItem = ChatItem.getDeletedContentSampleData(), chatItem = ChatItem.getDeletedContentSampleData(),
null null,
showViaProxy = false
) )
} }
@@ -14,6 +14,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import chat.simplex.common.model.* import chat.simplex.common.model.*
import chat.simplex.common.ui.theme.CurrentColors import chat.simplex.common.ui.theme.CurrentColors
import chat.simplex.common.ui.theme.appColors
import chat.simplex.common.views.helpers.AlertManager import chat.simplex.common.views.helpers.AlertManager
import chat.simplex.common.views.helpers.generalGetString import chat.simplex.common.views.helpers.generalGetString
import chat.simplex.res.MR import chat.simplex.res.MR
@@ -137,7 +138,7 @@ fun DecryptionErrorItemFixButton(
onClick: () -> Unit, onClick: () -> Unit,
syncSupported: Boolean syncSupported: Boolean
) { ) {
val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage val receivedColor = MaterialTheme.appColors.receivedMessage
Surface( Surface(
Modifier.clickable(onClick = onClick), Modifier.clickable(onClick = onClick),
shape = RoundedCornerShape(18.dp), shape = RoundedCornerShape(18.dp),
@@ -164,17 +165,18 @@ fun DecryptionErrorItemFixButton(
tint = if (syncSupported) MaterialTheme.colors.primary else MaterialTheme.colors.secondary tint = if (syncSupported) MaterialTheme.colors.primary else MaterialTheme.colors.secondary
) )
Spacer(Modifier.padding(2.dp)) Spacer(Modifier.padding(2.dp))
val secondaryColor = MaterialTheme.colors.secondary
Text( Text(
buildAnnotatedString { buildAnnotatedString {
append(generalGetString(MR.strings.fix_connection)) append(generalGetString(MR.strings.fix_connection))
withStyle(reserveTimestampStyle) { append(reserveSpaceForMeta(ci.meta, null, encrypted = null)) } withStyle(reserveTimestampStyle) { append(reserveSpaceForMeta(ci.meta, null, encrypted = null, secondaryColor = secondaryColor)) }
withStyle(reserveTimestampStyle) { append(" ") } // for icon withStyle(reserveTimestampStyle) { append(" ") } // for icon
}, },
color = if (syncSupported) MaterialTheme.colors.primary else MaterialTheme.colors.secondary color = if (syncSupported) MaterialTheme.colors.primary else MaterialTheme.colors.secondary
) )
} }
} }
CIMetaView(ci, timedMessagesTTL = null) CIMetaView(ci, timedMessagesTTL = null, showViaProxy = false)
} }
} }
} }
@@ -184,7 +186,7 @@ fun DecryptionErrorItem(
ci: ChatItem, ci: ChatItem,
onClick: () -> Unit onClick: () -> Unit
) { ) {
val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage val receivedColor = MaterialTheme.appColors.receivedMessage
Surface( Surface(
Modifier.clickable(onClick = onClick), Modifier.clickable(onClick = onClick),
shape = RoundedCornerShape(18.dp), shape = RoundedCornerShape(18.dp),
@@ -195,14 +197,15 @@ fun DecryptionErrorItem(
Modifier.padding(vertical = 6.dp, horizontal = 12.dp), Modifier.padding(vertical = 6.dp, horizontal = 12.dp),
contentAlignment = Alignment.BottomEnd, contentAlignment = Alignment.BottomEnd,
) { ) {
val secondaryColor = MaterialTheme.colors.secondary
Text( Text(
buildAnnotatedString { buildAnnotatedString {
withStyle(SpanStyle(fontStyle = FontStyle.Italic, color = Color.Red)) { append(ci.content.text) } withStyle(SpanStyle(fontStyle = FontStyle.Italic, color = Color.Red)) { append(ci.content.text) }
withStyle(reserveTimestampStyle) { append(reserveSpaceForMeta(ci.meta, null, encrypted = null)) } withStyle(reserveTimestampStyle) { append(reserveSpaceForMeta(ci.meta, null, encrypted = null, secondaryColor = secondaryColor)) }
}, },
style = MaterialTheme.typography.body1.copy(lineHeight = 22.sp) style = MaterialTheme.typography.body1.copy(lineHeight = 22.sp)
) )
CIMetaView(ci, timedMessagesTTL = null) CIMetaView(ci, timedMessagesTTL = null, showViaProxy = false)
} }
} }
} }
@@ -73,7 +73,7 @@ fun CIVideoView(
VideoPreviewImageView(preview, onClick = { VideoPreviewImageView(preview, onClick = {
if (file != null) { if (file != null) {
when (file.fileStatus) { when (file.fileStatus) {
CIFileStatus.RcvInvitation -> CIFileStatus.RcvInvitation, CIFileStatus.RcvAborted ->
receiveFileIfValidSize(file, receiveFile) receiveFileIfValidSize(file, receiveFile)
CIFileStatus.RcvAccepted -> CIFileStatus.RcvAccepted ->
when (file.fileProtocol) { when (file.fileProtocol) {
@@ -102,7 +102,7 @@ fun CIVideoView(
if (file != null) { if (file != null) {
DurationProgress(file, remember { mutableStateOf(false) }, remember { mutableStateOf(duration * 1000L) }, remember { mutableStateOf(0L) }/*, soundEnabled*/) DurationProgress(file, remember { mutableStateOf(false) }, remember { mutableStateOf(duration * 1000L) }, remember { mutableStateOf(0L) }/*, soundEnabled*/)
} }
if (file?.fileStatus is CIFileStatus.RcvInvitation) { if (file?.fileStatus is CIFileStatus.RcvInvitation || file?.fileStatus is CIFileStatus.RcvAborted) {
PlayButton(error = false, { showMenu.value = true }) { receiveFileIfValidSize(file, receiveFile) } PlayButton(error = false, { showMenu.value = true }) { receiveFileIfValidSize(file, receiveFile) }
} }
} }
@@ -396,6 +396,7 @@ private fun loadingIndicator(file: CIFile?) {
} else { } else {
progressIndicator() progressIndicator()
} }
is CIFileStatus.RcvAborted -> fileIcon(painterResource(MR.images.ic_sync_problem), MR.strings.icon_descr_file)
is CIFileStatus.RcvCancelled -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file) is CIFileStatus.RcvCancelled -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file)
is CIFileStatus.RcvError -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file) is CIFileStatus.RcvError -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file)
is CIFileStatus.Invalid -> fileIcon(painterResource(MR.images.ic_question_mark), MR.strings.icon_descr_file) is CIFileStatus.Invalid -> fileIcon(painterResource(MR.images.ic_question_mark), MR.strings.icon_descr_file)

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