Compare commits

..

309 Commits

Author SHA1 Message Date
Evgeny Poberezkin
498ffe8a71 4.5.4: Android 103, iOS 128 2023-03-18 09:57:19 +00:00
Evgeny Poberezkin
3dd5b5d835 core: 4.5.4.2 (add stateTVar imports) 2023-03-18 07:59:43 +00:00
Evgeny Poberezkin
9127b1bbc6 Merge pull request #2022 from simplex-chat/ep/v454
v4.5.4: add support for observer role
2023-03-17 17:14:43 +00:00
Evgeny Poberezkin
1657bcf97d core: 4.5.4.1 2023-03-17 15:02:41 +00:00
Evgeny Poberezkin
428db2f8f4 core: fix unused contact deletion (#2023)
* core: failing test for leaving and deleting the group joined via link

* fix test

* merge logic

* fix

* add condition

* refactor

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* compiles

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2023-03-17 16:03:19 +04:00
Evgeny Poberezkin
4cc59d9fbd core: 4.5.4.0 2023-03-16 23:30:28 +00:00
Evgeny Poberezkin
c50306709b mobile: do not show "observer" overlay when user leaves group 2023-03-16 23:29:47 +00:00
Evgeny Poberezkin
37d0bc2f14 mobile: hide observer role from UI (to be reverted after v4.5.4 is released) 2023-03-16 22:58:00 +00:00
Stanislav Dmitrenko
2fda0454e3 android: group link role, add observer role (#1981)
* android: group link role, add observer role

* padding

* disabled tint for buttons

* proper layout for long display name

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-03-16 22:20:42 +00:00
Evgeny Poberezkin
be19af62d9 ios: group link role, add observer role (#1978)
* ios: group link role, add observer role

* prevent observers from sending in UI, clear compose state on role change
2023-03-16 22:20:20 +00:00
Evgeny Poberezkin
f915eb2a20 core: initial group member role when joining via link (#1975)
* core: initial group member role when joining via link

* fix tests

* set role when joining group via link, enable observer test

* show group link when role changes

* amend test

* check role is member or observer when creating a link
2023-03-16 22:19:51 +00:00
Evgeny Poberezkin
2bc1236a2c terminal: update help, remove user ID from terminal /smp test command (#1973)
* terminal: update help, remove user ID from terminal /smp test command

* update mobile api

* update help
2023-03-16 22:19:22 +00:00
Evgeny Poberezkin
01acbb970a blog: typos 2023-03-01 20:03:06 +00:00
Evgeny Poberezkin
36cad35d46 blog: SimpleX File Transfer Protocol (XFTP) (#1965)
* blog: SimpleX File Transfer Protocol (XFTP)

* update blog

* simplify quick start

* update blog
2023-03-01 19:29:59 +00:00
spacedandy
41c9c84139 4.5.3: iOS 127 2023-03-01 19:38:50 +04:00
spacedandy
9e9ca521b0 4.5.3: Android 102 2023-03-01 18:00:49 +04:00
spaced4ndy
1927862871 android: show "export prohibited" alert on enabling app data backup with random db password set (#1962) 2023-03-01 14:36:08 +04:00
spacedandy
c80eaf8550 core: 4.5.3.1 2023-03-01 13:23:34 +04:00
Evgeny Poberezkin
9e6a35bac3 mobile: add Czech, fix translations (#1961)
* ios: import localizations

* re-export localizations

* add Czech language

* fix Czech strings

* add Czech to android
2023-03-01 08:52:56 +00:00
Evgeny Poberezkin
62ffcf94a6 translations (#1956)
* Translated using Weblate (Chinese (Simplified))

Currently translated at 73.6% (658 of 893 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (954 of 954 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.7% (891 of 893 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 78.2% (699 of 893 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 82.5% (737 of 893 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 96.5% (862 of 893 strings)

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

* Added translation using Weblate (Arabic)

* Added translation using Weblate (Arabic)

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 73.6% (658 of 893 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (954 of 954 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.7% (891 of 893 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 78.2% (699 of 893 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 82.5% (737 of 893 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 96.5% (862 of 893 strings)

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

* Added translation using Weblate (Arabic)

* Added translation using Weblate (Arabic)

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Dutch)

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

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Dutch)

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

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

* Translated using Weblate (Dutch)

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

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 59.2% (565 of 954 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 0.6% (6 of 954 strings)

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

---------

Co-authored-by: Aaron H <niximi333@gmail.com>
Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: zenobit <zen@osowoso.xyz>
Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: M Sarmad Qadeer <msarmadqadeer@gmail.com>
Co-authored-by: Bdd55oo <giggzuv9z.eofjx@aleeas.com>
Co-authored-by: Pedro Licio <pedro@agenciaregex.com>
2023-03-01 08:15:32 +00:00
Evgeny Poberezkin
2b77920dcd teminal: option to log errors and service messages to file, closes #1516 (#1957)
* teminal: option to log errors and service messages to file, closes #1516

* rename function
2023-02-28 23:26:08 +00:00
darkmaster
38b7e4d4a4 docs: fixes listening port (server) (#1958) 2023-02-27 20:15:08 +00:00
Evgeny Poberezkin
3e4d4f04ef ios: allow pasting profile image 2023-02-27 17:46:10 +00:00
Evgeny Poberezkin
f6f3d17383 ios: do not show notifications on update events in inactive profiles (#1959) 2023-02-27 16:20:54 +00:00
Evgeny Poberezkin
d5f6b76ec5 core: increase default queue sizes to 1024, fix broadcast bot not to lock when queue is smaller than the number of contacts (#1955) 2023-02-26 16:36:11 +00:00
zenobit
ae75be56ea blog: string fix (#1952) 2023-02-25 17:53:31 +00:00
Evgeny Poberezkin
50b90c4814 core: use 12 bytes IV for WebRTC frame encryption with AES-GCM (#1951)
* core: use 12 bytes IV for WebRTC frame encryption with AES-GCM

* refactor
2023-02-25 17:52:23 +00:00
+shyfire131
6eddb5f30f fix comment syntax in cabal.project.mac (#1948)
Co-authored-by: +shyfire131 <shyfire131@shyfire131.net>
2023-02-24 21:04:46 +00:00
Evgeny Poberezkin
cee8f3a4b6 website internationalization (#1922)
* website Internationalization (#1904)

* added devcontainer config

* internationalization under dev

* internationalization of _data done

* overlays internationalization done

* improved routing

* updated gitignore

* remove .devcontainer

* internationalization in progess
deleted all the intermediate files
added translation of few sections

* remaining website strings are added to translation

* done internationalization
- fully converted to i18n plugin
- wrote bash script for creating a combined translations.json

* internationalization UI done

* a quick fix

* remove jq installation

* Added translation using Weblate (German)

* Translated using Weblate (French)

Currently translated at 100.0% (212 of 212 strings)

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

* Translated using Weblate (German)

Currently translated at 42.9% (91 of 212 strings)

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

* fixes of web Internationalization (#1925)

* a quick fix

* blog route for all languages is shifted to root blog route

* wrote merge_translations.js

* remove language label from dropdown

* update language names

* refactor scripts

* remove catch from script

* Added translation using Weblate (Dutch)

* Added translation using Weblate (Norwegian Bokm├еl)

* Translated using Weblate (Dutch)

Currently translated at 33.0% (70 of 212 strings)

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

* website: internationalization fixes (#1931)

* added devcontainer config

* internationalization under dev

* internationalization of _data done

* overlays internationalization done

* improved routing

* updated gitignore

* remove .devcontainer

* internationalization in progess
deleted all the intermediate files
added translation of few sections

* remaining website strings are added to translation

* done internationalization
- fully converted to i18n plugin
- wrote bash script for creating a combined translations.json

* internationalization UI done

* a quick fix

* blog route for all languages is shifted to root blog route

* wrote merge_translations.js

* remove flag from blog

* updated nav stylings & logo links

* add enabled key

* updated nav dropdown styling

* gave specific lang to i18n plugin.
overlay translations are now working.

* enable nl & nb_NO

* updated nav stylings

* updated contact.js

---------

Co-authored-by: M Sarmad Qadeer <msarmadqadeer@gmail.com>

* Translated using Weblate (German)

Currently translated at 47.6% (101 of 212 strings)

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

* fixed internationalization issues

* updated strings, refactor contact.js

* updated nav stylings for mobile

* a bit smaller padding on mobile

* Added translation using Weblate (Czech)

* Translated using Weblate (Czech)

Currently translated at 100.0% (212 of 212 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (212 of 212 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (212 of 212 strings)

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

* enabled languages

* check/correct (#1949)

---------

Co-authored-by: M Sarmad Qadeer <MSarmadQadeer@gmail.com>
Co-authored-by: Ophiushi <Ophiushi@users.noreply.hosted.weblate.org>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: zenobit <zen@osowoso.xyz>
Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
2023-02-24 21:03:57 +00:00
Evgeny Poberezkin
a2e5733be6 core: update/fix webrtc frame encryption function to return error (#1950)
* core: update/fix webrtc frame encryption function to return error

* ios: update C header

* more tests
2023-02-24 20:55:59 +00:00
Evgeny Poberezkin
5075657c02 ios: update core library 2023-02-23 23:36:39 +00:00
Evgeny Poberezkin
0450b1ace2 mobile: welcome/about page (#1946)
* mobile: welcome/about page

* scroll view (fixes trimmed texts)

* layout

* bigger frame

* minHeight, to allow scroling with large font

---------

Co-authored-by: spacedandy <8711996+spaced4ndy@users.noreply.github.com>
2023-02-21 15:31:41 +00:00
Evgeny Poberezkin
0ebf1da05d core: WebRTC frames encryption (#1942)
* core: WebRTC frames encryption

* test
2023-02-19 23:51:50 +00:00
Evgeny Poberezkin
07ad3edbc2 4.5.3-beta.0: Android 101, iOS 126 2023-02-19 13:42:32 +00:00
Evgeny Poberezkin
b40ed2a7f3 core: 4.5.3.0 2023-02-19 10:09:22 +00:00
Evgeny Poberezkin
29b074607c mobile: add Dutch interface (#1941)
* iOS: add Dutch language

* ios: re-export localizations

* enable Dutch in Android
2023-02-19 08:47:26 +00:00
Evgeny Poberezkin
258a157e44 translations (#1938)
* Translated using Weblate (Italian)

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

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 46.5% (444 of 953 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% (953 of 953 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 50.8% (485 of 953 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% (953 of 953 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 0.3% (3 of 891 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 57.0% (544 of 953 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 51.6% (460 of 891 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 15.1% (144 of 953 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 15.2% (145 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 14.3% (128 of 891 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 15.1% (135 of 891 strings)

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

* Translated using Weblate (Hindi)

Currently translated at 14.3% (137 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (953 of 953 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 25.1% (224 of 891 strings)

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

* Translated using Weblate (Hindi)

Currently translated at 15.1% (144 of 953 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 59.4% (567 of 953 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 53.5% (477 of 891 strings)

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

* Translated using Weblate (Hindi)

Currently translated at 15.5% (148 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 29.2% (261 of 891 strings)

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

* Translated using Weblate (Hindi)

Currently translated at 15.6% (149 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 31.6% (282 of 891 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (953 of 953 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 36.2% (323 of 891 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 7.4% (71 of 953 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 41.5% (370 of 891 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 53.9% (481 of 891 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 57.5% (513 of 891 strings)

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

* Added translation using Weblate (Bulgarian)

* Added translation using Weblate (Bulgarian)

* Translated using Weblate (Dutch)

Currently translated at 100.0% (953 of 953 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 77.2% (688 of 891 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 83.9% (748 of 891 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 15.9% (152 of 953 strings)

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

* Translated using Weblate (Dutch)

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

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 7.9% (76 of 954 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 99.8% (953 of 954 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% (891 of 891 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 99.8% (953 of 954 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% (891 of 891 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 99.8% (953 of 954 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% (891 of 891 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (954 of 954 strings)

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

* Added translation using Weblate (Norwegian Bokm├еl)

* Added translation using Weblate (Norwegian Bokm├еl)

* Translated using Weblate (German)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (French)

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

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

* Translated using Weblate (Japanese)

Currently translated at 67.1% (641 of 954 strings)

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

* Translated using Weblate (Hindi)

Currently translated at 15.7% (150 of 954 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 99.3% (948 of 954 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 61.4% (586 of 954 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 55.5% (495 of 891 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 27.2% (260 of 954 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 42.5% (406 of 954 strings)

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

* Translated using Weblate (Croatian)

Currently translated at 2.0% (18 of 891 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 66.2% (632 of 954 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 61.3% (547 of 891 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (891 of 891 strings)

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

* Translated using Weblate (Italian)

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

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 46.5% (444 of 953 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% (953 of 953 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 50.8% (485 of 953 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% (953 of 953 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 0.3% (3 of 891 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 57.0% (544 of 953 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 51.6% (460 of 891 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 15.1% (144 of 953 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 15.2% (145 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 14.3% (128 of 891 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 15.1% (135 of 891 strings)

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

* Translated using Weblate (Hindi)

Currently translated at 14.3% (137 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (953 of 953 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 25.1% (224 of 891 strings)

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

* Translated using Weblate (Hindi)

Currently translated at 15.1% (144 of 953 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 59.4% (567 of 953 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 53.5% (477 of 891 strings)

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

* Translated using Weblate (Hindi)

Currently translated at 15.5% (148 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 29.2% (261 of 891 strings)

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

* Translated using Weblate (Hindi)

Currently translated at 15.6% (149 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 31.6% (282 of 891 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (953 of 953 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 36.2% (323 of 891 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 7.4% (71 of 953 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 41.5% (370 of 891 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 53.9% (481 of 891 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 57.5% (513 of 891 strings)

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

* Added translation using Weblate (Bulgarian)

* Added translation using Weblate (Bulgarian)

* Translated using Weblate (Dutch)

Currently translated at 100.0% (953 of 953 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 77.2% (688 of 891 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 83.9% (748 of 891 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 15.9% (152 of 953 strings)

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

* Translated using Weblate (Dutch)

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

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 7.9% (76 of 954 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 99.8% (953 of 954 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% (891 of 891 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 99.8% (953 of 954 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% (891 of 891 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 99.8% (953 of 954 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% (891 of 891 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (954 of 954 strings)

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

* Added translation using Weblate (Norwegian Bokm├еl)

* Added translation using Weblate (Norwegian Bokm├еl)

* Translated using Weblate (German)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (French)

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

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

* Translated using Weblate (Japanese)

Currently translated at 67.1% (641 of 954 strings)

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

* Translated using Weblate (Hindi)

Currently translated at 15.7% (150 of 954 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 99.3% (948 of 954 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 61.4% (586 of 954 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 55.5% (495 of 891 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 27.2% (260 of 954 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 42.5% (406 of 954 strings)

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

* Translated using Weblate (Croatian)

Currently translated at 2.0% (18 of 891 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 66.2% (632 of 954 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 61.3% (547 of 891 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (891 of 891 strings)

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

---------

Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: xqhjay <137847720@qq.com>
Co-authored-by: sith-on-mars <groguko36@pm.me>
Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: zenobit <zen@osowoso.xyz>
Co-authored-by: sith-on-mars <groguko36@tuta.io>
Co-authored-by: Bdd55oo <giggzuv9z.eofjx@aleeas.com>
Co-authored-by: Raman <translations.0l5zc@simplelogin.com>
Co-authored-by: LegendaryInfernalFox <0387agimeno@e-itaca.es>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: Ophiushi <Ophiushi@users.noreply.hosted.weblate.org>
Co-authored-by: tomato potato <4ryo49@protonmail.com>
Co-authored-by: Mehmed <pajazetovicmeho@gmail.com>
2023-02-19 08:23:32 +00:00
Evgeny Poberezkin
92d9a1f9f2 ios: export localizations 2023-02-18 19:32:25 +00:00
Evgeny Poberezkin
e5009a58df core: update simplexmq v4.4.1 2023-02-18 19:04:59 +00:00
Evgeny Poberezkin
35a1ce4903 core: separate core options to use in bots (#1937)
* core: separate core options to use in bots

* ci: install pkg-config for mac
2023-02-18 17:39:16 +00:00
Evgeny Poberezkin
7c4c627ee9 terminal: support multiline messages (as JSON strings) (#1936)
* terminal: support for multiline messages

* fix

* fix tests
2023-02-18 15:16:50 +00:00
Evgeny Poberezkin
b7575ec01d core: encrypt/decrypt WebRTC frames (#1935)
* core: encrypt/decrypt WebRTC frames

* swift API

* add decrypt stub

* change name

* remove unused type

* move functions

* update cabal file

* copy bytes from encrypted string
2023-02-16 20:25:37 +00:00
Evgeny Poberezkin
a0351d6f99 apps: update chat bots, readme (#1928)
* apps: update chat bots, readme

* CLI readme

* broadcast bot

* delete messages from non-publishers, better replies, support forwarding low-res images and links

* typo

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

* change

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2023-02-14 07:57:27 +00:00
JRoberts
6f68840b3a 4.5.2: ios 125, Android 100 2023-02-10 11:43:14 +04:00
JRoberts
2eef858db1 ios: update core library 2023-02-10 11:10:35 +04:00
Evgeny Poberezkin
434315fb08 Merge branch 'stable' 2023-02-09 20:53:05 +00:00
Evgeny Poberezkin
9b495e576c readme: update groups 2023-02-09 20:52:44 +00:00
JRoberts
5405f44f54 core: 4.5.2.0 2023-02-09 16:34:44 +04:00
Stanislav Dmitrenko
53b05974c9 android: limit width + height in chat item view (#1920) 2023-02-09 12:00:33 +00:00
JRoberts
3530022152 ios, android: moderated item content types, CIDeleted type (#1919) 2023-02-09 15:10:35 +04:00
Stanislav Dmitrenko
dc6bab7ae6 android: fixed broken autoscroll when a new message is coming (#1917)
* android: fixed broken autoscroll when a new message is coming

* spelling
2023-02-08 23:10:56 +00:00
Stanislav Dmitrenko
c9b4ce457e android: prevent race when opening chat (#1914)
* android: prevent race when opening chat

* different way of doing things

* change

* change
2023-02-08 19:25:51 +00:00
JRoberts
bd3325a889 core: show/keep message as moderated for moderator (#1916) 2023-02-08 22:29:36 +04:00
JRoberts
9e347484eb core: avoid using wickAckMessage handler on messages leading to connection deletion (#1915) 2023-02-08 21:23:53 +04:00
ishi_sama
894af0602d docs: french (#1905)
* Lang subfolder ; SERVER_fr.md ; fix minor typos

* fix the fix

* fix img src

* CONTRIBUTING_fr

* SQL_fr ; fix rev date

* WEBRTC_fr.md ; rev date

* CLI_fr ; rev date

* fix table content link and sum img src

* fix

* fixing the fix

am i dumb?

* polishing...

* README_fr.md (save)

* Update README_fr.md

* README_fr ; starting SIMPLEX_FR ; link fix

* update README (en+fr)

* Blog README_fr ; translators link

* typo

* SIMPLEX_fr ; fixes

* last fixes

* rename folder

* rename files/links

* update line

* remove ...

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-02-08 14:03:52 +00:00
Stanislav Dmitrenko
aa6011a196 android: prevent race when changing active user (#1913) 2023-02-08 13:21:55 +00:00
Stanislav Dmitrenko
f24035a99d android: prevent showing system alert after start (Android 13+) (#1909)
* android: prevent showing system alert after start (Android 13+)

* refactor

* added comments and renamed function

* rename

* rename

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-02-08 10:48:19 +00:00
Stanislav Dmitrenko
c006b8150f ios: ask permission to open profiles (#1910) 2023-02-08 10:39:41 +00:00
Evgeny Poberezkin
9e4499de6d core: allow admins/owners delete member messages (#1869)
* core: allow admins/owners delete member messages

* allow message deletion to admins/owners

* deleted by types, schema

* check role

* fix test, view

* view, tests

* comment

* test timed deletion events

* refactor

* refactor

* refactor

---------

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2023-02-08 11:08:53 +04:00
Evgeny Poberezkin
a018e4a581 docs: translation guide (#1911)
* docs: translations

* add doc

* add images, corrections

* spellcheck

* link to translation guide from readme

* change image

* images
2023-02-07 21:57:51 +00:00
Stanislav Dmitrenko
d29fd93ea7 docs: view files from private data directory of Android app (#1907)
* docs: view files from private data directory of Android app

* naming

* spelling

* spelling

* change
2023-02-07 15:17:10 +00:00
Stanislav Dmitrenko
b30c7af3a3 mobile: including fully localized languages only (#1908)
* mobile: including fully localized languages only

* better place for code

* ios: including fully localized languages only

* Revert "ios: including fully localized languages only"

This reverts commit 42a0334d83.
2023-02-07 15:16:34 +00:00
Stanislav Dmitrenko
2798671d22 android: show alert when decode exception happens (#1906) 2023-02-07 12:08:54 +00:00
Stanislav Dmitrenko
0339b399f7 ios: don't go back when system alert appears (#1903) 2023-02-06 16:33:45 +00:00
Stanislav Dmitrenko
d048962959 ios: Fixed screenshot size (#1902) 2023-02-06 16:00:29 +00:00
Stanislav Dmitrenko
4af91c4cae android: fixed size for exported QR code (#1901)
* android: fixed size for exported QR code

* border

* automatic version

* modifier

* make image instead of screenshot

* code folding

* don't use deprecated method

* function refactor

* dropped unneeded variable
2023-02-06 15:34:49 +00:00
Stanislav Dmitrenko
8a445ece90 ios: colored and clickable qr code with logo (#1885)
* ios: colored and clickable qr code with logo

* size of circle

* same padding as in android

* add padding to logo

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-02-06 08:45:56 +00:00
Stanislav Dmitrenko
5082f5b4a4 android: colored and clickable qr code with logo (#1884)
* android: colored and clickable qr code with logo

* save qr code as jpg for better quality

* bigger logo

* bigger logo (0.16f), low error correction (QR scans ok with up to 0.26f circle size)

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-02-06 08:45:40 +00:00
Evgeny Poberezkin
c8fae0ec43 blog: typo 2023-02-05 23:46:42 +00:00
Evgeny Poberezkin
d9a8d333f7 4.5.1: Android 99, iOS 124 2023-02-05 23:37:58 +00:00
Evgeny Poberezkin
73f8c543e3 translations (#1900)
* Translated using Weblate (Czech)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 26.7% (255 of 954 strings)

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

* Translated using Weblate (French)

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

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

* Translated using Weblate (Japanese)

Currently translated at 8.5% (82 of 954 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 24.4% (219 of 895 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 1.8% (18 of 954 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 56.4% (538 of 953 strings)

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

* Translated using Weblate (Czech)

Currently translated at 1.6% (15 of 891 strings)

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

* Added translation using Weblate (Croatian)

* Added translation using Weblate (Croatian)

* Translated using Weblate (Dutch)

Currently translated at 63.6% (607 of 953 strings)

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

* Translated using Weblate (Czech)

Currently translated at 99.6% (950 of 953 strings)

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

* Translated using Weblate (Czech)

Currently translated at 66.1% (589 of 891 strings)

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

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

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

* ios: import localizations

* ios: export localizations

---------

Co-authored-by: zenobit <zen@osowoso.xyz>
Co-authored-by: xqhjay <137847720@qq.com>
Co-authored-by: Ophiushi <Ophiushi@users.noreply.hosted.weblate.org>
Co-authored-by: tomato potato <4ryo49@protonmail.com>
Co-authored-by: Albert Bob <vicdorke@gmail.com>
Co-authored-by: Bdd55oo <giggzuv9z.eofjx@aleeas.com>
Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: Mehmed <pajazetovicmeho@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
2023-02-05 22:36:52 +00:00
Evgeny Poberezkin
14945a9296 ios: update core library 2023-02-05 22:30:03 +00:00
Evgeny Poberezkin
155ffd16ec core: 4.5.1.0 2023-02-05 22:06:27 +00:00
sh
1eb1e52912 call.ts: include udp stun/turn (#1892)
* call.ts: include udp stun/turn

* update JS

* show protocol, support TURNS

* mobile: add turn via UDP, remove protocol from view

* remove enums for protocol strings in ICE candidates

* 0.2.3

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-02-05 21:57:50 +00:00
Evgeny Poberezkin
af173ee5c4 blog: v4.5 (#1886)
* blog: v4.5

* update post, images

* update readme, post
2023-02-05 13:53:41 +00:00
Evgeny Poberezkin
06a2f7e4da mobile: remove option "transfer images faster" (#1891) 2023-02-05 11:25:31 +00:00
Evgeny Poberezkin
958299784d core: update simplexmq (more strict transport host parser 2023-02-04 23:31:01 +00:00
Evgeny Poberezkin
49b6979ff0 core: update simplexmq (not to fail batch subscriptions), terminal: log contact errors with -c option (#1890) 2023-02-04 23:13:20 +00:00
Evgeny Poberezkin
3c493db613 mobile: use TCP for ICE requests of WebRTC calls (#1888)
* ios: support query string parameters in ICE server addresses

* android: support query params in ICE server address, add transport=TCP to default servers
2023-02-04 15:44:39 +00:00
Evgeny Poberezkin
86cc85b3a5 mobile: change default of "transfer images faster/inline" to off, mark as BETA (#1889)
* mobile: change default of "transfer images faster/inline" to off, mark as BETA

* ios: import localizations
2023-02-04 15:19:12 +00:00
Evgeny Poberezkin
0427d2e578 core: prevent failure to acknowledge a group message in case its parsing or saving fails (potential cause for stuck delivery) (#1887) 2023-02-04 12:25:11 +00:00
Evgeny Poberezkin
c90d911d2a 4.5.0: Android 98, iOS 123 2023-02-03 15:19:26 +00:00
Evgeny Poberezkin
2473d14baa core: 4.5.0.4, update simplexmq 2023-02-03 11:32:32 +00:00
Evgeny Poberezkin
a36f2147d8 mobile: current profile button in profile menu opens settings (#1882) 2023-02-03 11:22:17 +00:00
Evgeny Poberezkin
3837e92556 ios: fix advanced network config (#1881) 2023-02-03 11:17:56 +00:00
Evgeny Poberezkin
76505afff2 ios: import/export translations 2023-02-02 23:28:31 +00:00
Evgeny Poberezkin
89c9a01b20 mobile: translations (#1878)
* Translated using Weblate (Russian)

Currently translated at 100.0% (954 of 954 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 98.5% (882 of 895 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% (895 of 895 strings)

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

* Translated using Weblate (French)

Currently translated at 98.5% (882 of 895 strings)

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

* Translated using Weblate (Italian)

Currently translated at 98.5% (882 of 895 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 42.1% (402 of 954 strings)

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

* Translated using Weblate (Czech)

Currently translated at 3.8% (37 of 954 strings)

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

* Translated using Weblate (Italian)

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

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (Czech)

Currently translated at 3.9% (38 of 954 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (954 of 954 strings)

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

---------

Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: zenobit <zen@osowoso.xyz>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: mlanp <github@lang.xyz>
2023-02-02 23:21:08 +00:00
Evgeny Poberezkin
68517cf852 android: disable chat preferences when chat is stopped (#1877) 2023-02-02 21:27:22 +00:00
Stanislav Dmitrenko
fbbad55a0f android: Fix constraints of Compose that could crash the app (#1876)
* android: Fix constraints of Compose that could crash the app

* made constant
2023-02-02 19:46:33 +00:00
Stanislav Dmitrenko
bcca27bfdb ios: show notifications for different users (#1874)
* ios: show notifications for different users

* refactore

* terminate background taks on chat item update

* refactor

* refactor2

* refactor3

* refactor 4

* refactor5

* fix chat item update in Android

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-02-02 16:09:36 +00:00
Evgeny Poberezkin
c55a7692c5 4.5.0-beta.3: Android 97, iOS 122 2023-02-02 12:16:58 +00:00
Evgeny Poberezkin
a8aa829e4c android: what is new in v45, ios: update texts 2023-02-02 11:20:37 +00:00
Evgeny Poberezkin
d5af03ce18 ios: import/export localizations (#1873)
* ios: import localizations

* ios: export localizations
2023-02-02 10:43:18 +00:00
Evgeny Poberezkin
101ef7a81a translations: updates for user profiles, new languages (#1872)
* Added translation using Weblate (Spanish)

* Added translation using Weblate (Dutch)

* Added translation using Weblate (Dutch)

* Translated using Weblate (Russian)

Currently translated at 100.0% (936 of 936 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (878 of 878 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 11.0% (103 of 936 strings)

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 13.8% (130 of 936 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 14.9% (131 of 878 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 13.7% (129 of 936 strings)

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

* Added translation using Weblate (Japanese)

* Added translation using Weblate (Japanese)

* Added translation using Weblate (Hindi)

* Added translation using Weblate (Hindi)

* Added translation using Weblate (Czech)

* Added translation using Weblate (Czech)

* Added translation using Weblate (Polish)

* Added translation using Weblate (Polish)

* Added translation using Weblate (Portuguese (Brazil))

* Added translation using Weblate (Portuguese (Brazil))

* Added translation using Weblate (Spanish)

* Added translation using Weblate (Dutch)

* Added translation using Weblate (Dutch)

* Translated using Weblate (Russian)

Currently translated at 100.0% (936 of 936 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (878 of 878 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 11.0% (103 of 936 strings)

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 13.8% (130 of 936 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 14.9% (131 of 878 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 13.7% (129 of 936 strings)

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

* Added translation using Weblate (Japanese)

* Added translation using Weblate (Japanese)

* Added translation using Weblate (Hindi)

* Added translation using Weblate (Hindi)

* Added translation using Weblate (Czech)

* Added translation using Weblate (Czech)

* Added translation using Weblate (Polish)

* Added translation using Weblate (Polish)

* Added translation using Weblate (Portuguese (Brazil))

* Added translation using Weblate (Portuguese (Brazil))

* Translated using Weblate (French)

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

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 17.3% (162 of 936 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 16.2% (143 of 878 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 15.7% (147 of 936 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (939 of 939 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% (878 of 878 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 21.9% (206 of 939 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 20.7% (182 of 878 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (939 of 939 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 24.3% (229 of 939 strings)

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

* Translated using Weblate (Hindi)

Currently translated at 7.8% (74 of 939 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 31.2% (294 of 940 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (940 of 940 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (940 of 940 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (940 of 940 strings)

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

* Translated using Weblate (Hindi)

Currently translated at 12.6% (119 of 940 strings)

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

* Translated using Weblate (Hindi)

Currently translated at 13.5% (127 of 940 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 22.7% (214 of 940 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 21.7% (191 of 878 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 2.5% (24 of 940 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 23.6% (222 of 940 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 24.3% (214 of 878 strings)

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

---------

Co-authored-by: sith-on-mars <groguko36@pm.me>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Ophiushi <Ophiushi@users.noreply.hosted.weblate.org>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: Raman <translations.0l5zc@simplelogin.com>
Co-authored-by: tomato potato <4ryo49@protonmail.com>
2023-02-02 10:30:40 +00:00
Evgeny Poberezkin
71daeed81a ios: what is new in v4.5 2023-02-02 10:11:11 +00:00
Evgeny Poberezkin
d44324eb4d readme: update weblate link 2023-02-02 09:47:39 +00:00
Evgeny Poberezkin
93d2ef66cf readme: translations (#1871) 2023-02-02 09:41:53 +00:00
Evgeny Poberezkin
8a78943e94 blog: v4.5 release announcement page 2023-02-02 09:00:34 +00:00
Evgeny Poberezkin
d0f0013755 core: 4.5.0.3 2023-02-02 08:20:12 +00:00
Stanislav Dmitrenko
f22ee1a6cf mobile: prevent WebRTC call failure/hanging when webview "failed" state happens before 30 sec timeout (#1866)
* mobile: do not end calls

* better way of continue connection and end with timeout

* making data classes instead of classes for making logs informative

* refactor

* update webrtc package version

* refactor

* fix

* clear conneciton timeout on disconnection

* refactor

* v0.2.1

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-02-01 19:23:28 +00:00
Evgeny Poberezkin
4a58ca60ac core: split tests (#1870) 2023-02-01 17:21:13 +00:00
Evgeny Poberezkin
b206868730 core: add grop member role "observer" (#1868)
* core: add grop member role "observer"

* disable observer role until supported by most clients
2023-02-01 13:57:39 +00:00
Evgeny Poberezkin
bd4c10b224 v4.5.0-beta.2: Android 96, iOS 121 2023-02-01 08:57:38 +00:00
Evgeny Poberezkin
46d15d1811 core: 4.5.0.2 2023-02-01 00:03:37 +00:00
Evgeny Poberezkin
ea64be55e1 core: fix cancelling inline file transfer (#1867)
* core: fix cancelling inline file transfer

* fix test
2023-02-01 00:01:22 +00:00
Stanislav Dmitrenko
c2cd58f63d android: adapted UserExists error (#1863)
* android: Adapted UserExists error

* updated texts

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-01-31 18:50:20 +00:00
Stanislav Dmitrenko
f21fc76ced ios: adapted UserExists error (#1864)
* ios: adapted UserExists alert

* updated texts

* refactor

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-01-31 15:55:41 +00:00
Evgeny Poberezkin
13bd51b97d core: prevent making all users inactive when duplicate user is created (#1862)
* core: prevent making all users inactive when duplicate user is created

* skip async group test
2023-01-31 12:24:18 +00:00
Evgeny Poberezkin
a1ed0a84b8 core: use port 7001 for test server (#1857)
* core: use port 7001 for test server

* enable only failing tests

* start/stop server for every test

* log message that failed to parse

* stop chat synchronously

* print call stack

* add HasCallStack

* increase test timeout

* add call stacks

* more call stacks

* fix test

* disable failing test

* add delay between the tests

* make delay more visible

* remove change in error message

* reduce test delay, increase timeout

* increase delay between the tests

* run each test with a database in a different folder

* folder name

* refactor

* update nix file, more stacks
2023-01-31 11:07:48 +00:00
Stanislav Dmitrenko
4815e447fa android: show alert instead of crash on user errors (#1861)
* android: show alert instead of crash on user errors

* show meaningful alert

* update alert messages

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-01-30 15:12:39 +00:00
Stanislav Dmitrenko
d80cad57b6 android: require auth when opening users list (#1860)
* android: require auth when opening users list

* different logic in asking to auth

* unused code
2023-01-30 13:24:15 +00:00
Stanislav Dmitrenko
a58be6ebb6 ios: limit number of items in console (#1859) 2023-01-30 12:07:06 +00:00
Stanislav Dmitrenko
dfa0272065 android: user picker UI changes + share button (#1858)
* android: user picker UI changes + share button

* limit terminal items size in a different way
2023-01-30 11:19:26 +00:00
Evgeny Poberezkin
9723c47b25 4.5.0-beta.1: Android 95, iOS 120 2023-01-29 19:37:37 +00:00
Evgeny Poberezkin
3eb51eca58 core: v4.5.0.1 2023-01-29 18:52:38 +00:00
Evgeny Poberezkin
86151d4ec2 core: drop index causing slow queries (#1855)
* core: drop index causing slow queries

* update schema
2023-01-29 15:22:09 +00:00
Evgeny Poberezkin
717d05c4a3 android: fix bug when creating group with image 2023-01-29 12:01:19 +00:00
Evgeny Poberezkin
3c43c5d254 ios: import/export localizations 2023-01-28 17:48:37 +00:00
Evgeny Poberezkin
0bae260fae mobile: translations (#1850)
* Added translation using Weblate (Chinese (Simplified))

* Added translation using Weblate (Chinese (Simplified))

* Translated using Weblate (Chinese (Simplified))

Currently translated at 2.7% (25 of 911 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 2.6% (23 of 854 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 2.7% (25 of 911 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 7.0% (60 of 854 strings)

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

* 4.5-beta.0: Android 93, iOS 119

* Added translation using Weblate (Chinese (Simplified))

* Added translation using Weblate (Chinese (Simplified))

* Translated using Weblate (Chinese (Simplified))

Currently translated at 2.7% (25 of 911 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 2.6% (23 of 854 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 2.7% (25 of 911 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 7.0% (60 of 854 strings)

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

* Translated using Weblate (French)

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

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

* Translated using Weblate (Italian)

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

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

* Translated using Weblate (French)

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

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 10.7% (92 of 859 strings)

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

* Added translation using Weblate (Chinese (Traditional))

* Added translation using Weblate (Chinese (Traditional))

* Added translation using Weblate (English (United Kingdom))

* Added translation using Weblate (Spanish)

* Translated using Weblate (Chinese (Simplified))

Currently translated at 10.1% (95 of 936 strings)

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

* remove UK English file

---------

Co-authored-by: sith-on-mars <groguko36@pm.me>
Co-authored-by: Ophiushi <Ophiushi@users.noreply.hosted.weblate.org>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: Albert Bob <vicdorke@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
2023-01-28 17:44:12 +00:00
Evgeny Poberezkin
e694848cd5 4.5-beta.0: Android 94, iOS 119 2023-01-28 17:11:32 +00:00
Evgeny Poberezkin
f5f61c5806 core: update simplexmq, v4.5.0.0 2023-01-28 13:59:46 +00:00
Stanislav Dmitrenko
96c1c1d439 android: drafts (#1849)
* android: drafts

* empty line

* delete unused voice files

* finish recording properly

* refactor

* fix

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-01-28 13:35:31 +00:00
Evgeny Poberezkin
3e560278b6 ios: update chat previews, show filename in drafts (#1847)
* ios: update chat previews, show filename in drafts

* save and restore images/file/voice for draft

* refactor image

* it was a wrong value

* use param label

* proper stop of voice recording

* safe draft logic

* different way of finishing recording

* keep condition

* refactor

* fix live

* fix

* refactor

* fix

* simplify

* add space after filename in draft

---------

Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
2023-01-27 22:09:39 +00:00
Evgeny Poberezkin
6e131e0bad ios: disable current user profile button 2023-01-27 12:54:42 +00:00
Stanislav Dmitrenko
bd158f3b0d android: user-specific settings (#1848)
* android: multiuser-peruser

* padding

* bigger padding

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-01-27 12:28:44 +00:00
M Sarmad Qadeer
a96fb2f8d1 website: improvements to design (#1405)
* updated the design of join simplex

* updated the design of Comparison section

* updated the design of Simplex explained

* updated the design of Simplex network

* updated the design of private section

* updated the design of features section

* updated the design of unique section

* updated the design of privacy matters

* added improvements in design

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-01-27 12:07:22 +00:00
Evgeny Poberezkin
148261a1ee ios: allow to reply in another chat without losing draft 2023-01-26 23:28:56 +00:00
Stanislav Dmitrenko
22b7aa90b2 android: multiuser-notifications (#1846) 2023-01-26 23:00:54 +00:00
Stanislav Dmitrenko
88d9e70ef8 android: mutliuser-calls (#1845)
* android: mutliuser-calls

* tint color of icon

* userId from function

* better line

* missing question

* bigger avatar

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-01-26 21:36:49 +00:00
Evgeny Poberezkin
a0cfc19063 Merge pull request #1844 from simplex-chat/av/multiuser-ui
android: multiusers-profilemanager
2023-01-26 20:49:02 +00:00
Avently
451aab46dc disable deleting the last user 2023-01-26 23:46:10 +03:00
Avently
3b3db562cd added description 2023-01-26 23:14:33 +03:00
Avently
15ed6ea4a6 android: multiusers-profilemanager 2023-01-26 21:23:52 +03:00
Evgeny Poberezkin
9f661ff4e2 Merge pull request #1698 from simplex-chat/users
support multiple user profiles
2023-01-26 10:14:22 +00:00
Evgeny Poberezkin
52c39c9641 Merge branch 'master' into users 2023-01-25 23:31:18 +00:00
Evgeny Poberezkin
0dab486ac8 readme: update group links 2023-01-25 22:48:08 +00:00
Evgeny Poberezkin
d640f4a5d5 readme: remove broken twitter badge 2023-01-25 21:04:19 +00:00
Stanislav Dmitrenko
1c47bfbf44 android: better user picker layout (#1842)
* android: multiuser-fixes

* update paddings

* progressIndicator

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-01-25 20:43:02 +00:00
Stanislav Dmitrenko
db3fc4ee7b android: multiuser-userpicker (#1839)
* android: multiuser-userpicker

* sizes of buttons

* update paddings

* change names

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-01-25 15:30:30 +00:00
JRoberts
74df35d3b0 core: add multiple users tests for subscription, chat item expiration, timed messages (#1840) 2023-01-25 19:29:09 +04:00
Evgeny Poberezkin
c01c629f73 mobile: use GMT timezone in filenames to prevent leaking user location (#1837)
* ios: use GMT timezone in filenames to prevent leaking user location

* android: use GMT timestamp in generated file names
2023-01-25 11:48:54 +00:00
Evgeny Poberezkin
25e4a1e86d ios: fix layout of voice message (#1836)
* ios: fix layout of voice message

* fix layout

* prevent translations
2023-01-25 10:18:02 +00:00
Evgeny Poberezkin
e27013071b ios: preserve message draft in the latest chat only (#1834)
* ios: preserve message draft in the latest chat only

* show attachment icon and formatting in draft

* button to remove message text

* show icon for active draft, refactor

* add voice message duration to draft
2023-01-25 08:35:25 +00:00
Stanislav Dmitrenko
2679bc2e94 ios: enable swipe to go back from chat to list (#1824)
* ios: Testing workaround of a crash

* another try

* complete

* added file

* enable swipe to go back from ChatView

* Revert "enable swipe to go back from ChatView"

This reverts commit 22de79505c.

* ios: enable swipe to go back from ChatView

* remove title change

* remove unused

* remove unused variable

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-01-24 19:24:46 +00:00
Evgeny Poberezkin
93ab713748 ios: choose user deletion mode (#1833)
* ios: choose user deletion mode

* update text

* refactor, disable button

* darker profile icon colors

* do not delete active user if changing user failed
2023-01-24 19:00:30 +00:00
JRoberts
bc1d86e303 core: send agent DEL events to view (#1832) 2023-01-24 20:07:35 +04:00
Evgeny Poberezkin
b386346cf1 core: update syntact for /_delete (#1831) 2023-01-24 16:00:32 +00:00
JRoberts
b6db41dd50 update simplexmq (complete) 2023-01-24 18:46:02 +04:00
Stanislav Dmitrenko
6bca013e67 android: multiuser-api (#1829)
* android: multiuser-api

* add line in try-catch block

* changed lines position

* when -> if

* take condition outside

* mutable version of objects and usage of a new function

* changed additional places in code

* added toMap() so state will be updated

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-01-24 14:29:20 +00:00
Evgeny Poberezkin
e538826cd8 Merge branch 'master' into users 2023-01-24 14:09:38 +00:00
JRoberts
a7a56ea1d9 core: use batch delete api when deleting unused group contacts (#1830) 2023-01-24 17:58:08 +04:00
Evgeny Poberezkin
77d0f70270 update simplexmq 2023-01-24 13:39:12 +00:00
JRoberts
2a20f78877 core: use batch connection deletion api (#1814) 2023-01-24 16:24:34 +04:00
Evgeny Poberezkin
b027708828 4.4.4: Android 92, iOS 118 2023-01-23 20:17:52 +00:00
Evgeny Poberezkin
a0bf298b66 Merge branch 'master' into users 2023-01-23 18:45:52 +00:00
Evgeny Poberezkin
e3b22d83ad Merge branch 'master' into users 2023-01-23 18:45:24 +00:00
JRoberts
ab4e4e1db9 core: test cancelling inline file transfer (#1827)
* core: test cancelling inline file transfer

* tests
2023-01-23 18:27:44 +00:00
Stanislav Dmitrenko
a393bc8163 ios: Testing workaround of a crash (#1789)
* ios: Testing workaround of a crash

* another try

* complete

* added file

* enable swipe to go back from ChatView

* Revert "enable swipe to go back from ChatView"

This reverts commit 22de79505c.

* refactor

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-01-23 18:17:33 +00:00
Evgeny Poberezkin
3bc4fd222c core: fix cancelling sending inline files (#1826)
* core: fix cancelling sending inline files

* add comments

* typos

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2023-01-23 21:17:42 +04:00
JRoberts
1b01dcec6d core: fix inline file transfer - optional connection ids in RcvFileInfo, update rcv_file_inline on accept (#1823)
* core: optional connection id in RcvFileInfo

* query

* check snd cancel

* Revert "check snd cancel"

This reverts commit f16651345d.

* update rcv_file_inline
2023-01-23 15:55:19 +00:00
Stanislav Dmitrenko
1d5c361b9a ios: restore scroll and update user profile in user profile menu (#1811)
* ios: Small UserPicker fixes

* update scroll

* update current user and update users list

* refactor

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-01-23 15:48:29 +00:00
Stanislav Dmitrenko
4cd396a0d2 ios: Multiuser calls (#1800)
* ios: Multiuser calls

* counter update on badge

* padding before profile info in call view

* underline in name

* change after merge

* do not show Simplex Info button if users already created

* unread counter

* do not increase badge counter when chat has disabled notifications

* update incoming call

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-01-23 13:20:58 +00:00
Evgeny Poberezkin
bcc80be8e9 Merge branch 'master' into users 2023-01-22 23:08:53 +00:00
Evgeny Poberezkin
5d12217eab 4.4.4: Android 91, iOS 117 2023-01-22 19:30:19 +00:00
Evgeny Poberezkin
4b0046a60b mobile: show version information (#1820)
* mobile: show version information

* export localizations
2023-01-22 18:34:01 +00:00
Evgeny Poberezkin
114b76e3f8 core: update version response (#1819)
* core: update version response

* add simplexmq commit and version to version info

* refactor
2023-01-22 15:16:45 +00:00
Evgeny Poberezkin
c72aa5d074 Merge branch 'master' into users 2023-01-21 23:14:26 +00:00
Evgeny Poberezkin
2e9882b0bd Merge branch 'master' into users 2023-01-21 23:00:06 +00:00
Evgeny Poberezkin
8ff8f9d695 core: add build timestamp to version information (#1816) 2023-01-21 22:56:33 +00:00
Evgeny Poberezkin
1e3c2024bb android: add localization of "offered/cancelled" feature items, closes #1766 (#1815) 2023-01-21 16:47:26 +00:00
Evgeny Poberezkin
8bec0004cc mobile: UI to choose transport isolation mode (#1813)
* mobile: UI to choose transport isolation mode

* typo

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* ios: update alerts

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2023-01-21 16:05:09 +00:00
JRoberts
c337a6888d core: delete previous contact calls when receiving a new one (#1812) 2023-01-21 16:40:24 +04:00
JRoberts
e72d4638d2 core: exlude muted chats from user unread count (#1810) 2023-01-20 20:48:24 +04:00
JRoberts
7dd4dc3b40 core: support accepting contact requests for non active users (for accepting via notification) (#1809)
* core: support accepting contact requests for non active users (for accepting via notification)

* getContactRequest'
2023-01-20 17:55:57 +04:00
JRoberts
980c7a9ddd ios: use agent connection id as key for network statuses map (#1808) 2023-01-20 17:35:39 +04:00
Evgeny Poberezkin
cb5f26d354 ios: only show menu if there is more than 1 user, do not show unread count (only badge) 2023-01-20 13:17:41 +00:00
Evgeny Poberezkin
04d886e546 ios: user profiles view, per-user settings (#1801)
* ios: user profiles view, per-user settings

* remove comment

* bold profile name
2023-01-20 12:38:38 +00:00
Evgeny Poberezkin
ed12ccaac2 ios: update library 2023-01-20 12:28:45 +00:00
Evgeny Poberezkin
e9e9286fbb Merge branch 'master' into users 2023-01-20 12:22:29 +00:00
Evgeny Poberezkin
f4ffa5237c 4.4.4-beta.1: Android 90, iOS 116 2023-01-20 12:02:24 +00:00
JRoberts
ef15dca0b4 core: don't filter out non active user connections on UP & DOWN agent events; use agent connection id instead of db connection id for ContactRef (#1807) 2023-01-20 15:02:27 +04:00
JRoberts
396b3ae639 ios: maintain connections network statuses map separately from chats (allows to keep track of network statuses for all users) (#1803) 2023-01-20 14:56:05 +04:00
Evgeny Poberezkin
c409f58067 mobile: add PING count to network config, make advanced network config available without dev tools (#1805)
* mobile: add PING count to network config, make advanced network config available without dev tools

* export ios translations

* add 120 sec PING interval back
2023-01-20 10:55:12 +00:00
Evgeny Poberezkin
69ca731641 core: process push notifications for any user (#1806)
* core: process push notifications for any user

* return regardless

* refactor

* more refactor
2023-01-20 10:48:25 +00:00
Evgeny Poberezkin
006a30e65c update simplexmq 2023-01-19 19:41:19 +00:00
Evgeny Poberezkin
0a9aa8b3d2 translations: update French and Italian (#1799)
* Translated using Weblate (French)

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

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

* Translated using Weblate (Italian)

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

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

* import localizations

* re-export localizations

Co-authored-by: Ophiushi <Ophiushi@users.noreply.hosted.weblate.org>
Co-authored-by: random r <epsilin@yopmail.com>
2023-01-19 19:27:38 +00:00
Evgeny Poberezkin
69196084fc core: update simplexmq 2023-01-19 19:17:32 +00:00
JRoberts
cf4105e256 core: add connection id to ContactRef (#1798) 2023-01-19 20:54:00 +04:00
Stanislav Dmitrenko
ad6aa10cd2 ios: Multiusers feature continue (#1793)
* ios: Multiusers feature continue

* Logging of user in responses

* UserId

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* Undo ugly user inclusion into functions.  Now it's in backend

* Do not set active user if it's unchanged

* Blank line

* if

* Change active user function

* refactor

* refactor

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* Alert

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2023-01-19 16:22:56 +00:00
Stanislav Dmitrenko
ba29d0242e core: add user to RcvCallInvitation (#1797)
* core: Include user into RcvCallInvitation

* update build

* parens

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-01-19 16:00:41 +00:00
JRoberts
ca64ed9784 core: option to reuse servers for new user; support for users to configure same smp servers (add user_id to smp_servers UNIQUE constraint) (#1792) 2023-01-18 18:49:56 +04:00
JRoberts
a227e21fcf core: support user deletion (#1788)
* core: support user deletion

* doSendCancel

* Apply suggestions from code review

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* sendCancel

* refactor

* error to view

* refactor

* refactor

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-01-18 17:08:48 +04:00
Evgeny Poberezkin
7c9c358ce2 ios: update core library 2023-01-18 11:01:51 +00:00
Evgeny Poberezkin
84237f79fc core: refactor (#1764) 2023-01-18 10:20:55 +00:00
Evgeny Poberezkin
80e0bfb61f core: v4.4.4 2023-01-17 18:10:47 +00:00
Stanislav Dmitrenko
153f80fe64 ios: menu to switch active user profile (#1758)
* ios: User chooser UI

* Change

* Changes

* update view

* fix layout/refactor

* fix preview

* wider menu, update label

* hide Your profiles button

* Clickable background that hides userChooser

* No click listener

* Better animation

* Disabled scrolling for small number of items

* Separated scrollview and buttons

* No transition

* Re-indent

* Limiting width by the longest label

* UserManagerView

* Adapted API

* Hide user chooser after selection

* Top counter,  users refactor

* Padding

* use VStack to fix layout bug

* eol

* rename: rename to getUserChatData

* update layout

* s/semibold/medium

* remove SettingsButton view

* rename

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-01-17 17:47:37 +00:00
Evgeny Poberezkin
19881703e5 core: increase default internal queue max size to 1024 2023-01-17 14:07:47 +00:00
JRoberts
a668bd5736 core: cleanup obsolete chat item deletion code (see #1625) (#1787) 2023-01-17 16:58:36 +04:00
JRoberts
e8cab01c03 core: update simplexmq (fkey indexes) (#1786) 2023-01-17 16:41:19 +04:00
JRoberts
3f72633d22 Merge branch 'master' into users 2023-01-17 15:58:02 +04:00
JRoberts
5c7ad0926c core: add missing fkey indexes (#1785) 2023-01-17 15:45:37 +04:00
Evgeny Poberezkin
7eca44bb84 4.4.4-beta.0: update simplexmq 2023-01-17 10:09:36 +00:00
JRoberts
2f39cfd86f core: support marking chat items read for any user (#1784) 2023-01-17 13:08:51 +04:00
JRoberts
2fdc23274d core: return user unread counts on ListUsers command (#1763)
* core: return user unread counts on ListUsers command

* split

* tests

* refactor

* viewUserInfo

* refactor

* remove omit nothing

* corrections

* fix

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-01-16 18:57:31 +00:00
Evgeny Poberezkin
91a39cae23 core: fix error handling (#1761)
* core: fix error handling

* fix tests
2023-01-16 17:25:06 +00:00
Evgeny Poberezkin
882966d5d3 ios: update network config (#1760) 2023-01-16 15:10:16 +00:00
JRoberts
3ed5e6e50b core: support receiving file by id for any user (not only current) (#1759) 2023-01-16 17:51:25 +04:00
JRoberts
df6cec6a32 core: update simplexmq (session mode, users commands) (#1757) 2023-01-16 17:00:24 +04:00
JRoberts
24c47657f4 Merge branch 'master' into users 2023-01-16 16:37:13 +04:00
JRoberts
cf6afb7687 Merge branch 'master' into users 2023-01-16 16:24:38 +04:00
Evgeny Poberezkin
774af334fd terminal: command to show the most recent chats (#1756)
* terminal: command to show the list of the last active chats

* indent for chats without messages, help

* update command in the test
2023-01-16 12:10:47 +00:00
JRoberts
9dc6c1327f core: manage calls for all users (#1748) 2023-01-16 15:06:03 +04:00
Evgeny Poberezkin
af414d7f6e terminal: options for log level and internal queue sizes (#1755)
* terminal: log levels

* option for internal queue sizes
2023-01-16 09:13:46 +00:00
JRoberts
a040fa65bb core: run cleanup for all users (#1746) 2023-01-14 19:21:10 +04:00
JRoberts
9fc26ca799 core: start chat item expiration thread for new users (#1745) 2023-01-14 17:52:40 +04:00
Evgeny Poberezkin
9dad55ce8d 4.4.3: iOS 115, Android 89 2023-01-14 11:46:29 +00:00
JRoberts
6e0addbea3 core: add user to CRSmpTestResult response (#1744) 2023-01-14 15:45:42 +04:00
JRoberts
e452edb781 core: subscribe all users (#1743) 2023-01-14 15:45:13 +04:00
Evgeny Poberezkin
a3283708e7 translations: add Italian, updates (#1741)
* Added translation using Weblate (Italian)

* Added translation using Weblate (Italian)

* Translated using Weblate (French)

Currently translated at 100.0% (906 of 906 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 99.6% (850 of 853 strings)

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

* Translated using Weblate (Italian)

Currently translated at 24.9% (226 of 906 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 26.3% (225 of 853 strings)

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (Italian)

Currently translated at 28.8% (261 of 906 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (853 of 853 strings)

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

* Translated using Weblate (Italian)

Currently translated at 58.2% (528 of 906 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% (906 of 906 strings)

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (German)

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

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

* Added translation using Weblate (Italian)

* Added translation using Weblate (Italian)

* Translated using Weblate (French)

Currently translated at 100.0% (906 of 906 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 99.6% (850 of 853 strings)

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

* Translated using Weblate (Italian)

Currently translated at 24.9% (226 of 906 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 26.3% (225 of 853 strings)

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (Italian)

Currently translated at 28.8% (261 of 906 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (853 of 853 strings)

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

* Translated using Weblate (Italian)

Currently translated at 58.2% (528 of 906 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% (906 of 906 strings)

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (907 of 907 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 38.5% (329 of 853 strings)

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

* Translated using Weblate (Italian)

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

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

* correction

* correction

* correction

* ios: import localizations

* ios: re-export localizations

Co-authored-by: Ophiushi <ptlfr@pm.me>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: Ophiushi <Ophiushi@users.noreply.hosted.weblate.org>
2023-01-13 23:14:01 +00:00
Evgeny Poberezkin
e833d66557 Merge branch 'stable' 2023-01-13 19:18:51 +00:00
Stanislav Dmitrenko
71f5b51350 ios: change network config in NSE when updated via UI (#1731)
* ios: Apply changed network config to NSE

* Better

* Separate function

* rename

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-01-13 19:05:53 +00:00
Stanislav Dmitrenko
20cec4db11 mobile: do not reset chat preferences on profile update (#1740)
* android: Do not reset prefs on profile update

* ios: Include prefs into Profile/LocalProfile
2023-01-13 18:57:54 +00:00
Stanislav Dmitrenko
2085dc5d60 android: Fix blocked thread while making live messages (#1739)
* android: Fix blocked thread while making live messages

* Test

* Revert "Test"

This reverts commit bd8a5b6ca0.
2023-01-13 18:33:52 +00:00
JRoberts
9290fcc6b2 core: set active prompt to none when changing current user (#1738) 2023-01-13 21:01:36 +04:00
JRoberts
0c3d643408 core: expire chat items for all users (#1737) 2023-01-13 21:01:26 +04:00
Micha├лl Bitard
6d5c3ae484 Fix typo (#1732) 2023-01-13 15:49:58 +00:00
JRoberts
cccdcef914 core: add delays to tests to prevent output races (#1736) 2023-01-13 16:26:55 +04:00
Evgeny Poberezkin
e63e158b2d core: refactor withUserId (#1735)
* refactor withUserId

* update

* more
2023-01-13 16:24:54 +04:00
JRoberts
892b91e958 core: update simplexmq (subscribe users in different sessions) (#1734) 2023-01-13 15:14:46 +04:00
JRoberts
fb04108b11 Merge branch 'master' into users 2023-01-13 14:19:21 +04:00
JRoberts
424328b9d1 core: agent users (#1727) 2023-01-13 13:54:07 +04:00
JRoberts
e73f5c40cf 4.4.2: ios 114, Android 88 2023-01-12 18:38:58 +04:00
JRoberts
4c960bdc44 4.4.2 2023-01-12 16:46:28 +04:00
JRoberts
dcb82951ed core: catch errors when sending messages in loops where they haven't been previously caught (#1729) 2023-01-12 16:31:27 +04:00
Stanislav Dmitrenko
138cce4436 ios: fix opening via notification returning to chat list when screen lock is enabled (#1725)
* ios: Fixed broken chat push/pop logic

* remove binding parameter

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-01-11 22:48:52 +00:00
Evgeny Poberezkin
98417dafc4 4.4.1: ios 113, Android 87 2023-01-11 17:54:24 +00:00
Evgeny Poberezkin
e8374be19c mobile: set defaults consistently (protected screen: iOS off/Android on, accept images: on, faster image transfer: on) (#1724)
* ios: set defaults consistently (protected screen: off, accept images: on, faster image transfer: on)

* android: transfer images faster by default
2023-01-11 17:09:17 +00:00
Stanislav Dmitrenko
62a2f61751 android: Fix non-unique chat item id in listState (#1723)
* android: Fix non-unique chat item id in listState

* Test

* Revert "Test"

This reverts commit 6625bce138.
2023-01-11 16:41:34 +00:00
JRoberts
7323bb4333 Merge branch 'master' into users 2023-01-11 18:38:55 +04:00
Evgeny Poberezkin
2d47175f94 ios: disable reply/edit actions and deletion of live item in live mode (#1722) 2023-01-11 17:29:09 +04:00
Evgeny Poberezkin
a6d7604d21 mobile: send live message when there is any content (#1721)
* ios: send live message when there is any content

* android: improve live message logic

* fix, refactor

* prohibit live messages with quotes
2023-01-11 12:01:02 +00:00
JRoberts
9e3573fc76 android: fix send button being disabled on images, files & voice messages (#1720)
* android: fix send button being disabled on images, files & voice messages

* rename view

* format
2023-01-11 08:59:04 +00:00
JRoberts
41e873d5ca core: multiple users view, tests (#1710) 2023-01-11 11:00:28 +04:00
Evgeny Poberezkin
13ebaf587e 4.4.1-beta.1: iOS 112, Android 86 2023-01-10 23:21:37 +00:00
Stanislav Dmitrenko
61e20550bc core: Updated scripts for downloading libs (#1712) 2023-01-10 20:22:18 +00:00
Stanislav Dmitrenko
d1cc5c1769 ios: Better check for existing of image's alpha (#1718)
* ios: Better check for existing of image's alpha

* Allow non-transparent pixels

* optimize

* remove prints

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-01-10 19:39:42 +00:00
Stanislav Dmitrenko
16b041c8c6 ios: Live messages without sending an empty text (#1714)
* ios: Live messages without sending an empty text

* Custom Equatable

* Changes

* Change

* Fix liveMessage not hiding

* Refactoring

* Refactoring

* No animation when removing dummy live message item

* Check

* Anim

* Animation

* whitespace

* refactor

* Fix race

* Better fix of race

* fix race condition

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-01-10 19:12:48 +00:00
JRoberts
810f248c74 core: test async file transfer (sender & receiver restarts); close files in stopChatController; handle openFile error in getFileHandle (#1716) 2023-01-10 20:52:59 +04:00
JRoberts
813fecddfe core: fix live file transfers queries (#1715) 2023-01-10 16:22:21 +04:00
Stanislav Dmitrenko
5a7d61c964 android: Live messages without sending an empty text (#1709)
* android: Live messages without sending an empty text

* Better quoted messages handling

* Do not add item into preview

* Change

* Changes
2023-01-09 18:30:26 +00:00
JRoberts
ad1b091b18 Merge branch 'master' into users 2023-01-09 17:02:38 +04:00
Evgeny Poberezkin
cba24983e6 4.4.1-beta.0: iOS 111, Android 85 2023-01-08 16:41:40 +00:00
Evgeny Poberezkin
4dc2a1b72d Revert "core: include commit information in /v response (#1705)"
This reverts commit 3d4e4e2ef9.
2023-01-08 13:13:13 +00:00
Evgeny Poberezkin
3d4e4e2ef9 core: include commit information in /v response (#1705) 2023-01-07 16:38:35 +00:00
JRoberts
113c67ec95 core: disable connections on repeat AUTH errors (#1704) 2023-01-07 19:47:51 +04:00
Stanislav Dmitrenko
a2e887024f android: Fixed compatibility with API 29 (#1674) 2023-01-07 14:24:28 +00:00
JRoberts
37262b3ed5 core: fix view agent error context text (#1700) 2023-01-06 19:58:03 +04:00
Evgeny Poberezkin
dca4fe7701 Merge branch 'stable' 2023-01-06 13:16:21 +00:00
Evgeny Poberezkin
88c9334d18 readme: SOL address for donations 2023-01-06 13:14:26 +00:00
JRoberts
58f06aa821 core: print error context on store internal errors (#1699) 2023-01-06 14:22:16 +04:00
JRoberts
ae5deab8d3 core: print error context on agent errors (#1697) 2023-01-06 13:11:21 +04:00
JRoberts
bb0482104c core, ios, android: add UserId to api commands (#1696) 2023-01-05 20:38:31 +04:00
Evgeny Poberezkin
edfece3206 core: test for live messages (#1694) 2023-01-05 09:08:31 +00:00
Evgeny Poberezkin
c32cf8055d ios: update library (better quote error handing) 2023-01-04 20:51:16 +00:00
Evgeny Poberezkin
72ec03a822 ios: localize "feature offered/cancelled" items (#1689)
* ios: localize "feature offered/cancelled" items

* import
2023-01-04 20:48:23 +00:00
Evgeny Poberezkin
d89e0efedd translations: ios German, French (#1687)
* translations: ios German, French

* reexport ios
2023-01-04 20:36:50 +00:00
Evgeny Poberezkin
707e8592d9 translations: German, French (#1682)
* Translated using Weblate (German)

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

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

* Translated using Weblate (German)

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

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

Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: Ophiushi <ptlfr@pm.me>
2023-01-04 20:25:33 +00:00
JRoberts
fa9e0086f6 core: multiple users api (#1679)
* api

* UCR

* Revert "UCR"

This reverts commit 1f98d25192.

* comment

* events User

* events in api User

* CRActiveUser in APISetActiveUser

* process message with/without connection

* refactor

* mute error

* user in api responses

* name

* lost response

* user in CRChatCmdError

* compiles

* user in CRChatError

* -- UserId

* mute unused warning

* catch in withUser

* remove comment

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-01-04 21:06:28 +04:00
Evgeny Poberezkin
a1b27e9a99 update simplexmq (better handling of quota errors) 2023-01-04 15:07:33 +00:00
Evgeny Poberezkin
f68d8fd97c update readme 2023-01-03 13:13:51 +00:00
Evgeny Poberezkin
abff42a264 blog: v4.4 (#1675)
* blog: v4.4

* add images

* update post

* update readme, roadmap

* corrections

* correction
2023-01-03 13:07:30 +00:00
Evgeny Poberezkin
c1ced70836 Merge branch 'stable' 2023-01-01 14:06:51 +00:00
Evgeny Poberezkin
e14966d36e blog: v4.4 release announcement placeholder 2023-01-01 14:05:05 +00:00
Evgeny Poberezkin
97943fc609 ios: v4.4 release, build 110 2023-01-01 14:01:21 +00:00
shum
0f143b2e77 nix: bump lock 2022-12-31 19:42:31 +00:00
Evgeny Poberezkin
be10dcbcfc ios: build 109 2022-12-31 19:40:43 +00:00
Evgeny Poberezkin
97dbec927c ios: build 108 2022-12-31 18:23:26 +00:00
Evgeny Poberezkin
b4879ca2a3 ios: fix user chat preferences view 2022-12-31 13:38:55 +00:00
Evgeny Poberezkin
15884c0169 4.4.0: iOS 107, Android 84 2022-12-31 11:47:59 +00:00
Evgeny Poberezkin
9f2d5486b6 mobile: fix race condition with live messages (when message is "revived" when it is being sent as no longer live тАУ possibly core should reject "live" updates to non-live messages too) (#1668)
* mobile: fix race condition with live messages (when message is "revived" when it is being sent as no longer live тАУ possibly core should reject "live" updates to non-live messages too)

* android: move delay for live messages
2022-12-31 10:25:32 +00:00
JRoberts
80f0108b41 ios: correctly update chat when opening from another chat via notification (#1667) 2022-12-30 21:47:11 +04:00
Evgeny Poberezkin
c37a7ebfe7 ios: import/export localizations 2022-12-30 17:05:32 +00:00
Evgeny Poberezkin
02c2c65d41 translations (#1661)
* Translated using Weblate (French)

Currently translated at 100.0% (845 of 845 strings)

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

* Translated using Weblate (French)

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

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

* Translated using Weblate (French)

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

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (906 of 906 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (845 of 845 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (845 of 845 strings)

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

* Translated using Weblate (French)

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

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

* Translated using Weblate (French)

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

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (906 of 906 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (845 of 845 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (850 of 850 strings)

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

* Translated using Weblate (French)

Currently translated at 99.6% (847 of 850 strings)

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

* Translated using Weblate (French)

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

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

* Translated using Weblate (German)

Currently translated at 90.9% (824 of 906 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 92.4% (786 of 850 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% (845 of 845 strings)

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

* Translated using Weblate (French)

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

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

* Translated using Weblate (French)

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

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (906 of 906 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (845 of 845 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (850 of 850 strings)

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

* Translated using Weblate (French)

Currently translated at 99.6% (847 of 850 strings)

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

* Translated using Weblate (French)

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

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

* Translated using Weblate (German)

Currently translated at 90.9% (824 of 906 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 92.4% (786 of 850 strings)

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

* Translated using Weblate (German)

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

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

Co-authored-by: Ophiushi <ptlfr@pm.me>
Co-authored-by: mlanp <github@lang.xyz>
2022-12-30 17:02:49 +00:00
JRoberts
7c4700b238 ios: check chats not empty before showing lock notice (#1666) 2022-12-30 16:17:56 +00:00
JRoberts
54190ffff9 core: add db indexes for faster group deletion (#1664) 2022-12-30 16:34:42 +04:00
Evgeny Poberezkin
17eed9662e ios: export localizations 2022-12-29 23:31:35 +00:00
Stanislav Dmitrenko
bb116bccb4 android: Fallback to manual parsing of apiChats and apiChat responses (#1660)
* android: Fallback to manual parsing of apiChats and apiChat responses

* Different icon

* eol
2022-12-29 22:27:08 +00:00
JRoberts
6cc267689e ios: fallback to manual parsing of apiChats and apiChat responses (#1659) 2022-12-29 18:15:19 +04:00
Evgeny Poberezkin
0e6909845f mobile: preserve group description in profile (#1658) 2022-12-28 16:59:25 +00:00
JRoberts
96ad9faa85 docs: user profiles rfc (#1656) 2022-12-28 16:28:07 +04:00
Evgeny Poberezkin
768c497025 readme: change group links (#1657) 2022-12-28 11:30:57 +00:00
Evgeny Poberezkin
3ec29d8ef4 4.4-beta.4: ios 106, android 83 (fixes wrong type/ios crash) 2022-12-27 20:02:43 +00:00
JRoberts
6c4b92531f android: version 4.4-beta.3 (82) 2022-12-27 20:56:23 +04:00
JRoberts
46d6159da5 ios: version 4.4 beta (105) 2022-12-27 20:48:34 +04:00
JRoberts
aab6e1c52f ios, android: set ttl to 1 day when accepting timed messages w/t configured ttl (#1654) 2022-12-27 19:24:33 +04:00
Evgeny Poberezkin
c0a01318b5 mobile: French translations (#1655)
* mobile: items with feature offers (#1623)

* mobile: items with feature offers

* ios interactive contact/user preference change items

* android: interactive preference items

* Translated using Weblate (French)

Currently translated at 8.1% (68 of 831 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% (784 of 784 strings)

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

* Translated using Weblate (French)

Currently translated at 10.4% (87 of 831 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% (785 of 785 strings)

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

* Translated using Weblate (French)

Currently translated at 10.5% (88 of 831 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% (785 of 785 strings)

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

* Translated using Weblate (French)

Currently translated at 14.2% (122 of 855 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% (785 of 785 strings)

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

* Translated using Weblate (French)

Currently translated at 15.9% (137 of 858 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 18.2% (157 of 858 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% (785 of 785 strings)

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

* Translated using Weblate (French)

Currently translated at 36.3% (312 of 858 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% (785 of 785 strings)

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

* Translated using Weblate (French)

Currently translated at 54.0% (464 of 858 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% (785 of 785 strings)

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

* Translated using Weblate (French)

Currently translated at 81.0% (695 of 858 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% (785 of 785 strings)

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

* Translated using Weblate (French)

Currently translated at 82.1% (705 of 858 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 85.9% (756 of 880 strings)

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

* import/export localizations

Co-authored-by: Ophiushi <ptlfr@pm.me>
2022-12-27 13:21:55 +00:00
Evgeny Poberezkin
13090ff6ed core: do not log TLS handshake errors by default, option to enable (#1652)
* core: do not log TLS handshake errors by default, option to enable

* update simplexmq
2022-12-27 12:05:13 +00:00
Evgeny Poberezkin
90a20cd52f mobile: change live message button to lightning bolt (#1653) 2022-12-27 09:18:20 +00:00
Evgeny Poberezkin
74245d3f2b core: agent stats (#1650) 2022-12-26 22:24:34 +00:00
JRoberts
e48452ccff android: show what is new in the latest version (#1651) 2022-12-26 21:46:56 +04:00
JRoberts
39370ba1ef ios: fix - restore Save button in voice message context menu (#1647) 2022-12-26 14:08:58 +00:00
Evgeny Poberezkin
a02cfb4f41 ios: show what is new in the latest version (#1644)
* ios: show what is new in the latest version

* add OK button to WhatsNew

* separate state for nav buttons
2022-12-26 14:08:01 +00:00
JRoberts
4370012b8a ios: fix navigation to member info view (#1648) 2022-12-26 13:45:02 +00:00
JRoberts
20c33aea72 android: show connect via url alert in chat list instead of notifications mode on fresh app (#1649) 2022-12-26 13:43:26 +00:00
Evgeny Poberezkin
c11a1aa0e6 ios: disallow editing text attributes 2022-12-25 13:58:07 +00:00
Evgeny Poberezkin
166b789f3c ios: v4.4 beta (104) 2022-12-25 09:48:25 +00:00
Evgeny Poberezkin
bbc26e272c v4.4-beta.2: android (81) 2022-12-24 21:59:55 +00:00
Evgeny Poberezkin
6c839f8075 android: fix voice recording in groups 2022-12-24 21:47:58 +00:00
Evgeny Poberezkin
be91f97c83 ios: disable screen protection by default 2022-12-24 15:41:31 +00:00
413 changed files with 90424 additions and 9658 deletions

View File

@@ -5,7 +5,7 @@ on:
branches:
- master
- stable
- sqlcipher
- users
tags:
- "v*"
pull_request:
@@ -91,6 +91,10 @@ jobs:
echo " extra-lib-dirs: /usr/local/opt/openssl@1.1/lib" >> cabal.project.local
echo " flags: +openssl" >> cabal.project.local
- name: Install pkg-config for Mac
if: matrix.os == 'macos-latest'
run: brew install pkg-config
- name: Unix prepare cabal.project.local for Ubuntu
if: matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-18.04'
shell: bash
@@ -109,7 +113,7 @@ jobs:
- name: Unix test
if: matrix.os != 'windows-latest' && matrix.os != 'ubuntu-20.04'
timeout-minutes: 10
timeout-minutes: 20
shell: bash
run: cabal test --test-show-details=direct

5
.gitignore vendored
View File

@@ -42,12 +42,15 @@ stack.yaml.lock
# Temporary test files
tests/tmp
tests/tmp*
logs/
*.devcontainer
# for website
website/node_modules/
website/src/blog/
website/translations.json
website/src/_data/supported_languages.json
website/src/img/images/
website/src/images/
# Generated files

View File

@@ -1,3 +1,5 @@
| Updated 07.02.2023 | Languages: EN, [FR](/docs/lang/fr/README.md) |
<img src="images/simplex-chat-logo.svg" alt="SimpleX logo" width="100%">
# SimpleX - the first messaging platform that has no user identifiers of any kind - 100% private by design!
@@ -7,7 +9,6 @@
[![GitHub release](https://img.shields.io/github/v/release/simplex-chat/simplex-chat)](https://github.com/simplex-chat/simplex-chat/releases)
[![Join on Reddit](https://img.shields.io/reddit/subreddit-subscribers/SimpleXChat?style=social)](https://www.reddit.com/r/SimpleXChat)
[![Follow on Mastodon](https://img.shields.io/mastodon/follow/108619463746856738?domain=https%3A%2F%2Fmastodon.social&style=social)](https://mastodon.social/@simplex)
[![Follow on Twitter](https://img.shields.io/twitter/follow/SimpleXChat?style=social)](https://twitter.com/SimpleXChat)
[<img src="https://github.com/simplex-chat/.github/blob/master/profile/images/apple_store.svg" alt="iOS app" height="42">](https://apps.apple.com/us/app/simplex-chat/id1605771084)
&nbsp;
@@ -23,7 +24,7 @@
- ЁЯФР Double ratchet end-to-end encryption, with additional encryption layer.
- ЁЯУ▒ Mobile apps for Android ([Google Play](https://play.google.com/store/apps/details?id=chat.simplex.app), [APK](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex.apk)) and [iOS](https://apps.apple.com/us/app/simplex-chat/id1605771084).
- ЁЯЪА [TestFlight preview for iOS](https://testflight.apple.com/join/DWuT2LQu) with the new features 1-2 weeks earlier - **limited to 10,000 users**!
- ЁЯЦе Available as a terminal (console) app / CLI on Linux, MacOS, Windows.
- ЁЯЦе Available as a terminal (console) [app / CLI](#zap-quick-installation-of-a-terminal-app) on Linux, MacOS, Windows.
**NEW**: Security audit by [Trail of Bits](https://www.trailofbits.com/about), the [new website](https://simplex.chat) and v4.2 released! [See the announcement](./blog/20221108-simplex-chat-v4.2-security-audit-new-website.md)
@@ -44,6 +45,7 @@
- [For developers](#for-developers)
- [Roadmap](#roadmap)
- [Join a user group](#join-a-user-group)
- [Translate the apps](#translate-the-apps)
- [Contribute](#contribute)
- [Help us with donations](#help-us-with-donations)
- [Disclaimers, Security contact, License](#disclaimers)
@@ -76,23 +78,25 @@ You can use SimpleX with your own servers and still communicate with people usin
## Frequently asked questions
1. _How SimpleX can deliver messages without any user identifiers?_ See [v2 release annoucement](./blog/20220511-simplex-chat-v2-images-files.md#the-first-messaging-platform-without-user-identifiers) explaining how SimpleX works.
1. _How SimpleX can deliver messages without any user identifiers?_ See [v2 release announcement](./blog/20220511-simplex-chat-v2-images-files.md#the-first-messaging-platform-without-user-identifiers) explaining how SimpleX works.
2. _Why should I not just use Signal?_ Signal is a centralised platform that uses phone numbers to identify its users and their contacts. It means that while the content of your messages on Signal is protected with robust end-to-end encryption, there is a large amount of meta-data visible to Signal - who you talk with and when.
2. _Why should I not just use Signal?_ Signal is a centralized platform that uses phone numbers to identify its users and their contacts. It means that while the content of your messages on Signal is protected with robust end-to-end encryption, there is a large amount of meta-data visible to Signal - who you talk with and when.
3. _How is it different from Matrix, Session, Ricochet, Cwtch, etc., that also don't require user identites?_ Although these platforms do not require a _real identity_, they do rely on anonymous user identities to deliver messages тАУ it can be, for example, an identity key or a random number. Using a persistent user identity, even anonymous, creates a risk that user's connection graph becomes known to the observers and/or service providers, and it can lead to de-anonymizing some users. If the same user profile is used to connect to two different people via any messenger other than SimpleX, these two people can confirm if they are connected to the same person - they would use the same user identifier in the messages. With SimpleX there is no meta-data in common between your conversations with different contacts - the quality that no other messaging platform has.
3. _How is it different from Matrix, Session, Ricochet, Cwtch, etc., that also don't require user identities?_ Although these platforms do not require a _real identity_, they do rely on anonymous user identities to deliver messages тАУ it can be, for example, an identity key or a random number. Using a persistent user identity, even anonymous, creates a risk that user's connection graph becomes known to the observers and/or service providers, and it can lead to de-anonymizing some users. If the same user profile is used to connect to two different people via any messenger other than SimpleX, these two people can confirm if they are connected to the same person - they would use the same user identifier in the messages. With SimpleX there is no meta-data in common between your conversations with different contacts - the quality that no other messaging platform has.
## News and updates
Recent updates:
[Dec 06, 2022. November reviews and v4.3 released - with instant voice messages, irreversible deletion of sent messages and improved server configuration.](./blog/20221206-simplex-chat-v4.3-voice-messages.md)
[Feb 04, 2023. v4.5 released - with multiple user profiles, message draft, transport isolation and Italian interface](./blog/20230204-simplex-chat-v4-5-user-chat-profiles.md).
[Nov 08, 2022. Security audit by Trail of Bits, the new website and v4.2 released](./blog/20221108-simplex-chat-v4.2-security-audit-new-website.md)
[Jan 03, 2023. v4.4 released - with disappearing messages, "live" messages, connection security verifications, GIFs and stickers and with French interface language](./blog/20230103-simplex-chat-v4.4-disappearing-messages.md).
[Sep 28, 2022. v4.0: encrypted local chat database and many other changes](./blog/20220928-simplex-chat-v4-encrypted-database.md)
[Dec 06, 2022. November reviews and v4.3 released - with instant voice messages, irreversible deletion of sent messages and improved server configuration](./blog/20221206-simplex-chat-v4.3-voice-messages.md).
[Sep 1, 2022. v3.2: incognito mode, support .onion server hostnames, setting contact names, changing color scheme, etc. Implementation audit is arranged for October!](./blog/20220901-simplex-chat-v3.2-incognito-mode.md)
[Nov 08, 2022. Security audit by Trail of Bits, the new website and v4.2 released](./blog/20221108-simplex-chat-v4.2-security-audit-new-website.md).
[Sep 28, 2022. v4.0: encrypted local chat database and many other changes](./blog/20220928-simplex-chat-v4-encrypted-database.md).
[All updates](./blog)
@@ -136,7 +140,7 @@ SimpleX Chat is a work in progress тАУ we are releasing improvements as they are
What is already implemented:
1. Instead of user profile identifiers used by all other platforms, even the most private ones, SimpleX uses pairwise per-queue identifiers (2 addresses for each unidirectional message queue, with an optional 3rd address for push notificaitons on iOS, 2 queues in each connection between the users). It makes observing the network graph on the application level more difficult, as for `n` users there can be up to `n * (n-1)` message queues.
1. Instead of user profile identifiers used by all other platforms, even the most private ones, SimpleX uses pairwise per-queue identifiers (2 addresses for each unidirectional message queue, with an optional 3rd address for push notifications on iOS, 2 queues in each connection between the users). It makes observing the network graph on the application level more difficult, as for `n` users there can be up to `n * (n-1)` message queues.
2. End-to-end encryption in each message queue using [NaCl cryptobox](https://nacl.cr.yp.to/box.html). This is added to allow redundancy in the future (passing each message via several servers), to avoid having the same ciphertext in different queues (that would only be visible to the attacker if TLS is compromised). The encryption keys used for this encryption are not rotated, instead we are planning to rotate the queues. Curve25519 keys are used for key negotiation.
3. [Double ratchet](https://signal.org/docs/specifications/doubleratchet/) end-to-end encryption in each conversation between two users (or group members). This is the same algorithm that is used in Signal and many other messaging apps; it provides OTR messaging with forward secrecy (each message is encrypted by its own ephemeral key), break-in recovery (the keys are frequently re-negotiated as part of the message exchange). Two pairs of Curve448 keys are used for the initial key agreement, initiating party passes these keys via the connection link, accepting side - in the header of the confirmation message.
4. Additional layer of encryption using NaCL cryptobox for the messages delivered from the server to the recipient. This layer avoids having any ciphertext in common between sent and received traffic of the server inside TLS (and there are no identifiers in common as well).
@@ -146,10 +150,11 @@ What is already implemented:
8. To protect against replay attacks SimpleX servers require [tlsunique channel binding](https://www.rfc-editor.org/rfc/rfc5929.html) as session ID in each client command signed with per-queue ephemeral key.
9. To protect your IP address all SimpleX Chat clients support accessing messaging servers via Tor - see [v3.1 release announcement](./blog/20220808-simplex-chat-v3.1-chat-groups.md) for more details.
10. Local database encryption with passphrase - your contacts, groups and all sent and received messages are stored encrypted. If you used SimpleX Chat before v4.0 you need to enable the encryption via the app settings.
11. Transport isolation - different TCP connections and Tor circuits are used for traffic of different user profiles, optionally - for different contacts and group member connections.
We plan to add soon:
1. Message queue rotation. Currently the queues created between two users are used until the contact is deleted, providing a long-term pairwise identifiers of the conversation. We are planning to add queue rotation to make these identifiers termporary and rotate based on some schedule TBC (e.g., every X messages, or every X hours/days).
1. Automatic message queue rotation. Currently the queues created between two users are used until the queue is manually changed by the user or contact is deleted. We are planning to add automatic queue rotation to make these identifiers temporary and rotate based on some schedule TBC (e.g., every X messages, or every X hours/days).
2. Local files encryption. Currently the images and files you send and receive are stored in the app unencrypted, you can delete them via `Settings / Database passphrase & export`.
3. Message "mixing" - adding latency to message delivery, to protect against traffic correlation by message time.
@@ -159,7 +164,7 @@ You can:
- use SimpleX Chat library to integrate chat functionality into your mobile apps.
- create chat bots and services in Haskell - see [simple](./apps/simplex-bot/) and more [advanced chat bot example](./apps/simplex-bot-advanced/).
- create chat bots and services in any language running SimpleX Chat terminal CLI as a local WebSocket server. See [TypeScript SimpleX Chat client](./packages/simplex-chat-client/) and [JavaScipt chat bot example](./packages/simplex-chat-client/typescript/examples/squaring-bot.js).
- create chat bots and services in any language running SimpleX Chat terminal CLI as a local WebSocket server. See [TypeScript SimpleX Chat client](./packages/simplex-chat-client/) and [JavaScript chat bot example](./packages/simplex-chat-client/typescript/examples/squaring-bot.js).
- run [simplex-chat terminal CLI](./docs/CLI.md) to execute individual chat commands, e.g. to send messages as part of shell script execution.
If you are considering developing with SimpleX platform please get in touch for any advice and support.
@@ -191,17 +196,24 @@ If you are considering developing with SimpleX platform please get in touch for
- тЬЕ View deleted messages, full message deletion by sender (with recipient opt-in per contact).
- тЬЕ Block screenshots and view in recent apps.
- тЬЕ Advanced server configuration.
- тЬЕ Disappearing messages (with recipient opt-in per-contact).
- тЬЕ "Live" messages.
- тЬЕ Contact verification via a separate out-of-band channel.
- тЬЕ Multiple user profiles in the same chat database.
- тЬЕ Optionally avoid re-using the same TCP session for multiple connections.
- тЬЕ Preserve message drafts.
- ЁЯПЧ File server to optimize for efficient and private sending of large files.
- ЁЯПЧ Improved audio & video calls.
- ЁЯПЧ SMP queue redundancy and rotation (manual is supported).
- ЁЯПЧ Contact verification via a separate out-of-band channel.
- ЁЯПЧ Ephemeral/disappearing/OTR conversations with the existing contacts.
- Optionally avoid re-using the same TCP session for multiple connections.
- ЁЯПЧ Reduced battery and traffic usage in large groups.
- ЁЯПЧ Support older Android OS and 32-bit CPUs.
- Ephemeral/disappearing/OTR conversations with the existing contacts.
- Access password/pin (with optional alternative access password).
- Media server to optimize sending large files to groups.
- Local app files encryption.
- Video messages.
- Improved navigation and search in the conversation (expand and scroll to quoted message, scroll to search results, etc.).
- Message delivery confirmation (with sender opt-in or opt-out per contact, TBC).
- Multiple user profiles in the same chat database.
- Feeds/broadcasts.
- Unconfirmed: disappearing messages (with recipient opt-in per-contact).
- Web widgets for custom interactivity in the chats.
- Programmable chat automations / rules (automatic replies/forward/deletion/sending, reminders, etc.).
- Supporting the same profile on multiple devices.
@@ -209,28 +221,46 @@ If you are considering developing with SimpleX platform please get in touch for
- Privacy-preserving identity server for optional DNS-based contact/group addresses to simplify connection and discovery, but not used to deliver messages:
- keep all your contacts and groups even if you lose the domain.
- the server doesn't have information about your contacts and groups.
- Channels server for large groups and broadcast channels.
- Hosting server for large groups, communities and public channels.
- Message delivery relay for senders (to conceal IP address from the recipients' servers and to reduce the traffic).
- High capacity multi-node SMP relays.
## Join a user group
You can join a general group with more than 100 members: [#SimpleX-Group](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FWHV0YU1sYlU7NqiEHkHDB6gxO1ofTync%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAWbebOqVYuBXaiqHcXYjEHCpYi6VzDlu6CVaijDTmsQU%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22mL-7Divb94GGmGmRBef5Dg%3D%3D%22%7D).
You can join an English-speaking group if you want to ask any questions: [#SimpleX-Group-2](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FQP8zaGjjmlXV-ix_Er4JgJ0lNPYGS1KX%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEApAgBkRZ3x12ayZ7sHrjHQWNMvqzZpWUgM_fFCUdLXwo%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22xWpPXEZZsQp_F7vwAcAYDw%3D%3D%22%7D)
You can also join smaller groups by countries/languages: [\#SimpleX-DE](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FmIorjTDPG24jdLKXwutS6o9hdQQRZwfQ%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEA9N0BZaECrAw3we3S1Wq4QO7NERBuPt9447immrB50wo%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22S8aISlOgkTMytSox9gAM2Q%3D%3D%22%7D) (German), [\#SimpleX-US](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FlTWmQplLEaoJyHnEL1-B3f2PtDsikcTs%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEA-hMBlsQjNxK2vaVhqW_UyAVtuoYqgYTigK4B9dJ9CGc%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22G0UtRHIn0TmPoo08h_cbTA%3D%3D%22%7D) (US/English), [\#SimpleX-France](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2F11r6XyjwVMj0WDIUMbmNDXO996M_EN_1%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAXDmc2Lrj9WQOjEcWa0DeQHF3HcYOp9b68s8M_BJ7gEk%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22EZCeSYpeIBkaQwCcpcF00w%3D%3D%22%7D), [\#SimpleX-RU](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FZSYM278L5WoZiApx3925EAjSXcsAVNVu%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEA7RJ2wfT8zdfOLyE5OtWLEAPowj-q6F2HB0ExbATw8Gk%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22fsVoklNGptt7n-droqJYUQ%3D%3D%22%7D) (Russian), [#SimpleX-NL](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FmP0LbswSbfxoVkkxiWE2NYnBCgZ9Snvj%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAVwZuSsw4Mf52EaBNdNI3RebsLm0jg65ZIkcmH9E5uy8%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22M9xIULUNZx51Wsa5Kdb0Sg%3D%3D%22%7D) (Netherlands/Dutch), [#SimpleX-IT](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FaZ_wjh6QAYHB-LjyGtp8bllkzoq880u-%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEA-_Wulzc3j16i7t77XJ5wgwxeW8_Ea8GxetMo7K4MgjI%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22QWmXdrFzIeMd2OoEPMFkBQ%3D%3D%22%7D) (Italian).
There are also several groups in languages other than English, that we have the apps interface translated into. These groups are for testing, and asking questions to other SimpleX Chat users. We do not always answer questions there, so please ask them in one of the English-speaking groups.
- [\#SimpleX-DE](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FkIEl7OQzcp-J6aDmjdlQbRJwqkcZE7XR%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAR16PCu02MobRmKAsjzhDWMZcWP9hS8l5AUZi-Gs8z18%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22puYPMCQt11yPUvgmI5jCiw%3D%3D%22%7D) (German-speaking).
- [\#SimpleX-FR](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FvIHQDxTor53nwnWWTy5cHNwQQAdWN5Hw%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAPdgK1eBnETmgiqEQufbUkydKBJafoRx4iRrtrC2NAGc%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%221FyUryBPza-1ZFFE80Ekbg%3D%3D%22%7D) (French-speaking).
- [\#SimpleX-RU](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FXZyt3hJmWsycpN7Dqve_wbrAqb6myk1R%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAMFVIoytozTEa_QXOgoZFq_oe0IwZBYKvW50trSFXzXo%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22xz05ngjA3pNIxLZ32a8Vxg%3D%3D%22%7D) (Russian-speaking).
- [\#SimpleX-IT](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2F0weR-ZgDUl7ruOtI_8TZwEsnJP6UiImA%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAq4PSThO9Fvb5ydF48wB0yNbpzCbuQJCW3vZ9BGUfcxk%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22e-iceLA0SctC62eARgYDWg%3D%3D%22%7D) (Italian-speaking).
You can join these groups either by opening these links in the app or by opening them in a desktop browser and scanning QR code.
Let us know if you'd like to add some other countries to the list.
Join via the app to share what's going on and ask any questions!
## Translate the apps
Thanks to our users and [Weblate](https://hosted.weblate.org/engage/simplex-chat/), SimpleX Chat apps are translated to many other languages. Join our translators to help SimpleX grow faster!
Current interface languages:
- English (development language)
- German: [@mlanp](https://github.com/mlanp)
- French: [@ishi_sama](https://github.com/ishi-sama)
- Italian: [@unbranched](https://github.com/unbranched)
- Russian: project team
Languages in progress: Chinese, Hindi, Czech, Japanese, Dutch and [many others](https://hosted.weblate.org/projects/simplex-chat/#languages). We will be adding more languages as some of the already added are completed тАУ please suggest new languages, review the [translation guide](./docs/TRANSLATIONS.md) and get in touch with us!
## Contribute
We would love to have you join the development! You can contribute to SimpleX Chat with:
- developing features - please connect to us via chat so we can help you get started.
- writing a tutorial or recipes about hosting servers, chat bot automations, etc.
- translate UI to some language - we are currently setting up the UI to simplify it, please get in touch and let us know if you would be able to support and update the translations.
- translate website homepage - there is a lot of content we would like to share, it would help to bring the new users.
- writing a tutorial or recipes about hosting servers, chat bot automations, etc.
- developing features - please connect to us via chat so we can help you get started.
## Help us with donations
@@ -250,6 +280,7 @@ It is possible to donate via:
- Bitcoin address: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG
- BCH address: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG
- Ethereum address: 0x83fd788f7241a2be61780ea9dc72d2151e6843e2
- Solana address: 43tWFWDczgAcn4Rzwkpqg2mqwnQETSiTwznmCgA2tf1L
- please let us know, via GitHub issue or chat, if you want to create a donation in some other cryptocurrency - we will add the address to the list.
Thank you,

View File

@@ -11,8 +11,8 @@ android {
applicationId "chat.simplex.app"
minSdk 29
targetSdk 32
versionCode 80
versionName "4.4-beta.1"
versionCode 103
versionName "4.5.4"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
ndk {
@@ -76,6 +76,11 @@ android {
}
jniLibs.useLegacyPackaging = compression_level != "0"
}
def isRelease = gradle.getStartParameter().taskNames.find({ it.toLowerCase().contains("release") }) != null
if (isRelease) {
// Comma separated list of languages that will be included in the apk
android.defaultConfig.resConfigs("en", "ru", "de", "fr", "it", "nl", "cs")
}
}
dependencies {

View File

@@ -22,10 +22,12 @@ var TransformOperation;
TransformOperation["Decrypt"] = "decrypt";
})(TransformOperation || (TransformOperation = {}));
let activeCall;
let answerTimeout = 30000;
const processCommand = (function () {
const defaultIceServers = [
{ urls: ["stun:stun.simplex.im:443"] },
{ urls: ["turn:turn.simplex.im:443"], username: "private", credential: "yleob6AVkiNI87hpR94Z" },
{ urls: ["turn:turn.simplex.im:443?transport=udp"], username: "private", credential: "yleob6AVkiNI87hpR94Z" },
{ urls: ["turn:turn.simplex.im:443?transport=tcp"], username: "private", credential: "yleob6AVkiNI87hpR94Z" },
];
function getCallConfig(encodedInsertableStreams, iceServers, relay) {
return {
@@ -100,9 +102,16 @@ const processCommand = (function () {
const iceCandidates = getIceCandidates(pc, config);
const call = { connection: pc, iceCandidates, localMedia: mediaType, localCamera, localStream, remoteStream, aesKey, useWorker };
await setupMediaStreams(call);
let connectionTimeout = setTimeout(connectionHandler, answerTimeout);
pc.addEventListener("connectionstatechange", connectionStateChange);
return call;
async function connectionStateChange() {
// "failed" means the second party did not answer in time (15 sec timeout in Chrome WebView)
// See https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/p2p/base/p2p_constants.cc;l=70)
if (pc.connectionState !== "failed")
connectionHandler();
}
async function connectionHandler() {
sendMessageToNative({
resp: {
type: "connection",
@@ -115,6 +124,7 @@ const processCommand = (function () {
},
});
if (pc.connectionState == "disconnected" || pc.connectionState == "failed") {
clearConnectionTimeout();
pc.removeEventListener("connectionstatechange", connectionStateChange);
if (activeCall) {
setTimeout(() => sendMessageToNative({ resp: { type: "ended" } }), 0);
@@ -122,6 +132,7 @@ const processCommand = (function () {
endCall();
}
else if (pc.connectionState == "connected") {
clearConnectionTimeout();
const stats = (await pc.getStats());
for (const stat of stats.values()) {
const { type, state } = stat;
@@ -141,6 +152,12 @@ const processCommand = (function () {
}
}
}
function clearConnectionTimeout() {
if (connectionTimeout) {
clearTimeout(connectionTimeout);
connectionTimeout = undefined;
}
}
}
function serialize(x) {
return LZString.compressToBase64(JSON.stringify(x));

View File

@@ -29,6 +29,7 @@ import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.*
import chat.simplex.app.model.ChatModel
import chat.simplex.app.model.NtfManager
import chat.simplex.app.model.NtfManager.Companion.getUserIdFromIntent
import chat.simplex.app.ui.theme.SimpleButton
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.SplashView
@@ -42,7 +43,6 @@ import chat.simplex.app.views.newchat.*
import chat.simplex.app.views.onboarding.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
class MainActivity: FragmentActivity() {
@@ -374,13 +374,6 @@ fun MainPage(
.collect {
if (it != null) currentChatId = it
else onComposed()
// Deletes files that were not sent but already stored in files directory.
// Currently, it's voice records only
if (it == null && chatModel.filesToDelete.isNotEmpty()) {
chatModel.filesToDelete.forEach { it.delete() }
chatModel.filesToDelete.clear()
}
}
}
}
@@ -394,7 +387,7 @@ fun MainPage(
}
}
onboarding == OnboardingStage.Step1_SimpleXInfo -> SimpleXInfo(chatModel, onboarding = true)
onboarding == OnboardingStage.Step2_CreateProfile -> CreateProfile(chatModel)
onboarding == OnboardingStage.Step2_CreateProfile -> CreateProfile(chatModel) {}
onboarding == OnboardingStage.Step3_SetNotificationsMode -> SetNotificationsMode(chatModel)
}
ModalManager.shared.showInView()
@@ -405,20 +398,31 @@ fun MainPage(
}
fun processNotificationIntent(intent: Intent?, chatModel: ChatModel) {
val userId = getUserIdFromIntent(intent)
when (intent?.action) {
NtfManager.OpenChatAction -> {
val chatId = intent.getStringExtra("chatId")
Log.d(TAG, "processNotificationIntent: OpenChatAction $chatId")
if (chatId != null) {
val cInfo = chatModel.getChat(chatId)?.chatInfo
chatModel.clearOverlays.value = true
if (cInfo != null) withApi { openChat(cInfo, chatModel) }
withBGApi {
if (userId != null && userId != chatModel.currentUser.value?.userId) {
chatModel.controller.changeActiveUser(userId)
}
val cInfo = chatModel.getChat(chatId)?.chatInfo
chatModel.clearOverlays.value = true
if (cInfo != null) openChat(cInfo, chatModel)
}
}
}
NtfManager.ShowChatsAction -> {
Log.d(TAG, "processNotificationIntent: ShowChatsAction")
chatModel.chatId.value = null
chatModel.clearOverlays.value = true
withBGApi {
if (userId != null && userId != chatModel.currentUser.value?.userId) {
chatModel.controller.changeActiveUser(userId)
}
chatModel.chatId.value = null
chatModel.clearOverlays.value = true
}
}
NtfManager.AcceptCallAction -> {
val chatId = intent.getStringExtra("chatId")
@@ -479,7 +483,6 @@ fun processExternalIntent(intent: Intent?, chatModel: ChatModel) {
fun connectIfOpenedViaUri(uri: Uri, chatModel: ChatModel) {
Log.d(TAG, "connectIfOpenedViaUri: opened via link")
if (chatModel.currentUser.value == null) {
// TODO open from chat list view
chatModel.appOpenUrl.value = uri
} else {
withUriAction(uri) { linkType ->

View File

@@ -39,7 +39,7 @@ class SimplexApp: Application(), LifecycleEventObserver {
var isAppOnForeground: Boolean = false
fun initChatController(useKey: String? = null, startChat: Boolean = true) {
val dbKey = useKey ?: DatabaseUtils.useDatabaseKey() ?: ""
val dbKey = useKey ?: DatabaseUtils.useDatabaseKey()
val dbAbsolutePathPrefix = getFilesDirectory(SimplexApp.context)
val migrated: Array<Any> = chatMigrateInit(dbAbsolutePathPrefix, dbKey)
val res: DBMigrationResult = kotlin.runCatching {
@@ -90,6 +90,7 @@ class SimplexApp: Application(), LifecycleEventObserver {
context = this
initChatController()
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
context.getDir("temp", MODE_PRIVATE).deleteRecursively()
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
@@ -100,8 +101,19 @@ class SimplexApp: Application(), LifecycleEventObserver {
isAppOnForeground = true
if (chatModel.chatRunning.value == true) {
kotlin.runCatching {
val chats = chatController.apiGetChats()
chatModel.updateChats(chats)
val currentUserId = chatModel.currentUser.value?.userId
val chats = ArrayList(chatController.apiGetChats())
/** Active user can be changed in background while [ChatController.apiGetChats] is executing */
if (chatModel.currentUser.value?.userId == currentUserId) {
val currentChatId = chatModel.chatId.value
val oldStats = if (currentChatId != null) chatModel.getChat(currentChatId)?.chatStats else null
if (oldStats != null) {
val indexOfCurrentChat = chats.indexOfFirst { it.id == currentChatId }
/** Pass old chatStats because unreadCounter can be changed already while [ChatController.apiGetChats] is executing */
if (indexOfCurrentChat >= 0) chats[indexOfCurrentChat] = chats[indexOfCurrentChat].copy(chatStats = oldStats)
}
chatModel.updateChats(chats)
}
}.onFailure { Log.e(TAG, it.stackTraceToString()) }
}
}

View File

@@ -4,8 +4,6 @@ import android.net.Uri
import androidx.compose.material.MaterialTheme
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.Check
import androidx.compose.material.icons.outlined.Close
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
@@ -15,10 +13,12 @@ import androidx.compose.ui.text.style.TextDecoration
import chat.simplex.app.R
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.call.*
import chat.simplex.app.views.chat.ComposeState
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.onboarding.OnboardingStage
import chat.simplex.app.views.usersettings.NotificationPreviewMode
import chat.simplex.app.views.usersettings.NotificationsMode
import kotlinx.coroutines.*
import kotlinx.datetime.*
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
@@ -26,6 +26,7 @@ import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.*
import java.io.File
import kotlin.random.Random
import kotlin.time.*
/*
@@ -35,6 +36,7 @@ import kotlin.time.*
class ChatModel(val controller: ChatController) {
val onboardingStage = mutableStateOf<OnboardingStage?>(null)
val currentUser = mutableStateOf<User?>(null)
val users = mutableStateListOf<UserInfo>()
val userCreated = mutableStateOf<Boolean?>(null)
val chatRunning = mutableStateOf<Boolean?>(null)
val chatDbChanged = mutableStateOf<Boolean>(false)
@@ -42,6 +44,8 @@ class ChatModel(val controller: ChatController) {
val chatDbStatus = mutableStateOf<DBMigrationResult?>(null)
val chatDbDeleted = mutableStateOf(false)
val chats = mutableStateListOf<Chat>()
// map of connections network statuses, key is agent connection id
val networkStatuses = mutableStateMapOf<String, NetworkStatus>()
// current chat
val chatId = mutableStateOf<String?>(null)
@@ -81,19 +85,15 @@ class ChatModel(val controller: ChatController) {
// currently showing QR code
val connReqInv = mutableStateOf(null as String?)
var draft = mutableStateOf(null as ComposeState?)
var draftChatId = mutableStateOf(null as String?)
// working with external intents
val sharedContent = mutableStateOf(null as SharedContent?)
val filesToDelete = mutableSetOf<File>()
val simplexLinkMode = mutableStateOf(controller.appPrefs.simplexLinkMode.get())
fun updateUserProfile(profile: LocalProfile) {
val user = currentUser.value
if (user != null) {
currentUser.value = user.copy(profile = profile)
}
}
fun hasChat(id: String): Boolean = chats.firstOrNull { it.id == id } != null
fun getChat(id: String): Chat? = chats.firstOrNull { it.id == id }
fun getContactChat(contactId: Long): Chat? = chats.firstOrNull { it.chatInfo is ChatInfo.Direct && it.chatInfo.apiId == contactId }
@@ -120,17 +120,8 @@ class ChatModel(val controller: ChatController) {
}
fun updateChats(newChats: List<Chat>) {
val mergedChats = arrayListOf<Chat>()
for (newChat in newChats) {
val i = getChatIndex(newChat.chatInfo.id)
if (i >= 0) {
mergedChats.add(newChat.copy(serverInfo = chats[i].serverInfo))
} else {
mergedChats.add(newChat)
}
}
chats.clear()
chats.addAll(mergedChats)
chats.addAll(newChats)
val cId = chatId.value
// If chat is null, it was deleted in background after apiGetChats call
@@ -139,14 +130,6 @@ class ChatModel(val controller: ChatController) {
}
}
fun updateNetworkStatus(id: ChatId, status: Chat.NetworkStatus) {
val i = getChatIndex(id)
if (i >= 0) {
val chat = chats[i]
chats[i] = chat.copy(serverInfo = chat.serverInfo.copy(networkStatus = status))
}
}
fun replaceChat(id: String, chat: Chat) {
val i = getChatIndex(id)
if (i >= 0) {
@@ -157,7 +140,7 @@ class ChatModel(val controller: ChatController) {
}
}
fun addChatItem(cInfo: ChatInfo, cItem: ChatItem) {
suspend fun addChatItem(cInfo: ChatInfo, cItem: ChatItem) {
// update previews
val i = getChatIndex(cInfo.id)
val chat: Chat
@@ -168,6 +151,7 @@ class ChatModel(val controller: ChatController) {
chatStats =
if (cItem.meta.itemStatus is CIStatus.RcvNew) {
val minUnreadId = if(chat.chatStats.minUnreadItemId == 0L) cItem.id else chat.chatStats.minUnreadItemId
increaseUnreadCounter(currentUser.value!!)
chat.chatStats.copy(unreadCount = chat.chatStats.unreadCount + 1, minUnreadItemId = minUnreadId)
}
else
@@ -181,11 +165,17 @@ class ChatModel(val controller: ChatController) {
}
// add to current chat
if (chatId.value == cInfo.id) {
chatItems.add(cItem)
withContext(Dispatchers.Main) {
if (chatItems.lastOrNull()?.id == ChatItem.TEMP_LIVE_CHAT_ITEM_ID) {
chatItems.add(kotlin.math.max(0, chatItems.lastIndex), cItem)
} else {
chatItems.add(cItem)
}
}
}
}
fun upsertChatItem(cInfo: ChatInfo, cItem: ChatItem): Boolean {
suspend fun upsertChatItem(cInfo: ChatInfo, cItem: ChatItem): Boolean {
// update previews
val i = getChatIndex(cInfo.id)
val chat: Chat
@@ -212,7 +202,9 @@ class ChatModel(val controller: ChatController) {
chatItems[itemIndex] = cItem
return false
} else {
chatItems.add(cItem)
withContext(Dispatchers.Main) {
chatItems.add(cItem)
}
return true
}
} else {
@@ -248,6 +240,7 @@ class ChatModel(val controller: ChatController) {
// clear preview
val i = getChatIndex(cInfo.id)
if (i >= 0) {
decreaseUnreadCounter(currentUser.value!!, chats[i].chatStats.unreadCount)
chats[i] = chats[i].copy(chatItems = arrayListOf(), chatStats = Chat.ChatStats(), chatInfo = cInfo)
}
// clear current chat
@@ -256,6 +249,33 @@ class ChatModel(val controller: ChatController) {
}
}
fun updateCurrentUser(newProfile: Profile, preferences: FullChatPreferences? = null) {
val current = currentUser.value ?: return
val updated = current.copy(
profile = newProfile.toLocalProfile(current.profile.profileId),
fullPreferences = preferences ?: current.fullPreferences
)
val indexInUsers = users.indexOfFirst { it.user.userId == current.userId }
if (indexInUsers != -1) {
users[indexInUsers] = UserInfo(updated, users[indexInUsers].unreadCount)
}
currentUser.value = updated
}
suspend fun addLiveDummy(chatInfo: ChatInfo): ChatItem {
val cItem = ChatItem.liveDummy(chatInfo is ChatInfo.Direct)
withContext(Dispatchers.Main) {
chatItems.add(cItem)
}
return cItem
}
fun removeLiveDummy() {
if (chatItems.lastOrNull()?.id == ChatItem.TEMP_LIVE_CHAT_ITEM_ID) {
chatItems.removeLast()
}
}
fun markChatItemsRead(cInfo: ChatInfo, range: CC.ItemRange? = null, unreadCountAfter: Int? = null) {
val markedRead = markItemsReadInCurrentChat(cInfo, range)
// update preview
@@ -264,9 +284,11 @@ class ChatModel(val controller: ChatController) {
val chat = chats[chatIdx]
val lastId = chat.chatItems.lastOrNull()?.id
if (lastId != null) {
val unreadCount = unreadCountAfter ?: if (range != null) chat.chatStats.unreadCount - markedRead else 0
decreaseUnreadCounter(currentUser.value!!, chat.chatStats.unreadCount - unreadCount)
chats[chatIdx] = chat.copy(
chatStats = chat.chatStats.copy(
unreadCount = unreadCountAfter ?: if (range != null) chat.chatStats.unreadCount - markedRead else 0,
unreadCount = unreadCount,
// Can't use minUnreadItemId currently since chat items can have unread items between read items
//minUnreadItemId = if (range != null) kotlin.math.max(chat.chatStats.minUnreadItemId, range.to + 1) else lastId + 1
)
@@ -302,13 +324,30 @@ class ChatModel(val controller: ChatController) {
if (chatIndex == -1) return
val chat = chats[chatIndex]
val unreadCount = kotlin.math.max(chat.chatStats.unreadCount - 1, 0)
decreaseUnreadCounter(currentUser.value!!, chat.chatStats.unreadCount - unreadCount)
chats[chatIndex] = chat.copy(
chatStats = chat.chatStats.copy(
unreadCount = kotlin.math.max(chat.chatStats.unreadCount - 1, 0),
unreadCount = unreadCount,
)
)
}
fun increaseUnreadCounter(user: User) {
changeUnreadCounter(user, 1)
}
fun decreaseUnreadCounter(user: User, by: Int = 1) {
changeUnreadCounter(user, -by)
}
private fun changeUnreadCounter(user: User, by: Int) {
val i = users.indexOfFirst { it.user.userId == user.userId }
if (i != -1) {
users[i] = UserInfo(user, users[i].unreadCount + by)
}
}
// func popChat(_ id: String) {
// if let i = getChatIndex(id) {
// popChat_(i)
@@ -353,6 +392,20 @@ class ChatModel(val controller: ChatController) {
false
}
}
fun setContactNetworkStatus(contact: Contact, status: NetworkStatus) {
networkStatuses[contact.activeConn.agentConnId] = status
}
fun contactNetworkStatus(contact: Contact): NetworkStatus =
networkStatuses[contact.activeConn.agentConnId] ?: NetworkStatus.Unknown()
fun addTerminalItem(item: TerminalItem) {
if (terminalItems.size >= 500) {
terminalItems.removeAt(0)
}
terminalItems.add(item)
}
}
enum class ChatType(val type: String) {
@@ -388,6 +441,19 @@ data class User(
}
}
@Serializable
data class UserInfo(
val user: User,
val unreadCount: Int
) {
companion object {
val sampleData = UserInfo(
user = User.sampleData,
unreadCount = 1
)
}
}
typealias ChatId = String
interface NamedChat {
@@ -419,37 +485,30 @@ data class Chat (
val chatInfo: ChatInfo,
val chatItems: List<ChatItem>,
val chatStats: ChatStats = ChatStats(),
val serverInfo: ServerInfo = ServerInfo(NetworkStatus.Unknown())
) {
val userCanSend: Boolean
get() = when (chatInfo) {
is ChatInfo.Direct -> true
is ChatInfo.Group -> {
val m = chatInfo.groupInfo.membership
m.memberActive && m.memberRole >= GroupMemberRole.Member
}
else -> false
}
val userIsObserver: Boolean get() = when(chatInfo) {
is ChatInfo.Group -> {
val m = chatInfo.groupInfo.membership
m.memberActive && m.memberRole == GroupMemberRole.Observer
}
else -> false
}
val id: String get() = chatInfo.id
@Serializable
data class ChatStats(val unreadCount: Int = 0, val minUnreadItemId: Long = 0, val unreadChat: Boolean = false)
@Serializable
data class ServerInfo(val networkStatus: NetworkStatus)
@Serializable
sealed class NetworkStatus {
val statusString: String get() =
when (this) {
is Connected -> generalGetString(R.string.server_connected)
is Error -> generalGetString(R.string.server_error)
else -> generalGetString(R.string.server_connecting)
}
val statusExplanation: String get() =
when (this) {
is Connected -> generalGetString(R.string.connected_to_server_to_receive_messages_from_contact)
is Error -> String.format(generalGetString(R.string.trying_to_connect_to_server_to_receive_messages_with_error), error)
else -> generalGetString(R.string.trying_to_connect_to_server_to_receive_messages)
}
@Serializable @SerialName("unknown") class Unknown: NetworkStatus()
@Serializable @SerialName("connected") class Connected: NetworkStatus()
@Serializable @SerialName("disconnected") class Disconnected: NetworkStatus()
@Serializable @SerialName("error") class Error(val error: String): NetworkStatus()
}
companion object {
val sampleData = Chat(
chatInfo = ChatInfo.Direct.sampleData,
@@ -557,6 +616,51 @@ sealed class ChatInfo: SomeChat, NamedChat {
ContactConnection(PendingContactConnection.getSampleData(status, viaContactUri))
}
}
@Serializable @SerialName("invalidJSON")
class InvalidJSON(val json: String): ChatInfo() {
override val chatType get() = ChatType.Direct
override val localDisplayName get() = invalidChatName
override val id get() = ""
override val apiId get() = 0L
override val ready get() = false
override val sendMsgEnabled get() = false
override val ntfsEnabled get() = false
override val incognito get() = false
override fun featureEnabled(feature: ChatFeature) = false
override val timedMessagesTTL: Int? get() = null
override val createdAt get() = Clock.System.now()
override val updatedAt get() = Clock.System.now()
override val displayName get() = invalidChatName
override val fullName get() = invalidChatName
override val image get() = null
override val localAlias get() = ""
companion object {
private val invalidChatName = generalGetString(R.string.invalid_chat)
}
}
}
@Serializable
sealed class NetworkStatus {
val statusString: String get() =
when (this) {
is Connected -> generalGetString(R.string.server_connected)
is Error -> generalGetString(R.string.server_error)
else -> generalGetString(R.string.server_connecting)
}
val statusExplanation: String get() =
when (this) {
is Connected -> generalGetString(R.string.connected_to_server_to_receive_messages_from_contact)
is Error -> String.format(generalGetString(R.string.trying_to_connect_to_server_to_receive_messages_with_error), error)
else -> generalGetString(R.string.trying_to_connect_to_server_to_receive_messages)
}
@Serializable @SerialName("unknown") class Unknown: NetworkStatus()
@Serializable @SerialName("connected") class Connected: NetworkStatus()
@Serializable @SerialName("disconnected") class Disconnected: NetworkStatus()
@Serializable @SerialName("error") class Error(val error: String): NetworkStatus()
}
@Serializable
@@ -629,6 +733,8 @@ data class Contact(
@Serializable
class ContactRef(
val contactId: Long,
val agentConnId: String,
val connId: Long,
var localDisplayName: String
) {
val id: ChatId get() = "@$contactId"
@@ -643,6 +749,7 @@ class ContactSubStatus(
@Serializable
data class Connection(
val connId: Long,
val agentConnId: String,
val connStatus: ConnStatus,
val connLevel: Int,
val viaGroupLink: Boolean,
@@ -651,7 +758,7 @@ data class Connection(
) {
val id: ChatId get() = ":$connId"
companion object {
val sampleData = Connection(connId = 1, connStatus = ConnStatus.Ready, connLevel = 0, viaGroupLink = false, customUserProfileId = null)
val sampleData = Connection(connId = 1, agentConnId = "abc", connStatus = ConnStatus.Ready, connLevel = 0, viaGroupLink = false, customUserProfileId = null)
}
}
@@ -733,7 +840,7 @@ data class GroupInfo (
override fun featureEnabled(feature: ChatFeature) = when (feature) {
ChatFeature.TimedMessages -> fullGroupPreferences.timedMessages.on
ChatFeature.FullDelete -> fullGroupPreferences.fullDelete.on
ChatFeature.Voice -> fullGroupPreferences.fullDelete.on
ChatFeature.Voice -> fullGroupPreferences.voice.on
}
override val timedMessagesTTL: Int? get() = with(fullGroupPreferences.timedMessages) { if (on) ttl else null }
override val displayName get() = groupProfile.displayName
@@ -769,6 +876,7 @@ data class GroupInfo (
data class GroupProfile (
override val displayName: String,
override val fullName: String,
val description: String? = null,
override val image: String? = null,
override val localAlias: String = "",
val groupPreferences: GroupPreferences? = null
@@ -842,7 +950,7 @@ data class GroupMember (
fun canChangeRoleTo(groupInfo: GroupInfo): List<GroupMemberRole>? =
if (!canBeRemoved(groupInfo)) null
else groupInfo.membership.memberRole.let { userRole ->
GroupMemberRole.values().filter { it <= userRole }
GroupMemberRole.values().filter { it <= userRole && it != GroupMemberRole.Observer }
}
val memberIncognito = memberProfile.profileId != memberContactProfileId
@@ -873,11 +981,13 @@ class GroupMemberRef(
@Serializable
enum class GroupMemberRole(val memberRole: String) {
@SerialName("member") Member("member"), // order matters in comparisons
@SerialName("observer") Observer("observer"), // order matters in comparisons
@SerialName("member") Member("member"),
@SerialName("admin") Admin("admin"),
@SerialName("owner") Owner("owner");
val text: String get() = when (this) {
Observer -> generalGetString(R.string.group_member_role_observer)
Member -> generalGetString(R.string.group_member_role_member)
Admin -> generalGetString(R.string.group_member_role_admin)
Owner -> generalGetString(R.string.group_member_role_owner)
@@ -1167,6 +1277,9 @@ data class ChatItem (
is CIContent.SndGroupFeature -> showNtfDir
is CIContent.RcvChatFeatureRejected -> showNtfDir
is CIContent.RcvGroupFeatureRejected -> showNtfDir
is CIContent.SndModerated -> true
is CIContent.RcvModerated -> true
is CIContent.InvalidJSON -> false
}
fun withStatus(status: CIStatus): ChatItem = this.copy(meta = meta.copy(itemStatus = status))
@@ -1180,14 +1293,14 @@ data class ChatItem (
status: CIStatus = CIStatus.SndNew(),
quotedItem: CIQuote? = null,
file: CIFile? = null,
itemDeleted: Boolean = false,
itemDeleted: CIDeleted? = null,
itemEdited: Boolean = false,
itemTimed: CITimed? = null,
editable: Boolean = true
) =
ChatItem(
chatDir = dir,
meta = CIMeta.getSample(id, ts, text, status, itemDeleted, itemEdited, null, editable),
meta = CIMeta.getSample(id, ts, text, status, itemDeleted, itemEdited, itemTimed, editable),
content = CIContent.SndMsgContent(msgContent = MsgContent.MCText(text)),
quotedItem = quotedItem,
file = file
@@ -1202,7 +1315,7 @@ data class ChatItem (
) =
ChatItem(
chatDir = CIDirection.DirectRcv(),
meta = CIMeta.getSample(id, Clock.System.now(), text, CIStatus.RcvRead(), itemDeleted = false, itemEdited = false, editable = false),
meta = CIMeta.getSample(id, Clock.System.now(), text, CIStatus.RcvRead()),
content = CIContent.RcvMsgContent(msgContent = MsgContent.MCFile(text)),
quotedItem = null,
file = CIFile.getSample(fileName = fileName, fileSize = fileSize, fileStatus = fileStatus)
@@ -1217,7 +1330,7 @@ data class ChatItem (
) =
ChatItem(
chatDir = dir,
meta = CIMeta.getSample(id, ts, text, status, itemDeleted = false, itemEdited = false, editable = false),
meta = CIMeta.getSample(id, ts, text, status),
content = CIContent.RcvDeleted(deleteMode = CIDeleteMode.cidmBroadcast),
quotedItem = null,
file = null
@@ -1226,7 +1339,7 @@ data class ChatItem (
fun getGroupInvitationSample(status: CIGroupInvitationStatus = CIGroupInvitationStatus.Pending) =
ChatItem(
chatDir = CIDirection.DirectRcv(),
meta = CIMeta.getSample(1, Clock.System.now(), "received invitation to join group team as admin", CIStatus.RcvRead(), itemDeleted = false, itemEdited = false, editable = false),
meta = CIMeta.getSample(1, Clock.System.now(), "received invitation to join group team as admin", CIStatus.RcvRead()),
content = CIContent.RcvGroupInvitation(groupInvitation = CIGroupInvitation.getSample(status = status), memberRole = GroupMemberRole.Admin),
quotedItem = null,
file = null
@@ -1235,7 +1348,7 @@ data class ChatItem (
fun getGroupEventSample() =
ChatItem(
chatDir = CIDirection.DirectRcv(),
meta = CIMeta.getSample(1, Clock.System.now(), "group event text", CIStatus.RcvRead(), itemDeleted = false, itemEdited = false, editable = false),
meta = CIMeta.getSample(1, Clock.System.now(), "group event text", CIStatus.RcvRead()),
content = CIContent.RcvGroupEventContent(rcvGroupEvent = RcvGroupEvent.MemberAdded(groupMemberId = 1, profile = Profile.sampleData)),
quotedItem = null,
file = null
@@ -1245,7 +1358,7 @@ data class ChatItem (
val content = CIContent.RcvChatFeature(feature = feature, enabled = enabled, param = null)
return ChatItem(
chatDir = CIDirection.DirectRcv(),
meta = CIMeta.getSample(1, Clock.System.now(), content.text, CIStatus.RcvRead(), itemDeleted = false, itemEdited = false, editable = false),
meta = CIMeta.getSample(1, Clock.System.now(), content.text, CIStatus.RcvRead()),
content = content,
quotedItem = null,
file = null
@@ -1253,7 +1366,8 @@ data class ChatItem (
}
private const val TEMP_DELETED_CHAT_ITEM_ID = -1L
const val TEMP_LIVE_CHAT_ITEM_ID = -2L
val deletedItemDummy: ChatItem
get() = ChatItem(
chatDir = CIDirection.DirectRcv(),
@@ -1264,7 +1378,7 @@ data class ChatItem (
itemStatus = CIStatus.RcvRead(),
createdAt = Clock.System.now(),
updatedAt = Clock.System.now(),
itemDeleted = false,
itemDeleted = null,
itemEdited = false,
itemTimed = null,
itemLive = false,
@@ -1274,6 +1388,35 @@ data class ChatItem (
quotedItem = null,
file = null
)
fun liveDummy(direct: Boolean): ChatItem = ChatItem(
chatDir = if (direct) CIDirection.DirectSnd() else CIDirection.GroupSnd(),
meta = CIMeta(
itemId = TEMP_LIVE_CHAT_ITEM_ID,
itemTs = Clock.System.now(),
itemText = "",
itemStatus = CIStatus.RcvRead(),
createdAt = Clock.System.now(),
updatedAt = Clock.System.now(),
itemDeleted = null,
itemEdited = false,
itemTimed = null,
itemLive = true,
editable = false
),
content = CIContent.SndMsgContent(MsgContent.MCText("")),
quotedItem = null,
file = null
)
fun invalidJSON(json: String): ChatItem =
ChatItem(
chatDir = CIDirection.DirectSnd(),
meta = CIMeta.invalidJSON(),
content = CIContent.InvalidJSON(json),
quotedItem = null,
file = null
)
}
}
@@ -1300,7 +1443,7 @@ data class CIMeta (
val itemStatus: CIStatus,
val createdAt: Instant,
val updatedAt: Instant,
val itemDeleted: Boolean,
val itemDeleted: CIDeleted?,
val itemEdited: Boolean,
val itemTimed: CITimed?,
val itemLive: Boolean?,
@@ -1325,7 +1468,7 @@ data class CIMeta (
companion object {
fun getSample(
id: Long, ts: Instant, text: String, status: CIStatus = CIStatus.SndNew(),
itemDeleted: Boolean = false, itemEdited: Boolean = false, itemTimed: CITimed? = null, itemLive: Boolean = false, editable: Boolean = true
itemDeleted: CIDeleted? = null, itemEdited: Boolean = false, itemTimed: CITimed? = null, itemLive: Boolean = false, editable: Boolean = true
): CIMeta =
CIMeta(
itemId = id,
@@ -1340,6 +1483,22 @@ data class CIMeta (
itemLive = itemLive,
editable = editable
)
fun invalidJSON(): CIMeta =
CIMeta(
// itemId can not be the same for different items, otherwise ChatView will crash
itemId = Random.nextLong(-1000000L, -1000L),
itemTs = Clock.System.now(),
itemText = "invalid JSON",
itemStatus = CIStatus.SndNew(),
createdAt = Clock.System.now(),
updatedAt = Clock.System.now(),
itemDeleted = null,
itemEdited = false,
itemTimed = null,
itemLive = false,
editable = false
)
}
}
@@ -1369,6 +1528,12 @@ sealed class CIStatus {
@Serializable @SerialName("rcvRead") class RcvRead: CIStatus()
}
@Serializable
sealed class CIDeleted {
@Serializable @SerialName("deleted") class Deleted: CIDeleted()
@Serializable @SerialName("moderated") class Moderated(val byGroupMember: GroupMember): CIDeleted()
}
@Serializable
enum class CIDeleteMode(val deleteMode: String) {
@SerialName("internal") cidmInternal("internal"),
@@ -1404,6 +1569,9 @@ sealed class CIContent: ItemContent {
@Serializable @SerialName("sndGroupFeature") class SndGroupFeature(val groupFeature: GroupFeature, val preference: GroupPreference, val param: Int? = null): CIContent() { override val msgContent: MsgContent? get() = null }
@Serializable @SerialName("rcvChatFeatureRejected") class RcvChatFeatureRejected(val feature: ChatFeature): CIContent() { override val msgContent: MsgContent? get() = null }
@Serializable @SerialName("rcvGroupFeatureRejected") class RcvGroupFeatureRejected(val groupFeature: GroupFeature): CIContent() { override val msgContent: MsgContent? get() = null }
@Serializable @SerialName("sndModerated") object SndModerated: CIContent() { override val msgContent: MsgContent? get() = null }
@Serializable @SerialName("rcvModerated") object RcvModerated: CIContent() { override val msgContent: MsgContent? get() = null }
@Serializable @SerialName("invalidJSON") data class InvalidJSON(val json: String): CIContent() { override val msgContent: MsgContent? get() = null }
override val text: String get() = when (this) {
is SndMsgContent -> msgContent.text
@@ -1427,11 +1595,14 @@ sealed class CIContent: ItemContent {
is SndGroupFeature -> featureText(groupFeature, preference.enable.text, param)
is RcvChatFeatureRejected -> "${feature.text}: ${generalGetString(R.string.feature_received_prohibited)}"
is RcvGroupFeatureRejected -> "${groupFeature.text}: ${generalGetString(R.string.feature_received_prohibited)}"
is SndModerated -> generalGetString(R.string.moderated_description)
is RcvModerated -> generalGetString(R.string.moderated_description)
is InvalidJSON -> "invalid data"
}
companion object {
fun featureText(feature: Feature, enabled: String, param: Int?): String =
if (feature.hasParam && param != null) {
if (feature.hasParam) {
"${feature.text}: ${TimedMessagesPreference.ttlText(param)}"
} else {
"${feature.text}: $enabled"
@@ -1439,11 +1610,11 @@ sealed class CIContent: ItemContent {
fun preferenceText(feature: Feature, allowed: FeatureAllowed, param: Int?): String = when {
allowed != FeatureAllowed.NO && feature.hasParam && param != null ->
"offered ${feature.text}: ${TimedMessagesPreference.ttlText(param)}"
String.format(generalGetString(R.string.feature_offered_item_with_param), feature.text, TimedMessagesPreference.ttlText(param))
allowed != FeatureAllowed.NO ->
"offered ${feature.text}"
String.format(generalGetString(R.string.feature_offered_item), feature.text, TimedMessagesPreference.ttlText(param))
else ->
"cancelled ${feature.text}"
String.format(generalGetString(R.string.feature_cancelled_item), feature.text, TimedMessagesPreference.ttlText(param))
}
}
}

View File

@@ -30,7 +30,13 @@ class NtfManager(val context: Context, private val appPreferences: AppPreference
const val RejectCallAction: String = "chat.simplex.app.REJECT_CALL"
const val CallNotificationId: Int = -1
private const val UserIdKey: String = "userId"
private const val ChatIdKey: String = "chatId"
fun getUserIdFromIntent(intent: Intent?): Long? {
val userId = intent?.getLongExtra(UserIdKey, -1L)
return if (userId == -1L || userId == null) null else userId
}
}
private val manager: NotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
@@ -38,11 +44,7 @@ class NtfManager(val context: Context, private val appPreferences: AppPreference
private val msgNtfTimeoutMs = 30000L
init {
manager.createNotificationChannel(NotificationChannel(MessageChannel, generalGetString(R.string.ntf_channel_messages), NotificationManager.IMPORTANCE_HIGH))
manager.createNotificationChannel(callNotificationChannel(CallChannel, generalGetString(R.string.ntf_channel_calls)))
// Remove old channels since they can't be edited
manager.deleteNotificationChannel("chat.simplex.app.CALL_NOTIFICATION")
manager.deleteNotificationChannel("chat.simplex.app.LOCK_SCREEN_CALL_NOTIFICATION")
if (manager.areNotificationsEnabled()) createNtfChannelsMaybeShowAlert()
}
enum class NotificationAction {
@@ -77,8 +79,9 @@ class NtfManager(val context: Context, private val appPreferences: AppPreference
}
}
fun notifyContactRequestReceived(cInfo: ChatInfo.ContactRequest) {
fun notifyContactRequestReceived(user: User, cInfo: ChatInfo.ContactRequest) {
notifyMessageReceived(
user = user,
chatId = cInfo.id,
displayName = cInfo.displayName,
msgText = generalGetString(R.string.notification_new_contact_request),
@@ -87,21 +90,22 @@ class NtfManager(val context: Context, private val appPreferences: AppPreference
)
}
fun notifyContactConnected(contact: Contact) {
fun notifyContactConnected(user: User, contact: Contact) {
notifyMessageReceived(
user = user,
chatId = contact.id,
displayName = contact.displayName,
msgText = generalGetString(R.string.notification_contact_connected)
)
}
fun notifyMessageReceived(cInfo: ChatInfo, cItem: ChatItem) {
fun notifyMessageReceived(user: User, cInfo: ChatInfo, cItem: ChatItem) {
if (!cInfo.ntfsEnabled) return
notifyMessageReceived(chatId = cInfo.id, displayName = cInfo.displayName, msgText = hideSecrets(cItem))
notifyMessageReceived(user = user, chatId = cInfo.id, displayName = cInfo.displayName, msgText = hideSecrets(cItem))
}
fun notifyMessageReceived(chatId: String, displayName: String, msgText: String, image: String? = null, actions: List<NotificationAction> = emptyList()) {
fun notifyMessageReceived(user: User, chatId: String, displayName: String, msgText: String, image: String? = null, actions: List<NotificationAction> = emptyList()) {
Log.d(TAG, "notifyMessageReceived $chatId")
val now = Clock.System.now().toEpochMilliseconds()
val recentNotification = (now - prevNtfTime.getOrDefault(chatId, 0) < msgNtfTimeoutMs)
@@ -126,13 +130,14 @@ class NtfManager(val context: Context, private val appPreferences: AppPreference
.setColor(0x88FFFF)
.setAutoCancel(true)
.setVibrate(if (actions.isEmpty()) null else longArrayOf(0, 250, 250, 250))
.setContentIntent(chatPendingIntent(OpenChatAction, chatId))
.setContentIntent(chatPendingIntent(OpenChatAction, user.userId, chatId))
.setSilent(if (actions.isEmpty()) recentNotification else false)
for (action in actions) {
val flags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
val actionIntent = Intent(SimplexApp.context, NtfActionReceiver::class.java)
actionIntent.action = action.name
actionIntent.putExtra(UserIdKey, user.userId)
actionIntent.putExtra(ChatIdKey, chatId)
val actionPendingIntent: PendingIntent = PendingIntent.getBroadcast(SimplexApp.context, 0, actionIntent, flags)
val actionButton = when (action) {
@@ -147,7 +152,7 @@ class NtfManager(val context: Context, private val appPreferences: AppPreference
.setGroup(MessageGroup)
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
.setGroupSummary(true)
.setContentIntent(chatPendingIntent(ShowChatsAction))
.setContentIntent(chatPendingIntent(ShowChatsAction, null))
.build()
with(NotificationManagerCompat.from(context)) {
@@ -182,9 +187,9 @@ class NtfManager(val context: Context, private val appPreferences: AppPreference
val soundUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.packageName + "/" + R.raw.ring_once)
val fullScreenPendingIntent = PendingIntent.getActivity(context, 0, Intent(), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
NotificationCompat.Builder(context, CallChannel)
.setContentIntent(chatPendingIntent(OpenChatAction, invitation.contact.id))
.addAction(R.drawable.ntf_icon, generalGetString(R.string.accept), chatPendingIntent(AcceptCallAction, contactId))
.addAction(R.drawable.ntf_icon, generalGetString(R.string.reject), chatPendingIntent(RejectCallAction, contactId, true))
.setContentIntent(chatPendingIntent(OpenChatAction, invitation.user.userId, invitation.contact.id))
.addAction(R.drawable.ntf_icon, generalGetString(R.string.accept), chatPendingIntent(AcceptCallAction, invitation.user.userId, contactId))
.addAction(R.drawable.ntf_icon, generalGetString(R.string.reject), chatPendingIntent(RejectCallAction, invitation.user.userId, contactId, true))
.setFullScreenIntent(fullScreenPendingIntent, true)
.setSound(soundUri)
}
@@ -241,12 +246,13 @@ class NtfManager(val context: Context, private val appPreferences: AppPreference
}
}
private fun chatPendingIntent(intentAction: String, chatId: String? = null, broadcast: Boolean = false): PendingIntent {
private fun chatPendingIntent(intentAction: String, userId: Long?, chatId: String? = null, broadcast: Boolean = false): PendingIntent {
Log.d(TAG, "chatPendingIntent for $intentAction")
val uniqueInt = (System.currentTimeMillis() and 0xfffffff).toInt()
var intent = Intent(context, if (!broadcast) MainActivity::class.java else NtfActionReceiver::class.java)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
.setAction(intentAction)
.putExtra(UserIdKey, userId)
if (chatId != null) intent = intent.putExtra(ChatIdKey, chatId)
return if (!broadcast) {
TaskStackBuilder.create(context).run {
@@ -258,24 +264,46 @@ class NtfManager(val context: Context, private val appPreferences: AppPreference
}
}
/**
* This function creates notifications channels. On Android 13+ calling it for the first time will trigger system alert,
* The alert asks a user to allow or disallow to show notifications for the app. That's why it should be called only when the user
* already saw such alert or when you want to trigger showing the alert.
* On the first app launch the channels will be created after user profile is created. Subsequent calls will create new channels and delete
* old ones if needed
* */
fun createNtfChannelsMaybeShowAlert() {
manager.createNotificationChannel(NotificationChannel(MessageChannel, generalGetString(R.string.ntf_channel_messages), NotificationManager.IMPORTANCE_HIGH))
manager.createNotificationChannel(callNotificationChannel(CallChannel, generalGetString(R.string.ntf_channel_calls)))
// Remove old channels since they can't be edited
manager.deleteNotificationChannel("chat.simplex.app.CALL_NOTIFICATION")
manager.deleteNotificationChannel("chat.simplex.app.LOCK_SCREEN_CALL_NOTIFICATION")
}
/**
* Processes every action specified by [NotificationCompat.Builder.addAction] that comes with [NotificationAction]
* and [ChatInfo.id] as [ChatIdKey] in extra
* */
class NtfActionReceiver: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val userId = getUserIdFromIntent(intent)
val chatId = intent?.getStringExtra(ChatIdKey) ?: return
val cInfo = SimplexApp.context.chatModel.getChat(chatId)?.chatInfo
val m = SimplexApp.context.chatModel
when (intent.action) {
NotificationAction.ACCEPT_CONTACT_REQUEST.name -> {
if (cInfo !is ChatInfo.ContactRequest) return
acceptContactRequest(cInfo, SimplexApp.context.chatModel)
SimplexApp.context.chatModel.controller.ntfManager.cancelNotificationsForChat(chatId)
val isCurrentUser = m.currentUser.value?.userId == userId
val cInfo: ChatInfo.ContactRequest? = if (isCurrentUser) {
(m.getChat(chatId)?.chatInfo as? ChatInfo.ContactRequest) ?: return
} else {
null
}
val apiId = chatId.replace("<@", "").toLongOrNull() ?: return
acceptContactRequest(apiId, cInfo, isCurrentUser, m)
m.controller.ntfManager.cancelNotificationsForChat(chatId)
}
RejectCallAction -> {
val invitation = SimplexApp.context.chatModel.callInvitations[chatId]
val invitation = m.callInvitations[chatId]
if (invitation != null) {
SimplexApp.context.chatModel.callManager.endCall(invitation = invitation)
m.callManager.endCall(invitation = invitation)
}
}
else -> {

View File

@@ -17,6 +17,7 @@ enum class DefaultTheme {
val DEFAULT_PADDING = 16.dp
val DEFAULT_SPACE_AFTER_ICON = 4.dp
val DEFAULT_PADDING_HALF = DEFAULT_PADDING / 2
val DEFAULT_BOTTOM_PADDING = 48.dp
val DarkColorPalette = darkColors(
primary = SimplexBlue, // If this value changes also need to update #0088ff in string resource files

View File

@@ -1,29 +1,21 @@
package chat.simplex.app.views
import android.content.Context
import android.content.res.Configuration
import android.os.SystemClock
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.*
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Lock
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.fragment.app.FragmentActivity
import chat.simplex.app.R
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.chat.*
@@ -31,70 +23,18 @@ import chat.simplex.app.views.helpers.*
import com.google.accompanist.insets.ProvideWindowInsets
import com.google.accompanist.insets.navigationBarsWithImePadding
private val lastSuccessfulAuth: MutableState<Long?> = mutableStateOf(null)
@Composable
fun TerminalView(chatModel: ChatModel, close: () -> Unit) {
val composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = false)) }
val lastSuccessfulAuth = remember { lastSuccessfulAuth }
BackHandler(onBack = {
lastSuccessfulAuth.value = null
close()
})
val authorized = remember { !chatModel.controller.appPrefs.performLA.get() }
val context = LocalContext.current
LaunchedEffect(lastSuccessfulAuth.value) {
if (!authorized && !authorizedPreviously(lastSuccessfulAuth)) {
runAuth(lastSuccessfulAuth, context)
}
}
if (authorized || authorizedPreviously(lastSuccessfulAuth)) {
LaunchedEffect(Unit) {
// Update auth each time user visits this screen in authenticated state just to prolong authorized time
lastSuccessfulAuth.value = SystemClock.elapsedRealtime()
}
TerminalLayout(
chatModel.terminalItems,
remember { chatModel.terminalItems },
composeState,
sendCommand = { sendCommand(chatModel, composeState) },
close
)
} else {
Surface(Modifier.fillMaxSize()) {
Column(Modifier.background(MaterialTheme.colors.background)) {
CloseSheetBar(close)
Box(
Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
SimpleButton(
stringResource(R.string.auth_unlock),
icon = Icons.Outlined.Lock,
click = {
runAuth(lastSuccessfulAuth, context)
}
)
}
}
}
}
}
private fun authorizedPreviously(lastSuccessfulAuth: State<Long?>): Boolean =
lastSuccessfulAuth.value?.let { SystemClock.elapsedRealtime() - it < 30_000 } ?: false
private fun runAuth(lastSuccessfulAuth: MutableState<Long?>, context: Context) {
authenticate(
generalGetString(R.string.auth_open_chat_console),
generalGetString(R.string.auth_log_in_using_credential),
context as FragmentActivity,
completed = { laResult ->
lastSuccessfulAuth.value = when (laResult) {
LAResult.Success, LAResult.Unavailable -> SystemClock.elapsedRealtime()
is LAResult.Error, LAResult.Failed -> null
}
}
)
}
private fun sendCommand(chatModel: ChatModel, composeState: MutableState<ComposeState>) {
@@ -102,9 +42,9 @@ private fun sendCommand(chatModel: ChatModel, composeState: MutableState<Compose
val prefPerformLA = chatModel.controller.appPrefs.performLA.get()
val s = composeState.value.message
if (s.startsWith("/sql") && (!prefPerformLA || !developerTools)) {
val resp = CR.ChatCmdError(ChatError.ChatErrorChat(ChatErrorType.╨бommandError("Failed reading: empty")))
chatModel.terminalItems.add(TerminalItem.cmd(CC.Console(s)))
chatModel.terminalItems.add(TerminalItem.resp(resp))
val resp = CR.ChatCmdError(null, ChatError.ChatErrorChat(ChatErrorType.╨бommandError("Failed reading: empty")))
chatModel.addTerminalItem(TerminalItem.cmd(CC.Console(s)))
chatModel.addTerminalItem(TerminalItem.resp(resp))
composeState.value = ComposeState(useLinkPreviews = false)
} else {
withApi {
@@ -138,17 +78,19 @@ fun TerminalLayout(
SendMsgView(
composeState = composeState,
showVoiceRecordIcon = false,
recState = mutableStateOf(RecordingState.NotStarted),
recState = remember { mutableStateOf(RecordingState.NotStarted) },
isDirectChat = false,
liveMessageAlertShown = SharedPreference(get = { false }, set = {}),
needToAllowVoiceToContact = false,
allowedVoiceByPrefs = false,
userIsObserver = false,
userCanSend = true,
allowVoiceToContact = {},
sendMessage = sendCommand,
sendLiveMessage = null,
updateLiveMessage = null,
::onMessageChange,
textStyle
onMessageChange = ::onMessageChange,
textStyle = textStyle
)
}
},
@@ -174,7 +116,8 @@ fun TerminalLog(terminalItems: List<TerminalItem>) {
DisposableEffect(Unit) {
onDispose { lazyListState = listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset }
}
val reversedTerminalItems by remember { derivedStateOf { terminalItems.reversed() } }
val reversedTerminalItems by remember { derivedStateOf { terminalItems.reversed().toList() } }
val context = LocalContext.current
LazyColumn(state = listState, reverseLayout = true) {
items(reversedTerminalItems) { item ->
Text(
@@ -185,7 +128,7 @@ fun TerminalLog(terminalItems: List<TerminalItem>) {
modifier = Modifier
.fillMaxWidth()
.clickable {
ModalManager.shared.showModal {
ModalManager.shared.showModal(endButtons = { ShareButton { shareText(context, item.details) } }) {
SelectionContainer(modifier = Modifier.verticalScroll(rememberScrollState())) {
Text(item.details, modifier = Modifier.padding(horizontal = DEFAULT_PADDING).padding(bottom = DEFAULT_PADDING))
}

View File

@@ -21,8 +21,8 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.*
import chat.simplex.app.R
import chat.simplex.app.SimplexService
import chat.simplex.app.model.ChatModel
import chat.simplex.app.model.Profile
import chat.simplex.app.ui.theme.*
@@ -38,7 +38,7 @@ fun isValidDisplayName(name: String) : Boolean {
}
@Composable
fun CreateProfilePanel(chatModel: ChatModel) {
fun CreateProfilePanel(chatModel: ChatModel, close: () -> Unit) {
val displayName = remember { mutableStateOf("") }
val fullName = remember { mutableStateOf("") }
val focusRequester = remember { FocusRequester() }
@@ -72,10 +72,12 @@ fun CreateProfilePanel(chatModel: ChatModel) {
ProfileNameField(fullName)
Spacer(Modifier.fillMaxHeight().weight(1f))
Row {
SimpleButton(
text = stringResource(R.string.about_simplex),
icon = Icons.Outlined.ArrowBackIosNew
) { chatModel.onboardingStage.value = OnboardingStage.Step1_SimpleXInfo }
if (chatModel.users.isEmpty()) {
SimpleButton(
text = stringResource(R.string.about_simplex),
icon = Icons.Outlined.ArrowBackIosNew
) { chatModel.onboardingStage.value = OnboardingStage.Step1_SimpleXInfo }
}
Spacer(Modifier.fillMaxWidth().weight(1f))
@@ -83,7 +85,7 @@ fun CreateProfilePanel(chatModel: ChatModel) {
val createModifier: Modifier
val createColor: Color
if (enabled) {
createModifier = Modifier.clickable { createProfile(chatModel, displayName.value, fullName.value) }.padding(8.dp)
createModifier = Modifier.clickable { createProfile(chatModel, displayName.value, fullName.value, close) }.padding(8.dp)
createColor = MaterialTheme.colors.primary
} else {
createModifier = Modifier.padding(8.dp)
@@ -105,13 +107,23 @@ fun CreateProfilePanel(chatModel: ChatModel) {
}
}
fun createProfile(chatModel: ChatModel, displayName: String, fullName: String) {
fun createProfile(chatModel: ChatModel, displayName: String, fullName: String, close: () -> Unit) {
withApi {
val user = chatModel.controller.apiCreateActiveUser(
Profile(displayName, fullName, null)
)
chatModel.controller.startChat(user)
chatModel.onboardingStage.value = OnboardingStage.Step3_SetNotificationsMode
) ?: return@withApi
chatModel.currentUser.value = user
if (chatModel.users.isEmpty()) {
chatModel.controller.startChat(user)
chatModel.onboardingStage.value = OnboardingStage.Step3_SetNotificationsMode
SimplexApp.context.chatModel.controller.ntfManager.createNtfChannelsMaybeShowAlert()
} else {
val users = chatModel.controller.listUsers()
chatModel.users.clear()
chatModel.users.addAll(users)
chatModel.controller.getUserChatData()
close()
}
}
}

View File

@@ -18,7 +18,7 @@ class CallManager(val chatModel: ChatModel) {
controller.ntfManager.notifyCallInvitation(invitation)
} else {
val contact = invitation.contact
controller.ntfManager.notifyMessageReceived(chatId = contact.id, displayName = contact.displayName, msgText = invitation.callTypeText)
controller.ntfManager.notifyMessageReceived(user = invitation.user, chatId = contact.id, displayName = contact.displayName, msgText = invitation.callTypeText)
}
}
}

View File

@@ -37,6 +37,7 @@ import androidx.webkit.WebViewClientCompat
import chat.simplex.app.*
import chat.simplex.app.R
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.DEFAULT_BOTTOM_PADDING
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.helpers.ProfileImage
import chat.simplex.app.views.helpers.withApi
@@ -240,7 +241,7 @@ private fun ActiveCallOverlayLayout(
CallInfoView(call, alignment = Alignment.CenterHorizontally)
}
Spacer(Modifier.fillMaxHeight().weight(1f))
Box(Modifier.fillMaxWidth().padding(bottom = 48.dp), contentAlignment = Alignment.CenterStart) {
Box(Modifier.fillMaxWidth().padding(bottom = DEFAULT_BOTTOM_PADDING), contentAlignment = Alignment.CenterStart) {
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
IconButton(onClick = dismiss) {
Icon(Icons.Filled.CallEnd, stringResource(R.string.icon_descr_hang_up), tint = Color.Red, modifier = Modifier.size(64.dp))
@@ -297,10 +298,10 @@ fun CallInfoView(call: Call, alignment: Alignment.Horizontal) {
InfoText(call.contact.chatViewName, style = MaterialTheme.typography.h2)
InfoText(call.callState.text)
val connInfo =
if (call.connectionInfo == null) ""
else " (${call.connectionInfo.text})"
InfoText(call.encryptionStatus + connInfo)
val connInfo = call.connectionInfo
// val connInfoText = if (connInfo == null) "" else " (${connInfo.text}, ${connInfo.protocolText})"
val connInfoText = if (connInfo == null) "" else " (${connInfo.text})"
InfoText(call.encryptionStatus + connInfoText)
}
}
@@ -480,7 +481,10 @@ fun PreviewActiveCallOverlayVideo() {
callState = CallState.Negotiated,
localMedia = CallMediaType.Video,
peerMedia = CallMediaType.Video,
connectionInfo = ConnectionInfo(RTCIceCandidate(RTCIceCandidateType.Host), RTCIceCandidate(RTCIceCandidateType.Host))
connectionInfo = ConnectionInfo(
RTCIceCandidate(RTCIceCandidateType.Host, "tcp", null),
RTCIceCandidate(RTCIceCandidateType.Host, "tcp", null)
)
),
dismiss = {},
toggleAudio = {},
@@ -501,7 +505,10 @@ fun PreviewActiveCallOverlayAudio() {
callState = CallState.Negotiated,
localMedia = CallMediaType.Audio,
peerMedia = CallMediaType.Audio,
connectionInfo = ConnectionInfo(RTCIceCandidate(RTCIceCandidateType.Host), RTCIceCandidate(RTCIceCandidateType.Host))
connectionInfo = ConnectionInfo(
RTCIceCandidate(RTCIceCandidateType.Host, "udp", null),
RTCIceCandidate(RTCIceCandidateType.Host, "udp", null)
)
),
dismiss = {},
toggleAudio = {},

View File

@@ -126,6 +126,7 @@ fun IncomingCallLockScreenAlert(invitation: RcvCallInvitation, chatModel: ChatMo
IncomingCallLockScreenAlertLayout(
invitation,
callOnLockScreen,
chatModel,
rejectCall = { cm.endCall(invitation = invitation) },
ignoreCall = {
chatModel.activeCallInvitation.value = null
@@ -135,6 +136,7 @@ fun IncomingCallLockScreenAlert(invitation: RcvCallInvitation, chatModel: ChatMo
openApp = {
val intent = Intent(context, MainActivity::class.java)
.setAction(OpenChatAction)
.putExtra("userId", invitation.user.userId)
.putExtra("chatId", invitation.contact.id)
context.startActivity(intent)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -149,6 +151,7 @@ fun IncomingCallLockScreenAlert(invitation: RcvCallInvitation, chatModel: ChatMo
fun IncomingCallLockScreenAlertLayout(
invitation: RcvCallInvitation,
callOnLockScreen: CallOnLockScreen?,
chatModel: ChatModel,
rejectCall: () -> Unit,
ignoreCall: () -> Unit,
acceptCall: () -> Unit,
@@ -160,7 +163,7 @@ fun IncomingCallLockScreenAlertLayout(
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
IncomingCallInfo(invitation)
IncomingCallInfo(invitation, chatModel)
Spacer(Modifier.fillMaxHeight().weight(1f))
if (callOnLockScreen == CallOnLockScreen.ACCEPT) {
ProfileImage(size = 192.dp, image = invitation.contact.profile.image)
@@ -217,12 +220,14 @@ fun PreviewIncomingCallLockScreenAlert() {
.fillMaxSize()) {
IncomingCallLockScreenAlertLayout(
invitation = RcvCallInvitation(
user = User.sampleData,
contact = Contact.sampleData,
callType = CallType(media = CallMediaType.Audio, capabilities = CallCapabilities(encryption = false)),
sharedKey = null,
callTs = Clock.System.now()
),
callOnLockScreen = null,
chatModel = SimplexApp.context.chatModel,
rejectCall = {},
ignoreCall = {},
acceptCall = {},

View File

@@ -17,9 +17,10 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.model.ChatModel
import chat.simplex.app.model.Contact
import chat.simplex.app.SimplexApp
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.ProfileImage
import chat.simplex.app.views.usersettings.ProfilePreview
import kotlinx.datetime.Clock
@@ -32,6 +33,7 @@ fun IncomingCallAlertView(invitation: RcvCallInvitation, chatModel: ChatModel) {
DisposableEffect(true) { onDispose { SoundPlayer.shared.stop() } }
IncomingCallAlertLayout(
invitation,
chatModel,
rejectCall = { cm.endCall(invitation = invitation) },
ignoreCall = {
chatModel.activeCallInvitation.value = null
@@ -44,13 +46,14 @@ fun IncomingCallAlertView(invitation: RcvCallInvitation, chatModel: ChatModel) {
@Composable
fun IncomingCallAlertLayout(
invitation: RcvCallInvitation,
chatModel: ChatModel,
rejectCall: () -> Unit,
ignoreCall: () -> Unit,
acceptCall: () -> Unit
) {
val color = if (isInDarkTheme()) IncomingCallDark else IncomingCallLight
Column(Modifier.fillMaxWidth().background(color).padding(top = 16.dp, bottom = 16.dp, start = 16.dp, end = 8.dp)) {
IncomingCallInfo(invitation)
IncomingCallInfo(invitation, chatModel)
Spacer(Modifier.height(8.dp))
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween) {
Row(Modifier.fillMaxWidth().weight(1f), verticalAlignment = Alignment.CenterVertically) {
@@ -66,9 +69,13 @@ fun IncomingCallAlertLayout(
}
@Composable
fun IncomingCallInfo(invitation: RcvCallInvitation) {
fun IncomingCallInfo(invitation: RcvCallInvitation, chatModel: ChatModel) {
@Composable fun CallIcon(icon: ImageVector, descr: String) = Icon(icon, descr, tint = SimplexGreen)
Row {
Row(verticalAlignment = Alignment.CenterVertically) {
if (chatModel.users.size > 1) {
ProfileImage(size = 32.dp, image = invitation.user.profile.image, color = MaterialTheme.colors.secondary)
Spacer(Modifier.width(4.dp))
}
if (invitation.callType.media == CallMediaType.Video) CallIcon(Icons.Filled.Videocam, stringResource(R.string.icon_descr_video_call))
else CallIcon(Icons.Filled.Phone, stringResource(R.string.icon_descr_audio_call))
Spacer(Modifier.width(4.dp))
@@ -101,11 +108,13 @@ fun PreviewIncomingCallAlertLayout() {
SimpleXTheme {
IncomingCallAlertLayout(
invitation = RcvCallInvitation(
user = User.sampleData,
contact = Contact.sampleData,
callType = CallType(media = CallMediaType.Audio, capabilities = CallCapabilities(encryption = false)),
sharedKey = null,
callTs = Clock.System.now()
),
chatModel = SimplexApp.context.chatModel,
rejectCall = {},
ignoreCall = {},
acceptCall = {}

View File

@@ -1,15 +1,19 @@
package chat.simplex.app.views.call
import android.util.Log
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import chat.simplex.app.R
import chat.simplex.app.SimplexApp
import androidx.compose.ui.text.toUpperCase
import chat.simplex.app.*
import chat.simplex.app.model.Contact
import chat.simplex.app.model.User
import chat.simplex.app.views.helpers.generalGetString
import kotlinx.datetime.Instant
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import java.net.URI
import java.util.*
import kotlin.collections.ArrayList
data class Call(
val contact: Contact,
@@ -61,39 +65,39 @@ enum class CallState {
}
}
@Serializable class WVAPICall(val corrId: Int? = null, val command: WCallCommand)
@Serializable class WVAPIMessage(val corrId: Int? = null, val resp: WCallResponse, val command: WCallCommand? = null)
@Serializable data class WVAPICall(val corrId: Int? = null, val command: WCallCommand)
@Serializable data class WVAPIMessage(val corrId: Int? = null, val resp: WCallResponse, val command: WCallCommand? = null)
@Serializable
sealed class WCallCommand {
@Serializable @SerialName("capabilities") object Capabilities: WCallCommand()
@Serializable @SerialName("start") class Start(val media: CallMediaType, val aesKey: String? = null, val iceServers: List<RTCIceServer>? = null, val relay: Boolean? = null): WCallCommand()
@Serializable @SerialName("offer") class Offer(val offer: String, val iceCandidates: String, val media: CallMediaType, val aesKey: String? = null, val iceServers: List<RTCIceServer>? = null, val relay: Boolean? = null): WCallCommand()
@Serializable @SerialName("answer") class Answer (val answer: String, val iceCandidates: String): WCallCommand()
@Serializable @SerialName("ice") class Ice(val iceCandidates: String): WCallCommand()
@Serializable @SerialName("media") class Media(val media: CallMediaType, val enable: Boolean): WCallCommand()
@Serializable @SerialName("camera") class Camera(val camera: VideoCamera): WCallCommand()
@Serializable @SerialName("start") data class Start(val media: CallMediaType, val aesKey: String? = null, val iceServers: List<RTCIceServer>? = null, val relay: Boolean? = null): WCallCommand()
@Serializable @SerialName("offer") data class Offer(val offer: String, val iceCandidates: String, val media: CallMediaType, val aesKey: String? = null, val iceServers: List<RTCIceServer>? = null, val relay: Boolean? = null): WCallCommand()
@Serializable @SerialName("answer") data class Answer (val answer: String, val iceCandidates: String): WCallCommand()
@Serializable @SerialName("ice") data class Ice(val iceCandidates: String): WCallCommand()
@Serializable @SerialName("media") data class Media(val media: CallMediaType, val enable: Boolean): WCallCommand()
@Serializable @SerialName("camera") data class Camera(val camera: VideoCamera): WCallCommand()
@Serializable @SerialName("end") object End: WCallCommand()
}
@Serializable
sealed class WCallResponse {
@Serializable @SerialName("capabilities") class Capabilities(val capabilities: CallCapabilities): WCallResponse()
@Serializable @SerialName("offer") class Offer(val offer: String, val iceCandidates: String, val capabilities: CallCapabilities): WCallResponse()
@Serializable @SerialName("answer") class Answer(val answer: String, val iceCandidates: String): WCallResponse()
@Serializable @SerialName("ice") class Ice(val iceCandidates: String): WCallResponse()
@Serializable @SerialName("connection") class Connection(val state: ConnectionState): WCallResponse()
@Serializable @SerialName("connected") class Connected(val connectionInfo: ConnectionInfo): WCallResponse()
@Serializable @SerialName("capabilities") data class Capabilities(val capabilities: CallCapabilities): WCallResponse()
@Serializable @SerialName("offer") data class Offer(val offer: String, val iceCandidates: String, val capabilities: CallCapabilities): WCallResponse()
@Serializable @SerialName("answer") data class Answer(val answer: String, val iceCandidates: String): WCallResponse()
@Serializable @SerialName("ice") data class Ice(val iceCandidates: String): WCallResponse()
@Serializable @SerialName("connection") data class Connection(val state: ConnectionState): WCallResponse()
@Serializable @SerialName("connected") data class Connected(val connectionInfo: ConnectionInfo): WCallResponse()
@Serializable @SerialName("ended") object Ended: WCallResponse()
@Serializable @SerialName("ok") object Ok: WCallResponse()
@Serializable @SerialName("error") class Error(val message: String): WCallResponse()
@Serializable @SerialName("error") data class Error(val message: String): WCallResponse()
}
@Serializable class WebRTCCallOffer(val callType: CallType, val rtcSession: WebRTCSession)
@Serializable class WebRTCSession(val rtcSession: String, val rtcIceCandidates: String)
@Serializable class WebRTCExtraInfo(val rtcIceCandidates: String)
@Serializable class CallType(val media: CallMediaType, val capabilities: CallCapabilities)
@Serializable class RcvCallInvitation(val contact: Contact, val callType: CallType, val sharedKey: String? = null, val callTs: Instant) {
@Serializable data class WebRTCCallOffer(val callType: CallType, val rtcSession: WebRTCSession)
@Serializable data class WebRTCSession(val rtcSession: String, val rtcIceCandidates: String)
@Serializable data class WebRTCExtraInfo(val rtcIceCandidates: String)
@Serializable data class CallType(val media: CallMediaType, val capabilities: CallCapabilities)
@Serializable data class RcvCallInvitation(val user: User, val contact: Contact, val callType: CallType, val sharedKey: String? = null, val callTs: Instant) {
val callTypeText: String get() = generalGetString(when(callType.media) {
CallMediaType.Video -> if (sharedKey == null) R.string.video_call_no_encryption else R.string.encrypted_video_call
CallMediaType.Audio -> if (sharedKey == null) R.string.audio_call_no_encryption else R.string.encrypted_audio_call
@@ -103,19 +107,32 @@ sealed class WCallResponse {
CallMediaType.Audio -> R.string.incoming_audio_call
})
}
@Serializable class CallCapabilities(val encryption: Boolean)
@Serializable class ConnectionInfo(private val localCandidate: RTCIceCandidate?, private val remoteCandidate: RTCIceCandidate?) {
val text: String @Composable get() = when {
localCandidate?.candidateType == RTCIceCandidateType.Host && remoteCandidate?.candidateType == RTCIceCandidateType.Host ->
stringResource(R.string.call_connection_peer_to_peer)
localCandidate?.candidateType == RTCIceCandidateType.Relay && remoteCandidate?.candidateType == RTCIceCandidateType.Relay ->
stringResource(R.string.call_connection_via_relay)
else ->
"${localCandidate?.candidateType?.value ?: "unknown"} / ${remoteCandidate?.candidateType?.value ?: "unknown"}"
@Serializable data class CallCapabilities(val encryption: Boolean)
@Serializable data class ConnectionInfo(private val localCandidate: RTCIceCandidate?, private val remoteCandidate: RTCIceCandidate?) {
val text: String @Composable get() {
val local = localCandidate?.candidateType
val remote = remoteCandidate?.candidateType
return when {
local == RTCIceCandidateType.Host && remote == RTCIceCandidateType.Host ->
stringResource(R.string.call_connection_peer_to_peer)
local == RTCIceCandidateType.Relay && remote == RTCIceCandidateType.Relay ->
stringResource(R.string.call_connection_via_relay)
else ->
"${local?.value ?: "unknown"} / ${remote?.value ?: "unknown"}"
}
}
val protocolText: String get() {
val local = localCandidate?.protocol?.uppercase(Locale.ROOT) ?: "unknown"
val localRelay = localCandidate?.relayProtocol?.uppercase(Locale.ROOT) ?: "unknown"
val remote = remoteCandidate?.protocol?.uppercase(Locale.ROOT) ?: "unknown"
val localText = if (localRelay == local || localCandidate?.relayProtocol == null) local else "$local ($localRelay)"
return if (local == remote) localText else "$localText / $remote"
}
}
// https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidate
@Serializable class RTCIceCandidate(val candidateType: RTCIceCandidateType?)
@Serializable data class RTCIceCandidate(val candidateType: RTCIceCandidateType?, val protocol: String?, val relayProtocol: String?)
// https://developer.mozilla.org/en-US/docs/Web/API/RTCIceServer
@Serializable data class RTCIceServer(val urls: List<String>, val username: String? = null, val credential: String? = null)
@@ -150,7 +167,7 @@ enum class VideoCamera {
}
@Serializable
class ConnectionState(
data class ConnectionState(
val connectionState: String,
val iceConnectionState: String,
val iceGatheringState: String,
@@ -158,20 +175,22 @@ class ConnectionState(
)
// the servers are expected in this format:
// stun:stun.simplex.im:443
// turn:private:yleob6AVkiNI87hpR94Z@turn.simplex.im:443
// stun:stun.simplex.im:443?transport=tcp
// turn:private:yleob6AVkiNI87hpR94Z@turn.simplex.im:443?transport=tcp
fun parseRTCIceServer(str: String): RTCIceServer? {
var s = replaceScheme(str, "stun:")
s = replaceScheme(s, "turn:")
s = replaceScheme(s, "turns:")
val u = runCatching { URI(s) }.getOrNull()
if (u != null) {
val scheme = u.scheme
val host = u.host
val port = u.port
if (u.path == "" && (scheme == "stun" || scheme == "turn")) {
if (u.path == "" && (scheme == "stun" || scheme == "turn" || scheme == "turns")) {
val userInfo = u.userInfo?.split(":")
val query = if (u.query == null || u.query == "") "" else "?${u.query}"
return RTCIceServer(
urls = listOf("$scheme:$host:$port"),
urls = listOf("$scheme:$host:$port$query"),
username = userInfo?.getOrNull(0),
credential = userInfo?.getOrNull(1)
)

View File

@@ -54,10 +54,14 @@ fun ChatInfoView(
val chat = chatModel.chats.firstOrNull { it.id == chatModel.chatId.value }
val developerTools = chatModel.controller.appPrefs.developerTools.get()
if (chat != null) {
val contactNetworkStatus = remember(chatModel.networkStatuses.toMap()) {
mutableStateOf(chatModel.contactNetworkStatus(contact))
}
ChatInfoLayout(
chat,
contact,
connStats,
contactNetworkStatus.value,
customUserProfile,
localAlias,
connectionCode,
@@ -149,6 +153,7 @@ fun ChatInfoLayout(
chat: Chat,
contact: Contact,
connStats: ConnectionStats?,
contactNetworkStatus: NetworkStatus,
customUserProfile: Profile?,
localAlias: String,
connectionCode: String?,
@@ -200,9 +205,9 @@ fun ChatInfoLayout(
SectionItemView({
AlertManager.shared.showAlertMsg(
generalGetString(R.string.network_status),
chat.serverInfo.networkStatus.statusExplanation
contactNetworkStatus.statusExplanation
)}) {
NetworkStatusRow(chat.serverInfo.networkStatus)
NetworkStatusRow(contactNetworkStatus)
}
val rcvServers = connStats.rcvServers
if (rcvServers != null && rcvServers.isNotEmpty()) {
@@ -314,7 +319,7 @@ fun LocalAliasEditor(
}
@Composable
private fun NetworkStatusRow(networkStatus: Chat.NetworkStatus) {
private fun NetworkStatusRow(networkStatus: NetworkStatus) {
Row(
Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.SpaceBetween,
@@ -346,14 +351,14 @@ private fun NetworkStatusRow(networkStatus: Chat.NetworkStatus) {
}
@Composable
private fun ServerImage(networkStatus: Chat.NetworkStatus) {
private fun ServerImage(networkStatus: NetworkStatus) {
Box(Modifier.size(18.dp)) {
when (networkStatus) {
is Chat.NetworkStatus.Connected ->
is NetworkStatus.Connected ->
Icon(Icons.Filled.Circle, stringResource(R.string.icon_descr_server_status_connected), tint = MaterialTheme.colors.primaryVariant)
is Chat.NetworkStatus.Disconnected ->
is NetworkStatus.Disconnected ->
Icon(Icons.Filled.Pending, stringResource(R.string.icon_descr_server_status_disconnected), tint = HighOrLowlight)
is Chat.NetworkStatus.Error ->
is NetworkStatus.Error ->
Icon(Icons.Filled.Error, stringResource(R.string.icon_descr_server_status_error), tint = HighOrLowlight)
else -> Icon(Icons.Outlined.Circle, stringResource(R.string.icon_descr_server_status_pending), tint = HighOrLowlight)
}
@@ -446,14 +451,14 @@ fun PreviewChatInfoLayout() {
ChatInfoLayout(
chat = Chat(
chatInfo = ChatInfo.Direct.sampleData,
chatItems = arrayListOf(),
serverInfo = Chat.ServerInfo(Chat.NetworkStatus.Error("agent BROKER TIMEOUT"))
chatItems = arrayListOf()
),
Contact.sampleData,
localAlias = "",
connectionCode = "123",
developerTools = false,
connStats = null,
contactNetworkStatus = NetworkStatus.Connected(),
onLocalAliasChanged = {},
customUserProfile = null,
openPreferences = {},

View File

@@ -56,7 +56,13 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: () -> Unit) {
val user = chatModel.currentUser.value
val useLinkPreviews = chatModel.controller.appPrefs.privacyLinkPreviews.get()
val composeState = rememberSaveable(saver = ComposeState.saver()) {
mutableStateOf(ComposeState(useLinkPreviews = useLinkPreviews))
mutableStateOf(
if (chatModel.draftChatId.value == chatId && chatModel.draft.value != null) {
chatModel.draft.value ?: ComposeState(useLinkPreviews = useLinkPreviews)
} else {
ComposeState(useLinkPreviews = useLinkPreviews)
}
)
}
val attachmentOption = rememberSaveable { mutableStateOf<AttachmentOption?>(null) }
val attachmentBottomSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)
@@ -146,9 +152,14 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: () -> Unit) {
}
} else if (chat.chatInfo is ChatInfo.Group) {
setGroupMembers(chat.chatInfo.groupInfo, chatModel)
var groupLink = chatModel.controller.apiGetGroupLink(chat.chatInfo.groupInfo.groupId)
val link = chatModel.controller.apiGetGroupLink(chat.chatInfo.groupInfo.groupId)
var groupLink = link?.first
var groupLinkMemberRole = link?.second
ModalManager.shared.showModalCloseable(true) { close ->
GroupChatInfoView(chatModel, groupLink, { groupLink = it }, close)
GroupChatInfoView(chatModel, groupLink, groupLinkMemberRole, {
groupLink = it.first;
groupLinkMemberRole = it.second
}, close)
}
}
}
@@ -204,7 +215,10 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: () -> Unit) {
}
},
receiveFile = { fileId ->
withApi { chatModel.controller.receiveFile(fileId) }
val user = chatModel.currentUser.value
if (user != null) {
withApi { chatModel.controller.receiveFile(user, fileId) }
}
},
joinGroup = { groupId ->
withApi { chatModel.controller.apiJoinGroup(groupId) }
@@ -226,9 +240,9 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: () -> Unit) {
chatModel.callManager.acceptIncomingCall(invitation = invitation)
}
},
acceptFeature = { contact, feature ->
acceptFeature = { contact, feature, param ->
withApi {
chatModel.controller.allowFeatureToContact(contact, feature)
chatModel.controller.allowFeatureToContact(contact, feature, param)
}
},
addMembers = { groupInfo ->
@@ -287,7 +301,7 @@ fun ChatLayout(
joinGroup: (Long) -> Unit,
startCall: (CallMediaType) -> Unit,
acceptCall: (Contact) -> Unit,
acceptFeature: (Contact, ChatFeature) -> Unit,
acceptFeature: (Contact, ChatFeature, Int?) -> Unit,
addMembers: (GroupInfo) -> Unit,
markRead: (CC.ItemRange, unreadCountAfter: Int?) -> Unit,
changeNtfsState: (Boolean, currentValue: MutableState<Boolean>) -> Unit,
@@ -503,7 +517,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
receiveFile: (Long) -> Unit,
joinGroup: (Long) -> Unit,
acceptCall: (Contact) -> Unit,
acceptFeature: (Contact, ChatFeature) -> Unit,
acceptFeature: (Contact, ChatFeature, Int?) -> Unit,
markRead: (CC.ItemRange, unreadCountAfter: Int?) -> Unit,
setFloatingButton: (@Composable () -> Unit) -> Unit,
onComposed: () -> Unit,
@@ -529,7 +543,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
}
Spacer(Modifier.size(8.dp))
val reversedChatItems by remember { derivedStateOf { chatItems.reversed() } }
val reversedChatItems by remember { derivedStateOf { chatItems.reversed().toList() } }
val maxHeightRounded = with(LocalDensity.current) { maxHeight.roundToPx() }
val scrollToItem: (Long) -> Unit = { itemId: Long ->
val index = reversedChatItems.indexOfFirst { it.id == itemId }
@@ -568,7 +582,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
scope.launch {
if (composeState.value.editing) {
composeState.value = ComposeState(contextItem = ComposeContextItem.QuotedItem(cItem), useLinkPreviews = useLinkPreviews)
} else {
} else if (cItem.id != ChatItem.TEMP_LIVE_CHAT_ITEM_ID) {
composeState.value = composeState.value.copy(contextItem = ComposeContextItem.QuotedItem(cItem))
}
}
@@ -665,10 +679,18 @@ private fun ScrollToBottom(chatId: ChatId, listState: LazyListState, chatItems:
.distinctUntilChanged()
.filter { listState.layoutInfo.visibleItemsInfo.firstOrNull()?.key != it }
.collect {
if (listState.firstVisibleItemIndex == 0) {
listState.animateScrollToItem(0)
} else {
listState.animateScrollBy(scrollDistance)
try {
if (listState.firstVisibleItemIndex == 0) {
listState.animateScrollToItem(0)
} else {
listState.animateScrollBy(scrollDistance)
}
} catch (e: CancellationException) {
/**
* When you tap and hold a finger on a lazy column with chatItems, and then you receive a message,
* this coroutine will be canceled with the message "Current mutation had a higher priority" because of animatedScroll.
* Which breaks auto-scrolling to bottom. So just ignoring the exception
* */
}
}
}
@@ -1026,7 +1048,7 @@ fun PreviewChatLayout() {
joinGroup = {},
startCall = {},
acceptCall = { _ -> },
acceptFeature = { _, _ -> },
acceptFeature = { _, _, _ -> },
addMembers = { _ -> },
markRead = { _, _ -> },
changeNtfsState = { _, _ -> },
@@ -1085,7 +1107,7 @@ fun PreviewGroupChatLayout() {
joinGroup = {},
startCall = {},
acceptCall = { _ -> },
acceptFeature = { _, _ -> },
acceptFeature = { _, _, _ -> },
addMembers = { _ -> },
markRead = { _, _ -> },
changeNtfsState = { _, _ -> },

View File

@@ -1,7 +1,8 @@
@file:UseSerializers(UriSerializer::class)
package chat.simplex.app.views.chat
import ComposeVoiceView
import ComposeFileView
import ComposeVoiceView
import android.Manifest
import android.app.Activity
import android.content.*
@@ -16,11 +17,13 @@ import android.util.Log
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContract
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.filled.AttachFile
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.outlined.Reply
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.Saver
@@ -30,30 +33,28 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import androidx.core.net.toFile
import androidx.core.net.toUri
import chat.simplex.app.*
import chat.simplex.app.R
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.views.chat.item.*
import chat.simplex.app.views.helpers.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.*
import java.io.File
import java.nio.file.Files
@Serializable
sealed class ComposePreview {
@Serializable object NoPreview: ComposePreview()
@Serializable class CLinkPreview(val linkPreview: LinkPreview?): ComposePreview()
@Serializable class ImagePreview(val images: List<String>): ComposePreview()
@Serializable class VoicePreview(val voice: String, val durationMs: Int, val finished: Boolean): ComposePreview()
@Serializable class FilePreview(val fileName: String): ComposePreview()
@Serializable class ImagePreview(val images: List<String>, val content: List<UploadContent>): ComposePreview()
@Serializable data class VoicePreview(val voice: String, val durationMs: Int, val finished: Boolean): ComposePreview()
@Serializable class FilePreview(val fileName: String, val uri: Uri): ComposePreview()
}
@Serializable
@@ -67,7 +68,8 @@ sealed class ComposeContextItem {
data class LiveMessage(
val chatItem: ChatItem,
val typedMsg: String,
val sentMsg: String
val sentMsg: String,
val sent: Boolean
)
@Serializable
@@ -103,6 +105,9 @@ data class ComposeState(
}
hasContent && !inProgress
}
val endLiveDisabled: Boolean
get() = liveMessage != null && message.isEmpty() && preview is ComposePreview.NoPreview && contextItem is ComposeContextItem.NoContextItem
val linkPreviewAllowed: Boolean
get() =
when (preview) {
@@ -128,6 +133,9 @@ data class ComposeState(
}
}
val empty: Boolean
get() = message.isEmpty() && preview is ComposePreview.NoPreview
companion object {
fun saver(): Saver<MutableState<ComposeState>, *> = Saver(
save = { json.encodeToString(serializer(), it.value) },
@@ -148,15 +156,14 @@ sealed class RecordingState {
}
fun chatItemPreview(chatItem: ChatItem): ComposePreview {
val fileName = chatItem.file?.fileName ?: ""
return when (val mc = chatItem.content.msgContent) {
is MsgContent.MCText -> ComposePreview.NoPreview
is MsgContent.MCLink -> ComposePreview.CLinkPreview(linkPreview = mc.preview)
is MsgContent.MCImage -> ComposePreview.ImagePreview(images = listOf(mc.image))
is MsgContent.MCVoice -> ComposePreview.VoicePreview(voice = chatItem.file?.fileName ?: "", mc.duration / 1000, true)
is MsgContent.MCFile -> {
val fileName = chatItem.file?.fileName ?: ""
ComposePreview.FilePreview(fileName)
}
// TODO: include correct type
is MsgContent.MCImage -> ComposePreview.ImagePreview(images = listOf(mc.image), listOf(UploadContent.SimpleImage(getAppFileUri(fileName))))
is MsgContent.MCVoice -> ComposePreview.VoicePreview(voice = fileName, mc.duration / 1000, true)
is MsgContent.MCFile -> ComposePreview.FilePreview(fileName, getAppFileUri(fileName))
is MsgContent.MCUnknown, null -> ComposePreview.NoPreview
}
}
@@ -177,21 +184,12 @@ fun ComposeView(
val useLinkPreviews = chatModel.controller.appPrefs.privacyLinkPreviews.get()
val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground)
val textStyle = remember { mutableStateOf(smallFont) }
// attachments
val chosenContent = rememberSaveable { mutableStateOf<List<UploadContent>>(emptyList()) }
val audioSaver = Saver<MutableState<Pair<Uri, Int>?>, Pair<String, Int>>(
save = { it.value.let { if (it == null) null else it.first.toString() to it.second } },
restore = { mutableStateOf(Uri.parse(it.first) to it.second) }
)
val chosenAudio = rememberSaveable(saver = audioSaver) { mutableStateOf(null) }
val chosenFile = rememberSaveable { mutableStateOf<Uri?>(null) }
val cameraLauncher = rememberCameraLauncher { uri: Uri? ->
if (uri != null) {
val source = ImageDecoder.createSource(SimplexApp.context.contentResolver, uri)
val bitmap = ImageDecoder.decodeBitmap(source)
val imagePreview = resizeImageToStrSize(bitmap, maxDataSize = 14000)
chosenContent.value = listOf(UploadContent.SimpleImage(uri))
composeState.value = composeState.value.copy(preview = ComposePreview.ImagePreview(listOf(imagePreview)))
composeState.value = composeState.value.copy(preview = ComposePreview.ImagePreview(listOf(imagePreview), listOf(UploadContent.SimpleImage(uri))))
}
}
val cameraPermissionLauncher = rememberPermissionLauncher { isGranted: Boolean ->
@@ -238,8 +236,7 @@ fun ComposeView(
}
if (imagesPreview.isNotEmpty()) {
chosenContent.value = content
composeState.value = composeState.value.copy(message = text ?: composeState.value.message, preview = ComposePreview.ImagePreview(imagesPreview))
composeState.value = composeState.value.copy(message = text ?: composeState.value.message, preview = ComposePreview.ImagePreview(imagesPreview, content))
}
}
val processPickedFile = { uri: Uri?, text: String? ->
@@ -248,8 +245,7 @@ fun ComposeView(
if (fileSize != null && fileSize <= MAX_FILE_SIZE) {
val fileName = getFileName(SimplexApp.context, uri)
if (fileName != null) {
chosenFile.value = uri
composeState.value = composeState.value.copy(message = text ?: composeState.value.message, preview = ComposePreview.FilePreview(fileName))
composeState.value = composeState.value.copy(message = text ?: composeState.value.message, preview = ComposePreview.FilePreview(fileName, uri))
}
} else {
AlertManager.shared.showAlertMsg(
@@ -349,9 +345,12 @@ fun ComposeView(
}
recState.value = RecordingState.NotStarted
textStyle.value = smallFont
chosenContent.value = emptyList()
chosenAudio.value = null
chosenFile.value = null
chatModel.removeLiveDummy()
}
fun deleteUnusedFiles() {
chatModel.filesToDelete.forEach { it.delete() }
chatModel.filesToDelete.clear()
}
suspend fun send(cInfo: ChatInfo, mc: MsgContent, quoted: Long?, file: String? = null, live: Boolean = false): ChatItem? {
@@ -421,15 +420,17 @@ fun ComposeView(
return null
}
val liveMessage = cs.liveMessage
if (!live) {
if (liveMessage != null) composeState.value = cs.copy(liveMessage = null)
sending()
}
if (cs.contextItem is ComposeContextItem.EditingItem) {
val ei = cs.contextItem.chatItem
sent = updateMessage(ei, cInfo, live)
} else if (cs.liveMessage != null) {
sent = updateMessage(cs.liveMessage.chatItem, cInfo, live)
} else if (liveMessage != null && liveMessage.sent) {
sent = updateMessage(liveMessage.chatItem, cInfo, live)
} else {
val msgs: ArrayList<MsgContent> = ArrayList()
val files: ArrayList<String> = ArrayList()
@@ -437,35 +438,33 @@ fun ComposeView(
ComposePreview.NoPreview -> msgs.add(MsgContent.MCText(msgText))
is ComposePreview.CLinkPreview -> msgs.add(checkLinkPreview())
is ComposePreview.ImagePreview -> {
chosenContent.value.forEachIndexed { index, it ->
preview.content.forEachIndexed { index, it ->
val file = when (it) {
is UploadContent.SimpleImage -> saveImage(context, it.uri)
is UploadContent.AnimatedImage -> saveAnimImage(context, it.uri)
}
if (file != null) {
files.add(file)
msgs.add(MsgContent.MCImage(if (chosenContent.value.lastIndex == index) msgText else "", preview.images[index]))
msgs.add(MsgContent.MCImage(if (preview.content.lastIndex == index) msgText else "", preview.images[index]))
}
}
}
is ComposePreview.VoicePreview -> {
val chosenAudioVal = chosenAudio.value
if (chosenAudioVal != null) {
val file = chosenAudioVal.first.toFile().name
files.add((file))
chatModel.filesToDelete.remove(chosenAudioVal.first.toFile())
AudioPlayer.stop(chosenAudioVal.first.toFile().absolutePath)
msgs.add(MsgContent.MCVoice(if (msgs.isEmpty()) msgText else "", chosenAudioVal.second / 1000))
val tmpFile = File(preview.voice)
AudioPlayer.stop(tmpFile.absolutePath)
val actualFile = File(getAppFilePath(SimplexApp.context, tmpFile.name.replaceAfter(RecorderNative.extension, "")))
withContext(Dispatchers.IO) {
Files.move(tmpFile.toPath(), actualFile.toPath())
}
files.add(actualFile.name)
deleteUnusedFiles()
msgs.add(MsgContent.MCVoice(if (msgs.isEmpty()) msgText else "", preview.durationMs / 1000))
}
is ComposePreview.FilePreview -> {
val chosenFileVal = chosenFile.value
if (chosenFileVal != null) {
val file = saveFileFromUri(context, chosenFileVal)
if (file != null) {
files.add((file))
msgs.add(MsgContent.MCFile(if (msgs.isEmpty()) msgText else ""))
}
val file = saveFileFromUri(context, preview.uri)
if (file != null) {
files.add((file))
msgs.add(MsgContent.MCFile(if (msgs.isEmpty()) msgText else ""))
}
}
}
@@ -480,7 +479,7 @@ fun ComposeView(
if (content !is MsgContent.MCVoice && index == msgs.lastIndex) live else false
)
}
if (sent == null && chosenContent.value.isNotEmpty()) {
if (sent == null && (cs.preview is ComposePreview.ImagePreview || cs.preview is ComposePreview.FilePreview || cs.preview is ComposePreview.VoicePreview)) {
sent = send(cInfo, MsgContent.MCText(msgText), quotedItemId, null, live)
}
}
@@ -509,7 +508,6 @@ fun ComposeView(
fun onAudioAdded(filePath: String, durationMs: Int, finished: Boolean) {
val file = File(filePath)
chosenAudio.value = file.toUri() to durationMs
chatModel.filesToDelete.add(file)
composeState.value = composeState.value.copy(preview = ComposePreview.VoicePreview(filePath, durationMs, finished))
}
@@ -532,7 +530,6 @@ fun ComposeView(
fun cancelImages() {
composeState.value = composeState.value.copy(preview = ComposePreview.NoPreview)
chosenContent.value = emptyList()
}
fun cancelVoice() {
@@ -544,12 +541,10 @@ fun ComposeView(
AudioPlayer.stop(filePath)
filePath?.let { File(it).delete() }
}
chosenAudio.value = null
}
fun cancelFile() {
composeState.value = composeState.value.copy(preview = ComposePreview.NoPreview)
chosenFile.value = null
}
fun truncateToWords(s: String): String {
@@ -567,13 +562,16 @@ fun ComposeView(
}
suspend fun sendLiveMessage() {
val typedMsg = composeState.value.message
val sentMsg = truncateToWords(typedMsg)
if (composeState.value.liveMessage == null) {
val ci = sendMessageAsync(sentMsg, live = true)
val cs = composeState.value
val typedMsg = cs.message
if ((cs.sendEnabled() || cs.contextItem is ComposeContextItem.QuotedItem) && (cs.liveMessage == null || !cs.liveMessage?.sent)) {
val ci = sendMessageAsync(typedMsg, live = true)
if (ci != null) {
composeState.value = composeState.value.copy(liveMessage = LiveMessage(ci, typedMsg = typedMsg, sentMsg = sentMsg))
composeState.value = composeState.value.copy(liveMessage = LiveMessage(ci, typedMsg = typedMsg, sentMsg = typedMsg, sent = true))
}
} else if (cs.liveMessage == null) {
val cItem = chatModel.addLiveDummy(chat.chatInfo)
composeState.value = composeState.value.copy(liveMessage = LiveMessage(cItem, typedMsg = typedMsg, sentMsg = typedMsg, sent = false))
}
}
@@ -590,7 +588,7 @@ fun ComposeView(
if (sentMsg != null) {
val ci = sendMessageAsync(sentMsg, live = true)
if (ci != null) {
composeState.value = composeState.value.copy(liveMessage = LiveMessage(ci, typedMsg = typedMsg, sentMsg = sentMsg))
composeState.value = composeState.value.copy(liveMessage = LiveMessage(ci, typedMsg = typedMsg, sentMsg = sentMsg, sent = true))
}
} else if (liveMessage.typedMsg != typedMsg) {
composeState.value = composeState.value.copy(liveMessage = liveMessage.copy(typedMsg = typedMsg))
@@ -649,6 +647,9 @@ fun ComposeView(
chatModel.sharedContent.value = null
}
val userCanSend = rememberUpdatedState(chat.userCanSend)
val userIsObserver = rememberUpdatedState(chat.userIsObserver)
Column {
contextItemView()
when {
@@ -660,11 +661,11 @@ fun ComposeView(
modifier = Modifier.padding(end = 8.dp),
verticalAlignment = Alignment.Bottom,
) {
IconButton(showChooseAttachment, enabled = !composeState.value.attachmentDisabled) {
IconButton(showChooseAttachment, enabled = !composeState.value.attachmentDisabled && rememberUpdatedState(chat.userCanSend).value) {
Icon(
Icons.Filled.AttachFile,
contentDescription = stringResource(R.string.attach),
tint = if (!composeState.value.attachmentDisabled) MaterialTheme.colors.primary else HighOrLowlight,
tint = if (!composeState.value.attachmentDisabled && userCanSend.value) MaterialTheme.colors.primary else HighOrLowlight,
modifier = Modifier
.size(28.dp)
.clip(CircleShape)
@@ -672,7 +673,7 @@ fun ComposeView(
}
val allowedVoiceByPrefs = remember(chat.chatInfo) { chat.chatInfo.featureEnabled(ChatFeature.Voice) }
LaunchedEffect(allowedVoiceByPrefs) {
if (!allowedVoiceByPrefs && chosenAudio.value != null) {
if (!allowedVoiceByPrefs && composeState.value.preview is ComposePreview.VoicePreview) {
// Voice was disabled right when this user records it, just cancel it
cancelVoice()
}
@@ -695,13 +696,42 @@ fun ComposeView(
}
}
fun clearCurrentDraft() {
if (chatModel.draftChatId.value == chat.id) {
chatModel.draft.value = null
chatModel.draftChatId.value = null
}
}
LaunchedEffect(rememberUpdatedState(chat.userCanSend).value) {
if (!chat.userCanSend) {
clearCurrentDraft()
clearState()
}
}
val activity = LocalContext.current as Activity
DisposableEffect(Unit) {
val orientation = activity.resources.configuration.orientation
onDispose {
if (orientation == activity.resources.configuration.orientation && composeState.value.liveMessage != null) {
sendMessage()
resetLinkPreview()
if (orientation == activity.resources.configuration.orientation) {
val cs = composeState.value
if (cs.liveMessage != null && (cs.message.isNotEmpty() || cs.liveMessage.sent)) {
sendMessage()
resetLinkPreview()
clearCurrentDraft()
deleteUnusedFiles()
} else if (!composeState.value.empty) {
if (cs.preview is ComposePreview.VoicePreview && !cs.preview.finished) {
composeState.value = cs.copy(preview = cs.preview.copy(finished = true))
}
chatModel.draft.value = composeState.value
chatModel.draftChatId.value = chat.id
} else {
clearCurrentDraft()
deleteUnusedFiles()
}
chatModel.removeLiveDummy()
}
}
}
@@ -715,12 +745,18 @@ fun ComposeView(
needToAllowVoiceToContact,
allowedVoiceByPrefs,
allowVoiceToContact = ::allowVoiceToContact,
userIsObserver = userIsObserver.value,
userCanSend = userCanSend.value,
sendMessage = {
sendMessage()
resetLinkPreview()
},
sendLiveMessage = ::sendLiveMessage,
updateLiveMessage = ::updateLiveMessage,
cancelLiveMessage = {
composeState.value = composeState.value.copy(liveMessage = null)
chatModel.removeLiveDummy()
},
onMessageChange = ::onMessageChange,
textStyle = textStyle
)

View File

@@ -6,8 +6,10 @@ import android.app.Activity
import android.content.Context
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.os.Build
import android.text.InputType
import android.view.ViewGroup
import android.view.WindowManager
import android.view.inputmethod.*
import android.widget.EditText
import androidx.compose.animation.core.*
@@ -58,22 +60,37 @@ fun SendMsgView(
liveMessageAlertShown: SharedPreference<Boolean>,
needToAllowVoiceToContact: Boolean,
allowedVoiceByPrefs: Boolean,
userIsObserver: Boolean,
userCanSend: Boolean,
allowVoiceToContact: () -> Unit,
sendMessage: () -> Unit,
sendLiveMessage: ( suspend () -> Unit)? = null,
sendLiveMessage: (suspend () -> Unit)? = null,
updateLiveMessage: (suspend () -> Unit)? = null,
cancelLiveMessage: (() -> Unit)? = null,
onMessageChange: (String) -> Unit,
textStyle: MutableState<TextStyle>
) {
Box(Modifier.padding(vertical = 8.dp)) {
val cs = composeState.value
val showProgress = cs.inProgress && (cs.preview is ComposePreview.ImagePreview || cs.preview is ComposePreview.FilePreview)
val showProgress = cs.inProgress && (cs.preview is ComposePreview.ImagePreview || cs.preview is ComposePreview.FilePreview)
val showVoiceButton = cs.message.isEmpty() && showVoiceRecordIcon && !composeState.value.editing &&
cs.liveMessage == null && (cs.preview is ComposePreview.NoPreview || recState.value is RecordingState.Started)
NativeKeyboard(composeState, textStyle, onMessageChange)
val showDeleteTextButton = rememberSaveable { mutableStateOf(false) }
NativeKeyboard(composeState, textStyle, showDeleteTextButton, userIsObserver, onMessageChange)
// Disable clicks on text field
if (cs.preview is ComposePreview.VoicePreview) {
Box(Modifier.matchParentSize().clickable(enabled = false, onClick = { }))
if (cs.preview is ComposePreview.VoicePreview || !userCanSend) {
Box(Modifier
.matchParentSize()
.clickable(enabled = !userCanSend, indication = null, interactionSource = remember { MutableInteractionSource() }, onClick = {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.observer_cant_send_message_title),
text = generalGetString(R.string.observer_cant_send_message_desc)
)
})
)
}
if (showDeleteTextButton.value) {
DeleteTextButton(composeState)
}
Box(Modifier.align(Alignment.BottomEnd)) {
val sendButtonSize = remember { Animatable(36f) }
@@ -92,11 +109,11 @@ fun SendMsgView(
Row(verticalAlignment = Alignment.CenterVertically) {
val stopRecOnNextClick = remember { mutableStateOf(false) }
when {
needToAllowVoiceToContact || !allowedVoiceByPrefs -> {
DisallowedVoiceButton {
needToAllowVoiceToContact || !allowedVoiceByPrefs || !userCanSend -> {
DisallowedVoiceButton(userCanSend) {
if (needToAllowVoiceToContact) {
showNeedToAllowVoiceAlert(allowVoiceToContact)
} else {
} else if (!allowedVoiceByPrefs) {
showDisabledVoiceAlert(isDirectChat)
}
}
@@ -106,9 +123,12 @@ fun SendMsgView(
else ->
RecordVoiceView(recState, stopRecOnNextClick)
}
if (sendLiveMessage != null && updateLiveMessage != null && (cs.preview !is ComposePreview.VoicePreview || !stopRecOnNextClick.value)) {
if (sendLiveMessage != null
&& updateLiveMessage != null
&& (cs.preview !is ComposePreview.VoicePreview || !stopRecOnNextClick.value)
&& cs.contextItem is ComposeContextItem.NoContextItem) {
Spacer(Modifier.width(10.dp))
StartLiveMessageButton {
StartLiveMessageButton(userCanSend) {
if (composeState.value.preview is ComposePreview.NoPreview) {
startLiveMessage(scope, sendLiveMessage, updateLiveMessage, sendButtonSize, sendButtonAlpha, composeState, liveMessageAlertShown)
}
@@ -116,15 +136,24 @@ fun SendMsgView(
}
}
}
cs.liveMessage?.sent == false && cs.message.isEmpty() -> {
CancelLiveMessageButton {
cancelLiveMessage?.invoke()
}
}
else -> {
val cs = composeState.value
val icon = if (cs.editing || cs.liveMessage != null) Icons.Filled.Check else Icons.Outlined.ArrowUpward
val color = if (cs.sendEnabled()) MaterialTheme.colors.primary else HighOrLowlight
if (composeState.value.liveMessage == null &&
val disabled = !cs.sendEnabled() ||
(!allowedVoiceByPrefs && cs.preview is ComposePreview.VoicePreview) ||
cs.endLiveDisabled
if (cs.liveMessage == null &&
cs.preview !is ComposePreview.VoicePreview && !cs.editing &&
cs.contextItem is ComposeContextItem.NoContextItem &&
sendLiveMessage != null && updateLiveMessage != null
) {
var showDropdown by rememberSaveable { mutableStateOf(false) }
SendTextButton(icon, color, sendButtonSize, sendButtonAlpha, cs.sendEnabled(), sendMessage) { showDropdown = true }
SendMsgButton(icon, sendButtonSize, sendButtonAlpha, !disabled, sendMessage) { showDropdown = true }
DropdownMenu(
expanded = showDropdown,
@@ -133,7 +162,7 @@ fun SendMsgView(
) {
ItemAction(
generalGetString(R.string.send_live_message),
Icons.Filled.MoreHoriz,
Icons.Filled.Bolt,
onClick = {
startLiveMessage(scope, sendLiveMessage, updateLiveMessage, sendButtonSize, sendButtonAlpha, composeState, liveMessageAlertShown)
showDropdown = false
@@ -141,7 +170,7 @@ fun SendMsgView(
)
}
} else {
SendTextButton(icon, color, sendButtonSize, sendButtonAlpha, cs.sendEnabled(), sendMessage)
SendMsgButton(icon, sendButtonSize, sendButtonAlpha, !disabled, sendMessage)
}
}
}
@@ -153,6 +182,8 @@ fun SendMsgView(
private fun NativeKeyboard(
composeState: MutableState<ComposeState>,
textStyle: MutableState<TextStyle>,
showDeleteTextButton: MutableState<Boolean>,
userIsObserver: Boolean,
onMessageChange: (String) -> Unit
) {
val cs = composeState.value
@@ -163,7 +194,6 @@ private fun NativeKeyboard(
val paddingTop = with(LocalDensity.current) { 7.dp.roundToPx() }
val paddingEnd = with(LocalDensity.current) { 45.dp.roundToPx() }
val paddingBottom = with(LocalDensity.current) { 7.dp.roundToPx() }
var showKeyboard by remember { mutableStateOf(false) }
LaunchedEffect(cs.contextItem) {
if (cs.contextItem is ComposeContextItem.QuotedItem) {
@@ -184,6 +214,7 @@ private fun NativeKeyboard(
) {
super.setOnReceiveContentListener(mimeTypes, listener)
}
override fun onCreateInputConnection(editorInfo: EditorInfo): InputConnection {
val connection = super.onCreateInputConnection(editorInfo)
EditorInfoCompat.setContentMimeTypes(editorInfo, arrayOf("image/*"))
@@ -230,14 +261,32 @@ private fun NativeKeyboard(
imm.showSoftInput(it, InputMethodManager.SHOW_IMPLICIT)
showKeyboard = false
}
showDeleteTextButton.value = it.lineCount >= 4
}
if (composeState.value.preview is ComposePreview.VoicePreview) {
Text(
generalGetString(R.string.voice_message_send_text),
Modifier.padding(padding),
color = HighOrLowlight,
style = textStyle.value.copy(fontStyle = FontStyle.Italic)
)
ComposeOverlay(R.string.voice_message_send_text, textStyle, padding)
} else if (userIsObserver) {
ComposeOverlay(R.string.you_are_observer, textStyle, padding)
}
}
@Composable
private fun ComposeOverlay(textId: Int, textStyle: MutableState<TextStyle>, padding: PaddingValues) {
Text(
generalGetString(textId),
Modifier.padding(padding),
color = HighOrLowlight,
style = textStyle.value.copy(fontStyle = FontStyle.Italic)
)
}
@Composable
private fun BoxScope.DeleteTextButton(composeState: MutableState<ComposeState>) {
IconButton(
{ composeState.value = composeState.value.copy(message = "") },
Modifier.align(Alignment.TopEnd).size(36.dp)
) {
Icon(Icons.Filled.Close, null, Modifier.padding(7.dp).size(36.dp), tint = HighOrLowlight)
}
}
@@ -291,8 +340,8 @@ private fun RecordVoiceView(recState: MutableState<RecordingState>, stopRecOnNex
}
@Composable
private fun DisallowedVoiceButton(onClick: () -> Unit) {
IconButton(onClick, Modifier.size(36.dp)) {
private fun DisallowedVoiceButton(enabled: Boolean, onClick: () -> Unit) {
IconButton(onClick, Modifier.size(36.dp), enabled = enabled) {
Icon(
Icons.Outlined.KeyboardVoice,
stringResource(R.string.icon_descr_record_voice_message),
@@ -312,7 +361,7 @@ private fun VoiceButtonWithoutPermission(onClick: () -> Unit) {
stringResource(R.string.icon_descr_record_voice_message),
tint = MaterialTheme.colors.primary,
modifier = Modifier
.size(36.dp)
.size(34.dp)
.padding(4.dp)
)
}
@@ -323,7 +372,9 @@ private fun LockToCurrentOrientationUntilDispose() {
val context = LocalContext.current
DisposableEffect(Unit) {
val activity = context as Activity
activity.requestedOrientation = when (activity.display?.rotation) {
val manager = context.getSystemService(Activity.WINDOW_SERVICE) as WindowManager
val rotation = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) manager.defaultDisplay.rotation else activity.display?.rotation
activity.requestedOrientation = when (rotation) {
android.view.Surface.ROTATION_90 -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
android.view.Surface.ROTATION_180 -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT
android.view.Surface.ROTATION_270 -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
@@ -334,7 +385,6 @@ private fun LockToCurrentOrientationUntilDispose() {
}
}
@Composable
private fun StopRecordButton(onClick: () -> Unit) {
IconButton(onClick, Modifier.size(36.dp)) {
@@ -357,7 +407,7 @@ private fun RecordVoiceButton(interactionSource: MutableInteractionSource) {
stringResource(R.string.icon_descr_record_voice_message),
tint = MaterialTheme.colors.primary,
modifier = Modifier
.size(36.dp)
.size(34.dp)
.padding(4.dp)
)
}
@@ -369,9 +419,24 @@ private fun ProgressIndicator() {
}
@Composable
private fun SendTextButton(
private fun CancelLiveMessageButton(
onClick: () -> Unit
) {
IconButton(onClick, Modifier.size(36.dp)) {
Icon(
Icons.Filled.Close,
stringResource(R.string.icon_descr_cancel_live_message),
tint = MaterialTheme.colors.primary,
modifier = Modifier
.size(36.dp)
.padding(4.dp)
)
}
}
@Composable
private fun SendMsgButton(
icon: ImageVector,
backgroundColor: Color,
sizeDp: Animatable<Float, AnimationVector1D>,
alpha: Animatable<Float, AnimationVector1D>,
enabled: Boolean,
@@ -400,20 +465,20 @@ private fun SendTextButton(
.padding(4.dp)
.alpha(alpha.value)
.clip(CircleShape)
.background(backgroundColor)
.background(if (enabled) MaterialTheme.colors.primary else HighOrLowlight)
.padding(3.dp)
)
}
}
@Composable
private fun StartLiveMessageButton(onClick: () -> Unit) {
private fun StartLiveMessageButton(enabled: Boolean, onClick: () -> Unit) {
val interactionSource = remember { MutableInteractionSource() }
Box(
modifier = Modifier.requiredSize(36.dp)
.clickable(
onClick = onClick,
enabled = true,
enabled = enabled,
role = Role.Button,
interactionSource = interactionSource,
indication = rememberRipple(bounded = false, radius = 24.dp)
@@ -421,15 +486,12 @@ private fun StartLiveMessageButton(onClick: () -> Unit) {
contentAlignment = Alignment.Center
) {
Icon(
Icons.Filled.MoreHoriz,
Icons.Filled.Bolt,
stringResource(R.string.icon_descr_send_message),
tint = Color.White,
tint = if (enabled) MaterialTheme.colors.primary else HighOrLowlight,
modifier = Modifier
.size(36.dp)
.padding(4.dp)
.clip(CircleShape)
.background(MaterialTheme.colors.primary)
.padding(1.dp)
)
}
}
@@ -457,9 +519,10 @@ private fun startLiveMessage(
sendButtonAlpha.snapTo(1f)
}
scope.launch {
delay(3000)
while (composeState.value.liveMessage != null) {
delay(3000)
update()
delay(3000)
}
}
}
@@ -521,11 +584,13 @@ fun PreviewSendMsgView() {
SendMsgView(
composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) },
showVoiceRecordIcon = false,
recState = mutableStateOf(RecordingState.NotStarted),
recState = remember { mutableStateOf(RecordingState.NotStarted) },
isDirectChat = true,
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
needToAllowVoiceToContact = false,
allowedVoiceByPrefs = true,
userIsObserver = false,
userCanSend = true,
allowVoiceToContact = {},
sendMessage = {},
onMessageChange = { _ -> },
@@ -549,11 +614,13 @@ fun PreviewSendMsgViewEditing() {
SendMsgView(
composeState = remember { mutableStateOf(composeStateEditing) },
showVoiceRecordIcon = false,
recState = mutableStateOf(RecordingState.NotStarted),
recState = remember { mutableStateOf(RecordingState.NotStarted) },
isDirectChat = true,
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
needToAllowVoiceToContact = false,
allowedVoiceByPrefs = true,
userIsObserver = false,
userCanSend = true,
allowVoiceToContact = {},
sendMessage = {},
onMessageChange = { _ -> },
@@ -572,16 +639,18 @@ fun PreviewSendMsgViewEditing() {
fun PreviewSendMsgViewInProgress() {
val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground)
val textStyle = remember { mutableStateOf(smallFont) }
val composeStateInProgress = ComposeState(preview = ComposePreview.FilePreview("test.txt"), inProgress = true, useLinkPreviews = true)
val composeStateInProgress = ComposeState(preview = ComposePreview.FilePreview("test.txt", getAppFileUri("test.txt")), inProgress = true, useLinkPreviews = true)
SimpleXTheme {
SendMsgView(
composeState = remember { mutableStateOf(composeStateInProgress) },
showVoiceRecordIcon = false,
recState = mutableStateOf(RecordingState.NotStarted),
recState = remember { mutableStateOf(RecordingState.NotStarted) },
isDirectChat = true,
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
needToAllowVoiceToContact = false,
allowedVoiceByPrefs = true,
userIsObserver = false,
userCanSend = true,
allowVoiceToContact = {},
sendMessage = {},
onMessageChange = { _ -> },

View File

@@ -166,7 +166,7 @@ private fun RoleSelectionRow(groupInfo: GroupInfo, selectedRole: MutableState<Gr
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
val values = GroupMemberRole.values().filter { it <= groupInfo.membership.memberRole }.map { it to it.text }
val values = GroupMemberRole.values().filter { it <= groupInfo.membership.memberRole && it != GroupMemberRole.Observer }.map { it to it.text }
ExposedDropDownSettingRow(
generalGetString(R.string.new_member_role),
values,

View File

@@ -34,7 +34,7 @@ import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.usersettings.*
@Composable
fun GroupChatInfoView(chatModel: ChatModel, groupLink: String?, onGroupLinkUpdated: (String?) -> Unit, close: () -> Unit) {
fun GroupChatInfoView(chatModel: ChatModel, groupLink: String?, groupLinkMemberRole: GroupMemberRole?, onGroupLinkUpdated: (Pair<String?, GroupMemberRole?>) -> Unit, close: () -> Unit) {
BackHandler(onBack = close)
val chat = chatModel.chats.firstOrNull { it.id == chatModel.chatId.value }
val developerTools = chatModel.controller.appPrefs.developerTools.get()
@@ -95,9 +95,7 @@ fun GroupChatInfoView(chatModel: ChatModel, groupLink: String?, onGroupLinkUpdat
clearChat = { clearChatDialog(chat.chatInfo, chatModel, close) },
leaveGroup = { leaveGroupDialog(groupInfo, chatModel, close) },
manageGroupLink = {
withApi {
ModalManager.shared.showModal { GroupLinkView(chatModel, groupInfo, groupLink, onGroupLinkUpdated) }
}
ModalManager.shared.showModal { GroupLinkView(chatModel, groupInfo, groupLink, groupLinkMemberRole, onGroupLinkUpdated) }
}
)
}
@@ -300,6 +298,7 @@ private fun MemberRow(member: GroupMember, user: Boolean = false) {
verticalAlignment = Alignment.CenterVertically
) {
Row(
Modifier.weight(1f).padding(end = DEFAULT_PADDING),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
@@ -427,8 +426,7 @@ fun PreviewGroupChatInfoLayout() {
GroupChatInfoLayout(
chat = Chat(
chatInfo = ChatInfo.Direct.sampleData,
chatItems = arrayListOf(),
serverInfo = Chat.ServerInfo(Chat.NetworkStatus.Error("agent BROKER TIMEOUT"))
chatItems = arrayListOf()
),
groupInfo = GroupInfo.sampleData,
members = listOf(GroupMember.sampleData, GroupMember.sampleData, GroupMember.sampleData),

View File

@@ -1,6 +1,9 @@
package chat.simplex.app.views.chat.group
import SectionItemView
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
@@ -15,22 +18,26 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.R
import chat.simplex.app.model.ChatModel
import chat.simplex.app.model.GroupInfo
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.newchat.QRCode
@Composable
fun GroupLinkView(chatModel: ChatModel, groupInfo: GroupInfo, connReqContact: String?, onGroupLinkUpdated: (String?) -> Unit) {
fun GroupLinkView(chatModel: ChatModel, groupInfo: GroupInfo, connReqContact: String?, memberRole: GroupMemberRole?, onGroupLinkUpdated: (Pair<String?, GroupMemberRole?>) -> Unit) {
var groupLink by rememberSaveable { mutableStateOf(connReqContact) }
val groupLinkMemberRole = rememberSaveable { mutableStateOf(memberRole) }
var creatingLink by rememberSaveable { mutableStateOf(false) }
val cxt = LocalContext.current
fun createLink() {
creatingLink = true
withApi {
groupLink = chatModel.controller.apiCreateGroupLink(groupInfo.groupId)
onGroupLinkUpdated(groupLink)
val link = chatModel.controller.apiCreateGroupLink(groupInfo.groupId)
if (link != null) {
groupLink = link.first
groupLinkMemberRole.value = link.second
onGroupLinkUpdated(groupLink to groupLinkMemberRole.value)
}
creatingLink = false
}
}
@@ -41,9 +48,24 @@ fun GroupLinkView(chatModel: ChatModel, groupInfo: GroupInfo, connReqContact: St
}
GroupLinkLayout(
groupLink = groupLink,
groupInfo,
groupLinkMemberRole,
creatingLink,
createLink = ::createLink,
share = { shareText(cxt, groupLink ?: return@GroupLinkLayout) },
updateLink = {
val role = groupLinkMemberRole.value
if (role != null) {
withBGApi {
val link = chatModel.controller.apiGroupLinkMemberRole(groupInfo.groupId, role)
if (link != null) {
groupLink = link.first
groupLinkMemberRole.value = link.second
onGroupLinkUpdated(groupLink to groupLinkMemberRole.value)
}
}
}
},
deleteLink = {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.delete_link_question),
@@ -54,7 +76,7 @@ fun GroupLinkView(chatModel: ChatModel, groupInfo: GroupInfo, connReqContact: St
val r = chatModel.controller.apiDeleteGroupLink(groupInfo.groupId)
if (r) {
groupLink = null
onGroupLinkUpdated(null)
onGroupLinkUpdated(null to null)
}
}
}
@@ -69,13 +91,18 @@ fun GroupLinkView(chatModel: ChatModel, groupInfo: GroupInfo, connReqContact: St
@Composable
fun GroupLinkLayout(
groupLink: String?,
groupInfo: GroupInfo,
groupLinkMemberRole: MutableState<GroupMemberRole?>,
creatingLink: Boolean,
createLink: () -> Unit,
share: () -> Unit,
updateLink: () -> Unit,
deleteLink: () -> Unit
) {
Column(
Modifier.padding(horizontal = DEFAULT_PADDING),
Modifier
.verticalScroll(rememberScrollState())
.padding(start = DEFAULT_PADDING, bottom = DEFAULT_BOTTOM_PADDING, end = DEFAULT_PADDING),
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.Top
) {
@@ -93,7 +120,17 @@ fun GroupLinkLayout(
if (groupLink == null) {
SimpleButton(stringResource(R.string.button_create_group_link), icon = Icons.Outlined.AddLink, disabled = creatingLink, click = createLink)
} else {
QRCode(groupLink, Modifier.weight(1f, fill = false).aspectRatio(1f))
// SectionItemView(padding = PaddingValues(bottom = DEFAULT_PADDING)) {
// RoleSelectionRow(groupInfo, groupLinkMemberRole)
// }
var initialLaunch by remember { mutableStateOf(true) }
LaunchedEffect(groupLinkMemberRole.value) {
if (!initialLaunch) {
updateLink()
}
initialLaunch = false
}
QRCode(groupLink, Modifier.aspectRatio(1f))
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically,
@@ -116,6 +153,25 @@ fun GroupLinkLayout(
}
}
@Composable
private fun RoleSelectionRow(groupInfo: GroupInfo, selectedRole: MutableState<GroupMemberRole?>, enabled: Boolean = true) {
Row(
Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
val values = listOf(GroupMemberRole.Member, GroupMemberRole.Observer).map { it to it.text }
ExposedDropDownSettingRow(
generalGetString(R.string.initial_member_role),
values,
selectedRole,
icon = null,
enabled = rememberUpdatedState(enabled),
onSelected = { selectedRole.value = it }
)
}
}
@Composable
fun ProgressIndicator() {
Box(

View File

@@ -66,11 +66,9 @@ fun GroupMemberInfoView(
withApi {
val c = chatModel.controller.apiGetChat(ChatType.Direct, it)
if (c != null) {
// TODO it's not correct to blindly set network status to connected - we should manage network status in model / backend
val newChat = c.copy(serverInfo = Chat.ServerInfo(networkStatus = Chat.NetworkStatus.Connected()))
chatModel.addChat(newChat)
chatModel.addChat(c)
chatModel.chatItems.clear()
chatModel.chatId.value = newChat.id
chatModel.chatId.value = c.id
closeAll()
}
}

View File

@@ -136,7 +136,13 @@ fun GroupProfileLayout(
if (enabled) {
Text(
stringResource(R.string.save_group_profile),
modifier = Modifier.clickable { saveProfile(GroupProfile(displayName.value, fullName.value, profileImage.value)) },
modifier = Modifier.clickable {
saveProfile(groupProfile.copy(
displayName = displayName.value,
fullName = fullName.value,
image = profileImage.value
))
},
color = MaterialTheme.colors.primary
)
} else {

View File

@@ -1,26 +1,17 @@
package chat.simplex.app.views.chat.item
import android.content.ActivityNotFoundException
import android.util.Log
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.R
import chat.simplex.app.TAG
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.ui.theme.SimpleButton
import chat.simplex.app.views.helpers.generalGetString
@Composable
@@ -29,7 +20,7 @@ fun CIFeaturePreferenceView(
contact: Contact?,
feature: ChatFeature,
allowed: FeatureAllowed,
acceptFeature: (Contact, ChatFeature) -> Unit
acceptFeature: (Contact, ChatFeature, Int?) -> Unit
) {
Row(
Modifier.padding(horizontal = 6.dp, vertical = 6.dp),
@@ -39,17 +30,20 @@ fun CIFeaturePreferenceView(
Icon(feature.icon, feature.text, Modifier.size(18.dp), tint = HighOrLowlight)
if (contact != null && allowed != FeatureAllowed.NO && contact.allowsFeature(feature) && !contact.userAllowsFeature(feature)) {
val acceptStyle = SpanStyle(color = MaterialTheme.colors.primary, fontSize = 12.sp)
val setParam = feature == ChatFeature.TimedMessages && contact.mergedPreferences.timedMessages.userPreference.pref.ttl == null
val acceptTextId = if (setParam) R.string.accept_feature_set_1_day else R.string.accept_feature
val param = if (setParam) 86400 else null
val annotatedText = buildAnnotatedString {
withStyle(chatEventStyle) { append(chatItem.content.text + " ") }
withAnnotation(tag = "Accept", annotation = "Accept") {
withStyle(acceptStyle) { append(generalGetString(R.string.accept) + " ") }
withStyle(acceptStyle) { append(generalGetString(acceptTextId) + " ") }
}
withStyle(chatEventStyle) { append(chatItem.timestampText) }
}
fun accept(offset: Int): Boolean = annotatedText.getStringAnnotations(tag = "Accept", start = offset, end = offset).isNotEmpty()
ClickableText(
annotatedText,
onClick = { if (accept(it)) { acceptFeature(contact, feature) } },
onClick = { if (accept(it)) { acceptFeature(contact, feature, param) } },
shouldConsumeEvent = ::accept
)
} else {

View File

@@ -174,14 +174,14 @@ fun CIFileView(
class ChatItemProvider: PreviewParameterProvider<ChatItem> {
private val sentFile = ChatItem(
chatDir = CIDirection.DirectSnd(),
meta = CIMeta.getSample(1, Clock.System.now(), "", CIStatus.SndSent(), itemDeleted = false, itemEdited = true, editable = false),
meta = CIMeta.getSample(1, Clock.System.now(), "", CIStatus.SndSent(), itemEdited = true),
content = CIContent.SndMsgContent(msgContent = MsgContent.MCFile("")),
quotedItem = null,
file = CIFile.getSample(fileStatus = CIFileStatus.SndComplete)
)
private val fileChatItemWtFile = ChatItem(
chatDir = CIDirection.DirectRcv(),
meta = CIMeta.getSample(1, Clock.System.now(), "", CIStatus.RcvRead(), itemDeleted = false, itemEdited = false, editable = false),
meta = CIMeta.getSample(1, Clock.System.now(), "", CIStatus.RcvRead(), ),
content = CIContent.RcvMsgContent(msgContent = MsgContent.MCFile("")),
quotedItem = null,
file = null

View File

@@ -0,0 +1,46 @@
package chat.simplex.app.views.chat.item
import SectionSpacer
import SectionView
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Share
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.ui.theme.DEFAULT_PADDING
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.usersettings.SettingsActionItem
@Composable
fun CIInvalidJSONView(json: String) {
Row(Modifier
.clickable { ModalManager.shared.showModal(true) { InvalidJSONView(json) } }
.padding(horizontal = 10.dp, vertical = 6.dp)
) {
Text(stringResource(R.string.invalid_data), color = Color.Red, fontStyle = FontStyle.Italic)
}
}
@Composable
fun InvalidJSONView(json: String) {
Column {
Spacer(Modifier.height(DEFAULT_PADDING))
SectionView {
val context = LocalContext.current
SettingsActionItem(Icons.Outlined.Share, generalGetString(R.string.share_verb), click = {
shareText(context, json)
})
}
Column(Modifier.padding(DEFAULT_PADDING).fillMaxWidth().verticalScroll(rememberScrollState())) {
Text(json)
}
}
}

View File

@@ -68,7 +68,7 @@ fun reserveSpaceForMeta(meta: CIMeta, chatTTL: Int?): String {
if (meta.itemEdited) res += iconSpace
if (meta.itemTimed != null) {
res += iconSpace
val ttl = meta.itemTimed?.ttl
val ttl = meta.itemTimed.ttl
if (ttl != chatTTL) {
res += TimedMessagesPreference.shortTtlText(ttl)
}

View File

@@ -43,7 +43,7 @@ fun ChatItemView(
joinGroup: (Long) -> Unit,
acceptCall: (Contact) -> Unit,
scrollToItem: (Long) -> Unit,
acceptFeature: (Contact, ChatFeature) -> Unit
acceptFeature: (Contact, ChatFeature, Int?) -> Unit
) {
val context = LocalContext.current
val uriHandler = LocalUriHandler.current
@@ -54,6 +54,7 @@ fun ChatItemView(
val fullDeleteAllowed = remember(cInfo) { cInfo.featureEnabled(ChatFeature.FullDelete) }
val saveFileLauncher = rememberSaveFileLauncher(cxt = context, ciFile = cItem.file)
val onLinkLongClick = { _: String -> showMenu.value = true }
val live = composeState.value.liveMessage != null
Box(
modifier = Modifier
@@ -97,7 +98,7 @@ fun ChatItemView(
onDismissRequest = { showMenu.value = false },
Modifier.width(220.dp)
) {
if (!cItem.meta.itemDeleted) {
if (cItem.meta.itemDeleted == null && !live) {
ItemAction(stringResource(R.string.reply_verb), Icons.Outlined.Reply, onClick = {
if (composeState.value.editing) {
composeState.value = ComposeState(contextItem = ComposeContextItem.QuotedItem(cItem), useLinkPreviews = useLinkPreviews)
@@ -133,13 +134,13 @@ fun ChatItemView(
})
}
}
if (cItem.meta.editable && cItem.content.msgContent !is MsgContent.MCVoice) {
if (cItem.meta.editable && cItem.content.msgContent !is MsgContent.MCVoice && !live) {
ItemAction(stringResource(R.string.edit_verb), Icons.Filled.Edit, onClick = {
composeState.value = ComposeState(editingItem = cItem, useLinkPreviews = useLinkPreviews)
showMenu.value = false
})
}
if (cItem.meta.itemDeleted && revealed.value) {
if (cItem.meta.itemDeleted != null && revealed.value) {
ItemAction(
stringResource(R.string.hide_verb),
Icons.Outlined.VisibilityOff,
@@ -149,7 +150,9 @@ fun ChatItemView(
}
)
}
DeleteItemAction(cItem, showMenu, questionText = deleteMessageQuestionText(), deleteMessage)
if (!(live && cItem.meta.isLive)) {
DeleteItemAction(cItem, showMenu, questionText = deleteMessageQuestionText(), deleteMessage)
}
}
}
@@ -175,10 +178,10 @@ fun ChatItemView(
@Composable
fun ContentItem() {
val mc = cItem.content.msgContent
if (cItem.meta.itemDeleted && !revealed.value) {
if (cItem.meta.itemDeleted != null && !revealed.value) {
MarkedDeletedItemView(cItem, cInfo.timedMessagesTTL, showMember = showMember)
MarkedDeletedItemDropdownMenu()
} else if (cItem.quotedItem == null && !cItem.meta.itemDeleted && !cItem.meta.isLive) {
} else if (cItem.quotedItem == null && cItem.meta.itemDeleted == null && !cItem.meta.isLive) {
if (mc is MsgContent.MCText && isShortEmoji(cItem.content.text)) {
EmojiItemView(cItem, cInfo.timedMessagesTTL)
MsgContentItemDropdownMenu()
@@ -235,6 +238,9 @@ fun ChatItemView(
is CIContent.SndGroupFeature -> CIChatFeatureView(cItem, c.groupFeature, c.preference.enable.iconColor)
is CIContent.RcvChatFeatureRejected -> CIChatFeatureView(cItem, c.feature, Color.Red)
is CIContent.RcvGroupFeatureRejected -> CIChatFeatureView(cItem, c.groupFeature, Color.Red)
is CIContent.SndModerated -> DeletedItem()
is CIContent.RcvModerated -> DeletedItem()
is CIContent.InvalidJSON -> CIInvalidJSONView(c.json)
}
}
}
@@ -326,7 +332,7 @@ fun PreviewChatItemView() {
joinGroup = {},
acceptCall = { _ -> },
scrollToItem = {},
acceptFeature = { _, _ -> }
acceptFeature = { _, _, _ -> }
)
}
}
@@ -346,7 +352,7 @@ fun PreviewChatItemViewDeletedContent() {
joinGroup = {},
acceptCall = { _ -> },
scrollToItem = {},
acceptFeature = { _, _ -> }
acceptFeature = { _, _, _ -> }
)
}
}

View File

@@ -1,8 +1,7 @@
package chat.simplex.app.views.chat.item
import android.content.res.Configuration
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.Composable
@@ -19,9 +18,10 @@ import chat.simplex.app.ui.theme.SimpleXTheme
@Composable
fun DeletedItemView(ci: ChatItem, timedMessagesTTL: Int?, showMember: Boolean = false) {
val sent = ci.chatDir.sent
Surface(
shape = RoundedCornerShape(18.dp),
color = ReceivedColorLight,
color = if (sent) SentColorLight else ReceivedColorLight,
) {
Row(
Modifier.padding(horizontal = 12.dp, vertical = 6.dp),

View File

@@ -28,6 +28,7 @@ import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.*
import kotlinx.datetime.Clock
import kotlin.math.min
val SentColorLight = Color(0x1E45B8FF)
val ReceivedColorLight = Color(0x20B1B0B5)
@@ -164,7 +165,7 @@ fun FramedItemView(
Box(contentAlignment = Alignment.BottomEnd) {
Column(Modifier.width(IntrinsicSize.Max)) {
PriorityLayout(Modifier, CHAT_IMAGE_LAYOUT_ID) {
if (ci.meta.itemDeleted) {
if (ci.meta.itemDeleted != null) {
FramedItemHeader(stringResource(R.string.marked_deleted_description), true, Icons.Outlined.Delete)
} else if (ci.meta.isLive) {
FramedItemHeader(stringResource(R.string.live), false)
@@ -241,6 +242,12 @@ fun CIMarkdownText(
}
const val CHAT_IMAGE_LAYOUT_ID = "chatImage"
/**
* Equal to [androidx.compose.ui.unit.Constraints.MaxFocusMask], which is 0x3FFFF - 1
* Other values make a crash `java.lang.IllegalArgumentException: Can't represent a width of 123456 and height of 9909 in Constraints`
* See [androidx.compose.ui.unit.Constraints.createConstraints]
* */
const val MAX_SAFE_WIDTH = 0x3FFFF - 1
@Composable
fun PriorityLayout(
@@ -248,6 +255,17 @@ fun PriorityLayout(
priorityLayoutId: String,
content: @Composable () -> Unit
) {
/**
* Limiting max value for height + width in order to not crash the app, see [androidx.compose.ui.unit.Constraints.createConstraints]
* */
fun maxSafeHeight(width: Int) = when { // width bits + height bits should be <= 31
width < 0x1FFF /*MaxNonFocusMask*/ -> 0x3FFFF - 1 /* MaxFocusMask */ // 13 bits width + 18 bits height
width < 0x7FFF /*MinNonFocusMask*/ -> 0xFFFF - 1 /* MinFocusMask */ // 15 bits width + 16 bits height
width < 0xFFFF /*MinFocusMask*/ -> 0x7FFF - 1 /* MinFocusMask */ // 16 bits width + 15 bits height
width < 0x3FFFF /*MaxFocusMask*/ -> 0x1FFF - 1 /* MaxNonFocusMask */ // 18 bits width + 13 bits height
else -> 0x1FFF // shouldn't happen since width is limited already
}
Layout(
content = content,
modifier = modifier
@@ -259,9 +277,11 @@ fun PriorityLayout(
if (it.layoutId == priorityLayoutId)
imagePlaceable!!
else
it.measure(constraints.copy(maxWidth = imagePlaceable?.width ?: constraints.maxWidth)) }
// Limit width for every other element to width of important element and height for a sum of all elements
layout(imagePlaceable?.measuredWidth ?: placeables.maxOf { it.width }, placeables.sumOf { it.height }) {
it.measure(constraints.copy(maxWidth = imagePlaceable?.width ?: min(MAX_SAFE_WIDTH, constraints.maxWidth))) }
// Limit width for every other element to width of important element and height for a sum of all elements.
val width = imagePlaceable?.measuredWidth ?: min(MAX_SAFE_WIDTH, placeables.maxOf { it.width })
val height = minOf(maxSafeHeight(width), placeables.sumOf { it.height })
layout(width, height) {
var y = 0
placeables.forEach {
it.place(0, y)

View File

@@ -14,6 +14,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.R
import chat.simplex.app.model.CIDeleted
import chat.simplex.app.model.ChatItem
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.ui.theme.SimpleXTheme
@@ -51,7 +52,7 @@ fun MarkedDeletedItemView(ci: ChatItem, timedMessagesTTL: Int?, showMember: Bool
fun PreviewMarkedDeletedItemView() {
SimpleXTheme {
DeletedItemView(
ChatItem.getSampleData(itemDeleted = true),
ChatItem.getSampleData(itemDeleted = CIDeleted.Deleted()),
null
)
}

View File

@@ -66,7 +66,7 @@ private fun typing(w: FontWeight = FontWeight.Light): AnnotatedString =
@Composable
fun MarkdownText (
text: String,
text: CharSequence,
formattedText: List<FormattedText>? = null,
sender: String? = null,
meta: CIMeta? = null,
@@ -78,6 +78,7 @@ fun MarkdownText (
senderBold: Boolean = false,
modifier: Modifier = Modifier,
linkMode: SimplexLinkMode,
inlineContent: Map<String, InlineTextContent>? = null,
onLinkLongClick: (link: String) -> Unit = {}
) {
val textLayoutDirection = remember (text) {
@@ -132,13 +133,14 @@ fun MarkdownText (
if (formattedText == null) {
val annotatedText = buildAnnotatedString {
appendSender(this, sender, senderBold)
append(text)
if (text is String) append(text)
else if (text is AnnotatedString) append(text)
if (meta?.isLive == true) {
append(typingIndicator(meta.recent, typingIdx))
}
if (meta != null) withStyle(reserveTimestampStyle) { append(reserve) }
}
Text(annotatedText, style = style, modifier = modifier, maxLines = maxLines, overflow = overflow)
Text(annotatedText, style = style, modifier = modifier, maxLines = maxLines, overflow = overflow, inlineContent = inlineContent ?: mapOf())
} else {
var hasLinks = false
val annotatedText = buildAnnotatedString {

View File

@@ -19,6 +19,7 @@ import androidx.compose.ui.unit.sp
import chat.simplex.app.R
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.helpers.annotatedStringResource
import chat.simplex.app.views.usersettings.MarkdownHelpView
import chat.simplex.app.views.usersettings.simplexTeamUri
val bold = SpanStyle(fontWeight = FontWeight.Bold)
@@ -76,6 +77,15 @@ fun ChatHelpView(addContact: (() -> Unit)? = null) {
Text(annotatedStringResource(R.string.desktop_scan_QR_code_from_app_via_scan_QR_code), lineHeight = 22.sp)
Text(annotatedStringResource(R.string.mobile_tap_open_in_mobile_app_then_tap_connect_in_app), lineHeight = 22.sp)
}
Column(
Modifier.padding(vertical = 24.dp),
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
Text(stringResource(R.string.markdown_in_messages), style = MaterialTheme.typography.h2)
MarkdownHelpView()
}
}
}

View File

@@ -11,15 +11,20 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.R
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.chat.*
import chat.simplex.app.views.chat.group.deleteGroupDialog
import chat.simplex.app.views.chat.group.leaveGroupDialog
import chat.simplex.app.views.chat.item.InvalidJSONView
import chat.simplex.app.views.chat.item.ItemAction
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.newchat.ContactConnectionInfoView
@@ -39,17 +44,19 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) {
delay(500L)
}
when (chat.chatInfo) {
is ChatInfo.Direct ->
is ChatInfo.Direct -> {
val contactNetworkStatus = chatModel.contactNetworkStatus(chat.chatInfo.contact)
ChatListNavLinkLayout(
chatLinkPreview = { ChatPreviewView(chat, chatModel.incognito.value, chatModel.currentUser.value?.profile?.displayName, stopped, linkMode) },
chatLinkPreview = { ChatPreviewView(chat, chatModel.draft.value, chatModel.draftChatId.value, chatModel.incognito.value, chatModel.currentUser.value?.profile?.displayName, contactNetworkStatus, stopped, linkMode) },
click = { directChatAction(chat.chatInfo, chatModel) },
dropdownMenuItems = { ContactMenuItems(chat, chatModel, showMenu, showMarkRead) },
showMenu,
stopped
)
}
is ChatInfo.Group ->
ChatListNavLinkLayout(
chatLinkPreview = { ChatPreviewView(chat, chatModel.incognito.value, chatModel.currentUser.value?.profile?.displayName, stopped, linkMode) },
chatLinkPreview = { ChatPreviewView(chat, chatModel.draft.value, chatModel.draftChatId.value, chatModel.incognito.value, chatModel.currentUser.value?.profile?.displayName, null, stopped, linkMode) },
click = { groupChatAction(chat.chatInfo.groupInfo, chatModel) },
dropdownMenuItems = { GroupMenuItems(chat, chat.chatInfo.groupInfo, chatModel, showMenu, showMarkRead) },
showMenu,
@@ -75,6 +82,18 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) {
showMenu,
stopped
)
is ChatInfo.InvalidJSON ->
ChatListNavLinkLayout(
chatLinkPreview = {
InvalidDataView()
},
click = {
ModalManager.shared.showModal(true) { InvalidJSONView(chat.chatInfo.json) }
},
dropdownMenuItems = null,
showMenu,
stopped
)
}
}
@@ -282,7 +301,7 @@ fun ContactRequestMenuItems(chatInfo: ChatInfo.ContactRequest, chatModel: ChatMo
if (chatModel.incognito.value) Icons.Filled.TheaterComedy else Icons.Outlined.Check,
color = if (chatModel.incognito.value) Indigo else MaterialTheme.colors.onBackground,
onClick = {
acceptContactRequest(chatInfo, chatModel)
acceptContactRequest(chatInfo.apiId, chatInfo, true, chatModel)
showMenu.value = false
}
)
@@ -320,6 +339,29 @@ fun ContactConnectionMenuItems(chatInfo: ChatInfo.ContactConnection, chatModel:
)
}
@Composable
private fun InvalidDataView() {
Row {
ProfileImage(72.dp, null, Icons.Filled.AccountCircle, HighOrLowlight)
Column(
modifier = Modifier
.padding(horizontal = 8.dp)
.weight(1F)
) {
Text(
stringResource(R.string.invalid_data),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.h3,
fontWeight = FontWeight.Bold,
color = Color.Red
)
val height = with(LocalDensity.current) { 46.sp.toDp() }
Spacer(Modifier.height(height))
}
}
}
fun markChatRead(c: Chat, chatModel: ChatModel) {
var chat = c
withApi {
@@ -367,16 +409,16 @@ fun contactRequestAlertDialog(contactRequest: ChatInfo.ContactRequest, chatModel
title = generalGetString(R.string.accept_connection_request__question),
text = generalGetString(R.string.if_you_choose_to_reject_the_sender_will_not_be_notified),
confirmText = if (chatModel.incognito.value) generalGetString(R.string.accept_contact_incognito_button) else generalGetString(R.string.accept_contact_button),
onConfirm = { acceptContactRequest(contactRequest, chatModel) },
onConfirm = { acceptContactRequest(contactRequest.apiId, contactRequest, true, chatModel) },
dismissText = generalGetString(R.string.reject_contact_button),
onDismiss = { rejectContactRequest(contactRequest, chatModel) }
)
}
fun acceptContactRequest(contactRequest: ChatInfo.ContactRequest, chatModel: ChatModel) {
fun acceptContactRequest(apiId: Long, contactRequest: ChatInfo.ContactRequest?, isCurrentUser: Boolean, chatModel: ChatModel) {
withApi {
val contact = chatModel.controller.apiAcceptContactRequest(contactRequest.apiId)
if (contact != null) {
val contact = chatModel.controller.apiAcceptContactRequest(apiId)
if (contact != null && isCurrentUser && contactRequest != null) {
val chat = Chat(ChatInfo.Direct(contact), listOf())
chatModel.replaceChat(contactRequest.id, chat)
}
@@ -585,8 +627,11 @@ fun PreviewChatListNavLinkDirect() {
),
chatStats = Chat.ChatStats()
),
null,
null,
false,
null,
null,
stopped = false,
linkMode = SimplexLinkMode.DESCRIPTION
)
@@ -623,8 +668,11 @@ fun PreviewChatListNavLinkGroup() {
),
chatStats = Chat.ChatStats()
),
null,
null,
false,
null,
null,
stopped = false,
linkMode = SimplexLinkMode.DESCRIPTION
)

View File

@@ -4,6 +4,7 @@ import androidx.activity.compose.BackHandler
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
@@ -13,41 +14,60 @@ import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.*
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.capitalize
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.*
import chat.simplex.app.*
import chat.simplex.app.R
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.newchat.NewChatSheet
import chat.simplex.app.views.onboarding.WhatsNewView
import chat.simplex.app.views.onboarding.shouldShowWhatsNew
import chat.simplex.app.views.usersettings.SettingsView
import chat.simplex.app.views.usersettings.simplexTeamUri
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
@Composable
fun ChatListView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, stopped: Boolean) {
val newChatSheetState by rememberSaveable(stateSaver = NewChatSheetState.saver()) { mutableStateOf(MutableStateFlow(NewChatSheetState.GONE)) }
val newChatSheetState by rememberSaveable(stateSaver = AnimatedViewState.saver()) { mutableStateOf(MutableStateFlow(AnimatedViewState.GONE)) }
val userPickerState by rememberSaveable(stateSaver = AnimatedViewState.saver()) { mutableStateOf(MutableStateFlow(AnimatedViewState.GONE)) }
val showNewChatSheet = {
newChatSheetState.value = NewChatSheetState.VISIBLE
newChatSheetState.value = AnimatedViewState.VISIBLE
}
val hideNewChatSheet: (animated: Boolean) -> Unit = { animated ->
if (animated) newChatSheetState.value = NewChatSheetState.HIDING
else newChatSheetState.value = NewChatSheetState.GONE
if (animated) newChatSheetState.value = AnimatedViewState.HIDING
else newChatSheetState.value = AnimatedViewState.GONE
}
LaunchedEffect(Unit) {
if (shouldShowWhatsNew(chatModel)) {
delay(1000L)
ModalManager.shared.showCustomModal { close -> WhatsNewView(close = close) }
}
}
LaunchedEffect(chatModel.clearOverlays.value) {
if (chatModel.clearOverlays.value && newChatSheetState.value.isVisible()) hideNewChatSheet(false)
}
LaunchedEffect(chatModel.appOpenUrl.value) {
val url = chatModel.appOpenUrl.value
if (url != null) {
chatModel.appOpenUrl.value = null
connectIfOpenedViaUri(url, chatModel)
}
}
var searchInList by rememberSaveable { mutableStateOf("") }
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
val switchingUsers = rememberSaveable { mutableStateOf(false) }
Scaffold(
topBar = { ChatListToolbar(chatModel, scaffoldState.drawerState, stopped) { searchInList = it.trim() } },
topBar = { ChatListToolbar(chatModel, scaffoldState.drawerState, userPickerState, stopped) { searchInList = it.trim() } },
scaffoldState = scaffoldState,
drawerContent = { SettingsView(chatModel, setPerformLA) },
floatingActionButton = {
@@ -80,7 +100,7 @@ fun ChatListView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, stopped:
) {
if (chatModel.chats.isNotEmpty()) {
ChatList(chatModel, search = searchInList)
} else {
} else if (!switchingUsers.value) {
Box(Modifier.fillMaxSize()) {
if (!stopped && !newChatSheetState.collectAsState().value.isVisible()) {
OnboardingButtons(showNewChatSheet)
@@ -94,6 +114,17 @@ fun ChatListView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, stopped:
if (searchInList.isEmpty()) {
NewChatSheet(chatModel, newChatSheetState, stopped, hideNewChatSheet)
}
UserPicker(chatModel, userPickerState, switchingUsers) {
scope.launch { if (scaffoldState.drawerState.isOpen) scaffoldState.drawerState.close() else scaffoldState.drawerState.open() }
}
if (switchingUsers.value) {
Box(
Modifier.fillMaxSize().clickable(enabled = false, onClick = {}),
contentAlignment = Alignment.Center
) {
ProgressIndicator()
}
}
}
@Composable
@@ -139,7 +170,7 @@ private fun ConnectButton(text: String, onClick: () -> Unit) {
}
@Composable
private fun ChatListToolbar(chatModel: ChatModel, drawerState: DrawerState, stopped: Boolean, onSearchValueChanged: (String) -> Unit) {
private fun ChatListToolbar(chatModel: ChatModel, drawerState: DrawerState, userPickerState: MutableStateFlow<AnimatedViewState>, stopped: Boolean, onSearchValueChanged: (String) -> Unit) {
var showSearch by rememberSaveable { mutableStateOf(false) }
val hideSearchOnBack = { onSearchValueChanged(""); showSearch = false }
if (showSearch) {
@@ -172,10 +203,23 @@ private fun ChatListToolbar(chatModel: ChatModel, drawerState: DrawerState, stop
val scope = rememberCoroutineScope()
DefaultTopAppBar(
navigationButton = {
if (showSearch)
if (showSearch) {
NavigationButtonBack(hideSearchOnBack)
else
} else if (chatModel.users.isEmpty()) {
NavigationButtonMenu { scope.launch { if (drawerState.isOpen) drawerState.close() else drawerState.open() } }
} else {
val users by remember { derivedStateOf { chatModel.users.toList() } }
val allRead = users
.filter { !it.user.activeUser }
.all { u -> u.unreadCount == 0 }
UserProfileButton(chatModel.currentUser.value?.profile?.image, allRead) {
if (users.size == 1) {
scope.launch { drawerState.open() }
} else {
userPickerState.value = AnimatedViewState.VISIBLE
}
}
}
},
title = {
Row(verticalAlignment = Alignment.CenterVertically) {
@@ -202,6 +246,47 @@ private fun ChatListToolbar(chatModel: ChatModel, drawerState: DrawerState, stop
Divider(Modifier.padding(top = AppBarHeight))
}
@Composable
private fun UserProfileButton(image: String?, allRead: Boolean, onButtonClicked: () -> Unit) {
IconButton(onClick = onButtonClicked) {
Box {
ProfileImage(
image = image,
size = 37.dp
)
if (!allRead) {
unreadBadge()
}
}
}
}
@Composable
private fun BoxScope.unreadBadge(text: String? = "") {
Text(
text ?: "",
color = MaterialTheme.colors.onPrimary,
fontSize = 6.sp,
modifier = Modifier
.background(MaterialTheme.colors.primary, shape = CircleShape)
.badgeLayout()
.padding(horizontal = 3.dp)
.padding(vertical = 1.dp)
.align(Alignment.TopEnd)
)
}
@Composable
private fun ProgressIndicator() {
CircularProgressIndicator(
Modifier
.padding(horizontal = 2.dp)
.size(30.dp),
color = HighOrLowlight,
strokeWidth = 2.5.dp
)
}
private var lazyListState = 0 to 0
@Composable

View File

@@ -4,16 +4,20 @@ import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.foundation.text.appendInlineContent
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
@@ -21,11 +25,22 @@ import androidx.compose.ui.unit.*
import chat.simplex.app.R
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.chat.ComposePreview
import chat.simplex.app.views.chat.ComposeState
import chat.simplex.app.views.chat.item.MarkdownText
import chat.simplex.app.views.helpers.*
@Composable
fun ChatPreviewView(chat: Chat, chatModelIncognito: Boolean, currentUserProfileDisplayName: String?, stopped: Boolean, linkMode: SimplexLinkMode) {
fun ChatPreviewView(
chat: Chat,
chatModelDraft: ComposeState?,
chatModelDraftChatId: ChatId?,
chatModelIncognito: Boolean,
currentUserProfileDisplayName: String?,
contactNetworkStatus: NetworkStatus?,
stopped: Boolean,
linkMode: SimplexLinkMode
) {
val cInfo = chat.chatInfo
@Composable
@@ -67,6 +82,43 @@ fun ChatPreviewView(chat: Chat, chatModelIncognito: Boolean, currentUserProfileD
Icon(Icons.Outlined.VerifiedUser, null, Modifier.size(19.dp).padding(end = 3.dp, top = 1.dp), tint = HighOrLowlight)
}
fun messageDraft(draft: ComposeState): Pair<AnnotatedString, Map<String, InlineTextContent>> {
fun attachment(): Pair<ImageVector, String?>? =
when (draft.preview) {
is ComposePreview.FilePreview -> Icons.Filled.InsertDriveFile to draft.preview.fileName
is ComposePreview.ImagePreview -> Icons.Outlined.Image to null
is ComposePreview.VoicePreview -> Icons.Filled.PlayArrow to durationText(draft.preview.durationMs / 1000)
else -> null
}
val attachment = attachment()
val text = buildAnnotatedString {
appendInlineContent(id = "editIcon")
append(" ")
if (attachment != null) {
appendInlineContent(id = "attachmentIcon")
if (attachment.second != null) {
append(attachment.second as String)
}
append(" ")
}
append(draft.message)
}
val inlineContent: Map<String, InlineTextContent> = mapOf(
"editIcon" to InlineTextContent(
Placeholder(20.sp, 20.sp, PlaceholderVerticalAlign.TextCenter)
) {
Icon(Icons.Outlined.EditNote, null, tint = MaterialTheme.colors.primary)
},
"attachmentIcon" to InlineTextContent(
Placeholder(20.sp, 20.sp, PlaceholderVerticalAlign.TextCenter)
) {
Icon(attachment?.first ?: Icons.Outlined.EditNote, null, tint = HighOrLowlight)
}
)
return text to inlineContent
}
@Composable
fun chatPreviewTitle() {
when (cInfo) {
@@ -91,15 +143,30 @@ fun ChatPreviewView(chat: Chat, chatModelIncognito: Boolean, currentUserProfileD
fun chatPreviewText(chatModelIncognito: Boolean) {
val ci = chat.chatItems.lastOrNull()
if (ci != null) {
val (text: CharSequence, inlineTextContent) = when {
chatModelDraftChatId == chat.id && chatModelDraft != null -> remember(chatModelDraft) { messageDraft(chatModelDraft) }
ci.meta.itemDeleted == null -> ci.text to null
else -> generalGetString(R.string.marked_deleted_description) to null
}
val formattedText = when {
chatModelDraftChatId == chat.id && chatModelDraft != null -> null
ci.meta.itemDeleted == null -> ci.formattedText
else -> null
}
MarkdownText(
if (!ci.meta.itemDeleted) ci.text else generalGetString(R.string.marked_deleted_description),
if (!ci.meta.itemDeleted) ci.formattedText else null,
sender = if (cInfo is ChatInfo.Group && !ci.chatDir.sent) ci.memberDisplayName else null,
text,
formattedText,
sender = when {
chatModelDraftChatId == chat.id && chatModelDraft != null -> null
cInfo is ChatInfo.Group && !ci.chatDir.sent -> ci.memberDisplayName
else -> null
},
linkMode = linkMode,
senderBold = true,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.body1.copy(color = if (isInDarkTheme()) MessagePreviewDark else MessagePreviewLight, lineHeight = 22.sp),
inlineContent = inlineTextContent,
modifier = Modifier.fillMaxWidth(),
)
} else {
@@ -187,7 +254,7 @@ fun ChatPreviewView(chat: Chat, chatModelIncognito: Boolean, currentUserProfileD
Modifier.padding(top = 52.dp),
contentAlignment = Alignment.Center
) {
ChatStatusImage(chat)
ChatStatusImage(contactNetworkStatus)
}
}
}
@@ -210,10 +277,9 @@ fun unreadCountStr(n: Int): String {
}
@Composable
fun ChatStatusImage(chat: Chat) {
val s = chat.serverInfo.networkStatus
val descr = s.statusString
if (s is Chat.NetworkStatus.Error) {
fun ChatStatusImage(s: NetworkStatus?) {
val descr = s?.statusString
if (s is NetworkStatus.Error) {
Icon(
Icons.Outlined.ErrorOutline,
contentDescription = descr,
@@ -221,7 +287,7 @@ fun ChatStatusImage(chat: Chat) {
modifier = Modifier
.size(19.dp)
)
} else if (s !is Chat.NetworkStatus.Connected) {
} else if (s !is NetworkStatus.Connected) {
CircularProgressIndicator(
Modifier
.padding(horizontal = 2.dp)
@@ -241,6 +307,6 @@ fun ChatStatusImage(chat: Chat) {
@Composable
fun PreviewChatPreviewView() {
SimpleXTheme {
ChatPreviewView(Chat.sampleData, false, "", stopped = false, linkMode = SimplexLinkMode.DESCRIPTION)
ChatPreviewView(Chat.sampleData, null, null, false, "", contactNetworkStatus = NetworkStatus.Connected(), stopped = false, linkMode = SimplexLinkMode.DESCRIPTION)
}
}

View File

@@ -29,7 +29,7 @@ fun ShareListNavLinkView(chat: Chat, chatModel: ChatModel) {
click = { groupChatAction(chat.chatInfo.groupInfo, chatModel) },
stopped
)
is ChatInfo.ContactRequest, is ChatInfo.ContactConnection -> {}
is ChatInfo.ContactRequest, is ChatInfo.ContactConnection, is ChatInfo.InvalidJSON -> {}
}
}

View File

@@ -0,0 +1,198 @@
package chat.simplex.app.views.chatlist
import SectionItemViewSpaceBetween
import android.util.Log
import androidx.compose.animation.core.*
import androidx.compose.foundation.*
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Done
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.runtime.*
import androidx.compose.ui.*
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.capitalize
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.*
import chat.simplex.app.R
import chat.simplex.app.TAG
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
@Composable
fun UserPicker(chatModel: ChatModel, userPickerState: MutableStateFlow<AnimatedViewState>, switchingUsers: MutableState<Boolean>, openSettings: () -> Unit) {
val scope = rememberCoroutineScope()
var newChat by remember { mutableStateOf(userPickerState.value) }
val users by remember { derivedStateOf { chatModel.users.sortedByDescending { it.user.activeUser } } }
val animatedFloat = remember { Animatable(if (newChat.isVisible()) 0f else 1f) }
LaunchedEffect(Unit) {
launch {
userPickerState.collect {
newChat = it
launch {
animatedFloat.animateTo(if (newChat.isVisible()) 1f else 0f, newChatSheetAnimSpec())
if (newChat.isHiding()) userPickerState.value = AnimatedViewState.GONE
}
}
}
}
LaunchedEffect(Unit) {
snapshotFlow { newChat.isVisible() }
.distinctUntilChanged()
.filter { it }
.collect {
try {
val updatedUsers = chatModel.controller.listUsers().sortedByDescending { it.user.activeUser }
var same = users.size == updatedUsers.size
if (same) {
for (i in 0 until minOf(users.size, updatedUsers.size)) {
val prev = updatedUsers[i].user
val next = users[i].user
if (prev.userId != next.userId || prev.activeUser != next.activeUser || prev.chatViewName != next.chatViewName || prev.image != next.image) {
same = false
break
}
}
}
if (!same) {
chatModel.users.clear()
chatModel.users.addAll(updatedUsers)
}
} catch (e: Exception) {
Log.e(TAG, "Error updating users ${e.stackTraceToString()}")
}
}
}
val xOffset = with(LocalDensity.current) { 10.dp.roundToPx() }
val maxWidth = with(LocalDensity.current) { LocalConfiguration.current.screenWidthDp * density }
Box(Modifier
.fillMaxSize()
.offset { IntOffset(if (newChat.isGone()) -maxWidth.roundToInt() else xOffset, 0) }
.clickable(interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = { userPickerState.value = AnimatedViewState.HIDING })
.padding(bottom = 10.dp, top = 10.dp)
.graphicsLayer {
alpha = animatedFloat.value
translationY = (animatedFloat.value - 1) * xOffset
}
) {
Column(
Modifier
.widthIn(min = 220.dp)
.width(IntrinsicSize.Min)
.height(IntrinsicSize.Min)
.shadow(8.dp, MaterialTheme.shapes.medium, clip = false)
.background(MaterialTheme.colors.background, MaterialTheme.shapes.medium)
) {
Column(Modifier.weight(1f).verticalScroll(rememberScrollState())) {
users.forEach { u ->
UserProfilePickerItem(u.user, u.unreadCount, openSettings = {
openSettings()
userPickerState.value = AnimatedViewState.GONE
}) {
userPickerState.value = AnimatedViewState.HIDING
if (!u.user.activeUser) {
chatModel.chats.clear()
scope.launch {
val job = launch {
delay(500)
switchingUsers.value = true
}
chatModel.controller.changeActiveUser(u.user.userId)
job.cancel()
switchingUsers.value = false
}
}
}
Divider(Modifier.requiredHeight(1.dp))
if (u.user.activeUser) Divider(Modifier.requiredHeight(0.5.dp))
}
}
SettingsPickerItem {
openSettings()
userPickerState.value = AnimatedViewState.GONE
}
}
}
}
@Composable
fun UserProfilePickerItem(u: User, unreadCount: Int = 0, onLongClick: () -> Unit = {}, openSettings: () -> Unit = {}, onClick: () -> Unit) {
Row(
Modifier
.fillMaxWidth()
.sizeIn(minHeight = 46.dp)
.combinedClickable(
onClick = if (u.activeUser) openSettings else onClick,
onLongClick = onLongClick,
interactionSource = remember { MutableInteractionSource() },
indication = if (!u.activeUser) LocalIndication.current else null
)
.padding(PaddingValues(start = 8.dp, end = DEFAULT_PADDING)),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(
Modifier
.widthIn(max = LocalConfiguration.current.screenWidthDp.dp * 0.7f)
.padding(vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
ProfileImage(
image = u.image,
size = 54.dp
)
Text(
u.displayName,
modifier = Modifier
.padding(start = 8.dp, end = 8.dp),
fontWeight = if (u.activeUser) FontWeight.Medium else FontWeight.Normal
)
}
if (u.activeUser) {
Icon(Icons.Filled.Done, null, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
} else if (unreadCount > 0) {
Row {
Text(
unreadCountStr(unreadCount),
color = MaterialTheme.colors.onPrimary,
fontSize = 11.sp,
modifier = Modifier
.background(MaterialTheme.colors.primary, shape = CircleShape)
.sizeIn(minWidth = 20.dp, minHeight = 20.dp)
.padding(horizontal = 3.dp)
.padding(vertical = 1.dp),
textAlign = TextAlign.Center,
maxLines = 1
)
Spacer(Modifier.width(2.dp))
}
} else {
Box(Modifier.size(20.dp))
}
}
}
@Composable
private fun SettingsPickerItem(onClick: () -> Unit) {
SectionItemViewSpaceBetween(onClick, minHeight = 68.dp) {
val text = generalGetString(R.string.settings_section_title_settings).lowercase().capitalize(Locale.current)
Text(
text,
color = MaterialTheme.colors.onBackground,
)
Icon(Icons.Outlined.Settings, text, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
}
}

View File

@@ -27,6 +27,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.fragment.app.FragmentActivity
@@ -87,6 +89,8 @@ fun DatabaseView(
m.controller.appPrefs.privacyFullBackup,
appFilesCountAndSize,
chatItemTTL,
m.currentUser.value,
m.users,
startChat = { startChat(m, runChat, chatLastStart, m.chatDbChanged) },
stopChatAlert = { stopChatAlert(m, runChat, context) },
exportArchive = { exportArchive(context, m, progressIndicator, chatArchiveName, chatArchiveTime, chatArchiveFile, saveArchiveLauncher) },
@@ -136,6 +140,8 @@ fun DatabaseLayout(
privacyFullBackup: SharedPreference<Boolean>,
appFilesCountAndSize: MutableState<Pair<Int, Long>>,
chatItemTTL: MutableState<ChatItemTTL>,
currentUser: User?,
users: List<UserInfo>,
startChat: () -> Unit,
stopChatAlert: () -> Unit,
exportArchive: () -> Unit,
@@ -148,10 +154,27 @@ fun DatabaseLayout(
val operationsDisabled = !stopped || progressIndicator
Column(
Modifier.fillMaxWidth().verticalScroll(rememberScrollState()),
Modifier.fillMaxWidth().verticalScroll(rememberScrollState()).padding(bottom = DEFAULT_BOTTOM_PADDING),
horizontalAlignment = Alignment.Start,
) {
AppBarTitle(stringResource(R.string.your_chat_database))
SectionView(stringResource(R.string.messages_section_title).uppercase()) {
SectionItemView { TtlOptions(chatItemTTL, enabled = rememberUpdatedState(!progressIndicator && !chatDbChanged), onChatItemTTLSelected) }
}
SectionTextFooter(
remember(currentUser?.displayName) {
buildAnnotatedString {
append(generalGetString(R.string.messages_section_description) + " ")
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
append(currentUser?.displayName ?: "")
}
append(".")
}
}
)
SectionSpacer()
SectionView(stringResource(R.string.run_chat_section)) {
RunChatSetting(runChat, stopped, chatDbDeleted, startChat, stopChatAlert)
}
@@ -167,7 +190,7 @@ fun DatabaseLayout(
disabled = operationsDisabled
)
SectionDivider()
SettingsPreferenceItem(Icons.Outlined.Backup, stringResource(R.string.full_backup), privacyFullBackup)
AppDataBackupPreference(privacyFullBackup, initialRandomDBPassphrase)
SectionDivider()
SettingsActionItem(
Icons.Outlined.IosShare,
@@ -224,16 +247,14 @@ fun DatabaseLayout(
)
SectionSpacer()
SectionView(stringResource(R.string.data_section)) {
SectionItemView { TtlOptions(chatItemTTL, enabled = rememberUpdatedState(!progressIndicator && !chatDbChanged), onChatItemTTLSelected) }
SectionDivider()
SectionView(stringResource(R.string.files_and_media_section).uppercase()) {
val deleteFilesDisabled = operationsDisabled || appFilesCountAndSize.value.first == 0
SectionItemView(
deleteAppFilesAndMedia,
disabled = deleteFilesDisabled
) {
Text(
stringResource(R.string.delete_files_and_media),
stringResource(if (users.size > 1) R.string.delete_files_and_media_for_all_users else R.string.delete_files_and_media_all),
color = if (deleteFilesDisabled) HighOrLowlight else Color.Red
)
}
@@ -249,6 +270,36 @@ fun DatabaseLayout(
}
}
@Composable
private fun AppDataBackupPreference(privacyFullBackup: SharedPreference<Boolean>, initialRandomDBPassphrase: SharedPreference<Boolean>) {
SectionItemView {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Outlined.Backup, stringResource(R.string.full_backup), tint = HighOrLowlight)
Spacer(Modifier.padding(horizontal = 4.dp))
val prefState = remember { mutableStateOf(privacyFullBackup.get()) }
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Text(stringResource(R.string.full_backup), Modifier.padding(end = 24.dp))
Spacer(Modifier.fillMaxWidth().weight(1f))
Switch(
checked = prefState.value,
onCheckedChange = {
if (initialRandomDBPassphrase.get()) {
exportProhibitedAlert()
} else {
privacyFullBackup.set(it)
prefState.value = it
}
},
colors = SwitchDefaults.colors(
checkedThumbColor = MaterialTheme.colors.primary,
uncheckedThumbColor = HighOrLowlight
)
)
}
}
}
}
private fun setChatItemTTLAlert(
m: ChatModel, selectedChatItemTTL: MutableState<ChatItemTTL>,
progressIndicator: MutableState<Boolean>,
@@ -696,6 +747,8 @@ fun PreviewDatabaseLayout() {
privacyFullBackup = SharedPreference({ true }, {}),
appFilesCountAndSize = remember { mutableStateOf(0 to 0L) },
chatItemTTL = remember { mutableStateOf(ChatItemTTL.None) },
currentUser = User.sampleData,
users = listOf(UserInfo.sampleData),
startChat = {},
stopChatAlert = {},
exportArchive = {},

View File

@@ -8,11 +8,13 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.*
import androidx.compose.ui.window.Dialog
import chat.simplex.app.R
import chat.simplex.app.TAG
import chat.simplex.app.ui.theme.DEFAULT_PADDING
import chat.simplex.app.ui.theme.*
class AlertManager {
var alertViews = mutableStateListOf<(@Composable () -> Unit)>()
@@ -44,15 +46,25 @@ class AlertManager {
fun showAlertDialogButtonsColumn(
title: String,
text: String? = null,
text: AnnotatedString? = null,
buttons: @Composable () -> Unit,
) {
showAlert {
Dialog(onDismissRequest = this::hideAlert) {
Column(Modifier.background(MaterialTheme.colors.background)) {
Text(title, Modifier.padding(DEFAULT_PADDING), fontSize = 18.sp)
Column(Modifier.background(MaterialTheme.colors.background, MaterialTheme.shapes.medium)) {
Text(title,
Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, top = DEFAULT_PADDING, bottom = if (text == null) DEFAULT_PADDING else DEFAULT_PADDING_HALF),
fontSize = 15.sp,
fontWeight = FontWeight.SemiBold
)
if (text != null) {
Text(text)
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.high) {
Text(
text,
Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING),
fontSize = 14.sp,
)
}
}
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.high) {
buttons()

View File

@@ -11,7 +11,7 @@ import androidx.compose.ui.unit.dp
import chat.simplex.app.ui.theme.*
@Composable
fun CloseSheetBar(close: () -> Unit) {
fun CloseSheetBar(close: () -> Unit, endButtons: @Composable RowScope.() -> Unit = {}) {
Column(
Modifier
.fillMaxWidth()
@@ -20,9 +20,15 @@ fun CloseSheetBar(close: () -> Unit) {
) {
Row(
Modifier
.width(TitleInsetWithIcon - AppBarHorizontalPadding)
.padding(top = 4.dp), // Like in DefaultAppBar
content = { NavigationButtonBack(close) }
content = {
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
NavigationButtonBack(close)
Row {
endButtons()
}
}
}
)
}
}
@@ -30,7 +36,7 @@ fun CloseSheetBar(close: () -> Unit) {
@Composable
fun AppBarTitle(title: String, withPadding: Boolean = true) {
val padding = if (withPadding)
PaddingValues(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING )
PaddingValues(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING)
else
PaddingValues(bottom = DEFAULT_PADDING)
Text(

View File

@@ -53,6 +53,15 @@ fun NavigationButtonBack(onButtonClicked: () -> Unit) {
}
}
@Composable
fun ShareButton(onButtonClicked: () -> Unit) {
IconButton(onButtonClicked) {
Icon(
Icons.Outlined.Share, stringResource(R.string.share_verb), tint = MaterialTheme.colors.primary
)
}
}
@Composable
fun NavigationButtonMenu(onButtonClicked: () -> Unit) {
IconButton(onClick = onButtonClicked) {

View File

@@ -1,9 +1,13 @@
@file:UseSerializers(UriSerializer::class)
package chat.simplex.app.views.helpers
import android.graphics.Bitmap
import android.net.Uri
import androidx.compose.runtime.saveable.Saver
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
sealed class SharedContent {
data class Text(val text: String): SharedContent()
@@ -11,7 +15,7 @@ sealed class SharedContent {
data class File(val text: String, val uri: Uri): SharedContent()
}
enum class NewChatSheetState {
enum class AnimatedViewState {
VISIBLE, HIDING, GONE;
fun isVisible(): Boolean {
return this == VISIBLE
@@ -23,7 +27,7 @@ enum class NewChatSheetState {
return this == GONE
}
companion object {
fun saver(): Saver<MutableStateFlow<NewChatSheetState>, *> = Saver(
fun saver(): Saver<MutableStateFlow<AnimatedViewState>, *> = Saver(
save = { it.value.toString() },
restore = {
MutableStateFlow(valueOf(it))
@@ -32,7 +36,16 @@ enum class NewChatSheetState {
}
}
sealed class UploadContent {
data class SimpleImage(val uri: Uri): UploadContent()
data class AnimatedImage(val uri: Uri): UploadContent()
@Serializer(forClass = Uri::class)
object UriSerializer : KSerializer<Uri> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Uri", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: Uri) = encoder.encodeString(value.toString())
override fun deserialize(decoder: Decoder): Uri = Uri.parse(decoder.decodeString())
}
@Serializable
sealed class UploadContent {
@Serializable data class SimpleImage(val uri: Uri): UploadContent()
@Serializable data class AnimatedImage(val uri: Uri): UploadContent()
}

View File

@@ -6,6 +6,7 @@ import android.content.*
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.pm.PackageManager
import android.graphics.*
import android.graphics.ImageDecoder.DecodeException
import android.net.Uri
import android.provider.MediaStore
import android.util.Base64
@@ -205,21 +206,22 @@ fun GetImageBottomSheet(
val processPickedImage = { uri: Uri? ->
if (uri != null) {
val source = ImageDecoder.createSource(context.contentResolver, uri)
val bitmap = ImageDecoder.decodeBitmap(source)
imageBitmap.value = uri
onImageChange(bitmap)
}
}
val galleryLauncher = rememberLauncherForActivityResult(contract = PickFromGallery()) { processPickedImage(it) }
val galleryLauncherFallback = rememberGetContentLauncher { processPickedImage(it) }
val cameraLauncher = rememberCameraLauncher { uri: Uri? ->
if (uri != null) {
imageBitmap.value = uri
val source = ImageDecoder.createSource(SimplexApp.context.contentResolver, uri)
val bitmap = ImageDecoder.decodeBitmap(source)
onImageChange(bitmap)
try {
val bitmap = ImageDecoder.decodeBitmap(source)
imageBitmap.value = uri
onImageChange(bitmap)
} catch (e: DecodeException) {
Log.e(TAG, "Unable to decode the image: ${e.stackTraceToString()}")
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.image_decoding_exception_title),
text = generalGetString(R.string.image_decoding_exception_desc)
)
}
}
}
val galleryLauncher = rememberLauncherForActivityResult(contract = PickFromGallery(), processPickedImage)
val galleryLauncherFallback = rememberGetContentLauncher(processPickedImage)
val cameraLauncher = rememberCameraLauncher(processPickedImage)
val permissionLauncher = rememberPermissionLauncher { isGranted: Boolean ->
if (isGranted) {
cameraLauncher.launchWithFallback()

View File

@@ -20,12 +20,13 @@ fun ModalView(
close: () -> Unit,
background: Color = MaterialTheme.colors.background,
modifier: Modifier = Modifier,
endButtons: @Composable RowScope.() -> Unit = {},
content: @Composable () -> Unit,
) {
BackHandler(onBack = close)
Surface(Modifier.fillMaxSize()) {
Column(Modifier.background(background)) {
CloseSheetBar(close)
CloseSheetBar(close, endButtons)
Box(modifier) { content() }
}
}
@@ -37,9 +38,9 @@ class ModalManager {
private val toRemove = mutableSetOf<Int>()
private var oldViewChanging = AtomicBoolean(false)
fun showModal(settings: Boolean = false, content: @Composable () -> Unit) {
fun showModal(settings: Boolean = false, endButtons: @Composable RowScope.() -> Unit = {}, content: @Composable () -> Unit) {
showCustomModal { close ->
ModalView(close, if (!settings || isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight, content = content)
ModalView(close, if (!settings || isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight, endButtons = endButtons, content = content)
}
}

View File

@@ -1,5 +1,6 @@
package chat.simplex.app.views.helpers
import android.app.Application
import android.content.Context
import android.media.*
import android.media.AudioManager.AudioPlaybackCallback
@@ -14,8 +15,6 @@ import chat.simplex.app.model.ChatItem
import chat.simplex.app.views.helpers.AudioPlayer.duration
import kotlinx.coroutines.*
import java.io.*
import java.text.SimpleDateFormat
import java.util.*
interface Recorder {
fun start(onProgressUpdate: (position: Int?, finished: Boolean) -> Unit): String
@@ -26,6 +25,7 @@ class RecorderNative(private val recordedBytesLimit: Long): Recorder {
companion object {
// Allows to stop the recorder from outside without having the recorder in a variable
var stopRecording: (() -> Unit)? = null
const val extension = "m4a"
}
private var recorder: MediaRecorder? = null
private var progressJob: Job? = null
@@ -50,8 +50,10 @@ class RecorderNative(private val recordedBytesLimit: Long): Recorder {
rec.setAudioEncodingBitRate(16000)
rec.setMaxDuration(MAX_VOICE_MILLIS_FOR_SENDING)
rec.setMaxFileSize(recordedBytesLimit)
val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
val path = getAppFilePath(SimplexApp.context, uniqueCombine(SimplexApp.context, getAppFilePath(SimplexApp.context, "voice_${timestamp}.m4a")))
val tmpDir = SimplexApp.context.getDir("temp", Application.MODE_PRIVATE)
val fileToSave = File.createTempFile(generateNewFileName(SimplexApp.context, "voice", "${extension}_"), ".tmp", tmpDir)
fileToSave.deleteOnExit()
val path = fileToSave.absolutePath
filePath = path
rec.setOutputFile(path)
rec.prepare()

View File

@@ -1,4 +1,5 @@
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.*
@@ -11,6 +12,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.*
import chat.simplex.app.ui.theme.*
@@ -83,13 +85,14 @@ fun SectionItemView(
click: (() -> Unit)? = null,
minHeight: Dp = 46.dp,
disabled: Boolean = false,
padding: PaddingValues = PaddingValues(horizontal = DEFAULT_PADDING),
content: (@Composable RowScope.() -> Unit)
) {
val modifier = Modifier
.fillMaxWidth()
.sizeIn(minHeight = minHeight)
Row(
if (click == null || disabled) modifier.padding(horizontal = DEFAULT_PADDING) else modifier.clickable(onClick = click).padding(horizontal = DEFAULT_PADDING),
if (click == null || disabled) modifier.padding(padding) else modifier.clickable(onClick = click).padding(padding),
verticalAlignment = Alignment.CenterVertically
) {
content()
@@ -99,6 +102,7 @@ fun SectionItemView(
@Composable
fun SectionItemViewSpaceBetween(
click: (() -> Unit)? = null,
onLongClick: (() -> Unit)? = null,
minHeight: Dp = 46.dp,
padding: PaddingValues = PaddingValues(horizontal = DEFAULT_PADDING),
disabled: Boolean = false,
@@ -108,7 +112,7 @@ fun SectionItemViewSpaceBetween(
.fillMaxWidth()
.sizeIn(minHeight = minHeight)
Row(
if (click == null || disabled) modifier.padding(padding) else modifier.clickable(onClick = click).padding(padding),
if (click == null || disabled) modifier.padding(padding) else modifier.combinedClickable(onClick = click, onLongClick = onLongClick).padding(padding),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
@@ -157,6 +161,11 @@ fun <T> SectionItemWithValue(
@Composable
fun SectionTextFooter(text: String) {
SectionTextFooter(AnnotatedString(text))
}
@Composable
fun SectionTextFooter(text: AnnotatedString) {
Text(
text,
Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, top = DEFAULT_PADDING_HALF).fillMaxWidth(0.9F),

View File

@@ -1,5 +1,6 @@
package chat.simplex.app.views.helpers
import android.app.Application
import android.content.Context
import android.content.res.Resources
import android.graphics.*
@@ -244,6 +245,11 @@ fun getAppFilePath(context: Context, fileName: String): String {
return "${getAppFilesDirectory(context)}/$fileName"
}
fun getAppFileUri(fileName: String): Uri {
return Uri.parse("${getAppFilesDirectory(SimplexApp.context)}/$fileName")
}
fun getLoadedFilePath(context: Context, file: CIFile?): String? {
return if (file?.filePath != null && file.loaded) {
val filePath = getAppFilePath(context, file.filePath)
@@ -331,8 +337,7 @@ fun saveImage(context: Context, image: Bitmap): String? {
return try {
val ext = if (image.hasAlpha()) "png" else "jpg"
val dataResized = resizeImageToDataSize(image, ext == "png", maxDataSize = MAX_IMAGE_SIZE)
val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
val fileToSave = uniqueCombine(context, "IMG_${timestamp}.$ext")
val fileToSave = generateNewFileName(context, "IMG", ext)
val file = File(getAppFilePath(context, fileToSave))
val output = FileOutputStream(file)
dataResized.writeTo(output)
@@ -355,8 +360,7 @@ fun saveAnimImage(context: Context, uri: Uri): String? {
}
// Just in case the image has a strange extension
if (ext.length < 3 || ext.length > 4) ext = "gif"
val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
val fileToSave = uniqueCombine(context, "IMG_${timestamp}.$ext")
val fileToSave = generateNewFileName(context, "IMG", ext)
val file = File(getAppFilePath(context, fileToSave))
val output = FileOutputStream(file)
context.contentResolver.openInputStream(uri)!!.use { input ->
@@ -371,6 +375,24 @@ fun saveAnimImage(context: Context, uri: Uri): String? {
}
}
fun saveTempImageUncompressed(image: Bitmap, asPng: Boolean): File? {
return try {
val ext = if (asPng) "png" else "jpg"
val tmpDir = SimplexApp.context.getDir("temp", Application.MODE_PRIVATE)
return File(tmpDir.absolutePath + File.separator + generateNewFileName(SimplexApp.context, "IMG", ext)).apply {
outputStream().use { out ->
image.compress(if (asPng) Bitmap.CompressFormat.PNG else Bitmap.CompressFormat.JPEG, 85, out)
out.flush()
}
deleteOnExit()
SimplexApp.context.chatModel.filesToDelete.add(this)
}
} catch (e: Exception) {
Log.e(TAG, "Util.kt saveTempImageUncompressed error: ${e.message}")
null
}
}
fun saveFileFromUri(context: Context, uri: Uri): String? {
return try {
val inputStream = context.contentResolver.openInputStream(uri)
@@ -390,15 +412,23 @@ fun saveFileFromUri(context: Context, uri: Uri): String? {
}
}
fun generateNewFileName(context: Context, prefix: String, ext: String): String {
val sdf = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US)
sdf.timeZone = TimeZone.getTimeZone("GMT")
val timestamp = sdf.format(Date())
return uniqueCombine(context, "${prefix}_$timestamp.$ext")
}
fun uniqueCombine(context: Context, fileName: String): String {
fun tryCombine(fileName: String, n: Int): String {
val name = File(fileName).nameWithoutExtension
val ext = File(fileName).extension
val orig = File(fileName)
val name = orig.nameWithoutExtension
val ext = orig.extension
fun tryCombine(n: Int): String {
val suffix = if (n == 0) "" else "_$n"
val f = "$name$suffix.$ext"
return if (File(getAppFilePath(context, f)).exists()) tryCombine(fileName, n + 1) else f
return if (File(getAppFilePath(context, f)).exists()) tryCombine(n + 1) else f
}
return tryCombine(fileName, 0)
return tryCombine(0)
}
fun formatBytes(bytes: Long): String {

View File

@@ -134,7 +134,13 @@ fun AddGroupLayout(chatModelIncognito: Boolean, createGroup: (GroupProfile) -> U
val enabled = displayName.value.isNotEmpty() && isValidDisplayName(displayName.value)
if (enabled) {
CreateGroupButton(MaterialTheme.colors.primary, Modifier
.clickable { createGroup(GroupProfile(displayName.value, fullName.value, profileImage.value)) }
.clickable {
createGroup(GroupProfile(
displayName = displayName.value,
fullName = fullName.value,
image = profileImage.value
))
}
.padding(8.dp))
} else {
CreateGroupButton(HighOrLowlight, Modifier.padding(8.dp))

View File

@@ -37,7 +37,7 @@ import kotlinx.coroutines.launch
import kotlin.math.roundToInt
@Composable
fun NewChatSheet(chatModel: ChatModel, newChatSheetState: StateFlow<NewChatSheetState>, stopped: Boolean, closeNewChatSheet: (animated: Boolean) -> Unit) {
fun NewChatSheet(chatModel: ChatModel, newChatSheetState: StateFlow<AnimatedViewState>, stopped: Boolean, closeNewChatSheet: (animated: Boolean) -> Unit) {
if (newChatSheetState.collectAsState().value.isVisible()) BackHandler { closeNewChatSheet(true) }
NewChatSheetLayout(
newChatSheetState,
@@ -63,7 +63,7 @@ private val icons = listOf(Icons.Outlined.AddLink, Icons.Outlined.QrCode, Icons.
@Composable
private fun NewChatSheetLayout(
newChatSheetState: StateFlow<NewChatSheetState>,
newChatSheetState: StateFlow<AnimatedViewState>,
stopped: Boolean,
addContact: () -> Unit,
connectViaLink: () -> Unit,
@@ -216,7 +216,7 @@ fun ActionButton(
private fun PreviewNewChatSheet() {
SimpleXTheme {
NewChatSheetLayout(
MutableStateFlow(NewChatSheetState.VISIBLE),
MutableStateFlow(AnimatedViewState.VISIBLE),
stopped = false,
addContact = {},
connectViaLink = {},

View File

@@ -1,32 +1,100 @@
package chat.simplex.app.views.newchat
import android.graphics.Bitmap
import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.*
import androidx.compose.ui.platform.*
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import boofcv.alg.fiducial.qrcode.QrCodeEncoder
import boofcv.alg.fiducial.qrcode.QrCodeGeneratorImage
import androidx.core.graphics.*
import androidx.core.graphics.drawable.toBitmap
import boofcv.alg.drawing.FiducialImageEngine
import boofcv.alg.fiducial.qrcode.*
import boofcv.android.ConvertBitmap
import chat.simplex.app.R
import chat.simplex.app.SimplexApp
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.helpers.*
import kotlinx.coroutines.launch
@Composable
fun QRCode(connReq: String, modifier: Modifier = Modifier) {
Image(
bitmap = qrCodeBitmap(connReq, 1024).asImageBitmap(),
contentDescription = stringResource(R.string.image_descr_qr_code),
modifier = modifier
)
fun QRCode(
connReq: String,
modifier: Modifier = Modifier,
tintColor: Color = Color(0xff062d56),
withLogo: Boolean = true
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
BoxWithConstraints {
val maxWidthInPx = with(LocalDensity.current) { maxWidth.roundToPx() }
val qr = remember(maxWidthInPx, connReq, tintColor, withLogo) {
qrCodeBitmap(connReq, maxWidthInPx).replaceColor(Color.Black.toArgb(), tintColor.toArgb())
.let { if (withLogo) it.addLogo() else it }
.asImageBitmap()
}
Image(
bitmap = qr,
contentDescription = stringResource(R.string.image_descr_qr_code),
modifier
.clickable {
scope.launch {
val image = qrCodeBitmap(connReq, 1024).replaceColor(Color.Black.toArgb(), tintColor.toArgb())
.let { if (withLogo) it.addLogo() else it }
val file = saveTempImageUncompressed(image, false)
if (file != null) {
shareFile(context, "", file.absolutePath)
}
}
}
)
}
}
fun qrCodeBitmap(content: String, size: Int): Bitmap {
val qrCode = QrCodeEncoder().addAutomatic(content).fixate()
val renderer = QrCodeGeneratorImage(5)
fun qrCodeBitmap(content: String, size: Int = 1024): Bitmap {
val qrCode = QrCodeEncoder().addAutomatic(content).setError(QrCode.ErrorLevel.L).fixate()
/** See [QrCodeGeneratorImage.initialize] and [FiducialImageEngine.configure] for size calculation */
val numModules = QrCode.totalModules(qrCode.version)
val borderModule = 1
// val calculatedFinalWidth = (pixelsPerModule * numModules) + 2 * (borderModule * pixelsPerModule)
// size = (x * numModules) + 2 * (borderModule * x)
// size / x = numModules + 2 * borderModule
// x = size / (numModules + 2 * borderModule)
val pixelsPerModule = size / (numModules + 2 * borderModule)
// + 1 to make it with better quality
val renderer = QrCodeGeneratorImage(pixelsPerModule + 1)
renderer.borderModule = borderModule
renderer.render(qrCode)
return ConvertBitmap.grayToBitmap(renderer.gray, Bitmap.Config.RGB_565)
return ConvertBitmap.grayToBitmap(renderer.gray, Bitmap.Config.RGB_565).scale(size, size)
}
fun Bitmap.replaceColor(from: Int, to: Int): Bitmap {
val pixels = IntArray(width * height)
getPixels(pixels, 0, width, 0, 0, width, height)
var i = 0
while (i < pixels.size) {
if (pixels[i] == from) {
pixels[i] = to
}
i++
}
setPixels(pixels, 0, width, 0, 0, width, height)
return this
}
fun Bitmap.addLogo(): Bitmap = applyCanvas {
val radius = (width * 0.16f) / 2
val paint = android.graphics.Paint()
paint.color = android.graphics.Color.WHITE
drawCircle(width / 2f, height / 2f, radius, paint)
val logo = SimplexApp.context.resources.getDrawable(R.mipmap.icon_foreground, null).toBitmap()
val logoSize = (width * 0.24).toInt()
translate((width - logoSize) / 2f, (height - logoSize) / 2f)
drawBitmap(logo, null, android.graphics.Rect(0, 0, logoSize, logoSize), null)
}
@Preview

View File

@@ -21,7 +21,7 @@ enum class OnboardingStage {
}
@Composable
fun CreateProfile(chatModel: ChatModel) {
fun CreateProfile(chatModel: ChatModel, close: () -> Unit) {
val scope = rememberCoroutineScope()
val scrollState = rememberScrollState()
val keyboardState by getKeyboardState()
@@ -34,7 +34,10 @@ fun CreateProfile(chatModel: ChatModel) {
.background(color = MaterialTheme.colors.background)
.padding(20.dp)
) {
CreateProfilePanel(chatModel)
CreateProfilePanel(chatModel, close)
LaunchedEffect(Unit) {
setLastVersionDefault(chatModel)
}
if (savedKeyboardState != keyboardState) {
LaunchedEffect(keyboardState) {
scope.launch {

View File

@@ -18,8 +18,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.unit.*
import chat.simplex.app.R
import chat.simplex.app.model.ChatModel
import chat.simplex.app.model.User
@@ -47,15 +46,15 @@ fun SimpleXInfoLayout(
.verticalScroll(rememberScrollState())
.padding(horizontal = DEFAULT_PADDING),
) {
Box(Modifier.fillMaxWidth().padding(top = 24.dp, bottom = 8.dp), contentAlignment = Alignment.Center) {
Box(Modifier.fillMaxWidth().padding(top = 8.dp), contentAlignment = Alignment.Center) {
SimpleXLogo()
}
Text(stringResource(R.string.next_generation_of_private_messaging), style = MaterialTheme.typography.h2, modifier = Modifier.padding(bottom = 24.dp), textAlign = TextAlign.Center)
Text(stringResource(R.string.next_generation_of_private_messaging), style = MaterialTheme.typography.h2, modifier = Modifier.padding(bottom = 36.dp).padding(horizontal = 48.dp), textAlign = TextAlign.Center)
InfoRow(painterResource(R.drawable.privacy), R.string.privacy_redefined, R.string.first_platform_without_user_ids)
InfoRow(painterResource(R.drawable.privacy), R.string.privacy_redefined, R.string.first_platform_without_user_ids, width = 80.dp)
InfoRow(painterResource(R.drawable.shield), R.string.immune_to_spam_and_abuse, R.string.people_can_connect_only_via_links_you_share)
InfoRow(painterResource(R.drawable.decentralized), R.string.decentralized, R.string.opensource_protocol_and_code_anybody_can_run_servers)
InfoRow(painterResource(if (isInDarkTheme()) R.drawable.decentralized_light else R.drawable.decentralized), R.string.decentralized, R.string.opensource_protocol_and_code_anybody_can_run_servers)
Spacer(Modifier.fillMaxHeight().weight(1f))
@@ -89,14 +88,14 @@ fun SimpleXLogo() {
}
@Composable
private fun InfoRow(icon: Painter, @StringRes titleId: Int, @StringRes textId: Int) {
Row(Modifier.padding(bottom = 20.dp), verticalAlignment = Alignment.Top) {
private fun InfoRow(icon: Painter, @StringRes titleId: Int, @StringRes textId: Int, width: Dp = 76.dp) {
Row(Modifier.padding(bottom = 27.dp), verticalAlignment = Alignment.Top) {
Image(icon, contentDescription = null, modifier = Modifier
.width(60.dp)
.padding(top = 8.dp, end = 16.dp))
.width(width)
.padding(top = 8.dp, start = 8.dp, end = 24.dp))
Column(horizontalAlignment = Alignment.Start) {
Text(stringResource(titleId), fontWeight = FontWeight.Bold, style = MaterialTheme.typography.h3, lineHeight = 24.sp)
Text(stringResource(textId), lineHeight = 24.sp, style = MaterialTheme.typography.caption)
Text(stringResource(textId), lineHeight = 24.sp, style = MaterialTheme.typography.body1)
}
}
}

View File

@@ -0,0 +1,301 @@
package chat.simplex.app.views.onboarding
import android.content.res.Configuration
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.model.ChatModel
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.*
@Composable
fun WhatsNewView(viaSettings: Boolean = false, close: () -> Unit) {
val currentVersion = remember { mutableStateOf(versionDescriptions.lastIndex) }
@Composable
fun featureDescription(icon: ImageVector, titleId: Int, descrId: Int, link: String?) {
@Composable
fun linkButton(link: String) {
val uriHandler = LocalUriHandler.current
Icon(
Icons.Outlined.OpenInNew, stringResource(titleId), tint = MaterialTheme.colors.primary,
modifier = Modifier
.clickable { uriHandler.openUri(link) }
)
}
Column(
horizontalAlignment = Alignment.Start
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Icon(icon, stringResource(titleId), tint = HighOrLowlight)
Text(
generalGetString(titleId),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.h3,
fontWeight = FontWeight.Medium
)
if (link != null) {
linkButton(link)
}
}
Text(generalGetString(descrId))
}
}
@Composable
fun pagination() {
Row(
Modifier
.padding(bottom = 16.dp)
) {
if (currentVersion.value > 0) {
val prev = currentVersion.value - 1
Surface(shape = RoundedCornerShape(20.dp)) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.clickable { currentVersion.value = prev }
.padding(8.dp)
) {
Icon(Icons.Outlined.ArrowBackIosNew, "previous", tint = MaterialTheme.colors.primary)
Text(versionDescriptions[prev].version, color = MaterialTheme.colors.primary)
}
}
}
Spacer(Modifier.fillMaxWidth().weight(1f))
if (currentVersion.value < versionDescriptions.lastIndex) {
val next = currentVersion.value + 1
Surface(shape = RoundedCornerShape(20.dp)) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.clickable { currentVersion.value = next }
.padding(8.dp)
) {
Text(versionDescriptions[next].version, color = MaterialTheme.colors.primary)
Icon(Icons.Outlined.ArrowForwardIos, "next", tint = MaterialTheme.colors.primary)
}
}
}
}
}
val v = versionDescriptions[currentVersion.value]
ModalView(close = close) {
Column(
Modifier
.fillMaxWidth()
.padding(horizontal = DEFAULT_PADDING),
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
String.format(generalGetString(R.string.new_in_version), v.version),
Modifier
.fillMaxWidth()
.padding(DEFAULT_PADDING),
textAlign = TextAlign.Center,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.h1,
fontWeight = FontWeight.Normal,
color = HighOrLowlight
)
v.features.forEach { feature ->
featureDescription(feature.icon, feature.titleId, feature.descrId, feature.link)
}
if (!viaSettings) {
Spacer(Modifier.fillMaxHeight().weight(1f))
Box(
Modifier.fillMaxWidth(), contentAlignment = Alignment.Center
) {
Text(
generalGetString(R.string.ok),
modifier = Modifier.clickable(onClick = close),
style = MaterialTheme.typography.h3,
color = MaterialTheme.colors.primary
)
}
Spacer(Modifier.fillMaxHeight().weight(1f))
}
Spacer(Modifier.fillMaxHeight().weight(1f))
pagination()
}
}
}
private data class FeatureDescription(
val icon: ImageVector,
val titleId: Int,
val descrId: Int,
val link: String? = null
)
private data class VersionDescription(
val version: String,
val features: List<FeatureDescription>
)
private val versionDescriptions: List<VersionDescription> = listOf(
VersionDescription(
version = "v4.2",
features = listOf(
FeatureDescription(
icon = Icons.Outlined.VerifiedUser,
titleId = R.string.v4_2_security_assessment,
descrId = R.string.v4_2_security_assessment_desc,
link = "https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html"
),
FeatureDescription(
icon = Icons.Outlined.Group,
titleId = R.string.v4_2_group_links,
descrId = R.string.v4_2_group_links_desc
),
FeatureDescription(
icon = Icons.Outlined.Check,
titleId = R.string.v4_2_auto_accept_contact_requests,
descrId = R.string.v4_2_auto_accept_contact_requests_desc
),
)
),
VersionDescription(
version = "v4.3",
features = listOf(
FeatureDescription(
icon = Icons.Outlined.Mic,
titleId = R.string.v4_3_voice_messages,
descrId = R.string.v4_3_voice_messages_desc
),
FeatureDescription(
icon = Icons.Outlined.DeleteForever,
titleId = R.string.v4_3_irreversible_message_deletion,
descrId = R.string.v4_3_irreversible_message_deletion_desc
),
FeatureDescription(
icon = Icons.Outlined.WifiTethering,
titleId = R.string.v4_3_improved_server_configuration,
descrId = R.string.v4_3_improved_server_configuration_desc
),
FeatureDescription(
icon = Icons.Outlined.VisibilityOff,
titleId = R.string.v4_3_improved_privacy_and_security,
descrId = R.string.v4_3_improved_privacy_and_security_desc
),
)
),
VersionDescription(
version = "v4.4",
features = listOf(
FeatureDescription(
icon = Icons.Outlined.Timer,
titleId = R.string.v4_4_disappearing_messages,
descrId = R.string.v4_4_disappearing_messages_desc
),
FeatureDescription(
icon = Icons.Outlined.Pending,
titleId = R.string.v4_4_live_messages,
descrId = R.string.v4_4_live_messages_desc
),
FeatureDescription(
icon = Icons.Outlined.VerifiedUser,
titleId = R.string.v4_4_verify_connection_security,
descrId = R.string.v4_4_verify_connection_security_desc
),
FeatureDescription(
icon = Icons.Outlined.Translate,
titleId = R.string.v4_4_french_interface,
descrId = R.string.v4_4_french_interface_descr
)
)
),
VersionDescription(
version = "v4.5",
features = listOf(
FeatureDescription(
icon = Icons.Outlined.ManageAccounts,
titleId = R.string.v4_5_multiple_chat_profiles,
descrId = R.string.v4_5_multiple_chat_profiles_descr
),
FeatureDescription(
icon = Icons.Outlined.EditNote,
titleId = R.string.v4_5_message_draft,
descrId = R.string.v4_5_message_draft_descr
),
FeatureDescription(
icon = Icons.Outlined.SafetyDivider,
titleId = R.string.v4_5_transport_isolation,
descrId = R.string.v4_5_transport_isolation_descr,
link = "https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation"
),
FeatureDescription(
icon = Icons.Outlined.Task,
titleId = R.string.v4_5_private_filenames,
descrId = R.string.v4_5_private_filenames_descr
),
FeatureDescription(
icon = Icons.Outlined.Battery2Bar,
titleId = R.string.v4_5_reduced_battery_usage,
descrId = R.string.v4_5_reduced_battery_usage_descr
),
FeatureDescription(
icon = Icons.Outlined.Translate,
titleId = R.string.v4_5_italian_interface,
descrId = R.string.v4_5_italian_interface_descr,
link = "https://github.com/simplex-chat/simplex-chat/tree/stable#translate-the-apps"
)
)
)
)
private val lastVersion = versionDescriptions.last().version
fun setLastVersionDefault(m: ChatModel) {
m.controller.appPrefs.whatsNewVersion.set(lastVersion)
}
fun shouldShowWhatsNew(m: ChatModel): Boolean {
val v = m.controller.appPrefs.whatsNewVersion.get()
setLastVersionDefault(m)
return v != lastVersion
}
@Preview(showBackground = true)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
fun PreviewWhatsNewView() {
SimpleXTheme {
WhatsNewView(
viaSettings = true,
close = {}
)
}
}

View File

@@ -33,6 +33,7 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) {
val networkTCPConnectTimeout = remember { mutableStateOf(currentCfgVal.tcpConnectTimeout) }
val networkTCPTimeout = remember { mutableStateOf(currentCfgVal.tcpTimeout) }
val networkSMPPingInterval = remember { mutableStateOf(currentCfgVal.smpPingInterval) }
val networkSMPPingCount = remember { mutableStateOf(currentCfgVal.smpPingCount) }
val networkEnableKeepAlive = remember { mutableStateOf(currentCfgVal.enableKeepAlive) }
val networkTCPKeepIdle: MutableState<Int>
val networkTCPKeepIntvl: MutableState<Int>
@@ -48,10 +49,6 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) {
}
fun buildCfg(): NetCfg {
val socksProxy = currentCfg.value.socksProxy
val tcpConnectTimeout = networkTCPConnectTimeout.value
val tcpTimeout = networkTCPTimeout.value
val smpPingInterval = networkSMPPingInterval.value
val enableKeepAlive = networkEnableKeepAlive.value
val tcpKeepAlive = if (enableKeepAlive) {
val keepIdle = networkTCPKeepIdle.value
@@ -62,11 +59,15 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) {
null
}
return NetCfg(
socksProxy = socksProxy,
tcpConnectTimeout = tcpConnectTimeout,
tcpTimeout = tcpTimeout,
socksProxy = currentCfg.value.socksProxy,
hostMode = currentCfg.value.hostMode,
requiredHostMode = currentCfg.value.requiredHostMode,
sessionMode = currentCfg.value.sessionMode,
tcpConnectTimeout = networkTCPConnectTimeout.value,
tcpTimeout = networkTCPTimeout.value,
tcpKeepAlive = tcpKeepAlive,
smpPingInterval = smpPingInterval
smpPingInterval = networkSMPPingInterval.value,
smpPingCount = networkSMPPingCount.value
)
}
@@ -74,6 +75,7 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) {
networkTCPConnectTimeout.value = cfg.tcpConnectTimeout
networkTCPTimeout.value = cfg.tcpTimeout
networkSMPPingInterval.value = cfg.smpPingInterval
networkSMPPingCount.value = cfg.smpPingCount
networkEnableKeepAlive.value = cfg.enableKeepAlive
if (cfg.tcpKeepAlive != null) {
networkTCPKeepIdle.value = cfg.tcpKeepAlive.keepIdle
@@ -113,6 +115,7 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) {
networkTCPConnectTimeout,
networkTCPTimeout,
networkSMPPingInterval,
networkSMPPingCount,
networkEnableKeepAlive,
networkTCPKeepIdle,
networkTCPKeepIntvl,
@@ -129,6 +132,7 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) {
networkTCPConnectTimeout: MutableState<Long>,
networkTCPTimeout: MutableState<Long>,
networkSMPPingInterval: MutableState<Long>,
networkSMPPingCount: MutableState<Int>,
networkEnableKeepAlive: MutableState<Boolean>,
networkTCPKeepIdle: MutableState<Int>,
networkTCPKeepIntvl: MutableState<Int>,
@@ -170,7 +174,14 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) {
SectionItemView {
TimeoutSettingRow(
stringResource(R.string.network_option_ping_interval), networkSMPPingInterval,
listOf(120_000000, 300_000000, 600_000000, 1200_000000, 2400_000000), secondsLabel
listOf(120_000000, 300_000000, 600_000000, 1200_000000, 2400_000000, 3600_000000), secondsLabel
)
}
SectionDivider()
SectionItemView {
IntSettingRow(
stringResource(R.string.network_option_ping_count), networkSMPPingCount,
listOf(1, 2, 3, 5, 8), ""
)
}
SectionDivider()
@@ -412,6 +423,7 @@ fun PreviewAdvancedNetworkSettingsLayout() {
networkTCPConnectTimeout = remember { mutableStateOf(10_000000) },
networkTCPTimeout = remember { mutableStateOf(10_000000) },
networkSMPPingInterval = remember { mutableStateOf(10_000000) },
networkSMPPingCount = remember { mutableStateOf(3) },
networkEnableKeepAlive = remember { mutableStateOf(true) },
networkTCPKeepIdle = remember { mutableStateOf(10) },
networkTCPKeepIntvl = remember { mutableStateOf(10) },

View File

@@ -24,8 +24,6 @@ import androidx.compose.ui.graphics.*
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.capitalize
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
@@ -65,11 +63,6 @@ fun AppearanceView() {
AppearanceLayout(
appIcon,
changeIcon = ::setAppIcon,
showThemeSelector = {
ModalManager.shared.showModal(true) {
ThemeSelectorView()
}
},
editPrimaryColor = { primary ->
ModalManager.shared.showModalCloseable { close ->
ColorEditor(primary, close)
@@ -81,7 +74,6 @@ fun AppearanceView() {
@Composable fun AppearanceLayout(
icon: MutableState<AppIcon>,
changeIcon: (AppIcon) -> Unit,
showThemeSelector: () -> Unit,
editPrimaryColor: (Color) -> Unit,
) {
Column(
@@ -115,8 +107,12 @@ fun AppearanceView() {
SectionSpacer()
val currentTheme by CurrentColors.collectAsState()
SectionView(stringResource(R.string.settings_section_title_themes)) {
SectionItemViewSpaceBetween(showThemeSelector) {
Text(generalGetString(R.string.theme))
SectionItemViewSpaceBetween {
val darkTheme = isSystemInDarkTheme()
val state = remember { derivedStateOf { currentTheme.second } }
ThemeSelector(state) {
ThemeManager.applyTheme(it.name, darkTheme)
}
}
SectionDivider()
SectionItemViewSpaceBetween({ editPrimaryColor(currentTheme.first.primary) }) {
@@ -183,6 +179,21 @@ fun ColorPicker(initialColor: Color, onColorChanged: (Color) -> Unit) {
)
}
@Composable
private fun ThemeSelector(state: State<DefaultTheme>, onSelected: (DefaultTheme) -> Unit) {
val darkTheme = isSystemInDarkTheme()
val values by remember { mutableStateOf(ThemeManager.allThemes(darkTheme).map { it.second to it.third }) }
ExposedDropDownSettingRow(
generalGetString(R.string.theme),
values,
state,
icon = null,
enabled = remember { mutableStateOf(true) },
onSelected = onSelected
)
}
private fun findEnabledIcon(): AppIcon = AppIcon.values().first { icon ->
SimplexApp.context.packageManager.getComponentEnabledSetting(
ComponentName(BuildConfig.APPLICATION_ID, "chat.simplex.app.MainActivity_${icon.name.lowercase()}")
@@ -196,7 +207,6 @@ fun PreviewAppearanceSettings() {
AppearanceLayout(
icon = remember { mutableStateOf(AppIcon.DARK_BLUE) },
changeIcon = {},
showThemeSelector = {},
editPrimaryColor = {},
)
}

View File

@@ -9,6 +9,7 @@ import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
@@ -113,7 +114,7 @@ fun SharedPreferenceToggleWithIcon(
) {
val prefState = preferenceState ?: remember { mutableStateOf(preference.get()) }
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Text(text, Modifier.padding(end = 4.dp))
Text(text, Modifier.padding(end = 4.dp), color = if (stopped) HighOrLowlight else Color.Unspecified)
Icon(
icon,
null,

View File

@@ -17,18 +17,13 @@ import chat.simplex.app.model.Format
import chat.simplex.app.model.FormatColor
import chat.simplex.app.ui.theme.DEFAULT_PADDING
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.helpers.AppBarTitle
import chat.simplex.app.views.helpers.generalGetString
@Composable
fun MarkdownHelpView() {
Column(
Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState())
.padding(horizontal = DEFAULT_PADDING)
) {
AppBarTitle(stringResource(R.string.how_to_use_markdown), false)
Text(stringResource(R.string.you_can_use_markdown_to_format_messages__prompt))
Spacer(Modifier.height(DEFAULT_PADDING))
val bold = stringResource(R.string.bold)

View File

@@ -31,6 +31,7 @@ fun NetworkAndServersView(
val networkUseSocksProxy: MutableState<Boolean> = remember { mutableStateOf(netCfg.useSocksProxy) }
val developerTools = chatModel.controller.appPrefs.developerTools.get()
val onionHosts = remember { mutableStateOf(netCfg.onionHosts) }
val sessionMode = remember { mutableStateOf(netCfg.sessionMode) }
LaunchedEffect(Unit) {
chatModel.userSMPServersUnsaved.value = null
@@ -40,6 +41,7 @@ fun NetworkAndServersView(
developerTools = developerTools,
networkUseSocksProxy = networkUseSocksProxy,
onionHosts = onionHosts,
sessionMode = sessionMode,
showModal = showModal,
showSettingsModal = showSettingsModal,
toggleSocksProxy = { enable ->
@@ -82,9 +84,13 @@ fun NetworkAndServersView(
OnionHosts.PREFER -> generalGetString(R.string.network_use_onion_hosts_prefer_desc_in_alert)
OnionHosts.REQUIRED -> generalGetString(R.string.network_use_onion_hosts_required_desc_in_alert)
}
updateOnionHostsDialog(startsWith, onDismiss = {
onionHosts.value = prevValue
}) {
updateNetworkSettingsDialog(
title = generalGetString(R.string.update_onion_hosts_settings_question),
startsWith,
onDismiss = {
onionHosts.value = prevValue
}
) {
withApi {
val newCfg = chatModel.controller.getNetCfg().withOnionHosts(it)
val res = chatModel.controller.apiSetNetworkConfig(newCfg)
@@ -96,6 +102,31 @@ fun NetworkAndServersView(
}
}
}
},
updateSessionMode = {
if (sessionMode.value == it) return@NetworkAndServersLayout
val prevValue = sessionMode.value
sessionMode.value = it
val startsWith = when (it) {
TransportSessionMode.User -> generalGetString(R.string.network_session_mode_user_description)
TransportSessionMode.Entity -> generalGetString(R.string.network_session_mode_entity_description)
}
updateNetworkSettingsDialog(
title = generalGetString(R.string.update_network_session_mode_question),
startsWith,
onDismiss = { sessionMode.value = prevValue }
) {
withApi {
val newCfg = chatModel.controller.getNetCfg().copy(sessionMode = it)
val res = chatModel.controller.apiSetNetworkConfig(newCfg)
if (res) {
chatModel.controller.setNetCfg(newCfg)
sessionMode.value = it
} else {
sessionMode.value = prevValue
}
}
}
}
)
}
@@ -104,10 +135,12 @@ fun NetworkAndServersView(
developerTools: Boolean,
networkUseSocksProxy: MutableState<Boolean>,
onionHosts: MutableState<OnionHosts>,
sessionMode: MutableState<TransportSessionMode>,
showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
toggleSocksProxy: (Boolean) -> Unit,
useOnion: (OnionHosts) -> Unit,
updateSessionMode: (TransportSessionMode) -> Unit,
) {
Column(
Modifier.fillMaxWidth(),
@@ -123,10 +156,12 @@ fun NetworkAndServersView(
}
SectionDivider()
UseOnionHosts(onionHosts, networkUseSocksProxy, showSettingsModal, useOnion)
SectionDivider()
if (developerTools) {
SessionModePicker(sessionMode, showSettingsModal, updateSessionMode)
SectionDivider()
SettingsActionItem(Icons.Outlined.Cable, stringResource(R.string.network_settings), showSettingsModal { AdvancedNetworkSettingsView(it) })
}
SettingsActionItem(Icons.Outlined.Cable, stringResource(R.string.network_settings), showSettingsModal { AdvancedNetworkSettingsView(it) })
}
Spacer(Modifier.height(8.dp))
SectionView(generalGetString(R.string.settings_section_title_calls)) {
@@ -185,7 +220,6 @@ private fun UseOnionHosts(
}
}
val onSelected = showModal {
Column(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.Start,
@@ -205,14 +239,47 @@ private fun UseOnionHosts(
)
}
private fun updateOnionHostsDialog(
@Composable
private fun SessionModePicker(
sessionMode: MutableState<TransportSessionMode>,
showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
updateSessionMode: (TransportSessionMode) -> Unit,
) {
val values = remember {
TransportSessionMode.values().map {
when (it) {
TransportSessionMode.User -> ValueTitleDesc(TransportSessionMode.User, generalGetString(R.string.network_session_mode_user), generalGetString(R.string.network_session_mode_user_description))
TransportSessionMode.Entity -> ValueTitleDesc(TransportSessionMode.Entity, generalGetString(R.string.network_session_mode_entity), generalGetString(R.string.network_session_mode_entity_description))
}
}
}
SectionItemWithValue(
generalGetString(R.string.network_session_mode_transport_isolation),
sessionMode,
values,
icon = Icons.Outlined.SafetyDivider,
onSelected = showModal {
Column(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.Start,
) {
AppBarTitle(stringResource(R.string.network_session_mode_transport_isolation))
SectionViewSelectable(null, sessionMode, values, updateSessionMode)
}
}
)
}
private fun updateNetworkSettingsDialog(
title: String,
startsWith: String = "",
message: String = generalGetString(R.string.updating_settings_will_reconnect_client_to_all_servers),
onDismiss: () -> Unit,
onConfirm: () -> Unit
) {
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.update_onion_hosts_settings_question),
title = title,
text = startsWith + "\n\n" + message,
confirmText = generalGetString(R.string.update_network_settings_confirmation),
onDismiss = onDismiss,
@@ -232,7 +299,9 @@ fun PreviewNetworkAndServersLayout() {
showSettingsModal = { {} },
toggleSocksProxy = {},
onionHosts = remember { mutableStateOf(OnionHosts.PREFER) },
sessionMode = remember { mutableStateOf(TransportSessionMode.User) },
useOnion = {},
updateSessionMode = {},
)
}
}

View File

@@ -29,12 +29,8 @@ fun PreferencesView(m: ChatModel, user: User, close: () -> Unit,) {
val newProfile = user.profile.toProfile().copy(preferences = preferences.toPreferences())
val updatedProfile = m.controller.apiUpdateProfile(newProfile)
if (updatedProfile != null) {
val updatedUser = user.copy(
profile = updatedProfile.toLocalProfile(user.profile.profileId),
fullPreferences = preferences
)
m.updateCurrentUser(updatedProfile, preferences)
currentPreferences = preferences
m.currentUser.value = updatedUser
}
afterSave()
}

View File

@@ -13,7 +13,6 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
import androidx.fragment.app.FragmentActivity
import chat.simplex.app.R
@@ -51,8 +50,6 @@ fun PrivacySettingsView(
SectionView(stringResource(R.string.settings_section_title_chats)) {
SettingsPreferenceItem(Icons.Outlined.Image, stringResource(R.string.auto_accept_images), chatModel.controller.appPrefs.privacyAcceptImages)
SectionDivider()
SettingsPreferenceItem(Icons.Outlined.ImageAspectRatio, stringResource(R.string.transfer_images_faster), chatModel.controller.appPrefs.privacyTransferImagesInline)
SectionDivider()
SettingsPreferenceItem(Icons.Outlined.TravelExplore, stringResource(R.string.send_link_previews), chatModel.controller.appPrefs.privacyLinkPreviews)
SectionDivider()
SectionItemView { SimpleXLinkOptions(chatModel.simplexLinkMode, onSelected = {

View File

@@ -3,6 +3,7 @@ package chat.simplex.app.views.usersettings
import SectionDivider
import SectionItemView
import SectionSpacer
import SectionTextFooter
import SectionView
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
@@ -15,6 +16,8 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.model.*
@@ -76,6 +79,7 @@ fun SMPServersView(m: ChatModel) {
serversUnchanged = serversUnchanged.value,
saveDisabled = saveDisabled.value,
allServersDisabled = allServersDisabled.value,
m.currentUser.value,
addServer = {
AlertManager.shared.showAlertDialogButtonsColumn(
title = generalGetString(R.string.smp_servers_add),
@@ -156,6 +160,7 @@ private fun SMPServersLayout(
serversUnchanged: Boolean,
saveDisabled: Boolean,
allServersDisabled: Boolean,
currentUser: User?,
addServer: () -> Unit,
testServers: () -> Unit,
resetServers: () -> Unit,
@@ -186,6 +191,17 @@ private fun SMPServersLayout(
iconColor = if (testing) HighOrLowlight else MaterialTheme.colors.primary
)
}
SectionTextFooter(
remember(currentUser?.displayName) {
buildAnnotatedString {
append(generalGetString(R.string.smp_servers_per_user) + " ")
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
append(currentUser?.displayName ?: "")
}
append(".")
}
}
)
SectionSpacer()
SectionView {
SectionItemView(resetServers, disabled = serversUnchanged) {

View File

@@ -4,6 +4,7 @@ import SectionDivider
import SectionItemView
import SectionSpacer
import SectionView
import android.content.Context
import android.content.res.Configuration
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
@@ -16,14 +17,14 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.platform.UriHandler
import androidx.compose.ui.platform.*
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.*
import androidx.fragment.app.FragmentActivity
import chat.simplex.app.*
import chat.simplex.app.R
import chat.simplex.app.model.*
@@ -34,6 +35,7 @@ import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.newchat.CreateLinkTab
import chat.simplex.app.views.newchat.CreateLinkView
import chat.simplex.app.views.onboarding.SimpleXInfo
import chat.simplex.app.views.onboarding.WhatsNewView
@Composable
fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit) {
@@ -43,6 +45,8 @@ fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit) {
MaintainIncognitoState(chatModel)
if (user != null) {
val requireAuth = remember { chatModel.controller.appPrefs.performLA.state }
val context = LocalContext.current
SettingsLayout(
profile = user.profile,
stopped,
@@ -55,8 +59,43 @@ fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit) {
showModal = { modalView -> { ModalManager.shared.showModal { modalView(chatModel) } } },
showSettingsModal = { modalView -> { ModalManager.shared.showModal(true) { modalView(chatModel) } } },
showCustomModal = { modalView -> { ModalManager.shared.showCustomModal { close -> modalView(chatModel, close) } } },
showTerminal = { ModalManager.shared.showCustomModal { close -> TerminalView(chatModel, close) } },
// showVideoChatPrototype = { ModalManager.shared.showCustomModal { close -> CallViewDebug(close) } },
showVersion = {
withApi {
val info = chatModel.controller.apiGetVersion()
if (info != null) {
ModalManager.shared.showModal { VersionInfoView(info) }
}
}
},
withAuth = { block ->
if (!requireAuth.value) {
block()
} else {
ModalManager.shared.showModalCloseable { close ->
val onFinishAuth = { success: Boolean ->
if (success) {
close()
block()
}
}
LaunchedEffect(Unit) {
runAuth(context, onFinishAuth)
}
Box(
Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
SimpleButton(
stringResource(R.string.auth_unlock),
icon = Icons.Outlined.Lock,
click = {
runAuth(context, onFinishAuth)
}
)
}
}
}
},
)
}
}
@@ -64,16 +103,6 @@ fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit) {
val simplexTeamUri =
"simplex:/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D"
// TODO pass close
//fun showSectionedModal(chatModel: ChatModel, modalView: (@Composable (ChatModel) -> Unit)) {
// ModalManager.shared.showCustomModal { close ->
// ModalView(close = close, modifier = Modifier,
// background = if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight) {
// modalView(chatModel)
// }
// }
//}
@Composable
fun SettingsLayout(
profile: LocalProfile,
@@ -87,8 +116,8 @@ fun SettingsLayout(
showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit),
showTerminal: () -> Unit,
// showVideoChatPrototype: () -> Unit
showVersion: () -> Unit,
withAuth: (block: () -> Unit) -> Unit
) {
val uriHandler = LocalUriHandler.current
Surface(Modifier.fillMaxSize().verticalScroll(rememberScrollState())) {
@@ -112,11 +141,13 @@ fun SettingsLayout(
ProfilePreview(profile, stopped = stopped)
}
SectionDivider()
SettingsActionItem(Icons.Outlined.ManageAccounts, stringResource(R.string.your_chat_profiles), { withAuth { showSettingsModal { UserProfilesView(it) }() } }, disabled = stopped)
SectionDivider()
SettingsIncognitoActionItem(incognitoPref, incognito, stopped) { showModal { IncognitoView() }() }
SectionDivider()
SettingsActionItem(Icons.Outlined.QrCode, stringResource(R.string.your_simplex_contact_address), showModal { CreateLinkView(it, CreateLinkTab.LONG_TERM) }, disabled = stopped)
SectionDivider()
ChatPreferencesItem(showCustomModal)
ChatPreferencesItem(showCustomModal, stopped = stopped)
}
SectionSpacer()
@@ -138,9 +169,9 @@ fun SettingsLayout(
SectionView(stringResource(R.string.settings_section_title_help)) {
SettingsActionItem(Icons.Outlined.HelpOutline, stringResource(R.string.how_to_use_simplex_chat), showModal { HelpView(userDisplayName) }, disabled = stopped)
SectionDivider()
SettingsActionItem(Icons.Outlined.Info, stringResource(R.string.about_simplex_chat), showModal { SimpleXInfo(it, onboarding = false) })
SettingsActionItem(Icons.Outlined.Add, stringResource(R.string.whats_new), showCustomModal { _, close -> WhatsNewView(viaSettings = true, close) }, disabled = stopped)
SectionDivider()
SettingsActionItem(Icons.Outlined.TextFormat, stringResource(R.string.markdown_in_messages), showModal { MarkdownHelpView() })
SettingsActionItem(Icons.Outlined.Info, stringResource(R.string.about_simplex_chat), showModal { SimpleXInfo(it, onboarding = false) })
SectionDivider()
SettingsActionItem(Icons.Outlined.Tag, stringResource(R.string.chat_with_the_founder), { uriHandler.openUri(simplexTeamUri) }, textColor = MaterialTheme.colors.primary, disabled = stopped)
SectionDivider()
@@ -162,14 +193,14 @@ fun SettingsLayout(
SettingsPreferenceItem(Icons.Outlined.Construction, stringResource(R.string.settings_developer_tools), developerTools, devTools)
SectionDivider()
if (devTools.value) {
ChatConsoleItem(showTerminal)
ChatConsoleItem { withAuth(showCustomModal { it, close -> TerminalView(it, close) }) }
SectionDivider()
InstallTerminalAppItem(uriHandler)
SectionDivider()
}
// SettingsActionItem(Icons.Outlined.Science, stringResource(R.string.settings_experimental_features), showSettingsModal { ExperimentalFeaturesView(it, enableCalls) })
// SectionDivider()
AppVersionItem()
AppVersionItem(showVersion)
}
}
}
@@ -239,17 +270,18 @@ fun MaintainIncognitoState(chatModel: ChatModel) {
}
}
@Composable fun ChatPreferencesItem(showCustomModal: ((@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit))) {
@Composable fun ChatPreferencesItem(showCustomModal: ((@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit)), stopped: Boolean) {
SettingsActionItem(
Icons.Outlined.ToggleOn,
stringResource(R.string.chat_preferences),
click = {
click = if (stopped) null else ({
withApi {
showCustomModal { m, close ->
PreferencesView(m, m.currentUser.value ?: return@showCustomModal, close)
}()
}
}
}),
disabled = stopped
)
}
@@ -344,8 +376,8 @@ fun MaintainIncognitoState(chatModel: ChatModel) {
}
}
@Composable private fun AppVersionItem() {
SectionItemView() {
@Composable private fun AppVersionItem(showVersion: () -> Unit) {
SectionItemView(showVersion) {
Text("v${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})")
}
}
@@ -407,7 +439,7 @@ fun SettingsPreferenceItemWithInfo(
pref: SharedPreference<Boolean>,
prefState: MutableState<Boolean>? = null
) {
SectionItemView(onClickInfo) {
SectionItemView(if (stopped) null else onClickInfo) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(icon, text, tint = if (stopped) HighOrLowlight else iconTint)
Spacer(Modifier.padding(horizontal = 4.dp))
@@ -468,6 +500,17 @@ fun PreferenceToggleWithIcon(
}
}
private fun runAuth(context: Context, onFinish: (success: Boolean) -> Unit) {
authenticate(
generalGetString(R.string.auth_open_chat_console),
generalGetString(R.string.auth_log_in_using_credential),
context as FragmentActivity,
completed = { laResult ->
onFinish(laResult == LAResult.Success || laResult == LAResult.Unavailable)
}
)
}
@Preview(showBackground = true)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
@@ -488,9 +531,9 @@ fun PreviewSettingsLayout() {
setPerformLA = {},
showModal = { {} },
showSettingsModal = { {} },
showCustomModal = { {}},
showTerminal = {},
// showVideoChatPrototype = {}
showCustomModal = { {} },
showVersion = {},
withAuth = {},
)
}
}

View File

@@ -1,44 +0,0 @@
package chat.simplex.app.views.usersettings
import SectionViewSelectable
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.capitalize
import androidx.compose.ui.text.intl.Locale
import chat.simplex.app.R
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.*
@Composable
fun ThemeSelectorView() {
val darkTheme = isSystemInDarkTheme()
val allThemes by remember { mutableStateOf(ThemeManager.allThemes(darkTheme).map { ValueTitleDesc(it.second, it.third, "") }) }
ThemeSelectorLayout(
allThemes,
onSelectTheme = {
ThemeManager.applyTheme(it.name, darkTheme)
},
)
}
@Composable
private fun ThemeSelectorLayout(
allThemes: List<ValueTitleDesc<DefaultTheme>>,
onSelectTheme: (DefaultTheme) -> Unit,
) {
Column(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.Start,
) {
AppBarTitle(stringResource(R.string.settings_section_title_themes).lowercase().capitalize(Locale.current))
val currentTheme by CurrentColors.collectAsState()
val state = remember { derivedStateOf { currentTheme.second } }
SectionViewSelectable(null, state, allThemes, onSelectTheme)
}
}

View File

@@ -44,12 +44,9 @@ fun UserProfileView(chatModel: ChatModel, close: () -> Unit) {
close,
saveProfile = { displayName, fullName, image ->
withApi {
val p = Profile(displayName, fullName, image)
val newProfile = chatModel.controller.apiUpdateProfile(p)
val newProfile = chatModel.controller.apiUpdateProfile(profile.copy(displayName = displayName, fullName = fullName, image = image))
if (newProfile != null) {
chatModel.currentUser.value?.profile?.profileId?.let {
chatModel.updateUserProfile(newProfile.toLocalProfile(it))
}
chatModel.updateCurrentUser(newProfile)
profile = newProfile
}
editProfile.value = false
@@ -97,7 +94,7 @@ fun UserProfileLayout(
.padding(horizontal = DEFAULT_PADDING),
horizontalAlignment = Alignment.Start
) {
AppBarTitle(stringResource(R.string.your_chat_profile), false)
AppBarTitle(stringResource(R.string.your_current_profile), false)
Text(
stringResource(R.string.your_profile_is_stored_on_device_and_shared_only_with_contacts_simplex_cannot_see_it),
Modifier.padding(bottom = 24.dp),

View File

@@ -0,0 +1,141 @@
package chat.simplex.app.views.usersettings
import SectionDivider
import SectionItemView
import SectionTextFooter
import SectionView
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.chat.item.ItemAction
import chat.simplex.app.views.chatlist.UserProfilePickerItem
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.onboarding.CreateProfile
@Composable
fun UserProfilesView(m: ChatModel) {
val users by remember { derivedStateOf { m.users.map { it.user } } }
UserProfilesView(
users = users,
addUser = {
ModalManager.shared.showModalCloseable { close ->
CreateProfile(m, close)
}
},
activateUser = { user ->
withBGApi {
m.controller.changeActiveUser(user.userId)
}
},
removeUser = { user ->
val text = buildAnnotatedString {
append(generalGetString(R.string.users_delete_all_chats_deleted) + "\n\n" + generalGetString(R.string.users_delete_profile_for) + " ")
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
append(user.displayName)
}
append(":")
}
AlertManager.shared.showAlertDialogButtonsColumn(
title = generalGetString(R.string.users_delete_question),
text = text,
buttons = {
Column {
SectionItemView({
AlertManager.shared.hideAlert()
removeUser(m, user, users, true)
}) {
Text(stringResource(R.string.users_delete_with_connections), color = Color.Red)
}
SectionItemView({
AlertManager.shared.hideAlert()
removeUser(m, user, users, false)
}
) {
Text(stringResource(R.string.users_delete_data_only), color = Color.Red)
}
}
}
)
}
)
}
@Composable
private fun UserProfilesView(
users: List<User>,
addUser: () -> Unit,
activateUser: (User) -> Unit,
removeUser: (User) -> Unit,
) {
Column(
Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState())
.padding(bottom = DEFAULT_PADDING),
) {
AppBarTitle(stringResource(R.string.your_chat_profiles))
SectionView {
for (user in users) {
UserView(user, users, activateUser, removeUser)
SectionDivider()
}
SectionItemView(addUser, minHeight = 68.dp) {
Icon(Icons.Outlined.Add, stringResource(R.string.users_add), tint = MaterialTheme.colors.primary)
Spacer(Modifier.padding(horizontal = 4.dp))
Text(stringResource(R.string.users_add), color = MaterialTheme.colors.primary)
}
}
SectionTextFooter(stringResource(R.string.your_chat_profiles_stored_locally))
}
}
@Composable
private fun UserView(user: User, users: List<User>, activateUser: (User) -> Unit, removeUser: (User) -> Unit) {
var showDropdownMenu by remember { mutableStateOf(false) }
UserProfilePickerItem(user, onLongClick = { if (users.size > 1) showDropdownMenu = true }) {
activateUser(user)
}
Box(Modifier.padding(horizontal = 16.dp)) {
DropdownMenu(
expanded = showDropdownMenu,
onDismissRequest = { showDropdownMenu = false },
Modifier.width(220.dp)
) {
ItemAction(stringResource(R.string.delete_verb), Icons.Outlined.Delete, color = Color.Red, onClick = {
removeUser(user)
showDropdownMenu = false
}
)
}
}
}
private fun removeUser(m: ChatModel, user: User, users: List<User>, delSMPQueues: Boolean) {
if (users.size < 2) return
withBGApi {
try {
if (user.activeUser) {
val newActive = users.first { !it.activeUser }
m.controller.changeActiveUser_(newActive.userId)
}
m.controller.apiDeleteUser(user.userId, delSMPQueues)
m.users.removeAll { it.user.userId == user.userId }
} catch (e: Exception) {
AlertManager.shared.showAlertMsg(generalGetString(R.string.error_deleting_user), e.stackTraceToString())
}
}
}

View File

@@ -0,0 +1,30 @@
package chat.simplex.app.views.usersettings
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import chat.simplex.app.BuildConfig
import chat.simplex.app.R
import chat.simplex.app.model.CoreVersionInfo
import chat.simplex.app.ui.theme.DEFAULT_PADDING
import chat.simplex.app.views.helpers.AppBarTitle
@Composable
fun VersionInfoView(info: CoreVersionInfo) {
Column(
Modifier.padding(horizontal = DEFAULT_PADDING),
horizontalAlignment = Alignment.Start
) {
AppBarTitle(stringResource(R.string.app_version_title), false)
Text(String.format(stringResource(R.string.app_version_name), BuildConfig.VERSION_NAME))
Text(String.format(stringResource(R.string.app_version_code), BuildConfig.VERSION_CODE))
Text(String.format(stringResource(R.string.core_version), info.version))
Text(String.format(stringResource(R.string.core_build_timestamp), info.buildTimestamp))
val simplexmqCommit = if (info.simplexmqCommit.length >= 7) info.simplexmqCommit.substring(startIndex = 0, endIndex = 7) else info.simplexmqCommit
Text(String.format(stringResource(R.string.core_simplexmq_version), info.simplexmqVersion, simplexmqCommit))
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View File

@@ -0,0 +1,970 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="allow_voice_messages_only_if">Povolte hlasov├й zpr├бvy, pouze pokud je v├б┼б kontakt povol├н.</string>
<string name="allow_to_send_disappearing">Povolit odes├нl├бn├н mizej├нc├нch zpr├бv.</string>
<string name="allow_to_send_voice">Povolit odes├нl├бn├н hlasov├╜ch zpr├бv.</string>
<string name="v4_2_group_links_desc">Spr├бvci mohou vytv├б┼Щet odkazy pro p┼Щipojen├н ke skupin├бm.</string>
<string name="accept_contact_button">P┼Щijmout</string>
<string name="smp_servers_preset_add">P┼Щidejte p┼Щednastaven├й servery</string>
<string name="network_settings">Pokro─Нil├б nastaven├н s├нt─Ы</string>
<string name="accept">P┼Щijmout</string>
<string name="smp_servers_add">P┼Щidat serverтАж</string>
<string name="network_enable_socks_info">P┼Щistupovat k server┼пm p┼Щes SOCKS proxy na portu 9050\? P┼Щed povolen├нm t├йto mo┼╛nosti mus├н b├╜t spu┼бt─Ыna proxy.</string>
<string name="accept_feature">P┼Щijmout</string>
<string name="allow_your_contacts_to_send_disappearing_messages">Umo┼╛n─Ыte sv├╜m kontakt┼пm odes├нlat mizej├нc├н zpr├бvy.</string>
<string name="about_simplex_chat">O <xliff:g id="appNameFull">SimpleX Chat</xliff:g></string>
<string name="smp_servers_add_to_another_device">P┼Щidat do jin├йho za┼Щ├нzen├н</string>
<string name="accept_requests">P┼Щij├нmat ┼╛├бdosti</string>
<string name="allow_verb">Povolit</string>
<string name="allow_voice_messages_question">Povolit hlasov├й zpr├бvy\?</string>
<string name="about_simplex">O SimpleX</string>
<string name="a_plus_b">a + b</string>
<string name="accept_call_on_lock_screen">P┼Щijmout</string>
<string name="chat_item_ttl_day">1 dni</string>
<string name="group_member_role_admin">spr├бvce</string>
<string name="users_add">P┼Щidat profil</string>
<string name="users_delete_all_chats_deleted">V┼бechny chaty a zpr├бvy budou smaz├бny тАУ tuto akci nelze vr├бtit zp─Ыt!</string>
<string name="allow_disappearing_messages_only_if">Povolte mizej├нc├н zpr├бvy, pouze pokud to v├б┼б kontakt povol├н.</string>
<string name="v4_3_improved_server_configuration_desc">P┼Щidejte servery skenov├бn├нm QR k├│d┼п.</string>
<string name="chat_item_ttl_month">1 m─Ыs├нci</string>
<string name="chat_item_ttl_week">1 t├╜dnu</string>
<string name="callstatus_accepted">p┼Щijat├╜ hovor</string>
<string name="accept_contact_incognito_button">P┼Щijmout inkognito</string>
<string name="accept_connection_request__question">P┼Щijmout ┼╛├бdost o p┼Щipojen├н\?</string>
<string name="all_group_members_will_remain_connected">V┼бichni ─Нlenov├й skupiny z┼пstanou p┼Щipojeni.</string>
<string name="allow_irreversible_message_deletion_only_if">Povolte nevratn├й smaz├бn├н zpr├бvy pouze v p┼Щ├нpad─Ы, ┼╛e v├бm to v├б┼б kontakt dovol├н.</string>
<string name="allow_direct_messages">Povolit odes├нl├бn├н p┼Щ├нm├╜ch zpr├бv ─Нlen┼пm.</string>
<string name="allow_to_delete_messages">Povolit nevratn├й smaz├бn├н odeslan├╜ch zpr├бv.</string>
<string name="clear_chat_warning">V┼бechny zpr├бvy budou smaz├бny тАУ tuto akci nelze vr├бtit zp─Ыt! Zpr├бvy budou smaz├бny POUZE pro v├бs.</string>
<string name="allow_your_contacts_irreversibly_delete">Umo┼╛n─Ыte sv├╜m kontakt┼пm nevratn─Ы odstranit odeslan├й zpr├бvy.</string>
<string name="allow_your_contacts_to_send_voice_messages">Povolte sv├╜m kontakt┼пm odes├нl├бn├н hlasov├╜ch zpr├бv.</string>
<string name="button_create_group_link">Vytvo┼Щit odkaz</string>
<string name="delete_link_question">Smazat odkaz\?</string>
<string name="button_send_direct_message">Odeslat p┼Щ├нmou zpr├бvu</string>
<string name="member_info_section_title_member">─МLEN</string>
<string name="change_member_role_question">Zm─Ыnit roli ve skupin─Ы\?</string>
<string name="info_row_connection">P┼Щipojen├н</string>
<string name="conn_level_desc_indirect">nep┼Щ├нm├й (<xliff:g id="conn_level">%1$s</xliff:g>)</string>
<string name="conn_stats_section_title_servers">SERVERY</string>
<string name="receiving_via">P┼Щ├нj├нm├бno p┼Щez</string>
<string name="create_secret_group_title">Vytvo┼Щen├н tajn├й skupiny</string>
<string name="group_display_name_field">Zobrazen├н n├бzvu skupiny:</string>
<string name="group_full_name_field">├Ъpln├╜ n├бzev skupiny:</string>
<string name="group_main_profile_sent">V├б┼б profil v chatu bude zasl├бn ─Нlen┼пm skupiny</string>
<string name="group_profile_is_stored_on_members_devices">Profil skupiny je ulo┼╛en v za┼Щ├нzen├нch ─Нlen┼п, nikoli na serverech.</string>
<string name="network_options_save">Ulo┼╛it</string>
<string name="update_network_settings_question">Aktualizovat nastaven├н s├нt─Ы\?</string>
<string name="incognito">Inkognito</string>
<string name="incognito_random_profile">V├б┼б n├бhodn├╜ profil</string>
<string name="incognito_random_profile_description">Va┼бemu kontaktu bude zasl├бn n├бhodn├╜ profil</string>
<string name="save_color">Ulo┼╛it barvu</string>
<string name="reset_color">Obnoven├н barev</string>
<string name="color_primary">P┼Щ├нzvuk</string>
<string name="chat_preferences_you_allow">Povolujete</string>
<string name="chat_preferences_default">v├╜choz├н (%s)</string>
<string name="chat_preferences_yes">ano</string>
<string name="chat_preferences_no">ne</string>
<string name="chat_preferences_always">v┼╛dy</string>
<string name="set_group_preferences">Nastaven├н skupinov├╜ch p┼Щedvoleb</string>
<string name="your_preferences">Va┼бe preference</string>
<string name="timed_messages">Mizej├нc├н zpr├бvy</string>
<string name="feature_enabled_for_contact">povoleno pro kontakt</string>
<string name="feature_received_prohibited">p┼Щijat├й, zak├бzan├й</string>
<string name="both_you_and_your_contact_can_send_disappearing">Vy i v├б┼б kontakt m┼п┼╛ete pos├нlat mizej├нc├н zpr├бvy.</string>
<string name="only_your_contact_can_send_disappearing">Zmizel├й zpr├бvy m┼п┼╛e odes├нlat pouze v├б┼б kontakt.</string>
<string name="only_you_can_delete_messages">Nevratn─Ы mazat zpr├бvy m┼п┼╛ete pouze vy (v├б┼б kontakt je m┼п┼╛e ozna─Нit ke smaz├бn├н).</string>
<string name="message_deletion_prohibited">Nevratn├й maz├бn├н zpr├бv je v tomto chatu zak├бz├бno.</string>
<string name="prohibit_direct_messages">Zak├бzat odes├нl├бn├н p┼Щ├нm├╜ch zpr├бv ─Нlen┼пm.</string>
<string name="ttl_sec">%d sec</string>
<string name="ttl_s">%ds</string>
<string name="ttl_min">%d min</string>
<string name="ttl_hour">%d hodina</string>
<string name="feature_offered_item_with_param">offered %s: %2s</string>
<string name="v4_2_group_links">Odkazy na skupiny</string>
<string name="v4_3_voice_messages">Hlasov├й zpr├бvy</string>
<string name="v4_3_irreversible_message_deletion_desc">Va┼бe kontakty mohou povolit ├║pln├й vymaz├бn├н zpr├бv.</string>
<string name="v4_4_disappearing_messages">Zmizen├н zpr├бv</string>
<string name="v4_4_verify_connection_security_desc">Porovnejte bezpe─Нnostn├н k├│dy se sv├╜mi kontakty.</string>
<string name="app_name"><xliff:g id="appName">SimpleX</xliff:g></string>
<string name="thousand_abbreviation">k</string>
<string name="connect_via_contact_link">P┼Щipojit se p┼Щes kontaktn├н odkaz\?</string>
<string name="connect_via_invitation_link">P┼Щipojit se p┼Щes odkaz na pozv├бnku\?</string>
<string name="connect_via_group_link">P┼Щipojit se p┼Щes odkaz skupiny\?</string>
<string name="profile_will_be_sent_to_contact_sending_link">V├б┼б profil bude odesl├бn kontaktu, od kter├йho jste obdr┼╛eli tento odkaz.</string>
<string name="server_connected">p┼Щipojeno</string>
<string name="server_error">chyba</string>
<string name="server_connecting">p┼Щipojen├н</string>
<string name="trying_to_connect_to_server_to_receive_messages">Pokus o p┼Щipojen├н k serveru pou┼╛├нvan├йmu pro p┼Щ├нjem zpr├бv od tohoto kontaktu.</string>
<string name="deleted_description">smaz├бno</string>
<string name="invalid_chat">neplatn├╜ chat</string>
<string name="invalid_data">neplatn├й ├║daje</string>
<string name="connection_local_display_name">spojen├н <xliff:g id="connection ID" example="1">%1$d</xliff:g></string>
<string name="display_name_connection_established">spojen├н nav├бz├бno</string>
<string name="display_name_invited_to_connect">pozv├бnka k p┼Щipojen├н</string>
<string name="display_name_connecting">p┼Щipojen├нтАж</string>
<string name="description_you_shared_one_time_link">jste sd├нleli jednor├бzov├й spojen├н</string>
<string name="description_you_shared_one_time_link_incognito">sd├нleli jste jednor├бzov├╜ odkaz inkognito</string>
<string name="description_via_group_link">prost┼Щednictv├нm skupinov├йho odkazu</string>
<string name="description_via_contact_address_link">prost┼Щednictv├нm odkazu na kontaktn├н adresu</string>
<string name="description_via_contact_address_link_incognito">inkognito p┼Щes odkaz na kontaktn├н adresu</string>
<string name="description_via_one_time_link">prost┼Щednictv├нm jednor├бzov├йho odkazu</string>
<string name="description_via_one_time_link_incognito">inkognito p┼Щes jednor├бzov├╜ odkaz</string>
<string name="simplex_link_contact">SimpleX kontaktn├н adresa</string>
<string name="simplex_link_invitation">Jednor├бzov├б pozv├бnka SimpleX</string>
<string name="simplex_link_group">Skupinov├╜ odkaz SimpleX</string>
<string name="simplex_link_connection">prost┼Щednictv├нm <xliff:g id="serverHost" example="smp.simplex.im">%1$s</xliff:g></string>
<string name="simplex_link_mode">Odkazy na SimpleX</string>
<string name="simplex_link_mode_description">Popis</string>
<string name="simplex_link_mode_full">├Ъpln├╜ odkaz</string>
<string name="simplex_link_mode_browser">Prost┼Щednictv├нm prohl├н┼╛e─Нe</string>
<string name="simplex_link_mode_browser_warning">Otev┼Щen├н odkazu v prohl├н┼╛e─Нi m┼п┼╛e sn├н┼╛it soukrom├н a bezpe─Нnost p┼Щipojen├н. Ned┼пv─Ыryhodn├й odkazy SimpleX budou ─Нerven├й.</string>
<string name="error_saving_smp_servers">Chyba p┼Щi ukl├бd├бn├н server┼п SMP</string>
<string name="error_setting_network_config">Chyba p┼Щi aktualizaci konfigurace s├нt─Ы</string>
<string name="failed_to_parse_chat_title">Nepoda┼Щilo se na─Н├нst chat</string>
<string name="failed_to_parse_chats_title">Nepoda┼Щilo se na─Н├нst chaty</string>
<string name="contact_developers">Aktualizujte aplikaci a kontaktujte v├╜voj├б┼Щe.</string>
<string name="connection_timeout">─Мasov├╜ limit p┼Щipojen├н</string>
<string name="connection_error">Chyba p┼Щipojen├н</string>
<string name="network_error_desc">Zkontrolujte pros├нm sv├й s├н┼еov├й p┼Щipojen├н pomoc├н <xliff:g id="serverHost" example="smp.simplex.im">%1$s</xliff:g> a zkuste to znovu.</string>
<string name="error_sending_message">Chyba p┼Щi odes├нl├бn├н zpr├бvy</string>
<string name="error_adding_members">Chyba p┼Щi p┼Щid├бv├бn├н ─Нlen┼п</string>
<string name="contact_already_exists">Kontakt ji┼╛ existuje</string>
<string name="you_are_already_connected_to_vName_via_this_link">Jste ji┼╛ p┼Щipojeni k <xliff:g id="contactName" example="Alice">%1$s!</xliff:g>.</string>
<string name="invalid_connection_link">Neplatn├╜ odkaz na spojen├н</string>
<string name="error_accepting_contact_request">Chyba p┼Щ├нjmu po┼╛adavku od kontaktu</string>
<string name="error_changing_address">Chyba zm─Ыny adresy</string>
<string name="settings_notifications_mode_title">Slu┼╛ba oznamov├бn├н</string>
<string name="notifications_mode_service_desc">Slu┼╛ba na pozad├н je spu┼бt─Ыna v┼╛dy - ozn├бmen├н se zobraz├н, jakmile jsou zpr├бvy k dispozici.</string>
<string name="notification_preview_mode_message">Text zpr├бvy</string>
<string name="notification_preview_mode_contact">Jm├йno kontaktu</string>
<string name="notification_preview_mode_hidden">Skryt├й</string>
<string name="notification_preview_mode_message_desc">Zobrazit kontakt a zpr├бvu</string>
<string name="notification_contact_connected">P┼Щipojeno</string>
<string name="la_notice_title_simplex_lock">SimpleX Z├бmek</string>
<string name="auth_log_in_using_credential">P┼Щihlaste se pomoc├н sv├йho pov─Ы┼Щen├н</string>
<string name="auth_enable_simplex_lock">Zapnut├н z├бmku SimpleX</string>
<string name="reply_verb">Odpov─Ы─П</string>
<string name="share_verb">Sd├нlet</string>
<string name="copy_verb">Kop├нrovat</string>
<string name="icon_descr_received_msg_status_unread">nep┼Щe─Нteno</string>
<string name="personal_welcome">V├нtejte <xliff:g>%1$s</xliff:g>!</string>
<string name="welcome">V├нtejte!</string>
<string name="this_text_is_available_in_settings">Tento text je k dispozici v nastaven├н</string>
<string name="icon_descr_sent_msg_status_send_failed">odesl├бn├н se nezda┼Щilo</string>
<string name="share_file">Sd├нlet souborтАж</string>
<string name="attach">P┼Щipojit</string>
<string name="icon_descr_context">Kontextov├б ikona</string>
<string name="image_decoding_exception_desc">Obr├бzek nelze dek├│dovat. Zkuste pros├нm pou┼╛├нt jin├╜ obr├бzek nebo kontaktujte v├╜voj├б┼Щe.</string>
<string name="image_descr">Obr├бzek</string>
<string name="icon_descr_waiting_for_image">─Мek├бn├н na obr├бzek</string>
<string name="icon_descr_asked_to_receive">Po┼╛├бd├бno o p┼Щijet├н obr├бzku</string>
<string name="icon_descr_image_snd_complete">Obr├бzek odesl├бn</string>
<string name="waiting_for_image">─Мek├бme na obr├бzek</string>
<string name="image_will_be_received_when_contact_is_online">Obr├бzek bude p┼Щijat, a┼╛ bude v├б┼б kontakt online, vy─Нkejte pros├нm nebo se pod├нvejte pozd─Ыji!</string>
<string name="contact_sent_large_file">V├б┼б kontakt odeslal soubor, kter├╜ je v─Ыt┼б├н ne┼╛ aktu├бln─Ы podporovan├б maxim├бln├н velikost (<xliff:g id="maxFileSize">%1$s</xliff:g>).</string>
<string name="maximum_supported_file_size">V sou─Нasn├й dob─Ы je maxim├бln├н podporovan├б velikost souboru <xliff:g id="maxFileSize">%1$s</xliff:g>.</string>
<string name="error_saving_file">Chyba p┼Щi ukl├бd├бn├н souboru</string>
<string name="voice_message">Hlasov├б zpr├бva</string>
<string name="voice_message_send_text">Hlasov├б zpr├бvaтАж</string>
<string name="icon_descr_server_status_connected">P┼Щipojeno</string>
<string name="icon_descr_server_status_disconnected">Odpojeno</string>
<string name="icon_descr_server_status_error">Chyba</string>
<string name="switch_receiving_address_desc">Tato funkce je experiment├бln├н! Bude fungovat pouze v p┼Щ├нpad─Ы, ┼╛e druh├╜ klient m├б nainstalovanou verzi 4.2. Po dokon─Нen├н zm─Ыny adresy by se m─Ыla v konverzaci zobrazit zpr├бva - zkontrolujte, zda m┼п┼╛ete od tohoto kontaktu (nebo ─Нlena skupiny) st├бle p┼Щij├нmat zpr├бvy.</string>
<string name="switch_receiving_address_question">P┼Щepnout p┼Щij├нmac├н adresu\?</string>
<string name="send_verb">Poslat</string>
<string name="you_need_to_allow_to_send_voice">Abyste mohli odes├нlat hlasov├й zpr├бvy, mus├нte sv├йmu kontaktu odes├нl├бn├н hlasov├╜ch zpr├бv povolit.</string>
<string name="icon_descr_cancel_live_message">Zru┼бit ┼╛ivou zpr├бvu</string>
<string name="back">Zp─Ыt</string>
<string name="cancel_verb">Zru┼бit</string>
<string name="reset_verb">Obnovit</string>
<string name="ok">OK</string>
<string name="no_details">bez podrobnost├н</string>
<string name="add_contact">Jednor├бzov├╜ zvac├н odkaz</string>
<string name="copied">Zkop├нrov├бno do schr├бnky</string>
<string name="add_contact_or_create_group">Za─Н├нt novou konverzaci</string>
<string name="create_group">Vytvo┼Щit tajnou skupinu</string>
<string name="to_share_with_your_contact">(sd├нlet s kontaktem)</string>
<string name="only_stored_on_members_devices">(ulo┼╛eno pouze ─Нleny skupiny)</string>
<string name="toast_permission_denied">Opr├бvn─Ыn├н zam├нtnuto!</string>
<string name="use_camera_button">Pou┼╛├нt fotoapar├бt</string>
<string name="from_gallery_button">Z Galerie</string>
<string name="choose_file">Vybrat soubor</string>
<string name="to_start_a_new_chat_help_header">Pro zah├бjen├н nov├й konverzace</string>
<string name="chat_help_tap_button">Klepn─Ыte na tla─Н├нtko</string>
<string name="above_then_preposition_continuation">nad, potom:</string>
<string name="add_new_contact_to_create_one_time_QR_code"><b>P┼Щidat nov├╜ kontakt</b>: pro vytvo┼Щen├н jednor├бzov├йho QR k├│du pro v├б┼б kontakt.</string>
<string name="scan_QR_code_to_connect_to_contact_who_shows_QR_code"><b>Skenovat QR k├│d</b>: p┼Щipojen├н ke kontaktu, kter├╜ v├бm uk├б┼╛e QR k├│d.</string>
<string name="desktop_scan_QR_code_from_app_via_scan_QR_code">ЁЯТ╗ desktop: scan displayed QR code from the app, via <b>Scan QR code</b>.</string>
<string name="clear_chat_question">Vy─Нistit chat\?</string>
<string name="clear_verb">Vy─Нistit</string>
<string name="mark_read">Ozna─Нit jako p┼Щe─Нteno</string>
<string name="mark_unread">Ozna─Нit jako nep┼Щe─Нteno</string>
<string name="mute_chat">Ztlumit</string>
<string name="unmute_chat">Zru┼бit ztlumen├н</string>
<string name="you_invited_your_contact">Pozvali jste sv┼пj kontakt</string>
<string name="contact_you_shared_link_with_wont_be_able_to_connect">Kontakt, se kter├╜m jste tento odkaz sd├нleli, se NEBUDE moci p┼Щipojit!</string>
<string name="connection_you_accepted_will_be_cancelled">P┼Щipojen├н, kter├й jste p┼Щijali, bude zru┼бeno!</string>
<string name="icon_descr_help">help</string>
<string name="icon_descr_simplex_team"><xliff:g id="appName">SimpleX</xliff:g> T├╜m</string>
<string name="icon_descr_address"><xliff:g id="appName">SimpleX</xliff:g> Adresa</string>
<string name="you_will_be_connected_when_group_host_device_is_online">Ke skupin─Ы budete p┼Щipojeni, a┼╛ bude za┼Щ├нzen├н hostitele skupiny online, vy─Нkejte pros├нm nebo se pod├нvejte pozd─Ыji!</string>
<string name="you_will_be_connected_when_your_connection_request_is_accepted">Budete p┼Щipojeni, jakmile bude va┼бe ┼╛├бdost o p┼Щipojen├н p┼Щijata, vy─Нkejte pros├нm nebo se pod├нvejte pozd─Ыji!</string>
<string name="connection_request_sent">Po┼╛adavek na p┼Щipojen├н byl odesl├бn!</string>
<string name="your_profile_will_be_sent">V├б┼б profil v chatu bude odesl├бn va┼бemu kontaktu</string>
<string name="create_one_time_link">Vytvo┼Щte jednor├бzov├╜ odkaz na pozv├бnku</string>
<string name="one_time_link">Vytvo┼Щit jednor├бzov├╜ zvac├н odkaz</string>
<string name="security_code">Bezpe─Нnostn├н k├│d</string>
<string name="is_verified">%s je ov─Ы┼Щeno</string>
<string name="chat_console">Chat konzole</string>
<string name="smp_servers">SMP servery</string>
<string name="smp_servers_preset_address">P┼Щednastaven├б adresa serveru</string>
<string name="smp_servers_test_failed">Test serveru se nezda┼Щil!</string>
<string name="smp_servers_test_some_failed">N─Ыkter├й servery nepro┼бly testem:</string>
<string name="smp_servers_scan_qr">Naskenujte QR k├│d serveru</string>
<string name="smp_servers_enter_manually">Zadejte server ru─Нn─Ы</string>
<string name="smp_servers_invalid_address">Neplatn├б adresa serveru!</string>
<string name="smp_servers_check_address">Zkontrolujte adresu serveru a zkuste to znovu.</string>
<string name="smp_servers_delete_server">Smazat server</string>
<string name="contribute">P┼Щisp─Ыjte</string>
<string name="how_to">Jak</string>
<string name="how_to_use_your_servers">Jak pou┼╛├нvat servery</string>
<string name="your_ICE_servers">Va┼бe servery ICE</string>
<string name="configure_ICE_servers">Konfigurace server┼п ICE</string>
<string name="network_settings_title">Nastaven├н s├нt─Ы</string>
<string name="network_enable_socks">Pou┼╛├нt proxy server SOCKS\?</string>
<string name="network_disable_socks">Pou┼╛├нt p┼Щ├нm├й p┼Щipojen├н k internetu\?</string>
<string name="network_use_onion_hosts_no">Ne</string>
<string name="network_use_onion_hosts_no_desc_in_alert">Onion hostitel├й nebudou pou┼╛iti.</string>
<string name="network_session_mode_user">Chat profil</string>
<string name="network_session_mode_entity">P┼Щipojen├н</string>
<string name="core_simplexmq_version">simplexmq: v%s (%2s)</string>
<string name="create_address">Vytvo┼Щit adresu</string>
<string name="accept_automatically">Automaticky</string>
<string name="section_title_welcome_message">UV├НTAC├Н ZPR├БVA</string>
<string name="save_and_notify_group_members">Ulo┼╛it a upozornit ─Нleny skupiny</string>
<string name="exit_without_saving">Ukon─Нit bez ulo┼╛en├н</string>
<string name="the_messaging_and_app_platform_protecting_your_privacy_and_security">Platforma pro zas├нl├бn├н zpr├бv a aplikace chr├бn├нc├н va┼бe soukrom├н a bezpe─Нnost.</string>
<string name="create_profile">Vytvo┼Щit profil</string>
<string name="profile_is_only_shared_with_your_contacts">Profil je sd├нlen pouze s va┼бimi kontakty.</string>
<string name="display_name_cannot_contain_whitespace">Zobrazovan├й jm├йno nesm├н obsahovat pr├бzdn├й znaky.</string>
<string name="bold">tu─Нn─Ы</string>
<string name="callstatus_in_progress">prob├нhaj├нc├н hovor</string>
<string name="decentralized">Decentralizovan├б</string>
<string name="how_it_works">Jak to funguje</string>
<string name="how_simplex_works">Jak funguje <xliff:g id="appName">SimpleX</xliff:g></string>
<string name="only_client_devices_store_contacts_groups_e2e_encrypted_messages">Pouze klientsk├б za┼Щ├нzen├н ukl├бdaj├н u┼╛ivatelsk├й profily, kontakty, skupiny a zpr├бvy odes├нlan├й pomoc├н <b>2 vrstv├йho end-to-end ┼бifrov├бn├н</b>.</string>
<string name="onboarding_notifications_mode_title">Soukrom├б ozn├бmen├н</string>
<string name="onboarding_notifications_mode_periodic">Pravideln├й</string>
<string name="ignore">Ignorovat</string>
<string name="call_already_ended">Hovor ji┼╛ skon─Нil!</string>
<string name="icon_descr_video_call">videohovor</string>
<string name="icon_descr_audio_call">audio hovor</string>
<string name="settings_audio_video_calls">Audio a video hovory</string>
<string name="call_on_lock_screen">Hovory na uzam─Нen├й obrazovce:</string>
<string name="open_simplex_chat_to_accept_call">Otev┼Щete <xliff:g id="appNameFull">SimpleX Chat</xliff:g> pro p┼Щijet├н hovoru</string>
<string name="allow_accepting_calls_from_lock_screen">Povolte vol├бn├н ze zam─Нen├й obrazovky prost┼Щednictv├нm Nastaven├н.</string>
<string name="open_verb">Otev┼Щen├╜</string>
<string name="icon_descr_audio_on">Zvuk zapnut</string>
<string name="icon_descr_speaker_off">Reproduktor vypnut</string>
<string name="icon_descr_speaker_on">Reproduktor zapnut</string>
<string name="icon_descr_call_progress">Prob├нhaj├нc├н hovor</string>
<string name="auto_accept_images">Automaticky p┼Щij├нmat obr├бzky</string>
<string name="settings_section_title_settings">NASTAVEN├Н</string>
<string name="settings_section_title_help">N├БPOV─ЪDA</string>
<string name="settings_section_title_device">ZA┼Ш├НZEN├Н</string>
<string name="settings_section_title_chats">CHATS</string>
<string name="settings_experimental_features">Experiment├бln├н funkce</string>
<string name="settings_section_title_socks">SOCKS PROXY</string>
<string name="settings_section_title_icon">IKONA APLIKACE</string>
<string name="settings_section_title_themes">T├ЙMATA</string>
<string name="settings_section_title_messages">ZPR├БVY</string>
<string name="settings_section_title_calls">VOL├БN├Н</string>
<string name="export_database">Export datab├бze</string>
<string name="import_database">Import datab├бze</string>
<string name="delete_database">Smaz├бn├н datab├бze</string>
<string name="error_exporting_chat_database">Chyba p┼Щi exportu chat datab├бze</string>
<string name="import_database_confirmation">Import</string>
<string name="restart_the_app_to_use_imported_chat_database">Restartujte aplikaci, abyste mohli pou┼╛├нvat importovanou chat datab├бzi.</string>
<string name="delete_chat_profile_question">Smazat profil chatu\?</string>
<string name="delete_chat_profile_action_cannot_be_undone_warning">Tuto akci nelze vz├нt zp─Ыt! V├б┼б profil, kontakty, zpr├бvy a soubory budou nen├бvratn─Ы ztraceny.</string>
<string name="restart_the_app_to_create_a_new_chat_profile">Restartujte aplikaci a vytvo┼Щte nov├╜ chat profil.</string>
<string name="you_must_use_the_most_recent_version_of_database">Nejnov─Ыj┼б├н verzi chat datab├бze mus├нte pou┼╛├нvat POUZE v jednom za┼Щ├нzen├н, jinak se m┼п┼╛e st├бt, ┼╛e p┼Щestanete p┼Щij├нmat zpr├бvy od n─Ыkter├╜ch kontakt┼п.</string>
<string name="stop_chat_to_enable_database_actions">Zastavte chat a povolte akce s datab├бz├н.</string>
<string name="files_and_media_section">Soubory a m├йdia</string>
<string name="delete_files_and_media_question">Smazat soubory a m├йdia\?</string>
<string name="delete_messages">Odstranit zpr├бvy</string>
<string name="remove_passphrase_from_keychain">Odstranit p┼Щ├нstupovou fr├бzi z ├║lo┼╛i┼бt─Ы kl├н─Н┼п\?</string>
<string name="notifications_will_be_hidden">Ozn├бmen├н budou doru─Нov├бna pouze do doby, ne┼╛ se aplikace zastav├н!</string>
<string name="remove_passphrase">Odstranit</string>
<string name="update_database">Aktualizovat</string>
<string name="current_passphrase">Aktu├бln├н p┼Щ├нstupov├б fr├бzeтАж</string>
<string name="update_database_passphrase">Aktualizovat p┼Щ├нstupovou fr├бzi datab├бze</string>
<string name="enter_correct_current_passphrase">Zadejte pros├нm spr├бvnou aktu├бln├н p┼Щ├нstupovou fr├бzi.</string>
<string name="database_is_not_encrypted">V├б┼б chat datab├бze nen├н ┼бifrov├бna - nastavte p┼Щ├нstupovou fr├бzi pro jej├н ochranu.</string>
<string name="keychain_is_storing_securely">K bezpe─Нn├йmu ulo┼╛en├н heslov├й fr├бze slou┼╛├н ├║lo┼╛i┼бt─Ы kl├н─Н┼п Android - umo┼╛┼Иuje fungov├бn├н slu┼╛by ozn├бmen├н.</string>
<string name="impossible_to_recover_passphrase"><b>Upozorn─Ыn├н</b>: pokud p┼Щ├нstupovou fr├бzi ztrat├нte, NEBUDE mo┼╛n├й ji obnovit ani zm─Ыnit.</string>
<string name="database_will_be_encrypted_and_passphrase_stored">Datab├бze bude ┼бifrov├бna a p┼Щ├нstupov├б fr├бze bude ulo┼╛ena v ├║lo┼╛i┼бti kl├н─Н┼п.</string>
<string name="store_passphrase_securely">Heslo ulo┼╛te bezpe─Нn─Ы, v p┼Щ├нpad─Ы jeho ztr├бty jej NEBUDE mo┼╛n├й zm─Ыnit.</string>
<string name="file_with_path">Soubor: %s</string>
<string name="database_passphrase_is_required">Pro otev┼Щen├н chatu je vy┼╛adov├бna p┼Щ├нstupov├б fr├бze datab├бze.</string>
<string name="unknown_error">Nezn├бm├б chyba</string>
<string name="open_chat">Otev┼Щete chat</string>
<string name="restore_database">Obnovte z├бlohu datab├бze</string>
<string name="restore_database_alert_desc">Po obnoven├н z├бlohy datab├бze zadejte p┼Щedchoz├н heslo. Tuto akci nelze vr├бtit zp─Ыt.</string>
<string name="chat_is_stopped_indication">Chat je zastaven</string>
<string name="chat_archive_header">Chat se archivuje</string>
<string name="delete_chat_archive_question">Smazat chat archiv\?</string>
<string name="join_group_question">P┼Щipojit se ke skupin─Ы\?</string>
<string name="join_group_button">P┼Щipojte se na</string>
<string name="leave_group_button">Opustit</string>
<string name="icon_descr_add_members">Pozvat ─Нleny</string>
<string name="alert_title_no_group">Skupina nebyla nalezena!</string>
<string name="alert_title_cant_invite_contacts">Nelze pozvat kontakty!</string>
<string name="snd_group_event_changed_member_role">zm─Ыnili jste roli %s na %s</string>
<string name="snd_group_event_changed_role_for_yourself">zm─Ыnili jste svou roli na %s</string>
<string name="snd_group_event_member_deleted">odebrali jste <xliff:g id="member profile" example="alice (Alice)">%1$s</xliff:g></string>
<string name="snd_group_event_user_left">ode┼бli jste</string>
<string name="rcv_conn_event_switch_queue_phase_completed">zm─Ыnila se va┼бe adresa</string>
<string name="icon_descr_expand_role">Roz┼б├н┼Щit v├╜b─Ыr rol├н</string>
<string name="invite_prohibited">Nelze pozvat kontakt!</string>
<string name="failed_to_create_user_duplicate_desc">Ji┼╛ m├бte profil chatu se stejn├╜m n├бzvem. Zvolte pros├нm jin├й jm├йno.</string>
<string name="smp_server_test_create_queue">Vytvo┼Щit frontu</string>
<string name="smp_server_test_secure_queue">Zabezpe─Нit frontu</string>
<string name="service_notifications">Okam┼╛it├б ozn├бmen├н!</string>
<string name="it_can_disabled_via_settings_notifications_still_shown"><b>V nastaven├н ji lze vypnout</b> - ozn├бmen├н se budou zobrazovat pokud aplikace b─Ы┼╛├н.</string>
<string name="turn_off_battery_optimization">Chcete-li ji pou┼╛├нvat, <b>vypn─Ыte optimalizaci baterie</b> pro <xliff:g id="appName">SimpleX</xliff:g> v dal┼б├нm dialogu. V opa─Нn├йm p┼Щ├нpad─Ы budou ozn├бmen├н vypnuta.</string>
<string name="periodic_notifications_desc">Aplikace pravideln─Ы na─Н├нt├б nov├й zpr├бvy - denn─Ы spot┼Щebuje n─Ыkolik procent baterie. Aplikace nepou┼╛├нv├б push ozn├бmen├н - data ze za┼Щ├нzen├н nejsou odes├нl├бna na servery.</string>
<string name="enter_passphrase_notification_title">Je vy┼╛adov├бna p┼Щ├нstupov├б fr├бze</string>
<string name="enter_passphrase_notification_desc">Chcete-li dost├бvat ozn├бmen├н, zadejte p┼Щ├нstupovou fr├бzi do datab├бze.</string>
<string name="database_initialization_error_title">Nelze na─Н├нst datab├бzi</string>
<string name="hide_notification">Skr├╜t</string>
<string name="ntf_channel_calls">Vol├бn├н SimpleX Chat</string>
<string name="notification_preview_new_message">nov├б zpr├бva</string>
<string name="notification_new_contact_request">┼╜├бdost o nov├╜ kontakt</string>
<string name="delete_message_cannot_be_undone_warning">Zpr├бva bude smaz├бna - nelze to vz├нt zp─Ыt!</string>
<string name="confirm_verb">Potvrdit</string>
<string name="send_us_an_email">Po┼бlete n├бm e-mail</string>
<string name="chat_lock">Z├бmek SimpleX</string>
<string name="install_simplex_chat_for_terminal">Instalovat <xliff:g id="appNameFull">SimpleX Chat</xliff:g> termin├бl</string>
<string name="star_on_github">Hv─Ыzdu na GitHubu</string>
<string name="rate_the_app">Ohodno┼еte aplikaci</string>
<string name="your_SMP_servers">Va┼бe servery SMP</string>
<string name="network_disable_socks_info">Pokud potvrd├нte, budou servery zas├нlaj├нc├н zpr├бvy vid─Ыt va┼бi IP adresu a v├б┼б poskytovatel - ke kter├╜m server┼пm se p┼Щipojujete.</string>
<string name="colored">barevn├й</string>
<string name="secret">tajn├й</string>
<string name="callstatus_calling">vol├бn├нтАж</string>
<string name="callstate_connected">p┼Щipojen</string>
<string name="callstate_ended">ukon─Нen</string>
<string name="next_generation_of_private_messaging">P┼Щ├н┼бt├н generace soukrom├й komunikace</string>
<string name="people_can_connect_only_via_links_you_share">Lid├й se s v├бmi mohou spojit pouze prost┼Щednictv├нm odkazu, kter├╜ sd├нl├нte.</string>
<string name="integrity_msg_bad_hash">┼бpatn├╜ kontroln├н sou─Нet zpr├бvy</string>
<string name="chat_database_imported">Chat datab├бze importov├бna</string>
<string name="new_passphrase">Nov├б p┼Щ├нstupov├б fr├бzeтАж</string>
<string name="save_passphrase_and_open_chat">Ulo┼╛te heslo a otev┼Щete chat</string>
<string name="chat_archive_section">CHAT ARCHIV</string>
<string name="no_contacts_selected">Nebyl vybr├бn ┼╛├бdn├╜ kontakt</string>
<string name="invite_prohibited_description">Sna┼╛├нte se pozvat kontakt se kter├╜m jste sd├нleli inkognito profil, do skupiny ve kter├й pou┼╛├нv├бte sv┼пj hlavn├н profil</string>
<string name="info_row_group">Skupina</string>
<string name="network_options_revert">Vr├бtit</string>
<string name="updating_settings_will_reconnect_client_to_all_servers">Aktualizac├н nastaven├н se klient znovu p┼Щipoj├н ke v┼бem server┼пm.</string>
<string name="accept_feature_set_1_day">Nastavit 1 den</string>
<string name="connection_error_auth">Chyba spojen├н (AUTH)</string>
<string name="sender_may_have_deleted_the_connection_request">Odes├нlatel mo┼╛n├б smazal po┼╛adavek p┼Щipojen├н</string>
<string name="error_smp_test_server_auth">Server vy┼╛aduje autorizaci pro vytv├б┼Щen├н front, zkontrolujte heslo</string>
<string name="smp_server_test_delete_queue">Odstranit frontu</string>
<string name="delete_group_menu_action">Smazat</string>
<string name="delete_pending_connection__question">Smazat ─Нekaj├нc├н p┼Щipojen├н\?</string>
<string name="icon_descr_settings">Nastaven├н</string>
<string name="image_descr_qr_code">QR k├│d</string>
<string name="show_QR_code_for_your_contact_to_scan_from_the_app__multiline">V├б┼б kontakt m┼п┼╛e z aplikace naskenovat QR k├│d.</string>
<string name="if_you_cannot_meet_in_person_show_QR_in_video_call_or_via_another_channel">Pokud se nem┼п┼╛ete setkat osobn─Ы, uka┼╛te ve <b>videohovoru QR k├│d</b> nebo sd├нlejte odkaz.</string>
<string name="scan_code">Skenovat k├│d</string>
<string name="incorrect_code">Nespr├бvn├╜ bezpe─Нnostn├н k├│d!</string>
<string name="scan_code_from_contacts_app">Naskenujte bezpe─Нnostn├н k├│d z aplikace va┼бeho kontaktu.</string>
<string name="mark_code_verified">Ozna─Нit jako ov─Ы┼Щen├╜</string>
<string name="clear_verification">Jasn├й ov─Ы┼Щen├н</string>
<string name="to_verify_compare">Chcete-li ov─Ы┼Щit koncov├й ┼бifrov├бn├н u sv├йho kontaktu, porovnejte (nebo naskenujte) k├│d na sv├╜ch za┼Щ├нzen├нch.</string>
<string name="your_settings">Va┼бe nastaven├н</string>
<string name="your_simplex_contact_address">Va┼бe kontaktn├н adresa <xliff:g id="appName">SimpleX</xliff:g></string>
<string name="database_passphrase_and_export">Heslo datab├бze a export</string>
<string name="your_chat_profiles">Va┼бe chat profily</string>
<string name="chat_with_the_founder">Zaslat ot├бzky a n├бpady</string>
<string name="smp_servers_test_server">Test serveru</string>
<string name="enter_one_ICE_server_per_line">Servery ICE (jeden na ┼Щ├бdek)</string>
<string name="network_use_onion_hosts_required_desc">Pro p┼Щipojen├н budou vy┼╛adov├бni Onion hostitel├й.</string>
<string name="update_network_session_mode_question">Aktualizovat re┼╛im dopravn├н izolace\?</string>
<string name="app_version_code">Sestaven├н aplikace: %s</string>
<string name="you_can_share_your_address_anybody_will_be_able_to_connect">M┼п┼╛ete sd├нlet svou adresu jako odkaz nebo jako QR k├│d - kdokoli se k v├бm bude moci p┼Щipojit. O sv├й kontakty nep┼Щijdete, pokud ji pozd─Ыji sma┼╛ete.</string>
<string name="share_link">Sd├нlet odkaz</string>
<string name="delete_address">Smazat adresu</string>
<string name="full_name__field">Cel├й jm├йno:</string>
<string name="your_current_profile">V├б┼б sou─Нasn├╜ profil</string>
<string name="to_preserve_privacy_simplex_has_background_service_instead_of_push_notifications_it_uses_a_few_pc_battery">Pro zachov├бn├н va┼бeho soukrom├н pou┼╛├нv├б aplikace m├нsto push ozn├бmen├н <b><xliff:g id="appName">SimpleX</xliff:g> slu┼╛bu na pozad├н</b> - denn─Ы vyu┼╛ije n─Ыkolik procent baterie.</string>
<string name="periodic_notifications">Pravideln├б ozn├бmen├н</string>
<string name="simplex_service_notification_title"><xliff:g id="appNameFull">SimpleX Chat</xliff:g> slu┼╛ba</string>
<string name="simplex_service_notification_text">P┼Щ├нjem zpr├бvтАж</string>
<string name="ntf_channel_messages">Zpr├бvy SimpleX Chat</string>
<string name="settings_notification_preview_mode_title">Zobrazen├н n├бhledu</string>
<string name="settings_notification_preview_title">N├бhled ozn├бmen├н</string>
<string name="notifications_mode_off">Spust├н se p┼Щi otev┼Щen├н aplikace</string>
<string name="notifications_mode_periodic">Spou┼бt├н se pravideln─Ы</string>
<string name="notifications_mode_service">V┼╛dy zapnuto</string>
<string name="notifications_mode_periodic_desc">Ka┼╛d├╜ch 10 minut kontroluje nov├й zpr├бvy, po dobu a┼╛ 1 minuty</string>
<string name="notification_preview_mode_contact_desc">Zobrazit pouze kontakt</string>
<string name="notification_preview_somebody">Skryt├╜ kontakt:</string>
<string name="la_notice_turn_on">Zapn─Ыte funkci</string>
<string name="auth_simplex_lock_turned_on">Zapnut├╜ z├бmek SimpleX Lock</string>
<string name="auth_unlock">Odemknout</string>
<string name="auth_device_authentication_is_not_enabled_you_can_turn_on_in_settings_once_enabled">Ov─Ы┼Щov├бn├н za┼Щ├нzen├н nen├н povoleno. Jakmile povol├нte ov─Ы┼Щov├бn├н za┼Щ├нzen├н, m┼п┼╛ete z├бmek SimpleX Lock zapnout prost┼Щednictv├нm Nastaven├н.</string>
<string name="auth_device_authentication_is_disabled_turning_off">Ov─Ы┼Щov├бn├н za┼Щ├нzen├н je zak├бz├бno. Z├бmek SimpleX je vypnut.</string>
<string name="edit_verb">Upravit</string>
<string name="delete_verb">Smazat</string>
<string name="for_everybody">Pro v┼бechny</string>
<string name="icon_descr_sent_msg_status_sent">odesl├бno</string>
<string name="contact_connection_pending">p┼Щipojov├бn├нтАж</string>
<string name="images_limit_desc">Sou─Нasn─Ы lze odeslat pouze 10 obr├бzk┼п</string>
<string name="image_decoding_exception_title">Chyba dek├│dov├бn├н</string>
<string name="image_saved">Obr├бzek ulo┼╛en do galerie</string>
<string name="icon_descr_file">Soubor</string>
<string name="large_file">Velk├╜ soubor!</string>
<string name="file_will_be_received_when_contact_is_online">Soubor bude p┼Щijat, a┼╛ bude v├б┼б kontakt online, vy─Нkejte pros├нm, nebo se pod├нvejte pozd─Ыji!</string>
<string name="file_saved">Soubor ulo┼╛en</string>
<string name="file_not_found">Soubor nenalezen</string>
<string name="voice_message_with_duration">Hlasov├б zpr├бva (<xliff:g id="duration">%1$s</xliff:g>)</string>
<string name="delete_contact_all_messages_deleted_cannot_undo_warning">Kontakt a v┼бechny zpr├бvy budou smaz├бny - nelze to vz├нt zp─Ыt!</string>
<string name="button_delete_contact">Smazat kontakt</string>
<string name="text_field_set_contact_placeholder">Nastaven├н jm├йna kontaktuтАж</string>
<string name="view_security_code">Zobrazen├н bezpe─Нnostn├нho k├│du</string>
<string name="icon_descr_record_voice_message">Nahr├бt hlasovou zpr├бvu</string>
<string name="voice_messages_prohibited">Hlasov├й zpr├бvy jsou zak├бz├бny!</string>
<string name="ask_your_contact_to_enable_voice">Pros├нm, po┼╛├бdejte kontaktn├н osobu, aby umo┼╛nila odes├нl├бn├н hlasov├╜ch zpr├бv.</string>
<string name="send_live_message_desc">Poslat ┼╛ivou zpr├бvu - zpr├бva se bude aktualizovat pro p┼Щ├нjemce b─Ыhem psan├н.</string>
<string name="share_one_time_link">Vytvo┼Щte jednor├бzov├╜ odkaz na pozv├бnku</string>
<string name="scan_QR_code">Skenovat QR k├│d</string>
<string name="connect_via_link_or_qr_from_clipboard_or_in_person">( skenovat nebo vlo┼╛it ze schr├бnky)</string>
<string name="edit_image">Upravit obr├бzek</string>
<string name="delete_image">Smazat obr├бzek</string>
<string name="callstatus_error">chyba vol├бn├н</string>
<string name="opensource_protocol_and_code_anybody_can_run_servers">Protokol a k├│d s otev┼Щen├╜m zdrojov├╜m k├│dem - servery m┼п┼╛e provozovat kdokoli.</string>
<string name="create_your_profile">Vytvo┼Щte si sv┼пj profil</string>
<string name="make_private_connection">Vytvo┼Щte si soukrom├й p┼Щipojen├н</string>
<string name="encrypted_video_call">Videohovor ┼бifrovan├╜ e2e</string>
<string name="encrypted_audio_call">e2e ┼бifrovan├╜ audio hovor</string>
<string name="status_contact_has_e2e_encryption">kontakt m├б ┼бifrov├бn├н e2e</string>
<string name="status_contact_has_no_e2e_encryption">kontakt nem├б ┼бifrov├бn├н e2e</string>
<string name="call_connection_peer_to_peer">peer-to-peer</string>
<string name="call_connection_via_relay">p┼Щes relay</string>
<string name="icon_descr_hang_up">Zav─Ыsit</string>
<string name="icon_descr_video_off">Video vypnuto</string>
<string name="icon_descr_video_on">Video zapnuto</string>
<string name="icon_descr_audio_off">Zvuk vypnut├╜</string>
<string name="integrity_msg_bad_id">┼бpatn├й ID zpr├бvy</string>
<string name="integrity_msg_duplicate">duplicitn├н zpr├бva</string>
<string name="alert_title_skipped_messages">P┼Щesko─Нen├й zpr├бvy</string>
<string name="privacy_and_security">Ochrana osobn├нch ├║daj┼п a zabezpe─Нen├н</string>
<string name="your_privacy">Va┼бe soukrom├н</string>
<string name="protect_app_screen">Ochrana obrazovky aplikace</string>
<string name="send_link_previews">Odes├нl├бn├н n├бhled┼п odkaz┼п</string>
<string name="full_backup">Z├бlohov├бn├н dat aplikace</string>
<string name="confirm_new_passphrase">Potvrdit novou heslovou fr├бziтАж</string>
<string name="error_with_info">Chyba: %s</string>
<string name="leave_group_question">Opustit skupinu\?</string>
<string name="icon_descr_group_inactive">Skupina je neaktivn├н</string>
<string name="rcv_group_event_member_left">vlevo</string>
<string name="clear_contacts_selection_button">Vy─Нistit</string>
<string name="switch_verb">P┼Щepnout</string>
<string name="member_role_will_be_changed_with_notification">Role bude zm─Ыn─Ыna na \"%s\". V┼бichni ve skupin─Ы budou informov├бni.</string>
<string name="error_removing_member">Chyba odeb├нr├бn├н ─Нlena</string>
<string name="error_saving_group_profile">Chyba ukl├бd├бn├н profilu skupiny</string>
<string name="network_option_seconds_label">vte┼Щin</string>
<string name="incognito_info_allows">Umo┼╛┼Иuje m├нt v jednom chat profilu mnoho anonymn├нch spojen├н bez sd├нlen├н ├║daj┼п mezi nimi.</string>
<string name="incognito_info_share">Pokud s n─Ыk├╜m sd├нl├нte inkognito profil, bude pou┼╛it pro skupiny, do kter├╜ch v├бs pozve.</string>
<string name="theme_system">Syst├йm</string>
<string name="voice_messages">Hlasov├й zpr├бvy</string>
<string name="both_you_and_your_contacts_can_delete">Vy i v├б┼б kontakt m┼п┼╛ete nevratn─Ы mazat odeslan├й zpr├бvy.</string>
<string name="ttl_m">%dm</string>
<string name="ttl_mth">%dmth</string>
<string name="ttl_hours">%d hodin</string>
<string name="ttl_h">%dh</string>
<string name="ttl_d">%dd</string>
<string name="v4_2_security_assessment">Posouzen├н bezpe─Нnosti</string>
<string name="v4_2_security_assessment_desc">Bezpe─Нnost SimpleX Chat byla prov─Ы┼Щena spole─Нnost├н Trail of Bits.</string>
<string name="v4_3_irreversible_message_deletion">Nevratn├й maz├бn├н zpr├бv</string>
<string name="v4_3_improved_server_configuration">Vylep┼бena konfigurace serveru</string>
<string name="v4_3_improved_privacy_and_security">Vylep┼бena ochrana soukrom├н a zabezpe─Нen├н</string>
<string name="v4_3_improved_privacy_and_security_desc">Skryta obrazovka aplikace v posledn├нch aplikac├нch.</string>
<string name="v4_4_disappearing_messages_desc">Odeslan├й zpr├бvy se po uplynut├н nastaven├й doby odstran├н.</string>
<string name="v4_4_live_messages">┼╜iv├й zpr├бvy</string>
<string name="v4_4_live_messages_desc">P┼Щ├нjemci uvid├н aktualizaci b─Ыhem psan├н.</string>
<string name="v4_4_verify_connection_security">Ov─Ы┼Щen├н zabezpe─Нen├н p┼Щipojen├н</string>
<string name="v4_4_french_interface">Francouzsk├й rozhran├н</string>
<string name="v4_4_french_interface_descr">D├нky u┼╛ivatel┼пm - p┼Щekl├бdejte prost┼Щednictv├нm Weblate!</string>
<string name="v4_5_multiple_chat_profiles">V├нce chat profil┼п</string>
<string name="v4_5_multiple_chat_profiles_descr">R┼пzn├б jm├йna, avata┼Щi a izolace p┼Щenosu.</string>
<string name="v4_5_message_draft">N├бvrh zpr├бv</string>
<string name="v4_5_message_draft_descr">Zachov├бn├н posledn├нho n├бvrhu zpr├бvy s p┼Щ├нlohami.</string>
<string name="v4_5_transport_isolation">Izolace p┼Щenosu</string>
<string name="v4_5_transport_isolation_descr">Podle chat profilu (v├╜choz├н) nebo podle p┼Щipojen├н (BETA).</string>
<string name="you_will_join_group">P┼Щipoj├нte se ke skupin─Ы, na kterou odkazuje tento odkaz, a spoj├нte se s jej├нmi ─Нleny.</string>
<string name="connect_via_link_verb">P┼Щipojen├н</string>
<string name="connected_to_server_to_receive_messages_from_contact">Jste p┼Щipojeni k serveru, kter├╜ se pou┼╛├нv├б k p┼Щij├нm├бn├н zpr├бv od tohoto kontaktu.</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Pokou┼б├нte se p┼Щipojit k serveru pou┼╛├нvan├йm pro p┼Щ├нjem zpr├бv od tohoto kontaktu (chyba: <xliff:g id="errorMsg">%1$s</xliff:g>).</string>
<string name="marked_deleted_description">ozna─Нit jako smaz├бno</string>
<string name="sending_files_not_yet_supported">Odes├нl├бn├н soubor┼п zat├нm nen├н podporov├бno</string>
<string name="receiving_files_not_yet_supported">P┼Щij├нm├бn├н soubor┼п zat├нm nen├н podporov├бno</string>
<string name="sender_you_pronoun">ty</string>
<string name="unknown_message_format">nezn├бm├╜ form├бt zpr├бvy</string>
<string name="invalid_message_format">neplatn├╜ form├бt zpr├бvy</string>
<string name="live">┼╜IV─Ъ</string>
<string name="description_via_group_link_incognito">inkognito p┼Щes skupinov├╜ odkaz</string>
<string name="ensure_smp_server_address_are_correct_format_and_unique">Ujist─Ыte se, ┼╛e adresy server┼п SMP jsou ve spr├бvn├йm form├бtu, odd─Ыlen├й na ┼Щ├бdc├нch a nejsou duplicitn├н.</string>
<string name="failed_to_create_user_title">Chyba p┼Щi vytv├б┼Щen├н profilu!</string>
<string name="failed_to_create_user_duplicate_title">Duplicitn├н zobrazovan├й jm├йno!</string>
<string name="failed_to_active_user_title">Chyba p┼Щi p┼Щep├нn├бn├н profilu!</string>
<string name="error_joining_group">Chyba p┼Щi p┼Щipojov├бn├н ke skupin─Ы</string>
<string name="cannot_receive_file">Nelze p┼Щijmout soubor</string>
<string name="sender_cancelled_file_transfer">Odes├нlatel zru┼бil p┼Щenos souboru.</string>
<string name="error_receiving_file">Chyba p┼Щ├нjmu souboru</string>
<string name="error_creating_address">Chyba vytv├б┼Щen├н adresy</string>
<string name="please_check_correct_link_and_maybe_ask_for_a_new_one">Zkontrolujte, zda jste pou┼╛ili spr├бvn├╜ odkaz, nebo po┼╛├бdejte kontakt, aby v├бm poslal jin├╜.</string>
<string name="connection_error_auth_desc">Pokud v├б┼б kontakt neodstranil p┼Щipojen├н nebo tento odkaz ji┼╛ nebyl pou┼╛it, m┼п┼╛e se jednat o chybu тАУ nahlaste ji.
\nChcete-li se p┼Щipojit, po┼╛├бdejte sv┼пj kontakt o vytvo┼Щen├н dal┼б├нho odkazu na p┼Щipojen├н a zkontrolujte, zda m├бte stabiln├н p┼Щipojen├н k s├нti.</string>
<string name="error_deleting_contact">Chyba maz├бn├н kontaktu</string>
<string name="error_deleting_group">Chyba maz├бn├н skupiny</string>
<string name="error_deleting_contact_request">Chyba maz├бn├н ┼╛├бdosti kontaktu</string>
<string name="error_deleting_pending_contact_connection">Chyba maz├бn├н prob├нhaj├нc├нho p┼Щipojen├н kontaktu</string>
<string name="error_smp_test_failed_at_step">Test selhal v kroku %s.</string>
<string name="error_smp_test_certificate">Je mo┼╛n├й, ┼╛e otisk certifik├бtu v adrese serveru je nespr├бvn├╜.</string>
<string name="smp_server_test_connect">P┼Щipojit</string>
<string name="smp_server_test_disconnect">Odpojit</string>
<string name="error_deleting_user">Chyba maz├бn├н u┼╛ivatelsk├йho profilu</string>
<string name="icon_descr_instant_notifications">Okam┼╛it├б ozn├бmen├н</string>
<string name="service_notifications_disabled">Okam┼╛it├б ozn├бmen├н jsou vypnut├б!</string>
<string name="turning_off_service_and_periodic">Je aktivn├н optimalizace baterie, kter├б vyp├нn├б slu┼╛bu na pozad├н a pravideln├й po┼╛adavky na nov├й zpr├бvy. M┼п┼╛ete ji znovu povolit prost┼Щednictv├нm nastaven├н.</string>
<string name="periodic_notifications_disabled">Pravideln├б ozn├бmen├н jsou vypnuta!</string>
<string name="database_initialization_error_desc">Datab├бze nefunguje spr├бvn─Ы. Klepnut├нm se dozv├нte v├нce</string>
<string name="notifications_mode_off_desc">Aplikace m┼п┼╛e p┼Щij├нmat ozn├бmen├н pouze p┼Щi sv├йm b─Ыhu, ┼╛├бdn├б slu┼╛ba na pozad├н nebude spu┼бt─Ыna</string>
<string name="notification_display_mode_hidden_desc">Skr├╜t kontakt a zpr├бvu</string>
<string name="la_notice_to_protect_your_information_turn_on_simplex_lock_you_will_be_prompted_to_complete_authentication_before_this_feature_is_enabled">Chcete-li chr├бnit sv├й informace, zapn─Ыte z├бmek SimpleX Lock.
\nP┼Щed zapnut├нm t├йto funkce budete vyzv├бni k dokon─Нen├н ov─Ы┼Щen├н.</string>
<string name="auth_you_will_be_required_to_authenticate_when_you_start_or_resume">P┼Щi spu┼бt─Ыn├н nebo obnoven├н aplikace po 30 sekund├бch na pozad├н budete vyzv├бni k ov─Ы┼Щen├н.</string>
<string name="auth_disable_simplex_lock">Vypnout z├бmek SimpleX</string>
<string name="auth_confirm_credential">Potvr─Пte sv├й pov─Ы┼Щen├н</string>
<string name="auth_unavailable">Ov─Ы┼Щov├бn├н nen├н k dispozici</string>
<string name="auth_stop_chat">Zastavit chat</string>
<string name="auth_open_chat_console">Otev┼Щete chat konzoli</string>
<string name="message_delivery_error_title">Chyba doru─Нen├н zpr├бvy</string>
<string name="message_delivery_error_desc">Tento kontakt s nejv─Ыt┼б├н pravd─Ыpodobnost├н smazal spojen├н s v├бmi.</string>
<string name="save_verb">Ulo┼╛it</string>
<string name="reveal_verb">Odhalit</string>
<string name="hide_verb">Skr├╜t</string>
<string name="delete_message__question">Smazat zpr├бvu\?</string>
<string name="delete_message_mark_deleted_warning">Zpr├бva bude ozna─Нena ke smaz├бn├н. P┼Щ├нjemce (p┼Щ├нjemci) bude moci tuto zpr├бvu odhalit.</string>
<string name="for_me_only">Smazat pro m─Ы</string>
<string name="icon_descr_edited">upraveno</string>
<string name="icon_descr_sent_msg_status_unauthorized_send">neautorizovan├й odesl├бn├н</string>
<string name="group_preview_you_are_invited">jste pozv├бni do skupiny</string>
<string name="group_preview_join_as">p┼Щipojit jako %s</string>
<string name="group_connection_pending">p┼Щipojuje seтАж</string>
<string name="tap_to_start_new_chat">Za─Нn─Ыte nov├╜ chat</string>
<string name="chat_with_developers">Chat s v├╜voj├б┼Щi</string>
<string name="you_have_no_chats">Nem├бte ┼╛├бdn├й konverzace</string>
<string name="icon_descr_cancel_image_preview">Zru┼бit n├бhled obr├бzku</string>
<string name="share_message">Sd├нlet zpr├бvuтАж</string>
<string name="share_image">Sd├нlet obr├бzekтАж</string>
<string name="icon_descr_cancel_file_preview">Zru┼бit n├бhled souboru</string>
<string name="images_limit_title">P┼Щ├нli┼б mnoho obr├бzk┼п!</string>
<string name="waiting_for_file">─Мek├бn├н na soubor</string>
<string name="notifications">Ozn├бmen├н</string>
<string name="delete_contact_question">Smazat kontakt\?</string>
<string name="icon_descr_server_status_pending">─Мek├б na vy┼Щ├нzen├н</string>
<string name="verify_security_code">Ov─Ы┼Щit bezpe─Нnostn├н k├│d</string>
<string name="icon_descr_send_message">Odeslat zpr├бvu</string>
<string name="only_group_owners_can_enable_voice">Pouze majitel├й skupin mohou povolit zas├нl├бn├н hlasov├╜ch zpr├бv.</string>
<string name="send_live_message">Odeslat ┼╛ivou zpr├бvu</string>
<string name="live_message">┼╜iv├б zpr├бva!</string>
<string name="connect_via_link_or_qr">P┼Щipojit se prost┼Щednictv├нm odkazu / QR k├│du</string>
<string name="thank_you_for_installing_simplex">D─Ыkujeme za instalaci <xliff:g id="appNameFull">SimpleX Chat</xliff:g>!</string>
<string name="you_can_connect_to_simplex_chat_founder">M┼п┼╛ete se <font color="#0088ff">p┼Щipojit k <xliff:g id="appNameFull">SimpleX Chat</xliff:g> v├╜voj├б┼Щ┼пm a polo┼╛it jim p┼Щ├нpadn├й dotazy a z├нskat aktualizace</font>.</string>
<string name="to_connect_via_link_title">P┼Щipojen├н prost┼Щednictv├нm odkazu</string>
<string name="if_you_received_simplex_invitation_link_you_can_open_in_browser">Pokud jste dostali <xliff:g id="appName">SimpleX Chat</xliff:g> zvac├н odkaz, M┼п┼╛ete ho otev┼Щ├нt v prohl├н┼╛e─Нi:</string>
<string name="if_you_choose_to_reject_the_sender_will_not_be_notified">Pokud zvol├нte odm├нtnut├н, odes├нlatel NEBUDE upozorn─Ыn.</string>
<string name="mobile_tap_open_in_mobile_app_then_tap_connect_in_app">ЁЯУ▒ mobiln├н telefon: tap <b>Otev┼Щete v mobiln├н aplikaci</b>, potom klikn─Ыte <b>P┼Щipojit</b>.</string>
<string name="reject_contact_button">Odm├нtnout</string>
<string name="clear_chat_button">Smazat chat</string>
<string name="clear_chat_menu_action">Vy─Нistit</string>
<string name="delete_contact_menu_action">Smazat</string>
<string name="set_contact_name">Nastavit jm├йno kontaktu</string>
<string name="you_accepted_connection">P┼Щijali jste spojen├н</string>
<string name="alert_text_connection_pending_they_need_to_be_online_can_delete_and_retry">Aby se p┼Щipojen├н dokon─Нilo, mus├н b├╜t v├б┼б kontakt online.
\nToto p┼Щipojen├н m┼п┼╛ete zru┼бit a kontakt odebrat (a zkusit to pozd─Ыji s nov├╜m odkazem).</string>
<string name="contact_wants_to_connect_with_you">Chce se s v├бmi spojit!</string>
<string name="icon_descr_profile_image_placeholder">Z├бstupn├╜ symbol profilov├йho obr├бzku</string>
<string name="image_descr_profile_image">profilov├╜ obr├бzek</string>
<string name="icon_descr_close_button">Tla─Н├нtko Zav┼Щ├нt</string>
<string name="alert_title_contact_connection_pending">Kontakt je┼бt─Ы nen├н p┼Щipojen!</string>
<string name="image_descr_link_preview">n├бhledov├╜ obr├бzek odkazu</string>
<string name="icon_descr_cancel_link_preview">Zru┼бit n├бhled odkazu</string>
<string name="image_descr_simplex_logo"><xliff:g id="appName">SimpleX</xliff:g> Logo</string>
<string name="icon_descr_email">E-mail</string>
<string name="icon_descr_more_button">V├нce</string>
<string name="show_QR_code">Zobrazit QR k├│d</string>
<string name="invalid_QR_code">Neplatn├╜ QR k├│d</string>
<string name="this_QR_code_is_not_a_link">Tento QR k├│d nen├н odkaz!</string>
<string name="invalid_contact_link">Neplatn├╜ odkaz!</string>
<string name="this_link_is_not_a_valid_connection_link">Tento odkaz nen├н platn├╜m odkazem pro p┼Щipojen├н!</string>
<string name="if_you_cannot_meet_in_person_scan_QR_in_video_call_or_ask_for_invitation_link">Pokud se nem┼п┼╛ete setkat osobn─Ы, m┼п┼╛ete <b>skenovat QR k├│d ve videohovoru</b> nebo m┼п┼╛e v├б┼б kontakt sd├нlet pozv├бnku.</string>
<string name="connect_via_link">P┼Щipojte se prost┼Щednictv├нm odkazu</string>
<string name="connect_button">P┼Щipojit</string>
<string name="paste_button">Vlo┼╛it</string>
<string name="this_string_is_not_a_connection_link">Tento ┼Щet─Ыzec nen├н odkazem na p┼Щipojen├н!</string>
<string name="you_can_also_connect_by_clicking_the_link">M┼п┼╛ete se tak├й p┼Щipojit kliknut├нm na odkaz. Pokud se otev┼Щe v prohl├н┼╛e─Нi, klikn─Ыte na tla─Н├нtko <b>Otev┼Щ├нt v mobiln├н aplikaci</b>.</string>
<string name="is_not_verified">%s nen├н ov─Ы┼Щeno</string>
<string name="how_to_use_simplex_chat">N├бvod k pou┼╛it├н</string>
<string name="markdown_help">N├бpov─Ыda k markdown</string>
<string name="smp_servers_save">Ulo┼╛it servery</string>
<string name="markdown_in_messages">Markdown ve zpr├бv├бch</string>
<string name="smp_servers_test_servers">Testovat servery</string>
<string name="smp_servers_preset_server">P┼Щednastaven├╜ server</string>
<string name="smp_servers_your_server">V├б┼б server</string>
<string name="smp_servers_your_server_address">Adresa va┼бeho serveru</string>
<string name="smp_servers_use_server">Pou┼╛├нt server</string>
<string name="smp_servers_use_server_for_new_conn">Pou┼╛├нt pro nov├б p┼Щipojen├н</string>
<string name="smp_servers_per_user">Servery pro nov├б p┼Щipojen├н va┼бeho aktu├бln├нho chat profilu.</string>
<string name="use_simplex_chat_servers__question">Pou┼╛├нt <xliff:g id="appNameFull">SimpleX Chat</xliff:g> servery\?</string>
<string name="using_simplex_chat_servers">Pou┼╛it├н <xliff:g id="appNameFull">SimpleX Chat</xliff:g> server┼п.</string>
<string name="saved_ICE_servers_will_be_removed">Ulo┼╛en├й servery WebRTC ICE budou odstran─Ыny.</string>
<string name="error_saving_ICE_servers">Chyba p┼Щi ukl├бd├бn├н server┼п ICE</string>
<string name="ensure_ICE_server_address_are_correct_format_and_unique">Ujist─Ыte se, ┼╛e adresy server┼п WebRTC ICE jsou ve spr├бvn├йm form├бtu, odd─Ыlen├й na ┼Щ├бdc├нch a nejsou duplicitn├н.</string>
<string name="save_servers_button">Ulo┼╛it</string>
<string name="network_and_servers">S├н┼е a servery</string>
<string name="network_socks_toggle">Pou┼╛├нt proxy server SOCKS (port 9050)</string>
<string name="update_onion_hosts_settings_question">Aktualizovat nastaven├н hostitel┼п .onion\?</string>
<string name="network_use_onion_hosts">Pou┼╛├нt hostitele .onion</string>
<string name="network_use_onion_hosts_prefer">Kdy┼╛ bude dostupn├╜</string>
<string name="network_use_onion_hosts_required">Povinn├й</string>
<string name="network_use_onion_hosts_prefer_desc">Onion hostitel├й budou pou┼╛iti, pokud jsou k dispozici.</string>
<string name="network_use_onion_hosts_no_desc">Onion hostitel├й nebudou pou┼╛iti.</string>
<string name="network_use_onion_hosts_prefer_desc_in_alert">Onion hostitel├й budou pou┼╛iti, pokud jsou k dispozici.</string>
<string name="network_use_onion_hosts_required_desc_in_alert">Pro p┼Щipojen├н budou vy┼╛adov├бni Onion hostitel├й.</string>
<string name="network_session_mode_transport_isolation">Izolace p┼Щenosu</string>
<string name="network_session_mode_user_description">A separate TCP connection (and SOCKS credential) will be used <b>for each chat profile you have in the app</b>.</string>
<string name="network_session_mode_entity_description">Odd─Ыlit TCP p┼Щipojen├н (a SOCKS pov─Ы┼Щen├н) bude pou┼╛ito <b>pro v┼бechny kontakty a ─Нleny skupin</b>.
\n<b>Upozorn─Ыn├н</b>: Pokud m├бte mnoho p┼Щipojen├н, m┼п┼╛e b├╜t spot┼Щeba baterie a provoz podstatn─Ы vy┼б┼б├н a n─Ыkter├б p┼Щipojen├н mohou selhat.</string>
<string name="appearance_settings">Vzhled</string>
<string name="app_version_title">Verze aplikace</string>
<string name="app_version_name">Verze aplikace: v%s</string>
<string name="core_version">Verze j├бdra: v%s</string>
<string name="core_build_timestamp">J├бdro sestaveno na: %s</string>
<string name="delete_address__question">Smazat adresu\?</string>
<string name="all_your_contacts_will_remain_connected">V┼бechny va┼бe kontakty z┼пstanou p┼Щipojeny.</string>
<string name="contact_requests">┼╜├бdosti o kontakt</string>
<string name="display_name__field">Zobrazovan├й jm├йno:</string>
<string name="your_profile_is_stored_on_device_and_shared_only_with_contacts_simplex_cannot_see_it">V├б┼б profil je ulo┼╛en v za┼Щ├нzen├н a je sd├нlen pouze s va┼бimi kontakty.
\n
\n<xliff:g id="appName">SimpleX</xliff:g> servery v├б┼б profil vid─Ыt nemohou.</string>
<string name="save_preferences_question">Ulo┼╛it p┼Щedvolby\?</string>
<string name="save_and_notify_contact">Ulo┼╛it a upozornit kontakt</string>
<string name="save_and_notify_contacts">Ulo┼╛it a upozornit kontakty</string>
<string name="you_control_your_chat">Kontrolujete chat!</string>
<string name="we_do_not_store_contacts_or_messages_on_servers">Na serverech neukl├бd├бme ┼╛├бdn├й va┼бe kontakty ani zpr├бvy (po doru─Нen├н).</string>
<string name="your_profile_is_stored_on_your_device">V├б┼б profil, kontakty a doru─Нen├й zpr├бvy jsou ulo┼╛eny ve va┼бem za┼Щ├нzen├н.</string>
<string name="display_name">Zobrazovan├й jm├йno</string>
<string name="full_name_optional__prompt">Cel├й jm├йno (voliteln├й)</string>
<string name="create_profile_button">Vytvo┼Щit</string>
<string name="how_to_use_markdown">Jak pou┼╛├нvat markdown</string>
<string name="you_can_use_markdown_to_format_messages__prompt">K form├бtov├бn├н zpr├бv m┼п┼╛ete pou┼╛├нt markdown:</string>
<string name="italic">kurz├нva</string>
<string name="strikethrough">p┼Щe┼бkrtnout</string>
<string name="callstatus_missed">zme┼бkan├й vol├бn├н</string>
<string name="callstatus_rejected">odm├нtnut├╜ hovor</string>
<string name="callstatus_connecting">spojovac├н hovorтАж</string>
<string name="callstatus_ended">hovor ukon─Нen <xliff:g id="duration" example="01:15">%1$s</xliff:g></string>
<string name="callstate_starting">za─Н├нn├бтАж</string>
<string name="callstate_waiting_for_answer">─Нek├б na odpov─Ы─ПтАж</string>
<string name="callstate_waiting_for_confirmation">─Нek├бn├н na potvrzen├нтАж</string>
<string name="callstate_received_answer">obdr┼╛el odpov─Ы─ПтАж</string>
<string name="callstate_received_confirmation">obdr┼╛el potvrzen├нтАж</string>
<string name="callstate_connecting">p┼Щipojen├нтАж</string>
<string name="privacy_redefined">Nov├й vymezen├н soukrom├н</string>
<string name="first_platform_without_user_ids">1. platforma bez jak├╜chkoliv u┼╛ivatelsk├╜ch identifik├бtor┼п тАУ soukrom├б ji┼╛ od n├бvrhu.</string>
<string name="immune_to_spam_and_abuse">Odoln├б v┼п─Нi spamu a zneu┼╛it├н</string>
<string name="to_protect_privacy_simplex_has_ids_for_queues">K ochran─Ы soukrom├н, m├нsto u┼╛ivatelsk├╜ch ID u┼╛├нvan├╜ch v┼бemi ostatn├нmi platformami, <xliff:g id="appName">SimpleX</xliff:g> pou┼╛├нv├б identifik├бtory pro fronty zpr├бv, zvl├б┼б┼е pro ka┼╛d├╜ z va┼бich kontakt┼п.</string>
<string name="many_people_asked_how_can_it_deliver">Mnoho lid├н se pt├б: <i>kdy┼╛ <xliff:g id="appName">SimpleX</xliff:g> nem├б ┼╛├бdn├╜ identifik├бtor u┼╛ivatel┼п, jak m┼п┼╛e doru─Нovat zpr├бvy\?</i></string>
<string name="you_control_servers_to_receive_your_contacts_to_send">Vy ur─Нujete, p┼Щes kter├й servery <b>p┼Щij├нmat</b> zpr├бvy, va┼бe kontakty тАУ servery, kter├й pou┼╛├нv├бte k zas├нl├бn├н zpr├бv.</string>
<string name="read_more_in_github">Dal┼б├н informace najdete v na┼бem repozit├б┼Щi na GitHubu.</string>
<string name="read_more_in_github_with_link">Dal┼б├н informace najdete v na┼бem <font color="#0088ff">├║lo┼╛i┼бti GitHub</font>.</string>
<string name="use_chat">Pou┼╛ijte chat</string>
<string name="onboarding_notifications_mode_subtitle">Lze zm─Ыnit pozd─Ыji v nastaven├н.</string>
<string name="onboarding_notifications_mode_off">Kdy┼╛ aplikace b─Ы┼╛├н</string>
<string name="onboarding_notifications_mode_service">Okam┼╛it├й</string>
<string name="onboarding_notifications_mode_off_desc"><b>Nejlep┼б├н pro baterii</b>. Budete p┼Щij├нmat ozn├бmen├н pouze kdy┼╛ aplikace b─Ы┼╛├н, slu┼╛ba na pozad├н NEBUDE pou┼╛ita.</string>
<string name="onboarding_notifications_mode_periodic_desc"><b>Dobr├й pro baterii</b>. Slu┼╛ba na pozad├н bude kontrolovat nov├й zpr├бvy ka┼╛d├╜ch 10 minut. M┼п┼╛ete zme┼бkat hovory a nal├йhav├й zpr├бvy.</string>
<string name="onboarding_notifications_mode_service_desc"><b>Vyu┼╛├нv├б v├нce baterie</b>! Slu┼╛ba na pozad├н je v┼╛dy spu┼бt─Ыna - ozn├бmen├н se zobraz├н, jakmile jsou zpr├бvy k dispozici.</string>
<string name="paste_the_link_you_received">Vlo┼╛en├н p┼Щijat├йho odkazu</string>
<string name="incoming_video_call">P┼Щ├нchoz├н videohovor</string>
<string name="incoming_audio_call">P┼Щ├нchoz├н zvukov├╜ hovor</string>
<string name="contact_wants_to_connect_via_call"><xliff:g id="contactName" example="Alice">%1$s</xliff:g> se s v├бmi chce spojit prost┼Щednictv├нm</string>
<string name="video_call_no_encryption">videohovoru (ne┼бifrovan├йho e2e).</string>
<string name="audio_call_no_encryption">zvukov├╜ hovor (ne┼бifrov├бno e2e)</string>
<string name="reject">Odm├нtnout</string>
<string name="your_calls">Va┼бe hovory</string>
<string name="connect_calls_via_relay">Spojen├н p┼Щes relay</string>
<string name="show_call_on_lock_screen">Zobrazit</string>
<string name="no_call_on_lock_screen">Zak├бzat</string>
<string name="your_ice_servers">Va┼бe servery ICE</string>
<string name="webrtc_ice_servers">WebRTC servery ICE</string>
<string name="status_no_e2e_encryption">bez ┼бifrov├бn├н e2e</string>
<string name="icon_descr_flip_camera">Oto─Нit fo┼е├бk</string>
<string name="icon_descr_call_pending_sent">─Мekaj├нc├н hovor</string>
<string name="icon_descr_call_missed">Zme┼бkan├╜ hovor</string>
<string name="icon_descr_call_rejected">Odm├нtnut├╜ hovor</string>
<string name="icon_descr_call_connecting">Spojov├бn├н hovoru</string>
<string name="icon_descr_call_ended">Ukon─Нen├╜ hovor</string>
<string name="answer_call">P┼Щijmout hovor</string>
<string name="integrity_msg_skipped"><xliff:g id="connection ID" example="1">%1$d</xliff:g> p┼Щesko─Нen├б zpr├бva (zpr├бvy)</string>
<string name="alert_text_skipped_messages_it_can_happen_when">M┼п┼╛e se to st├бt, kdy┼╛:
\n1. Zpr├бvy na serveru vypr┼б├н, pokud nebyly p┼Щijaty po dobu 30 dn┼п,
\n2. Server, kter├╜ pou┼╛├нv├бte pro p┼Щ├нjem zpr├бv od tohoto kontaktu, byl aktualizov├бn a restartov├бn.
\n3. Spojen├н je naru┼бeno.
\nP┼Щipojte se k v├╜voj├б┼Щ┼пm prost┼Щednictv├нm Nastaven├н, abyste mohli dost├бvat aktualizace o serverech.
\nBudeme p┼Щid├бvat redundantn├н servery, abychom zabr├бnili ztr├бt─Ы zpr├бv.</string>
<string name="settings_section_title_you">VY</string>
<string name="settings_section_title_support">PODPO┼ШIT SIMPLEX CHAT</string>
<string name="settings_section_title_develop">ROZV├НJET</string>
<string name="settings_developer_tools">N├бstroje pro v├╜voj├б┼Щe</string>
<string name="settings_section_title_incognito">Inkognito m├│d</string>
<string name="your_chat_database">Va┼бe chat datab├бze</string>
<string name="run_chat_section">SPUSTIT CHAT</string>
<string name="chat_is_running">Chat je spu┼бt─Ыn</string>
<string name="chat_is_stopped">Chat je zastaven</string>
<string name="chat_database_section">CHAT DATAB├БZE</string>
<string name="database_passphrase">Heslo datab├бze</string>
<string name="new_database_archive">Archiv nov├й datab├бze</string>
<string name="old_database_archive">Archiv star├й datab├бze</string>
<string name="error_starting_chat">Chyba p┼Щi spu┼бt─Ыn├н chatu</string>
<string name="stop_chat_question">Zastavit chat\?</string>
<string name="stop_chat_to_export_import_or_delete_chat_database">Zastavte chat pro export, import nebo smaz├бn├н chat datab├бze. B─Ыhem zastaven├н, nebudete moci p┼Щij├нmat ani odes├нlat zpr├бvy.</string>
<string name="stop_chat_confirmation">Zastavit</string>
<string name="set_password_to_export">Nastaven├н p┼Щ├нstupov├й fr├бze pro export</string>
<string name="set_password_to_export_desc">Datab├бze je ┼бifrov├бna pomoc├н n├бhodn├й p┼Щ├нstupov├й fr├бze. P┼Щed exportem ji zm─Ы┼Иte.</string>
<string name="error_stopping_chat">Chyba p┼Щi zastaven├н chatu</string>
<string name="import_database_question">Importovat chat datab├бzi\?</string>
<string name="your_current_chat_database_will_be_deleted_and_replaced_with_the_imported_one">Va┼бe aktu├бln├н chat datab├бze bude SMAZ├БNA a NAHRAZENA importovanou datab├бz├н.
\nTuto akci nelze vz├нt zp─Ыt - v├б┼б profily, kontakty, zpr├бvy a soubory budou nen├бvratn─Ы ztraceny.</string>
<string name="error_deleting_database">Chyba maz├бn├н chat datab├бze</string>
<string name="error_importing_database">Chyba importu chat datab├бze</string>
<string name="chat_database_deleted">Chat datab├бze odstran─Ыna</string>
<string name="delete_files_and_media_for_all_users">Odstran─Ыn├н soubor┼п pro v┼бechny chat profily</string>
<string name="delete_files_and_media_all">Odstranit v┼бechny soubory</string>
<string name="delete_files_and_media_desc">Tuto akci nelze vr├бtit zp─Ыt - v┼бechny p┼Щijat├й a odeslan├й soubory a m├йdia budou smaz├бny. Obr├бzky s n├нzk├╜m rozli┼бen├нm z┼пstanou zachov├бny.</string>
<string name="no_received_app_files">┼╜├бdn├й p┼Щijat├й ani odeslan├й soubory</string>
<string name="total_files_count_and_size">%d soubor(┼п) s celkovou velikost├н %s</string>
<string name="chat_item_ttl_none">nikdy</string>
<string name="chat_item_ttl_seconds">%s vte┼Щin(y)</string>
<string name="messages_section_title">Zpr├бvy</string>
<string name="messages_section_description">Toto nastaven├н se vztahuje na zpr├бvy ve va┼бem aktu├бln├нm chat profilu.</string>
<string name="delete_messages_after">Smazat zpr├бvy po</string>
<string name="enable_automatic_deletion_question">Povolit automatick├й maz├бn├н zpr├бv\?</string>
<string name="enable_automatic_deletion_message">Tuto akci nelze vz├нt zp─Ыt - zpr├бvy odeslan├й a p┼Щijat├й d┼Щ├нve, ne┼╛ bylo zvoleno, budou smaz├бny. M┼п┼╛e to trvat n─Ыkolik minut.</string>
<string name="error_changing_message_deletion">Chyba zm─Ыny nastaven├н</string>
<string name="save_passphrase_in_keychain">Ulo┼╛it p┼Щ├нstupovou fr├бzi do ├║lo┼╛i┼бt─Ы kl├н─Н┼п</string>
<string name="database_encrypted">Datab├бze ┼бifrov├бna!</string>
<string name="error_encrypting_database">Chyba ┼бifrov├бn├н datab├бze</string>
<string name="encrypt_database">┼аifrovat</string>
<string name="encrypted_with_random_passphrase">Datab├бze je ┼бifrov├бna pomoc├н n├бhodn├й p┼Щ├нstupov├й fr├бze, m┼п┼╛ete ji zm─Ыnit.</string>
<string name="keychain_allows_to_receive_ntfs">K bezpe─Нn├йmu ulo┼╛en├н p┼Щ├нstupov├й fr├бze se pou┼╛ije ├║lo┼╛i┼бt─Ы kl├н─Н┼п Android, po restartov├бn├н aplikace nebo zm─Ыn─Ы p┼Щ├нstupov├й fr├бze - umo┼╛n├н p┼Щij├нm├бn├н ozn├бmen├н.</string>
<string name="you_have_to_enter_passphrase_every_time">Mus├нte zadat p┼Щ├нstupovou fr├бzi p┼Щi ka┼╛d├йm spu┼бt─Ыn├н aplikace - nen├н ulo┼╛ena v za┼Щ├нzen├н.</string>
<string name="encrypt_database_question">┼аifrovat datab├бzi\?</string>
<string name="change_database_passphrase_question">Zm─Ыnit p┼Щ├нstupovou fr├бzi datab├бze\?</string>
<string name="database_will_be_encrypted">Datab├бze bude ┼бifrov├бna.</string>
<string name="database_encryption_will_be_updated">P┼Щ├нstupov├б fr├бze datab├бze bude aktualizov├бna a ulo┼╛ena do ├║lo┼╛i┼бt─Ы kl├н─Н┼п.</string>
<string name="database_passphrase_will_be_updated">P┼Щ├нstupov├б fr├бze datab├бze bude aktualizov├бna.</string>
<string name="store_passphrase_securely_without_recover">Ulo┼╛te pros├нm bezpe─Нn─Ы p┼Щ├нstupovou fr├бzi, pokud ji ztrat├нte, NEBUDE mo┼╛n├й p┼Щistupovat k chatu.</string>
<string name="wrong_passphrase">┼аpatn├б p┼Щ├нstupov├б fr├бze k datab├бzi</string>
<string name="encrypted_database">┼аifrovan├б datab├бze</string>
<string name="database_error">Chyba datab├бze</string>
<string name="keychain_error">Chyba kl├н─Нenky</string>
<string name="passphrase_is_different">P┼Щ├нstupov├б fr├бze datab├бze se li┼б├н od t├й ulo┼╛en├й v kl├н─Нence.</string>
<string name="cannot_access_keychain">Nelze z├нskat p┼Щ├нstup k ├║lo┼╛i┼бti kl├н─Н┼п pro ulo┼╛en├н p┼Щ├нstupov├б fr├бze k datab├бzi.</string>
<string name="unknown_database_error_with_info">Nezn├бm├б chyba datab├бze: %s</string>
<string name="wrong_passphrase_title">┼аpatn├б p┼Щ├нstupov├б fr├бze!</string>
<string name="enter_correct_passphrase">Zadejte spr├бvnou p┼Щ├нstupovou fr├бzi.</string>
<string name="enter_passphrase">Zadejte p┼Щ├нstupovou fr├бziтАж</string>
<string name="database_backup_can_be_restored">Pokus o zm─Ыnu p┼Щ├нstupov├й fr├бze datab├бze nebyl dokon─Нen.</string>
<string name="restore_database_alert_title">Obnovit z├бlohu datab├бze\?</string>
<string name="restore_database_alert_confirm">Obnovit</string>
<string name="database_restore_error">Chyba obnoven├н datab├бze</string>
<string name="restore_passphrase_not_found_desc">Heslo nebylo nalezeno v ├║lo┼╛i┼бti kl├н─Н┼п, zadejte jej pros├нm ru─Нn─Ы. K t├йto situaci mohlo doj├нt, pokud jste obnovili data aplikace pomoc├н z├бlohovac├нho n├бstroje. Pokud tomu tak nen├н, obra┼еte se na v├╜voj├б┼Щe.</string>
<string name="you_can_start_chat_via_setting_or_by_restarting_the_app">Chat m┼п┼╛ete spustit p┼Щes Nastaven├н aplikace / Datab├бze nebo restartov├бn├нm aplikace.</string>
<string name="save_archive">Ulo┼╛it archiv</string>
<string name="delete_archive">Smazat archiv</string>
<string name="group_invitation_item_description">pozv├бnka do skupiny <xliff:g id="group_name">%1$s</xliff:g></string>
<string name="archive_created_on_ts">Vytvo┼Щeno dne <xliff:g id="archive_ts">%1$s</xliff:g></string>
<string name="you_are_invited_to_group_join_to_connect_with_group_members">Jste zv├бni do skupiny. P┼Щipojte se a spojte se s ─Нleny skupiny.</string>
<string name="join_group_incognito_button">P┼Щipojit se inkognito</string>
<string name="joining_group">P┼Щipojit ke skupin─Ы</string>
<string name="youve_accepted_group_invitation_connecting_to_inviting_group_member">P┼Щipojili jste se k t├йto skupin─Ы. P┼Щipojen├н k pozv├бn├н ─Нlena skupiny.</string>
<string name="you_will_stop_receiving_messages_from_this_group_chat_history_will_be_preserved">P┼Щestanete dost├бvat zpr├бvy z t├йto skupiny. Chat historie bude zachov├бna.</string>
<string name="alert_title_group_invitation_expired">Platnost pozv├бnky vypr┼бela!</string>
<string name="alert_message_group_invitation_expired">Pozv├бnka do skupiny ji┼╛ nen├н platn├б, byla odstran─Ыna odes├нlatelem.</string>
<string name="alert_message_no_group">Tato skupina ji┼╛ neexistuje.</string>
<string name="alert_title_cant_invite_contacts_descr">Pro tuto skupinu pou┼╛├нv├бte anonymn├н profil - abyste zabr├бnili sd├нlen├н sv├йho hlavn├нho profilu, nen├н pozv├бn├н kontakt┼п povoleno.</string>
<string name="you_sent_group_invitation">Odeslali jste pozv├бnku do skupiny</string>
<string name="you_are_invited_to_group">Jste pozv├бni do skupiny</string>
<string name="group_invitation_tap_to_join">Klepnut├нm se p┼Щipoj├нte</string>
<string name="group_invitation_tap_to_join_incognito">Klepnut├нm se p┼Щipoj├нte inkognito</string>
<string name="you_joined_this_group">P┼Щipojili jste se k t├йto skupin─Ы</string>
<string name="you_rejected_group_invitation">Odm├нtli jste pozv├бnku do skupiny</string>
<string name="group_invitation_expired">Platnost pozv├бnky do skupiny vypr┼бela</string>
<string name="rcv_group_event_member_added">pozvan├╜ <xliff:g id="member profile" example="alice (Alice)">%1$s</xliff:g></string>
<string name="rcv_group_event_member_connected">p┼Щipojen</string>
<string name="rcv_group_event_changed_member_role">zm─Ыnil roli %s na %s</string>
<string name="rcv_group_event_changed_your_role">zm─Ыnil svou roli na %s</string>
<string name="rcv_group_event_member_deleted">odstran─Ыn <xliff:g id="member profile" example="alice (Alice)">%1$s</xliff:g></string>
<string name="rcv_group_event_user_deleted">odstranil v├бs</string>
<string name="rcv_group_event_group_deleted">skupina odstran─Ыna</string>
<string name="rcv_group_event_updated_group_profile">aktualizoval profil skupiny</string>
<string name="rcv_group_event_invited_via_your_group_link">pozv├бni prost┼Щednictv├нm odkazu na va┼бi skupinu</string>
<string name="snd_group_event_group_profile_updated">profil skupiny aktualizov├бn</string>
<string name="rcv_conn_event_switch_queue_phase_changing">zm─Ыna adresyтАж</string>
<string name="snd_conn_event_switch_queue_phase_completed_for_member">zm─Ыnili jste adresu pro %s</string>
<string name="snd_conn_event_switch_queue_phase_changing_for_member">zm─Ыna adresy pro %sтАж</string>
<string name="snd_conn_event_switch_queue_phase_completed">zm─Ыnili jste adresu</string>
<string name="snd_conn_event_switch_queue_phase_changing">zm─Ыna adresyтАж</string>
<string name="group_member_role_member">─Нlen</string>
<string name="group_member_role_owner">vlastn├нk</string>
<string name="group_member_status_removed">odstran─Ыn</string>
<string name="group_member_status_left">vlevo</string>
<string name="group_member_status_group_deleted">skupina smaz├бna</string>
<string name="group_member_status_invited">pozv├бnka</string>
<string name="group_member_status_introduced">p┼Щipojuj├нc├н (zaveden├╜)</string>
<string name="group_member_status_intro_invitation">p┼Щipojen├н (pozv├бnka na p┼Щedstaven├н)</string>
<string name="group_member_status_accepted">p┼Щipojen├н (p┼Щijato)</string>
<string name="group_member_status_announced">p┼Щipojen├н (ozn├бmeno)</string>
<string name="group_member_status_connected">p┼Щipojen</string>
<string name="group_member_status_complete">kompletn├н</string>
<string name="group_member_status_creator">tv┼пrce</string>
<string name="group_member_status_connecting">p┼Щipojov├бn├н</string>
<string name="no_contacts_to_add">┼╜├бdn├й kontakty k p┼Щid├бn├н</string>
<string name="new_member_role">Nov├б role ─Нlena</string>
<string name="invite_to_group_button">Pozvat do skupiny</string>
<string name="skip_inviting_button">P┼Щesko─Нit pozv├бn├н ─Нlen┼п</string>
<string name="select_contacts">Vybrat kontakty</string>
<string name="icon_descr_contact_checked">Zkontrolovan├й kontakty</string>
<string name="num_contacts_selected"><xliff:g id="num_contacts">%1$s</xliff:g> kontakt(y) vybr├бn(y)</string>
<string name="button_add_members">Pozvat ─Нleny</string>
<string name="group_info_section_title_num_members"><xliff:g id="num_members">%1$s</xliff:g> MEMBERS</string>
<string name="group_info_member_you">vy: <xliff:g id="group_info_you">%1$s</xliff:g></string>
<string name="button_delete_group">Smazat skupinu</string>
<string name="delete_group_question">Smazat skupinu\?</string>
<string name="delete_group_for_all_members_cannot_undo_warning">Skupina bude smaz├бna pro v┼бechny ─Нleny - nelze to vz├нt zp─Ыt!</string>
<string name="delete_group_for_self_cannot_undo_warning">Skupina bude smaz├бna pro v├бs - toto nelze vz├нt zp─Ыt!</string>
<string name="button_leave_group">Opustit skupinu</string>
<string name="button_edit_group_profile">Upravit profil skupiny</string>
<string name="group_link">Odkaz na skupinu</string>
<string name="create_group_link">Vytvo┼Щit odkaz na skupinu</string>
<string name="delete_link">Smazat odkaz</string>
<string name="you_can_share_group_link_anybody_will_be_able_to_connect">M┼п┼╛ete sd├нlet odkaz nebo QR k├│d - ke skupin─Ы se bude moci p┼Щipojit kdokoli. O ─Нleny skupiny nep┼Щijdete, pokud ji pozd─Ыji odstran├нte.</string>
<string name="error_creating_link_for_group">Chyba vytv├б┼Щen├н odkazu skupiny</string>
<string name="error_deleting_link_for_group">Chyba odstra┼Иov├бn├н odkazu skupiny</string>
<string name="only_group_owners_can_change_prefs">P┼Щedvolby skupiny mohou m─Ыnit pouze vlastn├нci skupiny.</string>
<string name="section_title_for_console">PRO KONSOLE</string>
<string name="info_row_local_name">M├нstn├н n├бzev</string>
<string name="info_row_database_id">ID datab├бze</string>
<string name="button_remove_member">Odstranit ─Нlena</string>
<string name="member_will_be_removed_from_group_cannot_be_undone">─Мlen bude odstran─Ыn ze skupiny - toto nelze vz├нt zp─Ыt!</string>
<string name="remove_member_confirmation">Odstranit</string>
<string name="role_in_group">Role</string>
<string name="change_role">Zm─Ыnit roli</string>
<string name="change_verb">Zm─Ыnit</string>
<string name="member_role_will_be_changed_with_invitation">Role bude zm─Ыn─Ыna na \"%s\". ─Мlen obdr┼╛├н novou pozv├бnku.</string>
<string name="error_changing_role">Chyba zm─Ыny role</string>
<string name="conn_level_desc_direct">p┼Щ├нmo</string>
<string name="sending_via">Odes├нl├бno p┼Щez</string>
<string name="network_status">Stav s├нt─Ы</string>
<string name="switch_receiving_address">P┼Щep├нna─Н p┼Щij├нmac├н adresy</string>
<string name="group_is_decentralized">Skupina je pln─Ы decentralizovan├б - viditeln├б pouze pro ─Нleny.</string>
<string name="group_unsupported_incognito_main_profile_sent">Zde nen├н podporov├бn re┼╛im inkognito - ─Нlen┼пm skupiny bude zasl├бn v├б┼б hlavn├н profil.</string>
<string name="save_group_profile">Ulo┼╛en├н profilu skupiny</string>
<string name="network_options_reset_to_defaults">Obnovit v├╜choz├н nastaven├н</string>
<string name="network_option_tcp_connection_timeout">─Мasov├╜ limit p┼Щipojen├н TCP</string>
<string name="network_option_protocol_timeout">─Мasov├╜ limit protokolu</string>
<string name="network_option_ping_interval">Interval PING</string>
<string name="network_option_ping_count">Po─Нet PING</string>
<string name="network_option_enable_tcp_keep_alive">Povolit TCP keep-alive</string>
<string name="update_network_settings_confirmation">Aktualizovat</string>
<string name="users_delete_question">Smazat chat profil\?</string>
<string name="users_delete_profile_for">Smazat chat profil pro</string>
<string name="users_delete_with_connections">Profil a p┼Щipojen├н k serveru</string>
<string name="users_delete_data_only">Pouze m├нstn├н data profilu</string>
<string name="incognito_random_profile_from_contact_description">N├бhodn├╜ profil bude zasl├бn kontaktu, od kter├йho jste obdr┼╛eli tento odkaz.</string>
<string name="incognito_info_protects">Re┼╛im inkognito chr├бn├н soukrom├н va┼бeho hlavn├нho profilov├йho jm├йna a obr├бzku - pro ka┼╛d├╜ nov├╜ kontakt je vytvo┼Щen nov├╜ n├бhodn├╜ profil.</string>
<string name="incognito_info_find">Chcete-li naj├нt profil pou┼╛it├╜ pro inkognito p┼Щipojen├н, klepn─Ыte na n├бzev kontaktu nebo skupiny v horn├н ─Н├бsti chatu.</string>
<string name="theme_light">Sv─Ыtl├╜</string>
<string name="theme_dark">Tmav├╜</string>
<string name="theme">T├йma</string>
<string name="chat_preferences_contact_allows">Kontakt povolil</string>
<string name="chat_preferences_on">zapnuto</string>
<string name="chat_preferences_off">vypnuto</string>
<string name="chat_preferences">Chat p┼Щedvolby</string>
<string name="contact_preferences">P┼Щedvolby kontaktu</string>
<string name="group_preferences">P┼Щedvolby skupiny</string>
<string name="direct_messages">P┼Щ├нm├й zpr├бvy</string>
<string name="full_deletion">Smazat pro v┼бechny</string>
<string name="feature_enabled">povoleno</string>
<string name="feature_enabled_for_you">povoleno pro v├бs</string>
<string name="feature_off">vypnuto</string>
<string name="prohibit_sending_disappearing_messages">Zak├бzat zas├нl├бn├н mizej├нc├нch zpr├бv.</string>
<string name="contacts_can_mark_messages_for_deletion">Kontakty mohou ozna─Нit zpr├бvy ke smaz├бn├н; vy je budete moci zobrazit.</string>
<string name="prohibit_sending_voice_messages">Zak├бzat odes├нl├бn├н hlasov├╜ch zpr├бv.</string>
<string name="only_you_can_send_disappearing">Mizej├нc├н zpr├бvy m┼п┼╛ete odes├нlat pouze vy.</string>
<string name="disappearing_prohibited_in_this_chat">Mizej├нc├н zpr├бvy jsou v tomto chatu zak├бz├бny.</string>
<string name="only_your_contact_can_delete">Nevratn─Ы mazat zpr├бvy m┼п┼╛e pouze v├б┼б kontakt (vy je m┼п┼╛ete ozna─Нit ke smaz├бn├н).</string>
<string name="both_you_and_your_contact_can_send_voice">Hlasov├й zpr├бvy m┼п┼╛ete pos├нlat vy i v├б┼б kontakt.</string>
<string name="only_you_can_send_voice">Hlasov├й zpr├бvy m┼п┼╛ete pos├нlat pouze vy.</string>
<string name="only_your_contact_can_send_voice">Hlasov├й zpr├бvy m┼п┼╛e odes├нlat pouze v├б┼б kontakt.</string>
<string name="voice_prohibited_in_this_chat">Hlasov├й zpr├бvy jsou v tomto chatu zak├бz├бny.</string>
<string name="prohibit_sending_disappearing">Zak├бzat pos├нl├бn├н mizej├нc├нch zpr├бv.</string>
<string name="prohibit_message_deletion">Zak├бzat nevratn├й maz├бn├н zpr├бv.</string>
<string name="prohibit_sending_voice">Zak├бzat odes├нl├бn├н hlasov├╜ch zpr├бv.</string>
<string name="group_members_can_send_disappearing">─Мlenov├й skupiny mohou pos├нlat mizej├нc├н zpr├бvy.</string>
<string name="disappearing_messages_are_prohibited">Mizej├нc├н zpr├бvy jsou v t├йto skupin─Ы zak├бz├бny.</string>
<string name="group_members_can_send_dms">─Мlenov├й skupiny mohou pos├нlat p┼Щ├нm├й zpr├бvy.</string>
<string name="direct_messages_are_prohibited_in_chat">P┼Щ├нm├й zpr├бvy mezi ─Нleny jsou v t├йto skupin─Ы zak├бz├бny.</string>
<string name="group_members_can_delete">─Мlenov├й skupiny mohou nevratn─Ы mazat odeslan├й zpr├бvy.</string>
<string name="message_deletion_prohibited_in_chat">Nevratn├й maz├бn├н zpr├бv je v t├йto skupin─Ы zak├бz├бno.</string>
<string name="group_members_can_send_voice">─Мlenov├й skupiny mohou pos├нlat hlasov├й zpr├бvy.</string>
<string name="voice_messages_are_prohibited">Hlasov├й zpr├бvy jsou v t├йto skupin─Ы zak├бz├бny.</string>
<string name="delete_after">Smazat po</string>
<string name="ttl_month">%d m─Ыs├нc</string>
<string name="ttl_months">%d m─Ыs├нc┼п</string>
<string name="ttl_day">%d den</string>
<string name="ttl_days">%d dn┼п</string>
<string name="ttl_week">%d t├╜den</string>
<string name="ttl_weeks">%d t├╜dn┼п</string>
<string name="ttl_w">%dw</string>
<string name="feature_offered_item">nab├нzeno %s</string>
<string name="feature_cancelled_item">zru┼бeno %s</string>
<string name="whats_new">Co je nov├йho</string>
<string name="new_in_version">Novinky v %s</string>
<string name="v4_2_auto_accept_contact_requests">Automatick├й p┼Щij├нm├бn├н ┼╛├бdost├н o kontakt</string>
<string name="v4_2_auto_accept_contact_requests_desc">S volitelnou uv├нtac├н zpr├бvou.</string>
<string name="v4_3_voice_messages_desc">Max. 40 sekund, p┼Щij├нm├б se okam┼╛it─Ы.</string>
<string name="v4_5_private_filenames">Soukrom├й n├бzvy soubor┼п</string>
<string name="v4_5_private_filenames_descr">Pro ochranu ─Нasov├йho p├бsma, obrazov├й/hlasov├й soubory pou┼╛├нvaj├н UTC.</string>
<string name="v4_5_reduced_battery_usage">Sn├н┼╛en├н spot┼Щeby baterie</string>
<string name="v4_5_reduced_battery_usage_descr">Dal┼б├н vylep┼бen├н se chystaj├н ji┼╛ brzy!</string>
<string name="v4_5_italian_interface">Italsk├й rozhran├н</string>
<string name="v4_5_italian_interface_descr">D├нky u┼╛ivatel┼пm - p┼Щekl├бdejte prost┼Щednictv├нm Weblate!</string>
<string name="you_will_be_connected_when_your_contacts_device_is_online">Budete p┼Щipojeni, a┼╛ bude za┼Щ├нzen├н va┼бeho kontaktu online, vy─Нkejte pros├нm nebo se pod├нvejte pozd─Ыji!</string>
<string name="your_contact_address">Va┼бe kontaktn├н adresa</string>
<string name="your_chat_profile_will_be_sent_to_your_contact">V├б┼б chat profil bude odesl├бn
\nva┼бemu kontaktu</string>
<string name="your_chat_profiles_stored_locally">Va┼бe chat profily jsou ulo┼╛eny lok├бln─Ы, pouze ve va┼бem za┼Щ├нzen├н.</string>
<string name="your_chats">Va┼бe konverzace</string>
<string name="paste_connection_link_below_to_connect">Do n├н┼╛e uveden├йho pole vlo┼╛te odkaz, kter├╜ jste obdr┼╛eli pro spojen├н s kontaktem.</string>
<string name="share_invitation_link">Sd├нlet zvac├н odkaz</string>
<string name="status_e2e_encrypted">end-to-end ┼бifrovan├й</string>
<string name="moderated_description">moderovan├й</string>
</resources>

View File

@@ -49,7 +49,7 @@
<string name="simplex_link_mode_browser_warning">Das ├Цffnen des Links ├╝ber den Browser kann die Privatsph├дre und Sicherheit der Verbindung reduzieren. SimpleX-Links, denen nicht vertraut wird, werden Rot sein.</string>
<!-- SimpleXAPI.kt -->
<string name="error_saving_smp_servers">Fehler beim Speichern der SMP-Server</string>
<string name="ensure_smp_server_address_are_correct_format_and_unique">Stellen Sie sicher, dass die SMP-Server Adressen das richtige Format haben, zeilenweise angeordnet und nicht kopiert sind.</string>
<string name="ensure_smp_server_address_are_correct_format_and_unique">Stellen Sie sicher, dass die SMP-Server Adressen das richtige Format haben, zeilenweise angeordnet und nicht doppelt vorhanden sind.</string>
<string name="error_setting_network_config">Fehler bei der Aktualisierung der Netzwerk-Konfiguration.</string>
<!-- API Error Responses - SimpleXAPI.kt -->
<string name="connection_timeout">Verbindungszeit├╝berschreitung</string>
@@ -90,10 +90,10 @@
<string name="to_preserve_privacy_simplex_has_background_service_instead_of_push_notifications_it_uses_a_few_pc_battery">Um Ihre Privatsph├дre zu sch├╝tzen kann statt der Push-Benachrichtigung der <b><xliff:g id="appName">SimpleX</xliff:g> Hintergrunddienst genutzt werden</b> тАУ dieser ben├╢tigt ein paar Prozent Akkuleistung am Tag.</string>
<string name="it_can_disabled_via_settings_notifications_still_shown"><b>Diese k├╢nnen ├╝ber die Einstellungen deaktiviert werden</b> тАУ Solange die App abl├дuft werden Benachrichtigungen weiterhin angezeigt.</string>
<string name="turn_off_battery_optimization">Um diese Funktion zu nutzen, ist es n├╢tig, die Einstellung <b>Akkuoptimierung</b> f├╝r <xliff:g id="appName">SimpleX</xliff:g> im n├дchsten Dialog zu <b>deaktivieren</b>. Ansonsten werden die Benachrichtigungen deaktiviert.</string>
<string name="turning_off_service_and_periodic">Die Akkuoptimierung ist aktiv, der Hintergrunddienst und die regelm├д├Яige Nachfrage nach neuen Nachrichten ist abgeschaltet. Sie k├╢nnen diese Funktion in den Einstellungen wieder aktivieren.</string>
<string name="periodic_notifications">Regelm├д├Яige Benachrichtigungen</string>
<string name="periodic_notifications_disabled">Regelm├д├Яige Benachrichtigungen sind deaktiviert!</string>
<string name="periodic_notifications_desc">Die App holt regelm├д├Яig neue Nachrichten ab тАФ dies ben├╢tigt ein paar Prozent Akkuleistung am Tag. Die App nutzt keine Push-Benachrichtigungen тАФ es werden keine Daten von Ihrem Ger├дt an Server gesendet.</string>
<string name="turning_off_service_and_periodic">Die Akkuoptimierung ist aktiv, der Hintergrunddienst und die periodische Nachfrage nach neuen Nachrichten ist abgeschaltet. Sie k├╢nnen diese Funktion in den Einstellungen wieder aktivieren.</string>
<string name="periodic_notifications">Periodische Benachrichtigungen</string>
<string name="periodic_notifications_disabled">Periodische Benachrichtigungen sind deaktiviert!</string>
<string name="periodic_notifications_desc">Die App holt periodisch neue Nachrichten ab тАФ dies ben├╢tigt ein paar Prozent Akkuleistung am Tag. Die App nutzt keine Push-Benachrichtigungen тАФ es werden keine Daten von Ihrem Ger├дt an Server gesendet.</string>
<string name="enter_passphrase_notification_title">Passwort wird ben├╢tigt</string>
<string name="enter_passphrase_notification_desc">Geben Sie bitte das Datenbank-Passwort ein, um Benachrichtigungen zu erhalten.</string>
<string name="database_initialization_error_title">Die Datenbank kann nicht initialisiert werden</string>
@@ -110,7 +110,7 @@
<string name="settings_notification_preview_mode_title">Vorschau anzeigen</string>
<string name="settings_notification_preview_title">Benachrichtigungsvorschau</string>
<string name="notifications_mode_off">Wird ausgef├╝hrt, wenn die App ge├╢ffnet ist</string>
<string name="notifications_mode_periodic">Startet regelm├д├Яig</string>
<string name="notifications_mode_periodic">Startet periodisch</string>
<string name="notifications_mode_service">Immer aktiv</string>
<string name="notifications_mode_off_desc">Die App kann Benachrichtigungen nur empfangen, wenn sie ausgef├╝hrt wird, es wird kein Hintergrunddienst gestartet.</string>
<string name="notifications_mode_periodic_desc">├Ьberpr├╝ft alle 10 Minuten auf neue Nachrichten f├╝r bis zu einer Minute.</string>
@@ -230,8 +230,8 @@
<string name="icon_descr_send_message">Nachricht senden</string>
<string name="icon_descr_record_voice_message">Nehme Sprachnachricht auf</string>
<string name="allow_voice_messages_question">Sprachnachrichten erlauben?</string>
<string name="you_need_to_allow_to_send_voice">Sie m├╝ssen Ihrem Kontakt das Senden von Sprachnachrichten erlauben, damit Sie sie senden k├╢nnen.</string>
<string name="voice_messages_prohibited">Sprachnachrichten unzul├дssig!</string>
<string name="you_need_to_allow_to_send_voice">Sie m├╝ssen Ihrem Kontakt das Senden von Sprachnachrichten erlauben, damit Sie sie versenden k├╢nnen.</string>
<string name="voice_messages_prohibited">Sprachnachrichten nicht erlaubt!</string>
<string name="ask_your_contact_to_enable_voice">Bitten Sie Ihren Kontakt darum, das Senden von Sprachnachrichten zu aktivieren.</string>
<string name="only_group_owners_can_enable_voice">Sprachnachrichten k├╢nnen nur von Gruppen-Eigent├╝mern aktiviert werden.</string>
<!-- General Actions / Responses -->
@@ -328,11 +328,12 @@
<string name="you_will_be_connected_when_your_contacts_device_is_online">Sie werden verbunden, sobald das Endger├дt Ihres Kontakts online ist. Bitte warten oder schauen Sie sp├дter nochmal nach!</string>
<string name="show_QR_code_for_your_contact_to_scan_from_the_app__multiline">Zeigen Sie Ihrem Kontakt den QR-Code aus der App zum Scannen.</string>
<string name="if_you_cannot_meet_in_person_show_QR_in_video_call_or_via_another_channel">Wenn Sie sich nicht pers├╢nlich treffen k├╢nnen, k├╢nnen Sie <b>den QR-Code w├дhrend eines Videoanrufs anzeigen</b> oder einen Einladungslink ├╝ber einen anderen Kanal mit Ihrem Kontakt teilen.</string>
<string name="your_chat_profile_will_be_sent_to_your_contact">Ihr Chat-Profil wird\nan Ihren Kontakt gesendet.</string>
<string name="your_chat_profile_will_be_sent_to_your_contact">Ihr Chat-Profil wird
\nan Ihren Kontakt gesendet</string>
<string name="if_you_cannot_meet_in_person_scan_QR_in_video_call_or_ask_for_invitation_link">Wenn Sie sich nicht pers├╢nlich treffen k├╢nnen, k├╢nnen Sie <b>den QR-Code w├дhrend eines Videoanrufs scannen</b> oder Ihr Kontakt kann einen Einladungslink ├╝ber einen anderen Kanal mit Ihnen teilen.</string>
<string name="share_invitation_link">Einladungslink teilen</string>
<string name="paste_connection_link_below_to_connect">F├╝gen Sie den erhaltenen Link in das Feld unten ein, um sich mit Ihrem Kontakt zu verbinden.</string>
<string name="your_profile_will_be_sent">Ihr Chat-Profil wird an Ihren Kontakt gesendet.</string>
<string name="your_profile_will_be_sent">Ihr Chat-Profil wird an Ihren Kontakt gesendet</string>
<!-- PasteToConnect.kt -->
<string name="connect_via_link">├Ьber einen Link verbinden</string>
<string name="connect_button">Verbinden</string>
@@ -361,7 +362,7 @@
<string name="smp_servers_add">F├╝ge Server hinzuтАж</string>
<string name="smp_servers_test_server">Teste Server</string>
<string name="smp_servers_test_servers">Teste alle Server</string>
<string name="smp_servers_save">Sichere alle Server</string>
<string name="smp_servers_save">Alle Server speichern</string>
<string name="smp_servers_test_failed">Server Test ist fehlgeschlagen!</string>
<string name="smp_servers_test_some_failed">Einige Server haben den Test nicht bestanden:</string>
<string name="smp_servers_scan_qr">Scannen Sie den QR-Code des Servers</string>
@@ -386,10 +387,10 @@
<string name="how_to_use_your_servers">Wie Sie Ihre Server nutzen</string>
<string name="saved_ICE_servers_will_be_removed">Gespeicherte WebRTC ICE-Server werden entfernt.</string>
<string name="your_ICE_servers">Ihre ICE-Server</string>
<string name="configure_ICE_servers">Konfigurieren Sie ICE-Server</string>
<string name="configure_ICE_servers">ICE-Server konfigurieren</string>
<string name="enter_one_ICE_server_per_line">ICE-Server (einer pro Zeile)</string>
<string name="error_saving_ICE_servers">Fehler beim Speichern der ICE-Server</string>
<string name="ensure_ICE_server_address_are_correct_format_and_unique">Stellen Sie sicher, dass die WebRTC ICE-Server Adressen das richtige Format haben, zeilenweise angeordnet und nicht kopiert sind.</string>
<string name="ensure_ICE_server_address_are_correct_format_and_unique">Stellen Sie sicher, dass die WebRTC ICE-Server Adressen das richtige Format haben, zeilenweise angeordnet und nicht doppelt vorhanden sind.</string>
<string name="save_servers_button">Speichern</string>
<string name="network_and_servers">Netzwerk &amp; Server</string>
<string name="network_settings">Erweiterte Netzwerkeinstellungen</string>
@@ -426,7 +427,7 @@
<!-- User profile details - UserProfileView.kt -->
<string name="display_name__field">Angezeigter Name:</string>
<string name="full_name__field">"Vollst├дndiger Name:</string>
<string name="your_chat_profile">Mein Chat-Profil</string>
<string name="your_current_profile">Mein aktuelles Chat-Profil</string>
<string name="your_profile_is_stored_on_device_and_shared_only_with_contacts_simplex_cannot_see_it">Ihr Profil wird auf Ihrem Ger├дt gespeichert und nur mit Ihren Kontakten geteilt.\n\n<xliff:g id="appName">SimpleX</xliff:g>-Server k├╢nnen Ihr Profil nicht sehen.</string>
<string name="edit_image">Bild bearbeiten</string>
<string name="delete_image">Bild l├╢schen</string>
@@ -558,7 +559,6 @@
<string name="your_privacy">Meine Privatsph├дre</string>
<string name="protect_app_screen">App-Bildschirm sch├╝tzen</string>
<string name="auto_accept_images">Bilder automatisch akzeptieren</string>
<string name="transfer_images_faster">Bilder schneller ├╝bertragen</string>
<string name="send_link_previews">Link-Vorschau senden</string>
<string name="full_backup">App-Datensicherung</string>
<!-- Settings sections -->
@@ -578,7 +578,7 @@
<string name="settings_section_title_calls">CALLS</string>
<string name="settings_section_title_incognito">Inkognito Modus</string>
<!-- DatabaseView.kt -->
<string name="your_chat_database">Meine Chat-Datenbank</string>
<string name="your_chat_database">Chat-Datenbank</string>
<string name="run_chat_section">CHAT STARTEN</string>
<string name="chat_is_running">Der Chat l├дuft</string>
<string name="chat_is_stopped">Der Chat ist beendet</string>
@@ -598,20 +598,19 @@
<string name="error_stopping_chat">Fehler beim Beenden des Chats</string>
<string name="error_exporting_chat_database">Fehler beim Exportieren der Chat-Datenbank</string>
<string name="import_database_question">Chat-Datenbank importieren?</string>
<string name="your_current_chat_database_will_be_deleted_and_replaced_with_the_imported_one">Ihre aktuelle Chat-Datenbank wird GEL├ЦSCHT und durch die Importierte ERSETZT.\nDiese Aktion kann nicht r├╝ckg├дngig gemacht werden - Ihr Profil und Ihre Kontakte, Nachrichten und Dateien gehen unwiderruflich verloren.</string>
<string name="your_current_chat_database_will_be_deleted_and_replaced_with_the_imported_one">Ihre aktuelle Chat-Datenbank wird GEL├ЦSCHT und durch die Importierte ERSETZT.
\nDiese Aktion kann nicht r├╝ckg├дngig gemacht werden - Ihr Profil, Ihre Kontakte, Nachrichten und Dateien gehen unwiderruflich verloren.</string>
<string name="import_database_confirmation">Importieren</string>
<string name="error_deleting_database">Fehler beim L├╢schen der Chat-Datenbank</string>
<string name="error_importing_database">Fehler beim Importieren der Chat-Datenbank</string>
<string name="chat_database_imported">Chat-Datenbank importiert</string>
<string name="restart_the_app_to_use_imported_chat_database">Starten Sie die App neu, um die importierte Chat-Datenbank zu verwenden.</string>
<string name="delete_chat_profile_question">Chat-Profil l├╢schen?</string>
<string name="delete_chat_profile_action_cannot_be_undone_warning">Diese Aktion kann nicht r├╝ckg├дngig gemacht werden - Ihr Profil und Ihre Kontakte, Nachrichten und Dateien gehen unwiderruflich verloren.</string>
<string name="delete_chat_profile_action_cannot_be_undone_warning">Diese Aktion kann nicht r├╝ckg├дngig gemacht werden - Ihr Profil, Ihre Kontakte, Nachrichten und Dateien gehen unwiderruflich verloren.</string>
<string name="chat_database_deleted">Chat-Datenbank gel├╢scht</string>
<string name="restart_the_app_to_create_a_new_chat_profile">Starten Sie die App neu, um ein neues Chat-Profil zu erstellen.</string>
<string name="you_must_use_the_most_recent_version_of_database">Sie d├╝rfen die neueste Version Ihrer Chat-Datenbank NUR auf einem Ger├дt verwenden, andernfalls erhalten Sie m├╢glicherweise keine Nachrichten mehr von einigen Ihrer Kontakte.</string>
<string name="stop_chat_to_enable_database_actions">Chat beenden, um Datenbankaktionen zu erlauben.</string>
<string name="data_section">DATEN</string>
<string name="delete_files_and_media">Dateien \&amp; Medien l├╢schen</string>
<string name="delete_files_and_media_question">Dateien und Medien l├╢schen?</string>
<string name="delete_files_and_media_desc">Diese Aktion kann nicht r├╝ckg├дngig gemacht werden - Alle empfangenen und gesendeten Dateien und Medien werden gel├╢scht. Bilder mit niedriger Aufl├╢sung bleiben erhalten.</string>
<string name="no_received_app_files">Keine empfangenen oder gesendeten Dateien</string>
@@ -882,28 +881,164 @@
<string name="allow_your_contacts_irreversibly_delete">Erlauben Sie Ihren Kontakten gesendete Nachrichten unwiederbringlich zu l├╢schen.</string>
<string name="allow_irreversible_message_deletion_only_if">Erlauben Sie das unwiederbringliche L├╢schen von Nachrichten nur dann, wenn es Ihnen Ihr Kontakt ebenfalls erlaubt.</string>
<string name="contacts_can_mark_messages_for_deletion">Ihre Kontakte k├╢nnen Nachrichten zum L├╢schen markieren. Sie k├╢nnen diese Nachrichten trotzdem anschauen.</string>
<string name="allow_your_contacts_to_send_voice_messages">Erlauben Sie Ihre Kontakten Sprachnachrichten zu senden.</string>
<string name="allow_your_contacts_to_send_voice_messages">Erlauben Sie Ihre Kontakten Sprachnachrichten zu versenden.</string>
<string name="allow_voice_messages_only_if">Erlauben Sie Sprachnachrichten nur dann, wenn Ihr Kontakt diese ebenfalls erlaubt.</string>
<string name="prohibit_sending_voice_messages">Das Senden von Sprachnachrichten verbieten.</string>
<string name="prohibit_sending_voice_messages">Das Senden von Sprachnachrichten nicht erlauben.</string>
<string name="both_you_and_your_contacts_can_delete">Sowohl Ihr Kontakt, als auch Sie k├╢nnen Nachrichten unwiederbringlich l├╢schen.</string>
<string name="only_you_can_delete_messages">Nur Sie k├╢nnen Nachrichten unwiederbringlich l├╢schen (Ihr Kontakt kann sie zum L├╢schen markieren).</string>
<string name="only_your_contact_can_delete">Nur Ihr Kontakt kann Nachrichten unwiederbringlich l├╢schen (Sie k├╢nnen sie zum L├╢schen markieren).</string>
<string name="message_deletion_prohibited">In dieser Gruppe ist das unwiederbringliche L├╢schen von Nachrichten nicht erlaubt.</string>
<string name="both_you_and_your_contact_can_send_voice">Sowohl Ihr Kontakt, als auch Sie k├╢nnen Sprachnachrichten senden.</string>
<string name="only_you_can_send_voice">Nur Sie k├╢nnen Sprachnachrichten senden.</string>
<string name="only_your_contact_can_send_voice">Nur Ihr Kontakt kann Sprachnachrichten senden.</string>
<string name="voice_prohibited_in_this_chat">In diesem Chat sind Sprachnachrichten untersagt.</string>
<string name="allow_direct_messages">Das Senden von Direktnachrichten an Mitglieder erlauben.</string>
<string name="prohibit_direct_messages">Das Senden von Direktnachrichten an Mitglieder verbieten.</string>
<string name="allow_to_delete_messages">Unwiederbringliches L├╢schen von gesendeten Nachrichten erlauben.</string>
<string name="prohibit_message_deletion">Unwiederbringliches L├╢schen von Nachrichten verbieten.</string>
<string name="allow_to_send_voice">Senden von Sprachnachrichten erlauben.</string>
<string name="prohibit_sending_voice">Senden von Sprachnachrichten untersagen.</string>
<string name="both_you_and_your_contact_can_send_voice">Sowohl Ihr Kontakt, als auch Sie k├╢nnen Sprachnachrichten versenden.</string>
<string name="only_you_can_send_voice">Nur Sie k├╢nnen Sprachnachrichten versenden.</string>
<string name="only_your_contact_can_send_voice">Nur Ihr Kontakt kann Sprachnachrichten versenden.</string>
<string name="voice_prohibited_in_this_chat">In diesem Chat sind Sprachnachrichten nicht erlaubt.</string>
<string name="allow_direct_messages">Das Senden von Direktnachrichten an Gruppenmitglieder erlauben.</string>
<string name="prohibit_direct_messages">Das Senden von Direktnachrichten an Gruppenmitglieder nicht erlauben.</string>
<string name="allow_to_delete_messages">Unwiederbringliches l├╢schen von gesendeten Nachrichten erlauben.</string>
<string name="prohibit_message_deletion">Unwiederbringliches l├╢schen von Nachrichten nicht erlauben.</string>
<string name="allow_to_send_voice">Das Senden von Sprachnachrichten erlauben.</string>
<string name="prohibit_sending_voice">Das Senden von Sprachnachrichten nicht erlauben.</string>
<string name="group_members_can_send_dms">Gruppenmitglieder k├╢nnen Direktnachrichten versenden.</string>
<string name="direct_messages_are_prohibited_in_chat">In dieser Gruppe sind Direktnachrichten zwischen Mitgliedern nicht m├╢glich.</string>
<string name="direct_messages_are_prohibited_in_chat">In dieser Gruppe sind Direktnachrichten zwischen Mitgliedern nicht erlaubt.</string>
<string name="group_members_can_delete">Gruppenmitglieder k├╢nnen gesendete Nachrichten unwiederbringlich l├╢schen.</string>
<string name="message_deletion_prohibited_in_chat">In dieser Gruppe ist das unwiederbringliche L├╢schen von Nachrichten verboten.</string>
<string name="group_members_can_send_voice">Gruppenmitglieder k├╢nnen Sprachnachrichten senden.</string>
<string name="voice_messages_are_prohibited">In dieser Gruppe sind Sprachnachrichten untersagt.</string>
<string name="message_deletion_prohibited_in_chat">In dieser Gruppe ist das unwiederbringliche L├╢schen von Nachrichten nicht erlaubt.</string>
<string name="group_members_can_send_voice">Gruppenmitglieder k├╢nnen Sprachnachrichten versenden.</string>
<string name="voice_messages_are_prohibited">In dieser Gruppe sind Sprachnachrichten nicht erlaubt.</string>
<string name="live">LIVE</string>
<string name="view_security_code">Schauen Sie sich den Sicherheitscode an</string>
<string name="onboarding_notifications_mode_service">Sofort</string>
<string name="onboarding_notifications_mode_periodic_desc"><b>Gute Option f├╝r die Batterieausdauer</b>. Der Hintergrundservice ├╝berpr├╝ft alle 10 Minuten nach neuen Nachrichten. Sie k├╢nnen eventuell Anrufe und dringende Nachrichten verpassen.</string>
<string name="onboarding_notifications_mode_off_desc"><b>Beste Option f├╝r die Batterieausdauer</b>. Sie empfangen Benachrichtigungen nur solange die App abl├дuft. Der Hintergrundservice wird nicht genutzt!</string>
<string name="send_verb">Senden</string>
<string name="is_verified">%s wurde erfolgreich ├╝berpr├╝ft</string>
<string name="clear_verification">├Ьberpr├╝fung zur├╝cknehmen</string>
<string name="onboarding_notifications_mode_off">Solange die App abl├дuft</string>
<string name="onboarding_notifications_mode_subtitle">Kann sp├дter ├╝ber die Einstellungen ge├дndert werden.</string>
<string name="delete_after">L├╢schen nach</string>
<string name="ttl_hour">%d Stunde</string>
<string name="ttl_hours">%d Stunden</string>
<string name="ttl_m">%dm</string>
<string name="ttl_min">%d min</string>
<string name="ttl_month">%d Monat</string>
<string name="ttl_months">%d Monate</string>
<string name="ttl_mth">%dmth</string>
<string name="ttl_s">%ds</string>
<string name="ttl_sec">%d s</string>
<string name="ttl_d">%dd</string>
<string name="ttl_day">%d Tag</string>
<string name="ttl_days">%d Tage</string>
<string name="ttl_w">%dw</string>
<string name="ttl_week">%d Woche</string>
<string name="ttl_weeks">%d Wochen</string>
<string name="timed_messages">Verschwindende Nachrichten</string>
<string name="incorrect_code">Falscher Sicherheitscode!</string>
<string name="scan_code">Code scannen</string>
<string name="mark_code_verified">Als ├╝berpr├╝ft markieren</string>
<string name="scan_code_from_contacts_app">Scannen Sie den Sicherheitscode von der App Ihres Kontakts.</string>
<string name="security_code">Sicherheitscode</string>
<string name="onboarding_notifications_mode_periodic">Periodisch</string>
<string name="allow_to_send_disappearing">Erlauben Sie das Senden von verschwindenden Nachrichten.</string>
<string name="disappearing_prohibited_in_this_chat">In diesem Chat sind verschwindende Nachrichten nicht erlaubt.</string>
<string name="only_you_can_send_disappearing">Nur Sie k├╢nnen verschwindende Nachrichten senden.</string>
<string name="only_your_contact_can_send_disappearing">Nur Ihr Kontakt kann verschwindende Nachrichten senden.</string>
<string name="failed_to_parse_chat_title">Fehler beim Laden des Chats</string>
<string name="failed_to_parse_chats_title">Fehler beim Laden der Chats</string>
<string name="contact_developers">Bitte aktualisieren Sie die App und nehmen Sie Kontakt mit den Entwicklern auf.</string>
<string name="onboarding_notifications_mode_service_desc"><b>Ben├╢tigt mehr Leistung Ihrer Batterie</b>! Der Hintergrundservice l├дuft die ganze Zeit ab. Benachrichtigungen werden Ihnen sofort angezeigt, nachdem Sie neue Nachrichten erhalten haben.</string>
<string name="create_group_link">Gruppenlink erstellen</string>
<string name="allow_your_contacts_to_send_disappearing_messages">Erlauben Sie Ihren Kontakten das Senden von verschwindenden Nachrichten.</string>
<string name="prohibit_sending_disappearing_messages">Das Senden von verschwindenden Nachrichten verbieten.</string>
<string name="disappearing_messages_are_prohibited">In dieser Gruppe sind verschwindende Nachrichten nicht erlaubt.</string>
<string name="group_members_can_send_disappearing">Gruppenmitglieder k├╢nnen verschwindende Nachrichten senden.</string>
<string name="v4_3_improved_server_configuration_desc">F├╝gen Sie Server durch Scannen der QR Codes hinzu.</string>
<string name="v4_4_disappearing_messages">Verschwindende Nachrichten</string>
<string name="accept_feature">├Ьbernehmen</string>
<string name="accept_feature_set_1_day">Einen Tag festlegen</string>
<string name="invalid_chat">Ung├╝ltiger Chat</string>
<string name="live_message">Live Nachricht!</string>
<string name="send_live_message_desc">Eine Live Nachricht senden - der/die Empf├дnger sieht/sehen Nachrichtenaktualisierungen, w├дhrend Sie sie eingeben.</string>
<string name="send_live_message">Live Nachricht senden</string>
<string name="verify_security_code">Sicherheitscode ├╝berpr├╝fen</string>
<string name="is_not_verified">%s wurde noch nicht ├╝berpr├╝ft</string>
<string name="to_verify_compare">Um die Ende-zu-Ende-Verschl├╝sselung mit Ihrem Kontakt zu ├╝berpr├╝fen, m├╝ssen Sie den Sicherheitscode in Ihren Apps vergleichen oder scannen.</string>
<string name="onboarding_notifications_mode_title">Private Benachrichtigungen</string>
<string name="use_chat">Chat verwenden</string>
<string name="both_you_and_your_contact_can_send_disappearing">Ihr Kontakt und Sie k├╢nnen beide verschwindende Nachrichten senden.</string>
<string name="ttl_h">%dh</string>
<string name="v4_2_group_links">Gruppen-Links</string>
<string name="new_in_version">Neu in %s</string>
<string name="prohibit_sending_disappearing">Das Senden von verschwindenden Nachrichten verbieten.</string>
<string name="v4_2_security_assessment">Sicherheits-Gutachten</string>
<string name="v4_2_security_assessment_desc">Die Sicherheit von SimpleX Chat wurde von Trail of Bits ├╝berpr├╝ft.</string>
<string name="whats_new">Was ist neu</string>
<string name="v4_2_group_links_desc">Administratoren k├╢nnen Links f├╝r den Beitritt zu Gruppen erzeugen.</string>
<string name="v4_2_auto_accept_contact_requests">Kontaktanfragen automatisch annehmen</string>
<string name="v4_4_verify_connection_security_desc">Vergleichen Sie die Sicherheitscodes mit Ihren Kontakten.</string>
<string name="v4_3_improved_privacy_and_security_desc">App-Bildschirm in aktuellen Anwendungen verbergen.</string>
<string name="v4_3_improved_privacy_and_security">Verbesserte Privatsph├дre und Sicherheit</string>
<string name="v4_3_improved_server_configuration">Verbesserte Serverkonfiguration</string>
<string name="v4_3_irreversible_message_deletion">Unwiederbringliches l├╢schen einer Nachricht</string>
<string name="v4_4_live_messages">Live Nachrichten</string>
<string name="v4_3_voice_messages_desc">Max. 40 Sekunden, sofort erhalten.</string>
<string name="v4_4_live_messages_desc">Die Empf├дnger sehen Nachrichtenaktualisierungen, w├дhrend Sie sie eingeben.</string>
<string name="v4_4_disappearing_messages_desc">Gesendete Nachrichten werden nach der eingestellten Zeit gel├╢scht.</string>
<string name="v4_3_voice_messages">Sprachnachrichten</string>
<string name="allow_disappearing_messages_only_if">Erlauben Sie verschwindende Nachrichten nur dann, wenn es Ihnen Ihr Kontakt ebenfalls erlaubt.</string>
<string name="invalid_data">Ung├╝ltige Daten</string>
<string name="v4_4_verify_connection_security">Sicherheit der Verbindung ├╝berpr├╝fen</string>
<string name="v4_2_auto_accept_contact_requests_desc">Mit optionaler Begr├╝├Яungsmeldung.</string>
<string name="v4_3_irreversible_message_deletion_desc">Ihre Kontakte k├╢nnen die unwiederbringliche L├╢schung von Nachrichten erlauben.</string>
<string name="icon_descr_cancel_live_message">Livenachricht abbrechen</string>
<string name="feature_offered_item">beginne %s</string>
<string name="feature_offered_item_with_param">beginne %s: %2s</string>
<string name="feature_cancelled_item">beende %s</string>
<string name="core_simplexmq_version">simplexmq Version: v%s (%2s)</string>
<string name="delete_files_and_media_all">Alle Dateien l├╢schen</string>
<string name="messages_section_title">Nachrichten</string>
<string name="app_version_code">App Build: %s</string>
<string name="app_version_title">App Version</string>
<string name="app_version_name">App Version: v%s</string>
<string name="core_build_timestamp">Core ├╝bersetzt am: %s</string>
<string name="core_version">Core Version: v%s</string>
<string name="users_add">Profil hinzuf├╝gen</string>
<string name="users_delete_all_chats_deleted">Alle Chats und Nachrichten werden gel├╢scht! Dies kann nicht r├╝ckg├дngig gemacht werden!</string>
<string name="users_delete_profile_for">Chat-Profil l├╢schen f├╝r</string>
<string name="network_option_ping_count">PING Z├дhler</string>
<string name="update_network_session_mode_question">Transport-Isolations-Modus aktualisieren\?</string>
<string name="smp_servers_per_user">Server der neuen Verbindungen von Ihrem aktuellen Chat-Profil</string>
<string name="files_and_media_section">Dateien &amp; Medien</string>
<string name="network_session_mode_transport_isolation">Transport-Isolation</string>
<string name="users_delete_question">Chat-Profil l├╢schen\?</string>
<string name="error_deleting_user">Fehler beim L├╢schen des Benutzerprofils</string>
<string name="your_chat_profiles">Meine Chat-Profile</string>
<string name="network_session_mode_entity">Verbindung</string>
<string name="network_session_mode_user">Chat-Profil</string>
<string name="delete_files_and_media_for_all_users">Dateien f├╝r alle Chat-Profile l├╢schen</string>
<string name="network_session_mode_entity_description"><b>F├╝r jeden Kontakt und jedes Gruppenmitglied</b> wird eine separate TCP-Verbindung (und SOCKS-Berechtigung) genutzt.
\n
\n<b>Bitte beachten Sie</b>: Wenn Sie viele Verbindung haben, kann der Batterieverbrauch und die Datennutzung wesentlich h├╢her sein und einige Verbindungen k├╢nnen scheitern.</string>
<string name="network_session_mode_user_description"><b>F├╝r jedes von Ihnen in der App genutzte Chat-Profil</b> wird eine separate TCP-Verbindung (und SOCKS-Berechtigung) genutzt.</string>
<string name="users_delete_data_only">Nur lokale Profildaten</string>
<string name="users_delete_with_connections">Profil und Serververbindungen</string>
<string name="messages_section_description">Diese Einstellung gilt f├╝r Nachrichten in Ihrem aktuellen Chat-Profil</string>
<string name="your_chat_profiles_stored_locally">Ihre Chat-Profile werden nur lokal auf Ihrem Endger├дt gespeichert</string>
<string name="failed_to_create_user_duplicate_title">Doppelter Anzeigename!</string>
<string name="failed_to_create_user_title">Fehler beim Erstellen des Profils!</string>
<string name="failed_to_active_user_title">Fehler beim Umschalten des Profils!</string>
<string name="failed_to_create_user_duplicate_desc">Sie haben schon ein Chat-Profil mit dem gleichen Anzeigenamen. Bitte w├дhlen Sie einen anderen Namen aus.</string>
<string name="v4_5_multiple_chat_profiles">Mehrere Chat-Profile</string>
<string name="v4_5_reduced_battery_usage">Reduzierter Batterieverbrauch</string>
<string name="v4_5_private_filenames">Neutrale Dateinamen</string>
<string name="v4_5_message_draft_descr">Den letzten Nachrichtenentwurf, auch mit seinen Anh├дngen, aufbewahren.</string>
<string name="v4_5_transport_isolation_descr">Per Chat-Profil (Voreinstellung) oder per Verbindung (BETA).</string>
<string name="v4_5_italian_interface">Italienische Bedienoberfl├дche</string>
<string name="v4_4_french_interface">Franz├╢sische Bedienoberfl├дche</string>
<string name="v4_5_message_draft">Nachrichtenentwurf</string>
<string name="v4_5_reduced_battery_usage_descr">Weitere Verbesserungen sind bald verf├╝gbar!</string>
<string name="v4_5_multiple_chat_profiles_descr">Unterschiedliche Namen, Avatare und Transport-Isolation.</string>
<string name="v4_5_transport_isolation">Transport-Isolation</string>
<string name="v4_5_italian_interface_descr">Dank der Nutzer - Tragen Sie per Weblate bei!</string>
<string name="v4_4_french_interface_descr">Dank der Nutzer - Tragen Sie per Weblate bei!</string>
<string name="v4_5_private_filenames_descr">Bild- und Sprachdateinamen enthalten UTC, um Informationen zur Zeitzone zu sch├╝tzen.</string>
<string name="moderated_description">Moderiert</string>
</resources>

View File

@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="auth_unavailable">Autenticaci├│n no disponible</string>
<string name="accept_contact_button">Aceptar</string>
<string name="network_settings">Configuraci├│n de red avanzada</string>
<string name="onboarding_notifications_mode_off_desc"><b>Mejor para la bater├нa</b>. Recibir├бs notificaciones s├│lo cuando la aplicaci├│n se est├й ejecutando, NO se utilizar├б el servicio en segundo plano.</string>
<string name="onboarding_notifications_mode_periodic_desc"><b>Bueno para la bater├нa</b>. El servicio en segundo plano comprueba si hay mensajes nuevos cada 10 minutos. Puedes perderte llamadas y mensajes urgentes.</string>
<string name="accept_call_on_lock_screen">Aceptar</string>
<string name="full_backup">Copia de seguridad de los datos de la aplicaci├│n</string>
<string name="chat_item_ttl_day">un dia</string>
<string name="chat_item_ttl_month">un mes</string>
<string name="chat_item_ttl_week">una semana</string>
<string name="allow_disappearing_messages_only_if">Permitir que desaparezcan los mensajes s├│lo si su contacto lo permite.</string>
<string name="v4_3_improved_server_configuration_desc">A├▒adir servidores escaneando c├│digos QR.</string>
<string name="smp_servers_preset_add">A├▒adir servidores predefinidos</string>
<string name="all_group_members_will_remain_connected">Todos los miembros del grupo permanecer├бn conectados.</string>
<string name="allow_irreversible_message_deletion_only_if">Permitir el borrado irreversible de mensajes s├│lo si su contacto se lo permite.</string>
<string name="keychain_allows_to_receive_ntfs">Android Keystore se utilizar├б para almacenar de forma segura la frase de contrase├▒a despu├йs de reiniciar la aplicaci├│n o cambiar la frase de contrase├▒a - permitir├б recibir notificaciones.</string>
<string name="allow_your_contacts_to_send_disappearing_messages">Permitir a tus contactos enviar mensajes que desaparecen.</string>
<string name="allow_your_contacts_to_send_voice_messages">Permitir a tus contactos enviar mensajes de voz.</string>
<string name="chat_preferences_always">siempre</string>
<string name="notifications_mode_off_desc">La aplicaci├│n s├│lo puede recibir notificaciones cuando se est├б ejecutando, no se iniciar├б ning├║n servicio en segundo plano.</string>
<string name="settings_section_title_icon">ICONO DE LA APLICACI├УN</string>
<string name="incognito_random_profile_from_contact_description">Se enviar├б un perfil aleatorio al contacto del que recibi├│ este enlace</string>
<string name="turning_off_service_and_periodic">La optimizaci├│n de la bater├нa est├б activa, desactivando el servicio en segundo plano y las solicitudes peri├│dicas de nuevos mensajes. Puedes volver a activarlos a trav├йs de los ajustes.</string>
<string name="notifications_mode_service_desc">El servicio en segundo plano est├б siempre en funcionamiento тАУ las notificaciones se mostrar├бn en cuanto los mensajes est├йn disponibles.</string>
<string name="it_can_disabled_via_settings_notifications_still_shown"><b>Se puede desactivar a trav├йs de los ajustes</b> тАУ las notificaciones se seguir├бn mostrando mientras la app est├й en funcionamiento.</string>
<string name="notifications_mode_service">Siempre activo</string>
<string name="allow_verb">Permitir</string>
<string name="above_then_preposition_continuation">arriba, entonces:</string>
<string name="add_new_contact_to_create_one_time_QR_code"><b>A├▒adir nuevo contacto</b>: para crear tu c├│digo QR de un solo uso para tu contacto.</string>
<string name="accept_connection_request__question">┬┐Conectar mediante enlace de contacto\?</string>
<string name="accept_contact_incognito_button">Aceptar inc├│gnito</string>
<string name="clear_chat_warning">Se borrar├бn todos los mensajes - ┬бesto no se puede deshacer! Los mensajes se borrar├бn SOLO para ti.</string>
<string name="smp_servers_add">A├▒adir servidorтАж</string>
<string name="network_enable_socks_info">┬┐Acceder a los servidores a trav├йs del proxy SOCKS en el puerto 9050\? El proxy debe iniciarse antes de activar esta opci├│n.</string>
<string name="all_your_contacts_will_remain_connected">Todos tus contactos permanecer├бn conectados.</string>
<string name="appearance_settings">Apariencia</string>
<string name="app_version_title">Versi├│n de la aplicaci├│n</string>
<string name="network_session_mode_user_description">Se utilizar├б una conexi├│n TCP independiente (y una credencial SOCKS) <b>para cada perfil de chat que tengas en la aplicaci├│n</b>.</string>
<string name="network_session_mode_entity_description">Se utilizar├б una conexi├│n TCP independiente (y una credencial SOCKS) <b>para cada contacto y miembro del grupo</b>.
\n<b>Ten en cuenta</b>: si tienes muchas conexiones, tu consumo de bater├нa y tr├бfico puede ser sustancialmente mayor y algunas conexiones pueden fallar.</string>
<string name="a_plus_b">a + b</string>
<string name="about_simplex">Sobre SimpleX</string>
<string name="bold">negrita</string>
<string name="callstatus_accepted">llamada aceptada</string>
<string name="accept">Aceptar</string>
<string name="audio_call_no_encryption">llamada de audio (sin cifrado e2e)</string>
<string name="icon_descr_audio_call">llamada de audio</string>
<string name="settings_audio_video_calls">Llamadas de audio y v├нdeo</string>
<string name="icon_descr_audio_off">Audio desactivado</string>
<string name="icon_descr_audio_on">Audio activado</string>
<string name="integrity_msg_bad_id">ID de mensaje err├│neo</string>
<string name="auto_accept_images">Aceptar autom├бticamente im├бgenes</string>
<string name="users_delete_all_chats_deleted">Se borrar├бn todos los chats y mensajes - ┬бesto no se puede deshacer!</string>
<string name="accept_feature">Aceptar</string>
<string name="allow_to_send_disappearing">Permitir enviar mensajes que desaparecen.</string>
<string name="keychain_is_storing_securely">Android Keystore se utiliza para almacenar de forma segura la frase de contrase├▒a - permite que el servicio de notificaci├│n funcione.</string>
<string name="users_add">A├▒adir perfil</string>
<string name="incognito_random_profile_description">Se enviar├б un perfil aleatorio a tu contacto</string>
<string name="color_primary">Acento</string>
<string name="allow_your_contacts_irreversibly_delete">Permitir a tus contactos borrar irreversiblemente los mensajes enviados.</string>
<string name="allow_voice_messages_only_if">Permitir mensajes de voz s├│lo si tu contacto lo permite.</string>
<string name="allow_direct_messages">Permitir el env├нo de mensajes directos a los miembros.</string>
<string name="allow_to_delete_messages">Permitir borrar irreversiblemente los mensajes enviados.</string>
<string name="allow_to_send_voice">Permitir enviar mensajes de voz.</string>
<string name="v4_2_group_links_desc">Los administradores pueden crear los enlaces para unirse a los grupos.</string>
<string name="v4_2_auto_accept_contact_requests">Aceptar autom├бticamente solicitudes de contacto</string>
<string name="attach">Adjuntar</string>
<string name="integrity_msg_bad_hash">hash de mensaje err├│neo</string>
<string name="answer_call">Responder llamada</string>
<string name="group_member_role_admin">admin</string>
<string name="allow_voice_messages_question">┬┐Permitir mensajes de voz\?</string>
<string name="back">Atr├бs</string>
<string name="about_simplex_chat">Sobre <xliff:g id="appNameFull">SimpleX Chat</xliff:g></string>
<string name="smp_servers_add_to_another_device">A├▒adir a otro dispositivo</string>
<string name="app_version_name">Versi├│n de la aplicaci├│n: v%s</string>
<string name="accept_requests">Aceptar solicitudes</string>
<string name="accept_automatically">Autom├бticamente</string>
<string name="icon_descr_asked_to_receive">Solicita recibir la imagen</string>
<string name="impossible_to_recover_passphrase"><b>Ten en cuenta</b>: NO podr├бs recuperar o cambiar la contrase├▒a si la pierdes.</string>
<string name="both_you_and_your_contact_can_send_voice">Tanto t├║ como tu contacto pod├йis enviar mensajes de voz.</string>
<string name="onboarding_notifications_mode_service_desc"><b>┬бUsa m├бs bater├нa!</b> El servicio en segundo plano est├б siempre en funcionamiento - las notificaciones se mostrar├бn tan pronto como los mensajes est├йn disponibles.</string>
<string name="both_you_and_your_contacts_can_delete">Tanto t├║ como tu contacto pod├йis borrar de forma irreversible los mensajes enviados.</string>
<string name="both_you_and_your_contact_can_send_disappearing">Tanto t├║ como tu contacto pod├йis enviar mensajes de desaparici├│n.</string>
<string name="scan_QR_code_to_connect_to_contact_who_shows_QR_code"><b>Escanear c├│digo QR</b>: para conectar con tu contacto que te muestre c├│digo QR.</string>
</resources>

View File

@@ -21,7 +21,7 @@
<string name="error_joining_group">Erreur lors de la liaison avec le groupe</string>
<string name="sender_cancelled_file_transfer">L\'exp├йditeur a annul├й le transfert de fichiers.</string>
<string name="deleted_description">supprim├й</string>
<string name="marked_deleted_description">marquer comme supprim├й</string>
<string name="marked_deleted_description">supprim├й</string>
<string name="unknown_message_format">format de message inconnu</string>
<string name="display_name_connecting">connexionтАж</string>
<string name="description_you_shared_one_time_link_incognito">vous avez partag├й un lien unique en incognito</string>
@@ -143,7 +143,7 @@
<string name="group_preview_join_as">rejoindre en tant que %s</string>
<string name="group_preview_you_are_invited">vous ├кtes invit├й┬╖e au groupe</string>
<string name="chat_with_developers">Discuter avec les d├йveloppeurs</string>
<string name="tap_to_start_new_chat">Appuyez pour commencer un nouveau chat</string>
<string name="tap_to_start_new_chat">Appuyez ici pour d├йmarrer une nouvelle discussion</string>
<string name="you_have_no_chats">Vous n\'avez aucune discussion</string>
<string name="images_limit_title">Trop dтАЩimages !</string>
<string name="share_file">Partager le fichierтАж</string>
@@ -296,7 +296,7 @@
<string name="toast_permission_denied">Autorisation refus├йe !</string>
<string name="use_camera_button">Utiliser l\'Appareil photo</string>
<string name="thank_you_for_installing_simplex">Merci d\'avoir install├й <xliff:g id="appNameFull">SimpleX Chat</xliff:g> !</string>
<string name="you_can_connect_to_simplex_chat_founder">Vous pouvez <font color="#0088ff">vous connecter aux d├йveloppeurs de <xliff:g id="appNameFull">SimpleX Chat</xliff:g> pour leur poser toutes vos questions et pour recevoir des informations sur les mises ├а jour</font>.</string>
<string name="you_can_connect_to_simplex_chat_founder">Vous pouvez <font color="#0088ff">vous connecter aux d├йveloppeurs de <xliff:g id="appNameFull">SimpleX Chat</xliff:g> pour leur poser des questions et recevoir des r├йponses :</font>.</string>
<string name="above_then_preposition_continuation">ci-dessus, puis :</string>
<string name="add_new_contact_to_create_one_time_QR_code"><b>Ajouter un nouveau contact</b> : afin de cr├йer un code QR ├а usage unique pour votre contact.</string>
<string name="if_you_choose_to_reject_the_sender_will_not_be_notified">Si vous choisissez de la rejeter, l\'exp├йditeur┬╖rice NE sera PAS notifi├й┬╖e.</string>
@@ -328,14 +328,14 @@
<string name="network_use_onion_hosts">Utiliser les h├┤tes .onions</string>
<string name="network_use_onion_hosts_prefer_desc_in_alert">Les h├┤tes .onion seront utilis├йs lorsqu\'ils sont disponibles.</string>
<string name="network_use_onion_hosts_required_desc_in_alert">Les h├┤tes .onion seront n├йcessaires pour la connexion.</string>
<string name="you_control_servers_to_receive_your_contacts_to_send">Vous contr├┤lez par quel┬╖s serveur┬╖s vous pouvez <b>transmettre</b> ainsi que par quel┬╖s serveur┬╖s vous pouvez <b>recevoir</b> des messages de vos contacts.</string>
<string name="you_control_servers_to_receive_your_contacts_to_send">Vous contr├┤lez par quel┬╖s serveur┬╖s vous pouvez <b>transmettre</b> ainsi que par quel┬╖s serveur┬╖s vous pouvez <b>recevoir</b> les messages de vos contacts.</string>
<string name="your_settings">Vos param├иtres</string>
<string name="chat_lock">SimpleX Lock</string>
<string name="chat_console">Console du chat</string>
<string name="smp_servers">Serveurs SMP</string>
<string name="smp_servers_test_servers">Tester les serveurs</string>
<string name="smp_servers_save">Sauvegarder les serveurs</string>
<string name="smp_servers_scan_qr">Scanner le code QR du serveur</string>
<string name="smp_servers_scan_qr">Scanner un code QR de serveur</string>
<string name="smp_servers_use_server">Utiliser ce serveur</string>
<string name="smp_servers_use_server_for_new_conn">Utiliser pour les nouvelles connexions</string>
<string name="smp_servers_add_to_another_device">Ajouter ├а un autre appareil</string>
@@ -359,8 +359,8 @@
<string name="network_use_onion_hosts_prefer_desc">Les h├┤tes .onion seront utilis├йs lorsqu\'ils sont disponibles.</string>
<string name="appearance_settings">Apparence</string>
<string name="create_address">Cr├йer une adresse</string>
<string name="you_can_share_your_address_anybody_will_be_able_to_connect">Vous pouvez partager votre adresse sous forme de lien ou de code QR - n\'importe qui pourra se connecter ├а vous. Vous ne perdrez pas vos contacts si vous les supprimez par la suite.</string>
<string name="your_chat_profile">Votre profil de chat</string>
<string name="you_can_share_your_address_anybody_will_be_able_to_connect">Vous pouvez partager votre adresse sous forme de lien ou de code QR - n\'importe qui pourra se connecter ├а vous. Vous ne perdrez pas vos contacts si vous la supprimez par la suite.</string>
<string name="your_current_profile">Votre profil de chat</string>
<string name="edit_image">Modifier l\'image</string>
<string name="save_and_notify_contacts">Sauvegarder et notifier les contacts</string>
<string name="save_and_notify_group_members">Sauvegarder et en informer les membres du groupe</string>
@@ -380,8 +380,8 @@
<string name="callstate_received_answer">r├йponse re├зuтАж</string>
<string name="callstate_received_confirmation">confimation re├зuтАж</string>
<string name="callstate_connecting">connexionтАж</string>
<string name="opensource_protocol_and_code_anybody_can_run_servers">Protocole et code open-source тАУ tout le monde peut faire fonctionner les serveurs.</string>
<string name="to_protect_privacy_simplex_has_ids_for_queues">Pour prot├йger la vie priv├йe, au lieu d\'ID d\'utilisateur utilis├йs par toutes les autres plateformes, <xliff:g id="appName">SimpleX</xliff:g> poss├иde des identifiants pour les files d\'attente de messages, distincts pour chacun de vos contacts.</string>
<string name="opensource_protocol_and_code_anybody_can_run_servers">Protocole et code open-source тАУ n\'importe qui peut heberger un serveur.</string>
<string name="to_protect_privacy_simplex_has_ids_for_queues">Pour prot├йger votre vie priv├йe, au lieu d\'IDs utilis├йs par toutes les autres plateformes, <xliff:g id="appName">SimpleX</xliff:g> poss├иde des IDs pour les queues de messages, distinctes pour chacun de vos contacts.</string>
<string name="read_more_in_github">Plus d\'informations sur notre GitHub.</string>
<string name="paste_the_link_you_received">Coller le lien re├зu</string>
<string name="use_chat">Utiliser le chat</string>
@@ -461,13 +461,13 @@
<string name="privacy_redefined">La vie priv├йe red├йfinie</string>
<string name="first_platform_without_user_ids">La 1├иre plateforme sans aucun identifiant d\'utilisateur тАУ priv├йe par design.</string>
<string name="immune_to_spam_and_abuse">Prot├йg├й du spam et des abus</string>
<string name="people_can_connect_only_via_links_you_share">Les gens peuvent se connecter ├а vous uniquement via les liens que vous partagez.</string>
<string name="people_can_connect_only_via_links_you_share">On ne peut se connecter ├а vous quтАЩavec les liens que vous partagez.</string>
<string name="decentralized">D├йcentralis├й</string>
<string name="create_your_profile">Cr├йez votre profil</string>
<string name="make_private_connection">├Йtablir une connexion priv├йe</string>
<string name="how_it_works">Comment ├зa fonctionne</string>
<string name="how_simplex_works">Comment <xliff:g id="appName">SimpleX</xliff:g> fonctionne</string>
<string name="many_people_asked_how_can_it_deliver">Beaucoup se demande : <i>si <xliff:g id="appName">SimpleX</xliff:g> n\'a pas d\'identifiant d\'utilisateur, comment peut-il transmettre des messages \?</i></string>
<string name="many_people_asked_how_can_it_deliver">Beaucoup se demandent : <i>si <xliff:g id="appName">SimpleX</xliff:g> n\'a pas d\'identifiant d\'utilisateur, comment peut-il transmettre des messages \?</i></string>
<string name="only_client_devices_store_contacts_groups_e2e_encrypted_messages">Seuls les appareils clients stockent les profils des utilisateurs, les contacts, les groupes et les messages envoy├йs avec un <b>chiffrement de bout en bout ├а deux couches</b>.</string>
<string name="read_more_in_github_with_link">Pour en savoir plus, consultez notre <font color="#0088ff">GitHub repository</font>.</string>
<string name="onboarding_notifications_mode_periodic_desc"><b>Batterie peu utilis├йe</b>. Le service de fond v├йrifie les nouveaux messages toutes les 10 minutes. Vous risquez de manquer des appels et des messages urgents.</string>
@@ -519,7 +519,6 @@
<string name="run_chat_section">LANCER LE CHAT</string>
<string name="stop_chat_question">Arr├кter le chat \?</string>
<string name="restart_the_app_to_use_imported_chat_database">Red├йmarrez l\'application pour utiliser la base de donn├йes de chat import├йe.</string>
<string name="data_section">DONN├ЙES</string>
<string name="chat_item_ttl_day">1 jour</string>
<string name="delete_messages">Supprimer les messages</string>
<string name="save_passphrase_in_keychain">Sauvegarder la phrase secr├иte dans le keystore</string>
@@ -624,7 +623,6 @@
<string name="privacy_and_security">Vie priv├йe et s├йcurit├й</string>
<string name="protect_app_screen">Prot├йger l\'├йcran de l\'app</string>
<string name="auto_accept_images">Images auto-accept├йes</string>
<string name="transfer_images_faster">Transfert d\'images plus rapide</string>
<string name="full_backup">Sauvegarde des donn├йes de l\'app</string>
<string name="settings_section_title_you">VOUS</string>
<string name="settings_section_title_help">AIDE</string>
@@ -643,7 +641,6 @@
<string name="delete_chat_profile_question">Supprimer le profil du chat \?</string>
<string name="stop_chat_to_enable_database_actions">Arr├кter le chat pour agir sur la base de donn├йes.</string>
<string name="delete_files_and_media_question">Supprimer les fichiers et m├йdias \?</string>
<string name="delete_files_and_media">"Supprimer les fichiers m├йdias"</string>
<string name="delete_files_and_media_desc">Cette action ne peut ├кtre annul├йe - tous les fichiers et m├йdias re├зus et envoy├йs seront supprim├йs. Les photos ├а faible r├йsolution seront conserv├йes.</string>
<string name="no_received_app_files">Aucun fichier re├зu ou envoy├й</string>
<string name="chat_item_ttl_month">1 mois</string>
@@ -778,7 +775,7 @@
<string name="chat_preferences_off">off</string>
<string name="direct_messages">Messages dynamiques</string>
<string name="full_deletion">Supprimer pour tous</string>
<string name="only_you_can_delete_messages">Vous ├кtes le seul ├а pouvoir supprimer des messages de mani├иre irr├йversible (votre contact peut les marquer pour suppression).</string>
<string name="only_you_can_delete_messages">Vous ├кtes le seul ├а pouvoir supprimer des messages de mani├иre irr├йversible (votre contact peut les marquer comme supprim├й).</string>
<string name="conn_stats_section_title_servers">SERVEURS</string>
<string name="receiving_via">R├йception via</string>
<string name="theme_system">Syst├иme</string>
@@ -786,7 +783,7 @@
<string name="prohibit_direct_messages">Interdire l\'envoi de messages directs aux membres.</string>
<string name="group_members_can_delete">Les membres du groupe peuvent supprimer de mani├иre irr├йversible les messages envoy├йs.</string>
<string name="message_deletion_prohibited_in_chat">La suppression irr├йversible de messages est interdite dans ce groupe.</string>
<string name="sending_via">Envoy├й via</string>
<string name="sending_via">Envoi via</string>
<string name="network_status">├Йtat du r├йseau</string>
<string name="switch_receiving_address">Changer d\'adresse de r├йception</string>
<string name="create_secret_group_title">Cr├йer un groupe secret</string>
@@ -862,9 +859,9 @@
<string name="chat_preferences_yes">oui</string>
<string name="allow_disappearing_messages_only_if">Autorise les messages ├йph├йm├иres seulement si votre contact les autorises.</string>
<string name="allow_irreversible_message_deletion_only_if">Autoriser la suppression irr├йversible des messages uniquement si votre contact vous l\'autorise.</string>
<string name="only_your_contact_can_delete">Seul votre contact peut supprimer de mani├иre irr├йversible des messages (vous pouvez les marquer pour suppression).</string>
<string name="only_your_contact_can_delete">Seul votre contact peut supprimer de mani├иre irr├йversible des messages (vous pouvez les marquer comme supprim├й).</string>
<string name="only_your_contact_can_send_disappearing">Seulement votre contact peut envoyer des messages ├йph├йm├иres.</string>
<string name="both_you_and_your_contact_can_send_disappearing">Vous et votre contact peuvent envoyer des messages ├йph├йm├иres.</string>
<string name="both_you_and_your_contact_can_send_disappearing">Vous et votre contact ├кtes tous deux en mesure d\'envoyer des messages ├йph├йm├иres.</string>
<string name="voice_messages_are_prohibited">Les messages vocaux sont interdits dans ce groupe.</string>
<string name="group_display_name_field">Nom affich├й du groupe :</string>
<string name="group_unsupported_incognito_main_profile_sent">Le mode Incognito n\'est pas support├й ici - votre profil principal sera envoy├й aux membres du groupe</string>
@@ -879,9 +876,9 @@
<string name="network_options_reset_to_defaults">R├йinitialisation des valeurs par d├йfaut</string>
<string name="network_option_protocol_timeout">D├йlai du protocole</string>
<string name="network_option_ping_interval">Intervalle de PING</string>
<string name="both_you_and_your_contacts_can_delete">Vous et votre contact pouvez tous deux supprimer de mani├иre irr├йversible les messages envoy├йs.</string>
<string name="both_you_and_your_contacts_can_delete">Vous et votre contact ├кtes tous deux en mesure de supprimer de mani├иre irr├йversible les messages envoy├йs.</string>
<string name="message_deletion_prohibited">La suppression irr├йversible de message est interdite dans ce chat.</string>
<string name="both_you_and_your_contact_can_send_voice">Vous et votre contact pouvez tous deux supprimer de mani├иre irr├йversible les messages envoy├йs.</string>
<string name="both_you_and_your_contact_can_send_voice">Vous et votre contact ├кtes tous deux en mesure d\'envoyer des messages vocaux.</string>
<string name="only_your_contact_can_send_voice">Seul votre contact peut envoyer des messages vocaux.</string>
<string name="voice_prohibited_in_this_chat">Les messages vocaux sont interdits dans ce chat.</string>
<string name="disappearing_prohibited_in_this_chat">Les messages ├йph├йm├иres sont interdits dans cette discussion.</string>
@@ -892,4 +889,82 @@
<string name="prohibit_message_deletion">Interdire la suppression irr├йversible des messages.</string>
<string name="group_members_can_send_dms">Les membres du groupe peuvent envoyer des messages directs.</string>
<string name="direct_messages_are_prohibited_in_chat">Les messages directs entre membres sont interdits dans ce groupe.</string>
<string name="v4_4_live_messages_desc">Les destinataires voient les mises ├а jour au fur et ├а mesure que vous les tapez.</string>
<string name="v4_4_verify_connection_security">V├йrifier la s├йcurit├й de la connexion</string>
<string name="v4_4_verify_connection_security_desc">Comparez les codes de s├йcurit├й avec vos contacts.</string>
<string name="new_in_version">Nouveaut├йs de la %s</string>
<string name="v4_2_security_assessment">├Йvaluation de s├йcurit├й</string>
<string name="v4_2_group_links">Liens de groupe</string>
<string name="v4_2_auto_accept_contact_requests_desc">Avec message de bienvenue facultatif.</string>
<string name="v4_3_voice_messages">Messages vocaux</string>
<string name="v4_3_voice_messages_desc">Max 40 secondes, r├йception imm├йdiate.</string>
<string name="v4_3_irreversible_message_deletion">Suppression irr├йversible des messages</string>
<string name="v4_3_irreversible_message_deletion_desc">Vos contacts peuvent autoriser la suppression compl├иte des messages.</string>
<string name="v4_3_improved_privacy_and_security">Une meilleure s├йcurit├й et protection de la vie priv├йe</string>
<string name="v4_3_improved_privacy_and_security_desc">Masquer l\'├йcran de l\'app dans les apps r├йcentes.</string>
<string name="v4_4_disappearing_messages">Messages ├йph├йm├иres</string>
<string name="v4_4_disappearing_messages_desc">Les messages envoy├йs seront supprim├йs apr├иs une dur├йe d├йtermin├йe.</string>
<string name="v4_4_live_messages">Messages dynamiques</string>
<string name="accept_feature">Accepter</string>
<string name="v4_2_auto_accept_contact_requests">Demandes de contact auto-accept├йes</string>
<string name="whats_new">Quoi de neuf \?</string>
<string name="v4_2_group_links_desc">Les admins peuvent cr├йer les liens qui permettent de rejoindre les groupes.</string>
<string name="accept_feature_set_1_day">D├йfinir 1 jour</string>
<string name="v4_2_security_assessment_desc">La s├йcurit├й de SimpleX Chat a ├йt├й audit├йe par Trail of Bits.</string>
<string name="v4_3_improved_server_configuration">Configuration de serveur am├йlior├йe</string>
<string name="v4_3_improved_server_configuration_desc">Ajoutez des serveurs en scannant des codes QR.</string>
<string name="invalid_data">donn├йes invalides</string>
<string name="invalid_chat">chat invalide</string>
<string name="icon_descr_cancel_live_message">Annuler le message dynamique</string>
<string name="feature_offered_item">offert %s</string>
<string name="feature_offered_item_with_param">offert %s: %2s</string>
<string name="feature_cancelled_item">annul├й %s</string>
<string name="app_version_title">Version de l\'application</string>
<string name="core_simplexmq_version">simplexmq : v%s (%2s)</string>
<string name="app_version_code">Build de l\'app : %s</string>
<string name="app_version_name">Version de l\'app : v%s</string>
<string name="core_build_timestamp">C┼Уur compil├й le : %s</string>
<string name="core_version">Version du c┼Уur : v%s</string>
<string name="network_option_ping_count">Nombre de PING</string>
<string name="users_delete_all_chats_deleted">Toutes les discussions et tous les messages seront supprim├йs - il est impossible de revenir en arri├иre !</string>
<string name="delete_files_and_media_all">Effacer tous les fichiers</string>
<string name="users_delete_profile_for">Supprimer le profil de chat pour</string>
<string name="network_session_mode_user_description">Une connexion TCP distincte (et un identifiant SOCKS) sera utilis├йe <b>pour chaque profil de chat que vous avez dans l\'application</b>.</string>
<string name="users_delete_question">Supprimer le profil du chat \?</string>
<string name="files_and_media_section">Fichiers &amp; m├йdias</string>
<string name="messages_section_title">Messages</string>
<string name="smp_servers_per_user">Les serveurs pour les nouvelles connexions de votre profil de chat actuel</string>
<string name="messages_section_description">Ce param├иtre s\'applique aux messages de votre profil de chat actuel</string>
<string name="network_session_mode_entity">Connexion</string>
<string name="delete_files_and_media_for_all_users">Effacer les fichiers de tous les profils de chat</string>
<string name="users_delete_with_connections">Profil et connexions au serveur</string>
<string name="network_session_mode_transport_isolation">Isolement du transport</string>
<string name="update_network_session_mode_question">Mettre ├а jour le mode d\'isolation du transport \?</string>
<string name="your_chat_profiles_stored_locally">Vos profils de chat sont stock├йs localement, uniquement sur votre appareil</string>
<string name="network_session_mode_entity_description">Une connexion TCP distincte (et identifiant SOCKS) sera utilis├йe <b>pour chaque contact et membre de groupe</b>.
\n<b>Veuillez noter</b> : si vous avez de nombreuses connexions, votre consommation de batterie et de r├йseau peut ├кtre nettement plus ├йlev├йe et certaines liaisons peuvent ├йchouer.</string>
<string name="network_session_mode_user">Profil de chat</string>
<string name="users_add">Ajouter un profil</string>
<string name="users_delete_data_only">Donn├йes de profil local uniquement</string>
<string name="error_deleting_user">Erreur lors de la suppression du profil utilisateur</string>
<string name="your_chat_profiles">Vos profils de chat</string>
<string name="failed_to_active_user_title">Erreur lors du changement de profil !</string>
<string name="failed_to_create_user_title">Erreur lors de la cr├йation du profil !</string>
<string name="failed_to_create_user_duplicate_desc">Vous avez d├йj├а un profil de chat avec ce m├кme nom affich├й. Veuillez choisir un autre nom.</string>
<string name="failed_to_create_user_duplicate_title">Nom d\'affichage en double !</string>
<string name="v4_4_french_interface">Interface en fran├зais</string>
<string name="v4_5_transport_isolation_descr">Par profil de chat (par d├йfaut) ou par connexion (BETA).</string>
<string name="v4_5_italian_interface">Interface en italien</string>
<string name="v4_5_message_draft">Brouillon de message</string>
<string name="v4_5_reduced_battery_usage_descr">Plus d\'am├йliorations ├а venir !</string>
<string name="v4_5_multiple_chat_profiles">Diff├йrents profils de chat</string>
<string name="v4_5_message_draft_descr">Conserver le brouillon du dernier message, avec les pi├иces jointes.</string>
<string name="v4_5_reduced_battery_usage">R├йduction de la consommation de batterie</string>
<string name="v4_5_private_filenames">Noms de fichiers priv├йs</string>
<string name="v4_5_italian_interface_descr">Merci aux utilisateurs - contribuez via Weblate !</string>
<string name="v4_4_french_interface_descr">Merci aux utilisateurs - contribuez via Weblate !</string>
<string name="v4_5_private_filenames_descr">Pour pr├йserver le fuseau horaire, les fichiers image/voix utilisent le syst├иme UTC.</string>
<string name="v4_5_transport_isolation">Isolation du transport</string>
<string name="v4_5_multiple_chat_profiles_descr">Diff├йrents noms, avatars et mode d\'isolation de transport.</string>
<string name="moderated_description">mod├йr├й</string>
</resources>

View File

@@ -0,0 +1,183 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="share_image">рдЪрд┐рддреНрд░ рд╕рд╛рдЭрд╛ рдХрд░реЗрдВтАж</string>
<string name="chat_preferences_off">рдмрдВрдж</string>
<string name="accept_feature_set_1_day">1 рджрд┐рди рдирд┐рд░реНрдзрд╛рд░рд┐рдд рдХрд░реЗрдВ</string>
<string name="v4_3_improved_server_configuration_desc">рдХреНрдпреВрдЖрд░ рд╕рдВрд╣рд┐рддрд╛ рд╕реНрдХреИрди рдХрд░рдХреЗ рд╕рд░реНрд╡рд░ рдЬреЛрдбрд╝реЗрдВред</string>
<string name="group_preview_you_are_invited">рдЖрдкрдХреЛ рд╕рдореВрд╣ рдореЗрдВ рдЖрдордВрддреНрд░рд┐рдд рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ</string>
<string name="icon_descr_server_status_connected">рдЬреБрдбрд╝реЗ рд╣реБрдП</string>
<string name="use_camera_button">рдХреИрдорд░реЗ рдХрд╛ рдкреНрд░рдпреЛрдЧ рдХрд░реЗрдВ</string>
<string name="above_then_preposition_continuation">рдКрдкрд░,рддрдм:</string>
<string name="accept_contact_button">рд╕реНрд╡реАрдХрд╛рд░ рдХрд░рдирд╛</string>
<string name="connect_button">рдЬреБрдбрд┐рдпреЗ</string>
<string name="your_contact_address">рдЖрдкрдХрд╛ рд╕рдВрдкрд░реНрдХ рдкрддрд╛</string>
<string name="smp_servers_add_to_another_device">рджреВрд╕рд░реЗ рдЙрдкрдХрд░рдг рдореЗрдВ рдЬреЛрдбрд╝реЗрдВ</string>
<string name="bold">рдирд┐рдбрд░</string>
<string name="answer_call">рдХреЙрд▓ рдХрд╛ рдЙрддреНрддрд░ рджреЗрдВ</string>
<string name="settings_section_title_you">рддреБрдо</string>
<string name="settings_section_title_settings">рд╕рдорд╛рдпреЛрдЬрди</string>
<string name="chat_item_ttl_month">1 рдорд╣реАрдирд╛</string>
<string name="rcv_group_event_member_connected">рдЬреБрдбрд╝реЗ рд╣реБрдП</string>
<string name="group_member_role_admin">рд╡реНрдпрд╡рд╕реНрдерд╛рдкрдХ</string>
<string name="all_group_members_will_remain_connected">рд╕рдореВрд╣ рдХреЗ рд╕рднреА рд╕рджрд╕реНрдп рдЬреБрдбрд╝реЗ рд░рд╣реЗрдВрдЧреЗред</string>
<string name="change_verb">рдкрд░рд┐рд╡рд░реНрддрди</string>
<string name="sending_via">рдорд╛рдзреНрдпрдо рд╕реЗ рднреЗрдЬрд╛ рдЬрд╛ рд░рд╣рд╛ рд╣реИ</string>
<string name="feature_off">рдмрдВрдж</string>
<string name="whats_new">рдирдпрд╛ рдХреНрдпрд╛ рд╣реИ</string>
<string name="v4_2_group_links_desc">рд╡реНрдпрд╡рд╕реНрдерд╛рдкрдХ рд╕рдореВрд╣реЛрдВ рдореЗрдВ рд╢рд╛рдорд┐рд▓ рд╣реЛрдиреЗ рдХреЗ рд▓рд┐рдП рд▓рд┐рдВрдХ рдмрдирд╛ рд╕рдХрддреЗ рд╣реИрдВред</string>
<string name="chat_item_ttl_day">1 рджрд┐рди</string>
<string name="chat_item_ttl_week">1 рд╕рдкреНрддрд╛рд╣</string>
<string name="about_simplex">рд╕рд┐рдВрдкрд▓рдПрдХреНрд╕ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ</string>
<string name="about_simplex_chat">рдмрд╛рд░реЗ рдореЗрдВ <xliff:g id="appNameFull">рд╕рд┐рдВрдкреНрд▓реЗрдХреНрд╕ рдЪреИрдЯ</xliff:g></string>
<string name="accept_call_on_lock_screen">рд╕реНрд╡реАрдХрд╛рд░ рдХрд░рдирд╛</string>
<string name="accept">рд╕реНрд╡реАрдХрд╛рд░ рдХрд░рдирд╛</string>
<string name="accept_feature">рд╕реНрд╡реАрдХрд╛рд░ рдХрд░рдирд╛</string>
<string name="accept_connection_request__question">рд╕рдВрдмрдВрдз рдЕрдиреБрд░реЛрдз рд╕реНрд╡реАрдХрд╛рд░ рдХрд░реЗрдВ\?</string>
<string name="callstatus_accepted">рд╕реНрд╡реАрдХреГрдд рдХреЙрд▓</string>
<string name="accept_contact_incognito_button">рдЧреБрдкреНрдд рд╕реНрд╡реАрдХрд╛рд░ рдХрд░реЗрдВ</string>
<string name="accept_requests">рдирд┐рд╡реЗрджрди рд╕реНрд╡реАрдХрд╛рд░ рдХрд░реЛ</string>
<string name="smp_servers_preset_add">рдкреВрд░реНрд╡рдирд┐рд░реНрдзрд╛рд░рд┐рдд рд╕рд░реНрд╡рд░ рдЬреЛрдбрд╝реЗрдВ</string>
<string name="users_add">рдкреНрд░реЛрдлрд╝рд╛рдЗрд▓ рдЬреЛрдбрд╝реЗрдВ</string>
<string name="smp_servers_add">рд╕рд░реНрд╡рд░ рдЬреЛрдбрд╝реЗтАж</string>
<string name="notifications_mode_service">рд╣рдореЗрд╢рд╛ рдмрдиреЗ рд░рд╣реЗрдВ</string>
<string name="attach">рд╕рдВрд▓рдЧреНрди рдХрд░рдирд╛</string>
<string name="network_settings">рдЙрдиреНрдирдд рд╕рдВрдЬрд╛рд▓ рд╕рдорд╛рдпреЛрдЬрди</string>
<string name="users_delete_all_chats_deleted">рд╕рднреА рдмрд╛рддрдЪреАрдд рдФрд░ рд╕рдВрджреЗрд╢ рд╣рдЯрд╛ рджрд┐рдП рдЬрд╛рдПрдВрдЧреЗ - рдЗрд╕реЗ рдкреВрд░реНрд╡рд╡рдд рдирд╣реАрдВ рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛!</string>
<string name="chat_preferences_always">рд╣рдореЗрд╢рд╛</string>
<string name="allow_verb">рдЕрдиреБрдорддрд┐ рджреЗрдирд╛</string>
<string name="appearance_settings">рджрд┐рдЦрд╛рд╡рдЯ</string>
<string name="cancel_verb">рд░рджреНрдж рдХрд░рдирд╛</string>
<string name="icon_descr_cancel_file_preview">рдлрд╝рд╛рдЗрд▓ рдкреВрд░реНрд╡рд╛рд╡рд▓реЛрдХрди рд░рджреНрдж рдХрд░реЗрдВ</string>
<string name="icon_descr_cancel_image_preview">рдЫрд╡рд┐ рдкреВрд░реНрд╡рд╛рд╡рд▓реЛрдХрди рд░рджреНрдж рдХрд░реЗрдВ</string>
<string name="clear_verb">рд╕рд╛рдлрд╝</string>
<string name="colored">рд░рдВрдЧреАрди</string>
<string name="callstate_connected">рдЬреБрдбрд╝реЗ рд╣реБрдП</string>
<string name="smp_server_test_connect">рдЬреБрдбрд┐рдпреЗ</string>
<string name="connect_via_link_verb">рдЬреБрдбрд┐рдпреЗ</string>
<string name="server_connected">рдЬреБрдбрд╝реЗ рд╣реБрдП</string>
<string name="group_member_role_owner">рд╕реНрд╡рд╛рдореА</string>
<string name="group_member_status_connected">рдЬреБрдбрд╝реЗ рд╣реБрдП</string>
<string name="notification_contact_connected">рдЬреБрдбрд╝реЗ рд╣реБрдП</string>
<string name="you_joined_this_group">рдЖрдк рдЗрд╕ рд╕рдореВрд╣ рдореЗрдВ рд╢рд╛рдорд┐рд▓ рд╣реЛ рдЧрдП</string>
<string name="group_info_member_you">рддреБрдо: <xliff:g id="group_info_you">%1$s</xliff:g></string>
<string name="you_are_invited_to_group">рдЖрдкрдХреЛ рд╕рдореВрд╣ рдореЗрдВ рдЖрдордВрддреНрд░рд┐рдд рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ</string>
<string name="snd_conn_event_switch_queue_phase_completed">рддреБрдордиреЗ рдкрддрд╛ рдмрджрд▓ рд▓рд┐рдпрд╛</string>
<string name="snd_group_event_user_left">рдЖрдк рдЪрд▓реЗ рдЧрдП</string>
<string name="unknown_error">рдЕрдЬреНрдЮрд╛рдд рддреНрд░реБрдЯрд┐</string>
<string name="chat_preferences_you_allow">рдЖрдк рдЖрдЬреНрдЮрд╛ рджреЗрдВ</string>
<string name="welcome">рд╕реНрд╡рд╛рдЧрдд!</string>
<string name="la_notice_turn_on">рдЪрд╛рд▓реВ рдХрд░реЛ</string>
<string name="section_title_welcome_message">рд╕реНрд╡рд╛рдЧрдд рд╕рдВрджреЗрд╢</string>
<string name="unknown_message_format">рдЕрдЬреНрдЮрд╛рдд рд╕рдВрджреЗрд╢ рдкреНрд░рд╛рд░реВрдк</string>
<string name="personal_welcome">рд╕реНрд╡рд╛рдЧрдд <xliff:g>%1$s</xliff:g>!</string>
<string name="callstate_starting">рд╢реБрд░реБрдЖрдд</string>
<string name="send_verb">рднреЗрдЬрдирд╛</string>
<string name="save_color">рд░рдВрдЧ рдмрдЪрд╛рдУ</string>
<string name="share_verb">рд╕рд╛рдЭрд╛ рдХрд░рдирд╛</string>
<string name="reject_contact_button">рдЕрд╕реНрд╡реАрдХрд╛рд░</string>
<string name="network_use_onion_hosts_required">рдЖрд╡рд╢реНрдпрдХ</string>
<string name="reject">рдЕрд╕реНрд╡реАрдХрд╛рд░</string>
<string name="open_verb">рдЦреБрд▓рд╛</string>
<string name="group_member_status_removed">рдирд┐рдХрд╛рд▓рд╛ рдЧрдпрд╛</string>
<string name="rcv_group_event_member_deleted">рдирд┐рдХрд╛рд▓рд╛ рдЧрдпрд╛ <xliff:g id="member profile" example="alice (Alice)">%1$s</xliff:g></string>
<string name="reply_verb">рдЬрд╡рд╛рдм рджреЗ рджреЛ</string>
<string name="leave_group_button">рдЫреЛрдбрд╝рдирд╛</string>
<string name="mark_read">рдкрдврд╝рд╛ рд╣реБрдЖ рдЪрд┐рд╣реНрдирд┐рдд рдХрд░реЗрдВ</string>
<string name="icon_descr_more_button">рдЕрдзрд┐рдХ</string>
<string name="network_use_onion_hosts_no">рдирд╣реАрдВ</string>
<string name="chat_item_ttl_none">рдХрднреА рдирд╣реАрдВ</string>
<string name="group_member_status_invited">рдЖрдордВрддреНрд░рд┐рдд</string>
<string name="delete_after">рдмрд╛рдж рдорд┐рдЯрд╛ рджреЗрдВ</string>
<string name="display_name_invited_to_connect">рдЬреБрдбрд╝рдиреЗ рдХреЗ рд▓рд┐рдП рдЖрдордВрддреНрд░рд┐рдд рдХрд┐рдпрд╛</string>
<string name="rcv_group_event_invited_via_your_group_link">рдЖрдкрдХреЗ рд╕рдореВрд╣ рд▓рд┐рдВрдХ рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдЖрдордВрддреНрд░рд┐рдд рдХрд┐рдпрд╛ рдЧрдпрд╛</string>
<string name="rcv_group_event_member_added">рдЖрдордВрддреНрд░рд┐рдд <xliff:g id="member profile" example="alice (Alice)">%1$s</xliff:g></string>
<string name="icon_descr_add_members">рд╕рджрд╕реНрдпреЛрдВ рдХреЛ рдЖрдордВрддреНрд░рд┐рдд рдХрд░реЛ</string>
<string name="v4_3_irreversible_message_deletion">рдЕрдкрд░рд┐рд╡рд░реНрддрдиреАрдп рд╕рдВрджреЗрд╢ рд╡рд┐рд▓реЛрдкрди</string>
<string name="button_add_members">рд╕рджрд╕реНрдпреЛрдВ рдХреЛ рдЖрдордВрддреНрд░рд┐рдд рдХрд░реЛ</string>
<string name="invite_to_group_button">рд╕рдореВрд╣ рдореЗрдВ рдЖрдордВрддреНрд░рд┐рдд рдХрд░реЗрдВ</string>
<string name="message_deletion_prohibited_in_chat">рдЗрд╕ рд╕рдореВрд╣ рдореЗрдВ рдЕрдкрд░рд┐рд╡рд░реНрддрдиреАрдп рд╕рдВрджреЗрд╢ рд╣рдЯрд╛рдирд╛ рдкреНрд░рддрд┐рдмрдВрдзрд┐рдд рд╣реИред</string>
<string name="italic">рддрд┐рд░рдЫрд╛</string>
<string name="join_group_button">рдкреНрд░рд╡реЗрд╢ рд▓реЗрдирд╛</string>
<string name="join_group_incognito_button">рдЧреБрдкреНрдд рдореЗрдВ рд╢рд╛рдорд┐рд▓ рд╣реЛрдВ</string>
<string name="joining_group">рд╕рдореВрд╣ рдореЗрдВ рд╢рд╛рдорд┐рд▓ рд╣реЛрдирд╛</string>
<string name="join_group_question">рд╕рдореВрд╣ рдореЗрдВ рд╢рд╛рдорд┐рд▓ рд╣реЛрдВ\?</string>
<string name="thousand_abbreviation">рдХ</string>
<string name="keychain_error">рдЪрд╛рдмреА рдХрд╛ рдЧреБрдЪреНрдЫрд╛ рддреНрд░реБрдЯрд┐</string>
<string name="button_leave_group">рд╕рдореВрд╣ рдЫреЛрдбрд╝ рджреЗрдВ</string>
<string name="leave_group_question">рд╕рдореВрд╣ рдЫреЛрдбрд╝ рджреЗрдВ</string>
<string name="rcv_group_event_member_left">рдмрд╛рдПрдВ</string>
<string name="group_member_status_left">рдмрд╛рдПрдВ</string>
<string name="theme_light">рдкреНрд░рдХрд╛рд╢</string>
<string name="info_row_local_name">рд╕реНрдерд╛рдиреАрдп рдирд╛рдо</string>
<string name="users_delete_data_only">рдХреЗрд╡рд▓ рд╕реНрдерд╛рдиреАрдп рдкреНрд░реЛрдлрд╝рд╛рдЗрд▓ рдбреЗрдЯрд╛</string>
<string name="auth_log_in_using_credential">рдЕрдкрдиреЗ рдХреНрд░реЗрдбреЗрдВрд╢рд┐рдпрд▓ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рд▓реЙрдЧ рдЗрди рдХрд░реЗрдВ</string>
<string name="make_private_connection">рдПрдХ рдирд┐рдЬреА рд╕рдВрдмрдВрдз рдмрдирд╛рдУ</string>
<string name="marked_deleted_description">рдорд┐рдЯрд╛рдпрд╛ рд╣реБрдЖ рдЪрд┐рд╣реНрдирд┐рдд рдХрд┐рдпрд╛ рдЧрдпрд╛</string>
<string name="mark_unread">рдЕрдкрдард┐рдд рдХреЛ рдЪрд┐рд╣реНрдирд┐рдд рдХрд░реЗрдВ</string>
<string name="v4_3_voice_messages_desc">рдЕрдзрд┐рдХрддрдо 40 рд╕реЗрдХрдВрдб, рддреБрд░рдиреНрдд рдкреНрд░рд╛рдкреНрдд рд╣реБрдЖред</string>
<string name="you_sent_group_invitation">рдЖрдкрдиреЗ рд╕рдореВрд╣ рдЖрдордВрддреНрд░рдг рднреЗрдЬрд╛</string>
<string name="message_delivery_error_desc">рд╕рдмрд╕реЗ рдЕрдзрд┐рдХ рд╕рдВрднрд╛рд╡рдирд╛ рд╣реИ рдХрд┐ рдЗрд╕ рд╕рдВрдкрд░реНрдХ рдиреЗ рдЖрдкрдХреЗ рд╕рд╛рде рд╕рдВрдмрдВрдз рд╣рдЯрд╛ рджрд┐рдпрд╛ рд╣реИред</string>
<string name="mute_chat">рдореВрдХ</string>
<string name="network_status">рдиреЗрдЯрд╡рд░реНрдХ рдХреА рд╕реНрдерд┐рддрд┐</string>
<string name="notification_new_contact_request">рдирдпрд╛ рд╕рдВрдкрд░реНрдХ рдЕрдиреБрд░реЛрдз</string>
<string name="delete_files_and_media_all">рд╕рднреА рдлрд╛рдЗрд▓реЛрдВ рдХреЛ рдорд┐рдЯрд╛ рджреЗрдВ</string>
<string name="delete_archive">рд╕рдВрдЧреНрд░рд╣ рд╣рдЯрд╛ рджреЗрдирд╛</string>
<string name="new_database_archive">рдирдпрд╛ рдбреЗрдЯрд╛рдмреЗрд╕ рд╕рдВрдЧреНрд░рд╣</string>
<string name="new_member_role">рдирдП рд╕рджрд╕реНрдп рдХреА рднреВрдорд┐рдХрд╛</string>
<string name="settings_notifications_mode_title">рдЕрдзрд┐рд╕реВрдЪрдирд╛ рд╕реЗрд╡рд╛</string>
<string name="notification_preview_new_message">рдирдпрд╛ рд╕рдиреНрджреЗрд╢</string>
<string name="no_contacts_to_add">рдЬреЛрдбрд╝рдиреЗ рдХреЗ рд▓рд┐рдП рдХреЛрдИ рд╕рдВрдкрд░реНрдХ рдирд╣реАрдВ рд╣реИ</string>
<string name="chat_preferences_no">рдирд╣реАрдВ</string>
<string name="no_contacts_selected">рдХреЛрдИ рд╕рдВрдкрд░реНрдХ рдирд╣реАрдВ рдЪреБрдирд╛ рдЧрдпрд╛</string>
<string name="no_details">рдХреЛрдИ рд╡рд┐рд╡рд░рдг рдирд╣реАрдВ</string>
<string name="settings_notification_preview_title">рдЕрдзрд┐рд╕реВрдЪрдирд╛ рдкреВрд░реНрд╡рд╛рд╡рд▓реЛрдХрди</string>
<string name="notifications">рд╕реВрдЪрдирд╛рдПрдВ</string>
<string name="full_deletion">рд╕рднреА рдХреЗ рд▓рд┐рдП рд╣рдЯрд╛рдПрдВ</string>
<string name="delete_chat_archive_question">рд▓рд┐рдЦрдЪреАрдд рд╕рдВрдЧреНрд░рд╣ рд╣рдЯрд╛ рджреЗ\?</string>
<string name="delete_chat_profile_question">рдЪреИрдЯ рдкреНрд░реЛрдлрд╝рд╛рдЗрд▓ рд╣рдЯрд╛рдПрдВ\?</string>
<string name="users_delete_question">рдЪреИрдЯ рдкреНрд░реЛрдлрд╝рд╛рдЗрд▓ рд╣рдЯрд╛рдПрдВ\?</string>
<string name="users_delete_profile_for">рдХреЗ рд▓рд┐рдП рдЪреИрдЯ рдкреНрд░реЛрдлрд╝рд╛рдЗрд▓ рд╣рдЯрд╛рдПрдВ</string>
<string name="button_delete_contact">рд╕рдВрдкрд░реНрдХ рдорд┐рдЯрд╛ рджреЗрдВ</string>
<string name="deleted_description">рд╣рдЯрд╛рдП рдЧрдП</string>
<string name="delete_contact_question">рд╕рдВрдкрд░реНрдХ рдорд┐рдЯрд╛ рджреЗрдВ\?</string>
<string name="rcv_group_event_group_deleted">рд╣рдЯрд╛рдП рдЧрдП рд╕рдореВрд╣</string>
<string name="delete_image">рдЫрд╡рд┐ рд╣рдЯрд╛рдПрдВ</string>
<string name="button_delete_group">рд╕рдореВрд╣ рд╣рдЯрд╛рдПрдВ</string>
<string name="for_me_only">рдореЗрд░реЗ рд▓рд┐рдП рд╣рдЯрд╛рдПрдВ</string>
<string name="hide_verb">рдЫрд┐рдкрд╛рдирд╛</string>
<string name="both_you_and_your_contacts_can_delete">рдЖрдк рдФрд░ рдЖрдкрдХрд╛ рд╕рдВрдкрд░реНрдХ рджреЛрдиреЛрдВ рднреЗрдЬреЗ рдЧрдП рд╕рдВрджреЗрд╢реЛрдВ рдХреЛ рдЕрдкрд░рд┐рд╡рд░реНрддрдиреАрдп рд░реВрдк рд╕реЗ рд╣рдЯрд╛ рд╕рдХрддреЗ рд╣реИрдВред</string>
<string name="message_deletion_prohibited">рдЗрд╕ рдЪреИрдЯ рдореЗрдВ рдЕрдкрд░рд┐рд╡рд░реНрддрдиреАрдп рд╕рдВрджреЗрд╢ рд╣рдЯрд╛рдирд╛ рдкреНрд░рддрд┐рдмрдВрдзрд┐рдд рд╣реИред</string>
<string name="chat_with_the_founder">рдкреНрд░рд╢реНрди рдФрд░ рд╡рд┐рдЪрд╛рд░ рднреЗрдЬреЗрдВ</string>
<string name="send_us_an_email">рд╣рдореЗрдВ рдИрдореЗрд▓ рднреЗрдЬреЗрдВ</string>
<string name="display_name">рдкреНрд░рджрд░реНрд╢рд┐рдд рд╣реЛрдиреЗ рд╡рд╛рд▓рд╛ рдирд╛рдо</string>
<string name="confirm_verb">рдкреБрд╖реНрдЯрд┐ рдХрд░рдирд╛</string>
<string name="you_accepted_connection">рдЖрдкрдиреЗ рдХрдиреЗрдХреНрд╢рди рд╕реНрд╡реАрдХрд╛рд░ рдХрд┐рдпрд╛</string>
<string name="contribute">рдпреЛрдЧрджрд╛рди рджреЗрдирд╛</string>
<string name="both_you_and_your_contact_can_send_disappearing">рдЖрдк рдФрд░ рдЖрдкрдХрд╛ рд╕рдВрдкрд░реНрдХ рджреЛрдиреЛрдВ рдЧрд╛рдпрдм рд╣реЛрдиреЗ рд╡рд╛рд▓реЗ рд╕рдВрджреЗрд╢ рднреЗрдЬ рд╕рдХрддреЗ рд╣реИрдВред</string>
<string name="allow_to_send_disappearing">рдЧрд╛рдпрдм рд╣реЛрдиреЗ рд╡рд╛рд▓реЗ рд╕рдВрджреЗрд╢ рднреЗрдЬрдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрдВред</string>
<string name="prohibit_sending_disappearing">рдЧрд╛рдпрдм рд╣реЛрдиреЗ рд╡рд╛рд▓реЗ рд╕рдВрджреЗрд╢ рднреЗрдЬрдиреЗ рдкрд░ рд░реЛрдХ рд▓рдЧрд╛рдПрдВред</string>
<string name="how_to_use_simplex_chat">рдЗрд╕рдХрд╛ рдЙрдкрдпреЛрдЧ рдХреИрд╕реЗ рдХрд░рдирд╛ рд╣реИ</string>
<string name="group_member_status_group_deleted">рд╕рдореВрд╣ рд╣рдЯрд╛рдпрд╛ рдЧрдпрд╛</string>
<string name="delete_message_cannot_be_undone_warning">рд╕рдВрджреЗрд╢ рд╣рдЯрд╛ рджрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛ - рдЗрд╕реЗ рдкреВрд░реНрд╡рд╡рдд рдирд╣реАрдВ рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛!</string>
<string name="message_delivery_error_title">рд╕рдВрджреЗрд╢ рд╡рд┐рддрд░рдг рддреНрд░реБрдЯрд┐</string>
<string name="group_display_name_field">рд╕рдореВрд╣ рдкреНрд░рджрд░реНрд╢рди рдирд╛рдо:</string>
<string name="group_full_name_field">рд╕рдореВрд╣ рдХрд╛ рдкреВрд░рд╛ рдирд╛рдо:</string>
<string name="group_invitation_expired">рд╕рдореВрд╣ рдЖрдордВрддреНрд░рдг рд╕рдорд╛рдкреНрдд рд╣реЛ рдЧрдпрд╛</string>
<string name="v4_3_irreversible_message_deletion_desc">рдЖрдкрдХреЗ рд╕рдВрдкрд░реНрдХ рдкреВрд░реНрдг рд╕рдВрджреЗрд╢ рдХреЛ рд╣рдЯрд╛рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗ рд╕рдХрддреЗ рд╣реИрдВред</string>
<string name="new_in_version">%s рдореЗрдВ рдирдпрд╛</string>
<string name="icon_descr_group_inactive">рд╕рдореВрд╣ рдирд┐рд╖реНрдХреНрд░рд┐рдп</string>
<string name="delete_group_menu_action">рдорд┐рдЯрд╛рдирд╛</string>
<string name="delete_contact_menu_action">рдорд┐рдЯрд╛рдирд╛</string>
<string name="create_your_profile">рдЕрдкрдирд╛ рдкреНрд░реЛрдлрд╝рд╛рдЗрд▓ рдмрдирд╛рдП</string>
<string name="set_contact_name">рд╕рдВрдкрд░реНрдХ рдирд╛рдо рд╕реЗрдЯ рдХрд░реЗрдВ</string>
<string name="icon_descr_email">рдИрдореЗрд▓</string>
<string name="callstate_received_answer">рдЬрд╡рд╛рдм рдорд┐рд▓рд╛тАж</string>
<string name="use_chat">рдЪреИрдЯ рдХрд╛ рдкреНрд░рдпреЛрдЧ рдХрд░реЗрдВ</string>
<string name="error_with_info">рдЧрд▓рддреА: %s</string>
<string name="delete_messages_after">рд╕рдВрджреЗрд╢реЛрдВ рдХреЛ рдмрд╛рдж рдореЗрдВ рд╣рдЯрд╛рдПрдВ</string>
<string name="image_descr">рдЫрд╡рд┐</string>
<string name="icon_descr_server_status_error">рдЧрд▓рддреА</string>
<string name="restore_database_alert_confirm">рд╡рд╛рдкрд╕ рд▓реМрдЯрд╛рдирд╛</string>
<string name="a_plus_b">рдП + рдмреА</string>
<string name="incognito_random_profile">рдЖрдкрдХрд╛ рдпрд╛рджреГрдЪреНрдЫрд┐рдХ рдкреНрд░реЛрдлрд╝рд╛рдЗрд▓</string>
</resources>

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

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