Compare commits

...

84 Commits

Author SHA1 Message Date
Avently ed16f71ee4 creating wallpaper dir at launch 2024-06-20 09:55:41 +01:00
Avently 5bc70bdfed creating directory for wallpapers 2024-06-20 09:55:41 +01:00
Avently fb4927ca87 divided types per target 2024-06-20 09:55:41 +01:00
Avently 2023464a13 types and api 2024-06-20 09:55:41 +01:00
Stanislav Dmitrenko b8bf5871fb ios: wallpapers (#4304)
* ios: wallpapers

* theme selection

* applied theme colors and preset wallpaper

* more places with background

* one more

* accent color

* defaults

* rename

* background

* no change to cell color

* unneeded

* changes

* no global tint

* defaults

* removed unneeded class

* for merging
2024-06-20 09:51:29 +01:00
Diogo 1573b7af13 ios: prevented error on desktop in lack of camera permissions (#4306)
* refactor: 💡 moved ScannerInView to separate module

made it more extensible and ready to be reused

* fix: 🐛 prevented error on desktop in lack of camera permissions

right now, connect to desktop functionality was failing if you didn't
accept permissions before jumping to this screen for the first time. In
here I did pick from other sections where cases of no permissions or
unknown permissions were correctly handled and did reuse the same
mechanism

* chore: 🤖 reverted file move

* chore: 🤖 reverted class change

* chore: 🤖 reverted uncessary change

* chore: 🤖 removed leftover
2024-06-11 09:29:53 +04:00
Narasimha-sc a5bf3cbca8 v5.6 -> v5.8 (#4308) 2024-06-10 18:33:27 +01:00
Stanislav Dmitrenko d5529031ee android, desktop: socks proxy toggle enhancements (#4312) 2024-06-10 18:33:10 +01:00
spaced4ndy d244338b13 ui: differentiate remote ctrl errors, better error texts (#4302) 2024-06-10 18:18:05 +04:00
spaced4ndy ce55c84b96 core: differentiate RCP error in view, test (#4310) 2024-06-10 17:58:53 +04:00
spaced4ndy c52f9baefa desktop: close chat view when chat is stopped (#4313) 2024-06-10 13:11:35 +01:00
Stanislav Dmitrenko ea1c5da65d desktop: change thread who responsible for writting settings (#4311) 2024-06-10 13:05:53 +01:00
sh 5695bb9d46 docs: change smp webpage path (#4307) 2024-06-08 15:42:12 +01:00
spaced4ndy 13751d5705 core: update simplexmq (upload chunks after preparation) (#4299) 2024-06-06 16:09:56 +04:00
spaced4ndy 02803b1491 core: fix testXFTPRcvError test 2024-06-05 21:16:04 +04:00
spaced4ndy 222af69a71 multiplatform: file errors (#4289) 2024-06-05 21:03:59 +04:00
spaced4ndy 5e96e1ea2b ios: file errors (#4281) 2024-06-05 21:03:05 +04:00
spaced4ndy 490e8cead8 core: file errors (#4261) 2024-06-05 21:02:13 +04:00
Evgeny Poberezkin f578ee843b core: update simplexmq (remove protocol encodings) (#4272)
* core: update simplexmq (remove protocol encodings)

* nix

* update simplexmq

* update simplexmq
2024-06-05 20:49:01 +04:00
Evgeny Poberezkin cf2eb76cb7 website: update MoneroKon group link 2024-06-05 17:33:52 +01:00
Evgeny Poberezkin 48f9dcab9a blog: update image 2024-06-04 20:03:42 +01:00
Evgeny Poberezkin ac2ef54838 blog: v5.8 announcement (#4275)
* blog: v5.8 announcement

* update

* update

* update readme

* images, preview

* simplify

* spaces
2024-06-04 19:44:29 +01:00
Evgeny Poberezkin 7e269c6b8c android, desktop: enter app logic (#4288)
Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
2024-06-04 15:59:23 +01:00
Stanislav Dmitrenko 3e739924bb android, desktop: prevent duplication of group in UI when creating one (#4284) 2024-06-04 15:59:02 +01:00
Stanislav Dmitrenko 83706b19da android, desktop: removed additional background under chat bubbles (#4287) 2024-06-04 15:58:29 +01:00
Stanislav Dmitrenko 6f19a7a20f android, desktop: fixes swipe to reply when animations were disabled in the OS (#4286) 2024-06-04 15:57:48 +01:00
spaced4ndy a8740cee4b core: fix cancellation of snd files without description (e.g. while being prepared for upload) (#4285) 2024-06-04 16:17:12 +04:00
sh 4000fe383d docs/smp-server: web server pages configuration and statistics update (#4279)
* docs/smp-server: web server pages configuration and statistics update

* update table

* update

* typo

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-06-03 20:29:12 +01:00
Evgeny Poberezkin 75445e604e 5.8: ios 224, android 219, desktop 53 2024-06-03 14:05:59 +01:00
spaced4ndy 0596278e50 docs: flexible user records rfc (#4205) 2024-06-03 14:15:41 +04:00
Stanislav Dmitrenko 7a77775a42 android, desktop: fix member search in group (#4277) 2024-06-03 08:25:08 +01:00
Evgeny Poberezkin dc8d08ec04 website: translations (#4274)
* Added translation using Weblate (Hungarian)

* Added translation using Weblate (Hungarian)

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

Currently translated at 100.0% (257 of 257 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (257 of 257 strings)

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

* Translated using Weblate (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/

---------

Co-authored-by: M1K4 <oomikaoo@gmail.com>
Co-authored-by: Maksym Lukashenko <livelmaxim@gmail.com>
Co-authored-by: summoner001 <summoner@vivaldi.net>
Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
2024-06-02 19:43:59 +01:00
Evgeny Poberezkin 4ad0e0f65e ui: translations (#4273)
* Translated using Weblate (Italian)

Currently translated at 100.0% (1819 of 1819 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1549 of 1549 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1819 of 1819 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1819 of 1819 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 99.8% (1547 of 1549 strings)

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

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (1819 of 1819 strings)

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

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (1549 of 1549 strings)

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

* Translated using Weblate (Turkish)

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

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1549 of 1549 strings)

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

* Translated using Weblate (Turkish)

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

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

* Translated using Weblate (German)

Currently translated at 100.0% (1819 of 1819 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1549 of 1549 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1819 of 1819 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1549 of 1549 strings)

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

* Translated using Weblate (French)

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

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1823 of 1823 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1823 of 1823 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1549 of 1549 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1549 of 1549 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1823 of 1823 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1549 of 1549 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1823 of 1823 strings)

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

* Translated using Weblate (Persian)

Currently translated at 99.9% (1822 of 1823 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1819 of 1819 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1549 of 1549 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1819 of 1819 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1819 of 1819 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 99.8% (1547 of 1549 strings)

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

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (1819 of 1819 strings)

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

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (1549 of 1549 strings)

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

* Translated using Weblate (Turkish)

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

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1549 of 1549 strings)

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

* Translated using Weblate (Turkish)

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

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

* Translated using Weblate (German)

Currently translated at 100.0% (1819 of 1819 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1549 of 1549 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1819 of 1819 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1549 of 1549 strings)

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

* Translated using Weblate (French)

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

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1823 of 1823 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1823 of 1823 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1549 of 1549 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1549 of 1549 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1823 of 1823 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1549 of 1549 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1823 of 1823 strings)

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

* Translated using Weblate (Persian)

Currently translated at 99.9% (1822 of 1823 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1823 of 1823 strings)

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

* Translated using Weblate (Romanian)

Currently translated at 13.0% (237 of 1823 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1823 of 1823 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1549 of 1549 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1823 of 1823 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1549 of 1549 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1823 of 1823 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1549 of 1549 strings)

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

* Translated using Weblate (Polish)

Currently translated at 99.9% (1822 of 1823 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1549 of 1549 strings)

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

* Translated using Weblate (Hungarian)

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

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1549 of 1549 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1549 of 1549 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 96.9% (1767 of 1823 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 100.0% (1823 of 1823 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1823 of 1823 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (1823 of 1823 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1549 of 1549 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (1549 of 1549 strings)

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

* import/export localizations

---------

Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: M1K4 <oomikaoo@gmail.com>
Co-authored-by: Maksym Lukashenko <livelmaxim@gmail.com>
Co-authored-by: z0rden <kaanpeker196@gmail.com>
Co-authored-by: summoner001 <summoner@vivaldi.net>
Co-authored-by: J R <jr@simplex.chat>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: khalidbelk <khalid.belkassmi-el-hafi@epitech.eu>
Co-authored-by: Siavash <ciavash@proton.me>
Co-authored-by: Flavian <3zorro.1@gmail.com>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
Co-authored-by: Ninja Left <s1lntlwd@proton.me>
Co-authored-by: Miyu Sakatsuki <miyu-sakatsuki@outlook.jp>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
2024-06-02 19:20:46 +01:00
Evgeny Poberezkin d01515e755 blog: Protecting Children's Safety Requires End-to-End Encryption 2024-06-01 21:47:38 +01:00
Evgeny Poberezkin 3fa70ba217 5.8-beta.5: ios 223, android 218, desktop 52 2024-06-01 11:03:22 +01:00
Evgeny Poberezkin 6ae4cddc2e Merge branch 'stable' 2024-06-01 09:33:36 +01:00
Evgeny Poberezkin ac8ee971f5 5.7.5: ios 222, android 216, desktop 51 2024-06-01 09:15:41 +01:00
Evgeny Poberezkin 11d4872186 core: 5.8.0.5 (simplexmq 5.8.0.10) 2024-06-01 00:22:26 +01:00
Evgeny Poberezkin 370e2e87ad Merge branch 'stable' 2024-05-31 23:50:00 +01:00
Evgeny Poberezkin ef30fcf5e2 cli: options for private message routing and fallback mode (#4256)
* cli: options for private message routing and fallback mode

* remove short options

* fix tests

* encoding

* fix
2024-05-31 23:41:20 +01:00
Evgeny Poberezkin 6ad1423f29 core: 5.7.5.0 (simplexmq: 5.7.6.0, file download fix) 2024-05-31 23:35:00 +01:00
Stanislav Dmitrenko c52e5349db ios: more sheets will become hidden while protect screen on (#4259)
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-05-31 22:47:57 +01:00
Stanislav Dmitrenko 3c98c6acc0 android, desktop: selectable cards for proxy options (#4263)
* android, desktop: selectable cards for proxy options

* title
2024-05-31 22:25:15 +01:00
Stanislav Dmitrenko c251f5e2bd android, desktop: debug subscribed SMP queues (#4262)
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-05-31 18:54:12 +01:00
Evgeny Poberezkin b165603136 ios: log file error to console 2024-05-31 18:33:57 +01:00
spaced4ndy 184a24b70e ui: fix filtering of contacts that can be invited to group (#4265) 2024-05-31 18:30:59 +01:00
spaced4ndy d46bae6498 ios: allow to save sent file on tap (#4264) 2024-05-31 20:20:49 +04:00
Evgeny Poberezkin 5ac521f6d8 core: commands to debug subscribed SMP queues (#4253)
* core: commands to debug subscribed SMP queues

* getLastRcvMsgInfo

* DESC

* update

* update commands, ui compiles

* update

* ios: fix type

* swap

* simplexmq

* group member queue info

* copy button in the alert
2024-05-31 12:45:58 +01:00
Stanislav Dmitrenko 7b5b747b19 desktop: performant drawing of wallpaper (#4249)
* desktop: low quality drawing of wallpaper

* fast drawing

* clean up

* unused
2024-05-30 17:07:58 +01:00
Stanislav Dmitrenko 037655e30f android, desktop: creating wallpapers dir in some situations (#4254) 2024-05-30 17:04:00 +01:00
spaced4ndy 54088c6d5a core: "/get queues" api to debug agent queues (#4252) 2024-05-30 14:24:33 +04:00
Stanislav Dmitrenko 78f7fcc89f android, desktop: wallpaper fixes (#4251) 2024-05-30 07:03:23 +01:00
Evgeny Poberezkin b402caeb89 5.8-beta.4: ios 221, android 215, desktop 50 2024-05-29 18:49:35 +01:00
Evgeny Poberezkin ff8a8de3c8 Revert "android, desktop: change wallpaper file type from PNG to WebP (#4250)"
This reverts commit d22c0c2614.
2024-05-29 17:35:57 +01:00
Stanislav Dmitrenko d22c0c2614 android, desktop: change wallpaper file type from PNG to WebP (#4250) 2024-05-29 13:31:31 +01:00
Evgeny Poberezkin 73c8d3c2e3 android, desktop: add Persian interface language 2024-05-29 10:06:54 +01:00
Evgeny Poberezkin 0110385e65 ui: v5.8 whats new (#4244)
* ios: v5.8 whats new

* blog post stub

* update

* android

* ios: export localizations
2024-05-29 09:59:41 +01:00
Evgeny Poberezkin ad0a678c5f core: 5.8.0.4 (simplexmq 5.8.0.7) 2024-05-29 08:20:51 +01:00
sh 0495a4cd78 docs/smp-server: expand tor configuration, add security practices (#4228)
* docs/smp-server: expand tor configuration, add security practices

* docs/smp-server: add TOC

* update

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-05-28 22:26:15 +01:00
Stanislav Dmitrenko 02267c5263 android: show alert of exception while initializing WebView (#4245)
* android: show alert of exception while initializing WebView

* Update apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-05-28 22:01:45 +01:00
Stanislav Dmitrenko cad3bc048f desktop: prevent corruption of settings file (#4243)
* desktop: prevent corruption of settings file

* check for existence

* unneeded

* simplify

* unused
2024-05-28 21:41:22 +01:00
Stanislav Dmitrenko 1b04423745 android, desktop: close notes tab on switching between chats (#4246) 2024-05-28 16:35:43 +01:00
spaced4ndy 2143eb2d7a core: batch send pending group messages (#4242) 2024-05-28 18:32:29 +04:00
spaced4ndy 3d395b0e45 core: inactive connections/members (#4009) 2024-05-28 16:42:07 +04:00
Stanislav Dmitrenko d79b4dfb5a android: replace new line in Uri before transforming it to URI (#4240) 2024-05-28 11:24:57 +01:00
Evgeny Poberezkin 0ae9f5ebba website: translations (#4241)
* 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 (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 (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/

* 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 (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 (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: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: Miyu Sakatsuki <miyu-sakatsuki@outlook.jp>
2024-05-28 11:23:58 +01:00
Evgeny Poberezkin 57c80786dc ui: translations, remove translations for wallpaper names used in exported themes (#4239)
* 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 54.1% (943 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 (Persian)

Currently translated at 65.5% (1141 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% (1741 of 1741 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1515 of 1515 strings)

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

* Translated using Weblate (Persian)

Currently translated at 76.3% (1330 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 83.0% (1446 of 1741 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1751 of 1751 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1751 of 1751 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1751 of 1751 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1772 of 1772 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1772 of 1772 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 99.3% (1760 of 1772 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 88.3% (1566 of 1772 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% (1772 of 1772 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 100.0% (1772 of 1772 strings)

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

* Translated using Weblate (Polish)

Currently translated at 98.3% (1743 of 1772 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1772 of 1772 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1772 of 1772 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1772 of 1772 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1772 of 1772 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 98.8% (1751 of 1772 strings)

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

* Translated using Weblate (Polish)

Currently translated at 99.9% (1771 of 1772 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1772 of 1772 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1515 of 1515 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1772 of 1772 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1772 of 1772 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1515 of 1515 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 95.6% (1449 of 1515 strings)

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

* Translated using Weblate (Turkish)

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

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

* Translated using Weblate (Romanian)

Currently translated at 9.8% (175 of 1772 strings)

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

* 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.6% (189 of 1772 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1778 of 1778 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1515 of 1515 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1778 of 1778 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1778 of 1778 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1778 of 1778 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1778 of 1778 strings)

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

* Translated using Weblate (Polish)

Currently translated at 99.9% (1777 of 1778 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1778 of 1778 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1515 of 1515 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1778 of 1778 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1515 of 1515 strings)

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

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

Currently translated at 100.0% (1778 of 1778 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1778 of 1778 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1515 of 1515 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1778 of 1778 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1778 of 1778 strings)

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

* Translated using Weblate (Persian)

Currently translated at 100.0% (1778 of 1778 strings)

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

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

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

* Translated using Weblate (Hungarian)

Currently translated at 98.8% (1794 of 1814 strings)

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

* Translated using Weblate (Czech)

Currently translated at 94.2% (1710 of 1814 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (1814 of 1814 strings)

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

* Translated using Weblate (Polish)

Currently translated at 99.9% (1813 of 1814 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1814 of 1814 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 99.6% (1807 of 1814 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1814 of 1814 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1814 of 1814 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1814 of 1814 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1814 of 1814 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1515 of 1515 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 100.0% (1814 of 1814 strings)

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

* Translated using Weblate (Turkish)

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

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

* Translated using Weblate (Hungarian)

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

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

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

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

* Translated using Weblate (Ukrainian)

Currently translated at 97.1% (1764 of 1815 strings)

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

* process localizations

* removed wallpapers names

---------

Co-authored-by: summoner001 <summoner@vivaldi.net>
Co-authored-by: Siavash <ciavash@proton.me>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: M1K4 <oomikaoo@gmail.com>
Co-authored-by: Miyu Sakatsuki <miyu-sakatsuki@outlook.jp>
Co-authored-by: sith-on-mars <groguko36@pm.me>
Co-authored-by: z0rden <kaanpeker196@gmail.com>
Co-authored-by: Flavian <3zorro.1@gmail.com>
Co-authored-by: tuananh-ng <158744840+tuananh-ng@users.noreply.github.com>
Co-authored-by: indiandoc <marc@nymtech.net>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: zenobit <zen@osowoso.xyz>
Co-authored-by: Maksym Lukashenko <livelmaxim@gmail.com>
Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
2024-05-28 11:20:52 +01:00
Stanislav Dmitrenko da1cad96fc android: fix showing empty notification (#4237)
* android: fix showing empty notification

* fix desktop concurrent notifications
2024-05-28 08:04:23 +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
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
313 changed files with 17310 additions and 2675 deletions
+3 -1
View File
@@ -234,6 +234,8 @@ You can use SimpleX with your own servers and still communicate with people usin
Recent and important updates:
[Jun 4, 2024. SimpleX network: private message routing, v5.8 released with IP address protection and chat themes](./blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md)
[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)
@@ -382,10 +384,10 @@ Please also join [#simplex-devs](https://simplex.chat/contact#/?v=1-2&smp=smp%3A
- ✅ Private notes.
- ✅ Improve sending videos (including encryption of locally stored videos).
- ✅ Post-quantum resistant key exchange in double ratchet protocol.
- ✅ Message delivery relay for senders (to conceal IP address from the recipients' servers and to reduce the traffic).
- 🏗 Improve stability and reduce battery usage.
- 🏗 Improve experience for the new users.
- 🏗 Large groups, communities and public channels.
- 🏗 Message delivery relay for senders (to conceal IP address from the recipients' servers and to reduce the traffic).
- Privacy & security slider - a simple way to set all settings at once.
- SMP queue redundancy and rotation (manual is supported).
- Include optional message into connection request sent via contact address.
+9 -2
View File
@@ -17,6 +17,7 @@ class AppDelegate: NSObject, UIApplicationDelegate {
if #available(iOS 17.0, *) { trackKeyboard() }
NotificationCenter.default.addObserver(self, selector: #selector(pasteboardChanged), name: UIPasteboard.changedNotification, object: nil)
removePasscodesIfReinstalled()
prepareForLaunch()
return true
}
@@ -141,6 +142,10 @@ class AppDelegate: NSObject, UIApplicationDelegate {
}
}
private func prepareForLaunch() {
try? FileManager.default.createDirectory(at: getWallpaperDirectory(), withIntermediateDirectories: true)
}
static func keepScreenOn(_ on: Bool) {
UIApplication.shared.isIdleTimerDisabled = on
}
@@ -148,13 +153,15 @@ class AppDelegate: NSObject, UIApplicationDelegate {
class SceneDelegate: NSObject, ObservableObject, UIWindowSceneDelegate {
var window: UIWindow?
static var windowStatic: UIWindow?
var windowScene: UIWindowScene?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
UITableView.appearance().backgroundColor = .clear
guard let windowScene = scene as? UIWindowScene else { return }
self.windowScene = windowScene
window = windowScene.keyWindow
window?.tintColor = UIColor(cgColor: getUIAccentColorDefault())
window?.overrideUserInterfaceStyle = getUserInterfaceStyleDefault()
SceneDelegate.windowStatic = windowScene.keyWindow
ThemeManager.applyTheme(currentThemeDefault.get())
}
}
@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "wallpaper_cats@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "wallpaper_flowers@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "wallpaper_hearts@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "wallpaper_kids@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "wallpaper_school@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "wallpaper_travel@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

+21 -2
View File
@@ -14,6 +14,8 @@ struct ContentView: View {
@ObservedObject var alertManager = AlertManager.shared
@ObservedObject var callController = CallController.shared
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var theme: AppTheme
@EnvironmentObject var sceneDelegate: SceneDelegate
var contentAccessAuthenticationExtended: Bool
@@ -51,6 +53,16 @@ struct ContentView: View {
}
var body: some View {
if #available(iOS 16.0, *) {
allViews()
.scrollContentBackground(.hidden)
} else {
// on iOS 15 scroll view background disabled in SceneDelegate
allViews()
}
}
@ViewBuilder func allViews() -> some View {
ZStack {
let showCallArea = chatModel.activeCall != nil && chatModel.activeCall?.callState != .waitCapabilities && chatModel.activeCall?.callState != .invitationAccepted
// contentView() has to be in a single branch, so that enabling authentication doesn't trigger re-rendering and close settings.
@@ -96,6 +108,8 @@ struct ContentView: View {
initializationView()
}
}
//.tint(theme.colors.primary)
.background(theme.colors.background)
.alert(isPresented: $alertManager.presentAlert) { alertManager.alertView! }
.sheet(isPresented: $showSettings) {
SettingsView(showSettings: $showSettings)
@@ -138,6 +152,11 @@ struct ContentView: View {
break
}
}
.onChange(of: colorScheme) { scheme in
if sceneDelegate.window?.overrideUserInterfaceStyle == .unspecified {
reactOnDarkThemeChanges(scheme == .dark)
}
}
}
@ViewBuilder private func contentView() -> some View {
@@ -224,8 +243,8 @@ struct ContentView: View {
.frame(maxWidth: .infinity, maxHeight: .infinity )
.background(
Rectangle()
.fill(.background)
)
.fill(theme.colors.background)
)
}
private func mainView() -> some View {
+10
View File
@@ -422,6 +422,16 @@ final class ChatModel: ObservableObject {
}
}
func updateCurrentUserUiThemes(uiThemes: ThemeModeOverrides?) {
guard var current = currentUser else { return }
current.uiThemes = uiThemes
let i = users.firstIndex(where: { $0.user.userId == current.userId })
if let i {
users[i].user = current
}
currentUser = current
}
func addLiveDummy(_ chatInfo: ChatInfo) -> ChatItem {
let cItem = ChatItem.liveDummy(chatInfo.chatType)
withAnimation {
+37
View File
@@ -205,6 +205,43 @@ func moveTempFileFromURL(_ url: URL) -> CryptoFile? {
}
}
func saveWallpaperFile(url: URL) -> String? {
let destFile = URL(fileURLWithPath: generateNewFileName(getWallpaperDirectory().path + "/" + "wallpaper", "jpg", fullPath: true))
do {
try FileManager.default.copyItem(atPath: url.path, toPath: destFile.path)
return destFile.lastPathComponent
} catch {
logger.error("FileUtils.saveWallpaperFile error: \(error.localizedDescription)")
return nil
}
}
func saveWallpaperFile(image: UIImage) -> String? {
let hasAlpha = imageHasAlpha(image)
let destFile = URL(fileURLWithPath: generateNewFileName(getWallpaperDirectory().path + "/" + "wallpaper", hasAlpha ? "png" : "jpg", fullPath: true))
let dataResized = resizeImageToDataSize(image, maxDataSize: 5_000_000, hasAlpha: hasAlpha)
do {
try dataResized!.write(to: destFile)
return destFile.lastPathComponent
} catch {
logger.error("FileUtils.saveWallpaperFile error: \(error.localizedDescription)")
return nil
}
}
func removeWallpaperFile(fileName: String? = nil) {
do {
try FileManager.default.contentsOfDirectory(atPath: getWallpaperDirectory().path).forEach {
if URL(fileURLWithPath: $0).lastPathComponent == fileName { try FileManager.default.removeItem(atPath: $0) }
}
} catch {
logger.error("FileUtils.removeWallpaperFile error: \(error.localizedDescription)")
}
if let fileName {
WallpaperType.cachedImages.removeValue(forKey: fileName)
}
}
func generateNewFileName(_ prefix: String, _ ext: String, fullPath: Bool = false) -> String {
uniqueCombine("\(prefix)_\(getTimestamp()).\(ext)", fullPath: fullPath)
}
+59 -16
View File
@@ -242,14 +242,8 @@ func apiSuspendChat(timeoutMicroseconds: Int) {
logger.error("apiSuspendChat error: \(String(describing: r))")
}
func apiSetTempFolder(tempFolder: String, ctrl: chat_ctrl? = nil) throws {
let r = chatSendCmdSync(.setTempFolder(tempFolder: tempFolder), ctrl)
if case .cmdOk = r { return }
throw r
}
func apiSetFilesFolder(filesFolder: String, ctrl: chat_ctrl? = nil) throws {
let r = chatSendCmdSync(.setFilesFolder(filesFolder: filesFolder), ctrl)
func apiSetAppFilePaths(filesFolder: String, tempFolder: String, assetsFolder: String, ctrl: chat_ctrl? = nil) throws {
let r = chatSendCmdSync(.apiSetAppFilePaths(filesFolder: filesFolder, tempFolder: tempFolder, assetsFolder: assetsFolder), ctrl)
if case .cmdOk = r { return }
throw r
}
@@ -561,6 +555,18 @@ func apiGroupMemberInfo(_ groupId: Int64, _ groupMemberId: Int64) throws -> (Gro
throw r
}
func apiContactQueueInfo(_ contactId: Int64) async throws -> (RcvMsgInfo?, QueueInfo) {
let r = await chatSendCmd(.apiContactQueueInfo(contactId: contactId))
if case let .queueInfo(_, rcvMsgInfo, queueInfo) = r { return (rcvMsgInfo, queueInfo) }
throw r
}
func apiGroupMemberQueueInfo(_ groupId: Int64, _ groupMemberId: Int64) async throws -> (RcvMsgInfo?, QueueInfo) {
let r = await chatSendCmd(.apiGroupMemberQueueInfo(groupId: groupId, groupMemberId: groupMemberId))
if case let .queueInfo(_, rcvMsgInfo, queueInfo) = r { return (rcvMsgInfo, queueInfo) }
throw r
}
func apiSwitchContact(contactId: Int64) throws -> ConnectionStats {
let r = chatSendCmdSync(.apiSwitchContact(contactId: contactId))
if case let .contactSwitchStarted(_, _, connectionStats) = r { return connectionStats }
@@ -819,6 +825,21 @@ func apiSetConnectionAlias(connId: Int64, localAlias: String) async throws -> Pe
throw r
}
func apiSetUserUIThemes(userId: Int64, themes: ThemeModeOverrides?) async -> Bool {
let r = await chatSendCmd(.apiSetUserUIThemes(userId: userId, themes: themes))
if case .cmdOk = r { return true }
logger.error("apiSetUserUIThemes bad response: \(String(describing: r))")
return false
}
func apiSetChatUIThemes(chatId: ChatId, themes: ThemeModeOverrides?) async -> Bool {
let r = await chatSendCmd(.apiSetChatUIThemes(chatId: chatId, themes: themes))
if case .cmdOk = r { return true }
logger.error("apiSetChatUIThemes bad response: \(String(describing: r))")
return false
}
func apiCreateUserAddress() async throws -> String {
let userId = try currentUserId("apiCreateUserAddress")
let r = await chatSendCmd(.apiCreateMyAddress(userId: userId))
@@ -1265,7 +1286,7 @@ func filterMembersToAdd(_ ms: [GMember]) -> [Contact] {
let memberContactIds = ms.compactMap{ m in m.wrapped.memberCurrent ? m.wrapped.memberContactId : nil }
return ChatModel.shared.chats
.compactMap{ $0.chatInfo.contact }
.filter{ c in c.ready && c.active && !memberContactIds.contains(c.apiId) }
.filter{ c in c.sendMsgEnabled && !c.nextSendGrpInv && !memberContactIds.contains(c.apiId) }
.sorted{ $0.displayName.lowercased() < $1.displayName.lowercased() }
}
@@ -1341,8 +1362,7 @@ func initializeChat(start: Bool, confirmStart: Bool = false, dbKey: String? = ni
if encryptionStartedDefault.get() {
encryptionStartedDefault.set(false)
}
try apiSetTempFolder(tempFolder: getTempFilesDirectory().path)
try apiSetFilesFolder(filesFolder: getAppFilesDirectory().path)
try apiSetAppFilePaths(filesFolder: getAppFilesDirectory().path, tempFolder: getTempFilesDirectory().path, assetsFolder: getWallpaperDirectory().deletingLastPathComponent().path)
try apiSetEncryptLocalFiles(privacyEncryptLocalFilesGroupDefault.get())
m.chatInitialized = true
m.currentUser = try apiGetActiveUser()
@@ -1427,8 +1447,7 @@ func startChatWithTemporaryDatabase(ctrl: chat_ctrl) throws -> User? {
logger.debug("startChatWithTemporaryDatabase")
let migrationActiveUser = try? apiGetActiveUser(ctrl: ctrl) ?? apiCreateActiveUser(Profile(displayName: "Temp", fullName: ""), ctrl: ctrl)
try setNetworkConfig(getNetCfg(), ctrl: ctrl)
try apiSetTempFolder(tempFolder: getMigrationTempFilesDirectory().path, ctrl: ctrl)
try apiSetFilesFolder(filesFolder: getMigrationTempFilesDirectory().path, ctrl: ctrl)
try apiSetAppFilePaths(filesFolder: getMigrationTempFilesDirectory().path, tempFolder: getMigrationTempFilesDirectory().path, assetsFolder: getWallpaperDirectory().deletingLastPathComponent().path, ctrl: ctrl)
_ = try apiStartChat(ctrl: ctrl)
return migrationActiveUser
}
@@ -1814,11 +1833,15 @@ func processReceivedMsg(_ res: ChatResponse) async {
if let aChatItem = aChatItem {
await chatItemSimpleUpdate(user, aChatItem)
}
case let .rcvFileError(user, aChatItem, _):
case let .rcvFileError(user, aChatItem, _, _):
if let aChatItem = aChatItem {
await chatItemSimpleUpdate(user, aChatItem)
Task { cleanupFile(aChatItem) }
}
case let .rcvFileWarning(user, aChatItem, _, _):
if let aChatItem = aChatItem {
await chatItemSimpleUpdate(user, aChatItem)
}
case let .sndFileStart(user, aChatItem, _):
await chatItemSimpleUpdate(user, aChatItem)
case let .sndFileComplete(user, aChatItem, _):
@@ -1835,11 +1858,15 @@ func processReceivedMsg(_ res: ChatResponse) async {
}
case let .sndFileCompleteXFTP(user, aChatItem, _):
await chatItemSimpleUpdate(user, aChatItem)
case let .sndFileError(user, aChatItem, _):
case let .sndFileError(user, aChatItem, _, _):
if let aChatItem = aChatItem {
await chatItemSimpleUpdate(user, aChatItem)
Task { cleanupFile(aChatItem) }
}
case let .sndFileWarning(user, aChatItem, _, _):
if let aChatItem = aChatItem {
await chatItemSimpleUpdate(user, aChatItem)
}
case let .callInvitation(invitation):
await MainActor.run {
m.callInvitations[invitation.contact.id] = invitation
@@ -1937,12 +1964,28 @@ func processReceivedMsg(_ res: ChatResponse) async {
let state = UIRemoteCtrlSessionState.connected(remoteCtrl: remoteCtrl, sessionCode: m.remoteCtrlSession?.sessionCode ?? "")
m.remoteCtrlSession = m.remoteCtrlSession?.updateState(state)
}
case .remoteCtrlStopped:
case let .remoteCtrlStopped(_, rcStopReason):
// This delay is needed to cancel the session that fails on network failure,
// e.g. when user did not grant permission to access local network yet.
if let sess = m.remoteCtrlSession {
await MainActor.run {
m.remoteCtrlSession = nil
dismissAllSheets() {
switch rcStopReason {
case .connectionFailed(.errorAgent(.RCP(.identity))):
AlertManager.shared.showAlertMsg(
title: "Connection with desktop stopped",
message: "This link was used with another mobile device, please create a new link on the desktop."
)
default:
AlertManager.shared.showAlert(Alert(
title: Text("Connection with desktop stopped"),
message: Text("Please check that mobile and desktop are connected to the same local network, and that desktop firewall allows the connection.\nPlease share any other issues with the developers."),
primaryButton: .default(Text("Ok")),
secondaryButton: .default(Text("Copy error")) { UIPasteboard.general.string = String(describing: rcStopReason) }
))
}
}
}
if case .connected = sess.sessionState {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
+1
View File
@@ -39,6 +39,7 @@ struct SimpleXApp: App {
// so that it's computed by the time view renders, and not on event after rendering
ContentView(contentAccessAuthenticationExtended: !authenticationExpired())
.environmentObject(chatModel)
.environmentObject(AppTheme.shared)
.onOpenURL { url in
logger.debug("ContentView.onOpenURL: \(url)")
chatModel.appOpenUrl = url
+121
View File
@@ -0,0 +1,121 @@
//
// ThemeShared.swift
// SimpleX (iOS)
//
// Created by Avently on 14.06.2024.
// Copyright © 2024 SimpleX Chat. All rights reserved.
//
import Foundation
import SwiftUI
import SimpleXChat
var CurrentColors: ThemeManager.ActiveTheme = ThemeManager.currentColors(nil, nil, ChatModel.shared.currentUser?.uiThemes, themeOverridesDefault.get()) {
didSet {
AppTheme.shared.name = CurrentColors.name
AppTheme.shared.base = CurrentColors.base
AppTheme.shared.colors.updateColorsFrom(CurrentColors.colors)
AppTheme.shared.appColors.updateColorsFrom(CurrentColors.appColors)
AppTheme.shared.wallpaper.updateWallpaperFrom(CurrentColors.wallpaper)
AppTheme.shared.objectWillChange.send()
}
}
var MenuTextColor: Color { if isInDarkTheme() { AppTheme.shared.colors.onBackground.opacity(0.8) } else { Color.black } }
var NoteFolderIconColor: Color { AppTheme.shared.appColors.primaryVariant2 }
func isInDarkTheme() -> Bool { !CurrentColors.colors.isLight }
class AppTheme: ObservableObject {
static let shared = AppTheme(name: CurrentColors.name, base: CurrentColors.base, colors: CurrentColors.colors, appColors: CurrentColors.appColors, wallpaper: CurrentColors.wallpaper)
var name: String
var base: DefaultTheme
@ObservedObject var colors: Colors
@ObservedObject var appColors: AppColors
@ObservedObject var wallpaper: AppWallpaper
init(name: String, base: DefaultTheme, colors: Colors, appColors: AppColors, wallpaper: AppWallpaper) {
self.name = name
self.base = base
self.colors = colors
self.appColors = appColors
self.wallpaper = wallpaper
}
}
struct ThemedBackground: ViewModifier {
@EnvironmentObject var theme: AppTheme
func body(content: Content) -> some View {
content
.background(
theme.base == DefaultTheme.SIMPLEX
? LinearGradient(
colors: [
theme.colors.background.lighter(0.4),
theme.colors.background.darker(0.4)
],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
: LinearGradient(
colors: [],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
.background(
theme.base == DefaultTheme.SIMPLEX
? Color.clear
: theme.colors.background
)
}
}
func reactOnDarkThemeChanges(_ isDark: Bool) {
systemInDarkThemeCurrently = isDark
//sceneDelegate.window?.overrideUserInterfaceStyle == .unspecified
if currentThemeDefault.get() == DefaultTheme.SYSTEM_THEME_NAME && CurrentColors.colors.isLight == isDark {
// Change active colors from light to dark and back based on system theme
ThemeManager.applyTheme(DefaultTheme.SYSTEM_THEME_NAME)
}
}
extension ThemeWallpaper {
public func importFromString() -> ThemeWallpaper {
if preset == nil, let image {
// Need to save image from string and to save its path
if let data = Data(base64Encoded: dropImagePrefix(image)),
let parsed = UIImage(data: data),
let filename = saveWallpaperFile(image: parsed) {
var copy = self
copy.image = nil
copy.imageFile = filename
return copy
} else {
return ThemeWallpaper()
}
} else {
return self
}
}
func withFilledWallpaperBase64() -> ThemeWallpaper {
let aw = toAppWallpaper()
let type = aw.type
let preset: String? = if case let WallpaperType.Preset(filename, _) = type { filename } else { nil }
let scale: Float? = if case let WallpaperType.Preset(_, scale) = type { scale } else { if case let WallpaperType.Image(_, scale, _) = type { scale } else { 1.0 } }
let scaleType: WallpaperScaleType? = if case let WallpaperType.Image(_, _, scaleType) = type { scaleType } else { nil }
let image: String? = if case WallpaperType.Image = type, let image = type.uiImage { resizeImageToStrSize(image, maxDataSize: 5_000_000) } else { nil }
return ThemeWallpaper (
preset: preset,
scale: scale,
scaleType: scaleType,
background: aw.background?.toReadableHex(),
tint: aw.tint?.toReadableHex(),
image: image,
imageFile: nil
)
}
}
+263
View File
@@ -0,0 +1,263 @@
//
// ThemeManager.swift
// SimpleX (iOS)
//
// Created by Avently on 03.06.2024.
// Copyright © 2024 SimpleX Chat. All rights reserved.
//
import Foundation
import SwiftUI
import SimpleXChat
class ThemeManager {
struct ActiveTheme {
let name: String
let base: DefaultTheme
let colors: Colors
let appColors: AppColors
var wallpaper: AppWallpaper = AppWallpaper(background: nil, tint: nil, type: .Empty)
}
private static func systemDarkThemeColors() -> (Colors, DefaultTheme) {
switch systemDarkThemeDefault.get() {
case DefaultTheme.DARK.themeName: (DarkColorPalette, DefaultTheme.DARK)
case DefaultTheme.SIMPLEX.themeName: (SimplexColorPalette, DefaultTheme.SIMPLEX)
case DefaultTheme.BLACK.themeName: (BlackColorPalette, DefaultTheme.BLACK)
default: (SimplexColorPalette, DefaultTheme.SIMPLEX)
}
}
private static func nonSystemThemeName() -> String {
let themeName = currentThemeDefault.get()
return if themeName != DefaultTheme.SYSTEM_THEME_NAME {
themeName
} else {
systemInDarkThemeCurrently ? systemDarkThemeDefault.get() : DefaultTheme.LIGHT.themeName
}
}
static func defaultActiveTheme(_ appSettingsTheme: [ThemeOverrides]) -> ThemeOverrides? {
let nonSystemThemeName = nonSystemThemeName()
let defaultThemeId = currentThemeIdsDefault.get()[nonSystemThemeName]
return appSettingsTheme.getTheme(defaultThemeId)
}
static func defaultActiveTheme(_ perUserTheme: ThemeModeOverrides?, _ appSettingsTheme: [ThemeOverrides]) -> ThemeModeOverride {
let perUserTheme = !CurrentColors.colors.isLight ? perUserTheme?.dark : perUserTheme?.light
if let perUserTheme {
return perUserTheme
}
let defaultTheme = defaultActiveTheme(appSettingsTheme)
return ThemeModeOverride(mode: CurrentColors.base.mode, colors: defaultTheme?.colors ?? ThemeColors(), wallpaper: defaultTheme?.wallpaper)
}
static func currentColors(_ themeOverridesForType: WallpaperType?, _ perChatTheme: ThemeModeOverride?, _ perUserTheme: ThemeModeOverrides?, _ appSettingsTheme: [ThemeOverrides]) -> ActiveTheme {
let themeName = currentThemeDefault.get()
let nonSystemThemeName = nonSystemThemeName()
let defaultTheme = defaultActiveTheme(appSettingsTheme)
let baseTheme = switch nonSystemThemeName {
case DefaultTheme.LIGHT.themeName: ActiveTheme(name: DefaultTheme.LIGHT.themeName, base: DefaultTheme.LIGHT, colors: LightColorPalette.clone(), appColors: LightColorPaletteApp.clone(), wallpaper: AppWallpaper(background: nil, tint: nil, type: PresetWallpaper.school.toType(DefaultTheme.LIGHT)))
case DefaultTheme.DARK.themeName: ActiveTheme(name: DefaultTheme.DARK.themeName, base: DefaultTheme.DARK, colors: DarkColorPalette.clone(), appColors: DarkColorPaletteApp.clone(), wallpaper: AppWallpaper(background: nil, tint: nil, type: PresetWallpaper.school.toType(DefaultTheme.DARK)))
case DefaultTheme.SIMPLEX.themeName: ActiveTheme(name: DefaultTheme.SIMPLEX.themeName, base: DefaultTheme.SIMPLEX, colors: SimplexColorPalette.clone(), appColors: SimplexColorPaletteApp.clone(), wallpaper: AppWallpaper(background: nil, tint: nil, type: PresetWallpaper.school.toType(DefaultTheme.SIMPLEX)))
case DefaultTheme.BLACK.themeName: ActiveTheme(name: DefaultTheme.BLACK.themeName, base: DefaultTheme.BLACK, colors: BlackColorPalette.clone(), appColors: BlackColorPaletteApp.clone(), wallpaper: AppWallpaper(background: nil, tint: nil, type: PresetWallpaper.school.toType(DefaultTheme.BLACK)))
default: ActiveTheme(name: DefaultTheme.LIGHT.themeName, base: DefaultTheme.LIGHT, colors: LightColorPalette.clone(), appColors: LightColorPaletteApp.clone(), wallpaper: AppWallpaper(background: nil, tint: nil, type: PresetWallpaper.school.toType(DefaultTheme.LIGHT)))
}
let perUserTheme = baseTheme.colors.isLight ? perUserTheme?.light : perUserTheme?.dark
let theme = appSettingsTheme.sameTheme(themeOverridesForType ?? perChatTheme?.type ?? perUserTheme?.type ?? defaultTheme?.wallpaper?.toAppWallpaper().type, nonSystemThemeName) ?? defaultTheme
if theme == nil && perUserTheme == nil && perChatTheme == nil && themeOverridesForType == nil {
return ActiveTheme(name: themeName, base: baseTheme.base, colors: baseTheme.colors, appColors: baseTheme.appColors, wallpaper: baseTheme.wallpaper)
}
let presetWallpaperTheme: ThemeColors? = if let wallpaper = perChatTheme?.wallpaper {
if let preset = wallpaper.preset { PresetWallpaper.from(preset)?.colors[baseTheme.base] } else { nil }
} else if let wallpaper = perUserTheme?.wallpaper {
if let preset = wallpaper.preset { PresetWallpaper.from(preset)?.colors[baseTheme.base] } else { nil }
} else {
if let preset = theme?.wallpaper?.preset { PresetWallpaper.from(preset)?.colors[baseTheme.base] } else { nil }
}
let themeOrEmpty = theme ?? ThemeOverrides(base: baseTheme.base)
let colors = themeOrEmpty.toColors(themeOrEmpty.base, perChatTheme?.colors, perUserTheme?.colors, presetWallpaperTheme)
return ActiveTheme(
name: themeName,
base: baseTheme.base,
colors: colors,
appColors: themeOrEmpty.toAppColors(themeOrEmpty.base, perChatTheme?.colors, perChatTheme?.type, perUserTheme?.colors, perUserTheme?.type, presetWallpaperTheme),
wallpaper: themeOrEmpty.toAppWallpaper(themeOverridesForType, perChatTheme, perUserTheme, colors.background)
)
}
static func currentThemeOverridesForExport(_ perChatTheme: ThemeModeOverride?, _ perUserTheme: ThemeModeOverrides?) -> ThemeOverrides {
let current = currentColors(nil, perChatTheme, perUserTheme, themeOverridesDefault.get())
let wType = current.wallpaper.type
let wBackground = current.wallpaper.background
let wTint = current.wallpaper.tint
let w: ThemeWallpaper? = if case WallpaperType.Empty = wType {
nil
} else {
ThemeWallpaper.from(wType, wBackground?.toReadableHex(), wTint?.toReadableHex()).withFilledWallpaperBase64()
}
return ThemeOverrides(
themeId: "",
base: current.base,
colors: ThemeColors.from(current.colors, current.appColors),
wallpaper: w
)
}
static func applyTheme(_ theme: String) {
currentThemeDefault.set(theme)
CurrentColors = currentColors(nil, nil, ChatModel.shared.currentUser?.uiThemes, themeOverridesDefault.get())
SceneDelegate.windowStatic?.tintColor = UIColor(CurrentColors.colors.primary)
SceneDelegate.windowStatic?.backgroundColor = UIColor(CurrentColors.colors.background)
SceneDelegate.windowStatic?.overrideUserInterfaceStyle = switch currentThemeDefault.get() {
case DefaultTheme.LIGHT.themeName: .light
case DefaultTheme.SYSTEM_THEME_NAME: .unspecified
default: .dark
}
}
static func changeDarkTheme(_ theme: String) {
systemDarkThemeDefault.set(theme)
CurrentColors = currentColors(nil, nil, ChatModel.shared.currentUser?.uiThemes, themeOverridesDefault.get())
}
static func saveAndApplyThemeColor(_ baseTheme: DefaultTheme, _ name: ThemeColor, _ color: Color? = nil, _ pref: CodableDefault<[ThemeOverrides]>? = nil) {
let nonSystemThemeName = baseTheme.themeName
let pref = pref ?? themeOverridesDefault
let overrides = pref.get()
let themeId = currentThemeIdsDefault.get()[nonSystemThemeName]
let prevValue = overrides.getTheme(themeId) ?? ThemeOverrides(base: baseTheme)
pref.set(overrides.replace(prevValue.withUpdatedColor(name, color?.toReadableHex())))
var themeIds = currentThemeIdsDefault.get()
themeIds[nonSystemThemeName] = prevValue.themeId
currentThemeIdsDefault.set(themeIds)
CurrentColors = currentColors(nil, nil, ChatModel.shared.currentUser?.uiThemes, themeOverridesDefault.get())
}
static func applyThemeColor(name: ThemeColor, color: Color? = nil, pref: Binding<ThemeModeOverride>) {
pref.wrappedValue = pref.wrappedValue.withUpdatedColor(name, color?.toReadableHex())
}
static func saveAndApplyWallpaper(_ baseTheme: DefaultTheme, _ type: WallpaperType?, _ pref: CodableDefault<[ThemeOverrides]>?) {
let nonSystemThemeName = baseTheme.themeName
let pref = pref ?? themeOverridesDefault
let overrides = pref.get()
let theme = overrides.sameTheme(type, baseTheme.themeName)
var prevValue = theme ?? ThemeOverrides(base: baseTheme)
prevValue.wallpaper = if let type {
if case WallpaperType.Empty = type {
nil as ThemeWallpaper?
} else {
ThemeWallpaper.from(type, prevValue.wallpaper?.background, prevValue.wallpaper?.tint)
}
} else {
nil
}
pref.set(overrides.replace(prevValue))
var themeIds = currentThemeIdsDefault.get()
themeIds[nonSystemThemeName] = prevValue.themeId
currentThemeIdsDefault.set(themeIds)
CurrentColors = currentColors( nil, nil, ChatModel.shared.currentUser?.uiThemes, themeOverridesDefault.get())
}
static func copyFromSameThemeOverrides(_ type: WallpaperType?, _ lowerLevelOverride: ThemeModeOverride?, _ pref: Binding<ThemeModeOverride>) -> Bool {
let overrides = themeOverridesDefault.get()
let sameWallpaper: ThemeWallpaper? = if let wallpaper = lowerLevelOverride?.wallpaper, lowerLevelOverride?.type?.sameType(type) == true {
wallpaper
} else {
overrides.sameTheme(type, CurrentColors.base.themeName)?.wallpaper
}
guard let sameWallpaper else {
if let type {
var w: ThemeWallpaper = ThemeWallpaper.from(type, nil, nil)
w.scale = nil
w.scaleType = nil
pref.wrappedValue = ThemeModeOverride(mode: CurrentColors.base.mode, wallpaper: w)
} else {
// Make an empty wallpaper to override any top level ones
pref.wrappedValue = ThemeModeOverride(mode: CurrentColors.base.mode, wallpaper: ThemeWallpaper())
}
return true
}
var type = sameWallpaper.toAppWallpaper().type
if case let WallpaperType.Image(filename, scale, scaleType) = type, sameWallpaper.imageFile == filename {
// 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
if let filename = saveWallpaperFile(url: getWallpaperFilePath(filename)) {
type = WallpaperType.Image(filename, scale, scaleType)
} else {
logger.error("Error while copying wallpaper from global overrides to chat overrides")
return false
}
}
var prevValue = pref.wrappedValue
var w = ThemeWallpaper.from(type, nil, nil)
w.scale = nil
w.scaleType = nil
prevValue.colors = ThemeColors()
prevValue.wallpaper = w
pref.wrappedValue = prevValue
return true
}
static func applyWallpaper(_ type: WallpaperType?, _ pref: Binding<ThemeModeOverride>) {
var prevValue = pref.wrappedValue
prevValue.wallpaper = if let type {
ThemeWallpaper.from(type, prevValue.wallpaper?.background, prevValue.wallpaper?.tint)
} else {
nil
}
pref.wrappedValue = prevValue
}
static func saveAndApplyThemeOverrides(_ theme: ThemeOverrides, _ pref: CodableDefault<[ThemeOverrides]>? = nil) {
let wallpaper = theme.wallpaper?.importFromString()
let nonSystemThemeName = theme.base.themeName
let pref: CodableDefault<[ThemeOverrides]> = pref ?? themeOverridesDefault
let overrides = pref.get()
var prevValue = overrides.getTheme(nil, wallpaper?.toAppWallpaper().type, theme.base) ?? ThemeOverrides(base: theme.base)
if let imageFile = prevValue.wallpaper?.imageFile {
try? FileManager.default.removeItem(at: getWallpaperFilePath(imageFile))
}
prevValue.base = theme.base
prevValue.colors = theme.colors
prevValue.wallpaper = wallpaper
pref.set(overrides.replace(prevValue))
currentThemeDefault.set(nonSystemThemeName)
var currentThemeIds = currentThemeIdsDefault.get()
currentThemeIds[nonSystemThemeName] = prevValue.themeId
currentThemeIdsDefault.set(currentThemeIds)
CurrentColors = currentColors(nil, nil, ChatModel.shared.currentUser?.uiThemes, themeOverridesDefault.get())
}
static func resetAllThemeColors(_ pref: CodableDefault<[ThemeOverrides]>? = nil) {
let nonSystemThemeName = nonSystemThemeName()
let pref: CodableDefault<[ThemeOverrides]> = pref ?? themeOverridesDefault
let overrides = pref.get()
guard let themeId = currentThemeIdsDefault.get()[nonSystemThemeName],
var prevValue = overrides.getTheme(themeId)
else { return }
prevValue.colors = ThemeColors()
prevValue.wallpaper?.background = nil
prevValue.wallpaper?.tint = nil
pref.set(overrides.replace(prevValue))
CurrentColors = currentColors(nil, nil, ChatModel.shared.currentUser?.uiThemes, themeOverridesDefault.get())
}
static func resetAllThemeColors(_ pref: Binding<ThemeModeOverride>) {
var prevValue = pref.wrappedValue
prevValue.colors = ThemeColors()
prevValue.wallpaper?.background = nil
prevValue.wallpaper?.tint = nil
pref.wrappedValue = prevValue
}
static func removeTheme(_ themeId: String?) {
var themes = themeOverridesDefault.get().map { $0 }
themes.removeAll(where: { $0.themeId == themeId })
themeOverridesDefault.set(themes)
}
}
@@ -2,7 +2,7 @@
// MPVolumeView.swift
// SimpleX (iOS)
//
// Created by Stanislav on 24.04.2024.
// Created by Avently on 24.04.2024.
// Copyright © 2024 SimpleX Chat. All rights reserved.
//
@@ -11,6 +11,7 @@ import SimpleXChat
struct IncomingCallView: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
@ObservedObject var cc = CallController.shared
var body: some View {
@@ -43,7 +44,7 @@ struct IncomingCallView: View {
cc.endCall(invitation: invitation)
}
callButton("Ignore", "multiply", .accentColor) {
callButton("Ignore", "multiply", theme.colors.primary) {
cc.activeCallInvitation = nil
}
@@ -63,7 +64,7 @@ struct IncomingCallView: View {
.padding(.horizontal, 16)
.padding(.vertical, 12)
.frame(maxWidth: .infinity)
.background(Color(uiColor: .tertiarySystemGroupedBackground))
.modifier(ThemedBackground())
.onAppear { dismissAllSheets() }
}
+36 -1
View File
@@ -90,6 +90,7 @@ enum SendReceipts: Identifiable, Hashable {
struct ChatInfoView: View {
@EnvironmentObject var chatModel: ChatModel
@EnvironmentObject var theme: AppTheme
@Environment(\.dismiss) var dismiss: DismissAction
@ObservedObject var chat: Chat
@State var contact: Contact
@@ -110,6 +111,7 @@ struct ChatInfoView: View {
case switchAddressAlert
case abortSwitchAddressAlert
case syncConnectionForceAlert
case queueInfo(info: String)
case error(title: LocalizedStringKey, error: LocalizedStringKey = "")
var id: String {
@@ -119,6 +121,7 @@ struct ChatInfoView: View {
case .switchAddressAlert: return "switchAddressAlert"
case .abortSwitchAddressAlert: return "abortSwitchAddressAlert"
case .syncConnectionForceAlert: return "syncConnectionForceAlert"
case let .queueInfo(info): return "queueInfo \(info)"
case let .error(title, _): return "error \(title)"
}
}
@@ -224,9 +227,22 @@ struct ChatInfoView: View {
Section(header: Text("For console")) {
infoRow("Local name", chat.chatInfo.localDisplayName)
infoRow("Database ID", "\(chat.chatInfo.apiId)")
Button ("Debug delivery") {
Task {
do {
let info = queueInfoText(try await apiContactQueueInfo(chat.chatInfo.apiId))
await MainActor.run { alert = .queueInfo(info: info) }
} catch let e {
logger.error("apiContactQueueInfo error: \(responseError(e))")
let a = getErrorAlert(e, "Error")
await MainActor.run { alert = .error(title: a.title, error: a.message) }
}
}
}
}
}
}
.modifier(ThemedBackground())
.navigationBarHidden(true)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
@@ -243,6 +259,7 @@ struct ChatInfoView: View {
case .switchAddressAlert: return switchAddressAlert(switchContactAddress)
case .abortSwitchAddressAlert: return abortSwitchAddressAlert(abortSwitchContactAddress)
case .syncConnectionForceAlert: return syncConnectionForceAlert({ syncContactConnection(force: true) })
case let .queueInfo(info): return queueInfoAlert(info)
case let .error(title, error): return mkAlert(title: title, message: error)
}
}
@@ -355,6 +372,7 @@ struct ChatInfoView: View {
)
.navigationBarTitleDisplayMode(.inline)
.navigationTitle("Security code")
.modifier(ThemedBackground())
} label: {
Label(
contact.verified ? "View security code" : "Verify security code",
@@ -371,6 +389,7 @@ struct ChatInfoView: View {
currentFeaturesAllowed: contactUserPrefsToFeaturesAllowed(contact.mergedPreferences)
)
.navigationBarTitle("Contact preferences")
.modifier(ThemedBackground())
.navigationBarTitleDisplayMode(.large)
} label: {
Label("Contact preferences", systemImage: "switch.2")
@@ -419,7 +438,7 @@ struct ChatInfoView: View {
HStack {
Text("Network status")
Image(systemName: "info.circle")
.foregroundColor(.accentColor)
.foregroundColor(theme.colors.primary)
.font(.system(size: 14))
Spacer()
Text(chatModel.contactNetworkStatus(contact).statusString)
@@ -577,6 +596,22 @@ func syncConnectionForceAlert(_ syncConnectionForce: @escaping () -> Void) -> Al
)
}
func queueInfoText(_ info: (RcvMsgInfo?, QueueInfo)) -> String {
let (rcvMsgInfo, qInfo) = info
var msgInfo: String
if let rcvMsgInfo { msgInfo = encodeJSON(rcvMsgInfo) } else { msgInfo = "none" }
return String.localizedStringWithFormat(NSLocalizedString("server queue info: %@\n\nlast received msg: %@", comment: "queue info"), encodeJSON(qInfo), msgInfo)
}
func queueInfoAlert(_ info: String) -> Alert {
Alert(
title: Text("Message queue info"),
message: Text(info),
primaryButton: .default(Text("Ok")),
secondaryButton: .default(Text("Copy")) { UIPasteboard.general.string = info }
)
}
struct ChatInfoView_Previews: PreviewProvider {
static var previews: some View {
ChatInfoView(
@@ -11,6 +11,7 @@ import SimpleXChat
struct CICallItemView: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
@ObservedObject var chat: Chat
var chatItem: ChatItem
var status: CICallStatus
@@ -35,7 +36,7 @@ struct CICallItemView: View {
case .error: missedCallIcon(sent).foregroundColor(.orange)
}
CIMetaView(chat: chat, chatItem: chatItem, showStatus: false, showEdited: false)
CIMetaView(chat: chat, chatItem: chatItem, metaColor: theme.colors.secondary, showStatus: false, showEdited: false)
.padding(.bottom, 8)
.padding(.horizontal, 12)
}
@@ -11,6 +11,7 @@ import SimpleXChat
struct CIFeaturePreferenceView: View {
@ObservedObject var chat: Chat
@EnvironmentObject var theme: AppTheme
var chatItem: ChatItem
var feature: ChatFeature
var allowed: FeatureAllowed
@@ -45,7 +46,7 @@ struct CIFeaturePreferenceView: View {
r = r
+ Text(acceptText)
.fontWeight(.medium)
.foregroundColor(.accentColor)
.foregroundColor(theme.colors.primary)
+ Text(" ")
}
r = r + chatItem.timestampText
@@ -12,6 +12,7 @@ import SimpleXChat
struct CIFileView: View {
@EnvironmentObject var m: ChatModel
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var theme: AppTheme
let file: CIFile?
let edited: Bool
@@ -54,16 +55,18 @@ struct CIFileView: View {
switch (file.fileStatus) {
case .sndStored: return file.fileProtocol == .local
case .sndTransfer: return false
case .sndComplete: return false
case .sndComplete: return true
case .sndCancelled: return false
case .sndError: return false
case .sndError: return true
case .sndWarning: return true
case .rcvInvitation: return true
case .rcvAccepted: return true
case .rcvTransfer: return false
case .rcvAborted: return true
case .rcvComplete: return true
case .rcvCancelled: return false
case .rcvError: return false
case .rcvError: return true
case .rcvWarning: return true
case .invalid: return false
}
}
@@ -108,11 +111,40 @@ struct CIFileView: View {
if let fileSource = getLoadedFileSource(file) {
saveCryptoFile(fileSource)
}
case let .rcvError(rcvFileError):
logger.debug("CIFileView fileAction - in .rcvError")
AlertManager.shared.showAlert(Alert(
title: Text("File error"),
message: Text(rcvFileError.errorInfo)
))
case let .rcvWarning(rcvFileError):
logger.debug("CIFileView fileAction - in .rcvWarning")
AlertManager.shared.showAlert(Alert(
title: Text("Temporary file error"),
message: Text(rcvFileError.errorInfo)
))
case .sndStored:
logger.debug("CIFileView fileAction - in .sndStored")
if file.fileProtocol == .local, let fileSource = getLoadedFileSource(file) {
saveCryptoFile(fileSource)
}
case .sndComplete:
logger.debug("CIFileView fileAction - in .sndComplete")
if let fileSource = getLoadedFileSource(file) {
saveCryptoFile(fileSource)
}
case let .sndError(sndFileError):
logger.debug("CIFileView fileAction - in .sndError")
AlertManager.shared.showAlert(Alert(
title: Text("File error"),
message: Text(sndFileError.errorInfo)
))
case let .sndWarning(sndFileError):
logger.debug("CIFileView fileAction - in .sndWarning")
AlertManager.shared.showAlert(Alert(
title: Text("Temporary file error"),
message: Text(sndFileError.errorInfo)
))
default: break
}
}
@@ -136,9 +168,10 @@ struct CIFileView: View {
case .sndComplete: fileIcon("doc.fill", innerIcon: "checkmark", innerIconSize: 10)
case .sndCancelled: fileIcon("doc.fill", innerIcon: "xmark", innerIconSize: 10)
case .sndError: fileIcon("doc.fill", innerIcon: "xmark", innerIconSize: 10)
case .sndWarning: fileIcon("doc.fill", innerIcon: "exclamationmark.triangle.fill", innerIconSize: 10)
case .rcvInvitation:
if fileSizeValid(file) {
fileIcon("arrow.down.doc.fill", color: .accentColor)
fileIcon("arrow.down.doc.fill", color: theme.colors.primary)
} else {
fileIcon("doc.fill", color: .orange, innerIcon: "exclamationmark", innerIconSize: 12)
}
@@ -150,10 +183,11 @@ struct CIFileView: View {
progressView()
}
case .rcvAborted:
fileIcon("doc.fill", color: .accentColor, innerIcon: "exclamationmark.arrow.circlepath", innerIconSize: 12)
fileIcon("doc.fill", color: theme.colors.primary, innerIcon: "exclamationmark.arrow.circlepath", innerIconSize: 12)
case .rcvComplete: fileIcon("doc.fill")
case .rcvCancelled: fileIcon("doc.fill", innerIcon: "xmark", innerIconSize: 10)
case .rcvError: fileIcon("doc.fill", innerIcon: "xmark", innerIconSize: 10)
case .rcvWarning: fileIcon("doc.fill", innerIcon: "exclamationmark.triangle.fill", innerIconSize: 10)
case .invalid: fileIcon("doc.fill", innerIcon: "questionmark", innerIconSize: 10)
}
} else {
@@ -11,7 +11,7 @@ import SimpleXChat
struct CIGroupInvitationView: View {
@EnvironmentObject var chatModel: ChatModel
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var theme: AppTheme
@ObservedObject var chat: Chat
var chatItem: ChatItem
var groupInvitation: CIGroupInvitation
@@ -42,7 +42,7 @@ struct CIGroupInvitationView: View {
.overlay(DetermineWidth())
(
Text(chatIncognito ? "Tap to join incognito" : "Tap to join")
.foregroundColor(inProgress ? .secondary : chatIncognito ? .indigo : .accentColor)
.foregroundColor(inProgress ? .secondary : chatIncognito ? .indigo : theme.colors.primary)
.font(.callout)
+ Text(" ")
+ ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showStatus: false, showEdited: false, showViaProxy: showSentViaProxy)
@@ -65,11 +65,11 @@ struct CIGroupInvitationView: View {
}
}
CIMetaView(chat: chat, chatItem: chatItem, showStatus: false, showEdited: false)
CIMetaView(chat: chat, chatItem: chatItem, metaColor: theme.colors.secondary, showStatus: false, showEdited: false)
}
.padding(.horizontal, 12)
.padding(.vertical, 6)
.background(chatItemFrameColor(chatItem, colorScheme))
.background(chatItemFrameColor(chatItem, theme))
.cornerRadius(18)
.textSelection(.disabled)
.onPreferenceChange(DetermineWidth.Key.self) { frameWidth = $0 }
@@ -99,7 +99,7 @@ struct CIGroupInvitationView: View {
private func groupInfoView(_ action: Bool) -> some View {
var color: Color
if action && !inProgress {
color = chatIncognito ? .indigo : .accentColor
color = chatIncognito ? .indigo : theme.colors.primary
} else {
color = Color(uiColor: .tertiaryLabel)
}
@@ -60,6 +60,26 @@ struct CIImageView: View {
case .rcvTransfer: () // ?
case .rcvComplete: () // ?
case .rcvCancelled: () // TODO
case let .rcvError(rcvFileError):
AlertManager.shared.showAlert(Alert(
title: Text("File error"),
message: Text(rcvFileError.errorInfo)
))
case let .rcvWarning(rcvFileError):
AlertManager.shared.showAlert(Alert(
title: Text("Temporary file error"),
message: Text(rcvFileError.errorInfo)
))
case let .sndError(sndFileError):
AlertManager.shared.showAlert(Alert(
title: Text("File error"),
message: Text(sndFileError.errorInfo)
))
case let .sndWarning(sndFileError):
AlertManager.shared.showAlert(Alert(
title: Text("Temporary file error"),
message: Text(sndFileError.errorInfo)
))
default: ()
}
}
@@ -99,14 +119,16 @@ struct CIImageView: View {
case .sndComplete: fileIcon("checkmark", 10, 13)
case .sndCancelled: fileIcon("xmark", 10, 13)
case .sndError: fileIcon("xmark", 10, 13)
case .sndWarning: fileIcon("exclamationmark.triangle.fill", 10, 13)
case .rcvInvitation: fileIcon("arrow.down", 10, 13)
case .rcvAccepted: fileIcon("ellipsis", 14, 11)
case .rcvTransfer: progressView()
case .rcvAborted: fileIcon("exclamationmark.arrow.circlepath", 14, 11)
case .rcvComplete: EmptyView()
case .rcvCancelled: fileIcon("xmark", 10, 13)
case .rcvError: fileIcon("xmark", 10, 13)
case .rcvWarning: fileIcon("exclamationmark.triangle.fill", 10, 13)
case .invalid: fileIcon("questionmark", 10, 13)
default: EmptyView()
}
}
}
@@ -24,7 +24,7 @@ struct CIInvalidJSONView: View {
.cornerRadius(18)
.textSelection(.disabled)
.onTapGesture { showJSON = true }
.sheet(isPresented: $showJSON) {
.appSheet(isPresented: $showJSON) {
invalidJSONView(json)
}
}
@@ -44,6 +44,7 @@ func invalidJSONView(_ json: String) -> some View {
}
.frame(maxHeight: .infinity)
.padding()
.modifier(ThemedBackground())
}
struct CIInvalidJSONView_Previews: PreviewProvider {
@@ -11,6 +11,7 @@ import SimpleXChat
struct CIMemberCreatedContactView: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
var chatItem: ChatItem
var body: some View {
@@ -43,7 +44,7 @@ struct CIMemberCreatedContactView: View {
r = r
+ Text(openText)
.fontWeight(.medium)
.foregroundColor(.accentColor)
.foregroundColor(theme.colors.primary)
+ Text(" ")
}
r = r + chatItem.timestampText
@@ -11,8 +11,9 @@ import SimpleXChat
struct CIMetaView: View {
@ObservedObject var chat: Chat
@EnvironmentObject var theme: AppTheme
var chatItem: ChatItem
var metaColor = Color.secondary
var metaColor: Color
var paleMetaColor = Color(UIColor.tertiaryLabel)
var showStatus = true
var showEdited = true
@@ -63,6 +64,7 @@ func ciMetaText(
chatTTL: Int?,
encrypted: Bool?,
color: Color = .clear,
primaryColor: Color = .accentColor,
transparent: Bool = false,
sent: SentCheckmark? = nil,
showStatus: Bool = true,
@@ -85,7 +87,7 @@ func ciMetaText(
r = r + statusIconText("arrow.forward", color.opacity(0.67)).font(.caption2)
}
if showStatus {
if let (icon, statusColor) = meta.statusIcon(color) {
if let (icon, statusColor) = meta.statusIcon(color, primaryColor) {
let t = Text(Image(systemName: icon)).font(.caption2)
let gap = Text(" ").kerning(-1.25)
let t1 = t.foregroundColor(transparent ? .clear : statusColor.opacity(0.67))
@@ -112,15 +114,16 @@ private func statusIconText(_ icon: String, _ color: Color) -> Text {
}
struct CIMetaView_Previews: PreviewProvider {
static let metaColor = Color.secondary
static var previews: some View {
Group {
CIMetaView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete)))
CIMetaView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .partial)))
CIMetaView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndRcvd(msgRcptStatus: .ok, sndProgress: .complete)))
CIMetaView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndRcvd(msgRcptStatus: .ok, sndProgress: .partial)))
CIMetaView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndRcvd(msgRcptStatus: .badMsgHash, sndProgress: .complete)))
CIMetaView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), itemEdited: true))
CIMetaView(chat: Chat.sampleData, chatItem: ChatItem.getDeletedContentSample())
CIMetaView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete)), metaColor: metaColor)
CIMetaView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .partial)), metaColor: metaColor)
CIMetaView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndRcvd(msgRcptStatus: .ok, sndProgress: .complete)), metaColor: metaColor)
CIMetaView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndRcvd(msgRcptStatus: .ok, sndProgress: .partial)), metaColor: metaColor)
CIMetaView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndRcvd(msgRcptStatus: .badMsgHash, sndProgress: .complete)), metaColor: metaColor)
CIMetaView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), itemEdited: true), metaColor: metaColor)
CIMetaView(chat: Chat.sampleData, chatItem: ChatItem.getDeletedContentSample(), metaColor: metaColor)
}
.previewLayout(.fixed(width: 360, height: 100))
}
@@ -13,6 +13,7 @@ let decryptErrorReason: LocalizedStringKey = "It can happen when you or your con
struct CIRcvDecryptionError: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
@ObservedObject var chat: Chat
var msgDecryptError: MsgDecryptError
var msgCount: UInt32
@@ -114,18 +115,18 @@ struct CIRcvDecryptionError: View {
}
(
Text(Image(systemName: "exclamationmark.arrow.triangle.2.circlepath"))
.foregroundColor(syncSupported ? .accentColor : .secondary)
.foregroundColor(syncSupported ? theme.colors.primary : theme.colors.secondary)
.font(.callout)
+ Text(" ")
+ Text("Fix connection")
.foregroundColor(syncSupported ? .accentColor : .secondary)
.foregroundColor(syncSupported ? theme.colors.primary : theme.colors.secondary)
.font(.callout)
+ Text(" ")
+ ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showViaProxy: showSentViaProxy)
)
}
.padding(.horizontal, 12)
CIMetaView(chat: chat, chatItem: chatItem)
CIMetaView(chat: chat, chatItem: chatItem, metaColor: theme.colors.secondary)
.padding(.horizontal, 12)
}
.onTapGesture(perform: { onClick() })
@@ -145,7 +146,7 @@ struct CIRcvDecryptionError: View {
+ ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showViaProxy: showSentViaProxy)
}
.padding(.horizontal, 12)
CIMetaView(chat: chat, chatItem: chatItem)
CIMetaView(chat: chat, chatItem: chatItem, metaColor: theme.colors.secondary)
.padding(.horizontal, 12)
}
.onTapGesture(perform: { onClick() })
@@ -192,7 +192,7 @@ struct CIVideoView: View {
.disabled(!canBePlayed)
}
}
loadingIndicator()
fileStatusIcon()
}
.onAppear {
addObserver(player, url)
@@ -258,11 +258,11 @@ struct CIVideoView: View {
.resizable()
.scaledToFit()
.frame(width: w)
loadingIndicator()
fileStatusIcon()
}
}
@ViewBuilder private func loadingIndicator() -> some View {
@ViewBuilder private func fileStatusIcon() -> some View {
if let file = chatItem.file {
switch file.fileStatus {
case .sndStored:
@@ -279,7 +279,22 @@ struct CIVideoView: View {
}
case .sndComplete: fileIcon("checkmark", 10, 13)
case .sndCancelled: fileIcon("xmark", 10, 13)
case .sndError: fileIcon("xmark", 10, 13)
case let .sndError(sndFileError):
fileIcon("xmark", 10, 13)
.onTapGesture {
AlertManager.shared.showAlert(Alert(
title: Text("File error"),
message: Text(sndFileError.errorInfo)
))
}
case let .sndWarning(sndFileError):
fileIcon("exclamationmark.triangle.fill", 10, 13)
.onTapGesture {
AlertManager.shared.showAlert(Alert(
title: Text("Temporary file error"),
message: Text(sndFileError.errorInfo)
))
}
case .rcvInvitation: fileIcon("arrow.down", 10, 13)
case .rcvAccepted: fileIcon("ellipsis", 14, 11)
case let .rcvTransfer(rcvProgress, rcvTotal):
@@ -289,10 +304,25 @@ struct CIVideoView: View {
progressView()
}
case .rcvAborted: fileIcon("exclamationmark.arrow.circlepath", 14, 11)
case .rcvComplete: EmptyView()
case .rcvCancelled: fileIcon("xmark", 10, 13)
case .rcvError: fileIcon("xmark", 10, 13)
case let .rcvError(rcvFileError):
fileIcon("xmark", 10, 13)
.onTapGesture {
AlertManager.shared.showAlert(Alert(
title: Text("File error"),
message: Text(rcvFileError.errorInfo)
))
}
case let .rcvWarning(rcvFileError):
fileIcon("exclamationmark.triangle.fill", 10, 13)
.onTapGesture {
AlertManager.shared.showAlert(Alert(
title: Text("Temporary file error"),
message: Text(rcvFileError.errorInfo)
))
}
case .invalid: fileIcon("questionmark", 10, 13)
default: EmptyView()
}
}
}
@@ -11,6 +11,7 @@ import SimpleXChat
struct CIVoiceView: View {
@ObservedObject var chat: Chat
@EnvironmentObject var theme: AppTheme
var chatItem: ChatItem
let recordingFile: CIFile?
let duration: Int
@@ -92,7 +93,7 @@ struct CIVoiceView: View {
}
private func metaView() -> some View {
CIMetaView(chat: chat, chatItem: chatItem)
CIMetaView(chat: chat, chatItem: chatItem, metaColor: theme.colors.secondary)
}
}
@@ -118,7 +119,7 @@ struct VoiceMessagePlayerTime: View {
struct VoiceMessagePlayer: View {
@EnvironmentObject var chatModel: ChatModel
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var theme: AppTheme
var chatItem: ChatItem
var recordingFile: CIFile?
var recordingTime: TimeInterval
@@ -134,18 +135,53 @@ struct VoiceMessagePlayer: View {
ZStack {
if let recordingFile = recordingFile {
switch recordingFile.fileStatus {
case .sndStored: playbackButton()
case .sndTransfer: playbackButton()
case .sndStored:
if recordingFile.fileProtocol == .local {
playbackButton()
} else {
loadingIcon()
}
case .sndTransfer: loadingIcon()
case .sndComplete: playbackButton()
case .sndCancelled: playbackButton()
case .sndError: playbackButton()
case let .sndError(sndFileError):
fileStatusIcon("multiply", 14)
.onTapGesture {
AlertManager.shared.showAlert(Alert(
title: Text("File error"),
message: Text(sndFileError.errorInfo)
))
}
case let .sndWarning(sndFileError):
fileStatusIcon("exclamationmark.triangle.fill", 16)
.onTapGesture {
AlertManager.shared.showAlert(Alert(
title: Text("Temporary file error"),
message: Text(sndFileError.errorInfo)
))
}
case .rcvInvitation: downloadButton(recordingFile, "play.fill")
case .rcvAccepted: loadingIcon()
case .rcvTransfer: loadingIcon()
case .rcvAborted: downloadButton(recordingFile, "exclamationmark.arrow.circlepath")
case .rcvComplete: playbackButton()
case .rcvCancelled: playPauseIcon("play.fill", Color(uiColor: .tertiaryLabel))
case .rcvError: playPauseIcon("play.fill", Color(uiColor: .tertiaryLabel))
case let .rcvError(rcvFileError):
fileStatusIcon("multiply", 14)
.onTapGesture {
AlertManager.shared.showAlert(Alert(
title: Text("File error"),
message: Text(rcvFileError.errorInfo)
))
}
case let .rcvWarning(rcvFileError):
fileStatusIcon("exclamationmark.triangle.fill", 16)
.onTapGesture {
AlertManager.shared.showAlert(Alert(
title: Text("Temporary file error"),
message: Text(rcvFileError.errorInfo)
))
}
case .invalid: playPauseIcon("play.fill", Color(uiColor: .tertiaryLabel))
}
} else {
@@ -209,7 +245,7 @@ struct VoiceMessagePlayer: View {
.foregroundColor(color)
.padding(.leading, image == "play.fill" ? 4 : 0)
.frame(width: 56, height: 56)
.background(showBackground ? chatItemFrameColor(chatItem, colorScheme) : .clear)
.background(showBackground ? chatItemFrameColor(chatItem, theme) : .clear)
.clipShape(Circle())
if recordingTime > 0 {
ProgressCircle(length: recordingTime, progress: $playbackTime)
@@ -231,6 +267,7 @@ struct VoiceMessagePlayer: View {
}
private struct ProgressCircle: View {
@EnvironmentObject var theme: AppTheme
var length: TimeInterval
@Binding var progress: TimeInterval?
@@ -238,7 +275,7 @@ struct VoiceMessagePlayer: View {
Circle()
.trim(from: 0, to: ((progress ?? TimeInterval(0)) / length))
.stroke(
Color.accentColor,
theme.colors.primary,
style: StrokeStyle(lineWidth: 3)
)
.rotationEffect(.degrees(-90))
@@ -246,11 +283,22 @@ struct VoiceMessagePlayer: View {
}
}
private func fileStatusIcon(_ image: String, _ size: CGFloat) -> some View {
Image(systemName: image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: size, height: size)
.foregroundColor(Color(uiColor: .tertiaryLabel))
.frame(width: 56, height: 56)
.background(showBackground ? chatItemFrameColor(chatItem, theme) : .clear)
.clipShape(Circle())
}
private func loadingIcon() -> some View {
ProgressView()
.frame(width: 30, height: 30)
.frame(width: 56, height: 56)
.background(showBackground ? chatItemFrameColor(chatItem, colorScheme) : .clear)
.background(showBackground ? chatItemFrameColor(chatItem, theme) : .clear)
.clipShape(Circle())
}
@@ -10,7 +10,7 @@ import SwiftUI
import SimpleXChat
struct DeletedItemView: View {
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var theme: AppTheme
@ObservedObject var chat: Chat
var chatItem: ChatItem
@@ -19,12 +19,12 @@ struct DeletedItemView: View {
Text(chatItem.content.text)
.foregroundColor(.secondary)
.italic()
CIMetaView(chat: chat, chatItem: chatItem)
CIMetaView(chat: chat, chatItem: chatItem, metaColor: theme.colors.secondary)
.padding(.horizontal, 12)
}
.padding(.leading, 12)
.padding(.vertical, 6)
.background(chatItemFrameColor(chatItem, colorScheme))
.background(chatItemFrameColor(chatItem, theme))
.cornerRadius(18)
.textSelection(.disabled)
}
@@ -11,6 +11,7 @@ import SimpleXChat
struct EmojiItemView: View {
@ObservedObject var chat: Chat
@EnvironmentObject var theme: AppTheme
var chatItem: ChatItem
var body: some View {
@@ -18,7 +19,7 @@ struct EmojiItemView: View {
emojiText(chatItem.content.text)
.padding(.top, 8)
.padding(.horizontal, 6)
CIMetaView(chat: chat, chatItem: chatItem)
CIMetaView(chat: chat, chatItem: chatItem, metaColor: theme.colors.secondary)
.padding(.bottom, 8)
.padding(.horizontal, 12)
}
@@ -9,16 +9,9 @@
import SwiftUI
import SimpleXChat
let notesChatColorLight = Color(.sRGB, red: 0.27, green: 0.72, blue: 1, opacity: 0.21)
let notesChatColorDark = Color(.sRGB, red: 0.27, green: 0.72, blue: 1, opacity: 0.19)
let sentColorLight = Color(.sRGB, red: 0.27, green: 0.72, blue: 1, opacity: 0.12)
let sentColorDark = Color(.sRGB, red: 0.27, green: 0.72, blue: 1, opacity: 0.17)
private let sentQuoteColorLight = Color(.sRGB, red: 0.27, green: 0.72, blue: 1, opacity: 0.11)
private let sentQuoteColorDark = Color(.sRGB, red: 0.27, green: 0.72, blue: 1, opacity: 0.09)
struct FramedItemView: View {
@EnvironmentObject var m: ChatModel
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var theme: AppTheme
@ObservedObject var chat: Chat
var chatItem: ChatItem
@Binding var revealed: Bool
@@ -83,7 +76,10 @@ struct FramedItemView: View {
.accessibilityLabel("")
}
}
.background(chatItemFrameColorMaybeImageOrVideo(chatItem, colorScheme))
.onAppear {
metaColor = metaColor == .secondary ? theme.colors.secondary : metaColor
}
.background(chatItemFrameColorMaybeImageOrVideo(chatItem, theme))
.cornerRadius(18)
.onPreferenceChange(DetermineWidth.Key.self) { msgWidth = $0 }
@@ -175,13 +171,13 @@ struct FramedItemView: View {
.font(.caption)
.lineLimit(1)
}
.foregroundColor(.secondary)
.foregroundColor(theme.colors.secondary)
.padding(.horizontal, 12)
.padding(.top, 6)
.padding(.bottom, pad || (chatItem.quotedItem == nil && chatItem.meta.itemForwarded == nil) ? 6 : 0)
.overlay(DetermineWidth())
.frame(minWidth: msgWidth, alignment: .leading)
.background(chatItemFrameContextColor(chatItem, colorScheme))
.background(chatItemFrameContextColor(chatItem, theme))
if let mediaWidth = maxMediaWidth(), mediaWidth < maxWidth {
v.frame(maxWidth: mediaWidth, alignment: .leading)
} else {
@@ -233,7 +229,7 @@ struct FramedItemView: View {
// if enable this always, size of the framed voice message item will be incorrect after end of playback
.overlay { if case .voice = chatItem.content.msgContent {} else { DetermineWidth() } }
.frame(minWidth: msgWidth, alignment: .leading)
.background(chatItemFrameContextColor(chatItem, colorScheme))
.background(chatItemFrameContextColor(chatItem, theme))
if let mediaWidth = maxMediaWidth(), mediaWidth < maxWidth {
v.frame(maxWidth: mediaWidth, alignment: .leading)
@@ -248,7 +244,7 @@ struct FramedItemView: View {
VStack(alignment: .leading, spacing: 2) {
Text(sender)
.font(.caption)
.foregroundColor(.secondary)
.foregroundColor(theme.colors.secondary)
.lineLimit(1)
ciQuotedMsgTextView(qi, lines: 2)
}
@@ -363,22 +359,22 @@ func onlyImageOrVideo(_ ci: ChatItem) -> Bool {
return false
}
func chatItemFrameColorMaybeImageOrVideo(_ ci: ChatItem, _ colorScheme: ColorScheme) -> Color {
func chatItemFrameColorMaybeImageOrVideo(_ ci: ChatItem, _ theme: AppTheme) -> Color {
onlyImageOrVideo(ci)
? Color.clear
: chatItemFrameColor(ci, colorScheme)
: chatItemFrameColor(ci, theme)
}
func chatItemFrameColor(_ ci: ChatItem, _ colorScheme: ColorScheme) -> Color {
func chatItemFrameColor(_ ci: ChatItem, _ theme: AppTheme) -> Color {
ci.chatDir.sent
? (colorScheme == .light ? sentColorLight : sentColorDark)
: Color(uiColor: .tertiarySystemGroupedBackground)
? theme.appColors.sentMessage
: theme.appColors.receivedMessage
}
func chatItemFrameContextColor(_ ci: ChatItem, _ colorScheme: ColorScheme) -> Color {
func chatItemFrameContextColor(_ ci: ChatItem, _ theme: AppTheme) -> Color {
ci.chatDir.sent
? (colorScheme == .light ? sentQuoteColorLight : sentQuoteColorDark)
: Color(uiColor: .quaternarySystemFill)
? theme.appColors.sentQuote
: theme.appColors.receivedQuote
}
struct FramedItemView_Previews: PreviewProvider {
@@ -11,6 +11,7 @@ import SimpleXChat
struct IntegrityErrorItemView: View {
@ObservedObject var chat: Chat
@EnvironmentObject var theme: AppTheme
var msgError: MsgErrorType
var chatItem: ChatItem
@@ -54,6 +55,7 @@ struct IntegrityErrorItemView: View {
struct CIMsgError: View {
@ObservedObject var chat: Chat
@EnvironmentObject var theme: AppTheme
var chatItem: ChatItem
var onTap: () -> Void
@@ -62,7 +64,7 @@ struct CIMsgError: View {
Text(chatItem.content.text)
.foregroundColor(.red)
.italic()
CIMetaView(chat: chat, chatItem: chatItem)
CIMetaView(chat: chat, chatItem: chatItem, metaColor: theme.colors.secondary)
.padding(.horizontal, 12)
}
.padding(.leading, 12)
@@ -11,7 +11,7 @@ import SimpleXChat
struct MarkedDeletedItemView: View {
@EnvironmentObject var m: ChatModel
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var theme: AppTheme
@ObservedObject var chat: Chat
var chatItem: ChatItem
@Binding var revealed: Bool
@@ -22,7 +22,7 @@ struct MarkedDeletedItemView: View {
.foregroundColor(.secondary)
.padding(.horizontal, 12)
.padding(.vertical, 6)
.background(chatItemFrameColor(chatItem, colorScheme))
.background(chatItemFrameColor(chatItem, theme))
.cornerRadius(18)
.textSelection(.disabled)
}
@@ -35,6 +35,7 @@ struct ChatItemForwardingView: View {
}
}
}
.modifier(ThemedBackground())
}
@ViewBuilder private func forwardListView() -> some View {
@@ -59,9 +60,13 @@ struct ChatItemForwardingView: View {
.cornerRadius(12)
.padding(.horizontal)
}
.background(Color(.systemGroupedBackground))
.modifier(ThemedBackground())
} else {
emptyList()
ZStack {
emptyList()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.modifier(ThemedBackground())
}
}
}
@@ -12,11 +12,13 @@ import SimpleXChat
struct ChatItemInfoView: View {
@EnvironmentObject var chatModel: ChatModel
@Environment(\.dismiss) var dismiss
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var theme: AppTheme
var ci: ChatItem
@Binding var chatItemInfo: ChatItemInfo?
@State private var selection: CIInfoTab = .history
@State private var alert: CIInfoViewAlert? = nil
@State private var messageStatusLimited: Bool = true
@State private var fileStatusLimited: Bool = true
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
enum CIInfoTab {
@@ -99,12 +101,14 @@ struct ChatItemInfoView: View {
Label("History", systemImage: "clock")
}
.tag(CIInfoTab.history)
.modifier(ThemedBackground())
if let qi = ci.quotedItem {
quoteTab(qi)
.tabItem {
Label("In reply to", systemImage: "arrowshape.turn.up.left")
}
.tag(CIInfoTab.quote)
.modifier(ThemedBackground())
}
if let forwardedFromItem = chatItemInfo?.forwardedFromChatItem {
forwardedFromTab(forwardedFromItem)
@@ -112,6 +116,7 @@ struct ChatItemInfoView: View {
Label(local ? "Saved" : "Forwarded", systemImage: "arrowshape.turn.up.forward")
}
.tag(CIInfoTab.forwarded)
.modifier(ThemedBackground())
}
}
.onAppear {
@@ -121,6 +126,7 @@ struct ChatItemInfoView: View {
}
} else {
historyTab()
.modifier(ThemedBackground())
}
}
@@ -157,6 +163,35 @@ struct ChatItemInfoView: View {
if developerTools {
infoRow("Database ID", "\(meta.itemId)")
infoRow("Record updated at", localTimestamp(meta.updatedAt))
let msv = infoRow("Message status", ci.meta.itemStatus.id)
Group {
if messageStatusLimited {
msv.lineLimit(1)
} else {
msv
}
}
.onTapGesture {
withAnimation {
messageStatusLimited.toggle()
}
}
if let file = ci.file {
let fsv = infoRow("File status", file.fileStatus.id)
Group {
if fileStatusLimited {
fsv.lineLimit(1)
} else {
fsv
}
}
.onTapGesture {
withAnimation {
fileStatusLimited.toggle()
}
}
}
}
}
}
@@ -196,7 +231,7 @@ struct ChatItemInfoView: View {
textBubble(itemVersion.msgContent.text, itemVersion.formattedText, nil)
.padding(.horizontal, 12)
.padding(.vertical, 6)
.background(chatItemFrameColor(ci, colorScheme))
.background(chatItemFrameColor(ci, theme))
.cornerRadius(18)
.contextMenu {
if itemVersion.msgContent.text != "" {
@@ -265,7 +300,7 @@ struct ChatItemInfoView: View {
textBubble(qi.text, qi.formattedText, qi.getSender(nil))
.padding(.horizontal, 12)
.padding(.vertical, 6)
.background(quotedMsgFrameColor(qi, colorScheme))
.background(quotedMsgFrameColor(qi, theme))
.cornerRadius(18)
.contextMenu {
if qi.text != "" {
@@ -289,10 +324,10 @@ struct ChatItemInfoView: View {
.frame(maxWidth: maxWidth, alignment: .leading)
}
func quotedMsgFrameColor(_ qi: CIQuote, _ colorScheme: ColorScheme) -> Color {
func quotedMsgFrameColor(_ qi: CIQuote, _ theme: AppTheme) -> Color {
(qi.chatDir?.sent ?? false)
? (colorScheme == .light ? sentColorLight : sentColorDark)
: Color(uiColor: .tertiarySystemGroupedBackground)
? theme.appColors.sentMessage
: theme.appColors.receivedMessage
}
@ViewBuilder private func forwardedFromTab(_ forwardedFromItem: AChatItem) -> some View {
@@ -414,7 +449,7 @@ struct ChatItemInfoView: View {
.foregroundColor(.secondary).opacity(0.67)
}
let v = Group {
if let (icon, statusColor) = status.statusIcon(Color.secondary) {
if let (icon, statusColor) = status.statusIcon(theme.colors.secondary, theme.colors.primary) {
switch status {
case .sndRcvd:
ZStack(alignment: .trailing) {
@@ -473,8 +508,12 @@ struct ChatItemInfoView: View {
if developerTools {
shareText += [
String.localizedStringWithFormat(NSLocalizedString("Database ID: %d", comment: "copied message info"), meta.itemId),
String.localizedStringWithFormat(NSLocalizedString("Record updated at: %@", comment: "copied message info"), localTimestamp(meta.updatedAt))
String.localizedStringWithFormat(NSLocalizedString("Record updated at: %@", comment: "copied message info"), localTimestamp(meta.updatedAt)),
String.localizedStringWithFormat(NSLocalizedString("Message status: %@", comment: "copied message info"), meta.itemStatus.id)
]
if let file = ci.file {
shareText += [String.localizedStringWithFormat(NSLocalizedString("File status: %@", comment: "copied message info"), file.fileStatus.id)]
}
}
if let qi = ci.quotedItem {
shareText += ["", NSLocalizedString("## In reply to", comment: "copied message info")]
+44 -10
View File
@@ -14,7 +14,7 @@ private let memberImageSize: CGFloat = 34
struct ChatView: View {
@EnvironmentObject var chatModel: ChatModel
@Environment(\.colorScheme) var colorScheme
@State var theme: AppTheme = buildTheme()
@Environment(\.dismiss) var dismiss
@Environment(\.presentationMode) var presentationMode
@Environment(\.scenePhase) var scenePhase
@@ -60,7 +60,16 @@ struct ChatView: View {
Divider()
}
ZStack(alignment: .trailing) {
let wallpaperImage = theme.wallpaper.type.image
let wallpaperType = theme.wallpaper.type
let backgroundColor = theme.wallpaper.background ?? wallpaperType.defaultBackgroundColor(theme.base, theme.colors.background)
let tintColor = theme.wallpaper.tint ?? wallpaperType.defaultTintColor(theme.base)
chatItemsList()
.if(wallpaperImage != nil) { view in
view.modifier(
ChatViewBackground(image: wallpaperImage!, imageType: wallpaperType, background: backgroundColor, tint: tintColor)
)
}
if let proxy = scrollProxy {
floatingButtons(proxy)
}
@@ -78,7 +87,9 @@ struct ChatView: View {
}
.padding(.top, 1)
.navigationTitle(cInfo.chatViewName)
.background(theme.colors.background)
.navigationBarTitleDisplayMode(.inline)
.environmentObject(theme)
.onAppear {
initChatView()
}
@@ -89,6 +100,7 @@ struct ChatView: View {
chat = c
}
initChatView()
theme = buildTheme()
} else {
dismiss()
}
@@ -131,7 +143,7 @@ struct ChatView: View {
} label: {
ChatInfoToolbar(chat: chat)
}
.sheet(isPresented: $showChatInfoSheet, onDismiss: {
.appSheet(isPresented: $showChatInfoSheet, onDismiss: {
connectionStats = nil
customUserProfile = nil
connectionCode = nil
@@ -384,7 +396,7 @@ struct ChatView: View {
circleButton {
unreadCountText(unreadAbove)
.font(.callout)
.foregroundColor(.accentColor)
.foregroundColor(theme.colors.primary)
}
.onTapGesture { scrollUp(proxy) }
.contextMenu {
@@ -404,13 +416,13 @@ struct ChatView: View {
circleButton {
unreadCountText(counts.unreadBelow)
.font(.callout)
.foregroundColor(.accentColor)
.foregroundColor(theme.colors.primary)
}
.onTapGesture { scrollToBottom(proxy) }
} else if counts.totalBelow > 16 {
circleButton {
Image(systemName: "chevron.down")
.foregroundColor(.accentColor)
.foregroundColor(theme.colors.primary)
}
.onTapGesture { scrollToBottom(proxy) }
}
@@ -513,7 +525,7 @@ struct ChatView: View {
}
}
}
@ViewBuilder private func chatItemView(_ ci: ChatItem, _ maxWidth: CGFloat) -> some View {
ChatItemWithMenu(
chat: chat,
@@ -528,7 +540,7 @@ struct ChatView: View {
private struct ChatItemWithMenu: View {
@EnvironmentObject var m: ChatModel
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var theme: AppTheme
@ObservedObject var chat: Chat
var chatItem: ChatItem
var maxWidth: CGFloat
@@ -598,7 +610,7 @@ struct ChatView: View {
.padding(.top, 7)
}
HStack(alignment: .top, spacing: 8) {
ProfileImage(imageStr: member.memberProfile.image, size: memberImageSize)
ProfileImage(imageStr: member.memberProfile.image, size: memberImageSize, backgroundColor: theme.colors.background)
.onTapGesture {
if chatView.membersLoaded {
selectedMember = m.getGroupMember(member.groupMemberId)
@@ -662,7 +674,8 @@ struct ChatView: View {
playbackState: $playbackState,
playbackTime: $playbackTime
)
.uiKitContextMenu(hasImageOrVideo: ci.content.msgContent?.isImageOrVideo == true, maxWidth: maxWidth, itemWidth: $itemWidth, menu: uiMenu, allowMenu: $allowMenu)
.environmentObject(theme)
.uiKitContextMenu(hasImageOrVideo: ci.content.msgContent?.isImageOrVideo == true, maxWidth: maxWidth, itemWidth: $itemWidth, menu: uiMenu, allowMenu: $allowMenu, backgroundColor: theme.colors.background)
.accessibilityLabel("")
if ci.content.msgContent != nil && (ci.meta.itemDeleted == nil || revealed) && ci.reactions.count > 0 {
chatItemReactions(ci)
@@ -729,7 +742,7 @@ struct ChatView: View {
Text("\(r.totalReacted)")
.font(.caption)
.fontWeight(r.userReacted ? .bold : .light)
.foregroundColor(r.userReacted ? .accentColor : .secondary)
.foregroundColor(r.userReacted ? theme.colors.primary : theme.colors.secondary)
}
}
.padding(.horizontal, 6)
@@ -1219,6 +1232,27 @@ struct ChatView: View {
}
}
private func buildTheme() -> AppTheme {
if let cId = ChatModel.shared.chatId, let chat = ChatModel.shared.getChat(cId) {
let perChatTheme = if case let .direct(contact) = chat.chatInfo {
contact.uiThemes?.preferredMode(!AppTheme.shared.colors.isLight)
} else if case let .group(groupInfo) = chat.chatInfo {
groupInfo.uiThemes?.preferredMode(!AppTheme.shared.colors.isLight)
} else {
nil as ThemeModeOverride?
}
let overrides = if perChatTheme != nil {
ThemeManager.currentColors(nil, perChatTheme, ChatModel.shared.currentUser?.uiThemes, themeOverridesDefault.get())
} else {
nil as ThemeManager.ActiveTheme?
}
let theme = overrides ?? CurrentColors
return AppTheme(name: theme.name, base: theme.base, colors: theme.colors, appColors: theme.appColors, wallpaper: theme.wallpaper)
} else {
return AppTheme.shared
}
}
struct ToggleNtfsButton: View {
@ObservedObject var chat: Chat
@@ -9,7 +9,7 @@
import SwiftUI
struct ComposeFileView: View {
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var theme: AppTheme
let fileName: String
let cancelFile: (() -> Void)
let cancelEnabled: Bool
@@ -33,7 +33,7 @@ struct ComposeFileView: View {
.padding(.vertical, 1)
.padding(.trailing, 12)
.frame(height: 50)
.background(colorScheme == .light ? sentColorLight : sentColorDark)
.background(theme.appColors.sentMessage)
.frame(maxWidth: .infinity)
.padding(.top, 8)
}
@@ -10,7 +10,7 @@ import SwiftUI
import SimpleXChat
struct ComposeImageView: View {
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var theme: AppTheme
let images: [String]
let cancelImage: (() -> Void)
let cancelEnabled: Bool
@@ -48,7 +48,7 @@ struct ComposeImageView: View {
}
.padding(.vertical, 1)
.padding(.trailing, 12)
.background(colorScheme == .light ? sentColorLight : sentColorDark)
.background(theme.appColors.sentMessage)
.frame(maxWidth: .infinity)
.padding(.top, 8)
}
@@ -40,7 +40,7 @@ func getLinkPreview(url: URL, cb: @escaping (LinkPreview?) -> Void) {
}
struct ComposeLinkView: View {
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var theme: AppTheme
let linkPreview: LinkPreview?
var cancelPreview: (() -> Void)? = nil
let cancelEnabled: Bool
@@ -62,7 +62,7 @@ struct ComposeLinkView: View {
}
.padding(.vertical, 1)
.padding(.trailing, 12)
.background(colorScheme == .light ? sentColorLight : sentColorDark)
.background(theme.appColors.sentMessage)
.frame(maxWidth: .infinity)
.padding(.top, 8)
}
@@ -254,6 +254,7 @@ enum UploadContent: Equatable {
struct ComposeView: View {
@EnvironmentObject var chatModel: ChatModel
@EnvironmentObject var theme: AppTheme
@ObservedObject var chat: Chat
@Binding var composeState: ComposeState
@Binding var keyboardVisible: Bool
@@ -353,10 +354,10 @@ struct ComposeView: View {
keyboardVisible: $keyboardVisible,
sendButtonColor: chat.chatInfo.incognito
? .indigo.opacity(colorScheme == .dark ? 1 : 0.7)
: .accentColor
: theme.colors.primary
)
.padding(.trailing, 12)
.background(.background)
.background(theme.colors.background)
.disabled(!chat.userCanSend)
if chat.userIsObserver {
@@ -25,7 +25,7 @@ func voiceMessageTime_(_ time: TimeInterval?) -> String {
struct ComposeVoiceView: View {
@EnvironmentObject var chatModel: ChatModel
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var theme: AppTheme
var recordingFileName: String
@Binding var recordingTime: TimeInterval?
@Binding var recordingState: VoiceMessageRecordingState
@@ -50,7 +50,7 @@ struct ComposeVoiceView: View {
}
.padding(.vertical, 1)
.frame(height: ComposeVoiceView.previewHeight)
.background(colorScheme == .light ? sentColorLight : sentColorDark)
.background(theme.appColors.sentMessage)
.frame(maxWidth: .infinity)
.padding(.top, 8)
}
@@ -80,7 +80,7 @@ struct ComposeVoiceView: View {
Button {
startPlayback()
} label: {
playPauseIcon("play.fill")
playPauseIcon("play.fill", theme.colors.primary)
}
Text(voiceMessageTime_(recordingTime))
case .playing:
@@ -88,7 +88,7 @@ struct ComposeVoiceView: View {
audioPlayer?.pause()
playbackState = .paused
} label: {
playPauseIcon("pause.fill")
playPauseIcon("pause.fill", theme.colors.primary)
}
Text(voiceMessageTime_(playbackTime))
case .paused:
@@ -96,7 +96,7 @@ struct ComposeVoiceView: View {
audioPlayer?.play()
playbackState = .playing
} label: {
playPauseIcon("play.fill")
playPauseIcon("play.fill", theme.colors.primary)
}
Text(voiceMessageTime_(playbackTime))
}
@@ -131,7 +131,7 @@ struct ComposeVoiceView: View {
}
}
private func playPauseIcon(_ image: String, _ color: Color = .accentColor) -> some View {
private func playPauseIcon(_ image: String, _ color: Color) -> some View {
Image(systemName: image)
.resizable()
.aspectRatio(contentMode: .fit)
@@ -162,6 +162,7 @@ struct ComposeVoiceView: View {
}
private struct ProgressBar: View {
@EnvironmentObject var theme: AppTheme
var length: TimeInterval
@Binding var progress: TimeInterval?
@@ -169,7 +170,7 @@ struct ComposeVoiceView: View {
GeometryReader { geometry in
ZStack {
Rectangle()
.fill(Color.accentColor)
.fill(theme.colors.primary)
.frame(width: min(CGFloat((progress ?? TimeInterval(0)) / length) * geometry.size.width, geometry.size.width), height: 4)
.animation(.linear, value: progress)
}
@@ -9,7 +9,7 @@
import SwiftUI
struct ContextInvitingContactMemberView: View {
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var theme: AppTheme
var body: some View {
HStack {
@@ -20,7 +20,7 @@ struct ContextInvitingContactMemberView: View {
.padding(12)
.frame(minHeight: 50)
.frame(maxWidth: .infinity, alignment: .leading)
.background(colorScheme == .light ? sentColorLight : sentColorDark)
.background(theme.appColors.sentMessage)
.padding(.top, 8)
}
}
@@ -10,7 +10,7 @@ import SwiftUI
import SimpleXChat
struct ContextItemView: View {
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var theme: AppTheme
@ObservedObject var chat: Chat
let contextItem: ChatItem
let contextIcon: String
@@ -44,7 +44,7 @@ struct ContextItemView: View {
.padding(12)
.frame(minHeight: 50)
.frame(maxWidth: .infinity)
.background(chatItemFrameColor(contextItem, colorScheme))
.background(chatItemFrameColor(contextItem, theme))
.padding(.top, 8)
}
@@ -28,6 +28,7 @@ struct NativeTextEditor: UIViewRepresentable {
func makeUIView(context: Context) -> UITextView {
let field = CustomUITextField(height: _height)
field.backgroundColor = .clear
field.text = text
field.textAlignment = alignment(text)
field.autocapitalizationType = .sentences
@@ -13,6 +13,7 @@ private let liveMsgInterval: UInt64 = 3000_000000
struct SendMessageView: View {
@Binding var composeState: ComposeState
@EnvironmentObject var theme: AppTheme
var sendMessage: (Int?) -> Void
var sendLiveMessage: (() async -> Void)? = nil
var updateLiveMessage: (() async -> Void)? = nil
@@ -247,6 +248,7 @@ struct SendMessageView: View {
}
private struct RecordVoiceMessageButton: View {
@EnvironmentObject var theme: AppTheme
var startVoiceMessageRecording: (() -> Void)?
var finishVoiceMessageRecording: (() -> Void)?
@Binding var holdingVMR: Bool
@@ -256,7 +258,7 @@ struct SendMessageView: View {
var body: some View {
Button(action: {}) {
Image(systemName: "mic.fill")
.foregroundColor(.accentColor)
.foregroundColor(theme.colors.primary)
}
.disabled(disabled)
.frame(width: 29, height: 29)
@@ -323,7 +325,7 @@ struct SendMessageView: View {
Image(systemName: "multiply")
.resizable()
.scaledToFit()
.foregroundColor(.accentColor)
.foregroundColor(theme.colors.primary)
.frame(width: 15, height: 15)
}
.frame(width: 29, height: 29)
@@ -340,7 +342,7 @@ struct SendMessageView: View {
Image(systemName: "bolt.fill")
.resizable()
.scaledToFit()
.foregroundColor(.accentColor)
.foregroundColor(theme.colors.primary)
.frame(width: 20, height: 20)
}
.frame(width: 29, height: 29)
@@ -383,7 +385,7 @@ struct SendMessageView: View {
}
Task {
_ = try? await Task.sleep(nanoseconds: liveMsgInterval)
while composeState.liveMessage != nil {
while await composeState.liveMessage != nil {
await update()
_ = try? await Task.sleep(nanoseconds: liveMsgInterval)
}
@@ -394,7 +396,7 @@ struct SendMessageView: View {
private func finishVoiceMessageRecordingButton() -> some View {
Button(action: { finishVoiceMessageRecording?() }) {
Image(systemName: "stop.fill")
.foregroundColor(.accentColor)
.foregroundColor(theme.colors.primary)
}
.disabled(composeState.inProgress)
.frame(width: 29, height: 29)
@@ -21,6 +21,7 @@ struct AddGroupMembersView: View {
struct AddGroupMembersViewCommon: View {
@EnvironmentObject var chatModel: ChatModel
@EnvironmentObject var theme: AppTheme
var chat: Chat
@State var groupInfo: GroupInfo
var creatingGroup: Bool = false
@@ -125,6 +126,7 @@ struct AddGroupMembersViewCommon: View {
.onChange(of: selectedContacts) { _ in
searchFocussed = false
}
.modifier(ThemedBackground())
}
private func inviteMembersButton() -> some View {
@@ -176,7 +178,7 @@ struct AddGroupMembersViewCommon: View {
} else {
if checked {
icon = "checkmark.circle.fill"
iconColor = .accentColor
iconColor = theme.colors.primary
} else {
icon = "circle"
iconColor = Color(uiColor: .tertiaryLabel)
@@ -135,6 +135,7 @@ struct GroupChatInfoView: View {
}
}
}
.modifier(ThemedBackground())
.navigationBarHidden(true)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
@@ -210,6 +211,7 @@ struct GroupChatInfoView: View {
private struct MemberRowView: View {
var groupInfo: GroupInfo
@ObservedObject var groupMember: GMember
@EnvironmentObject var theme: AppTheme
var user: Bool = false
@Binding var alert: GroupChatInfoViewAlert?
@@ -282,7 +284,7 @@ struct GroupChatInfoView: View {
Button {
alert = .unblockMemberAlert(mem: member)
} label: {
Label("Unblock member", systemImage: "hand.raised.slash").foregroundColor(.accentColor)
Label("Unblock member", systemImage: "hand.raised.slash").foregroundColor(theme.colors.primary)
}
}
}
@@ -294,7 +296,7 @@ struct GroupChatInfoView: View {
Button {
alert = .unblockForAllAlert(mem: member)
} label: {
Label("Unblock for all", systemImage: "hand.raised.slash").foregroundColor(.accentColor)
Label("Unblock for all", systemImage: "hand.raised.slash").foregroundColor(theme.colors.primary)
}
} else {
Button {
@@ -333,6 +335,7 @@ struct GroupChatInfoView: View {
creatingGroup: false
)
.navigationBarTitle("Group link")
.modifier(ThemedBackground())
.navigationBarTitleDisplayMode(.large)
} label: {
if groupLink == nil {
@@ -350,6 +353,7 @@ struct GroupChatInfoView: View {
groupProfile: groupInfo.groupProfile
)
.navigationBarTitle("Group profile")
.modifier(ThemedBackground())
.navigationBarTitleDisplayMode(.large)
} label: {
Label("Edit group profile", systemImage: "pencil")
@@ -364,6 +368,7 @@ struct GroupChatInfoView: View {
welcomeText: groupInfo.groupProfile.description ?? ""
)
.navigationTitle("Welcome message")
.modifier(ThemedBackground())
.navigationBarTitleDisplayMode(.large)
} label: {
groupInfo.groupProfile.description == nil
@@ -518,6 +523,7 @@ func groupPreferencesButton(_ groupInfo: Binding<GroupInfo>, _ creatingGroup: Bo
creatingGroup: creatingGroup
)
.navigationBarTitle("Group preferences")
.modifier(ThemedBackground())
.navigationBarTitleDisplayMode(.large)
} label: {
if creatingGroup {
@@ -133,6 +133,7 @@ struct GroupLinkView: View {
shouldCreate = false
}
}
.modifier(ThemedBackground())
}
private func createGroupLink() {
@@ -35,6 +35,7 @@ struct GroupMemberInfoView: View {
case abortSwitchAddressAlert
case syncConnectionForceAlert
case planAndConnectAlert(alert: PlanAndConnectAlert)
case queueInfo(info: String)
case error(title: LocalizedStringKey, error: LocalizedStringKey)
var id: String {
@@ -49,6 +50,7 @@ struct GroupMemberInfoView: View {
case .abortSwitchAddressAlert: return "abortSwitchAddressAlert"
case .syncConnectionForceAlert: return "syncConnectionForceAlert"
case let .planAndConnectAlert(alert): return "planAndConnectAlert \(alert.id)"
case let .queueInfo(info): return "queueInfo \(info)"
case let .error(title, _): return "error \(title)"
}
}
@@ -178,6 +180,18 @@ struct GroupMemberInfoView: View {
Section("For console") {
infoRow("Local name", member.localDisplayName)
infoRow("Database ID", "\(member.groupMemberId)")
Button ("Debug delivery") {
Task {
do {
let info = queueInfoText(try await apiGroupMemberQueueInfo(groupInfo.apiId, member.groupMemberId))
await MainActor.run { alert = .queueInfo(info: info) }
} catch let e {
logger.error("apiContactQueueInfo error: \(responseError(e))")
let a = getErrorAlert(e, "Error")
await MainActor.run { alert = .error(title: a.title, error: a.message) }
}
}
}
}
}
}
@@ -223,6 +237,7 @@ struct GroupMemberInfoView: View {
case .abortSwitchAddressAlert: return abortSwitchAddressAlert(abortSwitchMemberAddress)
case .syncConnectionForceAlert: return syncConnectionForceAlert({ syncMemberConnection(force: true) })
case let .planAndConnectAlert(alert): return planAndConnectAlert(alert, dismiss: true)
case let .queueInfo(info): return queueInfoAlert(info)
case let .error(title, error): return Alert(title: Text(title), message: Text(error))
}
}
@@ -232,6 +247,7 @@ struct GroupMemberInfoView: View {
ProgressView().scaleEffect(2)
}
}
.modifier(ThemedBackground())
}
func connectViaAddressButton(_ contactLink: String) -> some View {
@@ -359,6 +375,7 @@ struct GroupMemberInfoView: View {
)
.navigationBarTitleDisplayMode(.inline)
.navigationTitle("Security code")
.modifier(ThemedBackground())
} label: {
Label(
member.verified ? "View security code" : "Verify security code",
@@ -34,8 +34,7 @@ struct GroupPreferencesView: View {
featureSection(.reactions, $preferences.reactions.enable)
featureSection(.voice, $preferences.voice.enable, $preferences.voice.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)
if groupInfo.canEdit {
@@ -102,8 +101,6 @@ struct GroupPreferencesView: View {
}
}
.frame(height: 36)
// remove in v5.8
.disabled(true)
}
} else {
settingsRow(icon, color: color) {
@@ -66,6 +66,7 @@ struct VerifyCodeView: View {
ScanCodeView(connectionVerified: $connectionVerified, verify: verify)
.navigationBarTitleDisplayMode(.large)
.navigationTitle("Scan code")
.modifier(ThemedBackground())
} label: {
Label("Scan code", systemImage: "qrcode")
}
@@ -26,6 +26,7 @@ private let rowHeights: [DynamicTypeSize: CGFloat] = [
struct ChatListNavLink: View {
@EnvironmentObject var chatModel: ChatModel
@EnvironmentObject var theme: AppTheme
@Environment(\.dynamicTypeSize) private var dynamicTypeSize
@ObservedObject var chat: Chat
@State private var showContactRequestDialog = false
@@ -224,7 +225,7 @@ struct ChatListNavLink: View {
} label: {
Label("Join", systemImage: chat.chatInfo.incognito ? "theatermasks" : "ipad.and.arrow.forward")
}
.tint(chat.chatInfo.incognito ? .indigo : .accentColor)
.tint(chat.chatInfo.incognito ? .indigo : theme.colors.primary)
}
@ViewBuilder private func markReadButton() -> some View {
@@ -234,14 +235,14 @@ struct ChatListNavLink: View {
} label: {
Label("Read", systemImage: "checkmark")
}
.tint(Color.accentColor)
.tint(theme.colors.primary)
} else {
Button {
Task { await markChatUnread(chat) }
} label: {
Label("Unread", systemImage: "circlebadge.fill")
}
.tint(Color.accentColor)
.tint(theme.colors.primary)
}
}
@@ -306,7 +307,7 @@ struct ChatListNavLink: View {
Button {
Task { await acceptContactRequest(incognito: false, contactRequest: contactRequest) }
} label: { Label("Accept", systemImage: "checkmark") }
.tint(.accentColor)
.tint(theme.colors.primary)
Button {
Task { await acceptContactRequest(incognito: true, contactRequest: contactRequest) }
} label: {
@@ -346,13 +347,16 @@ struct ChatListNavLink: View {
} label: {
Label("Name", systemImage: "pencil")
}
.tint(.accentColor)
.tint(theme.colors.primary)
}
.frame(height: rowHeights[dynamicTypeSize])
.sheet(isPresented: $showContactConnectionInfo) {
if case let .contactConnection(contactConnection) = chat.chatInfo {
ContactConnectionInfo(contactConnection: contactConnection)
.environment(\EnvironmentValues.refresh as! WritableKeyPath<EnvironmentValues, RefreshAction?>, nil)
.appSheet(isPresented: $showContactConnectionInfo) {
Group {
if case let .contactConnection(contactConnection) = chat.chatInfo {
ContactConnectionInfo(contactConnection: contactConnection)
.environment(\EnvironmentValues.refresh as! WritableKeyPath<EnvironmentValues, RefreshAction?>, nil)
.modifier(ThemedBackground())
}
}
}
.onTapGesture {
@@ -467,7 +471,7 @@ struct ChatListNavLink: View {
.padding(4)
.frame(height: rowHeights[dynamicTypeSize])
.onTapGesture { showInvalidJSON = true }
.sheet(isPresented: $showInvalidJSON) {
.appSheet(isPresented: $showInvalidJSON) {
invalidJSONView(json)
.environment(\EnvironmentValues.refresh as! WritableKeyPath<EnvironmentValues, RefreshAction?>, nil)
}
@@ -11,6 +11,7 @@ import SimpleXChat
struct ChatListView: View {
@EnvironmentObject var chatModel: ChatModel
@EnvironmentObject var theme: AppTheme
@Binding var showSettings: Bool
@State private var searchMode = false
@FocusState private var searchFocussed
@@ -86,6 +87,7 @@ struct ChatListView: View {
))
}
.listStyle(.plain)
.background(theme.colors.background)
.navigationBarTitleDisplayMode(.inline)
.navigationBarHidden(searchMode)
.toolbar {
@@ -136,7 +138,7 @@ struct ChatListView: View {
showUnreadAndFavorites = !showUnreadAndFavorites
} label: {
Image(systemName: "line.3.horizontal.decrease.circle" + (showUnreadAndFavorites ? ".fill" : ""))
.foregroundColor(.accentColor)
.foregroundColor(theme.colors.primary)
}
}
@@ -154,12 +156,14 @@ struct ChatListView: View {
searchChatFilteredBySimplexLink: $searchChatFilteredBySimplexLink
)
.listRowSeparator(.hidden)
.listRowBackground(Color.clear)
.frame(maxWidth: .infinity)
}
ForEach(cs, id: \.viewId) { chat in
ChatListNavLink(chat: chat)
.padding(.trailing, -16)
.disabled(chatModel.chatRunning != true || chatModel.deletedChats.contains(chat.chatInfo.id))
.listRowBackground(Color.clear)
}
.offset(x: -8)
}
@@ -179,7 +183,7 @@ struct ChatListView: View {
private func unreadBadge(_ text: Text? = Text(" "), size: CGFloat = 18) -> some View {
Circle()
.frame(width: size, height: size)
.foregroundColor(.accentColor)
.foregroundColor(theme.colors.primary)
}
private func onboardingButtons() -> some View {
@@ -190,7 +194,7 @@ struct ChatListView: View {
p.addLine(to: CGPoint(x: 0, y: 10))
p.addLine(to: CGPoint(x: 8, y: 0))
}
.fill(Color.accentColor)
.fill(theme.colors.primary)
.frame(width: 20, height: 10)
.padding(.trailing, 12)
@@ -213,7 +217,7 @@ struct ChatListView: View {
.padding(.vertical, 10)
.padding(.horizontal, 20)
}
.background(Color.accentColor)
.background(theme.colors.primary)
.foregroundColor(.white)
.clipShape(RoundedRectangle(cornerRadius: 16))
}
@@ -276,6 +280,7 @@ struct ChatListView: View {
struct ChatListSearchBar: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
@Binding var searchMode: Bool
@FocusState.Binding var searchFocussed: Bool
@Binding var searchText: String
@@ -330,7 +335,7 @@ struct ChatListSearchBar: View {
if searchFocussed {
Text("Cancel")
.foregroundColor(.accentColor)
.foregroundColor(theme.colors.primary)
.onTapGesture {
searchText = ""
searchFocussed = false
@@ -11,6 +11,7 @@ import SimpleXChat
struct ChatPreviewView: View {
@EnvironmentObject var chatModel: ChatModel
@EnvironmentObject var theme: AppTheme
@ObservedObject var chat: Chat
@Binding var progressByTimeout: Bool
@State var deleting: Bool = false
@@ -94,7 +95,7 @@ struct ChatPreviewView: View {
case let .group(groupInfo):
let v = previewTitle(t)
switch (groupInfo.membership.memberStatus) {
case .memInvited: v.foregroundColor(deleting ? .secondary : chat.chatInfo.incognito ? .indigo : .accentColor)
case .memInvited: v.foregroundColor(deleting ? .secondary : chat.chatInfo.incognito ? .indigo : theme.colors.primary)
case .memAccepted: v.foregroundColor(.secondary)
default: if deleting { v.foregroundColor(.secondary) } else { v }
}
@@ -133,7 +134,7 @@ struct ChatPreviewView: View {
.foregroundColor(.white)
.padding(.horizontal, 4)
.frame(minWidth: 18, minHeight: 18)
.background(chat.chatInfo.ntfsEnabled || chat.chatInfo.chatType == .local ? Color.accentColor : Color.secondary)
.background(chat.chatInfo.ntfsEnabled || chat.chatInfo.chatType == .local ? theme.colors.primary : theme.colors.secondary)
.cornerRadius(10)
} else if !chat.chatInfo.ntfsEnabled && chat.chatInfo.chatType != .local {
Image(systemName: "speaker.slash.fill")
@@ -151,7 +152,7 @@ struct ChatPreviewView: View {
private func messageDraft(_ draft: ComposeState) -> Text {
let msg = draft.message
return image("rectangle.and.pencil.and.ellipsis", color: .accentColor)
return image("rectangle.and.pencil.and.ellipsis", color: theme.colors.primary)
+ attachment()
+ messageText(msg, parseSimpleXMarkdown(msg), nil, preview: true, showSecrets: false)
@@ -206,7 +207,7 @@ struct ChatPreviewView: View {
case let .direct(contact):
if contact.activeConn == nil && contact.profile.contactLink != nil {
chatPreviewInfoText("Tap to Connect")
.foregroundColor(.accentColor)
.foregroundColor(theme.colors.primary)
} else if !contact.ready && contact.activeConn != nil {
if contact.nextSendGrpInv {
chatPreviewInfoText("send direct message")
@@ -11,6 +11,7 @@ import SimpleXChat
struct ContactConnectionInfo: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
@Environment(\.dismiss) var dismiss: DismissAction
@State var contactConnection: PendingContactConnection
@State private var alert: CCInfoAlert?
@@ -82,6 +83,7 @@ struct ContactConnectionInfo: View {
}
}
}
.modifier(ThemedBackground())
if #available(iOS 16, *) {
v
} else {
@@ -149,7 +151,7 @@ struct ContactConnectionInfo: View {
HStack(spacing: 6) {
Text("Incognito")
Image(systemName: "info.circle")
.foregroundColor(.accentColor)
.foregroundColor(theme.colors.primary)
.font(.system(size: 14))
}
.onTapGesture {
@@ -178,6 +180,7 @@ private func oneTimeLinkLearnMoreButton() -> some View {
NavigationLink {
AddContactLearnMore(showTitle: false)
.navigationTitle("One-time invitation link")
.modifier(ThemedBackground())
.navigationBarTitleDisplayMode(.large)
} label: {
settingsRow("info.circle") {
@@ -11,6 +11,7 @@ import SimpleXChat
struct ContactRequestView: View {
@EnvironmentObject var chatModel: ChatModel
@EnvironmentObject var theme: AppTheme
var contactRequest: UserContactRequest
@ObservedObject var chat: Chat
@@ -23,7 +24,7 @@ struct ContactRequestView: View {
Text(contactRequest.chatViewName)
.font(.title3)
.fontWeight(.bold)
.foregroundColor(.accentColor)
.foregroundColor(theme.colors.primary)
.padding(.leading, 8)
.frame(alignment: .topLeading)
Spacer()
@@ -6,13 +6,11 @@
import SwiftUI
import SimpleXChat
private let fillColorDark = Color(uiColor: UIColor(red: 0.11, green: 0.11, blue: 0.11, alpha: 255))
private let fillColorLight = Color(uiColor: UIColor(red: 0.99, green: 0.99, blue: 0.99, alpha: 255))
struct UserPicker: View {
@EnvironmentObject var m: ChatModel
@Environment(\.colorScheme) var colorScheme
@Environment(\.scenePhase) var scenePhase
@EnvironmentObject var theme: AppTheme
@Binding var showSettings: Bool
@Binding var showConnectDesktop: Bool
@Binding var userPickerVisible: Bool
@@ -21,9 +19,6 @@ struct UserPicker: View {
private let menuButtonHeight: CGFloat = 68
@State var chatViewNameWidth: CGFloat = 0
var fillColor: Color {
colorScheme == .dark ? fillColorDark : fillColorLight
}
var body: some View {
VStack {
@@ -82,7 +77,7 @@ struct UserPicker: View {
.clipShape(RoundedRectangle(cornerRadius: 16))
.background(
Rectangle()
.fill(fillColor)
.fill(theme.colors.surface)
.cornerRadius(16)
.shadow(color: .black.opacity(0.12), radius: 24, x: 0, y: 0)
)
@@ -137,7 +132,7 @@ struct UserPicker: View {
if user.activeUser {
Image(systemName: "checkmark")
} else if u.unreadCount > 0 {
unreadCounter(u.unreadCount, color: user.showNtfs ? .accentColor : .secondary)
unreadCounter(u.unreadCount, color: user.showNtfs ? theme.colors.primary : theme.colors.secondary)
} else if !user.showNtfs {
Image(systemName: "speaker.slash")
}
@@ -145,7 +140,7 @@ struct UserPicker: View {
.padding(.trailing)
.padding([.leading, .vertical], 12)
})
.buttonStyle(PressedButtonStyle(defaultColor: fillColor, pressedColor: Color(uiColor: .secondarySystemFill)))
.buttonStyle(PressedButtonStyle(defaultColor: theme.colors.surface, pressedColor: Color(uiColor: .secondarySystemFill)))
}
private func menuButton(_ title: LocalizedStringKey, icon: String, action: @escaping () -> Void) -> some View {
@@ -162,7 +157,7 @@ struct UserPicker: View {
.padding(.vertical, 22)
.frame(height: menuButtonHeight)
}
.buttonStyle(PressedButtonStyle(defaultColor: fillColor, pressedColor: Color(uiColor: .secondarySystemFill)))
.buttonStyle(PressedButtonStyle(defaultColor: theme.colors.surface, pressedColor: Color(uiColor: .secondarySystemFill)))
}
}
@@ -118,6 +118,7 @@ struct DatabaseView: View {
NavigationLink {
DatabaseEncryptionView(useKeychain: $useKeychain, migration: false)
.navigationTitle("Database passphrase")
.modifier(ThemedBackground())
} label: {
Text("Database passphrase")
}
@@ -144,6 +145,7 @@ struct DatabaseView: View {
NavigationLink {
ChatArchiveView(archiveName: archiveName)
.navigationTitle(title)
.modifier(ThemedBackground())
} label: {
Text(title)
}
@@ -355,6 +357,7 @@ struct DatabaseView: View {
Task {
do {
try await apiDeleteStorage()
try? FileManager.default.createDirectory(at: getWallpaperDirectory(), withIntermediateDirectories: true)
do {
let config = ArchiveConfig(archivePath: archivePath.path)
let archiveErrors = try await apiImportArchive(config: config)
@@ -189,6 +189,7 @@ struct MigrateToAppGroupView: View {
Task {
do {
try apiSaveAppSettings(settings: AppSettings.current.prepareForExport())
try? FileManager.default.createDirectory(at: getWallpaperDirectory(), withIntermediateDirectories: true)
try await apiExportArchive(config: config)
await MainActor.run { setV3DBMigration(.exported) }
} catch let error {
@@ -231,6 +232,7 @@ func exportChatArchive(_ storagePath: URL? = nil) async throws -> URL {
if !ChatModel.shared.chatDbChanged {
try apiSaveAppSettings(settings: AppSettings.current.prepareForExport())
}
try? FileManager.default.createDirectory(at: getWallpaperDirectory(), withIntermediateDirectories: true)
try await apiExportArchive(config: config)
if storagePath == nil {
deleteOldArchive()
@@ -10,7 +10,7 @@ import SwiftUI
import SimpleXChat
struct ChatInfoImage: View {
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var theme: AppTheme
@ObservedObject var chat: Chat
var size: CGFloat
var color = Color(uiColor: .tertiarySystemGroupedBackground)
@@ -24,8 +24,7 @@ struct ChatInfoImage: View {
case .contactRequest: iconName = "person.crop.circle.fill"
default: iconName = "circle.fill"
}
let notesColor = colorScheme == .light ? notesChatColorLight : notesChatColorDark
let iconColor = if case .local = chat.chatInfo { notesColor } else { color }
let iconColor = if case .local = chat.chatInfo { theme.appColors.primaryVariant2 } else { color }
return ProfileImage(
imageStr: chat.chatInfo.image,
iconName: iconName,
@@ -0,0 +1,91 @@
//
// ChatWallpaper.swift
// SimpleX (iOS)
//
// Created by Avently on 14.06.2024.
// Copyright © 2024 SimpleX Chat. All rights reserved.
//
import Foundation
import SwiftUI
import SimpleXChat
struct ChatViewBackground: ViewModifier {
@EnvironmentObject var theme: AppTheme
var image: Image
var imageType: WallpaperType
var background: Color
var tint: Color
func body(content: Content) -> some View {
content.background(
Canvas { context, size in
var image = context.resolve(image)
image.shading = .color(tint)
let rect = CGRectMake(0, 0, size.width, size.height)
func repeatDraw(_ imageScale: CGFloat) {
let scale = imageScale
for h in 0 ... Int(size.height / image.size.height / scale) {
for w in 0 ... Int(size.width / image.size.width / scale) {
let rect = CGRectMake(CGFloat(w) * image.size.width * scale, CGFloat(h) * image.size.height * scale, image.size.width * scale, image.size.height * scale)
context.draw(image, in: rect, style: FillStyle())
}
}
}
context.fill(Path(rect), with: .color(background))
switch imageType {
case let WallpaperType.Preset(filename, scale): repeatDraw(CGFloat((scale ?? 1) * (PresetWallpaper.from(filename)?.scale ?? 1)))
case let WallpaperType.Image(_, scale, scaleType):
let scaleType = scaleType ?? WallpaperScaleType.fill
switch scaleType {
case WallpaperScaleType.repeat: repeatDraw(CGFloat(scale ?? 1))
case WallpaperScaleType.fill: fallthrough
case WallpaperScaleType.fit:
// let scale = scaleType.contentScale.computeScaleFactor(Size(image.width.toFloat(), image.height.toFloat()), Size(size.width, size.height))
// let scaledWidth = (image.width * scale.scaleX).roundToInt()
// let scaledHeight = (image.height * scale.scaleY).roundToInt()
// drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality)
// if (scaleType == WallpaperScaleType.FIT) {
// if (scaledWidth < size.width) {
// // has black lines at left and right sides
// var x = (size.width - scaledWidth) / 2
// while (x > 0) {
// drawImage(image, dstOffset = IntOffset(x = (x - scaledWidth).roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality)
// x -= scaledWidth
// }
// x = size.width - (size.width - scaledWidth) / 2
// while (x < size.width) {
// drawImage(image, dstOffset = IntOffset(x = x.roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality)
// x += scaledWidth
// }
// } else {
// // has black lines at top and bottom sides
// var y = (size.height - scaledHeight) / 2
// while (y > 0) {
// drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = (y - scaledHeight).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality)
// y -= scaledHeight
// }
// y = size.height - (size.height - scaledHeight) / 2
// while (y < size.height) {
// drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = y.roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality)
// y += scaledHeight
// }
// }
// }
context.fill(Path(rect), with: .color(tint))
}
case WallpaperType.Empty: ()
}
}
)
}
}
extension PresetWallpaper {
public func toType(_ base: DefaultTheme, _ scale: Float? = nil) -> WallpaperType {
WallpaperType.Preset(
filename,
scale ?? themeOverridesDefault.get().first { $0.wallpaper != nil && $0.wallpaper!.preset == filename && $0.base == base }?.wallpaper?.scale ?? 1
)
}
}
@@ -11,7 +11,7 @@ import UIKit
import SwiftUI
extension View {
func uiKitContextMenu(hasImageOrVideo: Bool, maxWidth: CGFloat, itemWidth: Binding<CGFloat>, menu: Binding<UIMenu>, allowMenu: Binding<Bool>) -> some View {
func uiKitContextMenu(hasImageOrVideo: Bool, maxWidth: CGFloat, itemWidth: Binding<CGFloat>, menu: Binding<UIMenu>, allowMenu: Binding<Bool>, backgroundColor: Color) -> some View {
Group {
if allowMenu.wrappedValue {
if hasImageOrVideo {
@@ -19,10 +19,10 @@ extension View {
self.environmentObject(ChatModel.shared)
.overlay(DetermineWidthImageVideoItem())
.onPreferenceChange(DetermineWidthImageVideoItem.Key.self) { itemWidth.wrappedValue = $0 == 0 ? maxWidth : $0 }
, maxWidth: maxWidth, itemWidth: itemWidth, menu: menu)
, maxWidth: maxWidth, itemWidth: itemWidth, menu: menu, backgroundColor: backgroundColor)
.frame(maxWidth: itemWidth.wrappedValue)
} else {
InteractionView(content: self.environmentObject(ChatModel.shared), maxWidth: maxWidth, itemWidth: itemWidth, menu: menu)
InteractionView(content: self.environmentObject(ChatModel.shared), maxWidth: maxWidth, itemWidth: itemWidth, menu: menu, backgroundColor: backgroundColor)
.fixedSize(horizontal: true, vertical: false)
}
} else {
@@ -42,11 +42,14 @@ struct InteractionView<Content: View>: UIViewRepresentable {
var maxWidth: CGFloat
var itemWidth: Binding<CGFloat>
@Binding var menu: UIMenu
var backgroundColor: Color
func makeUIView(context: Context) -> UIView {
let view = HostingViewHolder()
view.backgroundColor = .clear
let hostView = UIHostingController(rootView: content)
// hostView.view.backgroundColor = UIColor(backgroundColor)
hostView.view.backgroundColor = .clear
view.contentSize = hostView.view.intrinsicContentSize
hostView.view.translatesAutoresizingMaskIntoConstraints = false
let constraints = [
@@ -72,6 +75,7 @@ struct InteractionView<Content: View>: UIViewRepresentable {
if was != (uiView as! HostingViewHolder).contentSize {
uiView.invalidateIntrinsicContentSize()
}
//uiView.backgroundColor = UIColor(backgroundColor)
}
func makeCoordinator() -> Coordinator {
@@ -16,6 +16,7 @@ struct ProfileImage: View {
var iconName: String = "person.crop.circle.fill"
var size: CGFloat
var color = Color(uiColor: .tertiarySystemGroupedBackground)
var backgroundColor: Color? = nil
@AppStorage(DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) private var radius = defaultProfileImageCorner
var body: some View {
@@ -28,6 +29,7 @@ struct ProfileImage: View {
.resizable()
.foregroundColor(color)
.frame(width: size, height: size)
.background(backgroundColor != nil ? backgroundColor! : .clear)
}
}
}
@@ -0,0 +1,19 @@
//
// ViewModifiers.swift
// SimpleX (iOS)
//
// Created by Avently on 12.06.2024.
// Copyright © 2024 SimpleX Chat. All rights reserved.
//
import SwiftUI
extension View {
@ViewBuilder func `if`<Content: View>(_ condition: @autoclosure () -> Bool, transform: (Self) -> Content) -> some View {
if condition() {
transform(self)
} else {
self
}
}
}
@@ -140,7 +140,7 @@ struct PasscodeEntry: View {
ZStack {
Circle()
.frame(width: h, height: h)
.foregroundColor(Color(uiColor: .systemBackground))
.foregroundColor(AppTheme.shared.colors.background)
label()
}
}
@@ -29,7 +29,7 @@ struct PasscodeView: View {
}
.padding(.horizontal, 40)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color(uiColor: .systemBackground))
.background(AppTheme.shared.colors.background)
}
private func verticalPasscodeView(_ g: GeometryProxy) -> some View {
@@ -53,6 +53,7 @@ private enum MigrateFromDeviceViewAlert: Identifiable {
struct MigrateFromDevice: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
@Environment(\.dismiss) var dismiss: DismissAction
@Binding var showSettings: Bool
@Binding var showProgressOnSettings: Bool
@@ -215,7 +216,7 @@ struct MigrateFromDevice: View {
Section {
Button(action: { migrationState = .archiving }) {
settingsRow("tray.and.arrow.up") {
Text("Archive and upload").foregroundColor(.accentColor)
Text("Archive and upload").foregroundColor(theme.colors.primary)
}
}
} header: {
@@ -249,7 +250,7 @@ struct MigrateFromDevice: View {
}
}
let ratio = Float(uploadedBytes) / Float(totalBytes)
MigrateFromDevice.largeProgressView(ratio, "\(Int(ratio * 100))%", "\(ByteCountFormatter.string(fromByteCount: uploadedBytes, countStyle: .binary)) uploaded")
MigrateFromDevice.largeProgressView(ratio, "\(Int(ratio * 100))%", "\(ByteCountFormatter.string(fromByteCount: uploadedBytes, countStyle: .binary)) uploaded", theme.colors.primary)
}
.onAppear {
startUploading(totalBytes, archivePath)
@@ -263,7 +264,7 @@ struct MigrateFromDevice: View {
migrationState = .uploadProgress(uploadedBytes: 0, totalBytes: totalBytes, fileId: 0, archivePath: archivePath, ctrl: nil)
}) {
settingsRow("tray.and.arrow.up") {
Text("Repeat upload").foregroundColor(.accentColor)
Text("Repeat upload").foregroundColor(theme.colors.primary)
}
}
} header: {
@@ -299,7 +300,7 @@ struct MigrateFromDevice: View {
}
Button(action: { finishMigration(fileId, ctrl) }) {
settingsRow("checkmark") {
Text("Finalize migration").foregroundColor(.accentColor)
Text("Finalize migration").foregroundColor(theme.colors.primary)
}
}
} footer: {
@@ -340,7 +341,7 @@ struct MigrateFromDevice: View {
}
Button(action: { alert = .deleteChat() }) {
settingsRow("trash.fill") {
Text("Delete database from this device").foregroundColor(.accentColor)
Text("Delete database from this device").foregroundColor(theme.colors.primary)
}
}
} header: {
@@ -379,7 +380,7 @@ struct MigrateFromDevice: View {
.truncationMode(.middle)
}
static func largeProgressView(_ value: Float, _ title: String, _ description: LocalizedStringKey) -> some View {
static func largeProgressView(_ value: Float, _ title: String, _ description: LocalizedStringKey, _ primaryColor: Color) -> some View {
ZStack {
VStack {
Text(description)
@@ -389,7 +390,7 @@ struct MigrateFromDevice: View {
Text(title)
.font(.system(size: 54))
.bold()
.foregroundColor(.accentColor)
.foregroundColor(primaryColor)
Text(description)
.font(.title3)
@@ -398,7 +399,7 @@ struct MigrateFromDevice: View {
Circle()
.trim(from: 0, to: CGFloat(value))
.stroke(
Color.accentColor,
primaryColor,
style: StrokeStyle(lineWidth: 27)
)
.rotationEffect(.degrees(180))
@@ -91,6 +91,7 @@ private enum MigrateToDeviceViewAlert: Identifiable {
struct MigrateToDevice: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
@Environment(\.dismiss) var dismiss: DismissAction
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
@Binding var migrationState: MigrationToState?
@@ -243,7 +244,7 @@ struct MigrateToDevice: View {
}
}
let ratio = Float(downloadedBytes) / Float(max(totalBytes, 1))
MigrateFromDevice.largeProgressView(ratio, "\(Int(ratio * 100))%", "\(ByteCountFormatter.string(fromByteCount: downloadedBytes, countStyle: .binary)) downloaded")
MigrateFromDevice.largeProgressView(ratio, "\(Int(ratio * 100))%", "\(ByteCountFormatter.string(fromByteCount: downloadedBytes, countStyle: .binary)) downloaded", theme.colors.primary)
}
}
@@ -255,7 +256,7 @@ struct MigrateToDevice: View {
migrationState = .linkDownloading(link: link)
}) {
settingsRow("tray.and.arrow.down") {
Text("Repeat download").foregroundColor(.accentColor)
Text("Repeat download").foregroundColor(theme.colors.primary)
}
}
} header: {
@@ -293,7 +294,7 @@ struct MigrateToDevice: View {
migrationState = .archiveImport(archivePath: archivePath)
}) {
settingsRow("square.and.arrow.down") {
Text("Repeat import").foregroundColor(.accentColor)
Text("Repeat import").foregroundColor(theme.colors.primary)
}
}
} header: {
@@ -334,7 +335,7 @@ struct MigrateToDevice: View {
migrationState = .migration(passphrase: passphrase, confirmation: confirmation, useKeychain: useKeychain)
}) {
settingsRow("square.and.arrow.down") {
Text(button).foregroundColor(.accentColor)
Text(button).foregroundColor(theme.colors.primary)
}
}
} else {
@@ -364,6 +365,7 @@ struct MigrateToDevice: View {
}
struct OnionView: View {
@EnvironmentObject var theme: AppTheme
@State var appSettings: AppSettings
@State private var onionHosts: OnionHosts = .no
var finishMigration: (AppSettings) -> Void
@@ -381,7 +383,7 @@ struct MigrateToDevice: View {
finishMigration(appSettings)
}) {
settingsRow("checkmark") {
Text("Apply").foregroundColor(.accentColor)
Text("Apply").foregroundColor(theme.colors.primary)
}
}
} header: {
@@ -475,6 +477,7 @@ struct MigrateToDevice: View {
chatInitControllerRemovingDatabases()
}
try await apiDeleteStorage()
try? FileManager.default.createDirectory(at: getWallpaperDirectory(), withIntermediateDirectories: true)
do {
let config = ArchiveConfig(archivePath: archivePath)
let archiveErrors = try await apiImportArchive(config: config)
@@ -30,6 +30,7 @@ struct AddContactLearnMore: View {
}
.listRowBackground(Color.clear)
}
.modifier(ThemedBackground())
}
}
@@ -11,6 +11,7 @@ import SimpleXChat
struct AddGroupView: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
@Environment(\.dismiss) var dismiss: DismissAction
@AppStorage(GROUP_DEFAULT_INCOGNITO, store: groupDefaults) private var incognitoDefault = false
@State private var chat: Chat?
@@ -95,7 +96,7 @@ struct AddGroupView: View {
Section {
groupNameTextField()
Button(action: createGroup) {
settingsRow("checkmark", color: .accentColor) { Text("Create group") }
settingsRow("checkmark", color: theme.colors.primary) { Text("Create group") }
}
.disabled(!canCreateProfile())
IncognitoToggle(incognitoEnabled: $incognitoDefault)
@@ -144,6 +145,7 @@ struct AddGroupView: View {
profile.image = nil
}
}
.modifier(ThemedBackground())
}
func groupNameTextField() -> some View {
@@ -94,7 +94,7 @@ struct NewChatView: View {
.background(
// Rectangle is needed for swipe gesture to work on mostly empty views (creatingLinkProgressView and retryButton)
Rectangle()
.fill(Color(uiColor: .systemGroupedBackground))
.fill(AppTheme.shared.colors.background)
)
.animation(.easeInOut(duration: 0.3333), value: selection)
.gesture(DragGesture(minimumDistance: 20.0, coordinateSpace: .local)
@@ -113,7 +113,7 @@ struct NewChatView: View {
}
)
}
.background(Color(.systemGroupedBackground))
.modifier(ThemedBackground())
.onChange(of: invitationUsed) { used in
if used && !(m.showingInvitation?.connChatUsed ?? true) {
m.markShowingInvitationUsed()
@@ -367,11 +367,12 @@ struct ScannerInView: View {
@Binding var showQRCodeScanner: Bool
let processQRCode: (_ resp: Result<ScanResult, ScanError>) -> Void
@State private var cameraAuthorizationStatus: AVAuthorizationStatus?
var scanMode: ScanMode = .continuous
var body: some View {
Group {
if showQRCodeScanner, case .authorized = cameraAuthorizationStatus {
CodeScannerView(codeTypes: [.qr], scanMode: .continuous, completion: processQRCode)
CodeScannerView(codeTypes: [.qr], scanMode: scanMode, completion: processQRCode)
.aspectRatio(1, contentMode: .fit)
.cornerRadius(12)
.listRowBackground(Color.clear)
@@ -436,6 +437,7 @@ struct ScannerInView: View {
}
}
private func linkTextView(_ link: String) -> some View {
Text(link)
.lineLimit(1)
@@ -486,6 +488,7 @@ func strHasSingleSimplexLink(_ str: String) -> FormattedText? {
}
struct IncognitoToggle: View {
@EnvironmentObject var theme: AppTheme
@Binding var incognitoEnabled: Bool
@State private var showIncognitoSheet = false
@@ -499,7 +502,7 @@ struct IncognitoToggle: View {
HStack(spacing: 6) {
Text("Incognito")
Image(systemName: "info.circle")
.foregroundColor(.accentColor)
.foregroundColor(theme.colors.primary)
.font(.system(size: 14))
}
.onTapGesture {
@@ -66,6 +66,7 @@ struct CreateProfile: View {
}
}
.navigationTitle("Create your profile")
.modifier(ThemedBackground())
.alert(item: $alert) { a in userProfileAlert(a, $displayName) }
.onAppear() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
@@ -78,6 +79,7 @@ struct CreateProfile: View {
struct CreateFirstProfile: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
@Environment(\.dismiss) var dismiss
@State private var displayName: String = ""
@FocusState private var focusDisplayName
@@ -89,9 +91,9 @@ struct CreateFirstProfile: View {
.font(.largeTitle)
.bold()
Text("Your profile, contacts and delivered messages are stored on your device.")
.foregroundColor(.secondary)
.foregroundColor(theme.colors.secondary)
Text("The profile is only shared with your contacts.")
.foregroundColor(.secondary)
.foregroundColor(theme.colors.secondary)
.padding(.bottom)
}
.padding(.bottom)
@@ -44,6 +44,7 @@ struct HowItWorks: View {
.lineLimit(10)
.padding()
.frame(maxHeight: .infinity, alignment: .top)
.modifier(ThemedBackground())
}
}
@@ -78,6 +78,7 @@ struct SetNotificationsMode: View {
}
struct NtfModeSelector: View {
@EnvironmentObject var theme: AppTheme
var mode: NotificationsMode
@Binding var selection: NotificationsMode
@State private var tapped = false
@@ -87,7 +88,7 @@ struct NtfModeSelector: View {
VStack(alignment: .leading, spacing: 4) {
Text(mode.label)
.font(.headline)
.foregroundColor(selection == mode ? .accentColor : .secondary)
.foregroundColor(selection == mode ? theme.colors.primary : theme.colors.secondary)
Text(ntfModeDescription(mode))
.lineLimit(10)
.font(.subheadline)
@@ -95,11 +96,11 @@ struct NtfModeSelector: View {
.padding(12)
}
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color(uiColor: tapped ? .secondarySystemFill : .systemBackground))
.background(tapped ? Color(uiColor: .secondarySystemFill) : theme.colors.background)
.clipShape(RoundedRectangle(cornerRadius: 18))
.overlay(
RoundedRectangle(cornerRadius: 18)
.stroke(selection == mode ? Color.accentColor : Color(uiColor: .secondarySystemFill), lineWidth: 2)
.stroke(selection == mode ? theme.colors.primary : Color(uiColor: .secondarySystemFill), lineWidth: 2)
)
._onButtonGesture { down in
tapped = down
@@ -79,7 +79,7 @@ struct SimpleXInfo: View {
MigrateToDevice(migrationState: $m.migrationState)
}
.navigationTitle("Migrate here")
.background(colorScheme == .light ? Color(uiColor: .tertiarySystemGroupedBackground) : .clear)
.modifier(ThemedBackground())
}
}
.sheet(isPresented: $showHowItWorks) {
@@ -406,7 +406,28 @@ private let versionDescriptions: [VersionDescription] = [
description: "More reliable network connection."
)
]
)
),
VersionDescription(
version: "v5.8",
post: URL(string: "https://simplex.chat/blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.html"),
features: [
FeatureDescription(
icon: "arrow.forward",
title: "Private message routing 🚀",
description: "Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings."
),
FeatureDescription(
icon: "network.badge.shield.half.filled",
title: "Safely receive files",
description: "Confirm files from unknown servers."
),
FeatureDescription(
icon: "battery.50",
title: "Improved message delivery",
description: "With reduced battery usage."
)
]
),
]
private let lastVersion = versionDescriptions.last!.version
@@ -178,26 +178,32 @@ struct ConnectDesktopView: View {
}
}
.navigationTitle("Connect to desktop")
.modifier(ThemedBackground())
}
private func connectingDesktopView(_ session: RemoteCtrlSession, _ rc: RemoteCtrlInfo?) -> some View {
List {
Section("Connecting to desktop") {
ctrlDeviceNameText(session, rc)
ctrlDeviceVersionText(session)
}
ZStack {
List {
Section("Connecting to desktop") {
ctrlDeviceNameText(session, rc)
ctrlDeviceVersionText(session)
}
if let sessCode = session.sessionCode {
Section("Session code") {
sessionCodeText(sessCode)
if let sessCode = session.sessionCode {
Section("Session code") {
sessionCodeText(sessCode)
}
}
Section {
disconnectButton()
}
}
.navigationTitle("Connecting to desktop")
Section {
disconnectButton()
}
ProgressView().scaleEffect(2)
}
.navigationTitle("Connecting to desktop")
.modifier(ThemedBackground())
}
private func searchingDesktopView() -> some View {
@@ -215,6 +221,7 @@ struct ConnectDesktopView: View {
}
}
.navigationTitle("Connecting to desktop")
.modifier(ThemedBackground())
}
@ViewBuilder private func foundDesktopView(_ session: RemoteCtrlSession, _ rc: RemoteCtrlInfo, _ compatible: Bool) -> some View {
@@ -242,6 +249,7 @@ struct ConnectDesktopView: View {
}
}
.navigationTitle("Found desktop")
.modifier(ThemedBackground())
if compatible && connectRemoteViaMulticastAuto {
v.onAppear { confirmKnownDesktop(rc) }
@@ -271,6 +279,7 @@ struct ConnectDesktopView: View {
}
}
.navigationTitle("Verify connection")
.modifier(ThemedBackground())
}
private func ctrlDeviceNameText(_ session: RemoteCtrlSession, _ rc: RemoteCtrlInfo?) -> Text {
@@ -311,6 +320,7 @@ struct ConnectDesktopView: View {
}
}
.navigationTitle("Connected to desktop")
.modifier(ThemedBackground())
}
private func sessionCodeText(_ code: String) -> some View {
@@ -332,13 +342,7 @@ struct ConnectDesktopView: View {
private func scanDesctopAddressView() -> some View {
Section("Scan QR code from desktop") {
CodeScannerView(codeTypes: [.qr], scanMode: .oncePerCode, completion: processDesktopQRCode)
.aspectRatio(1, contentMode: .fit)
.cornerRadius(12)
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
.padding(.horizontal)
ScannerInView(showQRCodeScanner: $showQRCodeScanner, processQRCode: processDesktopQRCode, scanMode: .oncePerCode)
}
}
@@ -391,6 +395,7 @@ struct ConnectDesktopView: View {
}
}
.navigationTitle("Linked desktops")
.modifier(ThemedBackground())
}
private func remoteCtrlView(_ rc: RemoteCtrlInfo) -> some View {
+2
View File
@@ -106,6 +106,7 @@ struct TerminalView: View {
}
.navigationViewStyle(.stack)
.navigationTitle("Chat console")
.modifier(ThemedBackground())
}
func scrollToBottom(_ proxy: ScrollViewProxy, animation: Animation = .default) {
@@ -130,6 +131,7 @@ struct TerminalView: View {
}
}
.onDisappear { terminalItem = nil }
.modifier(ThemedBackground())
}
func consoleSendMessage() {
@@ -44,6 +44,11 @@ extension AppSettings {
if let val = androidCallOnLockScreen { def.setValue(val.rawValue, forKey: ANDROID_DEFAULT_CALL_ON_LOCK_SCREEN) }
if let val = iosCallKitEnabled { callKitEnabledGroupDefault.set(val) }
if let val = iosCallKitCallsInRecents { def.setValue(val, forKey: DEFAULT_CALL_KIT_CALLS_IN_RECENTS) }
if let val = uiProfileImageCornerRadius { def.setValue(val, forKey: DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) }
if let val = uiColorScheme { def.setValue(val, forKey: DEFAULT_CURRENT_THEME) }
if let val = uiDarkColorScheme { def.setValue(val, forKey: DEFAULT_SYSTEM_DARK_THEME) }
if let val = uiCurrentThemeIds { def.setValue(val, forKey: DEFAULT_CURRENT_THEME_IDS) }
if let val = uiThemes { def.setValue(val.skipDuplicates(), forKey: DEFAULT_THEME_OVERRIDES) }
}
public static var current: AppSettings {
@@ -69,6 +74,11 @@ extension AppSettings {
c.androidCallOnLockScreen = AppSettingsLockScreenCalls(rawValue: def.string(forKey: ANDROID_DEFAULT_CALL_ON_LOCK_SCREEN)!)
c.iosCallKitEnabled = callKitEnabledGroupDefault.get()
c.iosCallKitCallsInRecents = def.bool(forKey: DEFAULT_CALL_KIT_CALLS_IN_RECENTS)
c.uiProfileImageCornerRadius = def.float(forKey: DEFAULT_PROFILE_IMAGE_CORNER_RADIUS)
c.uiColorScheme = currentThemeDefault.get()
c.uiDarkColorScheme = systemDarkThemeDefault.get()
c.uiCurrentThemeIds = currentThemeIdsDefault.get()
c.uiThemes = themeOverridesDefault.get()
return c
}
}
@@ -7,12 +7,17 @@
//
import SwiftUI
import SimpleXChat
let defaultAccentColor = CGColor.init(red: 0, green: 0.533, blue: 1, alpha: 1)
let interfaceStyles: [UIUserInterfaceStyle] = [.unspecified, .light, .dark]
//let interfaceStyles: [UIUserInterfaceStyle] = [.unspecified, .light, .dark]
let interfaceStyleNames: [LocalizedStringKey] = ["System", "Light", "Dark"]
let colorModesLocalized: [LocalizedStringKey] = ["System", "Light", "Dark"]
let colorModesNames: [DefaultThemeMode?] = [nil, DefaultThemeMode.light, DefaultThemeMode.dark]
let darkThemesLocalized: [LocalizedStringKey] = ["Dark", "SimpleX", "Black"]
let darkThemesNames: [String] = [DefaultTheme.DARK.themeName, DefaultTheme.SIMPLEX.themeName, DefaultTheme.BLACK.themeName]
let appSettingsURL = URL(string: UIApplication.openSettingsURLString)!
@@ -22,7 +27,11 @@ struct AppearanceSettings: View {
@EnvironmentObject var sceneDelegate: SceneDelegate
@State private var iconLightTapped = false
@State private var iconDarkTapped = false
@State private var userInterfaceStyle = getUserInterfaceStyleDefault()
//@State private var userInterfaceStyle = getUserInterfaceStyleDefault()
@State private var colorMode: DefaultThemeMode? = {
if currentThemeDefault.get() == DefaultTheme.SYSTEM_THEME_NAME { nil as DefaultThemeMode? } else { CurrentColors.base.mode }
}()
@State private var darkModeTheme: String = UserDefaults.standard.string(forKey: DEFAULT_SYSTEM_DARK_THEME) ?? DefaultTheme.SIMPLEX.themeName
@State private var uiTintColor = getUIAccentColorDefault()
@AppStorage(DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) private var profileImageCornerRadius = defaultProfileImageCorner
@@ -65,9 +74,15 @@ struct AppearanceSettings: View {
}
Section {
Picker("Theme", selection: $userInterfaceStyle) {
ForEach(interfaceStyles, id: \.self) { style in
Text(interfaceStyleNames[interfaceStyles.firstIndex(of: style) ?? 0])
Picker("Color mode", selection: $colorMode) {
ForEach(Array(colorModesNames.enumerated()), id: \.element) { index, mode in
Text(colorModesLocalized[index])
}
}
.frame(height: 36)
Picker("Dark mode colors", selection: $darkModeTheme) {
ForEach(Array(darkThemesNames.enumerated()), id: \.element) { index, darkTheme in
Text(darkThemesLocalized[index])
}
}
.frame(height: 36)
@@ -82,9 +97,24 @@ struct AppearanceSettings: View {
Text("Reset colors").font(.callout)
}
}
.onChange(of: userInterfaceStyle) { _ in
sceneDelegate.window?.overrideUserInterfaceStyle = userInterfaceStyle
setUserInterfaceStyleDefault(userInterfaceStyle)
.onChange(of: colorMode) { mode in
guard let mode else {
ThemeManager.applyTheme(DefaultTheme.SYSTEM_THEME_NAME)
return
}
if case DefaultThemeMode.light = mode {
ThemeManager.applyTheme(DefaultTheme.LIGHT.themeName)
} else if case DefaultThemeMode.dark = mode {
ThemeManager.applyTheme(systemDarkThemeDefault.get())
}
}
.onChange(of: darkModeTheme) { darkTheme in
ThemeManager.changeDarkTheme(darkTheme)
if currentThemeDefault.get() == DefaultTheme.SYSTEM_THEME_NAME {
ThemeManager.applyTheme(currentThemeDefault.get())
} else if currentThemeDefault.get() != DefaultTheme.LIGHT.themeName {
ThemeManager.applyTheme(systemDarkThemeDefault.get())
}
}
.onChange(of: uiTintColor) { _ in
sceneDelegate.window?.tintColor = UIColor(cgColor: uiTintColor)
@@ -136,24 +166,24 @@ func setUIAccentColorDefault(_ color: CGColor) {
}
}
func getUserInterfaceStyleDefault() -> UIUserInterfaceStyle {
switch UserDefaults.standard.integer(forKey: DEFAULT_USER_INTERFACE_STYLE) {
case 1: return .light
case 2: return .dark
default: return .unspecified
}
}
func setUserInterfaceStyleDefault(_ style: UIUserInterfaceStyle) {
var v: Int
switch style {
case .unspecified: v = 0
case .light: v = 1
case .dark: v = 2
default: v = 0
}
UserDefaults.standard.set(v, forKey: DEFAULT_USER_INTERFACE_STYLE)
}
//func getUserInterfaceStyleDefault() -> UIUserInterfaceStyle {
// switch UserDefaults.standard.integer(forKey: DEFAULT_USER_INTERFACE_STYLE) {
// case 1: return .light
// case 2: return .dark
// default: return .unspecified
// }
//}
//
//func setUserInterfaceStyleDefault(_ style: UIUserInterfaceStyle) {
// var v: Int
// switch style {
// case .unspecified: v = 0
// case .light: v = 1
// case .dark: v = 2
// default: v = 0
// }
// UserDefaults.standard.set(v, forKey: DEFAULT_USER_INTERFACE_STYLE)
//}
struct AppearanceSettings_Previews: PreviewProvider {
static var previews: some View {
@@ -22,6 +22,7 @@ struct CallSettings: View {
NavigationLink {
RTCServers()
.navigationTitle("Your ICE servers")
.modifier(ThemedBackground())
} label: {
Text("WebRTC ICE servers")
}
@@ -70,6 +70,7 @@ struct HiddenProfileView: View {
message: Text(savePasswordError ?? "")
)
}
.modifier(ThemedBackground())
}
var passwordValid: Bool { hidePassword == hidePassword.trimmingCharacters(in: .whitespaces) }
@@ -27,6 +27,7 @@ struct IncognitoHelp: View {
}
.listRowBackground(Color.clear)
}
.modifier(ThemedBackground())
}
}
@@ -47,6 +47,7 @@ struct NetworkAndServers: View {
NavigationLink {
ProtocolServersView(serverProtocol: .smp)
.navigationTitle("Your SMP servers")
.modifier(ThemedBackground())
} label: {
Text("SMP servers")
}
@@ -54,6 +55,7 @@ struct NetworkAndServers: View {
NavigationLink {
ProtocolServersView(serverProtocol: .xftp)
.navigationTitle("Your XFTP servers")
.modifier(ThemedBackground())
} label: {
Text("XFTP servers")
}
@@ -73,6 +75,7 @@ struct NetworkAndServers: View {
NavigationLink {
AdvancedNetworkSettings()
.navigationTitle("Network settings")
.modifier(ThemedBackground())
} label: {
Text("Advanced network settings")
}
@@ -82,34 +85,35 @@ struct NetworkAndServers: View {
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 {
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") {
NavigationLink {
RTCServers()
.navigationTitle("Your ICE servers")
.modifier(ThemedBackground())
} label: {
Text("WebRTC ICE servers")
}
@@ -33,6 +33,7 @@ struct NotificationsView: View {
}
}
.navigationTitle("Send notifications")
.modifier(ThemedBackground())
.navigationBarTitleDisplayMode(.inline)
.alert(item: $showAlert) { alert in
if let token = m.deviceToken {
@@ -68,6 +69,7 @@ struct NotificationsView: View {
}
}
.navigationTitle("Show preview")
.modifier(ThemedBackground())
.navigationBarTitleDisplayMode(.inline)
} label: {
HStack {
@@ -172,6 +174,7 @@ func ntfModeDescription(_ mode: NotificationsMode) -> LocalizedStringKey {
}
struct SelectionListView<Item: SelectableItem>: View {
@EnvironmentObject var theme: AppTheme
var list: [Item]
@Binding var selection: Item
var onSelection: ((Item) -> Void)?
@@ -185,11 +188,11 @@ struct SelectionListView<Item: SelectableItem>: View {
if selection == item {
Image(systemName: "checkmark")
.resizable().scaledToFit().frame(width: 16)
.foregroundColor(.accentColor)
.foregroundColor(theme.colors.primary)
}
}
.contentShape(Rectangle())
.listRowBackground(Color(uiColor: tapped == item ? .secondarySystemFill : .systemBackground))
.listRowBackground(tapped == item ? Color(uiColor: .secondarySystemFill) : theme.colors.background)
.onTapGesture {
if selection == item { return }
if let f = onSelection {
@@ -48,6 +48,7 @@ struct PrivacySettings: View {
NavigationLink {
SimplexLockView(prefPerformLA: $prefPerformLA, currentLAMode: $currentLAMode)
.navigationTitle("SimpleX Lock")
.modifier(ThemedBackground())
} label: {
if prefPerformLA {
settingsRow("lock.fill", color: .green) {
@@ -332,6 +333,7 @@ struct SimplexLockView: View {
@Binding var prefPerformLA: Bool
@Binding var currentLAMode: LAMode
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
@AppStorage(DEFAULT_LA_NOTICE_SHOWN) private var prefLANoticeShown = false
@State private var laMode: LAMode = privacyLocalAuthModeDefault.get()
@AppStorage(DEFAULT_LA_LOCK_DELAY) private var laLockDelay = 30
@@ -416,7 +418,7 @@ struct SimplexLockView: View {
HStack(spacing: 6) {
Text("Enable self-destruct")
Image(systemName: "info.circle")
.foregroundColor(.accentColor)
.foregroundColor(theme.colors.primary)
.font(.system(size: 14))
}
.onTapGesture {
@@ -94,6 +94,7 @@ struct ProtocolServersView: View {
}
.sheet(isPresented: $showScanProtoServer) {
ScanProtocolServer(servers: $servers)
.modifier(ThemedBackground())
}
.modifier(BackButton(disabled: Binding.constant(false)) {
if saveDisabled {
@@ -171,6 +172,7 @@ struct ProtocolServersView: View {
serverToEdit: srv
)
.navigationBarTitle(srv.preset ? "Preset server" : "Your server")
.modifier(ThemedBackground())
.navigationBarTitleDisplayMode(.large)
} label: {
let address = parseServerAddress(srv.server)
@@ -89,7 +89,7 @@ struct SetDeliveryReceiptsView: View {
.padding()
.padding(.horizontal)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color(uiColor: .systemBackground))
.background(AppTheme.shared.colors.background)
}
}
@@ -44,7 +44,7 @@ let DEFAULT_ENCRYPTION_STARTED_AT = "encryptionStartedAt"
let DEFAULT_ACCENT_COLOR_RED = "accentColorRed"
let DEFAULT_ACCENT_COLOR_GREEN = "accentColorGreen"
let DEFAULT_ACCENT_COLOR_BLUE = "accentColorBlue"
let DEFAULT_USER_INTERFACE_STYLE = "userInterfaceStyle"
//let DEFAULT_USER_INTERFACE_STYLE = "userInterfaceStyle"
let DEFAULT_PROFILE_IMAGE_CORNER_RADIUS = "profileImageCornerRadius"
let DEFAULT_CONNECT_VIA_LINK_TAB = "connectViaLinkTab"
let DEFAULT_LIVE_MESSAGE_ALERT_SHOWN = "liveMessageAlertShown"
@@ -62,6 +62,11 @@ let DEFAULT_CONNECT_REMOTE_VIA_MULTICAST = "connectRemoteViaMulticast"
let DEFAULT_CONNECT_REMOTE_VIA_MULTICAST_AUTO = "connectRemoteViaMulticastAuto"
let DEFAULT_SHOW_SENT_VIA_RPOXY = "showSentViaProxy"
let DEFAULT_CURRENT_THEME = "currentTheme"
let DEFAULT_SYSTEM_DARK_THEME = "systemDarkTheme"
let DEFAULT_CURRENT_THEME_IDS = "currentThemeIds"
let DEFAULT_THEME_OVERRIDES = "themeOverrides"
let ANDROID_DEFAULT_CALL_ON_LOCK_SCREEN = "androidCallOnLockScreen"
let appDefaults: [String: Any] = [
@@ -88,7 +93,7 @@ let appDefaults: [String: Any] = [
DEFAULT_ACCENT_COLOR_RED: 0.000,
DEFAULT_ACCENT_COLOR_GREEN: 0.533,
DEFAULT_ACCENT_COLOR_BLUE: 1.000,
DEFAULT_USER_INTERFACE_STYLE: 0,
//DEFAULT_USER_INTERFACE_STYLE: 0,
DEFAULT_PROFILE_IMAGE_CORNER_RADIUS: defaultProfileImageCorner,
DEFAULT_CONNECT_VIA_LINK_TAB: ConnectViaLinkTab.scan.rawValue,
DEFAULT_LIVE_MESSAGE_ALERT_SHOWN: false,
@@ -101,7 +106,12 @@ let appDefaults: [String: Any] = [
DEFAULT_CONNECT_REMOTE_VIA_MULTICAST: 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,
DEFAULT_THEME_OVERRIDES: "{}",
DEFAULT_CURRENT_THEME: DefaultTheme.SYSTEM_THEME_NAME,
DEFAULT_SYSTEM_DARK_THEME: DefaultTheme.SIMPLEX.themeName,
DEFAULT_CURRENT_THEME_IDS: "{}"
]
// not used anymore
@@ -148,14 +158,66 @@ let onboardingStageDefault = EnumDefault<OnboardingStage>(defaults: UserDefaults
let customDisappearingMessageTimeDefault = IntDefault(defaults: UserDefaults.standard, forKey: DEFAULT_CUSTOM_DISAPPEARING_MESSAGE_TIME)
let currentThemeDefault = StringDefault(defaults: UserDefaults.standard, forKey: DEFAULT_CURRENT_THEME, withDefault: DefaultTheme.SYSTEM_THEME_NAME)
let systemDarkThemeDefault = StringDefault(defaults: UserDefaults.standard, forKey: DEFAULT_SYSTEM_DARK_THEME, withDefault: DefaultTheme.SIMPLEX.themeName)
let currentThemeIdsDefault = CodableDefault<[String: String]>(defaults: UserDefaults.standard, forKey: DEFAULT_CURRENT_THEME_IDS, withDefault: [:] )
let themeOverridesDefault: CodableDefault<[ThemeOverrides]> = CodableDefault(defaults: UserDefaults.standard, forKey: DEFAULT_THEME_OVERRIDES, withDefault: [])
func setGroupDefaults() {
privacyAcceptImagesGroupDefault.set(UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_ACCEPT_IMAGES))
}
public class StringDefault {
var defaults: UserDefaults
var key: String
var defaultValue: String
public init(defaults: UserDefaults = UserDefaults.standard, forKey: String, withDefault: String) {
self.defaults = defaults
self.key = forKey
self.defaultValue = withDefault
}
public func get() -> String {
defaults.string(forKey: key) ?? defaultValue
}
public func set(_ value: String) {
defaults.set(value, forKey: key)
defaults.synchronize()
}
}
public class CodableDefault<T: Codable> {
var defaults: UserDefaults
var key: String
var defaultValue: T
public init(defaults: UserDefaults = UserDefaults.standard, forKey: String, withDefault: T) {
self.defaults = defaults
self.key = forKey
self.defaultValue = withDefault
}
public func get() -> T {
if let value = defaults.string(forKey: key) {
return decodeJSON(value) ?? defaultValue
}
return defaultValue
}
public func set(_ value: T) {
defaults.set(encodeJSON(value), forKey: key)
defaults.synchronize()
}
}
struct SettingsView: View {
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var chatModel: ChatModel
@EnvironmentObject var sceneDelegate: SceneDelegate
@EnvironmentObject var theme: AppTheme
@Binding var showSettings: Bool
@State private var showProgress: Bool = false
@@ -180,6 +242,7 @@ struct SettingsView: View {
NavigationLink {
UserProfile()
.navigationTitle("Your current profile")
.modifier(ThemedBackground())
} label: {
ProfilePreview(profileOf: user)
.padding(.leading, -8)
@@ -197,6 +260,7 @@ struct SettingsView: View {
NavigationLink {
UserAddressView(shareViaProfile: user.addressShared)
.navigationTitle("SimpleX address")
.modifier(ThemedBackground())
.navigationBarTitleDisplayMode(.large)
} label: {
settingsRow("qrcode") { Text("Your SimpleX address") }
@@ -205,6 +269,7 @@ struct SettingsView: View {
NavigationLink {
PreferencesView(profile: user.profile, preferences: user.fullPreferences, currentPreferences: user.fullPreferences)
.navigationTitle("Your preferences")
.modifier(ThemedBackground())
} label: {
settingsRow("switch.2") { Text("Chat preferences") }
}
@@ -219,6 +284,7 @@ struct SettingsView: View {
NavigationLink {
MigrateFromDevice(showSettings: $showSettings, showProgressOnSettings: $showProgress)
.navigationTitle("Migrate device")
.modifier(ThemedBackground())
.navigationBarTitleDisplayMode(.large)
} label: {
settingsRow("tray.and.arrow.up") { Text("Migrate to another device") }
@@ -230,6 +296,7 @@ struct SettingsView: View {
NavigationLink {
NotificationsView()
.navigationTitle("Notifications")
.modifier(ThemedBackground())
} label: {
HStack {
notificationsIcon()
@@ -241,6 +308,7 @@ struct SettingsView: View {
NavigationLink {
NetworkAndServers()
.navigationTitle("Network & servers")
.modifier(ThemedBackground())
} label: {
settingsRow("externaldrive.connected.to.line.below") { Text("Network & servers") }
}
@@ -249,6 +317,7 @@ struct SettingsView: View {
NavigationLink {
CallSettings()
.navigationTitle("Your calls")
.modifier(ThemedBackground())
} label: {
settingsRow("video") { Text("Audio & video calls") }
}
@@ -257,6 +326,7 @@ struct SettingsView: View {
NavigationLink {
PrivacySettings()
.navigationTitle("Your privacy")
.modifier(ThemedBackground())
} label: {
settingsRow("lock") { Text("Privacy & security") }
}
@@ -266,6 +336,7 @@ struct SettingsView: View {
NavigationLink {
AppearanceSettings()
.navigationTitle("Appearance")
.modifier(ThemedBackground())
} label: {
settingsRow("sun.max") { Text("Appearance") }
}
@@ -280,6 +351,7 @@ struct SettingsView: View {
NavigationLink {
ChatHelp(showSettings: $showSettings)
.navigationTitle("Welcome \(user.displayName)!")
.modifier(ThemedBackground())
.frame(maxHeight: .infinity, alignment: .top)
} label: {
settingsRow("questionmark") { Text("How to use it") }
@@ -287,6 +359,7 @@ struct SettingsView: View {
}
NavigationLink {
WhatsNewView(viaSettings: true)
.modifier(ThemedBackground())
.navigationBarTitleDisplayMode(.inline)
} label: {
settingsRow("plus") { Text("What's new") }
@@ -294,6 +367,7 @@ struct SettingsView: View {
NavigationLink {
SimpleXInfo(onboarding: false)
.navigationBarTitle("", displayMode: .inline)
.modifier(ThemedBackground())
.frame(maxHeight: .infinity, alignment: .top)
} label: {
settingsRow("info") { Text("About SimpleX Chat") }
@@ -333,18 +407,21 @@ struct SettingsView: View {
NavigationLink {
DeveloperView()
.navigationTitle("Developer tools")
.modifier(ThemedBackground())
} label: {
settingsRow("chevron.left.forwardslash.chevron.right") { Text("Developer tools") }
}
NavigationLink {
VersionView()
.navigationBarTitle("App version")
.modifier(ThemedBackground())
} label: {
Text("v\(appVersion ?? "?") (\(appBuild ?? "?"))")
}
}
}
.navigationTitle("Your settings")
.modifier(ThemedBackground())
}
.onDisappear {
chatModel.showingTerminal = false
@@ -356,6 +433,7 @@ struct SettingsView: View {
NavigationLink {
DatabaseView(showSettings: $showSettings, chatItemTTL: chatModel.chatItemTTL)
.navigationTitle("Your chat database")
.modifier(ThemedBackground())
} label: {
let color: Color = chatModel.chatDbEncrypted == false ? .orange : .secondary
settingsRow("internaldrive", color: color) {
@@ -300,6 +300,7 @@ struct UserAddressView: View {
NavigationLink {
UserAddressLearnMore()
.navigationTitle("SimpleX address")
.modifier(ThemedBackground())
.navigationBarTitleDisplayMode(.large)
} label: {
settingsRow("info.circle") {
@@ -8,6 +8,7 @@ import SimpleXChat
struct UserProfilesView: View {
@EnvironmentObject private var m: ChatModel
@EnvironmentObject private var theme: AppTheme
@Binding var showSettings: Bool
@Environment(\.editMode) private var editMode
@AppStorage(DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE) private var showHiddenProfilesNotice = true
@@ -111,6 +112,7 @@ struct UserProfilesView: View {
}
}
.navigationTitle("Your chat profiles")
.modifier(ThemedBackground())
.searchable(text: $searchTextOrPassword, placement: .navigationBarDrawer(displayMode: .always))
.autocorrectionDisabled(true)
.textInputAutocapitalization(.never)
@@ -123,7 +125,7 @@ struct UserProfilesView: View {
deleteModeButton("Profile and server connections", true)
deleteModeButton("Local profile data only", false)
}
.sheet(item: $selectedUser) { user in
.appSheet(item: $selectedUser) { user in
HiddenProfileView(user: user, profileHidden: $profileHidden)
}
.onChange(of: profileHidden) { _ in
@@ -131,7 +133,7 @@ struct UserProfilesView: View {
withAnimation { profileHidden = false }
}
}
.sheet(item: $profileAction) { action in
.appSheet(item: $profileAction) { action in
profileActionView(action)
}
.alert(item: $alert) { alert in
@@ -237,6 +239,7 @@ struct UserProfilesView: View {
}
}
}
.modifier(ThemedBackground())
}
@ViewBuilder func actionHeader(_ title: LocalizedStringKey, _ user: User) -> some View {
@@ -356,7 +359,7 @@ struct UserProfilesView: View {
}
}
}
.tint(.accentColor)
.tint(theme.colors.primary)
}
}
if #available(iOS 16, *) {
@@ -713,6 +713,10 @@
<target>Позволи изчезващи съобщения само ако вашият контакт ги разрешава.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Allow downgrade" xml:space="preserve">
<source>Allow downgrade</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Allow irreversible message deletion only if your contact allows it to you. (24 hours)" xml:space="preserve">
<source>Allow irreversible message deletion only if your contact allows it to you. (24 hours)</source>
<target>Позволи необратимо изтриване на съобщение само ако вашият контакт го рарешава. (24 часа)</target>
@@ -808,6 +812,10 @@
<target>Вече се присъединихте към групата!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Always use private routing." xml:space="preserve">
<source>Always use private routing.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Always use relay" xml:space="preserve">
<source>Always use relay</source>
<target>Винаги използвай реле</target>
@@ -1088,6 +1096,10 @@
<target>Файлът не може да бъде получен</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Capacity exceeded - recipient did not receive previously sent messages." xml:space="preserve">
<source>Capacity exceeded - recipient did not receive previously sent messages.</source>
<note>snd error text</note>
</trans-unit>
<trans-unit id="Cellular" xml:space="preserve">
<source>Cellular</source>
<target>Мобилна мрежа</target>
@@ -1284,6 +1296,10 @@
<target>Потвърди актуализаациите на базата данни</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Confirm files from unknown servers." xml:space="preserve">
<source>Confirm files from unknown servers.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Confirm network settings" xml:space="preserve">
<source>Confirm network settings</source>
<target>Потвърди мрежовите настройки</target>
@@ -1701,6 +1717,10 @@ This is your own one-time link!</source>
<target>Базата данни ще бъде мигрирана, когато приложението се рестартира</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Debug delivery" xml:space="preserve">
<source>Debug delivery</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Decentralized" xml:space="preserve">
<source>Decentralized</source>
<target>Децентрализиран</target>
@@ -1948,6 +1968,10 @@ This cannot be undone!</source>
<target>Настолни устройства</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Destination server error: %@" xml:space="preserve">
<source>Destination server error: %@</source>
<note>snd error text</note>
</trans-unit>
<trans-unit id="Develop" xml:space="preserve">
<source>Develop</source>
<target>Разработване</target>
@@ -2053,11 +2077,19 @@ This cannot be undone!</source>
<target>Откриване през локалната мрежа</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do NOT send messages directly, even if your or destination server does not support private routing." xml:space="preserve">
<source>Do NOT send messages directly, even if your or destination server does not support private routing.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do NOT use SimpleX for emergency calls." xml:space="preserve">
<source>Do NOT use SimpleX for emergency calls.</source>
<target>НЕ използвайте SimpleX за спешни повиквания.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do NOT use private routing." xml:space="preserve">
<source>Do NOT use private routing.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do it later" xml:space="preserve">
<source>Do it later</source>
<target>Отложи</target>
@@ -2621,7 +2653,7 @@ This cannot be undone!</source>
<trans-unit id="Error: %@" xml:space="preserve">
<source>Error: %@</source>
<target>Грешка: %@</target>
<note>No comment provided by engineer.</note>
<note>snd error text</note>
</trans-unit>
<trans-unit id="Error: URL is invalid" xml:space="preserve">
<source>Error: URL is invalid</source>
@@ -2713,6 +2745,10 @@ This cannot be undone!</source>
<target>Файл: %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Files" xml:space="preserve">
<source>Files</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Files &amp; media" xml:space="preserve">
<source>Files &amp; media</source>
<target>Файлове и медия</target>
@@ -2818,6 +2854,16 @@ This cannot be undone!</source>
<target>Препратено от</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarding server: %@&#10;Destination server error: %@" xml:space="preserve">
<source>Forwarding server: %1$@
Destination server error: %2$@</source>
<note>snd error text</note>
</trans-unit>
<trans-unit id="Forwarding server: %@&#10;Error: %@" xml:space="preserve">
<source>Forwarding server: %1$@
Error: %2$@</source>
<note>snd error text</note>
</trans-unit>
<trans-unit id="Found desktop" xml:space="preserve">
<source>Found desktop</source>
<target>Намерено настолно устройство</target>
@@ -3633,11 +3679,19 @@ This is your link for group %@!</source>
<target>Потвърждениe за доставка на съобщения!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message delivery warning" xml:space="preserve">
<source>Message delivery warning</source>
<note>item status text</note>
</trans-unit>
<trans-unit id="Message draft" xml:space="preserve">
<source>Message draft</source>
<target>Чернова на съобщение</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message queue info" xml:space="preserve">
<source>Message queue info</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message reactions" xml:space="preserve">
<source>Message reactions</source>
<target>Реакции на съобщения</target>
@@ -3653,6 +3707,14 @@ This is your link for group %@!</source>
<target>Реакциите на съобщения са забранени в тази група.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message routing fallback" xml:space="preserve">
<source>Message routing fallback</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message routing mode" xml:space="preserve">
<source>Message routing mode</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message source remains private." xml:space="preserve">
<source>Message source remains private.</source>
<target>Източникът на съобщението остава скрит.</target>
@@ -3783,11 +3845,6 @@ This is your link for group %@!</source>
<target>Най-вероятно тази връзка е изтрита.</target>
<note>item status description</note>
</trans-unit>
<trans-unit id="Most likely this contact has deleted the connection with you." xml:space="preserve">
<source>Most likely this contact has deleted the connection with you.</source>
<target>Най-вероятно този контакт е изтрил връзката с вас.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Multiple chat profiles" xml:space="preserve">
<source>Multiple chat profiles</source>
<target>Множество профили за чат</target>
@@ -3818,6 +3875,10 @@ This is your link for group %@!</source>
<target>Мрежова връзка</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Network issues - message expired after many attempts to send it." xml:space="preserve">
<source>Network issues - message expired after many attempts to send it.</source>
<note>snd error text</note>
</trans-unit>
<trans-unit id="Network management" xml:space="preserve">
<source>Network management</source>
<target>Управление на мрежата</target>
@@ -4359,11 +4420,23 @@ Error: %@</source>
<target>Поверителни имена на файлове</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Private message routing" xml:space="preserve">
<source>Private message routing</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Private message routing 🚀" xml:space="preserve">
<source>Private message routing 🚀</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Private notes" xml:space="preserve">
<source>Private notes</source>
<target>Лични бележки</target>
<note>name of notes to self</note>
</trans-unit>
<trans-unit id="Private routing" xml:space="preserve">
<source>Private routing</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Profile and server connections" xml:space="preserve">
<source>Profile and server connections</source>
<target>Профилни и сървърни връзки</target>
@@ -4444,11 +4517,20 @@ Error: %@</source>
<target>Забрани изпращането на гласови съобщения.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Protect IP address" xml:space="preserve">
<source>Protect IP address</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Protect app screen" xml:space="preserve">
<source>Protect app screen</source>
<target>Защити екрана на приложението</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Protect your IP address from the messaging relays chosen by your contacts.&#10;Enable in *Network &amp; servers* settings." xml:space="preserve">
<source>Protect your IP address from the messaging relays chosen by your contacts.
Enable in *Network &amp; servers* settings.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Protect your chat profiles with a password!" xml:space="preserve">
<source>Protect your chat profiles with a password!</source>
<target>Защитете чат профилите с парола!</target>
@@ -4554,11 +4636,6 @@ Error: %@</source>
<target>Получаващият адрес ще бъде променен към друг сървър. Промяната на адреса ще завърши, след като подателят е онлайн.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Receiving concurrency" xml:space="preserve">
<source>Receiving concurrency</source>
<target>Паралелност на получаване</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Receiving file will be stopped." xml:space="preserve">
<source>Receiving file will be stopped.</source>
<target>Получаващият се файл ще бъде спрян.</target>
@@ -4794,6 +4871,10 @@ Error: %@</source>
<target>SMP сървъри</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Safely receive files" xml:space="preserve">
<source>Safely receive files</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Safer groups" xml:space="preserve">
<source>Safer groups</source>
<target>По-безопасни групи</target>
@@ -5019,6 +5100,14 @@ Error: %@</source>
<target>Изпрати съобщение на живо</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Send messages directly when IP address is protected and your or destination server does not support private routing." xml:space="preserve">
<source>Send messages directly when IP address is protected and your or destination server does not support private routing.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Send messages directly when your or destination server does not support private routing." xml:space="preserve">
<source>Send messages directly when your or destination server does not support private routing.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Send notifications" xml:space="preserve">
<source>Send notifications</source>
<target>Изпращай известия</target>
@@ -5124,6 +5213,10 @@ Error: %@</source>
<target>Изпратените съобщения ще бъдат изтрити след зададеното време.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Server address is incompatible with network settings." xml:space="preserve">
<source>Server address is incompatible with network settings.</source>
<note>srv error text.</note>
</trans-unit>
<trans-unit id="Server requires authorization to create queues, check password" xml:space="preserve">
<source>Server requires authorization to create queues, check password</source>
<target>Сървърът изисква оторизация за създаване на опашки, проверете паролата</target>
@@ -5139,6 +5232,10 @@ Error: %@</source>
<target>Тестът на сървъра е неуспешен!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Server version is incompatible with network settings." xml:space="preserve">
<source>Server version is incompatible with network settings.</source>
<note>srv error text</note>
</trans-unit>
<trans-unit id="Servers" xml:space="preserve">
<source>Servers</source>
<target>Сървъри</target>
@@ -5259,11 +5356,19 @@ Error: %@</source>
<target>Показване на последните съобщения в листа с чатовете</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Show message status" xml:space="preserve">
<source>Show message status</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Show preview" xml:space="preserve">
<source>Show preview</source>
<target>Показване на визуализация</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Show → on messages sent via private routing." xml:space="preserve">
<source>Show → on messages sent via private routing.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Show:" xml:space="preserve">
<source>Show:</source>
<target>Покажи:</target>
@@ -5586,6 +5691,10 @@ It can happen because of some bug or when the connection is compromised.</source
<target>Приложението може да ви уведоми, когато получите съобщения или заявки за контакт - моля, отворете настройките, за да активирате.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The app will ask to confirm downloads from unknown file servers (except .onion)." xml:space="preserve">
<source>The app will ask to confirm downloads from unknown file servers (except .onion).</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The attempt to change database passphrase was not completed." xml:space="preserve">
<source>The attempt to change database passphrase was not completed.</source>
<target>Опитът за промяна на паролата на базата данни не беше завършен.</target>
@@ -5771,6 +5880,10 @@ It can happen because of some bug or when the connection is compromised.</source
<target>За да не се разкрива часовата зона, файловете с изображения/глас използват UTC.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To protect your IP address, private routing uses your SMP servers to deliver messages." xml:space="preserve">
<source>To protect your IP address, private routing uses your SMP servers to deliver messages.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To protect your information, turn on SimpleX Lock.&#10;You will be prompted to complete authentication before this feature is enabled." xml:space="preserve">
<source>To protect your information, turn on SimpleX Lock.
You will be prompted to complete authentication before this feature is enabled.</source>
@@ -5863,11 +5976,6 @@ You will be prompted to complete authentication before this feature is enabled.<
<target>Отблокирай член?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unexpected error: %@" xml:space="preserve">
<source>Unexpected error: %@</source>
<target>Неочаквана грешка: %@</target>
<note>item status description</note>
</trans-unit>
<trans-unit id="Unexpected migration state" xml:space="preserve">
<source>Unexpected migration state</source>
<target>Неочаквано състояние на миграция</target>
@@ -5913,6 +6021,10 @@ You will be prompted to complete authentication before this feature is enabled.<
<target>Непозната грешка</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unknown servers!" xml:space="preserve">
<source>Unknown servers!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions." xml:space="preserve">
<source>Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions.</source>
<target>Освен ако не използвате интерфейса за повикване на iOS, активирайте режима "Не безпокой", за да избегнете прекъсвания.</target>
@@ -6060,6 +6172,14 @@ To connect, please ask your contact to create another connection link and check
<target>Използвай само локални известия?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Use private routing with unknown servers when IP address is not protected." xml:space="preserve">
<source>Use private routing with unknown servers when IP address is not protected.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Use private routing with unknown servers." xml:space="preserve">
<source>Use private routing with unknown servers.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Use server" xml:space="preserve">
<source>Use server</source>
<target>Използвай сървър</target>
@@ -6295,11 +6415,23 @@ To connect, please ask your contact to create another connection link and check
<target>С намален разход на батерията.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Without Tor or VPN, your IP address will be visible to file servers." xml:space="preserve">
<source>Without Tor or VPN, your IP address will be visible to file servers.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." xml:space="preserve">
<source>Without Tor or VPN, your IP address will be visible to these XFTP relays: %@.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Wrong database passphrase" xml:space="preserve">
<source>Wrong database passphrase</source>
<target>Грешна парола за базата данни</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Wrong key or unknown connection - most likely this connection is deleted." xml:space="preserve">
<source>Wrong key or unknown connection - most likely this connection is deleted.</source>
<note>snd error text</note>
</trans-unit>
<trans-unit id="Wrong passphrase!" xml:space="preserve">
<source>Wrong passphrase!</source>
<target>Грешна парола!</target>
@@ -7415,6 +7547,12 @@ SimpleX сървърите не могат да видят вашия профи
<target>изпрати лично съобщение</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="server queue info: %@&#10;&#10;last received msg: %@" xml:space="preserve">
<source>server queue info: %1$@
last received msg: %2$@</source>
<note>queue info</note>
</trans-unit>
<trans-unit id="set new contact address" xml:space="preserve">
<source>set new contact address</source>
<target>зададен нов адрес за контакт</target>
@@ -7455,11 +7593,19 @@ SimpleX сървърите не могат да видят вашия профи
<target>неизвестен</target>
<note>connection info</note>
</trans-unit>
<trans-unit id="unknown relays" xml:space="preserve">
<source>unknown relays</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="unknown status" xml:space="preserve">
<source>unknown status</source>
<target>неизвестен статус</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="unprotected" xml:space="preserve">
<source>unprotected</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="updated group profile" xml:space="preserve">
<source>updated group profile</source>
<target>актуализиран профил на групата</target>
@@ -7525,6 +7671,10 @@ SimpleX сървърите не могат да видят вашия профи
<target>седмици</target>
<note>time unit</note>
</trans-unit>
<trans-unit id="when IP hidden" xml:space="preserve">
<source>when IP hidden</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="yes" xml:space="preserve">
<source>yes</source>
<target>да</target>
@@ -690,6 +690,10 @@
<target>Povolte mizící zprávy, pouze pokud vám to váš kontakt dovolí.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Allow downgrade" xml:space="preserve">
<source>Allow downgrade</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Allow irreversible message deletion only if your contact allows it to you. (24 hours)" xml:space="preserve">
<source>Allow irreversible message deletion only if your contact allows it to you. (24 hours)</source>
<target>Povolte nevratné smazání zprávy pouze v případě, že vám to váš kontakt dovolí. (24 hodin)</target>
@@ -782,6 +786,10 @@
<source>Already joining the group!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Always use private routing." xml:space="preserve">
<source>Always use private routing.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Always use relay" xml:space="preserve">
<source>Always use relay</source>
<target>Spojení přes relé</target>
@@ -1047,6 +1055,10 @@
<target>Nelze přijmout soubor</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Capacity exceeded - recipient did not receive previously sent messages." xml:space="preserve">
<source>Capacity exceeded - recipient did not receive previously sent messages.</source>
<note>snd error text</note>
</trans-unit>
<trans-unit id="Cellular" xml:space="preserve">
<source>Cellular</source>
<note>No comment provided by engineer.</note>
@@ -1238,6 +1250,10 @@
<target>Potvrdit aktualizaci databáze</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Confirm files from unknown servers." xml:space="preserve">
<source>Confirm files from unknown servers.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Confirm network settings" xml:space="preserve">
<source>Confirm network settings</source>
<note>No comment provided by engineer.</note>
@@ -1631,6 +1647,10 @@ This is your own one-time link!</source>
<target>Databáze bude přenesena po restartu aplikace</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Debug delivery" xml:space="preserve">
<source>Debug delivery</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Decentralized" xml:space="preserve">
<source>Decentralized</source>
<target>Decentralizované</target>
@@ -1870,6 +1890,10 @@ This cannot be undone!</source>
<source>Desktop devices</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Destination server error: %@" xml:space="preserve">
<source>Destination server error: %@</source>
<note>snd error text</note>
</trans-unit>
<trans-unit id="Develop" xml:space="preserve">
<source>Develop</source>
<target>Vyvinout</target>
@@ -1973,11 +1997,19 @@ This cannot be undone!</source>
<source>Discover via local network</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do NOT send messages directly, even if your or destination server does not support private routing." xml:space="preserve">
<source>Do NOT send messages directly, even if your or destination server does not support private routing.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do NOT use SimpleX for emergency calls." xml:space="preserve">
<source>Do NOT use SimpleX for emergency calls.</source>
<target>NEpoužívejte SimpleX pro tísňová volání.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do NOT use private routing." xml:space="preserve">
<source>Do NOT use private routing.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Do it later" xml:space="preserve">
<source>Do it later</source>
<target>Udělat později</target>
@@ -2519,7 +2551,7 @@ This cannot be undone!</source>
<trans-unit id="Error: %@" xml:space="preserve">
<source>Error: %@</source>
<target>Chyba: %@</target>
<note>No comment provided by engineer.</note>
<note>snd error text</note>
</trans-unit>
<trans-unit id="Error: URL is invalid" xml:space="preserve">
<source>Error: URL is invalid</source>
@@ -2608,6 +2640,10 @@ This cannot be undone!</source>
<target>Soubor: %@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Files" xml:space="preserve">
<source>Files</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Files &amp; media" xml:space="preserve">
<source>Files &amp; media</source>
<target>Soubory a média</target>
@@ -2706,6 +2742,16 @@ This cannot be undone!</source>
<source>Forwarded from</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Forwarding server: %@&#10;Destination server error: %@" xml:space="preserve">
<source>Forwarding server: %1$@
Destination server error: %2$@</source>
<note>snd error text</note>
</trans-unit>
<trans-unit id="Forwarding server: %@&#10;Error: %@" xml:space="preserve">
<source>Forwarding server: %1$@
Error: %2$@</source>
<note>snd error text</note>
</trans-unit>
<trans-unit id="Found desktop" xml:space="preserve">
<source>Found desktop</source>
<note>No comment provided by engineer.</note>
@@ -3490,11 +3536,19 @@ This is your link for group %@!</source>
<target>Potvrzení o doručení zprávy!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message delivery warning" xml:space="preserve">
<source>Message delivery warning</source>
<note>item status text</note>
</trans-unit>
<trans-unit id="Message draft" xml:space="preserve">
<source>Message draft</source>
<target>Návrh zprávy</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message queue info" xml:space="preserve">
<source>Message queue info</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message reactions" xml:space="preserve">
<source>Message reactions</source>
<target>Reakce na zprávy</target>
@@ -3510,6 +3564,14 @@ This is your link for group %@!</source>
<target>Reakce na zprávy jsou v této skupině zakázány.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message routing fallback" xml:space="preserve">
<source>Message routing fallback</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message routing mode" xml:space="preserve">
<source>Message routing mode</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Message source remains private." xml:space="preserve">
<source>Message source remains private.</source>
<note>No comment provided by engineer.</note>
@@ -3627,11 +3689,6 @@ This is your link for group %@!</source>
<target>Pravděpodobně je toto spojení smazáno.</target>
<note>item status description</note>
</trans-unit>
<trans-unit id="Most likely this contact has deleted the connection with you." xml:space="preserve">
<source>Most likely this contact has deleted the connection with you.</source>
<target>Tento kontakt s největší pravděpodobností smazal spojení s vámi.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Multiple chat profiles" xml:space="preserve">
<source>Multiple chat profiles</source>
<target>Více chatovacích profilů</target>
@@ -3661,6 +3718,10 @@ This is your link for group %@!</source>
<source>Network connection</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Network issues - message expired after many attempts to send it." xml:space="preserve">
<source>Network issues - message expired after many attempts to send it.</source>
<note>snd error text</note>
</trans-unit>
<trans-unit id="Network management" xml:space="preserve">
<source>Network management</source>
<note>No comment provided by engineer.</note>
@@ -4181,10 +4242,22 @@ Error: %@</source>
<target>Soukromé názvy souborů</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Private message routing" xml:space="preserve">
<source>Private message routing</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Private message routing 🚀" xml:space="preserve">
<source>Private message routing 🚀</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Private notes" xml:space="preserve">
<source>Private notes</source>
<note>name of notes to self</note>
</trans-unit>
<trans-unit id="Private routing" xml:space="preserve">
<source>Private routing</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Profile and server connections" xml:space="preserve">
<source>Profile and server connections</source>
<target>Profil a připojení k serveru</target>
@@ -4261,11 +4334,20 @@ Error: %@</source>
<target>Zakázat odesílání hlasových zpráv.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Protect IP address" xml:space="preserve">
<source>Protect IP address</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Protect app screen" xml:space="preserve">
<source>Protect app screen</source>
<target>Ochrana obrazovky aplikace</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Protect your IP address from the messaging relays chosen by your contacts.&#10;Enable in *Network &amp; servers* settings." xml:space="preserve">
<source>Protect your IP address from the messaging relays chosen by your contacts.
Enable in *Network &amp; servers* settings.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Protect your chat profiles with a password!" xml:space="preserve">
<source>Protect your chat profiles with a password!</source>
<target>Chraňte své chat profily heslem!</target>
@@ -4368,10 +4450,6 @@ Error: %@</source>
<target>Přijímací adresa bude změněna na jiný server. Změna adresy bude dokončena po připojení odesílatele.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Receiving concurrency" xml:space="preserve">
<source>Receiving concurrency</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Receiving file will be stopped." xml:space="preserve">
<source>Receiving file will be stopped.</source>
<target>Příjem souboru bude zastaven.</target>
@@ -4599,6 +4677,10 @@ Error: %@</source>
<target>SMP servery</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Safely receive files" xml:space="preserve">
<source>Safely receive files</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Safer groups" xml:space="preserve">
<source>Safer groups</source>
<note>No comment provided by engineer.</note>
@@ -4817,6 +4899,14 @@ Error: %@</source>
<target>Odeslat živou zprávu</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Send messages directly when IP address is protected and your or destination server does not support private routing." xml:space="preserve">
<source>Send messages directly when IP address is protected and your or destination server does not support private routing.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Send messages directly when your or destination server does not support private routing." xml:space="preserve">
<source>Send messages directly when your or destination server does not support private routing.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Send notifications" xml:space="preserve">
<source>Send notifications</source>
<target>Odeslat oznámení</target>
@@ -4921,6 +5011,10 @@ Error: %@</source>
<target>Odeslané zprávy se po uplynutí nastavené doby odstraní.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Server address is incompatible with network settings." xml:space="preserve">
<source>Server address is incompatible with network settings.</source>
<note>srv error text.</note>
</trans-unit>
<trans-unit id="Server requires authorization to create queues, check password" xml:space="preserve">
<source>Server requires authorization to create queues, check password</source>
<target>Server vyžaduje autorizaci pro vytváření front, zkontrolujte heslo</target>
@@ -4936,6 +5030,10 @@ Error: %@</source>
<target>Test serveru se nezdařil!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Server version is incompatible with network settings." xml:space="preserve">
<source>Server version is incompatible with network settings.</source>
<note>srv error text</note>
</trans-unit>
<trans-unit id="Servers" xml:space="preserve">
<source>Servers</source>
<target>Servery</target>
@@ -5051,11 +5149,19 @@ Error: %@</source>
<target>Zobrazit poslední zprávy</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Show message status" xml:space="preserve">
<source>Show message status</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Show preview" xml:space="preserve">
<source>Show preview</source>
<target>Zobrazení náhledu</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Show → on messages sent via private routing." xml:space="preserve">
<source>Show → on messages sent via private routing.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Show:" xml:space="preserve">
<source>Show:</source>
<target>Zobrazit:</target>
@@ -5369,6 +5475,10 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován
<target>Aplikace vás může upozornit na přijaté zprávy nebo žádosti o kontakt - povolte to v nastavení.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The app will ask to confirm downloads from unknown file servers (except .onion)." xml:space="preserve">
<source>The app will ask to confirm downloads from unknown file servers (except .onion).</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The attempt to change database passphrase was not completed." xml:space="preserve">
<source>The attempt to change database passphrase was not completed.</source>
<target>Pokus o změnu přístupové fráze databáze nebyl dokončen.</target>
@@ -5545,6 +5655,10 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován
<target>K ochraně časového pásma používají obrazové/hlasové soubory UTC.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To protect your IP address, private routing uses your SMP servers to deliver messages." xml:space="preserve">
<source>To protect your IP address, private routing uses your SMP servers to deliver messages.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To protect your information, turn on SimpleX Lock.&#10;You will be prompted to complete authentication before this feature is enabled." xml:space="preserve">
<source>To protect your information, turn on SimpleX Lock.
You will be prompted to complete authentication before this feature is enabled.</source>
@@ -5631,11 +5745,6 @@ Před zapnutím této funkce budete vyzváni k dokončení ověření.</target>
<source>Unblock member?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unexpected error: %@" xml:space="preserve">
<source>Unexpected error: %@</source>
<target>Neočekávaná chyba: %@</target>
<note>item status description</note>
</trans-unit>
<trans-unit id="Unexpected migration state" xml:space="preserve">
<source>Unexpected migration state</source>
<target>Neočekávaný stav přenášení</target>
@@ -5681,6 +5790,10 @@ Před zapnutím této funkce budete vyzváni k dokončení ověření.</target>
<target>Neznámá chyba</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unknown servers!" xml:space="preserve">
<source>Unknown servers!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions." xml:space="preserve">
<source>Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions.</source>
<target>Při nepoužívání rozhraní volání iOS, povolte režim Nerušit, abyste se vyhnuli vyrušování.</target>
@@ -5821,6 +5934,14 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu
<source>Use only local notifications?</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Use private routing with unknown servers when IP address is not protected." xml:space="preserve">
<source>Use private routing with unknown servers when IP address is not protected.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Use private routing with unknown servers." xml:space="preserve">
<source>Use private routing with unknown servers.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Use server" xml:space="preserve">
<source>Use server</source>
<target>Použít server</target>
@@ -6038,11 +6159,23 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu
<source>With reduced battery usage.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Without Tor or VPN, your IP address will be visible to file servers." xml:space="preserve">
<source>Without Tor or VPN, your IP address will be visible to file servers.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." xml:space="preserve">
<source>Without Tor or VPN, your IP address will be visible to these XFTP relays: %@.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Wrong database passphrase" xml:space="preserve">
<source>Wrong database passphrase</source>
<target>Špatná přístupová fráze k databázi</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Wrong key or unknown connection - most likely this connection is deleted." xml:space="preserve">
<source>Wrong key or unknown connection - most likely this connection is deleted.</source>
<note>snd error text</note>
</trans-unit>
<trans-unit id="Wrong passphrase!" xml:space="preserve">
<source>Wrong passphrase!</source>
<target>Špatná přístupová fráze!</target>
@@ -7122,6 +7255,12 @@ Servery SimpleX nevidí váš profil.</target>
<target>odeslat přímou zprávu</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="server queue info: %@&#10;&#10;last received msg: %@" xml:space="preserve">
<source>server queue info: %1$@
last received msg: %2$@</source>
<note>queue info</note>
</trans-unit>
<trans-unit id="set new contact address" xml:space="preserve">
<source>set new contact address</source>
<note>profile update event chat item</note>
@@ -7158,10 +7297,18 @@ Servery SimpleX nevidí váš profil.</target>
<target>neznámý</target>
<note>connection info</note>
</trans-unit>
<trans-unit id="unknown relays" xml:space="preserve">
<source>unknown relays</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="unknown status" xml:space="preserve">
<source>unknown status</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="unprotected" xml:space="preserve">
<source>unprotected</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="updated group profile" xml:space="preserve">
<source>updated group profile</source>
<target>aktualizoval profil skupiny</target>
@@ -7225,6 +7372,10 @@ Servery SimpleX nevidí váš profil.</target>
<target>týdnů</target>
<note>time unit</note>
</trans-unit>
<trans-unit id="when IP hidden" xml:space="preserve">
<source>when IP hidden</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="yes" xml:space="preserve">
<source>yes</source>
<target>ano</target>

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