Compare commits

...

839 Commits

Author SHA1 Message Date
Avently
391892bd64 ui: chatitem header 2023-11-13 10:29:22 -08:00
Evgeny Poberezkin
a2fe5cfb66 core: fix incorrect JSON serialization (#3361) 2023-11-13 17:45:10 +00:00
Stanislav Dmitrenko
338417d963 desktop: catch unreadable crypto file (#3359) 2023-11-13 14:06:01 +00:00
spaced4ndy
5beeff5cb6 core: take chat lock when synchronizing ratchet (#3349) 2023-11-12 12:41:41 +00:00
Evgeny Poberezkin
11362941fd 5.4.0-beta.3: iOS 181, Android 160, Desktop 16 2023-11-11 12:12:04 +00:00
Evgeny Poberezkin
f7b4e4b16a core: 5.4.0.3 2023-11-11 09:36:16 +00:00
Evgeny Poberezkin
83aaaa9ada translation: remove duplicate string 2023-11-10 20:45:41 +00:00
Stanislav Dmitrenko
ae286124aa ios: allow sound in silent mode (#3346)
Co-authored-by: Avently <avently@local>
2023-11-10 19:27:06 +00:00
Evgeny Poberezkin
9cc232054c website: translations (#3345)
* core: notify contact about contact deletion

* Translated using Weblate (French)

Currently translated at 100.0% (252 of 252 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (252 of 252 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (252 of 252 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 100.0% (252 of 252 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 34.1% (86 of 252 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (252 of 252 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (252 of 252 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 41.2% (104 of 252 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 74.6% (188 of 252 strings)

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

* Translated using Weblate (Czech)

Currently translated at 92.0% (232 of 252 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 100.0% (252 of 252 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 90.4% (228 of 252 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 94.8% (239 of 252 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 100.0% (252 of 252 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 100.0% (252 of 252 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (252 of 252 strings)

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

---------

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

Currently translated at 100.0% (1387 of 1387 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1250 of 1250 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1387 of 1387 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1250 of 1250 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1387 of 1387 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1250 of 1250 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 98.6% (1233 of 1250 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 99.2% (1376 of 1387 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 99.7% (1383 of 1387 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1387 of 1387 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1250 of 1250 strings)

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

* Translated using Weblate (Czech)

Currently translated at 98.0% (1360 of 1387 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1250 of 1250 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1387 of 1387 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 100.0% (1387 of 1387 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1387 of 1387 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1387 of 1387 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1250 of 1250 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1387 of 1387 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1250 of 1250 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1387 of 1387 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1250 of 1250 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1387 of 1387 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1250 of 1250 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (1387 of 1387 strings)

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

* Translated using Weblate (Russian)

Currently translated at 99.5% (1381 of 1387 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (1387 of 1387 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1387 of 1387 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1250 of 1250 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (1387 of 1387 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1387 of 1387 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1250 of 1250 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1387 of 1387 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1250 of 1250 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (1387 of 1387 strings)

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

* Translated using Weblate (Czech)

Currently translated at 99.9% (1249 of 1250 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 99.7% (1383 of 1387 strings)

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

* Translated using Weblate (Finnish)

Currently translated at 99.2% (1376 of 1387 strings)

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

* Translated using Weblate (Finnish)

Currently translated at 98.9% (1237 of 1250 strings)

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

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

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

* corrections

* correction

* fix android translations

* ios: import/export localizations

---------

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

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

* better

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

* changes

* change

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

* remove diff

* remove floating button

* refactor active

* open chat

* remove disabled

* fix incognito

---------

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

* ios: keep screen on while playing/recording media

* different implementation on ios

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

This reverts commit d291f006e9.

* different implementation on android

* refactor

---------

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

* Unbreak iOS

* update script and sha256map.nix

* ios: update core lib

---------

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

* use async function

* fit/fill of the video

* disconnect camera button from screen share

* enable video on audio call

* temp

* Revert "temp"

This reverts commit 8f8a2f7f88.

* Revert "enable video on audio call"

This reverts commit 120068d09a.

* different logic

---------

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

* compiles (some tests don't pass)

* remove commented code

* filter out user contact (fixes tests)

---------

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

* remove mime type change

---------

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

* imports

* desktop (mac): fix lib building

* imports

---------

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

* fixes

* more fixes

* fix

* fix

* color

* color and icon

---------

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

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

* CIBlocked, blocking api

* show item as blocked

* show blocked and merge multiple deleted items

* update block icons

* split sent and received deleted to two categories

* merge chat feature items, refactor CIMergedRange

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

* ensure range is withing chat items range

* merge group events

* fix/refactor

* make group member changes observable

* exclude some group events from merging

* fix states not updating and other fixes

* load list of members when tapping profile

* refactor

* fix incorrect merging of sent/received marked deleted

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

* load members list when opening member via item

* comments

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

* layout

* fix button

* update layout

* layout

* layout

---------

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

* style for ternaries

---------

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

* ios: show alert after saving profile with existing name

* return statements

* better way of showing alert

---------

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

* update text

---------

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

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

* using worker in desktop Chrome and Safari

* ui changes

* end call button in app bar

* fix android

* a lot of enhancements

* fix after merge master

* layout

* sound play on call

---------

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

* clear item statuses when changing current chat

* remove iOS hack

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

* refactor

* refactor 2

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

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

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

* improvements

* wording

* fixes

* rework to use dismissAllSheets with callback

* rework

* update texts

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

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

---------

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

* ui types, test

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

* errors

* html

* link

* screen sharing

* additions

* addition

* correction

---------

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

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

* change type for showMessages

* commands for member settings

* member and notification settings

* test

* take member settings into account when showing messages and notifications

* fix to show sent messages

* store blocked items

* types

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

* remove unused

* refactor activeTo

* move activeTo to ChatTerminal

* refactor

* move back

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

* update

* remove ()

* close when suspended too

* additional check

* fix

* refactore

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

* env

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

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

* remove entropy.patch

---------

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

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

* rename back

* disallow spaces only in names

* ios: disallow spaces only in names

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

* create/update groups

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

* mac fix

* windows fix

* .gitignore

* unused lines

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

* removed unused code

---------

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

* Windows

* copy files inside the app

* change

* encrypted files support

---------

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

* test

* more test

* update name validation, export C API

* refactor

* validate name when creating profile in CLI

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

* changes

* fix

---------

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

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

* temp

* temp

* new way of libs loading

* new way of libs loading

* Revert "new way of libs loading"

This reverts commit 8632f8a8f7.

* made VLC working on Windows

* unused lib

* scripts

* updated script

* fix path

* fix lib loading

* fix lib loading

* packaging options

* different file manager implementation on Windows

---------

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

* only show toggle with dev tools enabled

---------

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

* remove file names

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

This reverts commit ea319313f1.

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

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

* one more lib

* added check for dir existence in linking

* new line

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

* update .nix refs

* kotlin: types

* add test

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

* simplify

* simplify

* removed unused code

* follow up

---------

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

* 5.3.1: ios 174, desktop 11

---------

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

* update

* update post

* add images and previews

* website: add imageWide property

* website: add .float-to-left class

* update

* update images

* update readme

* fix typo

---------

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

* more

* fix tests

* use pokeByteOff

* write lazy bytestring to buffer without conversion to strict

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

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

* making player working without preinstalled VLC

* mac support

* don't use vlc lib when not needed

* updated jna version

* changes in script

* video player lazy loading

* mac script changes

* updated build gradle for preserving atrributes of file while copying

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

* updated script

* changes

---------

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

* update appimage metadata

* add screenshots

* update screenshots

* update

* update URLs

* update link

---------

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

* link

* website: fix h2 headers issue

* website: fix sidebar issue

* website: fix language changer for downloads page in navbar

* update, add security assessment section

---------

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

* desktop: 1.6.0, build 8

* action: file hashes (#3087)

* action: file hashes

* better output

* correct name

---------

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

* android: build 150

---------

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

* website: pagename from careers to career

* website: change pagename from career to jobs

* website: add jobs string to english language strings file

* website: add job tabs

* update

---------

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

Currently translated at 100.0% (245 of 245 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (245 of 245 strings)

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

---------

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

Currently translated at 100.0% (1381 of 1381 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (1244 of 1244 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1381 of 1381 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1244 of 1244 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1381 of 1381 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1244 of 1244 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1381 of 1381 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1244 of 1244 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 99.5% (1375 of 1381 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1381 of 1381 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1244 of 1244 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1244 of 1244 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1381 of 1381 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (1381 of 1381 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (1244 of 1244 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1381 of 1381 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1244 of 1244 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1381 of 1381 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1244 of 1244 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1381 of 1381 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1244 of 1244 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 99.5% (1375 of 1381 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1381 of 1381 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1244 of 1244 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1244 of 1244 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1381 of 1381 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1381 of 1381 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 99.2% (1235 of 1244 strings)

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

* import/export localizations

---------

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

* send both sides, correctly process update

* refactor

* revert diff

* comments

* test

---------

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

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

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

* observed

* network status

* contact ready

* add padding

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

* setContactNetworkStatus

* update text

* different context view

* update text, icon

* spinner

* reload all chats on swipe

* Revert "reload all chats on swipe"

This reverts commit 22c8372301.

* export localizations

---------

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

* disable test output

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

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

* fix migration

* progress

* xInfoProbeOk for member

* fix tests

* add test

* fix tests

* tests

* remove deleteSentProbe

* remove deleteContactProfile_

* views

* don't check connections in deleteUnusedProfile_

* Revert "don't check connections in deleteUnusedProfile_"

This reverts commit 2016a0efde.

* fix test

* core: update member merge

* update saved schema

* fix queries and tests

* rename tables to original names

* remove index, corrections

* update schema dump

---------

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

---------

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

* update

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

Currently translated at 100.0% (245 of 245 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (245 of 245 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (245 of 245 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 100.0% (245 of 245 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 0.4% (1 of 245 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (245 of 245 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (245 of 245 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (245 of 245 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 100.0% (245 of 245 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 0.4% (1 of 245 strings)

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

---------

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

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 99.9% (1368 of 1369 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1232 of 1232 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1232 of 1232 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1232 of 1232 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 99.1% (1358 of 1369 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 99.7% (1365 of 1369 strings)

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

* Translated using Weblate (Finnish)

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 89.8% (1107 of 1232 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1234 of 1234 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1234 of 1234 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1234 of 1234 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1234 of 1234 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1234 of 1234 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1234 of 1234 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 98.8% (1220 of 1234 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1234 of 1234 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 99.7% (1367 of 1371 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1234 of 1234 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (Finnish)

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (Finnish)

Currently translated at 100.0% (1234 of 1234 strings)

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

* Translated using Weblate (Russian)

Currently translated at 98.9% (1357 of 1371 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 99.2% (1361 of 1371 strings)

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

* Translated using Weblate (Czech)

Currently translated at 98.7% (1354 of 1371 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1234 of 1234 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 99.9% (1368 of 1369 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1232 of 1232 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1232 of 1232 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1232 of 1232 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 99.1% (1358 of 1369 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 99.7% (1365 of 1369 strings)

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

* Translated using Weblate (Finnish)

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 89.8% (1107 of 1232 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1234 of 1234 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1234 of 1234 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1234 of 1234 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1234 of 1234 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1234 of 1234 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1234 of 1234 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1369 of 1369 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 98.8% (1220 of 1234 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1234 of 1234 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 99.7% (1367 of 1371 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1234 of 1234 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (Finnish)

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (Finnish)

Currently translated at 100.0% (1234 of 1234 strings)

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

* Translated using Weblate (Russian)

Currently translated at 98.9% (1357 of 1371 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 99.2% (1361 of 1371 strings)

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

* Translated using Weblate (Czech)

Currently translated at 98.7% (1354 of 1371 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1371 of 1371 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1234 of 1234 strings)

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

* android: fix translations formatting

* ios: import/export localizations

---------

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

* correction, blog placeholder

* android: new in v5.3

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

* style

---------

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

* implementation

* fix contact replace, more tests

* comment

* rename fields

* fix

* fix

* test group is still incognito

* fix

* replace connection instead of contact

* fix

* check version

* style, names

---------

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

* fix package version on Windows

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

* Replace Bool with SubscriptionMode

* Add subscriptionMode to chat controller

* Start using subscriptionMode in event handlers

* Add need_subs to chat connections

* Add onlyNeeded to subscribeUserConnections

* Post-rebase fixes

* Pass onlyNeeded to Store functions

* Drop needs_sub for connections registered with agent

* update simplexmq, fix activate

* fix rebase, reduce diff

* fix rebase, tests

* fix rebase, executeMany, always subscribe on activate

* test

* update queries

* Update src/Simplex/Chat.hs

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

* unset connections to subscribe on start

* update simplexmq

---------

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

* setting

* fixed sharing

* check

* fixes, change lock icon

* update JNI bindings (crashes on desktop)

* fix JNI

* fix errors and warnings

* fix saving

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-09-10 17:05:12 +01:00
Evgeny Poberezkin
a87aaa50c7 website: add nostr.json for NIP-05 2023-09-10 11:21:40 +01:00
Evgeny Poberezkin
fdef37e8f3 Merge branch 'stable' 2023-09-10 11:01:42 +01:00
Evgeny Poberezkin
364d889056 docs: add mastodon rel=me to readme 2023-09-10 10:53:38 +01:00
Evgeny Poberezkin
923a5e2984 Merge branch 'stable' 2023-09-10 10:45:46 +01:00
Evgeny Poberezkin
272b02b686 docs: readme follow updates section, add rel=me for mastodon link 2023-09-10 10:45:30 +01:00
Evgeny Poberezkin
281d9c7f79 ios: add Finnish and Ukranian interface languages (#3040) 2023-09-08 13:21:56 +01:00
sh
45682aa7ce website: fix the apple-app-site-association (#3038) 2023-09-08 11:45:22 +01:00
Stanislav Dmitrenko
ad65622407 desktop: catch Toast exception (#3028) 2023-09-07 22:45:00 +01:00
Evgeny Poberezkin
113a57c7c7 ios: update chat_read_file (#3037) 2023-09-07 22:43:51 +01:00
Stanislav Dmitrenko
e76440ee66 desktop: local alias update (#3026)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-09-07 20:32:47 +01:00
Evgeny Poberezkin
82fd3b9004 core: change encoding of the result returned by chat_read_file C API (#3036)
* core: change encoding of the result returned by chat_read_file C API

* remove unused dependency

* remove pointer cast
2023-09-07 20:18:43 +01:00
Evgeny Poberezkin
b5a0269aa2 core: support unicode filenames and catch IO exceptions in C API for local file encryption (#3035)
* core: support unicode filenames in C API

* catch IO exceptions and return as errors
2023-09-07 13:44:37 +01:00
Evgeny Poberezkin
7cd4a417e7 ios: fix type that was preventing sent item status update 2023-09-07 11:51:17 +01:00
Evgeny Poberezkin
748572ace9 ui: types and stubs to encrypt local files (#3003)
* ui: types and stubs to encrypt local files

* ios: encrypt automatically received images in local storage

* encrypt sent images, marked to be received via NSE

* ios: encrypt sent and received local voice files

* encrypt sent and received local files

* fix NSE

* remove comment

* decrypt files in background thread
2023-09-07 11:28:37 +01:00
Stanislav Dmitrenko
a27f30ce12 android: changing a chat on user change (#3027)
* android: changing a chat on user change

* test

* test2

* Revert "test2"

This reverts commit 198873ecad.

* Revert "test"

This reverts commit 6e0e3d4930.

* style

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-09-07 10:59:37 +01:00
Seth For Privacy
a90641c1d1 Fix broken RSS feed source URL (#3033) 2023-09-07 10:03:32 +01:00
Evgeny Poberezkin
47b783e727 core: export chat_encrypt_file/chat_decrypt_file 2023-09-06 20:21:43 +01:00
Evgeny Poberezkin
edeaf36e8b core: C api to encrypt/decrypt local app files (#3029)
* core: C api to encrypt/decrypt local app files

* do not call CF.hPut with empty chunk
2023-09-06 19:54:13 +01:00
spaced4ndy
5e8e4c295c Merge pull request #2995 from simplex-chat/chat-version-negotiation
core: communicate connection chat version range; don't create direct connections in group (disabled)
2023-09-06 18:06:57 +04:00
spaced4ndy
37eef3c6c9 core: enable creation of direct connections in group (revert this instead of #3022) (#3025) 2023-09-06 18:05:42 +04:00
spaced4ndy
b6c23b59ca core: change logic of checking if peer supports feature (check if peer version is compatible) (#3024) 2023-09-06 17:48:37 +04:00
spaced4ndy
e6baca5610 core: rename conn vrange into peer vrange (#3023) 2023-09-06 11:41:23 +04:00
spaced4ndy
0c4b843a3f Merge branch 'master' into chat-version-negotiation 2023-09-06 10:41:06 +04:00
spaced4ndy
68f359c904 core: enable creation of direct connections in group for chat protocol v2 (for beta; to be reverted for full release) (#3022) 2023-09-06 10:15:32 +04:00
Stanislav Dmitrenko
e60dbf6add multiplatform: reduce variables that is controlling running chat (#3021)
* multiplatform: reduce variables that is controlling running chat

* removed second variable
2023-09-05 18:54:31 +01:00
Stanislav Dmitrenko
67d5b6eace desktop: open database dir (#3019)
* desktop: open database dir

* alert

* move button

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-09-05 18:24:39 +01:00
spaced4ndy
43e233f0eb core: don't create direct connections in group (#2996) 2023-09-05 20:15:50 +04:00
Evgeny Poberezkin
83b939d215 android: add Arabic, Finnish, Hebrew (#3018)
* android: add Arabic, Finnish, Hebrew

* update localization lib to support Hebrew

---------

Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
2023-09-05 15:07:37 +01:00
Evgeny Poberezkin
4d6283630a website: translations (#3017)
* Translated using Weblate (Arabic)

Currently translated at 100.0% (244 of 244 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (244 of 244 strings)

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

* Added translation using Weblate (Hebrew)

* Translated using Weblate (French)

Currently translated at 100.0% (245 of 245 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (245 of 245 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 100.0% (245 of 245 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (245 of 245 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (245 of 245 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 22.4% (55 of 245 strings)

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

* Added translation using Weblate (Finnish)

* Translated using Weblate (Arabic)

Currently translated at 100.0% (244 of 244 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (244 of 244 strings)

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

* Added translation using Weblate (Hebrew)

* Translated using Weblate (French)

Currently translated at 100.0% (245 of 245 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (245 of 245 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 100.0% (245 of 245 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (245 of 245 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (245 of 245 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 22.4% (55 of 245 strings)

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

* Added translation using Weblate (Finnish)

* Translated using Weblate (Hebrew)

Currently translated at 30.6% (75 of 245 strings)

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

* Translated using Weblate (Finnish)

Currently translated at 100.0% (245 of 245 strings)

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

---------

Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: a4318 <dalse.077@gmail.com>
Co-authored-by: ItaiShek <itaishek@gmail.com>
Co-authored-by: petri <pkajander@gmail.com>
2023-09-05 12:50:11 +01:00
Evgeny Poberezkin
6ff3024238 mobile: translations (#3015)
* Translated using Weblate (Chinese (Simplified))

Currently translated at 93.6% (1154 of 1232 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 96.5% (1311 of 1358 strings)

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

* Translated using Weblate (Finnish)

Currently translated at 100.0% (1358 of 1358 strings)

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

* Translated using Weblate (Finnish)

Currently translated at 19.4% (240 of 1232 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1232 of 1232 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 99.9% (1357 of 1358 strings)

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

* Translated using Weblate (French)

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

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1358 of 1358 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 99.9% (1231 of 1232 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1358 of 1358 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1232 of 1232 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 99.5% (1227 of 1232 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 99.0% (1345 of 1358 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 99.7% (1354 of 1358 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 3.7% (46 of 1232 strings)

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

* Translated using Weblate (Finnish)

Currently translated at 100.0% (1358 of 1358 strings)

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

* Translated using Weblate (Finnish)

Currently translated at 100.0% (1232 of 1232 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 100.0% (1358 of 1358 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 51.7% (637 of 1232 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 99.8% (1230 of 1232 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 99.8% (1356 of 1358 strings)

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

* Translated using Weblate (Czech)

Currently translated at 99.5% (1352 of 1358 strings)

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

* Translated using Weblate (Czech)

Currently translated at 99.0% (1220 of 1232 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.2% (1334 of 1358 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 99.0% (1220 of 1232 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 99.2% (1348 of 1358 strings)

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

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

* Translated using Weblate (Japanese)

Currently translated at 99.2% (1223 of 1232 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 99.2% (1348 of 1358 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 98.8% (1218 of 1232 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 98.9% (1344 of 1358 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 98.6% (1339 of 1358 strings)

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

* ios: import/export translations, android: formatted string tags

---------

Co-authored-by: 小连招 <onecombo666@gmail.com>
Co-authored-by: a4318 <dalse.077@gmail.com>
Co-authored-by: petri <pkajander@gmail.com>
Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
Co-authored-by: ItaiShek <itaishek@gmail.com>
Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Co-authored-by: zenobit <zen@osowoso.xyz>
Co-authored-by: marfS2 <work.j6nnu@slmail.me>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
2023-09-05 12:44:21 +01:00
Stanislav Dmitrenko
aff71c58d7 desktop: setup passphrase during onboarding (#2987)
* desktop: setup passphrase during onboarding

* updated logic

* removed unused code

* button and starting chat action

* better

* removed debug code

* fallback

* focusing and moving focus on desktop text fields

* different logic

* removed unused variable

* divided logic in two functions

* enabled keyboard enter

* rollback when db deleted by hand on desktop

* update texts, font size

* stopping chat before other actions

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-09-05 11:45:09 +01:00
Stanislav Dmitrenko
8aed568199 multiplatform: layout fix on link creation page and self destruct option (#3012) 2023-09-04 23:21:29 +01:00
Evgeny Poberezkin
0ec3e0c18d core: add debug info for subscriptions (#3014) 2023-09-04 23:19:24 +01:00
Stanislav Dmitrenko
c7f1af8742 android: sharing of files with plain text (#3011) 2023-09-04 18:37:53 +01:00
Evgeny Poberezkin
4793173465 core: update return type of read/write file C api, tests (#3010) 2023-09-03 22:25:19 +01:00
Evgeny Poberezkin
aa67692465 rfc: local file encryption (#2342) 2023-09-03 08:32:21 +01:00
Evgeny Poberezkin
af02a92442 core: fix WebRTC encryption, test (#3005) 2023-09-02 23:34:00 +01:00
Evgeny Poberezkin
461142b875 core: update simplexmq (import stateTVar) 2023-09-01 22:27:03 +01:00
Evgeny Poberezkin
0b214acf97 core: support encrypted local files (#2989)
* core: support encrypted local files

* add migration

* update agent api, chat api

* fix query, exported functions to read/write files

* update simplexmq

* remove formatting changes

* test, fix file size

* reduce diff

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

* fail when receiving SMP files with local encryption

* update simplexmq

* remove style changes

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2023-09-01 19:43:27 +01:00
spaced4ndy
1c90eb0a2e docs: groups improvements rfc (#2988) 2023-09-01 19:43:07 +04:00
spaced4ndy
7a5d4a5a3d core: communicate connection chat version range (#2886)
* core: communicate connection chat version range

* encoding

* type

* implementation wip

* contact requests

* tests

* more tests

* refactor

* remove comment

* change encoding

* remove Maybe

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-09-01 19:20:07 +04:00
Stanislav Dmitrenko
215020b1df desktop (mac): fixed linking (#2993) 2023-08-30 16:27:36 +01:00
Evgeny Poberezkin
5d580c3a1b Merge branch 'master' into master-ghc9 2023-08-30 16:27:11 +01:00
Stanislav Dmitrenko
7103524174 desktop: signing and notarizing mac build in Github action (#2986)
* desktop: signing and notarizing mac build in Github action

* changed path
2023-08-29 08:22:04 +01:00
Evgeny Poberezkin
4aac3c7922 website: update comparison 2023-08-28 22:23:49 +01:00
Evgeny Poberezkin
134465fd9d Merge branch 'stable' 2023-08-28 20:33:07 +01:00
Evgeny Poberezkin
0f076d9ac9 docs: update technical details and limitations 2023-08-28 20:32:44 +01:00
Stanislav Dmitrenko
95d57bc4e1 desktop: fixed gradle (#2982)
* fix gradle

* correct cert identity

* proper file paths

* moving to secrets

* order of lines

* returned back
2023-08-28 08:23:37 +01:00
M. Sarmad Qadeer
10f8b8086e Sq/website fdroid page (#2980)
* website: change height of f-droid page main section

* website: update lang strings for f-droid page

* website: fix fdroid page broken in other languages

* update translations

* website: add language navigation to fdroid page link

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-27 17:45:13 +01:00
Stanislav Dmitrenko
38ff7d173c desktop: fixed gradle (#2982)
* fix gradle

* correct cert identity

* proper file paths

* moving to secrets

* order of lines

* returned back
2023-08-27 09:16:29 +01:00
Evgeny Poberezkin
3cefe19264 Merge branch 'master' into master-ghc9 2023-08-26 21:02:07 +01:00
Evgeny Poberezkin
7504a82cb3 v5.3.0-beta.6: ios 169, android 148, desktop 1.4.0 (6) 2023-08-26 21:00:26 +01:00
Evgeny Poberezkin
a2aac72dd1 Merge branch 'master' into master-ghc9 2023-08-26 18:44:34 +01:00
Evgeny Poberezkin
ebb4c860b7 5.3.0.6: update simplexmq (create database path) 2023-08-26 18:36:34 +01:00
Evgeny Poberezkin
79e1bdaf61 rfc: improving iOS notifications 2023-08-26 16:51:05 +01:00
Evgeny Poberezkin
b1a6dec9b5 desktop (mac): signing and notarization (#2981)
* mac: signing and notarization

* updated example

* updated action

* update build script

* move mac CI script to file

---------

Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
2023-08-26 16:32:03 +01:00
Evgeny Poberezkin
960482f527 docs: correct year in privacy policy 2023-08-26 12:03:45 +01:00
Evgeny Poberezkin
7ef8ae9f42 Merge branch 'master' into master-ghc9 2023-08-25 17:50:01 +01:00
Stanislav Dmitrenko
96b253c3e7 dekstop: image compression (#2979)
* dekstop: image compression

* refactor
2023-08-25 17:28:39 +01:00
Evgeny Poberezkin
7fc108e6fa website: translations (#2978)
* Translated using Weblate (French)

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

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (248 of 248 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 100.0% (248 of 248 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (248 of 248 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (248 of 248 strings)

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

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (248 of 248 strings)

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

* Added translation using Weblate (Bulgarian)

---------

Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: Maksym Lukashenko <livelmaxim@gmail.com>
2023-08-25 17:05:56 +01:00
Evgeny Poberezkin
9134624e2a Merge branch 'master' into master-ghc9 2023-08-25 14:13:30 +01:00
Evgeny Poberezkin
3a93954c50 core: debug agent subscriptions (#2976)
* core: debug agent subscriptions

* update commands to include subscriptions summary
2023-08-25 14:10:40 +01:00
Evgeny Poberezkin
fde39e74ee Merge branch 'master' into master-ghc9 2023-08-25 11:32:37 +01:00
Evgeny Poberezkin
22dc58b735 android, desktop: add Ukrainian language 2023-08-25 11:31:15 +01:00
Evgeny Poberezkin
bcc265a3b1 ios: remove empty Localizable.strings files from NSE 2023-08-25 11:18:58 +01:00
Evgeny Poberezkin
5b258384f4 mobile: translations (#2977)
* Translated using Weblate (German)

Currently translated at 100.0% (1232 of 1232 strings)

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

* Translated using Weblate (French)

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

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1358 of 1358 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 99.9% (1231 of 1232 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1232 of 1232 strings)

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

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (1358 of 1358 strings)

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

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (1232 of 1232 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 51.7% (638 of 1232 strings)

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

* Translated using Weblate (Russian)

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

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

* ios: import/export localizations

---------

Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: Maksym Lukashenko <livelmaxim@gmail.com>
Co-authored-by: elgratea <weblate@fastmail.com>
2023-08-25 11:17:05 +01:00
Stanislav Dmitrenko
bdc08698c8 multiplatform: reduce recompositions in ChatView while entering text (#2974) 2023-08-25 10:12:06 +01:00
Stanislav Dmitrenko
7f894abbad desktop: better autoscroll to bottom on new message (#2973) 2023-08-25 10:09:21 +01:00
Moritz Angermann
761ddac55d core: use GHC 9.6.2 (#2641)
* Make it compiler with 9.6

Can be built with:

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

Using ghc 9.6

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

* compile with GHC 9.6.2: dependencies, imports, code

* update GHC version in CI

* update GHC version in desktop build scripts

* update simplexmq, sha256map.nix

* update compiler

* update simplexmq, direct-sqlcipher

* remove missing files from .cabal

* building on desktop

* mac build changes

* added version back

* building libffi from source

* update simplexmq

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
2023-08-24 21:56:37 +01:00
Stanislav Dmitrenko
06369e277c android: update contacts on profile update (#2969)
* android: update contacts on profile update

* returned back

* change
2023-08-23 18:43:21 +01:00
Stanislav Dmitrenko
16792de67a ios: update contacts on profile update (#2970)
* ios: update contacts on profile update

* change

* remove init

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-23 18:43:06 +01:00
Stanislav Dmitrenko
9639fd26b8 ios: making thumbnails faster (#2972)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-23 17:27:44 +01:00
Stanislav Dmitrenko
9537940494 ios: fullscreen player fix (#2968)
* ios: fullscreen player fix

* disable playing in some cases

* simpler conditions

* disabled video play button

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-23 17:11:39 +01:00
Evgeny Poberezkin
971e71727a core: specify compiler 2023-08-23 13:24:34 +01:00
Evgeny Poberezkin
4e861cc93a core: only send changed contacts in api response on user profile update (#2971) 2023-08-22 19:16:38 +01:00
Evgeny Poberezkin
c98b9cda85 core: change profile update API to return updated contacts (#2967) 2023-08-22 16:13:57 +01:00
Evgeny Poberezkin
a35ab7f9bc mobile: remove most user data from responses, to reduce noice in errors/console (#2966)
* mobile: remove most user data from responses, to reduce noice in errors/console

* small change for desktop

---------

Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
2023-08-22 12:20:21 +01:00
Evgeny Poberezkin
e804df9d58 Merge branch 'stable' 2023-08-22 09:11:12 +01:00
Evgeny Poberezkin
b1ecbb0355 docs: translations 2023-08-22 09:05:31 +01:00
Evgeny Poberezkin
55eab0976e mobile: add Thai language 2023-08-22 00:06:01 +01:00
Evgeny Poberezkin
3145095611 mobile: translations (#2964)
* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

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

* Translated using Weblate (French)

Currently translated at 98.8% (1338 of 1353 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 98.7% (1336 of 1353 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1353 of 1353 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1228 of 1228 strings)

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

* Translated using Weblate (French)

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

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

* Translated using Weblate (Dutch)

Currently translated at 99.0% (1340 of 1353 strings)

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

* Translated using Weblate (Thai)

Currently translated at 97.8% (1201 of 1228 strings)

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

* Translated using Weblate (Thai)

Currently translated at 98.5% (1334 of 1353 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 99.0% (1340 of 1353 strings)

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

* Translated using Weblate (Italian)

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

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1353 of 1353 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% (1228 of 1228 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 98.5% (1333 of 1353 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 98.4% (1209 of 1228 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1353 of 1353 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1228 of 1228 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1228 of 1228 strings)

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

* Translated using Weblate (Italian)

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

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

* Translated using Weblate (Arabic)

Currently translated at 99.7% (1349 of 1353 strings)

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1353 of 1353 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1228 of 1228 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 93.4% (1265 of 1353 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 94.3% (1277 of 1353 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1353 of 1353 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1228 of 1228 strings)

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

* Translated using Weblate (Dutch)

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

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

* Translated using Weblate (Bulgarian)

Currently translated at 4.9% (61 of 1228 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1353 of 1353 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 76.5% (1036 of 1353 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 6.4% (79 of 1228 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1356 of 1356 strings)

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

* Translated using Weblate (French)

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

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

* Translated using Weblate (Bulgarian)

Currently translated at 19.9% (245 of 1228 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1358 of 1358 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1358 of 1358 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1228 of 1228 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 22.2% (273 of 1228 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 42.1% (517 of 1228 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1358 of 1358 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 46.6% (573 of 1228 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 47.7% (586 of 1228 strings)

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (Dutch)

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

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

* Translated using Weblate (Arabic)

Currently translated at 99.7% (1354 of 1358 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 100.0% (1358 of 1358 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1358 of 1358 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1228 of 1228 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1228 of 1228 strings)

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

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (1358 of 1358 strings)

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

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (1228 of 1228 strings)

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

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

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

* Translated using Weblate (French)

Currently translated at 98.8% (1338 of 1353 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 98.7% (1336 of 1353 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1353 of 1353 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1228 of 1228 strings)

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

* Translated using Weblate (French)

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

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

* Translated using Weblate (Dutch)

Currently translated at 99.0% (1340 of 1353 strings)

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

* Translated using Weblate (Thai)

Currently translated at 97.8% (1201 of 1228 strings)

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

* Translated using Weblate (Thai)

Currently translated at 98.5% (1334 of 1353 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 99.0% (1340 of 1353 strings)

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

* Translated using Weblate (Italian)

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

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1353 of 1353 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% (1228 of 1228 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 98.5% (1333 of 1353 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 98.4% (1209 of 1228 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1353 of 1353 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1228 of 1228 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1228 of 1228 strings)

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

* Translated using Weblate (Italian)

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

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

* Translated using Weblate (Arabic)

Currently translated at 99.7% (1349 of 1353 strings)

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1353 of 1353 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1228 of 1228 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 93.4% (1265 of 1353 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 94.3% (1277 of 1353 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1353 of 1353 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1228 of 1228 strings)

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

* Translated using Weblate (Dutch)

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

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

* Translated using Weblate (Bulgarian)

Currently translated at 4.9% (61 of 1228 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1353 of 1353 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 76.5% (1036 of 1353 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 6.4% (79 of 1228 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1356 of 1356 strings)

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

* Translated using Weblate (French)

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

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

* Translated using Weblate (Bulgarian)

Currently translated at 19.9% (245 of 1228 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1358 of 1358 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1358 of 1358 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1228 of 1228 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 22.2% (273 of 1228 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 42.1% (517 of 1228 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1358 of 1358 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 46.6% (573 of 1228 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 47.7% (586 of 1228 strings)

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (Dutch)

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

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

* Translated using Weblate (Arabic)

Currently translated at 99.7% (1354 of 1358 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 100.0% (1358 of 1358 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1358 of 1358 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1228 of 1228 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1228 of 1228 strings)

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

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (1358 of 1358 strings)

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

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (1228 of 1228 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (1358 of 1358 strings)

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

* correction

* android: CDATA added

* Translated using Weblate (Russian)

Currently translated at 100.0% (1232 of 1232 strings)

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

* ios: import/export localizations

---------

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
Co-authored-by: Titapa (PunPun) Chaiyakiturajai <titapapunne@gmail.com>
Co-authored-by: elgratea <weblate@fastmail.com>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: zenobit <zen@osowoso.xyz>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: Víctor <bellzebu@hotmail.com>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: Andrew Elliot <Memorytoco@gmail.com>
Co-authored-by: xe1st <dnzkckali@gmail.com>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: ItaiShek <itaishek@gmail.com>
Co-authored-by: Maksym Lukashenko <livelmaxim@gmail.com>
2023-08-21 23:45:50 +01:00
Evgeny Poberezkin
05426842cd ios: export localizations 2023-08-21 21:53:15 +01:00
Evgeny Poberezkin
75d11c2b4f core: update simplexmq (fix repeated HELLO message) (#2962)
* core: update simplexmq (fix repeated HELLO message)

* add callstack to tests
2023-08-21 21:45:16 +01:00
Stanislav Dmitrenko
36e5fc64a8 multiplatform: section item text width limitation (#2955) 2023-08-21 20:40:21 +01:00
Stanislav Dmitrenko
788ee15942 multiplatform: fix handling HTML in some places (#2963) 2023-08-21 20:14:23 +01:00
M. Sarmad Qadeer
538cdd16de website: add f-droid page (#2960)
* website: add f-droid page

* update fdroid page

* website: improve the layout

* website: add strings to translation file

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-21 19:10:10 +01:00
Evgeny Poberezkin
63dd6e36b3 ios: fix view model update not on main thread 2023-08-21 13:56:53 +01:00
Evgeny Poberezkin
c3ffc2abb8 ios: fix chat console not showing command and response before re-opening 2023-08-21 13:52:09 +01:00
Evgeny Poberezkin
04cc0e8065 ios: fix incorrectly showing previous address when it is deleted and created without closing the view (#2961) 2023-08-21 13:21:43 +01:00
Evgeny Poberezkin
75c9b40262 core: update simplexmq (sequential sending of batches) (#2957)
* core: update simplexmq (sequential sending of batches)

* update simplexmq
2023-08-20 18:47:30 +01:00
Evgeny Poberezkin
847252f61e Merge branch 'stable' 2023-08-19 12:10:11 +01:00
Evgeny Poberezkin
c38af98a01 readme: add USDT address 2023-08-19 12:09:53 +01:00
Evgeny Poberezkin
ca3fd2ec36 docs: update "join team" 2023-08-19 09:13:01 +01:00
Evgeny Poberezkin
664954bc5c docs: update "join team" 2023-08-19 09:12:34 +01:00
Evgeny Poberezkin
212c2bdc1c docs: join the team (#2954) 2023-08-19 09:04:56 +01:00
Evgeny Poberezkin
14217227d8 docs: join the team (#2954) 2023-08-19 09:04:03 +01:00
Evgeny Poberezkin
41c68c82ac directory: add to website, send terms, sort search results (#2950)
* directory: add to website, send terms, sort search results

* corrections
2023-08-18 14:31:42 +01:00
spaced4ndy
addace9faf ios: fix group receipts override (#2951) 2023-08-18 17:02:11 +04:00
Evgeny Poberezkin
eb223f0c53 Merge branch 'stable' 2023-08-18 11:04:25 +01:00
Evgeny Poberezkin
4a99f58b93 docs: update privacy policy, directory service doc/terms (#2910)
* docs: update privacy policy, directory service doc/terms

* update

* correction

* corrections

* update doc

* heading

* amended

* correction
2023-08-18 11:03:55 +01:00
spaced4ndy
bb39a04d4f 5.3.0-beta.5: ios 168, android 147 2023-08-18 11:27:17 +04:00
Evgeny Poberezkin
8bb19db1ff core: 5.3.0.5 2023-08-17 23:16:23 +01:00
spaced4ndy
b829bd0c06 ios: fix Speak Screen repeating messages (#2897)
* ios: fix Speak Screen repeating messages

* fix

* different modifier

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-17 23:05:19 +04:00
Evgeny Poberezkin
01a95f88bb ios: more responsive UI, especially during app start (#2942)
* ios: more responsive UI, especially during app start

* move terminal items to actor

* fix for iOS 15/16
2023-08-17 18:21:05 +01:00
M. Sarmad Qadeer
45b7d09f83 website: lowercase the website urls (#2893) 2023-08-17 16:27:55 +01:00
Stanislav Dmitrenko
f1c86d20c9 desktop: cursor on hover over clickable link (#2946)
* desktop: cursor on hover over clickable link

* simplex link

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-17 16:25:52 +01:00
Stanislav Dmitrenko
ea397049f8 multiplatform: ChatView enhancements (#2945)
* multiplatform: ChatView enhancements

* removed unused code

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-17 15:26:11 +01:00
Stanislav Dmitrenko
63ca7a34ff desktop: clickable links in quoted messages (#2943) 2023-08-17 15:20:27 +01:00
Stanislav Dmitrenko
107b6e1aec multiplatform: mark read fix (#2932)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-17 15:06:42 +01:00
spaced4ndy
1d8a370c58 android: show chat previews & save last draft toggles (#2941)
* android: show chat previews & save last draft toggles

* wip

* Revert "wip"

This reverts commit 01b570913f.

* comment

* add to model

* toggle text and icon

* show draft

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-17 15:04:16 +01:00
spaced4ndy
faea5e90ac android: members connected aggregated item; group layout (#2934) 2023-08-17 15:56:43 +04:00
Evgeny Poberezkin
590644a359 ios: show draft even when messages in the list are hidden (#2944) 2023-08-17 12:52:03 +01:00
spaced4ndy
a5940962c7 ios: show chat previews & save last draft toggles (#2940)
* ios: show chat previews & save last draft toggles

* better view, toggle name

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-17 12:04:17 +01:00
Stanislav Dmitrenko
6cf9f0303b desktop: handling keyboard in auth screen (#2938)
* desktop: handling keyboard in auth screen

* numpad support

* numPadEnter

* padding
2023-08-16 18:50:27 +01:00
spaced4ndy
34cf672bc6 core: show count and average time for slow queries (#2939) 2023-08-16 21:21:12 +04:00
spaced4ndy
4a5dd0a3a4 core: add indexes to improve slow queries performance (#2931)
* core: add indexes to improve slow queries performance

* idx_chat_items_group_id

* update simplexmq, schema

* update simplexmq

* remove index

* update simplexmq

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-16 10:41:52 +04:00
Evgeny Poberezkin
a5642928eb ios: improve group layout (#2925)
* ios: improve group layout

* different font

* fix formatting

* returns

* localized strings

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2023-08-15 13:02:23 +01:00
Stanislav Dmitrenko
21dcb3b856 multiplatform: better draft (#2926)
* multiplatform: better draft

* added context item to check for emptyness

* show draft in preview when chat is active

* state sync

* clear draft when sending message
2023-08-15 12:04:24 +01:00
Evgeny Poberezkin
5a9ed86d1b ios: send button color for incognito conversations (#2918) 2023-08-14 22:55:18 +01:00
Stanislav Dmitrenko
67acc89dbf desktop: close info panel on chat deletion (#2927)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-14 22:45:29 +01:00
Stanislav Dmitrenko
5bcb62b306 desktop: alert style in protocol servers (#2928) 2023-08-14 22:26:36 +01:00
Stanislav Dmitrenko
4ccc4c1b82 multiplatform: do not remove focus from search field on member selection (#2923) 2023-08-14 21:31:41 +01:00
Stanislav Dmitrenko
58e6a408ea multiplatform: handling wrongly pasted link (#2922) 2023-08-14 21:21:19 +01:00
Stanislav Dmitrenko
45f58e34d5 multiplatform: do not close non-active chat on delete/leave (#2921)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-14 21:19:16 +01:00
Stanislav Dmitrenko
782355ccb5 desktop: better logic when switching chats (#2898)
* desktop: better logic when switching chats

* auto scroll to top when selected chat changes

* multiplatform: members page performance

* preloading group members

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-14 21:05:53 +01:00
spaced4ndy
8dcb70c019 ios: members connected aggregated item (#2900)
* ios: members connected aggregated item

* wrapping hstack wip

* Revert "wrapping hstack wip"

This reverts commit 75af7473fc.

* redesign

* fix scroll

* revert

* comment

* remove padding

* brackets

* texts, icon

* optimize - collect only member names

* refactor

* check different index

* refactor 2

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-14 17:34:22 +04:00
Evgeny Poberezkin
e326227d06 cli: change default member role from "admin" to "member" (#2915) 2023-08-14 07:37:04 +01:00
Evgeny Poberezkin
1cc14346b0 ios: optmize chat console performance (#2912) 2023-08-14 07:36:39 +01:00
Evgeny Poberezkin
4f9683f678 5.3.0-beta.4: ios 167, android 146 2023-08-12 23:28:36 +01:00
Evgeny Poberezkin
48261b7e8f core: 5.3.0.4 2023-08-12 22:00:25 +01:00
Evgeny Poberezkin
d0f4533a09 Merge branch 'stable' 2023-08-12 21:21:45 +01:00
Evgeny Poberezkin
8f9134b494 5.2.3: ios 166, android 144 2023-08-12 21:00:33 +01:00
Evgeny Poberezkin
113669ac16 core: track slow SQL queries (#2904)
* core: track slow SQL queries

* fixes

* update simplexmq
2023-08-12 18:27:10 +01:00
Moritz Angermann
85ddb646af core: explicitly set encoding to utf-8 (fixes unicode filenames on mobile) (#2908)
* Explicitly set encoding to utf-8 on mobile

GHC's rts tries to obtain the encoding from iconv, however this is not
really available on iOS. It therefore defaults to US-ASCII, and then
breaks with unicode data. We now explicitly set this to utf-8 here.

* ormolu

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-12 18:15:11 +01:00
Evgeny Poberezkin
cee0dffd46 core: 5.2.3.0 2023-08-12 14:33:17 +01:00
Evgeny Poberezkin
a2fef15440 android: disable app data backup competely (#2907) 2023-08-12 14:17:00 +01:00
Evgeny Poberezkin
0176bc3b2c core: improve members query performance (#2903) 2023-08-12 13:53:08 +01:00
Evgeny Poberezkin
42324b515d ios: only show "entity" notifications if the are enabled in the conversation (#2899) 2023-08-11 16:55:00 +01:00
Stanislav Dmitrenko
1bc880877d desktop: changing language without reload (#2896)
* desktop: changing language without reload

* comment

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-11 11:00:48 +01:00
Evgeny Poberezkin
7a41957d7b android: add Bulgarian UI language 2023-08-11 10:40:04 +01:00
Evgeny Poberezkin
837b6dcf46 directory: do not send welcome message to members joining the group (#2895) 2023-08-11 10:38:56 +01:00
sh
77a20f1ae3 docs: update and include tor installation section (#2806) 2023-08-10 22:12:04 +01:00
Stanislav Dmitrenko
e2dfc2071f desktop: fix scrolling in some cases (#2888) 2023-08-10 22:10:11 +01:00
Stanislav Dmitrenko
b8f289039a multiplatform: better performance in two cases (#2879)
* multiplatform: better performance in two cases

* remove unused API

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-10 22:00:18 +01:00
Stanislav Dmitrenko
b02eb79a2c multiplatform: showing progress indicator while sending a text message (#2880)
* multiplatform: showing progress indicator while sending a text message

* progress indicator after timeout

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-10 21:49:44 +01:00
Stanislav Dmitrenko
6ce00b45aa ios: show progress after send with timeout correctly (#2887)
* ios: show progress after send with timeout correctly

* was wrong idea

* remove unused property

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-10 21:38:11 +01:00
Evgeny Poberezkin
97a1a00f17 website: translations (#2891)
* Translated using Weblate (Arabic)

Currently translated at 100.0% (234 of 234 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 100.0% (234 of 234 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (234 of 234 strings)

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

* Translated using Weblate (German)

Currently translated at 99.5% (233 of 234 strings)

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

---------

Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: Pixelcode <pixelcode@dismail.de>
2023-08-10 21:15:31 +01:00
Stanislav Dmitrenko
121bca83aa multiplatform: format translations (#2892)
* multiplatform: format translations

* update strings

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-10 21:14:32 +01:00
Evgeny Poberezkin
18041ae471 mobile: translations (#2884)
* Translated using Weblate (Dutch)

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

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

* Translated using Weblate (Arabic)

Currently translated at 62.7% (831 of 1324 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 62.7% (831 of 1324 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1324 of 1324 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 71.9% (953 of 1324 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1324 of 1324 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1216 of 1216 strings)

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

* Translated using Weblate (Dutch)

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

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

* Translated using Weblate (Korean)

Currently translated at 71.2% (943 of 1324 strings)

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1324 of 1324 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1216 of 1216 strings)

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

* Translated using Weblate (Czech)

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

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

* Translated using Weblate (Arabic)

Currently translated at 77.3% (1024 of 1324 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 82.1% (1088 of 1324 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 100.0% (1324 of 1324 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 43.4% (528 of 1216 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 53.4% (708 of 1324 strings)

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1325 of 1325 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 86.7% (1150 of 1325 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1325 of 1325 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 1.3% (17 of 1216 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 53.4% (708 of 1325 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 87.2% (1156 of 1325 strings)

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

* Translated using Weblate (Thai)

Currently translated at 94.7% (1152 of 1216 strings)

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

* Translated using Weblate (Thai)

Currently translated at 95.7% (1269 of 1325 strings)

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1325 of 1325 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 99.6% (1321 of 1325 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (1325 of 1325 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 99.8% (1323 of 1325 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 3.7% (46 of 1216 strings)

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

* Translated using Weblate (Thai)

Currently translated at 94.7% (1152 of 1216 strings)

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

* Translated using Weblate (Thai)

Currently translated at 95.7% (1269 of 1325 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 43.9% (582 of 1325 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1326 of 1326 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 45.7% (607 of 1326 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 46.9% (623 of 1326 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 47.5% (631 of 1326 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 53.2% (706 of 1326 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 59.3% (787 of 1326 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1328 of 1328 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 76.3% (1014 of 1328 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 5.3% (65 of 1216 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 7.4% (90 of 1216 strings)

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

* Translated using Weblate (Dutch)

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

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

* Translated using Weblate (Turkish)

Currently translated at 58.2% (773 of 1328 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 58.2% (774 of 1328 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 7.5% (92 of 1216 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 63.9% (849 of 1328 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1328 of 1328 strings)

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

* Translated using Weblate (Finnish)

Currently translated at 94.2% (1252 of 1328 strings)

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

* Translated using Weblate (Finnish)

Currently translated at 19.9% (242 of 1216 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 68.5% (910 of 1328 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1356 of 1356 strings)

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

* Translated using Weblate (German)

Currently translated at 99.5% (1350 of 1356 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% (1356 of 1356 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% (1356 of 1356 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 70.2% (953 of 1356 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1356 of 1356 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 85.9% (1165 of 1356 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 90.2% (1224 of 1356 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 99.7% (1353 of 1356 strings)

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

* Translated using Weblate (Thai)

Currently translated at 100.0% (1216 of 1216 strings)

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

* Translated using Weblate (Thai)

Currently translated at 100.0% (1356 of 1356 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 1.0% (13 of 1216 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1356 of 1356 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1356 of 1356 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1356 of 1356 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 70.3% (954 of 1356 strings)

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

* Translated using Weblate (Dutch)

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

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

* Translated using Weblate (Arabic)

Currently translated at 99.7% (1355 of 1358 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1358 of 1358 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1358 of 1358 strings)

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (Turkish)

Currently translated at 77.2% (1049 of 1358 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 99.9% (1357 of 1358 strings)

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

* Translated using Weblate (Lithuanian)

Currently translated at 45.4% (617 of 1358 strings)

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

* export localizations

* update text

* fix German translations

* revert change in French

* remove "read" receipts translations in Japanese

* remove "read" receipts from Japanese

* revert French translations

* import/export localizations

---------

Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: Mosaab Emam <mosaab.emam123@gmail.com>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Co-authored-by: khalidbelk <khalid.belkassmi-el-hafi@epitech.eu>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: fxxk3rrth4ng <baech00@kakao.com>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: zenobit <zen@osowoso.xyz>
Co-authored-by: ItaiShek <itaishek@gmail.com>
Co-authored-by: p1ns <dnzkckali@gmail.com>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: Titapa (PunPun) Chaiyakiturajai <titapapunne@gmail.com>
Co-authored-by: Pixelcode <pixelcode@dismail.de>
Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: Y. Sakamoto <ysakamoto@tutanota.com>
Co-authored-by: elgratea <weblate@fastmail.com>
Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
Co-authored-by: Dennis <snap-roofing0h@icloud.com>
Co-authored-by: petri <pkajander@gmail.com>
Co-authored-by: 0xMarius <tech@marmakas.lt>
2023-08-10 17:59:42 +01:00
spaced4ndy
de3fdde2f6 docs: create groups without establishing direct connections RFC (#2890) 2023-08-10 20:33:11 +04:00
spaced4ndy
0cf2af916b docs: contact-groups rfc (#2775) 2023-08-10 20:32:18 +04:00
Evgeny Poberezkin
2ab938db60 mobile: remove item status alerts (#2883) 2023-08-09 22:38:18 +01:00
Stanislav Dmitrenko
9543af4784 android: workaround of system restricted background (#2873)
* android: workaround of system restricted background

* strings

* exclude lockscreen call from requirements to unrestrict

* texts

* added button and changed behaviour

* texts 2

* button instead of alert

* padding

* bigger padding

* refactor

* don't jump on button hide

* no exceptions for lockscreen

* sometimes do not show off alert

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-09 19:56:53 +01:00
Evgeny Poberezkin
38dc14f041 core: update simplexmq (revert DB busy error handling) 2023-08-09 16:45:31 +01:00
Evgeny Poberezkin
5353b466a9 directory: remove welcome when contact joins via group, member count in approval, chat commands via bot (#2872)
* directory: remove welcome when contact joins via group, member count in approval, chat commands via bot

* disable bot tests

* rename command
2023-08-08 16:06:56 +01:00
Stanislav Dmitrenko
b28a51106f desktop: small fix to AppImage action (#2862) 2023-08-08 15:56:08 +01:00
spaced4ndy
b095c09283 android: rework incognito mode - choose when making connection (#2867)
* android: rework incognito mode - choose when making connection

* remove commented code

* remove commented code

* change text

* text editor border

* smaller qr code

* chat preview height

* fix spacing

* desktop dialogue

* remove import

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-08 17:28:18 +04:00
spaced4ndy
1a567c88db ios: rework incognito mode - choose when making connection (#2851)
* wip

* layout

* more layout

* fix focus

* show incognito

* change icon layout

* remove presentation detents

* smaller button icon

* bigger icon

* show incognito profile status in connection info, layout, icons

* fix some lint warnings, update labels, add incognito label, conditionally hide toolbar to avoid jumping on iOS 17

* remove ignored color

* s/incognitoEnabled/incognito/

* shorter text

* remove parameter label

* restore note when creating a group

* add incognito icon to pending connections

* refactor

* refactor chat list action sheet

* revert to using new value in onChange

* remove unused variable

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-08 17:26:56 +04:00
spaced4ndy
d80ee14f77 Revert "Revert "core: rework incognito mode - set per connection (#2838)""
This reverts commit b003d659e4.
2023-08-08 17:25:28 +04:00
Evgeny Poberezkin
b374b5b753 Merge branch 'stable' 2023-08-07 21:04:33 +01:00
Evgeny Poberezkin
fde3c4f4e0 ios: 5.2.2, build 165 2023-08-07 20:42:09 +01:00
Stanislav Dmitrenko
f17889b3e3 android: 5.2.2, build 142 (revert to API 32) 2023-08-07 16:57:19 +01:00
Evgeny Poberezkin
34c5658560 Merge pull request #2854 from simplex-chat/directory-service
SimpleX Directory Service
2023-08-07 12:49:30 +01:00
Evgeny Poberezkin
53662ef077 directory: store log (#2863)
* directory: store log

* store log test (fails)

* fix store log
2023-08-07 08:25:15 +01:00
Evgeny Poberezkin
5a5876c258 core: 5.2.2.0 2023-08-06 22:14:11 +01:00
Evgeny Poberezkin
4826a62d36 directory: list groups with group member counts (#2855)
* directory: list groups with group member counts

* list groups, test

* superuser can list all groups

* rename command

* remove type synonym

* add member count to search results

* fix test
2023-08-06 11:56:40 +01:00
Evgeny Poberezkin
8cd362eed8 5.3-beta.3: android 141, ios 164, desktop 1.1.0 2023-08-05 22:24:02 +01:00
Evgeny Poberezkin
b7ac1b1b55 core: 5.3.0.2 2023-08-05 15:15:53 +01:00
Stanislav Dmitrenko
5952fd5290 desktop: font for emoji (#2853)
* desktop: font for emoji

* heart emoji for Mac

* limit font size

* alignment

* padding

* emoji item layout

* padding on desktop

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-04 22:28:09 +01:00
Stanislav Dmitrenko
353fe4539c desktop: package name for Mac (#2857)
* desktop: package name for Mac

* added Windows
2023-08-04 21:29:25 +01:00
Evgeny Poberezkin
b003d659e4 Revert "core: rework incognito mode - set per connection (#2838)"
This reverts commit 4e27a4ea4f.
2023-08-04 16:55:55 +01:00
Evgeny Poberezkin
8f72328136 directory: delist/relist groups when service or owner roles change (#2844)
* directory: delist/relist groups when service or owner roles change

* test role changes

* correction

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

* directory: suspend and resume group listing (#2848)

* directory: suspend and resume group listing

* correction

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

---------

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

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2023-08-04 09:23:16 +01:00
Stanislav Dmitrenko
6d113ae2e2 multiplatform: limit link preview width (#2847) 2023-08-03 16:40:29 +01:00
Stanislav Dmitrenko
f264470e14 desktop: fix saving files and images (#2846) 2023-08-03 16:34:35 +01:00
Stanislav Dmitrenko
65391756ef desktop: files Drag & Drop support (#2843)
* desktop: files Drag&Drop support

* reduce diff

* move

* move 2

* move 3

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-03 16:24:43 +01:00
spaced4ndy
4e27a4ea4f core: rework incognito mode - set per connection (#2838) 2023-08-03 17:41:23 +04:00
Stanislav Dmitrenko
b23b109b00 desktop: fixed shift+enter (#2842) 2023-08-03 12:04:26 +01:00
spaced4ndy
8bf830ced9 android: don't show file cancelled error alerts for automatically received files (#2818) 2023-08-03 14:26:23 +04:00
spaced4ndy
80a77a1104 ios: change logic of not showing alerts on file auto-receive - only don't show on file cancelled errors (#2833) 2023-08-03 14:25:56 +04:00
Stanislav Dmitrenko
760282cdfd desktop: edit previous message with Up arrow (#2841) 2023-08-03 10:32:01 +01:00
Evgeny Poberezkin
497275646d directory: registration of duplicate groups (#2840)
* directory: confirm registration of duplicate groups

* do not send duplicate groups for approval

* add test
2023-08-03 09:21:48 +01:00
Stanislav Dmitrenko
105a6afb4b ci: Github Action to build desktop app binaries (#2814)
* desktop: Github Action for desktop

* only on tag

* AppImage support
2023-08-02 22:45:58 +01:00
Stanislav Dmitrenko
061125ab63 desktop: AppImage support (#2839) 2023-08-02 22:30:24 +01:00
Stanislav Dmitrenko
2f10633d1d multiplatform: update compose version (#2835)
* multiplatform: update compose version

* Revert "desktop: fix freeze in some situations (#2820)"

This reverts commit 6268f0a32b.

* Revert "desktop: prevent deadlock (#2785)"

This reverts commit d77980e50e.

* restore debug commands in comments

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-02 17:20:15 +01:00
Evgeny Poberezkin
e2d5ad0e48 Merge branch 'master' into directory-service 2023-08-02 16:25:56 +01:00
Evgeny Poberezkin
e34a8ef719 Merge branch 'stable' 2023-08-02 16:23:17 +01:00
Evgeny Poberezkin
e30f7695ab core, ios: fix markdown (#2837) 2023-08-02 16:22:20 +01:00
spaced4ndy
2bb2042d7d ios, android: alert on connect via member address button (#2819)
* ios: alert on connect via member address button

* android

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

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

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

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

* ios texts

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-02 12:10:11 +04:00
spaced4ndy
0a6133fe5b ios: update error types (#2821) 2023-08-02 11:10:29 +04:00
spaced4ndy
a4a6e2418a android: update error types (#2827) 2023-08-02 11:09:54 +04:00
Evgeny Poberezkin
2b69103055 SimpleX Directory Service (#2766)
* SimpleX Directory Service

* more events

* update events

* fix

* Apply suggestions from code review

metavar

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

* metavar 2

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

* process events

* remove command serialization

* update

* update

* process group profile update

* basic group registration flow

* search works

* better messages

* improve messages

* test broadcast bot

* test for directory service

* better processing of group profile change, test

* refactor

* de-list group when owner or service is removed from the group, tests

* fix: removing any member or any member leaving should not delist the group

* refactor

* more tests, fixes

* disable bot tests in CI

* remove comment

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2023-08-01 20:54:51 +01:00
spaced4ndy
7344398826 ios: don't show error alerts for automatically received files (#2813) 2023-08-01 18:18:14 +04:00
Stanislav Dmitrenko
7f662ec7cc desktop: theme detector try-catch (#2817) 2023-08-01 15:08:04 +01:00
Stanislav Dmitrenko
298dd9744f desktop: include more libs for copying (#2823)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-08-01 15:07:10 +01:00
Stanislav Dmitrenko
6268f0a32b desktop: fix freeze in some situations (#2820)
* desktop: fix freeze in some situations

* old fashion

* small change
2023-08-01 14:58:26 +01:00
Evgeny Poberezkin
f0d64a30e9 5.3-beta.2: ios 163, android 140, desktop 1.0.1 (#2812) 2023-07-31 17:57:56 +01:00
Evgeny Poberezkin
d08df4cfbf core: 5.3.0.1 2023-07-31 16:13:19 +01:00
Evgeny Poberezkin
8e84b9e85f Merge branch 'stable' 2023-07-31 16:02:01 +01:00
spaced4ndy
c935e8aff3 core: fix status parsing 2 (#2809) 2023-07-31 15:52:42 +01:00
spaced4ndy
a0a567f5f7 core: fix status parsing (#2807) 2023-07-31 15:52:02 +01:00
spaced4ndy
ddd97baf5a core: 5.2.1.0 2023-07-31 15:49:53 +01:00
spaced4ndy
98e68c8e74 core: read unparsable item status as unknown (#2805) 2023-07-31 15:46:31 +01:00
Evgeny Poberezkin
03edde18eb core: update preset smp relays (#2804) 2023-07-31 15:19:28 +01:00
spaced4ndy
920b56e3d8 android: 5.2.1 (139) 2023-07-31 18:01:25 +04:00
spaced4ndy
dd51f032d2 ios: 5.2.1 (162) 2023-07-31 17:44:47 +04:00
spaced4ndy
1bdbea4f6d ios: update library (5.2.1.1) 2023-07-31 17:23:16 +04:00
spaced4ndy
90be54ff82 core: 5.2.1.1 2023-07-31 17:00:28 +04:00
Evgeny Poberezkin
bd4b445cbf mobile: increase default network timeouts (#2801)
* ios: increase default network timeouts

* android: increase default timeouts

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2023-07-31 16:56:23 +04:00
Stanislav Dmitrenko
8e7e5209d3 desktop: include openssl lib on Mac (#2810) 2023-07-31 13:46:00 +01:00
spaced4ndy
af98e703ec ios: update library (status parsing) (#2811) 2023-07-31 16:16:29 +04:00
spaced4ndy
fff8935b94 core: fix status parsing 2 (#2809) 2023-07-31 15:26:29 +04:00
spaced4ndy
9e7a45c734 core: fix status parsing (#2807) 2023-07-31 14:20:24 +04:00
spaced4ndy
af33f4e2d9 core: 5.2.1.0 2023-07-31 12:18:38 +04:00
spaced4ndy
98e53fb35b core: read unparsable item status as unknown (#2805) 2023-07-31 11:54:39 +04:00
Evgeny Poberezkin
cb4aa29549 core: update preset smp relays (#2804) 2023-07-30 15:15:41 +01:00
Evgeny Poberezkin
631dfff5e9 Merge branch 'stable' 2023-07-29 16:54:50 +01:00
M. Sarmad Qadeer
f69c842ba6 website: simplex explained graphics animation (#2713)
* website: animate simplex explained graphics

* website: update simplex network animation

* website: add lottie.min.js through node_modules

* website: update lottie.min.js copy command location
2023-07-29 14:20:51 +01:00
Evgeny Poberezkin
18c802159b android: build 138 2023-07-28 22:58:03 +01:00
Stanislav Dmitrenko
45e557fd80 android: make tmpDir (#2797) 2023-07-28 22:57:06 +01:00
Evgeny Poberezkin
d50562cfee 5.3-beta.1: Android 137 2023-07-28 20:11:04 +01:00
Stanislav Dmitrenko
15d3d3b11a desktop: encoding utf8 (#2795)
* desktop: encoding utf8

* rename functions

* logs
2023-07-28 19:38:31 +01:00
Stanislav Dmitrenko
2b715a0d8c desktop: notifications support (#2754)
* desktop: notifications support

* adapted external lib for interacting with notificatioins

* disabled some functions
2023-07-28 19:01:08 +01:00
Stanislav Dmitrenko
02d00944ff desktop: fix creating tmpDir and providing name for file to save (#2789)
* desktop: creating tmpDir

* provide name for saved file

* directories only selection on Mac

* specified filename for file savers

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-07-28 17:57:48 +01:00
spaced4ndy
71d6410604 5.3-beta.0: iOS 161, Android 136 2023-07-28 14:11:39 +04:00
spaced4ndy
141611293f android: group snd status (#2784) 2023-07-28 13:25:39 +04:00
spaced4ndy
c9400fe932 ios: group snd status (#2779) 2023-07-28 13:16:52 +04:00
spaced4ndy
445a8e75fe 5.3.0.0 2023-07-28 11:09:14 +04:00
Michael Sjöberg
8fc3f5a0f7 Fixed typo in en.json (#2773)
mesages -> messages
2023-07-27 20:26:10 +01:00
Stanislav Dmitrenko
9d30a3495e multiplatform: open SimpleX links inside the app (#2778)
* multiplatform: open SimpleX links inside the app

* one more place

* exclude via browser links
2023-07-27 20:21:53 +01:00
Stanislav Dmitrenko
d77980e50e desktop: prevent deadlock (#2785)
* desktop: prevent deadlock

* debug info
2023-07-27 15:59:06 +01:00
Stanislav Dmitrenko
bb02f07370 desktop: distribution changes (#2782)
* desktop: distribution

* icons update

* package name

* windows

* icons

* package name

* Update apps/multiplatform/desktop/build.gradle.kts

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-07-27 15:58:24 +01:00
Stanislav Dmitrenko
a3cd7ca89e desktop: enter + shift+enter keybindings (#2787) 2023-07-27 13:44:35 +01:00
Stanislav Dmitrenko
976fc68cc3 desktop: parsing html into annotated string (#2786) 2023-07-27 12:33:04 +01:00
Stanislav Dmitrenko
7c7e931aa9 desktop: bound long click to right click too (#2783)
* desktop: bound long click to right click too

* changes
2023-07-27 09:57:21 +01:00
Stanislav Dmitrenko
e8e619effa desktop: small fixes in initial setup (#2781)
* desktop: small fixes in initial setup

* small changes

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-07-27 08:47:11 +01:00
Stanislav Dmitrenko
bd0139eaab desktop: Linux lib path (#2780) 2023-07-26 20:13:06 +01:00
Stanislav Dmitrenko
e9f77e1064 desktop: development alerts (#2777)
* desktop: development alerts

* strings

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-07-26 14:31:21 +01:00
Stanislav Dmitrenko
677b75f368 multiplatform: possible race in ChatList (#2757)
* multiplatform: possible race in ChatList

* more changes

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-07-26 13:27:54 +01:00
Stanislav Dmitrenko
92d13591f3 desktop: change libs path (#2776)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-07-26 12:44:33 +01:00
Stanislav Dmitrenko
6be8476f90 desktop: decoding utf8 (#2774) 2023-07-26 12:38:49 +01:00
spaced4ndy
ae9b83515c core: group snd status (#2763)
* core: group snd status

* schema, implementation

* refactor direct, tests

* configure, tests

* item info

* refactor

* refactor

* remove do

* rename

* remove receipts on events

* refactor

* refactor

* refactor

* refactor

* tests

* rename tests

* aggregates

* fix name

* refactor

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-07-26 14:49:35 +04:00
Stanislav Dmitrenko
26a233ab1a desktop: adapted UI (#2755)
* desktop: adapted UI

* more changes

* divider fix

* do not close screens on non-desktop in terminal view

* background click to close views and small changes

* dark theme detection on supported OSes

* fix text color after theme change

* placement of desktop text field

* marked as @Composable

* padding of text view

* window sizes

* screen layout

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-07-26 09:35:29 +01:00
Evgeny Poberezkin
c7783a7039 Merge branch 'stable' 2023-07-22 20:09:03 +01:00
Evgeny Poberezkin
80bd734cc1 blog: v5.2 announcement (#2748)
* blog: v5.2 announcement

* remove extra heading

* updated

* update blog, images

* blog readme

* readme
2023-07-22 20:08:24 +01:00
Evgeny Poberezkin
0c34a545fa Merge branch 'stable' 2023-07-22 16:27:37 +01:00
Evgeny Poberezkin
65c6c63024 mobile: 5.2 android 134, ios 160 (patch for database performance) 2023-07-22 16:26:18 +01:00
Evgeny Poberezkin
f43fd57ec1 Merge pull request #2756 from simplex-chat/ep/521
5.2.0.4: update simplexmq (fix slow queries and incorrect message join)
2023-07-22 15:28:58 +01:00
Evgeny Poberezkin
065b932e1f update version to 5.2.0.4 2023-07-22 13:57:28 +01:00
Evgeny Poberezkin
7ebb763889 fix hpack version 2023-07-22 13:52:04 +01:00
Evgeny Poberezkin
eacfc4aa8c 5.2.1.0: update simplexmq (fix slow queries and incorrect message join) 2023-07-22 13:25:13 +01:00
Evgeny Poberezkin
9c49b038cd core: split preferences to separate file 2023-07-22 11:43:03 +01:00
Evgeny Poberezkin
1d4afe591e Merge pull request #2750 from simplex-chat/ep/move-prefs
core: split preferences to separate file
2023-07-22 11:41:30 +01:00
Evgeny Poberezkin
10ec3dd8b6 Merge pull request #2746 from simplex-chat/multiplatform
desktop (multiplatform) app 🚀
2023-07-21 21:34:05 +01:00
Evgeny Poberezkin
a715e847ad core: split preferences to separate file 2023-07-21 21:32:28 +01:00
Evgeny Poberezkin
9ac0f30c5a Merge branch 'master' into multiplatform 2023-07-21 13:12:05 +01:00
Evgeny Poberezkin
b033fdbeee Merge branch 'stable' 2023-07-21 13:10:47 +01:00
Evgeny Poberezkin
7996194f92 Merge pull request #2741 from simplex-chat/av/multiplatform-merge-master
multiplatform: merged master
2023-07-21 13:06:43 +01:00
Avently
c2054b5ccf Merge branch 'master' into av/multiplatform-merge-master 2023-07-21 15:47:51 +07:00
Evgeny Poberezkin
53dbe4b5d8 ios: 5.2 build 159 (more debug logging) 2023-07-21 08:10:39 +01:00
Evgeny Poberezkin
417eca74ad mobile: v5.2 ios 158 (update library, hide debug toggles), android 133 2023-07-20 19:51:29 +01:00
Evgeny Poberezkin
d25ef4e1a1 Merge pull request #2732 from simplex-chat/ep/fix-nse-crash
ios: builds 156, 157 (debug logs)
2023-07-20 19:11:13 +01:00
Evgeny Poberezkin
5d775a63c6 core: 5.2.0.3 2023-07-20 18:19:55 +01:00
Evgeny Poberezkin
576c886ba0 website: translations (#2744)
* Translated using Weblate (Czech)

Currently translated at 100.0% (234 of 234 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 100.0% (234 of 234 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (234 of 234 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 100.0% (234 of 234 strings)

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

---------

Co-authored-by: Pluto <notemailprotected@protonmail.com>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
2023-07-20 18:17:38 +01:00
Evgeny Poberezkin
511e3586d9 mobile: translations (#2738)
* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (Spanish)

Currently translated at 99.7% (1321 of 1324 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 99.7% (1321 of 1324 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1216 of 1216 strings)

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

* Translated using Weblate (Dutch)

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

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.4% (1317 of 1324 strings)

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

* Translated using Weblate (Lithuanian)

Currently translated at 45.3% (601 of 1324 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1324 of 1324 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1216 of 1216 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 98.8% (1309 of 1324 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 24.5% (325 of 1324 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 24.2% (321 of 1324 strings)

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

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

* Translated using Weblate (French)

Currently translated at 100.0% (1324 of 1324 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% (1216 of 1216 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% (1324 of 1324 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 99.6% (1319 of 1324 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1216 of 1216 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 49.8% (660 of 1324 strings)

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

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

* Translated using Weblate (Turkish)

Currently translated at 26.2% (347 of 1324 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 26.2% (347 of 1324 strings)

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

* Translated using Weblate (Czech)

Currently translated at 99.6% (1320 of 1324 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 94.9% (1155 of 1216 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 99.7% (1321 of 1324 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 26.6% (353 of 1324 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1324 of 1324 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 27.1% (360 of 1324 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 54.0% (715 of 1324 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 33.2% (440 of 1324 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 37.2% (493 of 1324 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 56.7% (751 of 1324 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 99.6% (1319 of 1324 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1216 of 1216 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 60.0% (795 of 1324 strings)

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

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (Spanish)

Currently translated at 99.7% (1321 of 1324 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 99.7% (1321 of 1324 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1216 of 1216 strings)

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

* Translated using Weblate (Dutch)

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

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.4% (1317 of 1324 strings)

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

* Translated using Weblate (Lithuanian)

Currently translated at 45.3% (601 of 1324 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1324 of 1324 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1216 of 1216 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 98.8% (1309 of 1324 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 24.5% (325 of 1324 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 24.2% (321 of 1324 strings)

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

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

* Translated using Weblate (French)

Currently translated at 100.0% (1324 of 1324 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% (1216 of 1216 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% (1324 of 1324 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 99.6% (1319 of 1324 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1216 of 1216 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 49.8% (660 of 1324 strings)

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

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

* Translated using Weblate (Turkish)

Currently translated at 26.2% (347 of 1324 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 26.2% (347 of 1324 strings)

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

* Translated using Weblate (Czech)

Currently translated at 99.6% (1320 of 1324 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 94.9% (1155 of 1216 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 99.7% (1321 of 1324 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 26.6% (353 of 1324 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1324 of 1324 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 27.1% (360 of 1324 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 54.0% (715 of 1324 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 33.2% (440 of 1324 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 37.2% (493 of 1324 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 56.7% (751 of 1324 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 99.6% (1319 of 1324 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1216 of 1216 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 60.0% (795 of 1324 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (1324 of 1324 strings)

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

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (Spanish)

Currently translated at 99.7% (1321 of 1324 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 99.7% (1321 of 1324 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1216 of 1216 strings)

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

* Translated using Weblate (Dutch)

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

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.4% (1317 of 1324 strings)

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

* Translated using Weblate (Lithuanian)

Currently translated at 45.3% (601 of 1324 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1324 of 1324 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1216 of 1216 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 98.8% (1309 of 1324 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 24.5% (325 of 1324 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 24.2% (321 of 1324 strings)

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

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

* Translated using Weblate (French)

Currently translated at 100.0% (1324 of 1324 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% (1216 of 1216 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% (1324 of 1324 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 99.6% (1319 of 1324 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1216 of 1216 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 49.8% (660 of 1324 strings)

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

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

* Translated using Weblate (Turkish)

Currently translated at 26.2% (347 of 1324 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 26.2% (347 of 1324 strings)

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

* Translated using Weblate (Czech)

Currently translated at 99.6% (1320 of 1324 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 94.9% (1155 of 1216 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 99.7% (1321 of 1324 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 26.6% (353 of 1324 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1324 of 1324 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 27.1% (360 of 1324 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 54.0% (715 of 1324 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 33.2% (440 of 1324 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 37.2% (493 of 1324 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 56.7% (751 of 1324 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 99.6% (1319 of 1324 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1216 of 1216 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 60.0% (795 of 1324 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (1324 of 1324 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 41.4% (549 of 1324 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 42.5% (563 of 1324 strings)

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

* Translated using Weblate (Russian)

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

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

* Translated using Weblate (Turkish)

Currently translated at 50.8% (673 of 1324 strings)

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

* Translated using Weblate (Czech)

Currently translated at 99.6% (1319 of 1324 strings)

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

* Translated using Weblate (Czech)

Currently translated at 99.1% (1206 of 1216 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/

* import/export

---------

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: khalidbelk <khalid.belkassmi-el-hafi@epitech.eu>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: axmfs <axmfs@proton.me>
Co-authored-by: Deividas Paukštė <deiv.paukst@protonmail.com>
Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
Co-authored-by: ItaiShek <itaishek@gmail.com>
Co-authored-by: elgratea <weblate@fastmail.com>
Co-authored-by: sir fish <github@havu.ch>
Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Co-authored-by: p1ns <dnzkckali@gmail.com>
Co-authored-by: Pluto <notemailprotected@protonmail.com>
2023-07-20 18:13:52 +01:00
Avently
1cb500bc16 Merge branch 'master' into av/multiplatform-merge-master 2023-07-20 20:43:35 +07:00
Avently
f5825d20e4 follow up 2023-07-20 20:43:17 +07:00
spaced4ndy
7a166e46a9 core: update simplexmq (delete expired messages migration) (#2740) 2023-07-20 14:36:44 +01:00
Avently
77d249cc37 Merge branch 'master' into av/multiplatform-merge-master 2023-07-20 20:35:08 +07:00
Avently
3f905f59df Merge branch 'master' into av/multiplatform-merge-master 2023-07-20 20:30:12 +07:00
Stanislav Dmitrenko
87c35b037e android: maybe fixes lang changer in some situations (#2739) 2023-07-20 14:13:59 +01:00
spaced4ndy
d63c7d2abc core: silence group errors to reduce load on UI event log (#2735) 2023-07-20 16:15:57 +04:00
spaced4ndy
ca5b3ddc0d ios, android: hide force renegotiate encryption button (#2736) 2023-07-20 15:50:29 +04:00
spaced4ndy
4e2acbf456 android: fix full name entry on profile creation; fix text on profile update (#2737) 2023-07-20 15:50:20 +04:00
Evgeny Poberezkin
202ecc369a 157: more logging 2023-07-20 12:07:00 +01:00
spaced4ndy
e5cec7a68b android: fix preset servers button color (#2734) 2023-07-20 13:51:14 +04:00
spaced4ndy
05b292ac00 core, mobile: fix editable interactions, item menu actions (#2733) 2023-07-20 13:50:31 +04:00
Stanislav Dmitrenko
562bd197bb android: removing tmp file when needed (#2729)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-07-19 23:06:15 +01:00
Stanislav Dmitrenko
94321cfc36 android: fix QR code scanner height (#2728)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-07-19 22:52:13 +01:00
Evgeny Poberezkin
7b863ef459 ios: build 156 (debug logs) 2023-07-19 22:47:04 +01:00
spaced4ndy
1aedfd6e5a android: fix interactions for member w/t contact - verify/fix connection buttons visible, member info can be opened via avatar in chat view (#2727) 2023-07-19 21:12:48 +04:00
Evgeny Poberezkin
572e3b7d32 ios: build 155 (last inlcuded pr #2723) 2023-07-19 17:53:18 +01:00
spaced4ndy
ab708f8855 android: move chat item menu info action above stop file action (#2725) 2023-07-19 20:49:25 +04:00
spaced4ndy
f5612504f5 android: correctly apply disable conditions to switch/abort switch buttons (#2726) 2023-07-19 20:49:05 +04:00
spaced4ndy
94e25d9bb4 ios: fix pull to refresh affecting pending connection sheet in chat list (#2724) 2023-07-19 20:48:47 +04:00
Evgeny Poberezkin
369d411fc1 ios: only run one notification mode, with dev overrides (#2723) 2023-07-19 16:37:46 +01:00
spaced4ndy
94312ec6fa ios, android: multiline display and full names in info views (#2716)
* ios, android: increase line limit to 2 for group display name in info header, if full name is not set

* make scrollable instead

* ios: multiline group and contact names in info page

* android; align to left with code

* ios rework shield

* android embed

* unused imports

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-07-19 15:16:50 +04:00
Evgeny Poberezkin
4b652b62da ios: better filtering of notificaitons in NSE (to avoid event notifications) (#2722) 2023-07-19 12:14:27 +01:00
Evgeny Poberezkin
bf4df9ca58 mobile: fix occasional notifications on sent messages (when sent event arrives before new chat item event) (#2721)
* mobile: fix occasional notifications on sent messages (when sent event arrives before new chat item event)

* fix

* fix condition
2023-07-19 11:59:56 +01:00
Stanislav Dmitrenko
27f4661ac4 desktop: send images and files (#2691)
* desktop: send images and files

* expect/actual

* file filter on non-Linux OSes

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-07-19 10:26:37 +01:00
Stanislav Dmitrenko
2389e870b3 desktop: do not show scan QR code screen (#2686)
* desktop: do not show scan QR code screen

* expect/actual and different button text

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-07-19 09:36:42 +01:00
Stanislav Dmitrenko
d61ff0f2a7 desktop: expanded dropdown menu (#2687)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-07-18 22:52:18 +01:00
sh
61334d7b77 build-android: fixes and improvements (#2715) 2023-07-18 21:42:58 +01:00
Evgeny Poberezkin
9a714a0926 mobile: translations (#2711)
* Translated using Weblate (Polish)

Currently translated at 100.0% (1269 of 1269 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1158 of 1158 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1269 of 1269 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1158 of 1158 strings)

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

* Translated using Weblate (French)

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

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1269 of 1269 strings)

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

* Translated using Weblate (Chinese (Simplified))

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

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (Spanish)

Currently translated at 99.9% (1268 of 1269 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 99.9% (1157 of 1158 strings)

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

* Translated using Weblate (Thai)

Currently translated at 29.6% (343 of 1158 strings)

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

* Translated using Weblate (Thai)

Currently translated at 100.0% (1269 of 1269 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 13.9% (161 of 1158 strings)

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

* Translated using Weblate (Finnish)

Currently translated at 5.3% (62 of 1158 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 100.0% (1269 of 1269 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 36.5% (423 of 1158 strings)

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

* Translated using Weblate (Thai)

Currently translated at 54.8% (635 of 1158 strings)

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

* Translated using Weblate (Thai)

Currently translated at 100.0% (1269 of 1269 strings)

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

* Translated using Weblate (Thai)

Currently translated at 100.0% (1158 of 1158 strings)

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

* Translated using Weblate (Thai)

Currently translated at 100.0% (1269 of 1269 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 44.6% (517 of 1158 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 45.0% (522 of 1158 strings)

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

* de: do not translate KeyChain

* es: shorter translation for Unfavorite

* ios: import/export localizations

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1292 of 1292 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 97.5% (1170 of 1200 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (1292 of 1292 strings)

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

* Added translation using Weblate (Bulgarian)

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1200 of 1200 strings)

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

* Added translation using Weblate (Bulgarian)

* Translated using Weblate (German)

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

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

* Translated using Weblate (Arabic)

Currently translated at 26.3% (341 of 1292 strings)

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

* Translated using Weblate (Thai)

Currently translated at 96.1% (1154 of 1200 strings)

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

* Translated using Weblate (Thai)

Currently translated at 98.2% (1270 of 1292 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 4.0% (52 of 1292 strings)

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

* Added translation using Weblate (Turkish)

* Added translation using Weblate (Turkish)

* Translated using Weblate (French)

Currently translated at 100.0% (1200 of 1200 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1292 of 1292 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1200 of 1200 strings)

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

* Translated using Weblate (Thai)

Currently translated at 96.1% (1154 of 1200 strings)

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

* Translated using Weblate (Thai)

Currently translated at 98.2% (1270 of 1292 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 16.9% (219 of 1292 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 2.1% (28 of 1292 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 3.0% (39 of 1292 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 31.7% (410 of 1292 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 6.1% (79 of 1292 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 32.4% (419 of 1292 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 12.1% (157 of 1292 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 32.5% (420 of 1292 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 37.4% (484 of 1292 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 42.3% (547 of 1292 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 100.0% (1292 of 1292 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 46.3% (599 of 1292 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 1.0% (13 of 1200 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 12.7% (165 of 1292 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 15.4% (200 of 1292 strings)

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

* export/import localizations

* import/export ios

* translation corrections

* import localization corrections

---------

Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: sith-on-mars <groguko36@pm.me>
Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: Titapa (PunPun) Chaiyakiturajai <titapapunne@gmail.com>
Co-authored-by: ItaiShek <itaishek@gmail.com>
Co-authored-by: petri <pkajander@gmail.com>
Co-authored-by: mf <work.j6nnu@slmail.me>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Co-authored-by: elgratea <weblate@fastmail.com>
Co-authored-by: khalidbelk <khalid.belkassmi-el-hafi@epitech.eu>
Co-authored-by: sir fish <github@havu.ch>
Co-authored-by: p1ns <dnzkckali@gmail.com>
2023-07-17 19:34:08 +01:00
spaced4ndy
7ddd300fe5 android: 5.2-beta.2 (132) 2023-07-17 19:37:54 +04:00
Evgeny Poberezkin
6b663baf10 ios: build 154 2023-07-17 16:03:58 +01:00
Evgeny Poberezkin
048ada79bb core: 5.2.0.2, update simplexmq (5.3.0.0) (#2707)
* core: 5.2.0.2, update simplexmq (5.3.0.0)

* fix tests
2023-07-17 14:44:33 +01:00
spaced4ndy
b69f422708 ios: fix editing w/t change (#2710) 2023-07-17 16:55:19 +04:00
Evgeny Poberezkin
396abdbfab ios: export localizations 2023-07-17 13:15:41 +01:00
spaced4ndy
938bd56c3a core: check item status is not already SndRcvd before updating to SndSent (#2708) 2023-07-17 15:25:32 +04:00
Evgeny Poberezkin
d3b5bbe566 android: delivery receipts (#2705)
* android: delivery receipts

* receipts toggle in privacy and in contact info (crashes), double tick in chat

* double tick image

* SetDeliveryReceiptsView

* small changes

* update users

* remove import

* fixing crash

* prevent ConcurrentModificationException

* fix enable all users

* android enable all users

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
2023-07-17 12:24:52 +01:00
Evgeny Poberezkin
1bd8f66730 mobile: update links to v5.2 release announcement (#2706) 2023-07-17 11:00:14 +01:00
Evgeny Poberezkin
c2177f3684 blog: stub for release v5.2 announcement (#2699)
* blog: stub for release v5.2 announcement

* update
2023-07-17 10:45:27 +01:00
spaced4ndy
72c0c61a86 ios: delivery receipts (#2701)
* ios: delivery receipts wip

* remove state variable

* fix 1 toggle

* fix 2nd toggle

* undo some changes, remove prints, remove commented code

* remove diff

* icon color

* comment

* refactor, double tick

* remove color from spaces

* update messages

* do not show Enable delievery receipts screen if any of the profiles was enabled

* update footer

* fix text

* better double ticks

* softer double tick

* improve double ticks

* a bit bigger gap in double tick

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-07-16 11:55:31 +01:00
Evgeny Poberezkin
f594752bb1 android: add missing icon 2023-07-14 19:41:57 +01:00
Evgeny Poberezkin
4a3c9366fd mobile: what's new in v5.2 (#2700)
* ios: whats new

* add

* improve

* export localizations

* android: whats new in v5.2
2023-07-14 19:28:56 +01:00
spaced4ndy
f5d61e7838 Merge pull request #2694 from simplex-chat/av/multiplatform-merged-master2
multiplatform: merged master
2023-07-14 17:52:43 +04:00
spaced4ndy
e762923410 Merge pull request #2685 from simplex-chat/av/multiplatform-merged-master
multiplatform: merged master
2023-07-14 17:40:41 +04:00
spaced4ndy
d87b86199c Merge branch 'multiplatform' into av/multiplatform-merged-master 2023-07-14 17:29:04 +04:00
Stanislav Dmitrenko
a7a66c2b55 multiplatform: adjusted database export path (#2698) 2023-07-14 17:28:13 +04:00
Evgeny Poberezkin
6ca76ec8a9 mobile: translations (#2695)
* Translated using Weblate (Polish)

Currently translated at 100.0% (1269 of 1269 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1158 of 1158 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1269 of 1269 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1158 of 1158 strings)

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

* Translated using Weblate (French)

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

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1269 of 1269 strings)

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

* Translated using Weblate (Chinese (Simplified))

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

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (Spanish)

Currently translated at 99.9% (1268 of 1269 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 99.9% (1157 of 1158 strings)

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

* Translated using Weblate (Thai)

Currently translated at 29.6% (343 of 1158 strings)

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

* Translated using Weblate (Thai)

Currently translated at 100.0% (1269 of 1269 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 13.9% (161 of 1158 strings)

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

* Translated using Weblate (Finnish)

Currently translated at 5.3% (62 of 1158 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 100.0% (1269 of 1269 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 36.5% (423 of 1158 strings)

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

* Translated using Weblate (Thai)

Currently translated at 54.8% (635 of 1158 strings)

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

* Translated using Weblate (Thai)

Currently translated at 100.0% (1269 of 1269 strings)

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

* Translated using Weblate (Thai)

Currently translated at 100.0% (1158 of 1158 strings)

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

* Translated using Weblate (Thai)

Currently translated at 100.0% (1269 of 1269 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 44.6% (517 of 1158 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 45.0% (522 of 1158 strings)

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

* de: do not translate KeyChain

* es: shorter translation for Unfavorite

* ios: import/export localizations

---------

Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: sith-on-mars <groguko36@pm.me>
Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: Titapa (PunPun) Chaiyakiturajai <titapapunne@gmail.com>
Co-authored-by: ItaiShek <itaishek@gmail.com>
Co-authored-by: petri <pkajander@gmail.com>
2023-07-14 13:26:31 +01:00
Evgeny Poberezkin
b089836efc ios: fix for XCode 14 2023-07-14 13:15:27 +01:00
Evgeny Poberezkin
90b616cd28 website: translations (#2696)
* Translated using Weblate (Japanese)

Currently translated at 11.9% (28 of 234 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 11.9% (28 of 234 strings)

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

---------

Co-authored-by: yukiokawaguchi <yukiokawaguchi@outlook.com>
2023-07-14 13:09:47 +01:00
Avently
f970ef264a Merge branch 'master' into av/multiplatform-merged-master2 2023-07-14 18:23:21 +07:00
Evgeny Poberezkin
3793cd138e Merge branch 'multiplatform' into av/multiplatform-merged-master 2023-07-14 12:01:48 +01:00
Stanislav Dmitrenko
8dd90733b8 multiplatform: default files and directories were changed (#2683)
* multiplatform: default files and directories were changed

* changes in paths and location of declaration

* more renames

* different paths

* update linux paths

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-07-14 12:00:37 +01:00
Evgeny Poberezkin
0f4473d272 core: delivery receipts (#2644)
* core: delivery receipts

* update simplexmq

* preference, migration

* add activated state to receipts preference, update tests

* set receiveReceipts as activated on new profiles

* update simplexmq, fix tests

* update simplexmq, fix withAckMessage

* one more option

* more

* use tryChatError in ack message

* enable all tests

* rename pref

* update item status on delivery receipts

* show receipts for tests

* remove chat preference for delivery receipts

* add user, contact and group settings for delivery receipts

* only send delivery receipts if enabled for the contact or user profile (and not disabled for the contact)

* fix tests

* reuse event, test

* configure per contact - db, api, test

* rename commands

* update simplexmq

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2023-07-13 23:48:25 +01:00
Stanislav Dmitrenko
0bdd96ae8a multiplatform: scripts for building the lib (#2682)
* multiplatform: scripts for building the lib

* refactor mac script

* fix path

* changes in Linux script

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-07-13 11:02:38 +01:00
spaced4ndy
43ceb184c4 android: add quoted message to chat item info (#2688) 2023-07-13 11:10:34 +04:00
Avently
dd62b1cccb Merge branch 'master' into av/multiplatform-merged-master 2023-07-12 22:43:57 +07:00
spaced4ndy
2e5a0fca1a ios: add quoted message to chat item info (#2680) 2023-07-12 19:25:03 +04:00
Stanislav Dmitrenko
38f40fec3d multiplatform: split common/android/desktop (#2672)
* multiplatform: relocated code to its new place

* code becomes better

* renamed file

* fixes for BASE64 and images, and changes for appFileUri

* different Base64 for both platforms

* fix file saving on long click

* platformCallbacks refactoring

* renamed callbacks to platform

* eol

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-07-12 14:42:10 +01:00
spaced4ndy
34c2303ef1 core: update simplexmq (db error busy treatments) (#2676) 2023-07-11 20:57:14 +04:00
spaced4ndy
ced69e431c 5.2-beta.1: iOS 153, Android 131 2023-07-10 20:10:14 +04:00
spaced4ndy
dcedbac379 android: ratchet synchronization (#2666)
* android: ratchet synchronization

* member info

* item

* icons

* update contact keeping stats

* update members keep stats

* update texts

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-07-10 19:01:51 +04:00
spaced4ndy
a6a87cb7de ios: ratchet synchronization (#2663)
* types

* info buttons

* prohibit send

* interactive item

* wip

* terminology

* item design

* comment

* rework

* design

* design

* move button

* update texts

* update texts

* sync not supported alert

* fix

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-07-10 19:01:22 +04:00
spaced4ndy
416ae400eb core: 5.2.0.1 2023-07-10 17:40:25 +04:00
Evgeny Poberezkin
b69916a3a3 ios: fix iOS 17 keyboard (#2671)
* ios: hide keyboard on scroll

* fix

* fix keyboard covering view with conditional padding
2023-07-10 13:53:46 +01:00
Evgeny Poberezkin
62726e345c ios: fix user profile in toolbar (#2667) 2023-07-10 07:57:23 +01:00
Evgeny Poberezkin
7a8db16791 core: catch IO exceptions in ExceptT (#2669)
* core: catch IO exceptions in ExceptT

* catch IO exceptions for ACK

* simplify, remove unnecessary changes

* fix, update simplexmq

* update simplexmq, enable all tests

* fix

* update simplexmq (fix finally)

* update sha256map.nix
2023-07-09 23:24:38 +01:00
Stanislav Dmitrenko
ff7c22e114 multiplatform: divided files in pieces (#2664)
* multiplatform: divided files in pieces

* whitespace

* whitespace 2

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-07-07 11:44:22 +01:00
Stanislav Dmitrenko
e24564d7d6 multiplatform: service refactor (#2661)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-07-06 14:19:35 +01:00
spaced4ndy
ae17566a94 core: ratchet synchronization (#2653) 2023-07-05 19:44:21 +04:00
Evgeny Poberezkin
c329bf4ea1 multiplatform: move dependency, reference (#2662) 2023-07-05 15:21:53 +01:00
Stanislav Dmitrenko
7fea9c85bd multiplatform: FWheelPicker dependency from source (#2659)
* multiplatform: FWheelPicker dependency from source

* EOLs

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-07-05 13:47:39 +01:00
Stanislav Dmitrenko
313d3a732d multiplatform: images and fonts (#2656)
* multiplatform: images and fonts

* update desktop app name and comment

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-07-05 10:16:43 +01:00
Evgeny Poberezkin
5d9b6266ea ios, core: pull to reconnect relays (#2652)
* ios, core: pull to reconnect relays

* update simplexmq

* update texts
2023-07-05 09:09:56 +01:00
Stanislav Dmitrenko
c35ce29cc1 android: fixed value in network config (#2655) 2023-07-04 18:14:27 +01:00
Evgeny Poberezkin
842bbf26c6 website: translations (#2654)
* Translated using Weblate (Arabic)

Currently translated at 100.0% (234 of 234 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 3.4% (8 of 234 strings)

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

---------

Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Co-authored-by: yukiokawaguchi <yukiokawaguchi@outlook.com>
2023-07-04 14:21:04 +01:00
Stanislav Dmitrenko
ebc5242932 multiplatform: translations refactor (#2649)
* no xliff

* CDATA

* moved to MR.strings

* unused StringRes

* renamed resources package

* translation of search_verb

* optimization

* optimization

* translation of la_mode_off
2023-07-04 13:32:28 +01:00
Evgeny Poberezkin
be5e0d7f75 mobile: add protocol timeout per KB (for batched commands) (#2650) 2023-07-04 07:52:47 +01:00
Evgeny Poberezkin
324a6ba38e website: translations (#2647)
* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (234 of 234 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (234 of 234 strings)

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

---------

Co-authored-by: sith-on-mars <groguko36@pm.me>
2023-07-03 17:21:40 +01:00
Evgeny Poberezkin
7b67bc2d47 mobile: translations (#2645)
* Added translation using Weblate (Bengali)

* Added translation using Weblate (Bengali)

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1256 of 1256 strings)

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

* Translated using Weblate (Chinese (Simplified))

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

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

* Translated using Weblate (Ukrainian)

Currently translated at 43.3% (499 of 1150 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1256 of 1256 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1150 of 1150 strings)

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

* Translated using Weblate (Ukrainian)

Currently translated at 47.7% (549 of 1150 strings)

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

* Translated using Weblate (French)

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

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

* Translated using Weblate (Ukrainian)

Currently translated at 65.2% (750 of 1150 strings)

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

* Translated using Weblate (Bengali)

Currently translated at 1.5% (20 of 1256 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 99.6% (1251 of 1256 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 4.3% (50 of 1150 strings)

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (Italian)

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

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1263 of 1263 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1150 of 1150 strings)

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

* Translated using Weblate (Malayalam)

Currently translated at 25.8% (327 of 1263 strings)

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

* Translated using Weblate (Bengali)

Currently translated at 2.8% (36 of 1263 strings)

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

* Translated using Weblate (Ukrainian)

Currently translated at 78.1% (899 of 1150 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1266 of 1266 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1150 of 1150 strings)

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

* Translated using Weblate (Bengali)

Currently translated at 3.0% (39 of 1266 strings)

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

* Translated using Weblate (Bengali)

Currently translated at 0.0% (0 of 1150 strings)

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

* Translated using Weblate (Ukrainian)

Currently translated at 78.2% (900 of 1150 strings)

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

* Translated using Weblate (Bengali)

Currently translated at 0.1% (1 of 1150 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1266 of 1266 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 99.6% (1262 of 1266 strings)

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

* Translated using Weblate (Malayalam)

Currently translated at 27.7% (351 of 1266 strings)

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

* Translated using Weblate (Bengali)

Currently translated at 3.1% (40 of 1266 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1266 of 1266 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1266 of 1266 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 99.7% (1263 of 1266 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 5.4% (63 of 1150 strings)

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

* move bn language to correct location

* corrections

* import/export localizations

---------

Co-authored-by: sith-on-mars <groguko36@pm.me>
Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: Maksym Lukashenko <livelmaxim@gmail.com>
Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: Adhra Sreoshi Athoi <adhrasreoshiathoi@protonmail.com>
Co-authored-by: ItaiShek <itaishek@gmail.com>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: Raman <aksharam.sme4i@aleeas.com>
2023-07-03 17:18:31 +01:00
spaced4ndy
2f7ea909e2 core: update simplexmq (5.2.0) (#2646) 2023-07-03 20:13:00 +04:00
Stanislav Dmitrenko
9238ac3445 multiplatform: make ChatModel and ChatController as object (#2639)
* multiplatform: make ChatModel and ChatController as object

* fix off uninitialized

* more contexts without Context

* smaller diff

* name

* refactor

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-07-03 15:55:37 +01:00
Stanislav Dmitrenko
3bd5fc7463 multiplatform: moved to Gradle KTS and common directory structure (#2633)
* multiplatform: moved to Gradle KTS and common directory structure

* renamed for review

* different versions for Android and desktop

* update desktop version_name

* Revert "renamed for review"

This reverts commit 80041efe40.

* EOLs

* change version to 1.0 to appease linter

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-07-01 20:44:36 +01:00
M Sarmad Qadeer
4c33ed92bb website: remove white border of demo terminal (#2634) 2023-07-01 06:46:37 +01:00
M Sarmad Qadeer
a0ae4125c5 website: add cli demo to cli docs page (#2622) 2023-06-30 11:12:05 +01:00
Evgeny Poberezkin
34a60066fb docs: add sample database passphrase for themes 2023-06-29 21:50:42 +01:00
Stanislav Dmitrenko
1f50e94bc9 multiplatform: API 33 support (#2631) 2023-06-29 13:17:43 +01:00
Stanislav Dmitrenko
0e4376bada multiplatform: file structure (#2630)
* multiplatform: file structure

* apk file name

* more paths in sources

* lint issues and apk file name
2023-06-29 12:53:11 +01:00
spaced4ndy
b0ad94fe7f 5.2-beta.0: iOS 151, Android 129 (#2628) 2023-06-28 19:52:52 +04:00
Stanislav Dmitrenko
fccd4f7ec4 android: returned lost focus in text field (#2625) 2023-06-28 16:49:01 +04:00
spaced4ndy
3f93397031 core: 5.2.0.0 (#2626) 2023-06-28 16:13:14 +04:00
Stanislav Dmitrenko
a5f8641d50 android: search members (#2621)
* android: search members

* changed placement of search fields

* less diff

* remove unused function

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-06-28 13:00:42 +01:00
Stanislav Dmitrenko
f23c0b55f8 android: filter favorite and unread chats (#2623)
* android: filter favorite and unread chats

* style

* unused icons

* inverted colors

* change colors, size

* changes to strings and icon

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-06-28 12:20:23 +01:00
Stanislav Dmitrenko
534151f1bb android: group preference to prohibit files and media (#2620)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-06-27 18:58:04 +01:00
Evgeny Poberezkin
2ad9d0ddbc ios: update library 2023-06-27 16:35:27 +01:00
Evgeny Poberezkin
388bdc7083 ios: improve chat filter, "No filtered chats" note (#2619)
* ios: improve chat filter, "No filtered chats" note

* refactor filter
2023-06-27 10:28:47 +01:00
Evgeny Poberezkin
3e370a7c16 ios: group preference to prohibit files and media (#2611)
* ios: group preference to prohibit files and media

* style
2023-06-27 07:55:33 +01:00
byrd19
30d4fc757c add cabal.project source-file to simplex-chat.cabal (#2588)
fix compilation error: ./cabal.project: openBinaryFile: does not exist
2023-06-27 07:44:18 +01:00
spaced4ndy
77b3870654 core: update simplexmq (switch status encoding) (#2615) 2023-06-26 21:48:01 +04:00
Evgeny Poberezkin
b088b1c44c mobile: translations (#2614)
* Translated using Weblate (German)

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

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1247 of 1247 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 97.6% (1218 of 1247 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 99.3% (1135 of 1143 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1247 of 1247 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1143 of 1143 strings)

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

* Translated using Weblate (Finnish)

Currently translated at 0.4% (5 of 1247 strings)

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1247 of 1247 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 97.6% (1218 of 1247 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 99.3% (1135 of 1143 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1247 of 1247 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1143 of 1143 strings)

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

* Translated using Weblate (Finnish)

Currently translated at 0.4% (5 of 1247 strings)

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

* Added translation using Weblate (Thai)

* Added translation using Weblate (Thai)

* Translated using Weblate (Finnish)

Currently translated at 3.3% (42 of 1247 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1247 of 1247 strings)

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

* Translated using Weblate (Hebrew)

Currently translated at 68.3% (852 of 1247 strings)

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

* Translated using Weblate (Thai)

Currently translated at 1.6% (20 of 1247 strings)

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

* Translated using Weblate (Thai)

Currently translated at 1.6% (20 of 1247 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 99.6% (1243 of 1247 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 99.3% (1136 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hant/

* Translated using Weblate (Thai)

Currently translated at 7.2% (91 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/th/

* Translated using Weblate (Hebrew)

Currently translated at 73.1% (912 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hant/

* Translated using Weblate (Hebrew)

Currently translated at 75.0% (936 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Thai)

Currently translated at 10.1% (126 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/th/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1247 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/

* Translated using Weblate (Thai)

Currently translated at 11.7% (147 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/th/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (1247 of 1247 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 100.0% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hant/

* Translated using Weblate (Hebrew)

Currently translated at 80.8% (1008 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Thai)

Currently translated at 21.4% (267 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/th/

* Translated using Weblate (Czech)

Currently translated at 100.0% (1247 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/

* Translated using Weblate (Hebrew)

Currently translated at 81.9% (1022 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (1247 of 1247 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 100.0% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hant/

* Translated using Weblate (Portuguese)

Currently translated at 54.5% (680 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/

* Translated using Weblate (Thai)

Currently translated at 41.6% (519 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/th/

* Translated using Weblate (Finnish)

Currently translated at 28.9% (361 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fi/

* Translated using Weblate (Hebrew)

Currently translated at 85.0% (1061 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Thai)

Currently translated at 42.1% (526 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/th/

* Translated using Weblate (Hebrew)

Currently translated at 88.6% (1106 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Russian)

Currently translated at 100.0% (1247 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/

* Translated using Weblate (Portuguese)

Currently translated at 55.2% (689 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/

* Translated using Weblate (Hebrew)

Currently translated at 96.4% (1203 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Thai)

Currently translated at 49.3% (616 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/th/

* Translated using Weblate (Finnish)

Currently translated at 48.5% (606 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fi/

* Translated using Weblate (Italian)

Currently translated at 100.0% (1247 of 1247 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% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/

* Translated using Weblate (Hebrew)

Currently translated at 97.1% (1212 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Hebrew)

Currently translated at 97.5% (1217 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Finnish)

Currently translated at 81.9% (1022 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fi/

* Translated using Weblate (Hebrew)

Currently translated at 100.0% (1247 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Hebrew)

Currently translated at 1.2% (14 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/he/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (1247 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 76.2% (872 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/pt_BR/

* Translated using Weblate (Finnish)

Currently translated at 100.0% (1247 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fi/

* Translated using Weblate (Finnish)

Currently translated at 1.0% (12 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fi/

* Translated using Weblate (Portuguese)

Currently translated at 59.3% (740 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/

* Translated using Weblate (Hebrew)

Currently translated at 100.0% (1247 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Hebrew)

Currently translated at 4.0% (46 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/he/

* Translated using Weblate (Thai)

Currently translated at 60.5% (755 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/th/

* Translated using Weblate (Thai)

Currently translated at 64.3% (802 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/th/

* Translated using Weblate (German)

Currently translated at 100.0% (1247 of 1247 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% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/

* Translated using Weblate (Italian)

Currently translated at 100.0% (1247 of 1247 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% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/

* Translated using Weblate (Thai)

Currently translated at 68.4% (854 of 1247 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/th/

* Translated using Weblate (Russian)

Currently translated at 99.6% (1246 of 1251 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/

* Translated using Weblate (French)

Currently translated at 100.0% (1251 of 1251 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% (1251 of 1251 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1251 of 1251 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1251 of 1251 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1251 of 1251 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/

* Translated using Weblate (Finnish)

Currently translated at 100.0% (1251 of 1251 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fi/

* Translated using Weblate (Polish)

Currently translated at 100.0% (1251 of 1251 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pl/

* Translated using Weblate (Thai)

Currently translated at 74.9% (937 of 1251 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/th/

* Translated using Weblate (Ukrainian)

Currently translated at 1.5% (19 of 1251 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/

* Translated using Weblate (Thai)

Currently translated at 81.2% (1016 of 1251 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/th/

* Translated using Weblate (Ukrainian)

Currently translated at 6.0% (76 of 1251 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/

* Translated using Weblate (Ukrainian)

Currently translated at 8.8% (111 of 1251 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/

* Translated using Weblate (Ukrainian)

Currently translated at 8.9% (112 of 1251 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/

* Translated using Weblate (Ukrainian)

Currently translated at 0.1% (1 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/uk/

* Translated using Weblate (Arabic)

Currently translated at 6.6% (83 of 1251 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/

* Translated using Weblate (Ukrainian)

Currently translated at 9.1% (115 of 1251 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/

* Translated using Weblate (Thai)

Currently translated at 97.1% (1215 of 1251 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/th/

* Translated using Weblate (Italian)

Currently translated at 100.0% (1144 of 1144 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/

* Translated using Weblate (Thai)

Currently translated at 100.0% (1251 of 1251 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/th/

* Translated using Weblate (German)

Currently translated at 100.0% (1251 of 1251 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% (1144 of 1144 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% (1144 of 1144 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% (1251 of 1251 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% (1144 of 1144 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 100.0% (1144 of 1144 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hans/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1251 of 1251 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1144 of 1144 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/

* Translated using Weblate (Arabic)

Currently translated at 12.9% (162 of 1251 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/

* Added translation using Weblate (Malayalam)

* Added translation using Weblate (Malayalam)

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1144 of 1144 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (1251 of 1251 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/

* Translated using Weblate (Finnish)

Currently translated at 3.4% (39 of 1144 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fi/

* Translated using Weblate (Malayalam)

Currently translated at 5.9% (75 of 1251 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ml/

* Translated using Weblate (German)

Currently translated at 100.0% (1251 of 1251 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (1251 of 1251 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/

* Translated using Weblate (Malayalam)

Currently translated at 12.9% (162 of 1251 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ml/

* Translated using Weblate (Czech)

Currently translated at 99.9% (1250 of 1251 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% (1144 of 1144 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/cs/

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (1251 of 1251 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/

* Translated using Weblate (Ukrainian)

Currently translated at 24.8% (284 of 1144 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/uk/

* Translated using Weblate (Malayalam)

Currently translated at 18.3% (230 of 1251 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ml/

* Translated using Weblate (Korean)

Currently translated at 71.2% (891 of 1251 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ko/

* Translated using Weblate (Malayalam)

Currently translated at 22.4% (281 of 1251 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ml/

* ios: import/export localizations

---------

Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: sith-on-mars <groguko36@pm.me>
Co-authored-by: Olivia Ng <uloo592@gmail.com>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: Jargon <sb94zj+4dyx519ewmt7o@sharklasers.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
Co-authored-by: Roee Hershberg <roihershberg@protonmail.com>
Co-authored-by: Titapa (PunPun) Chaiyakiturajai <titapapunne@gmail.com>
Co-authored-by: Jacky Lam <lamchun1110@gmail.com>
Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: zenobit <zen@osowoso.xyz>
Co-authored-by: Paulo Alexandre Pereira <github@paapereira.com>
Co-authored-by: petri <pkajander@gmail.com>
Co-authored-by: PigDog <blobster@tuta.io>
Co-authored-by: tay gelte <taydegelte@gufum.com>
Co-authored-by: Артём Котлубай <artemkotlubai@yandex.ru>
Co-authored-by: Maksym Lukashenko <livelmaxim@gmail.com>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Co-authored-by: Raman <aksharam.sme4i@aleeas.com>
Co-authored-by: Jaroslav Lichtblau <l10n@lichtblau.cz>
Co-authored-by: okmepro <ix28@proton.me>
2023-06-26 00:31:26 +01:00
Evgeny Poberezkin
8abad4f711 website: translations, Ukrainian language (#2613)
* Translated using Weblate (French)

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/fr/

* Translated using Weblate (French)

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/fr/

* Added translation using Weblate (Japanese)

* Translated using Weblate (Czech)

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/cs/

* Translated using Weblate (Dutch)

Currently translated at 99.5% (233 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/nl/

* Translated using Weblate (Dutch)

Currently translated at 99.5% (233 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/nl/

* Translated using Weblate (French)

Currently translated at 100.0% (234 of 234 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% (234 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/de/

* Translated using Weblate (Italian)

Currently translated at 100.0% (234 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/it/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (234 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/pt_BR/

* Translated using Weblate (Polish)

Currently translated at 100.0% (234 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/pl/

* Added translation using Weblate (Russian)

* Translated using Weblate (Dutch)

Currently translated at 99.5% (233 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/nl/

* Translated using Weblate (Japanese)

Currently translated at 2.5% (6 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/ja/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (234 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/es/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (234 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/

* Translated using Weblate (Arabic)

Currently translated at 100.0% (234 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/ar/

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (234 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/uk/

* Translated using Weblate (German)

Currently translated at 100.0% (234 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/de/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (234 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/nl/

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (234 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/uk/

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (234 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/uk/

* Translated using Weblate (Czech)

Currently translated at 100.0% (234 of 234 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% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/fr/

* Added translation using Weblate (Japanese)

* Translated using Weblate (Czech)

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/cs/

* Translated using Weblate (Dutch)

Currently translated at 99.5% (233 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/nl/

* Translated using Weblate (Dutch)

Currently translated at 99.5% (233 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/nl/

* Translated using Weblate (French)

Currently translated at 100.0% (234 of 234 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% (234 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/de/

* Translated using Weblate (Italian)

Currently translated at 100.0% (234 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/it/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (234 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/pt_BR/

* Translated using Weblate (Polish)

Currently translated at 100.0% (234 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/pl/

* Added translation using Weblate (Russian)

* Translated using Weblate (Dutch)

Currently translated at 99.5% (233 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/nl/

* Translated using Weblate (Japanese)

Currently translated at 2.5% (6 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/ja/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (234 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/es/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (234 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/

* Translated using Weblate (Arabic)

Currently translated at 100.0% (234 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/ar/

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (234 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/uk/

* Translated using Weblate (German)

Currently translated at 100.0% (234 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/de/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (234 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/nl/

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (234 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/uk/

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (234 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/uk/

* Translated using Weblate (Czech)

Currently translated at 100.0% (234 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/cs/

* Translated using Weblate (Arabic)

Currently translated at 100.0% (234 of 234 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/ar/

* add Ukranian language

---------

Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: zenobit <zen@osowoso.xyz>
Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: Random_1984 <weblate.x3nk3@simplelogin.com>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: mf <work.j6nnu@slmail.me>
Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
Co-authored-by: a4318 <dalse.077@gmail.com>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: Float <float.hu+@gmail.com>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Co-authored-by: Maksym Lukashenko <livelmaxim@gmail.com>
Co-authored-by: Jaroslav Lichtblau <l10n@lichtblau.cz>
2023-06-25 23:25:53 +01:00
Evgeny Poberezkin
4c668f7a34 website: social icons order 2023-06-25 16:06:53 +01:00
M Sarmad Qadeer
0bf5fbd641 website: add icons in footer (#2612) 2023-06-25 13:46:27 +01:00
Evgeny Poberezkin
cfec60bf86 website: monerokon group link 2023-06-25 12:01:19 +01:00
M Sarmad Qadeer
9caaab0e8e website: fix hero layout for small height screens (#2609) 2023-06-24 14:39:06 +01:00
Evgeny Poberezkin
6da18d9b2a core: group permision to allow files and media (#2610)
* core: group permision to allow files and media

* test
2023-06-24 12:36:07 +01:00
spaced4ndy
da2622f00e core: moderate messages that have arrived after the event of moderation (#2604)
* core: moderate messages that have arrived after the event of moderation

* remove index

* test, delete moderation

* unused selector

* rework

* refactor

* change error

* parameter

* fix syntax

* refactor

* Nothing

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-06-22 20:38:09 +04:00
Evgeny Poberezkin
7ed581dfbf Merge branch 'stable' 2023-06-21 21:54:25 +01:00
Evgeny Poberezkin
78c0fe73a7 readme: add simplex-devs group 2023-06-21 21:54:12 +01:00
spaced4ndy
15b00f6110 core, mobile: unhide share address (reverts #2468) (#2600) 2023-06-20 10:15:28 +04:00
spaced4ndy
f592a26b00 ios, android: increase disappearing message interval limit (#2599)
* ios, android: increase disappearing message interval limit

* Apply suggestions from code review

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-06-20 10:14:07 +04:00
spaced4ndy
30687f5fa6 ios, android: allow to set custom ttl per message if conversation timer is not set (#2598) 2023-06-20 10:13:16 +04:00
spaced4ndy
bc7217d686 ios, android: update connection stats on switch start (#2597) 2023-06-20 10:09:04 +04:00
Evgeny Poberezkin
8e0b3fa32e mobile: uncomment message reactions preferences/permissions 2023-06-19 22:47:49 +01:00
spaced4ndy
d929c34e71 core: include ConnectionStats into switch api response (#2594) 2023-06-19 16:07:17 +04:00
Evgeny Poberezkin
ec7bff9205 ios: search members (#2593) 2023-06-19 11:49:45 +01:00
spaced4ndy
22f20a9c5f ios, android: abort switching connection (#2584) 2023-06-19 14:46:08 +04:00
Evgeny Poberezkin
ddf81d28f1 ios: UI to filter favorite and unread chats (#2592)
* ios: UI to filter favorite and unread chats

* update localizations

* update colors

* star size and position

* filter button sizes and layout

* change AND to OR when both filters are chosen

* simplify filter UX

* store filter state in defaults

* remove comment, update localizations
2023-06-19 11:13:30 +01:00
Evgeny Poberezkin
5c105cb746 core: mark chats as favorite (#2591) 2023-06-18 12:46:38 +01:00
Evgeny Poberezkin
e1370e8f3c core: split Store.hs to multiple files for faster re-compilation (#2589)
* core: split Store.hs to multiple files for faster re-compilation

* remove unused compiler pragmas
2023-06-18 10:20:11 +01:00
Evgeny Poberezkin
9fbcc2b5bb core: rename module (#2587) 2023-06-17 11:03:22 +01:00
Evgeny Poberezkin
53d77b25ed core: count successes and failures for batch operations, only log errors in info log-level (#2585)
* core: count successes and failures for batch operations, only log errors in info log-level

* correction

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2023-06-17 10:34:04 +01:00
Evgeny Poberezkin
e7089d4c2f mobile: allow hiding profile when SimpleX lock is disabled (#2586) 2023-06-17 09:58:35 +01:00
spaced4ndy
6d3cb0ea2e core: api to abort connection switch; update simplexmq (#2544) 2023-06-16 19:05:53 +04:00
Evgeny Poberezkin
46c6f5e615 cli: option to auto-accept files (#2540)
* cli: option to auto-accept files

* auto-accept works

* test

* add missing field
2023-06-16 13:43:06 +01:00
Evgeny Poberezkin
c29c3179a0 Merge branch 'stable' 2023-06-16 12:23:33 +01:00
sh
3e84429a3a build.yml: bump actions version (#2580) 2023-06-16 12:23:03 +01:00
Evgeny Poberezkin
904b6db628 Merge branch 'stable' 2023-06-15 20:41:13 +01:00
Evgeny Poberezkin
af4e94058a readme: update users group link 2023-06-15 20:39:34 +01:00
Stanislav Dmitrenko
91b77b6d63 android: restart and shutdown the app with buttons (#2578)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-06-14 09:08:51 +01:00
Stanislav Dmitrenko
5a0c7c34bf ios: reactions in one line in menu (#2577)
* ios: reactions in one line in menu

* refactor, remove title

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-06-13 19:24:28 +01:00
spaced4ndy
3267b4d6ca 5.1.3: Android 127, iOS 150 2023-06-12 16:13:46 +04:00
spaced4ndy
9b302b856a ios: update binaries 2023-06-12 14:46:26 +04:00
spaced4ndy
4e696aed82 core: 5.1.3.0 2023-06-12 13:47:13 +04:00
spaced4ndy
425c7b947f core: optimize group deletion (delays deletion of unused contacts) (#2560)
* core: optimize group deletion (wip)

* delay deletion of unused contacts

* clean up, fix test

* rename field

* remove from type, more checks, remove ctx

* remove space

* rename functions

* rename

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-06-12 13:45:39 +04:00
M Sarmad Qadeer
d4f9429fc1 website: fix sidebar duplication issue (#2576) 2023-06-11 10:39:59 +01:00
Evgeny Poberezkin
161b43e85d ci: update node verion for website build 2023-06-11 08:40:47 +01:00
M Sarmad Qadeer
d585e8f5a7 website: glossary extended (#2574)
* web: quick fixes in glossary.md

* website: update header tags

* website: add glossary feature

* website: add & style the tooltips & glossary terms

* website: add overlay for glossary definition

* website: add list styling & update links

* website: fix tooltip alignment against multiple terms
fix the issue of aligning tooltip if page has multiple terms against it

* website: close already opened glossary overlay
close the already opened glossary overlay before opening another

* website: add popups for terms inside popups

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-06-11 08:31:41 +01:00
M Sarmad Qadeer
060e7cdf52 website: add glossary in reference dropdown (#2573) 2023-06-11 07:58:00 +01:00
M Sarmad Qadeer
6fa002948e website: fix logo color issue in dark mode in footer (#2572) 2023-06-11 07:54:31 +01:00
M Sarmad Qadeer
bbd4e6c8ba website: glossary feature (#2529)
* web: quick fixes in glossary.md

* website: update header tags

* website: add glossary feature

* website: add & style the tooltips & glossary terms

* website: add overlay for glossary definition

* website: add list styling & update links

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-06-11 07:52:55 +01:00
spaced4ndy
92cf945e10 core: update default mobile servers (#2548) 2023-06-11 07:48:03 +01:00
spaced4ndy
cc0f55c245 core, mobile: track contact connection network status when new contact joins group (#2566) 2023-06-09 16:43:53 +04:00
spaced4ndy
22f27c4255 mobile: allow to leave group in accepted status (#2564) 2023-06-09 14:40:17 +04:00
Stanislav Dmitrenko
14a888bf43 ios: show video in call from simulator (#2562) 2023-06-08 16:09:14 +01:00
Stanislav Dmitrenko
f6fddc9436 ios: WebRTC decryption between iOS-iOS works (#2561) 2023-06-08 15:08:35 +01:00
spaced4ndy
f581e91f19 core, mobile: correctly check whether date is recent (#2559) 2023-06-08 11:23:04 +04:00
spaced4ndy
fb72dfcdee core: calculate local item ts in view instead of having it in type (#2551) 2023-06-08 11:07:21 +04:00
Evgeny Poberezkin
925813b14c website: redirect page 2023-06-07 21:04:19 +01:00
sh
abd410fe62 build-android: refactor script (allow to parameterize script with architecture so builds can be done separately) (#2532)
* build-android: refactor script

* rearrange folder varibale

* fix gradle
2023-06-07 10:27:52 +04:00
spaced4ndy
875282e9ec core: fix parsing multiple servers passed as cli argument (#2549) 2023-06-06 14:17:14 +04:00
Evgeny Poberezkin
6afda28367 cli: change active chat after tail command (#2547) 2023-06-05 10:38:25 +04:00
Aminda Suomalainen
0721b24250 docs/SERVER: use sudo, specify protocol & add Fedora (#2542)
* docs/SERVER.md: specify protocol for ufw, use sudo & mention Fedora

* docs/lang/*/SERVER.md: update firewall instructions

* docs/SERVER (& lang): also reload firewalld

The manual page notes that --permanent is not effective immediately

* docs/{WEBRTC,XFTP-SERVER}.md: firewalld instructions

* docs/lang/*/WEBRTC.md: update firewall instructions from English

* Update docs/XFTP-SERVER.md

Co-authored-by: sh <37271604+shumvgolove@users.noreply.github.com>

* Update docs/SERVER.md

Co-authored-by: sh <37271604+shumvgolove@users.noreply.github.com>

* Update docs/lang/cs/SERVER.md

Co-authored-by: sh <37271604+shumvgolove@users.noreply.github.com>

* Update docs/lang/fr/SERVER.md

Co-authored-by: sh <37271604+shumvgolove@users.noreply.github.com>

* Update docs/lang/cs/WEBRTC.md

Co-authored-by: sh <37271604+shumvgolove@users.noreply.github.com>

* Update docs/lang/fr/WEBRTC.md

Co-authored-by: sh <37271604+shumvgolove@users.noreply.github.com>

* Update docs/WEBRTC.md

Co-authored-by: sh <37271604+shumvgolove@users.noreply.github.com>

---------

Co-authored-by: sh <37271604+shumvgolove@users.noreply.github.com>
2023-06-03 08:43:37 -07:00
spaced4ndy
10b6bce8a2 docs: connection switch improvements rfc (#2537) 2023-06-01 21:04:51 +04:00
Evgeny Poberezkin
0101444c5d cli: increase image size to match mobile auto-accept size limit, allow png and gif (#2535) 2023-05-30 11:18:27 +01:00
spaced4ndy
128883b8a3 core: improve queries performance; delay first chat item expiration cycle on start (#2521) 2023-05-29 15:18:22 +04:00
spaced4ndy
cc75b75d4e core: remove timing events (#2530) 2023-05-29 11:19:03 +04:00
Evgeny Poberezkin
dea6cd81c7 android: fix nl translation 2023-05-28 20:52:03 +01:00
sh
2f53ab08b5 github: update templates (#2527) 2023-05-28 20:51:31 +01:00
Evgeny Poberezkin
d7f3d1f19d 5.1.2: Android 125, iOS 149 2023-05-27 21:01:43 +01:00
Evgeny Poberezkin
a4517fcb9b core: 5.1.2.0 2023-05-27 19:34:02 +01:00
Evgeny Poberezkin
4a12cf0922 core: update simplexmq (fix missing ACK on duplicate messages) (#2525)
* core: update simplexmq (fix missing ACK on duplicate messages)

* update simplexmq
2023-05-27 19:31:56 +01:00
Evgeny Poberezkin
0ee91b0280 website: translations (#2502)
* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/

---------

Co-authored-by: ShouNichi <jiangyijjl@gmail.com>
Co-authored-by: Float <float.hu+@gmail.com>
2023-05-26 22:52:34 +01:00
Evgeny Poberezkin
e131890f54 mobile: show correct console times (#2519) 2023-05-26 16:55:57 +01:00
spaced4ndy
bd069aea49 core: add debug info to CEInvalidChatMessage (#2518) 2023-05-26 17:36:06 +04:00
spaced4ndy
42d4f94fec mobile: always show chat list search (#2517) 2023-05-26 15:49:26 +04:00
spaced4ndy
3af2848275 ios: fix database view crashing when in Japanese (#2516) 2023-05-26 15:19:20 +04:00
spaced4ndy
8b1e5d3db7 core: add indexes for cleanup, initial delay (#2514) 2023-05-26 14:03:26 +04:00
spaced4ndy
57ed903a48 Revert "core: don't keep connection of the merged contact (#2507)" (for cross-version compatibility)
This reverts commit 6093219ce9.
2023-05-26 13:52:06 +04:00
spaced4ndy
6093219ce9 core: don't keep connection of the merged contact (#2507) 2023-05-25 20:54:31 +04:00
spaced4ndy
f9f34911b1 android: only open direct or group chats when in processNotificationIntent (fixes error on address notification) (#2505) 2023-05-24 17:50:01 +04:00
Evgeny Poberezkin
494328541a ios: fix picker resetting value in iOS 15 (fixes disappearing messages and changing member role) (#2503)
* ios: fix picker resetting value in iOS 15

* group link view layout
2023-05-24 14:29:27 +01:00
spaced4ndy
fd2c7c888c core: stabilize tests (#2500) 2023-05-24 16:14:41 +04:00
Evgeny Poberezkin
24c09f2041 mobile: translations (#2501)
* Translated using Weblate (French)

Currently translated at 100.0% (1246 of 1246 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% (1143 of 1143 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 99.4% (1239 of 1246 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1246 of 1246 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1246 of 1246 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% (1143 of 1143 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 100.0% (1246 of 1246 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 96.5% (1103 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hans/

* Translated using Weblate (Czech)

Currently translated at 100.0% (1246 of 1246 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% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/cs/

* Translated using Weblate (Polish)

Currently translated at 100.0% (1246 of 1246 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pl/

* Translated using Weblate (Polish)

Currently translated at 100.0% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/pl/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 97.2% (1111 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hans/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1246 of 1246 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/

* Translated using Weblate (French)

Currently translated at 100.0% (1246 of 1246 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% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (1246 of 1246 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1246 of 1246 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1143 of 1143 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% (1246 of 1246 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% (1246 of 1246 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% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1246 of 1246 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/

* remove linebreak in NL

* Update apps/android/app/src/main/res/values-zh-rCN/strings.xml

revert change

* same translation as for Stop chat?

* import/export

---------

Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: Float <float.hu+@gmail.com>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: sith-on-mars <groguko36@pm.me>
Co-authored-by: zenobit <zen@osowoso.xyz>
Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
Co-authored-by: mf <work.j6nnu@slmail.me>
Co-authored-by: mlanp <github@lang.xyz>
2023-05-24 12:09:38 +01:00
spaced4ndy
a1e6d90e31 mobile: archive import errors (#2496) 2023-05-24 14:22:12 +04:00
Evgeny Poberezkin
de33fedea4 5.1.1: Android 123, iOS 148 2023-05-23 18:27:01 +01:00
Evgeny Poberezkin
9f89104f94 readme: translations (#2499)
* readme: translations

* -

* add Spanish group link
2023-05-23 17:43:06 +01:00
spaced4ndy
f0e88220c6 android: dropdown menu with delete button for moderated items (#2498) 2023-05-23 20:21:34 +04:00
Evgeny Poberezkin
6d7e16d6e1 docs: glossary (#2396)
* docs: glossary

* more terms

* more entries

* more definitions

* more definitions

* more definitions
2023-05-23 17:11:10 +01:00
Evgeny Poberezkin
d7d38fddb8 blog: v5.1 release announcement (#2492)
* blog: v5.1 release announcement

* update post

* readme

* typo

* update text

* add images

* corrections

* corrections, readme

* linebreaks

* separators
2023-05-23 15:41:29 +01:00
spaced4ndy
527a5bc6b5 core: replace catchError with Exception.catch in archive import (#2495) 2023-05-23 18:38:40 +04:00
spaced4ndy
9644dcb9b4 core: ArchiveError (#2493) 2023-05-23 15:54:44 +04:00
spaced4ndy
f4861482f1 mobile: group welcome message to fill max width (#2480)
* mobile: group welcome message to fill max width

* not limiting preview height

* min height, no scroll

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-05-23 14:31:43 +04:00
spaced4ndy
dc73bb3caf mobile: change disappearing messages dropdown choices (#2491) 2023-05-23 14:01:07 +04:00
spaced4ndy
bcbfc1758e core: catch errors on archive import (#2486)
* core: catch errors on archive import

* return list

* refactor

* rename

* rename

* refactor

* Update src/Simplex/Chat/Archive.hs

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* fix syntax

* refactor

* CRArchiveImported

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-05-23 13:51:23 +04:00
Evgeny Poberezkin
e65dcf51b0 rfc: community moderation (#2489) 2023-05-23 09:48:08 +01:00
spaced4ndy
1326701440 mobile: remove file on apiSendMessage error (#2487) 2023-05-23 12:43:18 +04:00
Evgeny Poberezkin
c32e45f686 identity RFC (#151) 2023-05-23 09:25:32 +01:00
Stanislav Dmitrenko
0160684004 android: periodic notifications and service start fix when database migrates (#2488)
* android: periodic notifications and service start fix when database migrates

* less timeout
2023-05-23 09:19:13 +01:00
Evgeny Poberezkin
734b920fde android: fix translations (#2483) 2023-05-23 08:54:56 +01:00
Evgeny Poberezkin
174e703b4c rfc: groups (#2364)
* rfc: groups

* message dissemination
2023-05-23 08:42:48 +01:00
spaced4ndy
36336a3a57 android: color set passcode buttons (#2479)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-05-22 21:03:48 +01:00
Evgeny Poberezkin
c10a4346a9 5.1.0: Android 121, iOS 147 2023-05-22 18:45:15 +01:00
Stanislav Dmitrenko
db55496fc7 android: show initialization screen on migration (#2478) 2023-05-22 17:25:50 +01:00
Evgeny Poberezkin
9a2efd0ef0 ios: fix showing init view (#2477)
* ios: fix showing init view

* init with delay

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2023-05-22 14:49:46 +01:00
Evgeny Poberezkin
579af09816 ci: configure pagefile size for Windows build (#2122)
* ci: configure pagefile size for Windows build

* change action version

* specify maximum

* change disk root

---------

Co-authored-by: shum <shum@liber.li>
2023-05-22 12:15:06 +01:00
Evgeny Poberezkin
d39614713d mobile: translations (#2474)
* Translated using Weblate (French)

Currently translated at 100.0% (1245 of 1245 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% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1245 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/

* Translated using Weblate (Japanese)

Currently translated at 99.8% (1141 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ja/

* Translated using Weblate (Japanese)

Currently translated at 99.8% (1243 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Polish)

Currently translated at 100.0% (1245 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pl/

* Translated using Weblate (Polish)

Currently translated at 100.0% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/pl/

* Translated using Weblate (German)

Currently translated at 100.0% (1245 of 1245 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% (1143 of 1143 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 93.7% (1167 of 1245 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 90.5% (1035 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hans/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1245 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1245 of 1245 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% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (1245 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (1245 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Czech)

Currently translated at 100.0% (1245 of 1245 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% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/cs/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (1245 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/

* Translated using Weblate (Hebrew)

Currently translated at 66.4% (827 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Russian)

Currently translated at 100.0% (1245 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/

* Translated using Weblate (French)

Currently translated at 100.0% (1245 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fr/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1245 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1245 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (1245 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (1245 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Russian)

Currently translated at 100.0% (1245 of 1245 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% (1143 of 1143 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% (1245 of 1245 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% (1143 of 1143 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% (1245 of 1245 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% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1245 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/

* corrections

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>

* Translated using Weblate (French)

Currently translated at 100.0% (1245 of 1245 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% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1245 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/

* Translated using Weblate (Japanese)

Currently translated at 99.8% (1141 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ja/

* Translated using Weblate (Japanese)

Currently translated at 99.8% (1243 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Polish)

Currently translated at 100.0% (1245 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pl/

* Translated using Weblate (Polish)

Currently translated at 100.0% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/pl/

* Translated using Weblate (German)

Currently translated at 100.0% (1245 of 1245 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% (1143 of 1143 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 93.7% (1167 of 1245 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 90.5% (1035 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hans/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1245 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1245 of 1245 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% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (1245 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (1245 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Czech)

Currently translated at 100.0% (1245 of 1245 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% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/cs/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (1245 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/

* Translated using Weblate (Hebrew)

Currently translated at 66.4% (827 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Russian)

Currently translated at 100.0% (1245 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/

* Translated using Weblate (French)

Currently translated at 100.0% (1245 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fr/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1245 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1245 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (1245 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (1245 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Russian)

Currently translated at 100.0% (1245 of 1245 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% (1143 of 1143 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% (1245 of 1245 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% (1143 of 1143 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% (1245 of 1245 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% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1245 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/

* Translated using Weblate (Czech)

Currently translated at 100.0% (1245 of 1245 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/

* correct nl

* correct nl

* correct nl

* Update apps/android/app/src/main/res/values-pt-rBR/strings.xml

* Translated using Weblate (Czech)

Currently translated at 100.0% (1143 of 1143 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/cs/

* import/export localizations

---------

Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: a4318 <dalse.077@gmail.com>
Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: sith-on-mars <groguko36@pm.me>
Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: tomato potato <4ryo49@protonmail.com>
Co-authored-by: zenobit <zen@osowoso.xyz>
Co-authored-by: mf <work.j6nnu@slmail.me>
Co-authored-by: Roee Hershberg <roihershberg@protonmail.com>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2023-05-22 10:38:04 +01:00
spaced4ndy
34af1e258c mobile: chat item info - show placeholder if version text is empty (#2476)
* mobile: show chat item text representation if message text is empty in info

* android, no menu

* undo diff

* update text for empty message in history

* update Android

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-05-22 12:36:20 +04:00
Evgeny Poberezkin
3ff68dbc7b 5.1.0-beta.1: Android 120, iOS 146 2023-05-21 00:13:38 +01:00
Evgeny Poberezkin
8952ac9af0 core: 5.1.0.1 2023-05-20 23:08:31 +01:00
Evgeny Poberezkin
7799a1e260 android: improve onboarding layout (#2472) 2023-05-20 23:07:06 +01:00
Evgeny Poberezkin
353927e6d2 mobile: hide share address until v5.2 (#2468)
* mobile: hide share address until v5.2

* terminal: hide /profile_address from help
2023-05-20 20:41:07 +01:00
Evgeny Poberezkin
d40db1ddea android: fix emoji 2023-05-20 19:55:51 +01:00
Evgeny Poberezkin
f85a9e174c mobile: translations (#2471)
* Translated using Weblate (Portuguese (Brazil))

Currently translated at 95.3% (1177 of 1234 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 95.3% (1177 of 1234 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/

* Translated using Weblate (Japanese)

Currently translated at 96.1% (1187 of 1234 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Japanese)

Currently translated at 96.1% (1187 of 1234 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 92.3% (1139 of 1234 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 95.3% (1177 of 1234 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 95.3% (1177 of 1234 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/

* Translated using Weblate (Japanese)

Currently translated at 96.1% (1187 of 1234 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Japanese)

Currently translated at 96.1% (1187 of 1234 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 92.3% (1139 of 1234 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 96.1% (1187 of 1234 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% (1234 of 1234 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/

* Translated using Weblate (Japanese)

Currently translated at 96.1% (1187 of 1234 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (1137 of 1137 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ja/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (1234 of 1234 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1234 of 1234 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1137 of 1137 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/

* add Japanese, import/export localizations

* android: add Japanese and Portuguese (Brazil)

---------

Co-authored-by: sith-on-mars <groguko36@pm.me>
Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: tomato potato <4ryo49@protonmail.com>
Co-authored-by: a4318 <dalse.077@gmail.com>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
2023-05-20 18:40:51 +01:00
Evgeny Poberezkin
838e14af60 mobile: whats new in v5.1 (#2470)
* mobile: whats new in v5.1

* post links, better layout

* ios: export localizations

* update translations
2023-05-20 17:09:18 +01:00
Evgeny Poberezkin
1d84c5cad8 blog: placeholder for v5.1 announcement 2023-05-20 14:57:31 +01:00
Evgeny Poberezkin
3be2259068 update available reactions (#2466) 2023-05-20 13:42:50 +01:00
Evgeny Poberezkin
acc4cad082 docs: update TRANSLATIONS.md 2023-05-20 11:55:53 +01:00
Evgeny Poberezkin
10a1788754 mobile: translations (#2465)
* Translated using Weblate (French)

Currently translated at 97.0% (1091 of 1124 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 95.8% (1090 of 1137 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 97.8% (1113 of 1137 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.1% (1116 of 1137 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 91.0% (1035 of 1137 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hans/

* Translated using Weblate (Spanish)

Currently translated at 95.8% (1090 of 1137 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/

* Translated using Weblate (Dutch)

Currently translated at 99.1% (1224 of 1234 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% (1137 of 1137 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% (1137 of 1137 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/

* Translated using Weblate (Japanese)

Currently translated at 95.6% (1087 of 1137 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ja/

* Translated using Weblate (Polish)

Currently translated at 100.0% (1234 of 1234 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pl/

* Translated using Weblate (Polish)

Currently translated at 100.0% (1137 of 1137 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/pl/

* Translated using Weblate (Portuguese)

Currently translated at 54.3% (671 of 1234 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/

* import/export localizations

---------

Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
Co-authored-by: Paulo Alexandre Pereira <github@paapereira.com>
2023-05-19 21:31:30 +01:00
Evgeny Poberezkin
f1c1059ff8 ios: export localizations 2023-05-19 20:31:24 +01:00
Evgeny Poberezkin
690c8ea2c9 website: translations (#2464)
* Added translation using Weblate (Chinese (Traditional))

* Added translation using Weblate (Chinese (Traditional))

* Translated using Weblate (German)

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/de/

* Translated using Weblate (Arabic)

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/ar/

---------

Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
2023-05-19 20:10:00 +01:00
Evgeny Poberezkin
1c8d1bc9ff mobile: translations (#2461)
* Translated using Weblate (Italian)

Currently translated at 100.0% (1171 of 1171 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% (1072 of 1072 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 21.2% (228 of 1072 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 70.3% (754 of 1072 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/pt_BR/

* Translated using Weblate (Portuguese)

Currently translated at 37.2% (437 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/

* Translated using Weblate (French)

Currently translated at 100.0% (1172 of 1172 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% (1072 of 1072 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1172 of 1172 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% (1072 of 1072 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/

* Translated using Weblate (Japanese)

Currently translated at 83.9% (984 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 96.5% (1131 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 96.5% (1131 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/

* Translated using Weblate (Portuguese)

Currently translated at 37.7% (442 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/

* Translated using Weblate (Hebrew)

Currently translated at 28.0% (329 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Japanese)

Currently translated at 90.6% (1063 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Japanese)

Currently translated at 90.6% (1063 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (1172 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 73.2% (785 of 1072 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/pt_BR/

* Translated using Weblate (Japanese)

Currently translated at 90.6% (1063 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Japanese)

Currently translated at 90.7% (1064 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Japanese)

Currently translated at 90.9% (1066 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Japanese)

Currently translated at 90.9% (1066 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Japanese)

Currently translated at 91.1% (1068 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Japanese)

Currently translated at 91.2% (1070 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Japanese)

Currently translated at 91.2% (1070 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Japanese)

Currently translated at 92.4% (1084 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Japanese)

Currently translated at 92.4% (1084 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (German)

Currently translated at 100.0% (1172 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/

* Translated using Weblate (Japanese)

Currently translated at 93.2% (1093 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Japanese)

Currently translated at 93.2% (1093 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Japanese)

Currently translated at 93.3% (1094 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Japanese)

Currently translated at 93.3% (1094 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Japanese)

Currently translated at 93.4% (1095 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Japanese)

Currently translated at 93.4% (1095 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (German)

Currently translated at 100.0% (1072 of 1072 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/

* Translated using Weblate (Italian)

Currently translated at 100.0% (1172 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 98.8% (1158 of 1172 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 72.6% (779 of 1072 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hant/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1172 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1072 of 1072 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/

* Translated using Weblate (Japanese)

Currently translated at 19.0% (204 of 1072 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ja/

* Translated using Weblate (Japanese)

Currently translated at 95.2% (1116 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Japanese)

Currently translated at 95.2% (1116 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Hebrew)

Currently translated at 35.1% (412 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1172 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 73.8% (792 of 1072 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/pt_BR/

* Translated using Weblate (Portuguese)

Currently translated at 41.8% (490 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/

* Translated using Weblate (Japanese)

Currently translated at 28.7% (308 of 1071 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ja/

* Translated using Weblate (Hebrew)

Currently translated at 37.3% (438 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Czech)

Currently translated at 94.9% (1113 of 1172 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 74.6% (800 of 1071 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/pt_BR/

* Translated using Weblate (Portuguese)

Currently translated at 43.5% (510 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/

* Translated using Weblate (Hebrew)

Currently translated at 39.7% (466 of 1172 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Japanese)

Currently translated at 31.3% (336 of 1071 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ja/

* Translated using Weblate (Japanese)

Currently translated at 38.1% (409 of 1071 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ja/

* Translated using Weblate (Italian)

Currently translated at 100.0% (1187 of 1187 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/

* Translated using Weblate (Hebrew)

Currently translated at 42.7% (507 of 1187 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Polish)

Currently translated at 100.0% (1187 of 1187 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pl/

* Translated using Weblate (Polish)

Currently translated at 100.0% (1071 of 1071 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/pl/

* Translated using Weblate (Hebrew)

Currently translated at 42.8% (509 of 1187 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (French)

Currently translated at 100.0% (1187 of 1187 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 100.0% (1187 of 1187 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/

* Translated using Weblate (Japanese)

Currently translated at 38.9% (417 of 1071 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ja/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.0% (1176 of 1187 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/

* Translated using Weblate (Portuguese)

Currently translated at 46.8% (556 of 1187 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/

* Translated using Weblate (Hebrew)

Currently translated at 43.4% (516 of 1187 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Japanese)

Currently translated at 50.7% (543 of 1071 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ja/

* Translated using Weblate (Japanese)

Currently translated at 94.1% (1117 of 1187 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (1187 of 1187 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 100.0% (1071 of 1071 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hant/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (1071 of 1071 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ja/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (1187 of 1187 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/

* Translated using Weblate (Portuguese)

Currently translated at 47.2% (561 of 1187 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/

* Translated using Weblate (Hebrew)

Currently translated at 44.3% (526 of 1187 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Spanish)

Currently translated at 98.8% (1173 of 1187 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Portuguese)

Currently translated at 48.1% (572 of 1187 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/

* Translated using Weblate (Hebrew)

Currently translated at 44.7% (531 of 1187 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1187 of 1187 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1071 of 1071 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1187 of 1187 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1071 of 1071 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.1% (1177 of 1187 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 74.8% (802 of 1071 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/pt_BR/

* Translated using Weblate (Portuguese)

Currently translated at 50.2% (596 of 1187 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/

* Translated using Weblate (Portuguese)

Currently translated at 50.2% (596 of 1187 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/

* Translated using Weblate (Hebrew)

Currently translated at 48.1% (571 of 1187 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Portuguese)

Currently translated at 50.6% (601 of 1187 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/

* Translated using Weblate (Hebrew)

Currently translated at 48.6% (578 of 1187 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (German)

Currently translated at 100.0% (1187 of 1187 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/

* Translated using Weblate (Hebrew)

Currently translated at 51.2% (608 of 1187 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Portuguese)

Currently translated at 52.2% (620 of 1187 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/

* Translated using Weblate (Portuguese)

Currently translated at 55.2% (656 of 1187 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/

* Translated using Weblate (Hebrew)

Currently translated at 58.6% (696 of 1187 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Italian)

Currently translated at 100.0% (1199 of 1199 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1199 of 1199 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/

* Translated using Weblate (Hebrew)

Currently translated at 58.9% (707 of 1199 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (French)

Currently translated at 100.0% (1212 of 1212 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 100.0% (1212 of 1212 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/

* Translated using Weblate (Polish)

Currently translated at 100.0% (1212 of 1212 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pl/

* Translated using Weblate (Hebrew)

Currently translated at 64.2% (779 of 1212 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Hebrew)

Currently translated at 64.6% (784 of 1212 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Italian)

Currently translated at 100.0% (1214 of 1214 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 92.0% (1117 of 1214 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 92.0% (1117 of 1214 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 96.4% (1033 of 1071 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% (1214 of 1214 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/

* Translated using Weblate (Hebrew)

Currently translated at 67.0% (814 of 1214 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 92.0% (1117 of 1214 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 92.0% (1117 of 1214 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 96.4% (1033 of 1071 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 92.0% (1117 of 1214 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 92.0% (1117 of 1214 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 92.0% (1117 of 1214 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 92.0% (1117 of 1214 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 92.0% (1117 of 1214 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 92.0% (1117 of 1214 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 92.0% (1117 of 1214 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 92.0% (1117 of 1214 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 92.0% (1117 of 1214 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 92.0% (1117 of 1214 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 92.0% (1117 of 1214 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 92.0% (1117 of 1214 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 92.1% (1119 of 1214 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 92.1% (1119 of 1214 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 96.5% (1034 of 1071 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hans/

* import/export localizations

---------

Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: Paulo Alexandre Pereira <github@paapereira.com>
Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: Dang Boumou <mail2mail4@proton.me>
Co-authored-by: Feroli <feroli@tuta.io>
Co-authored-by: Roee Hershberg <roihershberg@protonmail.com>
Co-authored-by: a4318 <dalse.077@gmail.com>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: Olivia Ng <uloo592@gmail.com>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: zenobit <zen@osowoso.xyz>
Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
Co-authored-by: Felipe Nogueira <contato.fnog@gmail.com>
Co-authored-by: sith-on-mars <groguko36@pm.me>
Co-authored-by: ShouNichi <jiangyijjl@gmail.com>
2023-05-19 20:00:25 +01:00
Evgeny Poberezkin
a9416d89e3 ios: prevent scrolling terminal view every time detail view is closed 2023-05-19 18:58:03 +01:00
Evgeny Poberezkin
9e33ba46af ios: update api for message info, refactor, localize (#2458)
* update api for message info, refactor, localize

* refactor

* change text, layout

* show reactions on deleted revealed items

* update

* trim

* refactor

* corrections

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2023-05-19 20:50:48 +04:00
Evgeny Poberezkin
a0c4726af3 android: ui for message details (#2454)
* android: ui for message details

* edit history, share text

* show info for deleted items, show reactions on revealed items

* unused imports

* revert swift changes

* update

* color

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2023-05-19 20:36:25 +04:00
spaced4ndy
a0b3c0a5a4 android: allow to configure custom disappearance interval on chat level (#2460) 2023-05-19 16:54:27 +04:00
Evgeny Poberezkin
9978957e6c core: deleted timestamps for chat item (#2459)
* core: edited and deleted timestamps for item

* migration

* add deleted timestamp to chat item, use chat item if there are no versions

* use broker timestamp for remote deletions

* refactor
2023-05-19 13:52:51 +01:00
spaced4ndy
f155611d29 android: allow to set disappearance interval when sending message (#2455) 2023-05-18 20:00:16 +04:00
Evgeny Poberezkin
01b3e98358 core: update chat item details api (#2456) 2023-05-18 16:52:58 +01:00
M Sarmad Qadeer
3a50da1b53 website: fix guide urls related to blogs (#2431) 2023-05-18 11:03:35 +01:00
Evgeny Poberezkin
a32fd5e665 android: message reactions (#2448)
* android: message reactions

* android: reactions UI

* call api to add/remove reactions, UI for preferences

* fix preferences

* hide Reactions preferences, ios: always show React menu, update icons

* fix reactions menu

* improve voice message layout
2023-05-18 10:43:44 +01:00
spaced4ndy
b6a4f5f518 core: enable creation of decryption errors chat items (#2450) 2023-05-17 16:13:35 +04:00
spaced4ndy
e799e80843 ios: remove "off" from group preferences ttl picker; core: allow optional ttl for group preference (#2452) 2023-05-17 13:31:24 +04:00
Evgeny Poberezkin
63cb7a75b3 ios: update reactions api (#2451) 2023-05-17 09:31:27 +01:00
Evgeny Poberezkin
922e95756a core: use JSON in reactions api, forward compatible JSON parsing for reactions (#2449) 2023-05-17 00:22:00 +01:00
Evgeny Poberezkin
b49f0d211b cli: amend help messages (#2447)
* cli: amend help messages

* print servers on multiple lines

* correction

* fix tests
2023-05-16 18:37:45 +01:00
spaced4ndy
761fbf7757 core: remove temp files directory when deleting storage (#2446) 2023-05-16 15:05:18 +04:00
spaced4ndy
4ee052e71e ios: update libraries (time diff calculation), time unit limits (#2445) 2023-05-16 15:04:47 +04:00
spaced4ndy
0274f3c2ac core: inform of profile_address command when creating address (#2444) 2023-05-16 15:03:49 +04:00
spaced4ndy
ae13f1aa23 mobile: connect to member via address (#2441) 2023-05-16 15:03:41 +04:00
Evgeny Poberezkin
904405ebee ios: reactions UI (#2442)
* ios: reactions UI

* remove JSON

* remove print

* align reactions, show all allowed reactions in  menu

* move react to the menu top

* ios: update preference texts

* always allow removing reactions, reduce spacing

* revent allow removing (backend does not allow it anyway)
2023-05-16 09:34:25 +01:00
spaced4ndy
a059739210 core: update simplexmq (time diff calculation); core, ios: add deleteAt to chat item info (#2440) 2023-05-15 21:07:03 +04:00
spaced4ndy
25156bb56c ios: allow to set disappearance interval when sending message; allow to configure custom interval (#2428)
* ios: allow to set disappearance interval when sending message; allow to configure custom interval

* custom time picker wip

* improve interaction with time picker - touch area, cancel, keyboard, preference

* dropdown picker, refactor, text

* button condition

* weeks limit

* refactor

* update texts

* simplify

* fix null selection

* texts, set current, switch columns

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-05-15 16:07:55 +04:00
Evgeny Poberezkin
d62761b3a8 terminal: show message that failed to parse (#2439) 2023-05-15 12:47:16 +01:00
Evgeny Poberezkin
817c0a5672 core: delete message reaction (#2438)
* core: delete message reaction

* remove unused name

* refactor

* remove unused names

* refactor 2
2023-05-15 12:43:22 +01:00
Evgeny Poberezkin
c06a970987 core: message reactions (#2429)
* core: message reactions

* remove comments

* core: commands to set reactions

* fix tests

* process reaction messages

* store functions

* include reactions on item updates

* remove print

* view, tests

* load reactions for new items

* test removing reaction

* remove spaces

* limit the number of different reactions on one item

* remove unique constraints

* fix permissions

* indexes

* check chat item content before adding reaction

* fix group reactions

* simpler index

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2023-05-15 11:28:53 +01:00
Stanislav Dmitrenko
baf3a12009 mobile: increased size of voice messages (#2150)
* android: increased size of voice messages

* ios: increased size of voice messages

* size changes

* increase audio quality to 16kHz/32Kbps, refactor auto-accept logic

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-05-15 09:17:32 +01:00
Stanislav Dmitrenko
0ec2468dce ios: slider for voice messages (#2432)
* ios: slider for voice messages

* better layout hiding and showing

* properly stop playback when other media started

* better layout

* change padding

* code style

* refactor

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-05-14 18:07:34 +01:00
Evgeny Poberezkin
0cfc9fd1fa mobile: enable calls preference (#2436)
* ios: enable calls preference

* android: enable calls preference
2023-05-13 10:22:31 +01:00
Evgeny Poberezkin
a2de9a3846 update typescript client (#2427)
* update typescript client

* update bot

* fix bot
2023-05-11 16:33:23 +01:00
spaced4ndy
88059a2cc5 core: allow to set disappearance interval when sending message; don't check content change on live item updates (#2423)
* core: allow to set disappearance interval when sending message

* remove commented code

* enable tests

* don't check content change on live item updates

* update logic

* rename variable

* refactor, restore that received message can disabled disappearing

* refactor

* Revert "refactor"

This reverts commit 60dee29d76.

* separate event

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-05-11 16:00:01 +04:00
Evgeny Poberezkin
635d797b2e docs: themes (#2425)
* docs: themes

* update

* update 2

* bigger images
2023-05-11 10:11:38 +01:00
Stanislav Dmitrenko
e635c45ec6 ios: fixed keyboard (#2424) 2023-05-10 18:45:55 +01:00
Stanislav Dmitrenko
594ae61192 android: paddings and background for voice messages (#2422)
* android: paddings and background for voice messages

* filePath for seek method

* paddings
2023-05-10 16:24:38 +01:00
Stanislav Dmitrenko
2945f688fa android: voice message slider (#2421)
* android: voice message slider

* no ring line

* different color of sliders

* Revert "no ring line"

This reverts commit aaec2a1136.

* size of preview and non-crashed slider

* padding

* size of voice slider

* padding

* background width

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-05-10 13:59:59 +01:00
Evgeny Poberezkin
d86cca2e26 core: fix user timestamp (#2420) 2023-05-10 13:47:36 +01:00
spaced4ndy
fca315ee1f core: update simplexmq (pending replicas indexes) (#2418) 2023-05-10 16:09:58 +04:00
Stanislav Dmitrenko
a12f140333 android: self destruct passcode (#2414)
* android: self destruct passcode

* icon at the end of text instead of start

* removed todo and moved to suspend function

* properly restart chat after database deletion

* changes

* android: disable self-destruct on LA mode change to "system", create new profile with past timestamp
2023-05-10 13:05:50 +01:00
spaced4ndy
ad7e4488ef core: time actions on chat start (#2417) 2023-05-10 15:18:50 +04:00
Evgeny Poberezkin
df4e954f8a ios: disable self-destruct on LA mode change to "system", create new profile with past timestamp (#2416) 2023-05-10 08:06:18 +01:00
spaced4ndy
63f344bde6 ios: view edit history; core: prohibit item updates w/t changes (#2413)
* ios: view edit history; core: prohibit item updates w/t changes

* read more less wip

* Revert "read more less wip"

This reverts commit 8e0663377b.

* comment for translations
2023-05-09 20:43:21 +04:00
Evgeny Poberezkin
0b8d9d11e2 core, iOS: support for self-destruct password (#2412)
* core, iOS: support for self-destruct password

* disable test logging

* core: fix tests, iOS: remove notifications on removal

* change alerts
2023-05-09 09:33:30 +01:00
Stanislav Dmitrenko
57801fde1f desktop: RFC (#2409) 2023-05-08 20:05:49 +01:00
spaced4ndy
c87f4e68f7 core: keep chat item edit history (#2410) 2023-05-08 20:07:51 +04:00
Stanislav Dmitrenko
27762492d7 android: onboarding notifications alert and restoring state (#2408)
* android: onboarding notifications alert and restoring state

* add comment

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-05-08 13:16:07 +01:00
Stanislav Dmitrenko
da7c408686 android: restore onboarding step (#2394) 2023-05-05 17:19:46 +01:00
Stanislav Dmitrenko
0c0a98605d android: progress indicator on migrations w/t confirmation (#2393)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-05-05 14:39:25 +01:00
Stanislav Dmitrenko
108226bcdc android: don't show auth screen in call (#2392)
* android: don't show auth screen in call

* Surface instead of Box to prevent touches through it

* name of function
2023-05-05 14:30:54 +01:00
Evgeny Poberezkin
8b400d4f2c website: enable Polish and Portuguese languages 2023-05-05 13:26:11 +01:00
Evgeny Poberezkin
d838e7b44d website: translations (#2389)
* Translated using Weblate (Polish)

Currently translated at 100.0% (211 of 211 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/pl/

* Translated using Weblate (Polish)

Currently translated at 100.0% (211 of 211 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/pl/

* Translated using Weblate (Polish)

Currently translated at 100.0% (211 of 211 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/pl/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (211 of 211 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/nl/

* Translated using Weblate (Polish)

Currently translated at 100.0% (211 of 211 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/pl/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (211 of 211 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (211 of 211 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/es/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 19.4% (41 of 211 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 76.7% (162 of 211 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/pt_BR/

* Translated using Weblate (French)

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/fr/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/nl/

* Translated using Weblate (Polish)

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/pl/

* Translated using Weblate (Italian)

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/it/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 97.4% (227 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/pt_BR/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (233 of 233 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/es/

---------

Co-authored-by: Display Name <ptrumtine@proton.me>
Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: Feroli <feroli@tuta.io>
Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: ShouNichi <jiangyijjl@gmail.com>
Co-authored-by: mf <work.j6nnu@slmail.me>
Co-authored-by: Float <float.hu+@gmail.com>
2023-05-05 13:22:58 +01:00
spaced4ndy
7b157fa8e5 ios: progress indicator on migrations w/t confirmation (#2378)
* ios: progress indicator on migrations w/t confirmation

* layout

* Update apps/ios/Shared/ContentView.swift

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* add delay

* move on appear

* Update apps/ios/Shared/ContentView.swift

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* Update apps/ios/Shared/ContentView.swift

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-05-05 15:52:16 +04:00
spaced4ndy
d19a59a364 core: start receiving files on chat activation (#2387) 2023-05-05 15:49:31 +04:00
spaced4ndy
b95a351222 mobile: group welcome message layout (#2388) 2023-05-05 15:49:10 +04:00
Evgeny Poberezkin
1038acd2ea mobile: translations (#2386)
* Translated using Weblate (German)

Currently translated at 100.0% (1125 of 1125 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% (1042 of 1042 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% (1125 of 1125 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% (1042 of 1042 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% (1125 of 1125 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% (1042 of 1042 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1125 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1042 of 1042 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1125 of 1125 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% (1042 of 1042 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (1125 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/

* Translated using Weblate (Lithuanian)

Currently translated at 47.2% (531 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/lt/

* Translated using Weblate (Polish)

Currently translated at 100.0% (1125 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pl/

* 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/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1125 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1042 of 1042 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/

* Added translation using Weblate (Portuguese)

* Added translation using Weblate (Portuguese)

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1125 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 98.6% (1110 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hant/

* Translated using Weblate (Portuguese)

Currently translated at 10.2% (115 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/

* Translated using Weblate (Portuguese)

Currently translated at 10.2% (115 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/

* 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/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (1125 of 1125 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 48.9% (510 of 1042 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hant/

* 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/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1125 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (1125 of 1125 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 55.2% (576 of 1042 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hant/

* 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/

* Translated using Weblate (Portuguese)

Currently translated at 10.8% (122 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/

* 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/

* Added translation using Weblate (Greek)

* Added translation using Weblate (Greek)

* Translated using Weblate (Greek)

Currently translated at 1.4% (16 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/el/

* 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/

* Translated using Weblate (Czech)

Currently translated at 99.8% (1123 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/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/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1125 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1042 of 1042 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1125 of 1125 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% (1042 of 1042 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/

* Translated using Weblate (Portuguese)

Currently translated at 11.2% (126 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/

* Translated using Weblate (Portuguese)

Currently translated at 15.9% (179 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (1125 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/

* Translated using Weblate (Portuguese)

Currently translated at 24.9% (281 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 4.2% (44 of 1042 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/pt_BR/

* Translated using Weblate (Portuguese)

Currently translated at 25.2% (284 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/

* Translated using Weblate (Portuguese)

Currently translated at 27.4% (309 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/

* Translated using Weblate (Portuguese)

Currently translated at 30.5% (344 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/

* Added translation using Weblate (Hebrew)

* Added translation using Weblate (Hebrew)

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1125 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/

* Translated using Weblate (Hebrew)

Currently translated at 1.5% (17 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Portuguese)

Currently translated at 30.9% (348 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/

* Translated using Weblate (Greek)

Currently translated at 5.0% (57 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/el/

* Translated using Weblate (Hebrew)

Currently translated at 5.7% (65 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Hebrew)

Currently translated at 5.8% (66 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Hebrew)

Currently translated at 6.2% (70 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Hebrew)

Currently translated at 6.9% (78 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Hebrew)

Currently translated at 9.1% (103 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Hebrew)

Currently translated at 11.1% (125 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1125 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1125 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1042 of 1042 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/

* Translated using Weblate (Hebrew)

Currently translated at 18.2% (205 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Hebrew)

Currently translated at 18.7% (211 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Hebrew)

Currently translated at 19.2% (217 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* Translated using Weblate (Hebrew)

Currently translated at 21.2% (239 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/

* ios: import/export localizations

---------

Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: mf <work.j6nnu@slmail.me>
Co-authored-by: Moo <hazap@hotmail.com>
Co-authored-by: Display Name <ptrumtine@proton.me>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Olivia Ng <uloo592@gmail.com>
Co-authored-by: Paulo Alexandre Pereira <github@paapereira.com>
Co-authored-by: sith-on-mars <groguko36@pm.me>
Co-authored-by: Panagis Sarantos <sarantospgs@gmail.com>
Co-authored-by: zenobit <zen@osowoso.xyz>
Co-authored-by: Feroli <feroli@tuta.io>
Co-authored-by: ShouNichi <jiangyijjl@gmail.com>
Co-authored-by: Roee Hershberg <roihershberg@protonmail.com>
2023-05-05 11:46:18 +01:00
spaced4ndy
54fc052e47 core: remove msg_delivery_events unique constraint (recreates table); cleanup old messages (#2376) 2023-05-05 13:49:09 +04:00
Evgeny Poberezkin
62bac800af ios: export localizations 2023-05-05 10:44:52 +01:00
spaced4ndy
aa2b36d5cc ios: restore onboarding step (#2384) 2023-05-05 12:56:48 +04:00
Evgeny Poberezkin
b5f482bb50 5.1.0-beta.0: Android 119, iOS 145 2023-05-04 18:22:17 +01:00
Stanislav Dmitrenko
649c104d29 android: create address during onboarding (#2374)
* android: create address during onboarding

* refactor

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2023-05-04 20:28:29 +04:00
Stanislav Dmitrenko
41368c85bf ios: more localized strings (#2377)
* ios: more localized strings

* translation comments

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-05-04 16:40:47 +01:00
Evgeny Poberezkin
af59178318 core: 5.1.0.0 2023-05-04 12:45:57 +01:00
Stanislav Dmitrenko
5149623b57 ios: prevent crash when no email account was setup (#2375) 2023-05-04 11:13:53 +01:00
spaced4ndy
205c74b5d8 core: make use of covering indexes when reading chats (#2372) 2023-05-03 20:26:04 +04:00
Evgeny Poberezkin
5116bfa79c cli: option to disable notifications, closes #2340 (#2373) 2023-05-03 16:40:11 +01:00
Stanislav Dmitrenko
69767126aa mobile: disable ability to write text in text view when sending a message and prevent saving message that is in progress as draft (#2371)
* android: prevent saving message that is in progress as draft

* android: disable ability to write text in text view when sending a message

* ios: prevent saving message that is in progress as draft

* ios: disable ability to write text in text view when sending a message

* do not show welcome button when not needed
2023-05-03 13:57:10 +01:00
spaced4ndy
5af389ae3f mobile: change links from repo to website (#2370) 2023-05-03 15:01:45 +04:00
Stanislav Dmitrenko
f711f4d8a8 android: socks proxy port (#2367)
* android: socks proxy port

* ability to specify socks host and port before enabling it

* comment
2023-05-03 11:23:23 +01:00
Stanislav Dmitrenko
8b80efd537 android: contact address UX (#2363)
* android: contact address UX

* unneeded block

* review changes
2023-05-03 12:42:43 +04:00
spaced4ndy
7b83450a9c ios: AddGroupMembersView fixes (#2369) 2023-05-03 12:17:39 +04:00
spaced4ndy
e3011a1cb0 core: disable creation of decryption errors chat items (#2365) 2023-05-02 19:38:58 +04:00
Stanislav Dmitrenko
7ff8dcfb78 android: floating button background (#2366) 2023-05-02 16:38:20 +01:00
spaced4ndy
551ed202be ios: create address during onboarding (#2362)
* ios: create address during onboarding

* contact picker

* email wip

* send email w/t leaving app

* fomatting

* layout, texts

* remove contact picker, add email button to address page

* refactor

* refactor

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-05-01 20:36:52 +04:00
Evgeny Poberezkin
6f11913359 core: fix CIStatus parser (#2361) 2023-05-01 11:33:30 +01:00
Stanislav Dmitrenko
d91a78da7d android: fixed background on some screens (#2360)
* android: fixed background on some screens

* background color and alert title color

* Revert "android: export all theme colors (#2348)"

This reverts commit 315d830357.

* small changes in logic

* unused color

* colors of background
2023-05-01 10:38:59 +01:00
M Sarmad Qadeer
02fdd058ec website: add theme switch for code snippets (#2359) 2023-05-01 09:44:15 +01:00
spaced4ndy
08148afac7 Merge pull request #2336 from simplex-chat/contact-address-ux
core, mobile: contact address ux
2023-05-01 11:21:53 +04:00
Evgeny Poberezkin
f037ffe107 core: prevent failure loading chat on invalid item content JSON (#2349)
* core: prevent failure loading chat on invalid item content JSON

* corrections

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>

* platform independent parsing

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2023-05-01 08:18:04 +01:00
M Sarmad Qadeer
fa6ba3110b website: fix Chinese in dropdown (#2358) 2023-05-01 07:38:59 +01:00
Evgeny Poberezkin
6f82ddc032 website: fix tailwindcss version 2023-05-01 07:13:29 +01:00
Evgeny Poberezkin
ee3267388f website: fix build 2023-04-30 23:22:35 +01:00
M Sarmad Qadeer
f97a1fcedf website: add docs to website (#2080)
* website: add fontmatter & improve image URLs where necessary

* website: add docs to website

* website: add prismjs for code highlighting

* website: change npm install position in web.sh

* website: fix an image URL in lang/cs/README.md

* website: improve image paths in lang/cs/translations.md

* website: add responsiveness & improve stylings of docs

* website: add dir to navbar in blog & docs

* website: remove scroll in mobile dropdown menu

* website: remove rfcs & add guide docs to website

* website: remove file renaming script from web.sh

* website: add menu to docs in nav

* website: add hash list & add scroll to headers

* website: customize docs frontmatter through JS

* website: remove supported_languages.json

* website: move merge_translations.js to JS folder

* website: add the following changes to docs
- add frontmatter to new doc merged from master
- add ignoreForWeb property to frontmatter of README.md docs

* website: remove package-lock.json from .gitignore

* website: add package-lock.json from .gitignore

* website: add no docs message to docs dropdown

* website: improve the sidebar of docs

* website: add revision date to docs

* website: add script to add version to docs frontmatter

* website: add layout to display message in docs if its version is old

* website: improve nav responsiveness

* website: remove frontmatter form main README & rfcs

* website: remove rfcs from website folder

* website: add ignore condition for rfcs in .eleventy

* website: remove frontmatter from lang README docs

* website: remove README from website's lang docs

* website: add guides menu in nav

* website: following changes
- add docs_dropdown.json
- extend reference menu in nav
- remove docs menu from nav

* website: fix in docs sidebar

* website: revert main docs README.md files

* website: revert main docs README.md files

* website: move scripts out of js that are for build

* website: remove displayAt form guide docs

* website: create a docs_sidebar.json & shift to that approach

* update navigation

* website: set navbar

* website: add icons to external links

* website: change the approach for docs sidebar creation

* website: update docs template

* website: add some strings to en.json and map them accordingly

* remove icon

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-04-30 22:31:23 +01:00
Evgeny Poberezkin
315d830357 android: export all theme colors (#2348) 2023-04-29 13:11:44 +01:00
M Sarmad Qadeer
00caeae914 website: add simplex reviews section (#2346)
* website: add simplex reviews section

* website: update review section position & add quality images

* website: add dark mode images

* website: avoid text selection when hover on logos

* website: add layout fix

* website: add spaces in simplex review

* website: improve margin of simplex review

* website: add title to logos in simplex review

* titles, order

* update images

* move images

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-04-29 09:53:27 +01:00
spaced4ndy
199835b671 ios: allow all group members to view welcome message (#2347)
* ios: allow all group members to view welcome message

* remove diff

* layout

* ignore taps on preview

* remove newline

* show/hide keyboard

* fix button flickering

* remove space

* remove unused function

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-04-28 20:34:24 +04:00
spaced4ndy
ce2225d355 ios: align 1-time link design with new address design (#2337)
* ios: align 1-time link design with new address design

* update text

* layout

* bigger font, icon

* padding

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-04-28 14:11:32 +04:00
Evgeny Poberezkin
b4f1f94bcc Merge branch 'master' into contact-address-ux 2023-04-28 10:41:49 +01:00
spaced4ndy
90cee6b802 ios: scroll address view on keyboard, translations (#2344)
* ios: scroll address view on keyboard, translations

* fix typo

* revert text

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-04-28 12:21:23 +04:00
spaced4ndy
2a883bb958 core: add /profile_address command to help (#2345) 2023-04-28 12:14:07 +04:00
Stanislav Dmitrenko
607f77d432 android: more checks for colors in customized theme (#2343)
* android: more checks for colors in customized theme

* code style

* refactor

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-04-28 09:09:42 +01:00
Stanislav Dmitrenko
c254b33753 android: blue theme and more options (#2331)
* android: blue theme and more options

* more color options and better code

* more color options and some fixes

* removed preferences about non-existent theme

* colors

* Revert "removed preferences about non-existent theme"

This reverts commit cbb38d54a8.

* colors

* update colors

* migrations

* HighOrLowLight -> secondary

* new color

* color

* update colors, move colors to a separate page

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-04-27 20:32:40 +01:00
Stanislav Dmitrenko
59f3848056 android: cancel and destructive buttons in alerts (#2238) 2023-04-27 17:00:03 +01:00
Stanislav Dmitrenko
69aa002c83 android: prevent click & long click at the same time (#2237) 2023-04-27 16:44:12 +01:00
spaced4ndy
0b57cc08a7 core, ios: include contact addresses in profiles (#2328)
* core: include contact links in profiles

* add connection request link to contact and group profiles

* set group link on update, view, api

* core: include contact addresses in profiles

* remove id from UserContactLink

* schema, fix test

* remove address from profile when deleting link, tests

* remove diff

* remove diff

* fix

* ios wip

* learn more, confirm save, reset on delete

* re-use in create link view

* remove obsolete files

* color

* revert scheme

* learn more with create

* layout

* layout

* progress indicator

* delete text

* save on change, layout

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-04-27 17:19:21 +04:00
sh
8630d1ab12 templates: update labels (#2335) 2023-04-27 09:18:40 +01:00
Evgeny Poberezkin
591aa9eaa5 core: get all chat items API (#2333)
* core: get all chat items API

* test
2023-04-27 08:12:34 +01:00
sh
f82fa42cba github: issue templates (#2330)
* issue template test

* templates: allow blank issues
2023-04-26 16:37:13 +01:00
Evgeny Poberezkin
aa441c88db Merge branch 'stable' 2023-04-26 08:30:55 +01:00
Evgeny Poberezkin
17ee22da72 readme: update group link 2023-04-26 08:30:35 +01:00
spaced4ndy
a9957fb46d core: delete xftp file when user is not found by file id (#2234) 2023-04-25 15:46:00 +04:00
Stanislav Dmitrenko
f5c87fdd4c android: better auth when opening profiles (#2236)
* android: better auth when opening profiles

* consistent behaviour between auth methods

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-04-25 10:45:46 +01:00
Stanislav Dmitrenko
23467a2248 android: lint fix and removed unused lib (#2235) 2023-04-25 10:31:26 +01:00
M Sarmad Qadeer
9fa93e40cb website: add atom feeds for blogs (#2233)
* website: add atom feeds for blogs

* update front-matter

* website: add atom and rss feeds for blogs

* include full blog content

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-04-24 19:31:59 +01:00
M Sarmad Qadeer
f4b852d2dd website: fix chines in dropdown (#2232) 2023-04-24 18:54:57 +01:00
Stanislav Dmitrenko
f698b7fa9f android: new icons (#2231)
* android: new icons

* different size

* all icons were changed

* sizes

* account icon was returned

* icon

* two icons

* bolt icon

* fix change avatar

* more vert button

* changes in icons

* icon size

* deleted unneeded icon

* icons

* spacer and padding

* no comments

* height of item

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-04-24 18:51:21 +01:00
Stanislav Dmitrenko
b21afa648d android: UserPicker enhancements (#2230)
* android: UserPicker enhancements

* paddings
2023-04-23 10:42:06 +01:00
Evgeny Poberezkin
b9575cc869 website: fix qrcode dependency 2023-04-22 17:19:49 +01:00
Evgeny Poberezkin
3ea91bc4ad Merge branch 'stable' 2023-04-22 17:04:02 +01:00
Evgeny Poberezkin
b3dce5fdb0 blog: v5 announcement (#2225)
* blog: vision, funding, v5 announcement

* keep file name

* update

* update blog

* images, site preview

* fix link

* remove duplication

* corrections
2023-04-22 17:03:41 +01:00
Evgeny Poberezkin
28ad8b8cd5 blog: v5 announcement (#2225)
* blog: vision, funding, v5 announcement

* keep file name

* update

* update blog

* images, site preview

* fix link

* remove duplication

* corrections
2023-04-22 17:01:52 +01:00
Stanislav Dmitrenko
37d4ef770c android: equal paddings between sections and bottom spacer (#2227)
* android: equal paddings between sections and bottom spacer

* one more

* aligning

* paddings

* paddings

* scream color

* switch

* background and scrim colors

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-04-22 16:38:32 +01:00
Stanislav Dmitrenko
ba24e40512 android: settings refactoring and new design (#2226)
* android: settings refactoring and new design

* spacers

* paddings

* paddings

* padding

* weight

* new chat button padding

* removed background color

* profiles

* cancel button function
2023-04-21 20:21:44 +01:00
Evgeny Poberezkin
8097593f5e website: anchor to join simplex 2023-04-21 13:42:44 +01:00
sh
23ccd69b5e docs: add xftp (#2223)
* docs: add xftp

* xftp: clarify statistics section

* xftp: expand configuring the app section
2023-04-21 11:33:55 +01:00
spaced4ndy
5e0d6d77b9 core: check max file size before sending (#2224) 2023-04-21 13:46:56 +04:00
spaced4ndy
a06393f520 core: file status command for XFTP files (#2222) 2023-04-21 13:36:44 +04:00
Evgeny Poberezkin
549ffcefc0 blog: v5 announcement placeholder 2023-04-20 19:57:06 +01:00
spaced4ndy
c8721e8000 5.0: Android 117, iOS 144 2023-04-20 20:26:12 +04:00
Evgeny Poberezkin
03882367da core: 5.0.0.2 2023-04-20 14:19:09 +01:00
spaced4ndy
4d700d113d core, ios: mark files to receive from NSE, receive marked files on chat start (#2218) 2023-04-20 16:52:55 +04:00
Stanislav Dmitrenko
17bdd2a1d2 android: different icons for attachments (#2219)
* android: different icons for attachments

* icon

* change color

* changes

* strings

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-04-20 13:27:27 +01:00
Evgeny Poberezkin
80a68012a2 website: translations (#2217)
* Added translation using Weblate (Polish)

* Added translation using Weblate (Polish)

* Translated using Weblate (Polish)

Currently translated at 33.1% (70 of 211 strings)

Translation: SimpleX Chat/SimpleX Chat website
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/pl/

---------

Co-authored-by: Display Name <ptrumtine@proton.me>
2023-04-20 11:56:53 +01:00
Evgeny Poberezkin
3742906f75 mobile: translations (#2216)
* Translated using Weblate (Spanish)

Currently translated at 100.0% (1125 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1043 of 1043 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/

* Translated using Weblate (Czech)

Currently translated at 99.8% (1123 of 1125 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% (1043 of 1043 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/cs/

* Translated using Weblate (French)

Currently translated at 100.0% (1123 of 1123 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% (1042 of 1042 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% (1123 of 1123 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% (1042 of 1042 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1125 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1125 of 1125 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 98.7% (1029 of 1042 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1125 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1042 of 1042 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hans/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1125 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1043 of 1043 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/

* Translated using Weblate (Czech)

Currently translated at 99.8% (1123 of 1125 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% (1043 of 1043 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/cs/

* Translated using Weblate (French)

Currently translated at 100.0% (1123 of 1123 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% (1042 of 1042 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% (1123 of 1123 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% (1042 of 1042 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1125 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1125 of 1125 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 98.7% (1029 of 1042 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1125 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1042 of 1042 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hans/

* Translated using Weblate (German)

Currently translated at 100.0% (1125 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/

* Translated using Weblate (Polish)

Currently translated at 100.0% (1125 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pl/

* Translated using Weblate (German)

Currently translated at 100.0% (1125 of 1125 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% (1042 of 1042 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% (1125 of 1125 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% (1125 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1125 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1125 of 1125 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/

* ios: import/export localizations

---------

Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
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: Float <float.hu+@gmail.com>
Co-authored-by: sith-on-mars <groguko36@pm.me>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
2023-04-20 11:55:04 +01:00
Stanislav Dmitrenko
ae90edcdb5 android: moved to BasicTextField in some places (#2215)
* adnroid: moved to BasicTextField in some places

* field height

* field height 2

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-04-20 11:29:50 +01:00
Evgeny Poberezkin
9e76aadb0f Merge branch 'stable' 2023-04-19 18:09:57 +01:00
Evgeny Poberezkin
043544d7ec readme: new users group 2023-04-19 18:09:05 +01:00
Evgeny Poberezkin
2bf7d1dddc android: share video as video (#2214) 2023-04-19 17:39:31 +01:00
Evgeny Poberezkin
e1741118ce android: fix profile string (#2213) 2023-04-19 15:52:33 +01:00
spaced4ndy
58fb3f7f2d android: fix onboarding fonts, paddings (#2212) 2023-04-19 18:48:00 +04:00
spaced4ndy
48e92a7e9b android: don't show button to hide user if authentication is not configured (#2211) 2023-04-19 18:16:26 +04:00
spaced4ndy
5bf16da09d core, mobile: prohibit to change chat item expiration when chat is stopped (#2210) 2023-04-19 15:21:28 +04:00
Evgeny Poberezkin
23ca3dd665 5.0.0-beta.2 2023-04-19 11:59:39 +01:00
Evgeny Poberezkin
2caff25fa2 android: revert to using gallery for images and videos (#2209)
* core: revert to using gallery for images and videos

* remove comment
2023-04-19 11:07:14 +01:00
Evgeny Poberezkin
37f835be8c mobile: remove XFTP toggle (#2208)
* mobile: remove XFTP toggle

* ios: remove unused string

* android: remove unused strings
2023-04-19 09:41:01 +01:00
1278 changed files with 188572 additions and 37776 deletions

68
.github/ISSUE_TEMPLATE/bug.yml vendored Normal file
View File

@@ -0,0 +1,68 @@
name: Bug
description: File a bug report/issue
title: "[Bug]: "
labels: ["bug", "triage"]
body:
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for the bug you encountered.
options:
- label: I have searched the existing issues
required: true
- type: dropdown
attributes:
label: Platform
description: Multiple selections are possible.
multiple: true
options:
- Linux
- Mac
- Windows
- Android
- iOS
validations:
required: true
- type: input
attributes:
label: OS version
description: Specify the OS version
placeholder: ex. Android 12, Ubuntu 20.04
validations:
required: true
- type: input
attributes:
label: App version
description: Specify the SimpleX version
placeholder: ex. 4.3.2
validations:
required: true
- type: textarea
attributes:
label: Current Behavior
description: A concise description of what you're experiencing.
placeholder: Bug happened!
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
description: A concise description of what you expected to happen.
placeholder: No bug should happen!
validations:
required: true
- type: textarea
attributes:
label: Steps To Reproduce
description: Steps to reproduce the behavior.
placeholder: |
1. Go to ...
3. Click on ...
4. See error...
validations:
required: true
- type: textarea
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
render: shell

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1 @@
blank_issues_enabled: true

40
.github/ISSUE_TEMPLATE/feature.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
name: Feature
description: Suggest your feature
title: "[Feature]: "
labels: ["enhancement", "triage"]
body:
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for the bug you encountered.
options:
- label: I have searched the existing issues
required: true
- type: dropdown
attributes:
label: Platform
description: Multiple selections are possible. If selected input is "all", this considered to be a general feature.
multiple: true
options:
- Linux
- Mac
- Windows
- Android
- iOS
- all
validations:
required: true
- type: input
attributes:
label: App version
description: Specify the SimpleX version
placeholder: ex. 4.3.2
validations:
required: false
- type: textarea
attributes:
label: Feature
description: Describe the feature you would like to see added
placeholder: SimpleX Chat should make me coffee!
validations:
required: true

16
.github/ISSUE_TEMPLATE/question.yml vendored Normal file
View File

@@ -0,0 +1,16 @@
name: Question
description: Ask your question
title: "[Q]: "
labels: ["question", "triage"]
body:
- type: markdown
attributes:
value: |
Generally, we encourage you to ask questions in our [official group](https://simplex.chat/invitation/#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3Dsimplex:/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D), but you can do it anyway :)
- type: textarea
attributes:
label: Question
description: Please ask your question in plain english.
placeholder: Is SimpleX - chat?
validations:
required: true

View File

@@ -8,6 +8,7 @@ on:
- users
tags:
- "v*"
- "!*-fdroid"
pull_request:
jobs:
@@ -16,11 +17,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Clone project
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Build changelog
id: build_changelog
uses: mikepenz/release-changelog-builder-action@v1
uses: mikepenz/release-changelog-builder-action@v4
with:
configuration: .github/changelog_conf.json
failOnError: true
@@ -52,27 +53,39 @@ jobs:
- os: ubuntu-20.04
cache_path: ~/.cabal/store
asset_name: simplex-chat-ubuntu-20_04-x86-64
desktop_asset_name: simplex-desktop-ubuntu-20_04-x86_64.deb
- os: ubuntu-22.04
cache_path: ~/.cabal/store
asset_name: simplex-chat-ubuntu-22_04-x86-64
desktop_asset_name: simplex-desktop-ubuntu-22_04-x86_64.deb
- os: macos-latest
cache_path: ~/.cabal/store
asset_name: simplex-chat-macos-x86-64
desktop_asset_name: simplex-desktop-macos-x86_64.dmg
- os: windows-latest
cache_path: C:/cabal
asset_name: simplex-chat-windows-x86-64
desktop_asset_name: simplex-desktop-windows-x86_64.msi
steps:
- name: Configure pagefile (Windows)
if: matrix.os == 'windows-latest'
uses: al-cheb/configure-pagefile-action@v1.3
with:
minimum-size: 16GB
maximum-size: 16GB
disk-root: "C:"
- name: Clone project
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Setup Haskell
uses: haskell/actions/setup@v1
uses: haskell-actions/setup@v2
with:
ghc-version: "8.10.7"
cabal-version: "latest"
ghc-version: "9.6.3"
cabal-version: "3.10.1.0"
- name: Cache dependencies
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: |
${{ matrix.cache_path }}
@@ -91,6 +104,10 @@ jobs:
echo " extra-lib-dirs: /usr/local/opt/openssl@1.1/lib" >> cabal.project.local
echo " flags: +openssl" >> cabal.project.local
- name: Install AppImage dependencies
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'ubuntu-20.04'
run: sudo apt install -y desktop-file-utils
- name: Install pkg-config for Mac
if: matrix.os == 'macos-latest'
run: brew install pkg-config
@@ -103,13 +120,135 @@ jobs:
echo "package direct-sqlcipher" >> cabal.project.local
echo " flags: +openssl" >> cabal.project.local
- name: Unix build
id: unix_build
- name: Unix build CLI
id: unix_cli_build
if: matrix.os != 'windows-latest'
shell: bash
run: |
cabal build --enable-tests
echo "::set-output name=bin_path::$(cabal list-bin simplex-chat)"
path=$(cabal list-bin simplex-chat)
echo "bin_path=$path" >> $GITHUB_OUTPUT
echo "bin_hash=$(echo SHA2-512\(${{ matrix.asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT
- name: Unix upload CLI binary to release
if: startsWith(github.ref, 'refs/tags/v') && matrix.os != 'windows-latest'
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ${{ steps.unix_cli_build.outputs.bin_path }}
asset_name: ${{ matrix.asset_name }}
tag: ${{ github.ref }}
- name: Unix update CLI binary hash
if: startsWith(github.ref, 'refs/tags/v') && matrix.os != 'windows-latest'
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
append_body: true
body: |
${{ steps.unix_cli_build.outputs.bin_hash }}
- name: Setup Java
if: startsWith(github.ref, 'refs/tags/v')
uses: actions/setup-java@v3
with:
distribution: 'corretto'
java-version: '17'
cache: 'gradle'
- name: Linux build desktop
id: linux_desktop_build
if: startsWith(github.ref, 'refs/tags/v') && (matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04')
shell: bash
run: |
scripts/desktop/build-lib-linux.sh
cd apps/multiplatform
./gradlew packageDeb
path=$(echo $PWD/release/main/deb/simplex_*_amd64.deb)
echo "package_path=$path" >> $GITHUB_OUTPUT
echo "package_hash=$(echo SHA2-512\(${{ matrix.desktop_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT
- name: Linux make AppImage
id: linux_appimage_build
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'ubuntu-20.04'
shell: bash
run: |
scripts/desktop/make-appimage-linux.sh
path=$(echo $PWD/apps/multiplatform/release/main/*imple*.AppImage)
echo "appimage_path=$path" >> $GITHUB_OUTPUT
echo "appimage_hash=$(echo SHA2-512\(simplex-desktop-x86_64.AppImage\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT
- name: Mac build desktop
id: mac_desktop_build
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'macos-latest'
shell: bash
env:
APPLE_SIMPLEX_SIGNING_KEYCHAIN: ${{ secrets.APPLE_SIMPLEX_SIGNING_KEYCHAIN }}
APPLE_SIMPLEX_NOTARIZATION_APPLE_ID: ${{ secrets.APPLE_SIMPLEX_NOTARIZATION_APPLE_ID }}
APPLE_SIMPLEX_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_SIMPLEX_NOTARIZATION_PASSWORD }}
run: |
scripts/ci/build-desktop-mac.sh
path=$(echo $PWD/apps/multiplatform/release/main/dmg/SimpleX-*.dmg)
echo "package_path=$path" >> $GITHUB_OUTPUT
echo "package_hash=$(echo SHA2-512\(${{ matrix.desktop_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT
- name: Linux upload desktop package to release
if: startsWith(github.ref, 'refs/tags/v') && (matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04')
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ${{ steps.linux_desktop_build.outputs.package_path }}
asset_name: ${{ matrix.desktop_asset_name }}
tag: ${{ github.ref }}
- name: Linux update desktop package hash
if: startsWith(github.ref, 'refs/tags/v') && (matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04')
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
append_body: true
body: |
${{ steps.linux_desktop_build.outputs.package_hash }}
- name: Linux upload AppImage to release
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'ubuntu-20.04'
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ${{ steps.linux_appimage_build.outputs.appimage_path }}
asset_name: simplex-desktop-x86_64.AppImage
tag: ${{ github.ref }}
- name: Linux update AppImage hash
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'ubuntu-20.04'
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
append_body: true
body: |
${{ steps.linux_appimage_build.outputs.appimage_hash }}
- name: Mac upload desktop package to release
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'macos-latest'
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ${{ steps.mac_desktop_build.outputs.package_path }}
asset_name: ${{ matrix.desktop_asset_name }}
tag: ${{ github.ref }}
- name: Mac update desktop package hash
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'macos-latest'
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
append_body: true
body: |
${{ steps.mac_desktop_build.outputs.package_hash }}
- name: Unix test
if: matrix.os != 'windows-latest'
@@ -117,33 +256,25 @@ jobs:
shell: bash
run: cabal test --test-show-details=direct
- name: Unix upload binary to release
if: startsWith(github.ref, 'refs/tags/v') && matrix.os != 'windows-latest'
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ${{ steps.unix_build.outputs.bin_path }}
asset_name: ${{ matrix.asset_name }}
tag: ${{ github.ref }}
# Unix /
# / Windows
# * In powershell multiline commands do not fail if individual commands fail - https://github.community/t/multiline-commands-on-windows-do-not-fail-if-individual-commands-fail/16753
# * And GitHub Actions does not support parameterizing shell in a matrix job - https://github.community/t/using-matrix-to-specify-shell-is-it-possible/17065
# rm -rf dist-newstyle/src/direct-sq* is here because of the bug in cabal's dependency which prevents second build from finishing
- name: Windows build
id: windows_build
if: matrix.os == 'windows-latest'
shell: cmd
shell: bash
run: |
rm -rf dist-newstyle/src/direct-sq*
sed -i "s/, unix /--, unix /" simplex-chat.cabal
cabal build --enable-tests
cabal list-bin simplex-chat > tmp_bin_path
set /p bin_path= < tmp_bin_path
echo ::set-output name=bin_path::%bin_path%
rm -rf dist-newstyle/src/direct-sq*
path=$(cabal list-bin simplex-chat | tail -n 1)
echo "bin_path=$path" >> $GITHUB_OUTPUT
echo "bin_hash=$(echo SHA2-512\(${{ matrix.asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT
- name: Windows upload binary to release
- name: Windows upload CLI binary to release
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest'
uses: svenstaro/upload-release-action@v2
with:
@@ -152,4 +283,60 @@ jobs:
asset_name: ${{ matrix.asset_name }}
tag: ${{ github.ref }}
- name: Windows update CLI binary hash
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest'
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
append_body: true
body: |
${{ steps.windows_build.outputs.bin_hash }}
- name: 'Setup MSYS2'
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest'
uses: msys2/setup-msys2@v2
with:
msystem: ucrt64
update: true
install: >-
git
perl
make
pacboy: >-
toolchain:p
cmake:p
- name: Windows build desktop
id: windows_desktop_build
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest'
shell: msys2 {0}
run: |
export PATH=$PATH:/c/ghcup/bin
scripts/desktop/build-lib-windows.sh
cd apps/multiplatform
./gradlew packageMsi
path=$(echo $PWD/release/main/msi/*imple*.msi | sed 's#/\([a-z]\)#\1:#' | sed 's#/#\\#g')
echo "package_path=$path" >> $GITHUB_OUTPUT
echo "package_hash=$(echo SHA2-512\(${{ matrix.desktop_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT
- name: Windows upload desktop package to release
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest'
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ${{ steps.windows_desktop_build.outputs.package_path }}
asset_name: ${{ matrix.desktop_asset_name }}
tag: ${{ github.ref }}
- name: Windows update desktop package hash
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest'
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
append_body: true
body: |
${{ steps.windows_desktop_build.outputs.package_hash }}
# Windows /

View File

@@ -9,6 +9,7 @@ on:
- website/**
- images/**
- blog/**
- docs/**
- .github/workflows/web.yml
jobs:
@@ -17,7 +18,7 @@ jobs:
strategy:
matrix:
node-version: [12.x]
node-version: [16.x]
steps:
- uses: actions/checkout@v2

3
.gitignore vendored
View File

@@ -49,10 +49,11 @@ logs/
# for website
website/node_modules/
website/src/blog/
website/src/docs/
website/translations.json
website/src/_data/supported_languages.json
website/src/img/images/
website/src/images/
website/src/js/lottie.min.js
# Generated files
website/package/generated*

View File

@@ -8,12 +8,12 @@ RUN a=$(arch); curl https://downloads.haskell.org/~ghcup/$a-linux-ghcup -o /usr/
chmod +x /usr/bin/ghcup
# Install ghc
RUN ghcup install ghc 8.10.7
RUN ghcup install ghc 9.6.3
# Install cabal
RUN ghcup install cabal
RUN ghcup install cabal 3.10.1.0
# Set both as default
RUN ghcup set ghc 8.10.7 && \
ghcup set cabal
RUN ghcup set ghc 9.6.3 && \
ghcup set cabal 3.10.1.0
COPY . /project
WORKDIR /project

View File

@@ -6,27 +6,63 @@ If you believe that some of the clauses in this document are not aligned with ou
## Privacy Policy
SimpleX Chat Ltd. ("SimpleX Chat") uses the best industry practices for security and end-to-end encryption to provide secure end-to-end encrypted messaging via private connections. SimpleX Chat is built on top of SimpleX messaging and application platform that uses a new message routing protocol that allows establishing private connection without having any kind of addresses that identify its users - we don't use emails, phone numbers, usernames, identity keys or any other user identifiers to pass messages between the users.
SimpleX Chat Ltd. ("SimpleX Chat") uses the best industry practices for security and encryption to provide secure [end-to-end encrypted](./docs/GLOSSARY.md#end-to-end-encryption) messaging via private connections. This encryption cannot be compromised by the servers via [man-in-the-middle attack](./docs/GLOSSARY.md#man-in-the-middle-attack).
SimpleX Chat security audit was performed in October 2022 by [Trail of Bits](https://www.trailofbits.com/about), and most fixes were released in v4.2.0 see [the announcement](./blog/20221108-simplex-chat-v4.2-security-audit-new-website.md).
SimpleX Chat is built on top of SimpleX messaging and application platform that uses a new message routing protocol allowing to establish private connections without having any kind of addresses that identify its users - we don't use emails, phone numbers, usernames, identity keys or any other user identifiers to pass messages between the users.
SimpleX Chat security assessment was done in October 2022 by [Trail of Bits](https://www.trailofbits.com/about), and most fixes were released in v4.2.0 see [the announcement](./blog/20221108-simplex-chat-v4.2-security-audit-new-website.md).
### Information you provide
We do not store user profiles. The profile you create in the app is local to your device. When you create a user profile, no records are created on our servers, and we have no access to any part of your profile information, and even to the fact that you created a profile - it is a local record stored only on your device. That means that if you delete the app, and have no backup, you will permanently lose all the data and the private connections you create with other users.
#### User profiles
Messages. SimpleX Chat cannot decrypt or otherwise access the content or even size of your messages (each message is padded to a fixed size of 16kb). SimpleX Chat temporarily stores end-to-end encrypted messages on its servers for delivery to the devices that are offline, these messages are permanently removed as soon as they are delivered. Your message history is stored only on your own devices.
We do not store user profiles. The profile you create in the app is local to your device.
Connections with other users. When you create a connection with another user, two messaging queues (you can think about them as about mailboxes) are created on our servers, or on the servers that you configured in the app, in case it allows such configuration (SimpleX uses separate queues for direct and response messages, that the client applications prefer to create on two different servers, in case you have more than one server configured in the app, which is the default). At the time of updating this document all our client applications allow configuring the servers. Our servers do not store information about which queues are linked to your profile on the device, and they do not collect any information that would allow us to establish that these queues are related to your device or your profile - the access to each queue is authorized by a set of anonymous unique cryptographic keys, different for each queue, and separate for sender and recipient of the messages. The exception to that is when you choose to use instant push notifications in our iOS app, because the design of push notifications requires storing the device token on notification server, and the server can observe how many messaging queues your device uses, and approximate how many messages are sent to each queue. It does not allow though to determine the actual addresses of these queues, as a separate address is used to subscibe to the notifications (unless notification and messaging servers exchange information), and who, or even how many contacts, send messages to you, as notifications are delivered to your device end-to-end encrypted by the messaging servers. It also does not allow to see message content or sizes, as the actual messages are not sent via the notification service, only the fact that the message is available and where it can be received from (the latter information is encrypted, so that the notification server cannot see it). You can read more about the design of iOS push notifications [here](https://simplex.chat/blog/20220404-simplex-chat-instant-notifications.html#our-ios-approach-has-one-trade-off).
When you create a user profile, no records are created on our servers, and we have no access to any part of your profile information, and even to the fact that you created a profile - it is a local record stored only on your device. That means that if you delete the app, and have no backup, you will permanently lose all the data and the private connections you create with other users.
#### Messages and Files
SimpleX Chat cannot decrypt or otherwise access the content or even the size of your messages and files you send or receive. Each message is padded to a fixed size of 16kb. Each file is sent in chunks of 256kb, 1mb or 8mb via all or some of the configured file servers. Both messages and files are sent end-to-end encrypted, and the servers do not have technical means to compromise this encryption, because part of the [key exchange](./docs/GLOSSARY.md#key-exchange) happens out-of-band.
Your message history is stored only on your own device and the devices of your contacts. While the recipients' devices are offline SimpleX Chat temporarily stores end-to-end encrypted messages on the messaging (SMP) servers that are preset in the app or chosen by the users.
The messages are permanently removed from the preset servers as soon as they are delivered. Undelivered messages are deleted after the time that is configured in the messaging servers you use (21 days for preset messaging servers).
The files are stored on file (XFTP) servers for the time configured in the file servers you use (48 hours for preset file servers).
If a messaging or file servers are restarted, the encrypted message or the record of the file can be stored in a backup file until it is overwritten by the next restart (usually within 1 week).
#### Connections with other users
When you create a connection with another user, two messaging queues (you can think about them as about mailboxes) are created on chosen messaging servers, that can be the preset servers or the servers that you configured in the app, in case it allows such configuration. SimpleX uses separate queues for direct and response messages, that the client applications prefer to create on two different servers, in case you have more than one server configured in the app, which is the default.
At the time of updating this document all our client applications allow configuring the servers. Our servers do not store information about which queues are linked to your profile on the device, and they do not collect any information that would allow us to establish that these queues are related to your device or your profile - the access to each queue is authorized by two anonymous unique cryptographic keys, different for each queue, and separate for sender and recipient of the messages.
#### iOS Push Notifications
When you choose to use instant push notifications in SimpleX iOS app, because the design of push notifications requires storing the device token on notification server, the notifications server can observe how many messaging queues your device has notifications enabled for, and approximately how many messages are sent to each queue.
Notification server cannot observe the actual addresses of these queues, as a separate address is used to subscribe to the notifications. It also cannot observe who, or even how many contacts, send messages to you, as notifications are delivered to your device end-to-end encrypted by the messaging servers.
It also does not allow to see message content or sizes, as the actual messages are not sent via the notification server, only the fact that the message is available and where it can be received from (the latter information is encrypted, so that the notification server cannot observe it). You can read more about the design of iOS push notifications [here](https://simplex.chat/blog/20220404-simplex-chat-instant-notifications.html#our-ios-approach-has-one-trade-off).
#### Another information stored on the servers
Additional technical information can be stored on our servers, including randomly generated authentication tokens, keys, push tokens, and other material that is necessary to transmit messages. SimpleX Chat limits this additional technical information to the minimum required to operate the Services.
User Support. If you contact SimpleX Chat any personal data you may share with us is kept only for the purposes of researching the issue and contacting you about your case. We recommend contacting support [via chat](https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23%2F%3Fv%3D1%26dh%3DMCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion), when it is possible.
#### SimpleX Directory Service
[SimpleX directory service](./docs/DIRECTORY.md) stores: your search requests, the messages and the members profiles in the group. You can connect to SimpleX Directory Service via [this address](https://simplex.chat/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion).
#### User Support.
If you contact SimpleX Chat any personal data you may share with us is kept only for the purposes of researching the issue and contacting you about your case. We recommend contacting support [via chat](https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23%2F%3Fv%3D1%26dh%3DMCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion), when it is possible.
### Information we may share
We operate our Services using third parties. While we do not share any user data, these third party may access the encrypted user data as it is stored or transmitted via our servers.
We use Third party to provide email services - if you ask for support via email, your and SimpleX Chat email providers may access these emails according their privacy policies and terms of service.
We use a third party for email services - if you ask for support via email, your and SimpleX Chat email providers may access these emails according to their privacy policies and terms of service.
The cases when SimpleX Chat may need to share the data we temporarily store on the servers:
@@ -39,19 +75,19 @@ At the time of updating this document, we have never provided or have been reque
### Updates
We will update this privacy policy as needed so that it is current, accurate, and as clear as possible. Your continued use of our Services confirms your acceptance of our updated Privacy Policy.
We will update this Privacy Policy as needed so that it is current, accurate, and as clear as possible. Your continued use of our Services confirms your acceptance of our updated Privacy Policy.
Please also read our Terms of Service.
Please also read our Terms of Service below.
If you have questions about our Privacy Policy please contact us at chat@simplex.chat.
If you have questions about our Privacy Policy please contact us via [email](chat@simplex.chat) or [chat](https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23%2F%3Fv%3D1%26dh%3DMCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion).
## Terms of Service
You accept to our Terms of Service ("Terms") by installing or using any of our apps or services ("Services").
You accept our Terms of Service ("Terms") by installing or using any of our apps or services ("Services").
**Minimal age**. You must be at least 13 years old to use our Services. The minimum age to use our Services without parental approval may be higher in your country.
**Accessing the servers**. For the efficiency of the network access, the apps access all queues you create on any server via the same network (TCP/IP) connection. Our servers do not collect information about which queues were accessed via the same connection, so we do cannot establish which queues belong to the same users. Whoever might observe your network traffic would know which servers you use, and how much data you send, but not to whom it is sent - the data that leaves the servers is always different from the data they receive - there are no identifiers or cyphertext in common. Please refer to our [technical design document](https://github.com/simplex-chat/simplexmq/blob/master/protocol/overview-tjr.md) for more information about our privacy model and known security and privacy risks.
**Accessing the servers**. For the efficiency of the network access, the apps access all queues you create on any server via the same network (TCP/IP) connection. Our servers do not collect information about which queues were accessed via the same connection, so we cannot establish which queues belong to the same users. Whoever might observe your network traffic would know which servers you use, and how much data you send, but not to whom it is sent - the data that leaves the servers is always different from the data they receive - there are no identifiers or ciphertext in common. Please refer to our [technical design document](https://github.com/simplex-chat/simplexmq/blob/master/protocol/overview-tjr.md) for more information about our privacy model and known security and privacy risks.
**Privacy of user data**. We do not retain any data we transmit for any longer than necessary to provide the Services. We only collect aggregate statistics across all users, not per user - we do not have information about how many people use SimpleX Chat (we only know an approximate number of app installations and the aggregate traffic through our servers). In any case, we do not and will not sell or in any way monetize user data.
@@ -67,15 +103,17 @@ You accept to our Terms of Service ("Terms") by installing or using any of our a
**Keeping your data secure**. SimpleX Chat is the first messaging platform that is 100% private by design - we neither have ability to access your messages, nor we have information about who you communicate with. That means that you are solely responsible for keeping your device and your user profile safe and secure. If you lose your phone or remove the app, you will not be able to recover the lost data, unless you made a back up.
**Storing the messages on the device**. Currently the messages are stored in the database on your device without encryption. It means that if you make a backup of the app and store it unecrypted, the backup provider may be able to access the messages.
**Storing the messages on the device**. The messages are stored in the encrypted database on your device. Whether and how database passphrase is stored is determined by the configuration of the application you use. Legacy databases created prior to 2023 or in CLI (terminal) app may remain unencrypted, and it will be indicated in the app. In this case, if you make a backup of the app data and store it unencrypted, the backup provider may be able to access the messages. Please note, that the beta version of desktop app currently stores the database passphrase in the configuration file in plaintext, so you may need to remove passphrase from the device via the app configuration.
**Storing the files on the device**. The files are stored on your device unencrypted. If you make a backup of the app data and store it unencrypted, the backup provider will be able to access the files.
**No Access to Emergency Services**. Our Services do not provide access to emergency service providers like the police, fire department, hospitals, or other public safety organizations. Make sure you can contact emergency service providers through a mobile, fixed-line telephone, or other service.
**Third-party services**. Our Services may allow you to access, use, or interact with third-party websites, apps, content, and other products and services. When you use third-party services, their terms and privacy policies govern your use of those services.
**Your Rights**. You own the mesasges and information you transmit through our Services. Your recipients are able to retain the messages you receive from you; there is no technical ability to delete data from their devices.
**Your Rights**. You own the messages and the information you transmit through our Services. Your recipients are able to retain the messages you receive from you; there is no technical ability to delete data from their devices. While there are various app features that allow deleting messages from the recipients' devices, such as _disappearing messages_ and _full message deletion_, their functioning on your recipients' devices cannot be guaranteed or enforced, as the device may be offline or have a modified version of the app.
**License**. SimpleX Chat grants you a limited, revocable, non-exclusive, and non-transferable license to use our Services in accordance with these Terms. The source-code of services is available and can be used under [AGPL v3 licence](https://github.com/simplex-chat/simplex-chat/blob/stable/LICENSE)
**License**. SimpleX Chat grants you a limited, revocable, non-exclusive, and non-transferable license to use our Services in accordance with these Terms. The source-code of services is available and can be used under [AGPL v3 license](https://github.com/simplex-chat/simplex-chat/blob/stable/LICENSE)
**SimpleX Chat Rights**. We own all copyrights, trademarks, domains, logos, trade secrets, and other intellectual property rights associated with our Services. You may not use our copyrights, trademarks, domains, logos, and other intellectual property rights unless you have our written permission, and unless under an open-source license distributed together with the source code. To report copyright, trademark, or other intellectual property infringement, please contact chat@simplex.chat.
@@ -93,4 +131,4 @@ You accept to our Terms of Service ("Terms") by installing or using any of our a
**Ending these Terms**. You may end these Terms with SimpleX Chat at any time by deleting SimpleX Chat app(s) from your device and discontinuing use of our Services. The provisions related to Licenses, Disclaimers, Limitation of Liability, Resolving dispute, Availability, Changes to the terms, Enforcing the terms, and Ending these Terms will survive termination of your relationship with SimpleX Chat.
Updated November 8, 2022
Updated August 17, 2023

156
README.md
View File

@@ -2,7 +2,7 @@
[![GitHub downloads](https://img.shields.io/github/downloads/simplex-chat/simplex-chat/total)](https://github.com/simplex-chat/simplex-chat/releases)
[![GitHub release](https://img.shields.io/github/v/release/simplex-chat/simplex-chat)](https://github.com/simplex-chat/simplex-chat/releases)
[![Join on Reddit](https://img.shields.io/reddit/subreddit-subscribers/SimpleXChat?style=social)](https://www.reddit.com/r/SimpleXChat)
[![Follow on Mastodon](https://img.shields.io/mastodon/follow/108619463746856738?domain=https%3A%2F%2Fmastodon.social&style=social)](https://mastodon.social/@simplex)
<a rel="me" href="https://mastodon.social/@simplex">![Follow on Mastodon](https://img.shields.io/mastodon/follow/108619463746856738?domain=https%3A%2F%2Fmastodon.social&style=social)</a>
| 30/03/2023 | EN, [FR](/docs/lang/fr/README.md), [CZ](/docs/lang/cs/README.md) |
@@ -15,7 +15,7 @@
## Welcome to SimpleX Chat!
1. 📲 [Install the app](#install-the-app).
2. ↔️ [Connect to the team](#connect-to-the-team-via-the-app) and [join user groups](#join-user-groups).
2. ↔️ [Connect to the team](#connect-to-the-team), [join user groups](#join-user-groups) and [follow our updates](#follow-our-updates).
3. 🤝 [Make a private connection](#make-a-private-connection) with a friend.
4. 🔤 [Help translating SimpleX Chat](#help-translating-simplex-chat).
5. ⚡️ [Contribute](#contribute) and [help us with donations](#help-us-with-donations).
@@ -40,22 +40,62 @@
- 🚀 [TestFlight preview for iOS](https://testflight.apple.com/join/DWuT2LQu) with the new features 1-2 weeks earlier - **limited to 10,000 users**!
- 🖥 Available as a terminal (console) [app / CLI](#zap-quick-installation-of-a-terminal-app) on Linux, MacOS, Windows.
## Connect to the team via the app
## Connect to the team
You can connect to the team via the app using "chat with the developers button" available when you have no conversations in the profile, "Send questions and ideas" in the app settings or via our [SimpleX address](https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23%2F%3Fv%3D1%26dh%3DMCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion). Please connect to:
- to ask any questions
- to suggest any improvements
- to share anything relevant
We are replying the questions manually, so it is not instant it can take up to 24 hours.
If you are interested in helping us to integrate open-source language models, and in [joining our team](./docs/JOIN_TEAM.md), please get in touch.
## Join user groups
You can join an English-speaking users 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 join the groups created by other users via the new [directory service](https://simplex.chat/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion). We are not responsible for the content shared in these groups.
**Please note**: The groups below are created for the users to be able to ask questions, make suggestions and ask questions about SimpleX Chat only.
You also can:
- criticize the app, and make comparisons with other messengers.
- share new messengers you think could be interesting for privacy, as long as you don't spam.
- share some privacy related publications, infrequently.
- having preliminary approved with the admin in direct message, share the link to a group you created, but only once. Once the group has more than 10 members it can be submitted to [SimpleX Directory Service](./docs/DIRECTORY.md) where the new users will be able to discover it.
You must:
- be polite to other users
- avoid spam (too frequent messages, even if they are relevant)
- avoid any personal attacks or hostility.
- avoid sharing any content that is not relevant to the above (that includes, but is not limited to, discussing politics or any aspects of society other than privacy, security, technology and communications, sharing any content that may be found offensive by other users, etc.).
Messages not following these rules will be deleted, the right to send messages may be revoked, and the access to the new members to the group may be temporarily restricted, to prevent re-joining under a different name - our imperfect group moderation does not have a better solution at the moment.
You can join an English-speaking users group if you want to ask any questions: [#SimpleX-Group-4](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2Fw2GlucRXtRVgYnbt_9ZP-kmt76DekxxS%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEA0tJhTyMGUxznwmjb7aT24P1I1Wry_iURTuhOFlMb1Eo%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22WoPxjFqGEDlVazECOSi2dg%3D%3D%22%7D)
There is also a group [#simplex-devs](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2F6eHqy7uAbZPOcA6qBtrQgQquVlt4Ll91%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAqV_pg3FF00L98aCXp4D3bOs4Sxv_UmSd-gb0juVoQVs%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22XonlixcHBIb2ijCehbZoiw%3D%3D%22%7D) for developers who build on SimpleX platform:
- chat bots and automations
- integrations with other apps
- social apps and services
- etc.
There are groups in other languages, that we have the apps interface translated into. These groups are for testing, and asking questions to other SimpleX Chat users:
[\#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).
[\#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-ES](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FaJ8O1O8A8GbeoaHTo_V8dcefaCl7ouPb%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEA034qWTA3sWcTsi6aWhNf9BA34vKVCFaEBdP2R66z6Ao%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22wiZ1v_wNjLPlT-nCSB-bRA%3D%3D%22%7D) (Spanish-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 either by opening these links in the app or by opening them in a desktop browser and scanning the QR code.
## Follow our updates
We publish our updates and releases via:
- [Reddit](https://www.reddit.com/r/SimpleXChat/), [Twitter](https://twitter.com/SimpleXChat), [Lemmy](https://lemmy.ml/c/simplex), [Mastodon](https://mastodon.social/@simplex) and [Nostr](https://snort.social/p/npub1exv22uulqnmlluszc4yk92jhs2e5ajcs6mu3t00a6avzjcalj9csm7d828).
- SimpleX Chat [team profile](#connect-to-the-team).
- [blog](https://simplex.chat/blog/) and [RSS feed](https://simplex.chat/feed.rss).
- [mailing list](https://simplex.chat/#join-simplex), very rarely.
## Make a private connection
You need to share a link with your friend or scan a QR code from their phone, in person or during a video call, to make a connection and start messaging.
@@ -79,15 +119,22 @@ Join our translators to help SimpleX grow!
|locale|language |contributor|[Android](https://play.google.com/store/apps/details?id=chat.simplex.app) and [iOS](https://apps.apple.com/us/app/simplex-chat/id1605771084)|[website](https://simplex.chat)|Github docs|
|:----:|:-------:|:---------:|:---------:|:---------:|:---------:|
|🇬🇧 en|English | |✓|✓|✓|✓|
|ar|العربية |[jermanuts](https://github.com/jermanuts)||[![website](https://hosted.weblate.org/widgets/simplex-chat/ar/website/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/website/ar/)||
|ar|العربية |[jermanuts](https://github.com/jermanuts)|[![android app](https://hosted.weblate.org/widgets/simplex-chat/ar/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/ar/)<br>-|[![website](https://hosted.weblate.org/widgets/simplex-chat/ar/website/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/website/ar/)||
|🇧🇬 bg|Български | |[![android app](https://hosted.weblate.org/widgets/simplex-chat/bg/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/bg/)<br>[![ios app](https://hosted.weblate.org/widget/simplex-chat/ios/bg/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/bg/)|||
|🇨🇿 cs|Čeština |[zen0bit](https://github.com/zen0bit)|[![android app](https://hosted.weblate.org/widgets/simplex-chat/cs/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/cs/)<br>[![ios app](https://hosted.weblate.org/widgets/simplex-chat/cs/ios/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/cs/)|[![website](https://hosted.weblate.org/widgets/simplex-chat/cs/website/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/website/cs/)|[](https://github.com/simplex-chat/simplex-chat/tree/master/docs/lang/cs)|
|🇩🇪 de|Deutsch |[mlanp](https://github.com/mlanp)|[![android app](https://hosted.weblate.org/widgets/simplex-chat/de/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/de/)<br>[![ios app](https://hosted.weblate.org/widgets/simplex-chat/de/ios/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/de/)|[![website](https://hosted.weblate.org/widgets/simplex-chat/de/website/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/website/de/)||
|🇪🇸 es|Español |[Mateyhv](https://github.com/Mateyhv)|[![android app](https://hosted.weblate.org/widgets/simplex-chat/es/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/es/)<br>[![ios app](https://hosted.weblate.org/widgets/simplex-chat/es/ios/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/es/)|[![website](https://hosted.weblate.org/widgets/simplex-chat/es/website/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/website/es/)||
|🇫🇮 fi|Suomi | |[![android app](https://hosted.weblate.org/widgets/simplex-chat/fi/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/fi/)<br>[![ios app](https://hosted.weblate.org/widgets/simplex-chat/fi/ios/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/fi/)|[![website](https://hosted.weblate.org/widgets/simplex-chat/fi/website/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/website/fi/)||
|🇫🇷 fr|Français |[ishi_sama](https://github.com/ishi-sama)|[![android app](https://hosted.weblate.org/widgets/simplex-chat/fr/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/fr/)<br>[![ios app](https://hosted.weblate.org/widgets/simplex-chat/fr/ios/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/fr/)|[![website](https://hosted.weblate.org/widgets/simplex-chat/fr/website/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/website/fr/)|[](https://github.com/simplex-chat/simplex-chat/tree/master/docs/lang/fr)|
|🇮🇱 he|עִברִית | |[![android app](https://hosted.weblate.org/widgets/simplex-chat/he/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/he/)<br>-|||
|🇮🇹 it|Italiano |[unbranched](https://github.com/unbranched)|[![android app](https://hosted.weblate.org/widgets/simplex-chat/it/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/it/)<br>[![ios app](https://hosted.weblate.org/widgets/simplex-chat/it/ios/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/it/)|[![website](https://hosted.weblate.org/widgets/simplex-chat/it/website/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/website/it/)||
|🇯🇵 ja|日本語 | |[![android app](https://hosted.weblate.org/widgets/simplex-chat/ja/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/ja/)<br>[![ios app](https://hosted.weblate.org/widgets/simplex-chat/ja/ios/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/ja/)|[![website](https://hosted.weblate.org/widgets/simplex-chat/ja/website/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/website/ja/)||
|🇳🇱 nl|Nederlands|[mika-nl](https://github.com/mika-nl)|[![android app](https://hosted.weblate.org/widgets/simplex-chat/nl/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/nl/)<br>[![ios app](https://hosted.weblate.org/widgets/simplex-chat/nl/ios/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/nl/)|[![website](https://hosted.weblate.org/widgets/simplex-chat/nl/website/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/website/nl/)||
|🇵🇱 pl|Polski |[BxOxSxS](https://github.com/BxOxSxS)|[![android app](https://hosted.weblate.org/widgets/simplex-chat/pl/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/ru/)<br>[![ios app](https://hosted.weblate.org/widgets/simplex-chat/ru/ios/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/pl/)|||
|🇵🇱 pl|Polski |[BxOxSxS](https://github.com/BxOxSxS)|[![android app](https://hosted.weblate.org/widgets/simplex-chat/pl/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/pl/)<br>[![ios app](https://hosted.weblate.org/widgets/simplex-chat/pl/ios/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/pl/)|||
|🇧🇷 pt-BR|Português||[![android app](https://hosted.weblate.org/widgets/simplex-chat/pt_BR/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/)<br>-|[![website](https://hosted.weblate.org/widgets/simplex-chat/pt_BR/website/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/website/pt_BR/)||
|🇷🇺 ru|Русский ||[![android app](https://hosted.weblate.org/widgets/simplex-chat/ru/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/ru/)<br>[![ios app](https://hosted.weblate.org/widgets/simplex-chat/ru/ios/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/ru/)|||
|🇹🇭 th|ภาษาไทย |[titapa-punpun](https://github.com/titapa-punpun)|[![android app](https://hosted.weblate.org/widgets/simplex-chat/th/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/th/)<br>[![ios app](https://hosted.weblate.org/widgets/simplex-chat/th/ios/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/th/)|||
|🇺🇦 uk|Українська| |[![android app](https://hosted.weblate.org/widgets/simplex-chat/uk/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/uk/)<br>[![ios app](https://hosted.weblate.org/widgets/simplex-chat/uk/ios/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/uk/)|[![website](https://hosted.weblate.org/widgets/simplex-chat/uk/website/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/website/uk/)||
|🇨🇳 zh-CHS|简体中文|[sith-on-mars](https://github.com/sith-on-mars)<br><br>[Float-hu](https://github.com/Float-hu)|[![android app](https://hosted.weblate.org/widgets/simplex-chat/zh_Hans/android/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/)<br>[![ios app](https://hosted.weblate.org/widgets/simplex-chat/zh_Hans/ios/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hans/)<br>&nbsp;|<br><br>[![website](https://hosted.weblate.org/widgets/simplex-chat/zh_Hans/website/svg-badge.svg)](https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/)||
Languages in progress: Arabic, Japanese, Korean, Portuguese and [others](https://hosted.weblate.org/projects/simplex-chat/#languages). We will be adding more languages as some of the already added are completed please suggest new languages, review the [translation guide](./docs/TRANSLATIONS.md) and get in touch with us!
@@ -96,6 +143,7 @@ Languages in progress: Arabic, Japanese, Korean, Portuguese and [others](https:/
We would love to have you join the development! You can help us with:
- [share the color theme](./docs/THEMES.md) you use in Android app!
- writing a tutorial or recipes about hosting servers, chat bot automations, etc.
- contributing to SimpleX Chat knowledge-base.
- developing features - please connect to us via chat so we can help you get started.
@@ -114,11 +162,14 @@ It is possible to donate via:
- [GitHub](https://github.com/sponsors/simplex-chat) - it is commission-free for us.
- [OpenCollective](https://opencollective.com/simplex-chat) - it charges a commission, and also accepts donations in crypto-currencies.
- Monero address: 8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt
- Bitcoin address: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG
- BCH address: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG
- Ethereum address: 0x83fd788f7241a2be61780ea9dc72d2151e6843e2
- Solana address: 43tWFWDczgAcn4Rzwkpqg2mqwnQETSiTwznmCgA2tf1L
- Monero: 8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt
- Bitcoin: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG
- BCH: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG
- USDT:
- BNB Smart Chain: 0x83fd788f7241a2be61780ea9dc72d2151e6843e2
- Tron: TNnTrKLBmdy2Wn3cAQR98dAVvWhLskQGfW
- Ethereum: 0x83fd788f7241a2be61780ea9dc72d2151e6843e2
- Solana: 43tWFWDczgAcn4Rzwkpqg2mqwnQETSiTwznmCgA2tf1L
Thank you,
@@ -138,7 +189,7 @@ SimpleX Chat founder
- [News and updates](#news-and-updates)
- [Quick installation of a terminal app](#zap-quick-installation-of-a-terminal-app)
- [SimpleX Platform design](#simplex-platform-design)
- [Privacy: technical details and limitations](#privacy-technical-details-and-limitations)
- [Privacy and security: technical details and limitations](#privacy-and-security-technical-details-and-limitations)
- [For developers](#for-developers)
- [Roadmap](#roadmap)
- [Disclaimers, Security contact, License](#disclaimers)
@@ -179,18 +230,18 @@ You can use SimpleX with your own servers and still communicate with people usin
## News and updates
Recent updates:
Recent and important updates:
[Mar 28, 2023. v4.6 released - with Android 8+ and ARMv7a support, hidden profiles, community moderation, improved audio/video calls and reduced battery usage](./blog/20230328-simplex-chat-v4-6-hidden-profiles.md).
[Sep 25, 2023. SimpleX Chat v5.3 released: desktop app, local file encryption, improved groups and directory service](./blog/20230925-simplex-chat-v5-3-desktop-app-local-file-encryption-directory-service.md).
[Jul 22, 2023. SimpleX Chat: v5.2 released with message delivery receipts](./blog/20230722-simplex-chat-v5-2-message-delivery-receipts.md).
[May 23, 2023. SimpleX Chat: v5.1 released with message reactions and self-destruct passcode](./blog/20230523-simplex-chat-v5-1-message-reactions-self-destruct-passcode.md).
[Apr 22, 2023. SimpleX Chat: vision and funding, v5.0 released with videos and files up to 1gb](./blog/20230422-simplex-chat-vision-funding-v5-videos-files-passcode.md).
[Mar 1, 2023. SimpleX File Transfer Protocol send large files efficiently, privately and securely, soon to be integrated into SimpleX Chat apps.](./blog/20230301-simplex-file-transfer-protocol.md).
[Feb 4, 2023. v4.5 released - with multiple user profiles, message draft, transport isolation and Italian interface](./blog/20230204-simplex-chat-v4-5-user-chat-profiles.md).
[Jan 3, 2023. v4.4 released - with disappearing messages, "live" messages, connection security verifications, GIFs and stickers and with French interface language](./blog/20230103-simplex-chat-v4.4-disappearing-messages.md).
[Dec 6, 2022. November reviews and v4.3 released - with instant voice messages, irreversible deletion of sent messages and improved server configuration](./blog/20221206-simplex-chat-v4.3-voice-messages.md).
[Nov 8, 2022. Security audit by Trail of Bits, the new website and v4.2 released](./blog/20221108-simplex-chat-v4.2-security-audit-new-website.md).
[Sep 28, 2022. v4.0: encrypted local chat database and many other changes](./blog/20220928-simplex-chat-v4-encrypted-database.md).
@@ -223,29 +274,36 @@ See [SimpleX whitepaper](https://github.com/simplex-chat/simplexmq/blob/stable/p
See [SimpleX Chat Protocol](./docs/protocol/simplex-chat.md) for the format of messages sent between chat clients over [SimpleX Messaging Protocol](https://github.com/simplex-chat/simplexmq/blob/stable/protocol/simplex-messaging.md).
## Privacy: technical details and limitations
## Privacy and security: technical details and limitations
SimpleX Chat is a work in progress we are releasing improvements as they are ready. You have to decide if the current state is good enough for your usage scenario.
We compiled a [glossary of terms](./docs/GLOSSARY.md) used to describe communication systems to help understand some terms below and to help compare advantages and disadvantages of various communication systems.
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 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.
1. Instead of user profile identifiers used by all other platforms, even the most private ones, SimpleX uses [pairwise per-queue identifiers](./docs/GLOSSARY.md#pairwise-pseudonymous-identifier) (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](./docs/GLOSSARY.md#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](./docs/GLOSSARY.md#double-ratchet-algorithm) end-to-end encryption in each conversation between two users (or group members). This is the same algorithm that is used in Signal and many other messaging apps; it provides OTR messaging with [forward secrecy](./docs/GLOSSARY.md#forward-secrecy) (each message is encrypted by its own ephemeral key) and [break-in recovery](./docs/GLOSSARY.md#post-compromise-security) (the keys are frequently re-negotiated as part of the message exchange). Two pairs of Curve448 keys are used for the initial [key agreement](./docs/GLOSSARY.md#key-agreement-protocol), initiating party passes these keys via the connection link, accepting side - in the header of the confirmation message.
4. Additional layer of encryption using NaCL cryptobox for the messages delivered from the server to the recipient. This layer avoids having any ciphertext in common between sent and received traffic of the server inside TLS (and there are no identifiers in common as well).
5. Several levels of content padding to frustrate message size attacks.
6. Starting from v2 of SMP protocol (the current version is v4) all message metadata, including the time when the message was received by the server (rounded to a second) is sent to the recipients inside an encrypted envelope, so even if TLS is compromised it cannot be observed.
5. Several levels of [content padding](./docs/GLOSSARY.md#message-padding) to frustrate message size attacks.
6. All message metadata, including the time when the message was received by the server (rounded to a second) is sent to the recipients inside an encrypted envelope, so even if TLS is compromised it cannot be observed.
7. Only TLS 1.2/1.3 are allowed for client-server connections, limited to cryptographic algorithms: CHACHA20POLY1305_SHA256, Ed25519/Ed448, Curve25519/Curve448.
8. To protect against replay attacks SimpleX servers require [tlsunique channel binding](https://www.rfc-editor.org/rfc/rfc5929.html) as session ID in each client command signed with per-queue ephemeral key.
9. To protect your IP address all SimpleX Chat clients support accessing messaging servers via Tor - see [v3.1 release announcement](./blog/20220808-simplex-chat-v3.1-chat-groups.md) for more details.
10. Local database encryption with passphrase - your contacts, groups and all sent and received messages are stored encrypted. If you used SimpleX Chat before v4.0 you need to enable the encryption via the app settings.
11. Transport isolation - different TCP connections and Tor circuits are used for traffic of different user profiles, optionally - for different contacts and group member connections.
12. Manual messaging queue rotations to move conversation to another SMP relay.
13. Sending end-to-end encrypted files using [XFTP protocol](https://simplex.chat/blog/20230301-simplex-file-transfer-protocol.html).
14. Local files encryption, except videos (to be added later).
We plan to add soon:
We plan to add:
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.
1. Senders' SMP relays and recipients' XFTP relays to reduce traffic and conceal IP addresses from the relays chosen, and potentially controlled, by another party.
2. Post-quantum resistant key exchange in double ratchet protocol.
3. Automatic message queue rotation and redundancy. Currently the queues created between two users are used until the queue is manually changed by the user or contact is deleted. We are planning to add automatic queue rotation to make these identifiers temporary and rotate based on some schedule TBC (e.g., every X messages, or every X hours/days).
4. Message "mixing" - adding latency to message delivery, to protect against traffic correlation by message time.
5. Reproducible builds this is the limitation of the development stack, but we will be investing into solving this problem. Users can still build all applications and services from the source code.
## For developers
@@ -258,6 +316,8 @@ You can:
If you are considering developing with SimpleX platform please get in touch for any advice and support.
Please also join [#simplex-devs](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2F6eHqy7uAbZPOcA6qBtrQgQquVlt4Ll91%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAqV_pg3FF00L98aCXp4D3bOs4Sxv_UmSd-gb0juVoQVs%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22XonlixcHBIb2ijCehbZoiw%3D%3D%22%7D) group to ask any questions and share your success stories.
## Roadmap
- ✅ Easy to deploy SimpleX server with in-memory message storage, without any dependencies.
@@ -295,27 +355,35 @@ If you are considering developing with SimpleX platform please get in touch for
- ✅ Improved audio & video calls.
- ✅ Support older Android OS and 32-bit CPUs.
- ✅ Hidden chat profiles.
- 🏗 Sending and receiving large files via [XFTP protocol](./blog/20230301-simplex-file-transfer-protocol.md).
- 🏗 Video messages.
- 🏗 SMP queue redundancy and rotation (manual is supported).
- 🏗 Reduced battery and traffic usage in large groups.
- Sending and receiving large files via [XFTP protocol](./blog/20230301-simplex-file-transfer-protocol.md).
- Video messages.
- ✅ App access passcode.
- ✅ Improved Android app UI design.
- ✅ Optional alternative access password.
- ✅ Message reactions
- ✅ Message editing history
- ✅ Reduced battery and traffic usage in large groups.
- ✅ Message delivery confirmation (with sender opt-out per contact).
- ✅ Desktop client.
- ✅ Encryption of local files stored in the app.
- 🏗 Using mobile profiles from the desktop app.
- Message delivery relay for senders (to conceal IP address from the recipients' servers and to reduce the traffic).
- Post-quantum resistant key exchange in double ratchet protocol.
- Large groups, communities and public channels.
- Privacy & security slider - a simple way to set all settings at once.
- Improve sending videos (including encryption of locally stored videos).
- Improve experience for the new users.
- SMP queue redundancy and rotation (manual is supported).
- Include optional message into connection request sent via contact address.
- Ephemeral/disappearing/OTR conversations with the existing contacts.
- Access password/pin (with optional alternative access password).
- Local app files encryption.
- 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).
- Privately share your location.
- Feeds/broadcasts.
- Ephemeral/disappearing/OTR conversations with the existing contacts.
- Privately share your location.
- Web widgets for custom interactivity in the chats.
- Programmable chat automations / rules (automatic replies/forward/deletion/sending, reminders, etc.).
- Supporting the same profile on multiple devices.
- Desktop client.
- 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.
- 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.
## Disclaimers

View File

@@ -1,20 +0,0 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
/.idea/deploymentTargetDropDown.xml
/.idea/misc.xml
/.idea/uiDesigner.xml
/.idea/kotlinc.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
app/src/main/cpp/libs/

View File

@@ -1 +0,0 @@
SimpleX

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
</component>
</project>

View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

View File

@@ -1,20 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="previewFile" value="true" />
</inspection_tool>
</profile>
</component>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
</component>
</project>

View File

@@ -1,234 +0,0 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'org.jetbrains.kotlin.plugin.serialization'
}
android {
compileSdk 32
defaultConfig {
applicationId "chat.simplex.app"
minSdk 26
targetSdk 32
versionCode 115
versionName "5.0-beta.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary true
}
externalNativeBuild {
cmake {
cppFlags ''
}
}
manifestPlaceholders.app_name = "@string/app_name"
manifestPlaceholders.provider_authorities = "chat.simplex.app.provider"
manifestPlaceholders.extract_native_libs = compression_level != "0"
}
buildTypes {
debug {
applicationIdSuffix "$application_id_suffix"
debuggable new Boolean("$enable_debuggable")
manifestPlaceholders.app_name = "$app_name"
// Provider can't be the same for different apps on the same device
manifestPlaceholders.provider_authorities = "chat.simplex.app${application_id_suffix}.provider"
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
freeCompilerArgs += "-opt-in=kotlinx.coroutines.DelicateCoroutinesApi"
freeCompilerArgs += "-opt-in=androidx.compose.foundation.ExperimentalFoundationApi"
freeCompilerArgs += "-opt-in=androidx.compose.ui.text.ExperimentalTextApi"
freeCompilerArgs += "-opt-in=androidx.compose.material.ExperimentalMaterialApi"
freeCompilerArgs += "-opt-in=com.google.accompanist.insets.ExperimentalAnimatedInsets"
freeCompilerArgs += "-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi"
freeCompilerArgs += "-opt-in=kotlinx.serialization.InternalSerializationApi"
freeCompilerArgs += "-opt-in=kotlinx.serialization.ExperimentalSerializationApi"
}
externalNativeBuild {
cmake {
path file('src/main/cpp/CMakeLists.txt')
}
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion compose_version
}
packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
jniLibs.useLegacyPackaging = compression_level != "0"
}
def isRelease = gradle.getStartParameter().taskNames.find({ it.toLowerCase().contains("release") }) != null
def isBundle = gradle.getStartParameter().taskNames.find({ it.toLowerCase().contains("bundle") }) != null
// if (isRelease) {
// Comma separated list of languages that will be included in the apk
android.defaultConfig.resConfigs(
"en",
"cs",
"de",
"es",
"fr",
"it",
"nl",
"pl",
"ru",
"zh-rCN"
)
// }
if (isBundle) {
defaultConfig.ndk.abiFilters 'arm64-v8a', 'armeabi-v7a'
} else {
splits {
abi {
enable true
reset()
if (isRelease) {
include 'arm64-v8a', 'armeabi-v7a'
} else {
include 'arm64-v8a', 'armeabi-v7a'
universalApk false
}
}
}
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'
implementation 'androidx.lifecycle:lifecycle-process:2.4.1'
implementation 'androidx.activity:activity-compose:1.4.0'
implementation 'androidx.fragment:fragment:1.4.1'
implementation 'org.jetbrains.kotlinx:kotlinx-datetime:0.3.2'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2'
implementation "androidx.compose.material:material-icons-extended:$compose_version"
implementation "androidx.compose.ui:ui-util:$compose_version"
implementation "androidx.navigation:navigation-compose:2.4.1"
implementation "com.google.accompanist:accompanist-insets:0.23.0"
implementation 'androidx.webkit:webkit:1.4.0'
implementation "com.godaddy.android.colorpicker:compose-color-picker:0.4.2"
def work_version = "2.7.1"
implementation "androidx.work:work-runtime-ktx:$work_version"
implementation "androidx.work:work-multiprocess:$work_version"
def camerax_version = "1.1.0-beta01"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
implementation "androidx.camera:camera-view:${camerax_version}"
//Barcode
implementation 'org.boofcv:boofcv-android:0.40.1'
implementation 'org.boofcv:boofcv-core:0.40.1'
//Camera Permission
implementation "com.google.accompanist:accompanist-permissions:0.23.0"
implementation "com.google.accompanist:accompanist-pager:0.25.1"
// Link Previews
implementation 'org.jsoup:jsoup:1.13.1'
// Biometric authentication
implementation 'androidx.biometric:biometric:1.2.0-alpha04'
// GIFs support
implementation "io.coil-kt:coil-compose:2.1.0"
implementation "io.coil-kt:coil-gif:2.1.0"
// Video support
implementation "com.google.android.exoplayer:exoplayer:2.17.1"
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
}
// Don't do anything if no compression is needed
if (compression_level != "0") {
tasks.whenTaskAdded { task ->
if (task.name == 'packageDebug') {
task.finalizedBy compressApk
} else if (task.name == 'packageRelease') {
task.finalizedBy compressApk
}
}
}
tasks.register("compressApk") {
doLast {
def isRelease = gradle.getStartParameter().taskNames.find({ it.toLowerCase().contains("release") }) != null
def buildType
if (isRelease) {
buildType = "release"
} else {
buildType = "debug"
}
def javaHome = System.properties['java.home'] ?: org.gradle.internal.jvm.Jvm.current().getJavaHome()
def sdkDir = android.getSdkDirectory().getAbsolutePath()
def keyAlias = ""
def keyPassword = ""
def storeFile = ""
def storePassword = ""
if (project.properties['android.injected.signing.key.alias'] != null) {
keyAlias = project.properties['android.injected.signing.key.alias']
keyPassword = project.properties['android.injected.signing.key.password']
storeFile = project.properties['android.injected.signing.store.file']
storePassword = project.properties['android.injected.signing.store.password']
} else if (android.signingConfigs.hasProperty(buildType)) {
def gradleConfig = android.signingConfigs[buildType]
keyAlias = gradleConfig.keyAlias
keyPassword = gradleConfig.keyPassword
storeFile = gradleConfig.storeFile
storePassword = gradleConfig.storePassword
} else {
// There is no signing config for current build type, can't sign the apk
println("No signing configs for this build type: $buildType")
return
}
def outputDir = tasks["package${buildType.capitalize()}"].outputs.files.last()
exec {
workingDir '../../../scripts/android'
setEnvironment(['JAVA_HOME': "$javaHome"])
commandLine './compress-and-sign-apk.sh', \
"$compression_level", \
"$outputDir", \
"$sdkDir", \
"$storeFile", \
"$storePassword", \
"$keyAlias", \
"$keyPassword"
}
if (project.properties['android.injected.signing.key.alias'] != null && buildType == 'release') {
new File(outputDir, "app-release.apk").renameTo(new File(outputDir, "simplex.apk"))
new File(outputDir, "app-armeabi-v7a-release.apk").renameTo(new File(outputDir, "simplex-armv7a.apk"))
new File(outputDir, "app-arm64-v8a-release.apk").renameTo(new File(outputDir, "simplex.apk"))
}
// View all gradle properties set
// project.properties.each { k, v -> println "$k -> $v" }
}
}

View File

@@ -1,111 +0,0 @@
#include <jni.h>
// from the RTS
void hs_init(int * argc, char **argv[]);
// from android-support
void setLineBuffering(void);
int pipe_std_to_socket(const char * name);
extern void __svfscanf(void){};
extern void __vfwscanf(void){};
extern void __memset_chk_fail(void){};
extern void __strcpy_chk_generic(void){};
extern void __strcat_chk_generic(void){};
extern void __libc_globals(void){};
extern void __rel_iplt_start(void){};
// Android 9 only, not 13
extern void reallocarray(void){};
JNIEXPORT jint JNICALL
Java_chat_simplex_app_SimplexAppKt_pipeStdOutToSocket(JNIEnv *env, __unused jclass clazz, jstring socket_name) {
const char *name = (*env)->GetStringUTFChars(env, socket_name, JNI_FALSE);
int ret = pipe_std_to_socket(name);
(*env)->ReleaseStringUTFChars(env, socket_name, name);
return ret;
}
JNIEXPORT void JNICALL
Java_chat_simplex_app_SimplexAppKt_initHS(__unused JNIEnv *env, __unused jclass clazz) {
hs_init(NULL, NULL);
setLineBuffering();
}
// from simplex-chat
typedef long* chat_ctrl;
extern char *chat_migrate_init(const char *path, const char *key, const char *confirm, chat_ctrl *ctrl);
extern char *chat_send_cmd(chat_ctrl ctrl, const char *cmd);
extern char *chat_recv_msg(chat_ctrl ctrl); // deprecated
extern char *chat_recv_msg_wait(chat_ctrl ctrl, const int wait);
extern char *chat_parse_markdown(const char *str);
extern char *chat_parse_server(const char *str);
extern char *chat_password_hash(const char *pwd, const char *salt);
JNIEXPORT jobjectArray JNICALL
Java_chat_simplex_app_SimplexAppKt_chatMigrateInit(JNIEnv *env, __unused jclass clazz, jstring dbPath, jstring dbKey, jstring confirm) {
const char *_dbPath = (*env)->GetStringUTFChars(env, dbPath, JNI_FALSE);
const char *_dbKey = (*env)->GetStringUTFChars(env, dbKey, JNI_FALSE);
const char *_confirm = (*env)->GetStringUTFChars(env, confirm, JNI_FALSE);
jlong _ctrl = (jlong) 0;
jstring res = (*env)->NewStringUTF(env, chat_migrate_init(_dbPath, _dbKey, _confirm, &_ctrl));
(*env)->ReleaseStringUTFChars(env, dbPath, _dbPath);
(*env)->ReleaseStringUTFChars(env, dbKey, _dbKey);
(*env)->ReleaseStringUTFChars(env, dbKey, _confirm);
// Creating array of Object's (boxed values can be passed, eg. Long instead of long)
jobjectArray ret = (jobjectArray)(*env)->NewObjectArray(env, 2, (*env)->FindClass(env, "java/lang/Object"), NULL);
// Java's String
(*env)->SetObjectArrayElement(env, ret, 0, res);
// Java's Long
(*env)->SetObjectArrayElement(env, ret, 1,
(*env)->NewObject(env, (*env)->FindClass(env, "java/lang/Long"),
(*env)->GetMethodID(env, (*env)->FindClass(env, "java/lang/Long"), "<init>", "(J)V"),
_ctrl));
return ret;
}
JNIEXPORT jstring JNICALL
Java_chat_simplex_app_SimplexAppKt_chatSendCmd(JNIEnv *env, __unused jclass clazz, jlong controller, jstring msg) {
const char *_msg = (*env)->GetStringUTFChars(env, msg, JNI_FALSE);
jstring res = (*env)->NewStringUTF(env, chat_send_cmd((void*)controller, _msg));
(*env)->ReleaseStringUTFChars(env, msg, _msg);
return res;
}
JNIEXPORT jstring JNICALL
Java_chat_simplex_app_SimplexAppKt_chatRecvMsg(JNIEnv *env, __unused jclass clazz, jlong controller) {
return (*env)->NewStringUTF(env, chat_recv_msg((void*)controller));
}
JNIEXPORT jstring JNICALL
Java_chat_simplex_app_SimplexAppKt_chatRecvMsgWait(JNIEnv *env, __unused jclass clazz, jlong controller, jint wait) {
return (*env)->NewStringUTF(env, chat_recv_msg_wait((void*)controller, wait));
}
JNIEXPORT jstring JNICALL
Java_chat_simplex_app_SimplexAppKt_chatParseMarkdown(JNIEnv *env, __unused jclass clazz, jstring str) {
const char *_str = (*env)->GetStringUTFChars(env, str, JNI_FALSE);
jstring res = (*env)->NewStringUTF(env, chat_parse_markdown(_str));
(*env)->ReleaseStringUTFChars(env, str, _str);
return res;
}
JNIEXPORT jstring JNICALL
Java_chat_simplex_app_SimplexAppKt_chatParseServer(JNIEnv *env, __unused jclass clazz, jstring str) {
const char *_str = (*env)->GetStringUTFChars(env, str, JNI_FALSE);
jstring res = (*env)->NewStringUTF(env, chat_parse_server(_str));
(*env)->ReleaseStringUTFChars(env, str, _str);
return res;
}
JNIEXPORT jstring JNICALL
Java_chat_simplex_app_SimplexAppKt_chatPasswordHash(JNIEnv *env, __unused jclass clazz, jstring pwd, jstring salt) {
const char *_pwd = (*env)->GetStringUTFChars(env, pwd, JNI_FALSE);
const char *_salt = (*env)->GetStringUTFChars(env, salt, JNI_FALSE);
jstring res = (*env)->NewStringUTF(env, chat_password_hash(_pwd, _salt));
(*env)->ReleaseStringUTFChars(env, pwd, _pwd);
(*env)->ReleaseStringUTFChars(env, salt, _salt);
return res;
}

View File

@@ -1,18 +0,0 @@
package chat.simplex.app
import android.app.backup.BackupAgentHelper
import android.app.backup.FullBackupDataOutput
import android.content.Context
import chat.simplex.app.model.AppPreferences
import chat.simplex.app.model.AppPreferences.Companion.SHARED_PREFS_PRIVACY_FULL_BACKUP
class BackupAgent: BackupAgentHelper() {
override fun onFullBackup(data: FullBackupDataOutput?) {
if (applicationContext
.getSharedPreferences(AppPreferences.SHARED_PREFS_ID, Context.MODE_PRIVATE)
.getBoolean(SHARED_PREFS_PRIVACY_FULL_BACKUP, true)
) {
super.onFullBackup(data)
}
}
}

View File

@@ -1,659 +0,0 @@
package chat.simplex.app
import SectionItemView
import android.app.Application
import android.content.Intent
import android.net.Uri
import android.os.*
import android.os.SystemClock.elapsedRealtime
import android.util.Log
import android.view.WindowManager
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.animation.core.*
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Lock
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.graphicsLayer
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.*
import chat.simplex.app.MainActivity.Companion.enteredBackground
import chat.simplex.app.model.*
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
import chat.simplex.app.views.call.ActiveCallView
import chat.simplex.app.views.call.IncomingCallAlertView
import chat.simplex.app.views.chat.ChatView
import chat.simplex.app.views.chatlist.*
import chat.simplex.app.views.database.DatabaseErrorView
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.helpers.DatabaseUtils.ksAppPassword
import chat.simplex.app.views.localauth.SetAppPasscodeView
import chat.simplex.app.views.newchat.*
import chat.simplex.app.views.onboarding.*
import chat.simplex.app.views.usersettings.LAMode
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.distinctUntilChanged
class MainActivity: FragmentActivity() {
companion object {
/**
* We don't want these values to be bound to Activity lifecycle since activities are changed often, for example, when a user
* clicks on new message in notification. In this case savedInstanceState will be null (this prevents restoring the values)
* See [SimplexService.onTaskRemoved] for another part of the logic which nullifies the values when app closed by the user
* */
val userAuthorized = mutableStateOf<Boolean?>(null)
val enteredBackground = mutableStateOf<Long?>(null)
// Remember result and show it after orientation change
private val laFailed = mutableStateOf(false)
fun clearAuthState() {
userAuthorized.value = null
enteredBackground.value = null
}
}
private val vm by viewModels<SimplexViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// testJson()
val m = vm.chatModel
applyAppLocale(m.controller.appPrefs.appLanguage)
// When call ended and orientation changes, it re-process old intent, it's unneeded.
// Only needed to be processed on first creation of activity
if (savedInstanceState == null) {
processNotificationIntent(intent, m)
processIntent(intent, m)
processExternalIntent(intent, m)
}
if (m.controller.appPrefs.privacyProtectScreen.get()) {
Log.d(TAG, "onCreate: set FLAG_SECURE")
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE
)
}
setContent {
SimpleXTheme {
Surface(
Modifier
.background(MaterialTheme.colors.background)
.fillMaxSize()
) {
MainPage(
m,
userAuthorized,
laFailed,
::runAuthenticate,
::setPerformLA,
showLANotice = { showLANotice(m.controller.appPrefs.laNoticeShown, this) }
)
}
}
}
SimplexApp.context.schedulePeriodicServiceRestartWorker()
SimplexApp.context.schedulePeriodicWakeUp()
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
processIntent(intent, vm.chatModel)
processExternalIntent(intent, vm.chatModel)
}
override fun onResume() {
super.onResume()
val enteredBackgroundVal = enteredBackground.value
val delay = vm.chatModel.controller.appPrefs.laLockDelay.get()
if (enteredBackgroundVal == null || elapsedRealtime() - enteredBackgroundVal >= delay * 1000) {
runAuthenticate()
}
}
override fun onPause() {
super.onPause()
/**
* When new activity is created after a click on notification, the old one receives onPause before
* recreation but receives onStop after recreation. So using both (onPause and onStop) to prevent
* unwanted multiple auth dialogs from [runAuthenticate]
* */
enteredBackground.value = elapsedRealtime()
}
override fun onStop() {
super.onStop()
VideoPlayer.stopAll()
enteredBackground.value = elapsedRealtime()
}
override fun onBackPressed() {
if (
onBackPressedDispatcher.hasEnabledCallbacks() // Has something to do in a backstack
|| Build.VERSION.SDK_INT >= Build.VERSION_CODES.R // Android 11 or above
|| isTaskRoot // there are still other tasks after we reach the main (home) activity
) {
// https://medium.com/mobile-app-development-publication/the-risk-of-android-strandhogg-security-issue-and-how-it-can-be-mitigated-80d2ddb4af06
super.onBackPressed()
}
if (!onBackPressedDispatcher.hasEnabledCallbacks() && vm.chatModel.controller.appPrefs.performLA.get()) {
// When pressed Back and there is no one wants to process the back event, clear auth state to force re-auth on launch
clearAuthState()
laFailed.value = true
}
if (!onBackPressedDispatcher.hasEnabledCallbacks()) {
// Drop shared content
SimplexApp.context.chatModel.sharedContent.value = null
}
}
private fun runAuthenticate() {
val m = vm.chatModel
if (!m.controller.appPrefs.performLA.get()) {
userAuthorized.value = true
} else {
userAuthorized.value = false
ModalManager.shared.closeModals()
// To make Main thread free in order to allow to Compose to show blank view that hiding content underneath of it faster on slow devices
CoroutineScope(Dispatchers.Default).launch {
delay(50)
withContext(Dispatchers.Main) {
authenticate(
if (m.controller.appPrefs.laMode.get() == LAMode.SYSTEM)
generalGetString(R.string.auth_unlock)
else
generalGetString(R.string.la_enter_app_passcode),
if (m.controller.appPrefs.laMode.get() == LAMode.SYSTEM)
generalGetString(R.string.auth_log_in_using_credential)
else
generalGetString(R.string.auth_unlock),
this@MainActivity,
completed = { laResult ->
when (laResult) {
LAResult.Success ->
userAuthorized.value = true
is LAResult.Failed -> { /* Can be called multiple times on every failure */ }
is LAResult.Error -> {
laFailed.value = true
if (m.controller.appPrefs.laMode.get() == LAMode.PASSCODE) {
laFailedAlert()
}
}
is LAResult.Unavailable -> {
userAuthorized.value = true
m.performLA.value = false
m.controller.appPrefs.performLA.set(false)
laUnavailableTurningOffAlert()
}
}
}
)
}
}
}
}
private fun showLANotice(laNoticeShown: SharedPreference<Boolean>, activity: FragmentActivity) {
Log.d(TAG, "showLANotice")
if (!laNoticeShown.get()) {
laNoticeShown.set(true)
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.la_notice_title_simplex_lock),
text = generalGetString(R.string.la_notice_to_protect_your_information_turn_on_simplex_lock_you_will_be_prompted_to_complete_authentication_before_this_feature_is_enabled),
confirmText = generalGetString(R.string.la_notice_turn_on),
onConfirm = {
withBGApi { // to remove this call, change ordering of onConfirm call in AlertManager
showChooseLAMode(laNoticeShown, activity)
}
}
)
}
}
private fun showChooseLAMode(laNoticeShown: SharedPreference<Boolean>, activity: FragmentActivity) {
Log.d(TAG, "showLANotice")
laNoticeShown.set(true)
AlertManager.shared.showAlertDialogStacked(
title = generalGetString(R.string.la_lock_mode),
text = null,
confirmText = generalGetString(R.string.la_lock_mode_passcode),
dismissText = generalGetString(R.string.la_lock_mode_system),
onConfirm = {
AlertManager.shared.hideAlert()
setPasscode()
},
onDismiss = {
AlertManager.shared.hideAlert()
initialEnableLA(activity)
}
)
}
private fun initialEnableLA(activity: FragmentActivity) {
val m = vm.chatModel
val appPrefs = m.controller.appPrefs
m.controller.appPrefs.laMode.set(LAMode.SYSTEM)
authenticate(
generalGetString(R.string.auth_enable_simplex_lock),
generalGetString(R.string.auth_confirm_credential),
activity,
completed = { laResult ->
when (laResult) {
LAResult.Success -> {
m.performLA.value = true
appPrefs.performLA.set(true)
laTurnedOnAlert()
}
is LAResult.Failed -> { /* Can be called multiple times on every failure */ }
is LAResult.Error -> {
m.performLA.value = false
appPrefs.performLA.set(false)
laFailedAlert()
}
is LAResult.Unavailable -> {
m.performLA.value = false
appPrefs.performLA.set(false)
m.showAdvertiseLAUnavailableAlert.value = true
}
}
}
)
}
private fun setPasscode() {
val chatModel = vm.chatModel
val appPrefs = chatModel.controller.appPrefs
ModalManager.shared.showCustomModal { close ->
Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
SetAppPasscodeView(
submit = {
chatModel.performLA.value = true
appPrefs.performLA.set(true)
appPrefs.laMode.set(LAMode.PASSCODE)
laTurnedOnAlert()
},
cancel = {
chatModel.performLA.value = false
appPrefs.performLA.set(false)
laPasscodeNotSetAlert()
},
close)
}
}
}
private fun setPerformLA(on: Boolean, activity: FragmentActivity) {
vm.chatModel.controller.appPrefs.laNoticeShown.set(true)
if (on) {
enableLA(activity)
} else {
disableLA(activity)
}
}
private fun enableLA(activity: FragmentActivity) {
val m = vm.chatModel
authenticate(
if (m.controller.appPrefs.laMode.get() == LAMode.SYSTEM)
generalGetString(R.string.auth_enable_simplex_lock)
else
generalGetString(R.string.new_passcode),
if (m.controller.appPrefs.laMode.get() == LAMode.SYSTEM)
generalGetString(R.string.auth_confirm_credential)
else
"",
activity,
completed = { laResult ->
val prefPerformLA = m.controller.appPrefs.performLA
when (laResult) {
LAResult.Success -> {
m.performLA.value = true
prefPerformLA.set(true)
laTurnedOnAlert()
}
is LAResult.Failed -> { /* Can be called multiple times on every failure */ }
is LAResult.Error -> {
m.performLA.value = false
prefPerformLA.set(false)
laFailedAlert()
}
is LAResult.Unavailable -> {
m.performLA.value = false
prefPerformLA.set(false)
laUnavailableInstructionAlert()
}
}
}
)
}
private fun disableLA(activity: FragmentActivity) {
val m = vm.chatModel
authenticate(
if (m.controller.appPrefs.laMode.get() == LAMode.SYSTEM)
generalGetString(R.string.auth_disable_simplex_lock)
else
generalGetString(R.string.la_enter_app_passcode),
if (m.controller.appPrefs.laMode.get() == LAMode.SYSTEM)
generalGetString(R.string.auth_confirm_credential)
else
generalGetString(R.string.auth_disable_simplex_lock),
activity,
completed = { laResult ->
val prefPerformLA = m.controller.appPrefs.performLA
when (laResult) {
LAResult.Success -> {
m.performLA.value = false
prefPerformLA.set(false)
ksAppPassword.remove()
}
is LAResult.Failed -> { /* Can be called multiple times on every failure */ }
is LAResult.Error -> {
m.performLA.value = true
prefPerformLA.set(true)
laFailedAlert()
}
is LAResult.Unavailable -> {
m.performLA.value = false
prefPerformLA.set(false)
laUnavailableTurningOffAlert()
}
}
}
)
}
}
class SimplexViewModel(application: Application): AndroidViewModel(application) {
val app = getApplication<SimplexApp>()
val chatModel = app.chatModel
}
@Composable
fun MainPage(
chatModel: ChatModel,
userAuthorized: MutableState<Boolean?>,
laFailed: MutableState<Boolean>,
runAuthenticate: () -> Unit,
setPerformLA: (Boolean, FragmentActivity) -> Unit,
showLANotice: () -> Unit
) {
var showChatDatabaseError by rememberSaveable {
mutableStateOf(chatModel.chatDbStatus.value != DBMigrationResult.OK && chatModel.chatDbStatus.value != null)
}
LaunchedEffect(chatModel.chatDbStatus.value) {
showChatDatabaseError = chatModel.chatDbStatus.value != DBMigrationResult.OK && chatModel.chatDbStatus.value != null
}
var showAdvertiseLAAlert by remember { mutableStateOf(false) }
LaunchedEffect(showAdvertiseLAAlert) {
if (
!chatModel.controller.appPrefs.laNoticeShown.get()
&& showAdvertiseLAAlert
&& chatModel.onboardingStage.value == OnboardingStage.OnboardingComplete
&& chatModel.chats.isNotEmpty()
&& chatModel.activeCallInvitation.value == null
) {
showLANotice()
}
}
LaunchedEffect(chatModel.showAdvertiseLAUnavailableAlert.value) {
if (chatModel.showAdvertiseLAUnavailableAlert.value) {
laUnavailableInstructionAlert()
}
}
LaunchedEffect(chatModel.clearOverlays.value) {
if (chatModel.clearOverlays.value) {
ModalManager.shared.closeModals()
chatModel.clearOverlays.value = false
}
}
@Composable
fun authView() {
Box(
Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
SimpleButton(
stringResource(R.string.auth_unlock),
icon = Icons.Outlined.Lock,
click = {
laFailed.value = false
runAuthenticate()
}
)
}
}
Box {
val onboarding = chatModel.onboardingStage.value
val userCreated = chatModel.userCreated.value
when {
showChatDatabaseError -> {
chatModel.chatDbStatus.value?.let {
DatabaseErrorView(chatModel.chatDbStatus, chatModel.controller.appPrefs)
}
}
onboarding == null || userCreated == null -> SplashView()
userAuthorized.value != true -> {
if (chatModel.controller.appPrefs.performLA.get() && laFailed.value) {
authView()
} else {
SplashView()
}
}
onboarding == OnboardingStage.OnboardingComplete && userCreated -> {
Box {
if (chatModel.showCallView.value) ActiveCallView(chatModel)
else {
showAdvertiseLAAlert = true
BoxWithConstraints {
var currentChatId by rememberSaveable { mutableStateOf(chatModel.chatId.value) }
val offset = remember { Animatable(if (chatModel.chatId.value == null) 0f else maxWidth.value) }
Box(
Modifier
.graphicsLayer {
translationX = -offset.value.dp.toPx()
}
) {
val stopped = chatModel.chatRunning.value == false
if (chatModel.sharedContent.value == null)
ChatListView(chatModel, setPerformLA, stopped)
else
ShareListView(chatModel, stopped)
}
val scope = rememberCoroutineScope()
val onComposed: () -> Unit = {
scope.launch {
offset.animateTo(
if (chatModel.chatId.value == null) 0f else maxWidth.value,
chatListAnimationSpec()
)
if (offset.value == 0f) {
currentChatId = null
}
}
}
LaunchedEffect(Unit) {
launch {
snapshotFlow { chatModel.chatId.value }
.distinctUntilChanged()
.collect {
if (it != null) currentChatId = it
else onComposed()
}
}
}
Box (Modifier.graphicsLayer { translationX = maxWidth.toPx() - offset.value.dp.toPx() }) Box2@ {
currentChatId?.let {
ChatView(it, chatModel, onComposed)
}
}
}
}
}
}
onboarding == OnboardingStage.Step1_SimpleXInfo -> SimpleXInfo(chatModel, onboarding = true)
onboarding == OnboardingStage.Step2_CreateProfile -> CreateProfile(chatModel) {}
onboarding == OnboardingStage.Step3_SetNotificationsMode -> SetNotificationsMode(chatModel)
}
ModalManager.shared.showInView()
val invitation = chatModel.activeCallInvitation.value
if (invitation != null) IncomingCallAlertView(invitation, chatModel)
AlertManager.shared.showInView()
}
DisposableEffectOnRotate {
// When using lock delay = 0 and screen rotates, the app will be locked which is not useful.
// Let's prolong the unlocked period to 3 sec for screen rotation to take place
if (chatModel.controller.appPrefs.laLockDelay.get() == 0) {
enteredBackground.value = elapsedRealtime() + 3000
}
}
}
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) {
withBGApi {
awaitChatStartedIfNeeded(chatModel)
if (userId != null && userId != chatModel.currentUser.value?.userId && chatModel.currentUser.value != null) {
chatModel.controller.changeActiveUser(userId, null)
}
val cInfo = chatModel.getChat(chatId)?.chatInfo
chatModel.clearOverlays.value = true
if (cInfo != null) openChat(cInfo, chatModel)
}
}
}
NtfManager.ShowChatsAction -> {
Log.d(TAG, "processNotificationIntent: ShowChatsAction")
withBGApi {
awaitChatStartedIfNeeded(chatModel)
if (userId != null && userId != chatModel.currentUser.value?.userId && chatModel.currentUser.value != null) {
chatModel.controller.changeActiveUser(userId, null)
}
chatModel.chatId.value = null
chatModel.clearOverlays.value = true
}
}
NtfManager.AcceptCallAction -> {
val chatId = intent.getStringExtra("chatId")
if (chatId == null || chatId == "") return
Log.d(TAG, "processNotificationIntent: AcceptCallAction $chatId")
chatModel.clearOverlays.value = true
val invitation = chatModel.callInvitations[chatId]
if (invitation == null) {
AlertManager.shared.showAlertMsg(generalGetString(R.string.call_already_ended))
} else {
chatModel.callManager.acceptIncomingCall(invitation = invitation)
}
}
}
}
fun processIntent(intent: Intent?, chatModel: ChatModel) {
when (intent?.action) {
"android.intent.action.VIEW" -> {
val uri = intent.data
if (uri != null) connectIfOpenedViaUri(uri, chatModel)
}
}
}
fun processExternalIntent(intent: Intent?, chatModel: ChatModel) {
when (intent?.action) {
Intent.ACTION_SEND -> {
// Close active chat and show a list of chats
chatModel.chatId.value = null
chatModel.clearOverlays.value = true
when {
"text/plain" == intent.type -> intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
chatModel.sharedContent.value = SharedContent.Text(it)
}
intent.type?.startsWith("image/") == true -> (intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri)?.let {
chatModel.sharedContent.value = SharedContent.Images(intent.getStringExtra(Intent.EXTRA_TEXT) ?: "", listOf(it))
} // All other mime types
else -> (intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri)?.let {
chatModel.sharedContent.value = SharedContent.File(intent.getStringExtra(Intent.EXTRA_TEXT) ?: "", it)
}
}
}
Intent.ACTION_SEND_MULTIPLE -> {
// Close active chat and show a list of chats
chatModel.chatId.value = null
chatModel.clearOverlays.value = true
when {
intent.type?.startsWith("image/") == true -> (intent.getParcelableArrayListExtra<Parcelable>(Intent.EXTRA_STREAM) as? List<Uri>)?.let {
chatModel.sharedContent.value = SharedContent.Images(intent.getStringExtra(Intent.EXTRA_TEXT) ?: "", it)
} // All other mime types
else -> {}
}
}
}
}
fun connectIfOpenedViaUri(uri: Uri, chatModel: ChatModel) {
Log.d(TAG, "connectIfOpenedViaUri: opened via link")
if (chatModel.currentUser.value == null) {
chatModel.appOpenUrl.value = uri
} else {
withUriAction(uri) { linkType ->
val title = when (linkType) {
ConnectionLinkType.CONTACT -> generalGetString(R.string.connect_via_contact_link)
ConnectionLinkType.INVITATION -> generalGetString(R.string.connect_via_invitation_link)
ConnectionLinkType.GROUP -> generalGetString(R.string.connect_via_group_link)
}
AlertManager.shared.showAlertMsg(
title = title,
text = if (linkType == ConnectionLinkType.GROUP)
generalGetString(R.string.you_will_join_group)
else
generalGetString(R.string.profile_will_be_sent_to_contact_sending_link),
confirmText = generalGetString(R.string.connect_via_link_verb),
onConfirm = {
withApi {
Log.d(TAG, "connectIfOpenedViaUri: connecting")
connectViaUri(chatModel, linkType, uri)
}
}
)
}
}
}
suspend fun awaitChatStartedIfNeeded(chatModel: ChatModel, timeout: Long = 30_000) {
// Still decrypting database
if (chatModel.chatRunning.value == null) {
val step = 50L
for (i in 0..(timeout / step)) {
if (chatModel.chatRunning.value == true || chatModel.onboardingStage.value == OnboardingStage.Step1_SimpleXInfo) {
break
}
delay(step)
}
}
}
//fun testJson() {
// val str: String = """
// """.trimIndent()
//
// println(json.decodeFromString<APIResponse>(str))
//}

View File

@@ -1,241 +0,0 @@
package chat.simplex.app
import android.app.Application
import android.net.LocalServerSocket
import android.util.Log
import androidx.lifecycle.*
import androidx.work.*
import chat.simplex.app.model.*
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.onboarding.OnboardingStage
import chat.simplex.app.views.usersettings.NotificationsMode
import kotlinx.coroutines.*
import kotlinx.serialization.decodeFromString
import java.io.*
import java.util.*
import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit
import kotlin.concurrent.thread
const val TAG = "SIMPLEX"
// ghc's rts
external fun initHS()
// android-support
external fun pipeStdOutToSocket(socketName: String) : Int
// SimpleX API
typealias ChatCtrl = Long
external fun chatMigrateInit(dbPath: String, dbKey: String, confirm: String): Array<Any>
external fun chatSendCmd(ctrl: ChatCtrl, msg: String): String
external fun chatRecvMsg(ctrl: ChatCtrl): String
external fun chatRecvMsgWait(ctrl: ChatCtrl, timeout: Int): String
external fun chatParseMarkdown(str: String): String
external fun chatParseServer(str: String): String
external fun chatPasswordHash(pwd: String, salt: String): String
class SimplexApp: Application(), LifecycleEventObserver {
lateinit var chatController: ChatController
var isAppOnForeground: Boolean = false
val defaultLocale: Locale = Locale.getDefault()
fun initChatController(useKey: String? = null, confirmMigrations: MigrationConfirmation? = null, startChat: Boolean = true) {
val dbKey = useKey ?: DatabaseUtils.useDatabaseKey()
val dbAbsolutePathPrefix = getFilesDirectory(SimplexApp.context)
val confirm = confirmMigrations ?: if (appPreferences.confirmDBUpgrades.get()) MigrationConfirmation.Error else MigrationConfirmation.YesUp
val migrated: Array<Any> = chatMigrateInit(dbAbsolutePathPrefix, dbKey, confirm.value)
val res: DBMigrationResult = kotlin.runCatching {
json.decodeFromString<DBMigrationResult>(migrated[0] as String)
}.getOrElse { DBMigrationResult.Unknown(migrated[0] as String) }
val ctrl = if (res is DBMigrationResult.OK) {
migrated[1] as Long
} else null
if (::chatController.isInitialized) {
chatController.ctrl = ctrl
} else {
chatController = ChatController(ctrl, ntfManager, applicationContext, appPreferences)
}
chatModel.chatDbEncrypted.value = dbKey != ""
chatModel.chatDbStatus.value = res
if (res != DBMigrationResult.OK) {
Log.d(TAG, "Unable to migrate successfully: $res")
} else if (startChat) {
// If we migrated successfully means previous re-encryption process on database level finished successfully too
if (appPreferences.encryptionStartedAt.get() != null) appPreferences.encryptionStartedAt.set(null)
withApi {
val user = chatController.apiGetActiveUser()
if (user == null) {
chatModel.onboardingStage.value = OnboardingStage.Step1_SimpleXInfo
} else {
chatController.startChat(user)
chatController.showBackgroundServiceNoticeIfNeeded()
if (appPreferences.notificationsMode.get() == NotificationsMode.SERVICE.name)
SimplexService.start(applicationContext)
}
}
}
}
val chatModel: ChatModel
get() = chatController.chatModel
private val ntfManager: NtfManager by lazy {
NtfManager(applicationContext, appPreferences)
}
private val appPreferences: AppPreferences by lazy {
AppPreferences(applicationContext)
}
override fun onCreate() {
super.onCreate()
context = this
initChatController()
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
context.getDir("temp", MODE_PRIVATE).deleteRecursively()
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
Log.d(TAG, "onStateChanged: $event")
withApi {
when (event) {
Lifecycle.Event.ON_START -> {
isAppOnForeground = true
if (chatModel.chatRunning.value == true) {
kotlin.runCatching {
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()) }
}
}
Lifecycle.Event.ON_RESUME -> {
isAppOnForeground = true
if (chatModel.onboardingStage.value == OnboardingStage.OnboardingComplete) {
chatController.showBackgroundServiceNoticeIfNeeded()
}
/**
* We're starting service here instead of in [Lifecycle.Event.ON_START] because
* after calling [ChatController.showBackgroundServiceNoticeIfNeeded] notification mode in prefs can be changed.
* It can happen when app was started and a user enables battery optimization while app in background
* */
if (chatModel.chatRunning.value != false &&
chatModel.onboardingStage.value == OnboardingStage.OnboardingComplete &&
appPreferences.notificationsMode.get() == NotificationsMode.SERVICE.name
) {
SimplexService.start(applicationContext)
}
}
else -> isAppOnForeground = false
}
}
}
fun allowToStartServiceAfterAppExit() = with(chatModel.controller) {
appPrefs.notificationsMode.get() == NotificationsMode.SERVICE.name &&
(!NotificationsMode.SERVICE.requiresIgnoringBattery || isIgnoringBatteryOptimizations(chatModel.controller.appContext))
}
private fun allowToStartPeriodically() = with(chatModel.controller) {
appPrefs.notificationsMode.get() == NotificationsMode.PERIODIC.name &&
(!NotificationsMode.PERIODIC.requiresIgnoringBattery || isIgnoringBatteryOptimizations(chatModel.controller.appContext))
}
/*
* It takes 1-10 milliseconds to process this function. Better to do it in a background thread
* */
fun schedulePeriodicServiceRestartWorker() = CoroutineScope(Dispatchers.Default).launch {
if (!allowToStartServiceAfterAppExit()) {
return@launch
}
val workerVersion = chatController.appPrefs.autoRestartWorkerVersion.get()
val workPolicy = if (workerVersion == SimplexService.SERVICE_START_WORKER_VERSION) {
Log.d(TAG, "ServiceStartWorker version matches: choosing KEEP as existing work policy")
ExistingPeriodicWorkPolicy.KEEP
} else {
Log.d(TAG, "ServiceStartWorker version DOES NOT MATCH: choosing REPLACE as existing work policy")
chatController.appPrefs.autoRestartWorkerVersion.set(SimplexService.SERVICE_START_WORKER_VERSION)
ExistingPeriodicWorkPolicy.REPLACE
}
val work = PeriodicWorkRequestBuilder<SimplexService.ServiceStartWorker>(SimplexService.SERVICE_START_WORKER_INTERVAL_MINUTES, TimeUnit.MINUTES)
.addTag(SimplexService.TAG)
.addTag(SimplexService.SERVICE_START_WORKER_WORK_NAME_PERIODIC)
.build()
Log.d(TAG, "ServiceStartWorker: Scheduling period work every ${SimplexService.SERVICE_START_WORKER_INTERVAL_MINUTES} minutes")
WorkManager.getInstance(context)?.enqueueUniquePeriodicWork(SimplexService.SERVICE_START_WORKER_WORK_NAME_PERIODIC, workPolicy, work)
}
fun schedulePeriodicWakeUp() = CoroutineScope(Dispatchers.Default).launch {
if (!allowToStartPeriodically()) {
return@launch
}
MessagesFetcherWorker.scheduleWork()
}
companion object {
lateinit var context: SimplexApp private set
init {
val socketName = BuildConfig.APPLICATION_ID + ".local.socket.address.listen.native.cmd2"
val s = Semaphore(0)
thread(name="stdout/stderr pipe") {
Log.d(TAG, "starting server")
var server: LocalServerSocket? = null
for (i in 0..100) {
try {
server = LocalServerSocket(socketName + i)
break
} catch (e: IOException) {
Log.e(TAG, e.stackTraceToString())
}
}
if (server == null) {
throw Error("Unable to setup local server socket. Contact developers")
}
Log.d(TAG, "started server")
s.release()
val receiver = server.accept()
Log.d(TAG, "started receiver")
val logbuffer = FifoQueue<String>(500)
if (receiver != null) {
val inStream = receiver.inputStream
val inStreamReader = InputStreamReader(inStream)
val input = BufferedReader(inStreamReader)
Log.d(TAG, "starting receiver loop")
while (true) {
val line = input.readLine() ?: break
Log.w("$TAG (stdout/stderr)", line)
logbuffer.add(line)
}
Log.w(TAG, "exited receiver loop")
}
}
System.loadLibrary("app-lib")
s.acquire()
pipeStdOutToSocket(socketName)
initHS()
}
}
}
class FifoQueue<E>(private var capacity: Int) : LinkedList<E>() {
override fun add(element: E): Boolean {
if(size > capacity) removeFirst()
return super.add(element)
}
}

View File

@@ -1,329 +0,0 @@
package chat.simplex.app
import android.app.*
import android.content.*
import android.content.pm.PackageManager
import android.os.*
import android.provider.Settings
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.work.*
import chat.simplex.app.views.helpers.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
// based on:
// https://robertohuertas.com/2019/06/29/android_foreground_services/
// https://github.com/binwiederhier/ntfy-android/blob/main/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt
class SimplexService: Service() {
private var wakeLock: PowerManager.WakeLock? = null
private var isStartingService = false
private var notificationManager: NotificationManager? = null
private var serviceNotification: Notification? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d(TAG, "onStartCommand startId: $startId")
if (intent != null) {
val action = intent.action
Log.d(TAG, "intent action $action")
when (action) {
Action.START.name -> startService()
else -> Log.e(TAG, "No action in the intent")
}
} else {
Log.d(TAG, "null intent. Probably restarted by the system.")
}
return START_STICKY // to restart if killed
}
override fun onCreate() {
super.onCreate()
Log.d(TAG, "Simplex service created")
val title = getString(R.string.simplex_service_notification_title)
val text = getString(R.string.simplex_service_notification_text)
notificationManager = createNotificationChannel()
serviceNotification = createNotification(title, text)
startForeground(SIMPLEX_SERVICE_ID, serviceNotification)
/**
* The reason [stopAfterStart] exists is because when the service is not called [startForeground] yet, and
* we call [stopSelf] on the same service, [ForegroundServiceDidNotStartInTimeException] will be thrown.
* To prevent that, we can call [stopSelf] only when the service made [startForeground] call
* */
if (stopAfterStart) {
stopForeground(true)
stopSelf()
} else {
isServiceStarted = true
}
}
override fun onDestroy() {
Log.d(TAG, "Simplex service destroyed")
try {
wakeLock?.let {
while (it.isHeld) it.release() // release all, in case acquired more than once
}
wakeLock = null
} catch (e: Exception) {
Log.d(TAG, "Exception while releasing wakelock: ${e.message}")
}
isServiceStarted = false
stopAfterStart = false
saveServiceState(this, ServiceState.STOPPED)
// If notification service is enabled and battery optimization is disabled, restart the service
if (SimplexApp.context.allowToStartServiceAfterAppExit())
sendBroadcast(Intent(this, AutoRestartReceiver::class.java))
super.onDestroy()
}
private fun startService() {
Log.d(TAG, "SimplexService startService")
if (wakeLock != null || isStartingService) return
val self = this
isStartingService = true
withApi {
val chatController = (application as SimplexApp).chatController
try {
Log.w(TAG, "Starting foreground service")
val chatDbStatus = chatController.chatModel.chatDbStatus.value
if (chatDbStatus != DBMigrationResult.OK) {
Log.w(chat.simplex.app.TAG, "SimplexService: problem with the database: $chatDbStatus")
showPassphraseNotification(chatDbStatus)
safeStopService(self)
return@withApi
}
saveServiceState(self, ServiceState.STARTED)
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOCK_TAG).apply {
acquire()
}
}
} finally {
isStartingService = false
}
}
}
private fun createNotificationChannel(): NotificationManager? {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val channel = NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW).let {
it.setShowBadge(false) // no long-press badge
it
}
notificationManager.createNotificationChannel(channel)
return notificationManager
}
return null
}
private fun createNotification(title: String, text: String): Notification {
val pendingIntent: PendingIntent = Intent(this, MainActivity::class.java).let { notificationIntent ->
PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE)
}
val builder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ntf_service_icon)
.setColor(0x88FFFF)
.setContentTitle(title)
.setContentText(text)
.setContentIntent(pendingIntent)
.setSilent(true)
.setShowWhen(false) // no date/time
// Shows a button which opens notification channel settings
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val flags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
val setupIntent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
setupIntent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName)
setupIntent.putExtra(Settings.EXTRA_CHANNEL_ID, NOTIFICATION_CHANNEL_ID)
val setup = PendingIntent.getActivity(this, 0, setupIntent, flags)
builder.addAction(0, getString(R.string.hide_notification), setup)
}
return builder.build()
}
override fun onBind(intent: Intent): IBinder? {
return null // no binding
}
// re-schedules the task when "Clear recent apps" is pressed
override fun onTaskRemoved(rootIntent: Intent) {
// Just to make sure that after restart of the app the user will need to re-authenticate
MainActivity.clearAuthState()
// If notification service isn't enabled or battery optimization isn't disabled, we shouldn't restart the service
if (!SimplexApp.context.allowToStartServiceAfterAppExit()) {
return
}
val restartServiceIntent = Intent(applicationContext, SimplexService::class.java).also {
it.setPackage(packageName)
};
val restartServicePendingIntent: PendingIntent = PendingIntent.getService(this, 1, restartServiceIntent, PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE);
applicationContext.getSystemService(Context.ALARM_SERVICE);
val alarmService: AlarmManager = applicationContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager;
alarmService.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 1000, restartServicePendingIntent);
}
// restart on reboot
class StartReceiver: BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.d(TAG, "StartReceiver: onReceive called")
scheduleStart(context)
}
companion object {
fun toggleReceiver(enable: Boolean) {
Log.d(TAG, "StartReceiver: toggleReceiver enabled: $enable")
val component = ComponentName(BuildConfig.APPLICATION_ID, StartReceiver::class.java.name)
SimplexApp.context.packageManager.setComponentEnabledSetting(
component,
if (enable) PackageManager.COMPONENT_ENABLED_STATE_ENABLED else PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP
)
}
}
}
// restart on destruction
class AutoRestartReceiver: BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.d(TAG, "AutoRestartReceiver: onReceive called")
scheduleStart(context)
}
}
class ServiceStartWorker(private val context: Context, params: WorkerParameters): CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
val id = this.id
if (context.applicationContext !is Application) {
Log.d(TAG, "ServiceStartWorker: Failed, no application found (work ID: $id)")
return Result.failure()
}
if (getServiceState(context) == ServiceState.STARTED) {
Log.d(TAG, "ServiceStartWorker: Starting foreground service (work ID: $id)")
start(context)
}
return Result.success()
}
}
enum class Action {
START,
}
enum class ServiceState {
STARTED,
STOPPED,
}
companion object {
const val TAG = "SIMPLEX_SERVICE"
const val NOTIFICATION_CHANNEL_ID = "chat.simplex.app.SIMPLEX_SERVICE_NOTIFICATION"
const val NOTIFICATION_CHANNEL_NAME = "SimpleX Chat service"
const val SIMPLEX_SERVICE_ID = 6789
const val SERVICE_START_WORKER_VERSION = BuildConfig.VERSION_CODE
const val SERVICE_START_WORKER_INTERVAL_MINUTES = 3 * 60L
const val SERVICE_START_WORKER_WORK_NAME_PERIODIC = "SimplexAutoRestartWorkerPeriodic" // Do not change!
private const val PASSPHRASE_NOTIFICATION_ID = 1535
private const val WAKE_LOCK_TAG = "SimplexService::lock"
private const val SHARED_PREFS_ID = "chat.simplex.app.SIMPLEX_SERVICE_PREFS"
private const val SHARED_PREFS_SERVICE_STATE = "SIMPLEX_SERVICE_STATE"
private const val WORK_NAME_ONCE = "ServiceStartWorkerOnce"
private var isServiceStarted = false
private var stopAfterStart = false
fun scheduleStart(context: Context) {
Log.d(TAG, "Enqueuing work to start subscriber service")
val workManager = WorkManager.getInstance(context)
val startServiceRequest = OneTimeWorkRequest.Builder(ServiceStartWorker::class.java).build()
workManager.enqueueUniqueWork(WORK_NAME_ONCE, ExistingWorkPolicy.KEEP, startServiceRequest) // Unique avoids races!
}
suspend fun start(context: Context) = serviceAction(context, Action.START)
/**
* If there is a need to stop the service, use this function only. It makes sure that the service will be stopped without an
* exception related to foreground services lifecycle
* */
fun safeStopService(context: Context) {
if (isServiceStarted) {
context.stopService(Intent(context, SimplexService::class.java))
} else {
stopAfterStart = true
}
}
private suspend fun serviceAction(context: Context, action: Action) {
Log.d(TAG, "SimplexService serviceAction: ${action.name}")
withContext(Dispatchers.IO) {
Intent(context, SimplexService::class.java).also {
it.action = action.name
ContextCompat.startForegroundService(context, it)
}
}
}
fun restart(context: Context) {
Intent(context, SimplexService::class.java).also { intent ->
context.stopService(intent) // Service will auto-restart
}
}
fun saveServiceState(context: Context, state: ServiceState) {
getPreferences(context).edit()
.putString(SHARED_PREFS_SERVICE_STATE, state.name)
.apply()
}
fun getServiceState(context: Context): ServiceState {
val value = getPreferences(context)
.getString(SHARED_PREFS_SERVICE_STATE, ServiceState.STOPPED.name)
return ServiceState.valueOf(value!!)
}
fun showPassphraseNotification(chatDbStatus: DBMigrationResult?) {
val pendingIntent: PendingIntent = Intent(SimplexApp.context, MainActivity::class.java).let { notificationIntent ->
PendingIntent.getActivity(SimplexApp.context, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE)
}
val title = when(chatDbStatus) {
is DBMigrationResult.ErrorNotADatabase -> generalGetString(R.string.enter_passphrase_notification_title)
is DBMigrationResult.OK -> return
else -> generalGetString(R.string.database_initialization_error_title)
}
val description = when(chatDbStatus) {
is DBMigrationResult.ErrorNotADatabase -> generalGetString(R.string.enter_passphrase_notification_desc)
is DBMigrationResult.OK -> return
else -> generalGetString(R.string.database_initialization_error_desc)
}
val builder = NotificationCompat.Builder(SimplexApp.context, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ntf_service_icon)
.setColor(0x88FFFF)
.setContentTitle(title)
.setContentText(description)
.setContentIntent(pendingIntent)
.setSilent(true)
.setShowWhen(false)
val notificationManager = SimplexApp.context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(PASSPHRASE_NOTIFICATION_ID, builder.build())
}
fun cancelPassphraseNotification() {
val notificationManager = SimplexApp.context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancel(PASSPHRASE_NOTIFICATION_ID)
}
private fun getPreferences(context: Context): SharedPreferences = context.getSharedPreferences(SHARED_PREFS_ID, Context.MODE_PRIVATE)
}
}

View File

@@ -1,79 +0,0 @@
package chat.simplex.app.ui.theme
import android.app.UiModeManager
import android.content.Context
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import chat.simplex.app.SimplexApp
import kotlinx.coroutines.flow.MutableStateFlow
enum class DefaultTheme {
SYSTEM, DARK, LIGHT
}
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 DEFAULT_BOTTOM_BUTTON_PADDING = 20.dp
val DarkColorPalette = darkColors(
primary = SimplexBlue, // If this value changes also need to update #0088ff in string resource files
primaryVariant = SimplexGreen,
secondary = DarkGray,
// background = Color.Black,
// surface = Color.Black,
// background = Color(0xFF121212),
// surface = Color(0xFF121212),
error = Color.Red,
onBackground = Color(0xFFFFFBFA),
onSurface = Color(0xFFFFFBFA),
// onError: Color = Color.Black,
)
val LightColorPalette = lightColors(
primary = SimplexBlue, // If this value changes also need to update #0088ff in string resource files
primaryVariant = SimplexGreen,
secondary = LightGray,
error = Color.Red,
// background = Color.White,
// surface = Color.White
// onPrimary = Color.White,
// onSecondary = Color.Black,
// onBackground = Color.Black,
// onSurface = Color.Black,
)
val CurrentColors: MutableStateFlow<Pair<Colors, DefaultTheme>> = MutableStateFlow(ThemeManager.currentColors(isInNightMode()))
// Non-@Composable implementation
private fun isInNightMode() =
(SimplexApp.context.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager).nightMode == UiModeManager.MODE_NIGHT_YES
@Composable
fun isInDarkTheme(): Boolean = !CurrentColors.collectAsState().value.first.isLight
@Composable
fun SimpleXTheme(darkTheme: Boolean? = null, content: @Composable () -> Unit) {
LaunchedEffect(darkTheme) {
// For preview
if (darkTheme != null)
CurrentColors.value = ThemeManager.currentColors(darkTheme)
}
val systemDark = isSystemInDarkTheme()
LaunchedEffect(systemDark) {
if (CurrentColors.value.second == DefaultTheme.SYSTEM && CurrentColors.value.first.isLight == systemDark) {
// Change active colors from light to dark and back based on system theme
ThemeManager.applyTheme(DefaultTheme.SYSTEM.name, systemDark)
}
}
val theme by CurrentColors.collectAsState()
MaterialTheme(
colors = theme.first,
typography = Typography,
shapes = Shapes,
content = content
)
}

View File

@@ -1,64 +0,0 @@
package chat.simplex.app.ui.theme
import androidx.compose.material.Colors
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import chat.simplex.app.R
import chat.simplex.app.SimplexApp
import chat.simplex.app.model.AppPreferences
import chat.simplex.app.views.helpers.generalGetString
object ThemeManager {
private val appPrefs: AppPreferences by lazy {
AppPreferences(SimplexApp.context)
}
fun currentColors(darkForSystemTheme: Boolean): Pair<Colors, DefaultTheme> {
val theme = appPrefs.currentTheme.get()!!
val systemThemeColors = if (darkForSystemTheme) DarkColorPalette else LightColorPalette
val res = when (theme) {
DefaultTheme.SYSTEM.name -> Pair(systemThemeColors, DefaultTheme.SYSTEM)
DefaultTheme.DARK.name -> Pair(DarkColorPalette, DefaultTheme.DARK)
DefaultTheme.LIGHT.name -> Pair(LightColorPalette, DefaultTheme.LIGHT)
else -> Pair(systemThemeColors, DefaultTheme.SYSTEM)
}
return res.copy(first = res.first.copy(primary = Color(appPrefs.primaryColor.get())))
}
// colors, default theme enum, localized name of theme
fun allThemes(darkForSystemTheme: Boolean): List<Triple<Colors, DefaultTheme, String>> {
val allThemes = ArrayList<Triple<Colors, DefaultTheme, String>>()
allThemes.add(
Triple(
if (darkForSystemTheme) DarkColorPalette else LightColorPalette,
DefaultTheme.SYSTEM,
generalGetString(R.string.theme_system)
)
)
allThemes.add(
Triple(
LightColorPalette,
DefaultTheme.LIGHT,
generalGetString(R.string.theme_light)
)
)
allThemes.add(
Triple(
DarkColorPalette,
DefaultTheme.DARK,
generalGetString(R.string.theme_dark)
)
)
return allThemes
}
fun applyTheme(name: String, darkForSystemTheme: Boolean) {
appPrefs.currentTheme.set(name)
CurrentColors.value = currentColors(darkForSystemTheme)
}
fun saveAndApplyPrimaryColor(color: Color) {
appPrefs.primaryColor.set(color.toArgb())
CurrentColors.value = currentColors(!CurrentColors.value.first.isLight)
}
}

View File

@@ -1,194 +0,0 @@
package chat.simplex.app.views
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.material.MaterialTheme.colors
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowBackIosNew
import androidx.compose.material.icons.outlined.ArrowForwardIos
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.style.*
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.model.ChatModel
import chat.simplex.app.model.Profile
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.onboarding.OnboardingStage
import chat.simplex.app.views.onboarding.ReadableText
import com.google.accompanist.insets.navigationBarsWithImePadding
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
fun isValidDisplayName(name: String) : Boolean {
return (name.firstOrNull { it.isWhitespace() }) == null && !name.startsWith("@") && !name.startsWith("#")
}
@Composable
fun CreateProfilePanel(chatModel: ChatModel, close: () -> Unit) {
val displayName = rememberSaveable { mutableStateOf("") }
val fullName = rememberSaveable { mutableStateOf("") }
val focusRequester = remember { FocusRequester() }
Surface(Modifier.background(MaterialTheme.colors.onBackground)) {
Column(
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())
) {
/*CloseSheetBar(close = {
if (chatModel.users.isEmpty()) {
chatModel.onboardingStage.value = OnboardingStage.Step1_SimpleXInfo
} else {
close()
}
})*/
Column(Modifier.padding(horizontal = DEFAULT_PADDING * 1f)) {
AppBarTitleCentered(stringResource(R.string.create_profile))
ReadableText(R.string.your_profile_is_stored_on_your_device, TextAlign.Center, padding = PaddingValues())
ReadableText(R.string.profile_is_only_shared_with_your_contacts, TextAlign.Center)
Spacer(Modifier.height(DEFAULT_PADDING * 1.5f))
Row(Modifier.padding(bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Text(
stringResource(R.string.display_name),
fontSize = 16.sp
)
if (!isValidDisplayName(displayName.value)) {
Text(
stringResource(R.string.no_spaces),
fontSize = 16.sp,
color = Color.Red
)
}
}
ProfileNameField(displayName, "", ::isValidDisplayName, focusRequester)
Spacer(Modifier.height(DEFAULT_PADDING))
Text(
stringResource(R.string.full_name_optional__prompt),
fontSize = 16.sp,
modifier = Modifier.padding(bottom = DEFAULT_PADDING_HALF)
)
ProfileNameField(fullName, "", ::isValidDisplayName)
}
Spacer(Modifier.fillMaxHeight().weight(1f))
Row {
if (chatModel.users.isEmpty()) {
SimpleButtonDecorated(
text = stringResource(R.string.about_simplex),
icon = Icons.Outlined.ArrowBackIosNew,
textDecoration = TextDecoration.None,
fontWeight = FontWeight.Bold
) { chatModel.onboardingStage.value = OnboardingStage.Step1_SimpleXInfo }
}
Spacer(Modifier.fillMaxWidth().weight(1f))
val enabled = displayName.value.isNotEmpty() && isValidDisplayName(displayName.value)
val createModifier: Modifier
val createColor: Color
if (enabled) {
createModifier = Modifier.clickable { createProfile(chatModel, displayName.value, fullName.value, close) }.padding(8.dp)
createColor = MaterialTheme.colors.primary
} else {
createModifier = Modifier.padding(8.dp)
createColor = HighOrLowlight
}
Surface(shape = RoundedCornerShape(20.dp)) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = createModifier) {
Text(stringResource(R.string.create_profile_button), style = MaterialTheme.typography.caption, color = createColor, fontWeight = FontWeight.Medium)
Icon(Icons.Outlined.ArrowForwardIos, stringResource(R.string.create_profile_button), tint = createColor)
}
}
}
LaunchedEffect(Unit) {
delay(300)
focusRequester.requestFocus()
}
}
}
}
fun createProfile(chatModel: ChatModel, displayName: String, fullName: String, close: () -> Unit) {
withApi {
val user = chatModel.controller.apiCreateActiveUser(
Profile(displayName, fullName, null)
) ?: 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()
}
}
}
@Composable
fun ProfileNameField(name: MutableState<String>, placeholder: String = "", isValid: (String) -> Boolean = { true }, focusRequester: FocusRequester? = null) {
var valid by rememberSaveable { mutableStateOf(true) }
var focused by rememberSaveable { mutableStateOf(false) }
val strokeColor by remember {
derivedStateOf {
if (valid) {
if (focused) {
HighOrLowlight.copy(alpha = 0.6f)
} else {
HighOrLowlight.copy(alpha = 0.3f)
}
} else Color.Red
}
}
val modifier = Modifier
.fillMaxWidth()
.height(55.dp)
.border(border = BorderStroke(1.dp, strokeColor), shape = RoundedCornerShape(50))
.padding(horizontal = 8.dp)
.navigationBarsWithImePadding()
.onFocusChanged { focused = it.isFocused }
TextField(
value = name.value,
onValueChange = { name.value = it },
modifier = if (focusRequester == null) modifier else modifier.focusRequester(focusRequester),
textStyle = TextStyle(fontSize = 18.sp, color = colors.onBackground),
keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.None,
autoCorrect = false
),
singleLine = true,
isError = !valid,
placeholder = { Text(placeholder, fontSize = 18.sp, color = HighOrLowlight.copy(alpha = 0.3f)) },
shape = RoundedCornerShape(50),
colors = TextFieldDefaults.textFieldColors(
backgroundColor = Color.Unspecified,
textColor = MaterialTheme.colors.onBackground,
focusedIndicatorColor = Color.Unspecified,
unfocusedIndicatorColor = Color.Unspecified,
cursorColor = HighOrLowlight,
errorIndicatorColor = Color.Unspecified
)
)
LaunchedEffect(Unit) {
snapshotFlow { name.value }
.distinctUntilChanged()
.collect {
valid = isValid(it)
}
}
}

View File

@@ -1,471 +0,0 @@
package chat.simplex.app.views.chat
import InfoRow
import InfoRowEllipsis
import SectionDivider
import SectionItemView
import SectionSpacer
import SectionView
import android.widget.Toast
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardActions
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.*
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.platform.ClipboardManager
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle
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.SimplexApp
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.usersettings.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.datetime.Clock
@Composable
fun ChatInfoView(
chatModel: ChatModel,
contact: Contact,
connStats: ConnectionStats?,
customUserProfile: Profile?,
localAlias: String,
connectionCode: String?,
close: () -> Unit,
) {
BackHandler(onBack = close)
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,
developerTools,
onLocalAliasChanged = {
setContactAlias(chat.chatInfo.apiId, it, chatModel)
},
openPreferences = {
ModalManager.shared.showCustomModal { close ->
val user = chatModel.currentUser.value
if (user != null) {
ContactPreferencesView(chatModel, user, contact.contactId, close)
}
}
},
deleteContact = { deleteContactDialog(chat.chatInfo, chatModel, close) },
clearChat = { clearChatDialog(chat.chatInfo, chatModel, close) },
switchContactAddress = {
showSwitchContactAddressAlert(chatModel, contact.contactId)
},
verifyClicked = {
ModalManager.shared.showModalCloseable { close ->
remember { derivedStateOf { (chatModel.getContactChat(contact.contactId)?.chatInfo as? ChatInfo.Direct)?.contact } }.value?.let { ct ->
VerifyCodeView(
ct.displayName,
connectionCode,
ct.verified,
verify = { code ->
chatModel.controller.apiVerifyContact(ct.contactId, code)?.let { r ->
val (verified, existingCode) = r
chatModel.updateContact(
ct.copy(
activeConn = ct.activeConn.copy(
connectionCode = if (verified) SecurityCode(existingCode, Clock.System.now()) else null
)
)
)
r
}
},
close,
)
}
}
}
)
}
}
fun deleteContactDialog(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> Unit)? = null) {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.delete_contact_question),
text = generalGetString(R.string.delete_contact_all_messages_deleted_cannot_undo_warning),
confirmText = generalGetString(R.string.delete_verb),
onConfirm = {
withApi {
val r = chatModel.controller.apiDeleteChat(chatInfo.chatType, chatInfo.apiId)
if (r) {
chatModel.removeChat(chatInfo.id)
chatModel.chatId.value = null
chatModel.controller.ntfManager.cancelNotificationsForChat(chatInfo.id)
close?.invoke()
}
}
}
)
}
fun clearChatDialog(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> Unit)? = null) {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.clear_chat_question),
text = generalGetString(R.string.clear_chat_warning),
confirmText = generalGetString(R.string.clear_verb),
onConfirm = {
withApi {
val updatedChatInfo = chatModel.controller.apiClearChat(chatInfo.chatType, chatInfo.apiId)
if (updatedChatInfo != null) {
chatModel.clearChat(updatedChatInfo)
chatModel.controller.ntfManager.cancelNotificationsForChat(chatInfo.id)
close?.invoke()
}
}
}
)
}
@Composable
fun ChatInfoLayout(
chat: Chat,
contact: Contact,
connStats: ConnectionStats?,
contactNetworkStatus: NetworkStatus,
customUserProfile: Profile?,
localAlias: String,
connectionCode: String?,
developerTools: Boolean,
onLocalAliasChanged: (String) -> Unit,
openPreferences: () -> Unit,
deleteContact: () -> Unit,
clearChat: () -> Unit,
switchContactAddress: () -> Unit,
verifyClicked: () -> Unit,
) {
Column(
Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.Start
) {
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
ChatInfoHeader(chat.chatInfo, contact)
}
LocalAliasEditor(localAlias, updateValue = onLocalAliasChanged)
if (customUserProfile != null) {
SectionSpacer()
SectionView(generalGetString(R.string.incognito).uppercase()) {
InfoRow(generalGetString(R.string.incognito_random_profile), customUserProfile.chatViewName)
}
}
SectionSpacer()
SectionView {
if (connectionCode != null) {
VerifyCodeButton(contact.verified, verifyClicked)
SectionDivider()
}
ContactPreferencesButton(openPreferences)
}
SectionSpacer()
SectionView(title = stringResource(R.string.conn_stats_section_title_servers)) {
SwitchAddressButton(switchContactAddress)
SectionDivider()
if (connStats != null) {
SectionItemView({
AlertManager.shared.showAlertMsg(
generalGetString(R.string.network_status),
contactNetworkStatus.statusExplanation
)}) {
NetworkStatusRow(contactNetworkStatus)
}
val rcvServers = connStats.rcvServers
if (rcvServers != null && rcvServers.isNotEmpty()) {
SectionDivider()
SimplexServers(stringResource(R.string.receiving_via), rcvServers)
}
val sndServers = connStats.sndServers
if (sndServers != null && sndServers.isNotEmpty()) {
SectionDivider()
SimplexServers(stringResource(R.string.sending_via), sndServers)
}
}
}
SectionSpacer()
SectionView {
ClearChatButton(clearChat)
SectionDivider()
DeleteContactButton(deleteContact)
}
SectionSpacer()
if (developerTools) {
SectionView(title = stringResource(R.string.section_title_for_console)) {
InfoRow(stringResource(R.string.info_row_local_name), chat.chatInfo.localDisplayName)
SectionDivider()
InfoRow(stringResource(R.string.info_row_database_id), chat.chatInfo.apiId.toString())
}
SectionSpacer()
}
}
}
@Composable
fun ChatInfoHeader(cInfo: ChatInfo, contact: Contact) {
Column(
Modifier.padding(horizontal = 8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
ChatInfoImage(cInfo, size = 192.dp, iconColor = if (isInDarkTheme()) GroupDark else SettingsSecondaryLight)
Row(Modifier.padding(bottom = 8.dp), verticalAlignment = Alignment.CenterVertically) {
if (contact.verified) {
Icon(Icons.Outlined.VerifiedUser, null, Modifier.padding(end = 6.dp, top = 4.dp).size(24.dp), tint = HighOrLowlight)
}
Text(
contact.profile.displayName, style = MaterialTheme.typography.h1.copy(fontWeight = FontWeight.Normal),
color = MaterialTheme.colors.onBackground,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
if (cInfo.fullName != "" && cInfo.fullName != cInfo.displayName && cInfo.fullName != contact.profile.displayName) {
Text(
cInfo.fullName, style = MaterialTheme.typography.h2,
color = MaterialTheme.colors.onBackground,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
}
}
}
@Composable
fun LocalAliasEditor(
initialValue: String,
center: Boolean = true,
leadingIcon: Boolean = false,
focus: Boolean = false,
updateValue: (String) -> Unit
) {
var value by rememberSaveable { mutableStateOf(initialValue) }
val modifier = if (center)
Modifier.padding(horizontal = if (!leadingIcon) DEFAULT_PADDING else 0.dp).widthIn(min = 100.dp)
else
Modifier.padding(horizontal = if (!leadingIcon) DEFAULT_PADDING else 0.dp).fillMaxWidth()
Row(Modifier.fillMaxWidth(), horizontalArrangement = if (center) Arrangement.Center else Arrangement.Start) {
DefaultBasicTextField(
modifier,
value,
{
Text(
generalGetString(R.string.text_field_set_contact_placeholder),
textAlign = if (center) TextAlign.Center else TextAlign.Start,
color = HighOrLowlight
)
},
leadingIcon = if (leadingIcon) {
{ Icon(Icons.Default.Edit, null, Modifier.padding(start = 7.dp)) }
} else null,
color = HighOrLowlight,
focus = focus,
textStyle = TextStyle.Default.copy(textAlign = if (value.isEmpty() || !center) TextAlign.Start else TextAlign.Center),
keyboardActions = KeyboardActions(onDone = { updateValue(value) })
) {
value = it
}
}
LaunchedEffect(Unit) {
snapshotFlow { value }
.onEach { delay(500) } // wait a little after every new character, don't emit until user stops typing
.conflate() // get the latest value
.filter { it == value } // don't process old ones
.collect {
updateValue(value)
}
}
DisposableEffect(Unit) {
onDispose { updateValue(value) } // just in case snapshotFlow will be canceled when user presses Back too fast
}
}
@Composable
private fun NetworkStatusRow(networkStatus: NetworkStatus) {
Row(
Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(stringResource(R.string.network_status))
Icon(
Icons.Outlined.Info,
stringResource(R.string.network_status),
tint = MaterialTheme.colors.primary
)
}
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
networkStatus.statusString,
color = HighOrLowlight
)
ServerImage(networkStatus)
}
}
}
@Composable
private fun ServerImage(networkStatus: NetworkStatus) {
Box(Modifier.size(18.dp)) {
when (networkStatus) {
is NetworkStatus.Connected ->
Icon(Icons.Filled.Circle, stringResource(R.string.icon_descr_server_status_connected), tint = MaterialTheme.colors.primaryVariant)
is NetworkStatus.Disconnected ->
Icon(Icons.Filled.Pending, stringResource(R.string.icon_descr_server_status_disconnected), tint = HighOrLowlight)
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)
}
}
}
@Composable
fun SimplexServers(text: String, servers: List<String>) {
val info = servers.joinToString(separator = ", ") { it.substringAfter("@") }
val clipboardManager: ClipboardManager = LocalClipboardManager.current
InfoRowEllipsis(text, info) {
clipboardManager.setText(AnnotatedString(servers.joinToString(separator = ",")))
Toast.makeText(SimplexApp.context, generalGetString(R.string.copied), Toast.LENGTH_SHORT).show()
}
}
@Composable
fun SwitchAddressButton(onClick: () -> Unit) {
SectionItemView(onClick) {
Text(stringResource(R.string.switch_receiving_address), color = MaterialTheme.colors.primary)
}
}
@Composable
fun VerifyCodeButton(contactVerified: Boolean, onClick: () -> Unit) {
SettingsActionItem(
if (contactVerified) Icons.Outlined.VerifiedUser else Icons.Outlined.Shield,
stringResource(if (contactVerified) R.string.view_security_code else R.string.verify_security_code),
click = onClick,
iconColor = HighOrLowlight,
)
}
@Composable
private fun ContactPreferencesButton(onClick: () -> Unit) {
SettingsActionItem(
Icons.Outlined.ToggleOn,
stringResource(R.string.contact_preferences),
click = onClick
)
}
@Composable
fun ClearChatButton(onClick: () -> Unit) {
SettingsActionItem(
Icons.Outlined.Restore,
stringResource(R.string.clear_chat_button),
click = onClick,
textColor = WarningOrange,
iconColor = WarningOrange,
)
}
@Composable
private fun DeleteContactButton(onClick: () -> Unit) {
SettingsActionItem(
Icons.Outlined.Delete,
stringResource(R.string.button_delete_contact),
click = onClick,
textColor = Color.Red,
iconColor = Color.Red,
)
}
private fun setContactAlias(contactApiId: Long, localAlias: String, chatModel: ChatModel) = withApi {
chatModel.controller.apiSetContactAlias(contactApiId, localAlias)?.let {
chatModel.updateContact(it)
}
}
private fun showSwitchContactAddressAlert(m: ChatModel, contactId: Long) {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.switch_receiving_address_question),
text = generalGetString(R.string.switch_receiving_address_desc),
confirmText = generalGetString(R.string.switch_verb),
onConfirm = {
switchContactAddress(m, contactId)
}
)
}
private fun switchContactAddress(m: ChatModel, contactId: Long) = withApi {
m.controller.apiSwitchContact(contactId)
}
@Preview
@Composable
fun PreviewChatInfoLayout() {
SimpleXTheme {
ChatInfoLayout(
chat = Chat(
chatInfo = ChatInfo.Direct.sampleData,
chatItems = arrayListOf()
),
Contact.sampleData,
localAlias = "",
connectionCode = "123",
developerTools = false,
connStats = null,
contactNetworkStatus = NetworkStatus.Connected(),
onLocalAliasChanged = {},
customUserProfile = null,
openPreferences = {},
deleteContact = {},
clearChat = {},
switchContactAddress = {},
verifyClicked = {},
)
}
}

View File

@@ -1,133 +0,0 @@
import androidx.compose.animation.core.Animatable
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.Close
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
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.durationText
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.chat.item.SentColorLight
import chat.simplex.app.views.helpers.*
import kotlinx.coroutines.flow.distinctUntilChanged
@Composable
fun ComposeVoiceView(
filePath: String,
recordedDurationMs: Int,
finishedRecording: Boolean,
cancelEnabled: Boolean,
cancelVoice: () -> Unit
) {
BoxWithConstraints(Modifier
.fillMaxWidth()
) {
val audioPlaying = rememberSaveable { mutableStateOf(false) }
val progress = rememberSaveable { mutableStateOf(0) }
val duration = rememberSaveable(recordedDurationMs) { mutableStateOf(recordedDurationMs) }
val progressBarWidth = remember { Animatable(0f) }
LaunchedEffect(recordedDurationMs, finishedRecording) {
snapshotFlow { progress.value }
.distinctUntilChanged()
.collect {
val startTime = when {
finishedRecording -> progress.value
else -> recordedDurationMs
}
val endTime = when {
finishedRecording -> duration.value
audioPlaying.value -> recordedDurationMs
else -> MAX_VOICE_MILLIS_FOR_SENDING
}
val to = ((startTime.toDouble() / endTime) * maxWidth.value).dp
progressBarWidth.animateTo(to.value, audioProgressBarAnimationSpec())
}
}
Spacer(
Modifier
.requiredWidth(progressBarWidth.value.dp)
.padding(top = 58.dp)
.height(3.dp)
.background(MaterialTheme.colors.primary)
)
Row(
Modifier
.height(60.dp)
.fillMaxWidth()
.padding(top = 8.dp)
.background(SentColorLight),
verticalAlignment = Alignment.CenterVertically
) {
IconButton(
onClick = {
if (!audioPlaying.value) {
AudioPlayer.play(filePath, audioPlaying, progress, duration, false)
} else {
AudioPlayer.pause(audioPlaying, progress)
}
},
enabled = finishedRecording) {
Icon(
if (audioPlaying.value) Icons.Filled.Pause else Icons.Filled.PlayArrow,
stringResource(R.string.icon_descr_file),
Modifier
.padding(start = 4.dp, end = 2.dp)
.size(36.dp),
tint = if (finishedRecording) MaterialTheme.colors.primary else HighOrLowlight
)
}
val numberInText = remember(recordedDurationMs, progress.value) {
derivedStateOf {
when {
finishedRecording && progress.value == 0 && !audioPlaying.value -> duration.value / 1000
finishedRecording -> progress.value / 1000
else -> recordedDurationMs / 1000
}
}
}
Text(
durationText(numberInText.value),
fontSize = 18.sp,
color = HighOrLowlight,
)
Spacer(Modifier.weight(1f))
if (cancelEnabled) {
IconButton(
onClick = {
AudioPlayer.stop(filePath)
cancelVoice()
},
modifier = Modifier.padding(0.dp)
) {
Icon(
Icons.Outlined.Close,
contentDescription = stringResource(R.string.icon_descr_cancel_file_preview),
tint = MaterialTheme.colors.primary,
modifier = Modifier.padding(10.dp)
)
}
}
}
}
}
@Preview
@Composable
fun PreviewComposeAudioView() {
SimpleXTheme {
ComposeFileView(
"test.txt",
cancelFile = {},
cancelEnabled = true
)
}
}

View File

@@ -1,80 +0,0 @@
package chat.simplex.app.views.chat
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.outlined.Close
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
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.*
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.chat.item.*
import kotlinx.datetime.Clock
@Composable
fun ContextItemView(
contextItem: ChatItem,
contextIcon: ImageVector,
cancelContextItem: () -> Unit
) {
val sent = contextItem.chatDir.sent
Row(
Modifier
.padding(top = 8.dp)
.background(if (sent) SentColorLight else ReceivedColorLight),
verticalAlignment = Alignment.CenterVertically
) {
Row(
Modifier
.padding(vertical = 12.dp)
.fillMaxWidth()
.weight(1F),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
contextIcon,
modifier = Modifier
.padding(horizontal = 8.dp)
.height(20.dp)
.width(20.dp),
contentDescription = stringResource(R.string.icon_descr_context),
tint = HighOrLowlight,
)
MarkdownText(
contextItem.text, contextItem.formattedText,
sender = contextItem.memberDisplayName, senderBold = true, maxLines = 3,
linkMode = SimplexLinkMode.DESCRIPTION,
modifier = Modifier.fillMaxWidth(),
)
}
IconButton(onClick = cancelContextItem) {
Icon(
Icons.Outlined.Close,
contentDescription = stringResource(R.string.cancel_verb),
tint = MaterialTheme.colors.primary,
modifier = Modifier.padding(10.dp)
)
}
}
}
@Preview
@Composable
fun PreviewContextItemView() {
SimpleXTheme {
ContextItemView(
contextItem = ChatItem.getSampleData(1, CIDirection.DirectRcv(), Clock.System.now(), "hello"),
contextIcon = Icons.Filled.Edit,
cancelContextItem = {}
)
}
}

View File

@@ -1,53 +0,0 @@
package chat.simplex.app.views.chat
import android.Manifest
import androidx.compose.foundation.layout.*
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import chat.simplex.app.R
import chat.simplex.app.ui.theme.DEFAULT_PADDING
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.newchat.QRCodeScanner
import com.google.accompanist.permissions.rememberPermissionState
@Composable
fun ScanCodeView(verifyCode: (String?, cb: (Boolean) -> Unit) -> Unit, close: () -> Unit) {
val cameraPermissionState = rememberPermissionState(permission = Manifest.permission.CAMERA)
LaunchedEffect(Unit) {
cameraPermissionState.launchPermissionRequest()
}
ScanCodeLayout(verifyCode, close)
}
@Composable
private fun ScanCodeLayout(verifyCode: (String?, cb: (Boolean) -> Unit) -> Unit, close: () -> Unit) {
Column(
Modifier
.fillMaxSize()
.padding(horizontal = DEFAULT_PADDING)
) {
AppBarTitle(stringResource(R.string.scan_code), false)
Box(
Modifier
.fillMaxWidth()
.aspectRatio(ratio = 1F)
.padding(bottom = DEFAULT_PADDING)
) {
QRCodeScanner { text ->
verifyCode(text) {
if (it) {
close()
} else {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.incorrect_code)
)
}
}
}
}
Text(stringResource(R.string.scan_code_from_contacts_app))
}
}

View File

@@ -1,466 +0,0 @@
package chat.simplex.app.views.chat.group
import InfoRow
import SectionDivider
import SectionItemView
import SectionSpacer
import SectionTextFooter
import SectionView
import android.util.Log
import androidx.activity.compose.BackHandler
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.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
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.TAG
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.chat.*
import chat.simplex.app.views.chatlist.cantInviteIncognitoAlert
import chat.simplex.app.views.chatlist.setGroupMembers
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.usersettings.*
@Composable
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()
if (chat != null && chat.chatInfo is ChatInfo.Group) {
val groupInfo = chat.chatInfo.groupInfo
GroupChatInfoLayout(
chat,
groupInfo,
members = chatModel.groupMembers
.filter { it.memberStatus != GroupMemberStatus.MemLeft && it.memberStatus != GroupMemberStatus.MemRemoved }
.sortedBy { it.displayName.lowercase() },
developerTools,
groupLink,
addMembers = {
withApi {
setGroupMembers(groupInfo, chatModel)
ModalManager.shared.showModalCloseable(true) { close ->
AddGroupMembersView(groupInfo, false, chatModel, close)
}
}
},
showMemberInfo = { member ->
withApi {
val stats = chatModel.controller.apiGroupMemberInfo(groupInfo.groupId, member.groupMemberId)
val (_, code) = if (member.memberActive) {
try {
chatModel.controller.apiGetGroupMemberCode(groupInfo.apiId, member.groupMemberId)
} catch (e: Exception) {
Log.e(TAG, e.stackTraceToString())
member to null
}
} else {
member to null
}
ModalManager.shared.showModalCloseable(true) { closeCurrent ->
remember { derivedStateOf { chatModel.groupMembers.firstOrNull { it.memberId == member.memberId } } }.value?.let { mem ->
GroupMemberInfoView(groupInfo, mem, stats, code, chatModel, closeCurrent) {
closeCurrent()
close()
}
}
}
}
},
editGroupProfile = {
ModalManager.shared.showCustomModal { close -> GroupProfileView(groupInfo, chatModel, close) }
},
addOrEditWelcomeMessage = {
ModalManager.shared.showCustomModal { close -> GroupWelcomeView(chatModel, groupInfo, close) }
},
openPreferences = {
ModalManager.shared.showCustomModal { close ->
GroupPreferencesView(
chatModel,
chat.id,
close
)
}
},
deleteGroup = { deleteGroupDialog(chat.chatInfo, groupInfo, chatModel, close) },
clearChat = { clearChatDialog(chat.chatInfo, chatModel, close) },
leaveGroup = { leaveGroupDialog(groupInfo, chatModel, close) },
manageGroupLink = {
ModalManager.shared.showModal { GroupLinkView(chatModel, groupInfo, groupLink, groupLinkMemberRole, onGroupLinkUpdated) }
}
)
}
}
fun deleteGroupDialog(chatInfo: ChatInfo, groupInfo: GroupInfo, chatModel: ChatModel, close: (() -> Unit)? = null) {
val alertTextKey =
if (groupInfo.membership.memberCurrent) R.string.delete_group_for_all_members_cannot_undo_warning
else R.string.delete_group_for_self_cannot_undo_warning
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.delete_group_question),
text = generalGetString(alertTextKey),
confirmText = generalGetString(R.string.delete_verb),
onConfirm = {
withApi {
val r = chatModel.controller.apiDeleteChat(chatInfo.chatType, chatInfo.apiId)
if (r) {
chatModel.removeChat(chatInfo.id)
chatModel.chatId.value = null
chatModel.controller.ntfManager.cancelNotificationsForChat(chatInfo.id)
close?.invoke()
}
}
}
)
}
fun leaveGroupDialog(groupInfo: GroupInfo, chatModel: ChatModel, close: (() -> Unit)? = null) {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.leave_group_question),
text = generalGetString(R.string.you_will_stop_receiving_messages_from_this_group_chat_history_will_be_preserved),
confirmText = generalGetString(R.string.leave_group_button),
onConfirm = {
withApi {
chatModel.controller.leaveGroup(groupInfo.groupId)
close?.invoke()
}
}
)
}
@Composable
fun GroupChatInfoLayout(
chat: Chat,
groupInfo: GroupInfo,
members: List<GroupMember>,
developerTools: Boolean,
groupLink: String?,
addMembers: () -> Unit,
showMemberInfo: (GroupMember) -> Unit,
editGroupProfile: () -> Unit,
addOrEditWelcomeMessage: () -> Unit,
openPreferences: () -> Unit,
deleteGroup: () -> Unit,
clearChat: () -> Unit,
leaveGroup: () -> Unit,
manageGroupLink: () -> Unit,
) {
Column(
Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.Start
) {
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
GroupChatInfoHeader(chat.chatInfo)
}
SectionSpacer()
SectionView {
if (groupInfo.canEdit) {
SectionItemView(editGroupProfile) { EditGroupProfileButton() }
SectionDivider()
SectionItemView(addOrEditWelcomeMessage) { AddOrEditWelcomeMessage(groupInfo.groupProfile.description) }
SectionDivider()
}
GroupPreferencesButton(openPreferences)
}
SectionTextFooter(stringResource(R.string.only_group_owners_can_change_prefs))
SectionSpacer()
SectionView(title = String.format(generalGetString(R.string.group_info_section_title_num_members), members.count() + 1)) {
if (groupInfo.canAddMembers) {
SectionItemView(manageGroupLink) {
if (groupLink == null) {
CreateGroupLinkButton()
} else {
GroupLinkButton()
}
}
SectionDivider()
val onAddMembersClick = if (chat.chatInfo.incognito) ::cantInviteIncognitoAlert else addMembers
SectionItemView(onAddMembersClick) {
val tint = if (chat.chatInfo.incognito) HighOrLowlight else MaterialTheme.colors.primary
AddMembersButton(tint)
}
SectionDivider()
}
SectionItemView(minHeight = 50.dp) {
MemberRow(groupInfo.membership, user = true)
}
if (members.isNotEmpty()) {
SectionDivider()
}
MembersList(members, showMemberInfo)
}
SectionSpacer()
SectionView {
ClearChatButton(clearChat)
if (groupInfo.canDelete) {
SectionDivider()
SectionItemView(deleteGroup) { DeleteGroupButton() }
}
if (groupInfo.membership.memberCurrent) {
SectionDivider()
SectionItemView(leaveGroup) { LeaveGroupButton() }
}
}
SectionSpacer()
if (developerTools) {
SectionView(title = stringResource(R.string.section_title_for_console)) {
InfoRow(stringResource(R.string.info_row_local_name), groupInfo.localDisplayName)
SectionDivider()
InfoRow(stringResource(R.string.info_row_database_id), groupInfo.apiId.toString())
}
SectionSpacer()
}
}
}
@Composable
private fun GroupChatInfoHeader(cInfo: ChatInfo) {
Column(
Modifier.padding(horizontal = 8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
ChatInfoImage(cInfo, size = 192.dp, iconColor = if (isInDarkTheme()) GroupDark else SettingsSecondaryLight)
Text(
cInfo.displayName, style = MaterialTheme.typography.h1.copy(fontWeight = FontWeight.Normal),
color = MaterialTheme.colors.onBackground,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
if (cInfo.fullName != "" && cInfo.fullName != cInfo.displayName) {
Text(
cInfo.fullName, style = MaterialTheme.typography.h2,
color = MaterialTheme.colors.onBackground,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
}
}
}
@Composable
private fun GroupPreferencesButton(onClick: () -> Unit) {
SettingsActionItem(
Icons.Outlined.ToggleOn,
stringResource(R.string.group_preferences),
click = onClick
)
}
@Composable
private fun AddMembersButton(tint: Color = MaterialTheme.colors.primary) {
Row(
Modifier.fillMaxSize(),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
Icons.Outlined.Add,
stringResource(R.string.button_add_members),
tint = tint
)
Spacer(Modifier.size(8.dp))
Text(stringResource(R.string.button_add_members), color = tint)
}
}
@Composable
private fun MembersList(members: List<GroupMember>, showMemberInfo: (GroupMember) -> Unit) {
Column {
members.forEachIndexed { index, member ->
SectionItemView({ showMemberInfo(member) }, minHeight = 50.dp) {
MemberRow(member)
}
if (index < members.lastIndex) {
SectionDivider()
}
}
}
}
@Composable
private fun MemberRow(member: GroupMember, user: Boolean = false) {
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(
Modifier.weight(1f).padding(end = DEFAULT_PADDING),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
ProfileImage(size = 46.dp, member.image)
Column {
Row(verticalAlignment = Alignment.CenterVertically) {
if (member.verified) {
MemberVerifiedShield()
}
Text(
member.chatViewName, maxLines = 1, overflow = TextOverflow.Ellipsis,
color = if (member.memberIncognito) Indigo else Color.Unspecified
)
}
val s = member.memberStatus.shortText
val statusDescr = if (user) String.format(generalGetString(R.string.group_info_member_you), s) else s
Text(
statusDescr,
color = HighOrLowlight,
fontSize = 12.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
val role = member.memberRole
if (role == GroupMemberRole.Owner || role == GroupMemberRole.Admin) {
Text(role.text, color = HighOrLowlight)
}
}
}
@Composable
private fun MemberVerifiedShield() {
Icon(Icons.Outlined.VerifiedUser, null, Modifier.padding(end = 3.dp).size(16.dp), tint = HighOrLowlight)
}
@Composable
private fun GroupLinkButton() {
Row(
Modifier
.fillMaxSize(),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
Icons.Outlined.Link,
stringResource(R.string.group_link),
tint = HighOrLowlight
)
Spacer(Modifier.size(8.dp))
Text(stringResource(R.string.group_link))
}
}
@Composable
private fun CreateGroupLinkButton() {
Row(
Modifier
.fillMaxSize(),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
Icons.Outlined.AddLink,
stringResource(R.string.create_group_link),
tint = HighOrLowlight
)
Spacer(Modifier.size(8.dp))
Text(stringResource(R.string.create_group_link))
}
}
@Composable
fun EditGroupProfileButton() {
Row(
Modifier
.fillMaxSize(),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
Icons.Outlined.Edit,
stringResource(R.string.button_edit_group_profile),
tint = HighOrLowlight
)
Spacer(Modifier.size(8.dp))
Text(stringResource(R.string.button_edit_group_profile))
}
}
@Composable
private fun AddOrEditWelcomeMessage(welcomeMessage: String?) {
val text = if (welcomeMessage == null) {
stringResource(R.string.button_add_welcome_message)
} else {
stringResource(R.string.button_welcome_message)
}
Row(
Modifier
.fillMaxSize(),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
Icons.Outlined.MapsUgc,
text,
tint = HighOrLowlight
)
Spacer(Modifier.size(8.dp))
Text(text)
}
}
@Composable
private fun LeaveGroupButton() {
Row(
Modifier.fillMaxSize(),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
Icons.Outlined.Logout,
stringResource(R.string.button_leave_group),
tint = Color.Red
)
Spacer(Modifier.size(8.dp))
Text(stringResource(R.string.button_leave_group), color = Color.Red)
}
}
@Composable
private fun DeleteGroupButton() {
Row(
Modifier.fillMaxSize(),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
Icons.Outlined.Delete,
stringResource(R.string.button_delete_group),
tint = Color.Red
)
Spacer(Modifier.size(8.dp))
Text(stringResource(R.string.button_delete_group), color = Color.Red)
}
}
@Preview
@Composable
fun PreviewGroupChatInfoLayout() {
SimpleXTheme {
GroupChatInfoLayout(
chat = Chat(
chatInfo = ChatInfo.Direct.sampleData,
chatItems = arrayListOf()
),
groupInfo = GroupInfo.sampleData,
members = listOf(GroupMember.sampleData, GroupMember.sampleData, GroupMember.sampleData),
developerTools = false,
groupLink = null,
addMembers = {}, showMemberInfo = {}, editGroupProfile = {}, addOrEditWelcomeMessage = {}, openPreferences = {}, deleteGroup = {}, clearChat = {}, leaveGroup = {}, manageGroupLink = {},
)
}
}

View File

@@ -1,360 +0,0 @@
package chat.simplex.app.views.chat.group
import InfoRow
import SectionDivider
import SectionItemView
import SectionSpacer
import SectionView
import androidx.activity.compose.BackHandler
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.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
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 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.helpers.*
import chat.simplex.app.views.usersettings.SettingsActionItem
import kotlinx.datetime.Clock
@Composable
fun GroupMemberInfoView(
groupInfo: GroupInfo,
member: GroupMember,
connStats: ConnectionStats?,
connectionCode: String?,
chatModel: ChatModel,
close: () -> Unit,
closeAll: () -> Unit, // Close all open windows up to ChatView
) {
BackHandler(onBack = close)
val chat = chatModel.chats.firstOrNull { it.id == chatModel.chatId.value }
val developerTools = chatModel.controller.appPrefs.developerTools.get()
if (chat != null) {
val newRole = remember { mutableStateOf(member.memberRole) }
GroupMemberInfoLayout(
groupInfo,
member,
connStats,
newRole,
developerTools,
connectionCode,
getContactChat = { chatModel.getContactChat(it) },
openDirectChat = {
withApi {
val c = chatModel.controller.apiGetChat(ChatType.Direct, it)
if (c != null) {
if (chatModel.getContactChat(it) == null) {
chatModel.addChat(c)
}
chatModel.chatItems.clear()
chatModel.chatItems.addAll(c.chatItems)
chatModel.chatId.value = c.id
closeAll()
}
}
},
removeMember = { removeMemberDialog(groupInfo, member, chatModel, close) },
onRoleSelected = {
if (it == newRole.value) return@GroupMemberInfoLayout
val prevValue = newRole.value
newRole.value = it
updateMemberRoleDialog(it, member, onDismiss = {
newRole.value = prevValue
}) {
withApi {
kotlin.runCatching {
val mem = chatModel.controller.apiMemberRole(groupInfo.groupId, member.groupMemberId, it)
chatModel.upsertGroupMember(groupInfo, mem)
}.onFailure {
newRole.value = prevValue
}
}
}
},
switchMemberAddress = {
switchMemberAddress(chatModel, groupInfo, member)
},
verifyClicked = {
ModalManager.shared.showModalCloseable { close ->
remember { derivedStateOf { chatModel.groupMembers.firstOrNull { it.memberId == member.memberId } } }.value?.let { mem ->
VerifyCodeView(
mem.displayName,
connectionCode,
mem.verified,
verify = { code ->
chatModel.controller.apiVerifyGroupMember(mem.groupId, mem.groupMemberId, code)?.let { r ->
val (verified, existingCode) = r
chatModel.upsertGroupMember(
groupInfo,
mem.copy(
activeConn = mem.activeConn?.copy(
connectionCode = if (verified) SecurityCode(existingCode, Clock.System.now()) else null
)
)
)
r
}
},
close,
)
}
}
}
)
}
}
fun removeMemberDialog(groupInfo: GroupInfo, member: GroupMember, chatModel: ChatModel, close: (() -> Unit)? = null) {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.button_remove_member),
text = generalGetString(R.string.member_will_be_removed_from_group_cannot_be_undone),
confirmText = generalGetString(R.string.remove_member_confirmation),
onConfirm = {
withApi {
val removedMember = chatModel.controller.apiRemoveMember(member.groupId, member.groupMemberId)
if (removedMember != null) {
chatModel.upsertGroupMember(groupInfo, removedMember)
}
close?.invoke()
}
}
)
}
@Composable
fun GroupMemberInfoLayout(
groupInfo: GroupInfo,
member: GroupMember,
connStats: ConnectionStats?,
newRole: MutableState<GroupMemberRole>,
developerTools: Boolean,
connectionCode: String?,
getContactChat: (Long) -> Chat?,
openDirectChat: (Long) -> Unit,
removeMember: () -> Unit,
onRoleSelected: (GroupMemberRole) -> Unit,
switchMemberAddress: () -> Unit,
verifyClicked: () -> Unit,
) {
Column(
Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.Start
) {
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
GroupMemberInfoHeader(member)
}
SectionSpacer()
if (member.memberActive) {
val contactId = member.memberContactId
if (contactId != null) {
SectionView {
val chat = getContactChat(contactId)
if ((chat != null && chat.chatInfo is ChatInfo.Direct && chat.chatInfo.contact.directOrUsed) || groupInfo.fullGroupPreferences.directMessages.on) {
OpenChatButton(onClick = { openDirectChat(contactId) })
if (connectionCode != null) {
SectionDivider()
}
}
if (connectionCode != null) {
VerifyCodeButton(member.verified, verifyClicked)
}
}
SectionSpacer()
}
}
SectionView(title = stringResource(R.string.member_info_section_title_member)) {
InfoRow(stringResource(R.string.info_row_group), groupInfo.displayName)
SectionDivider()
val roles = remember { member.canChangeRoleTo(groupInfo) }
if (roles != null) {
SectionItemView {
RoleSelectionRow(roles, newRole, onRoleSelected)
}
} else {
InfoRow(stringResource(R.string.role_in_group), member.memberRole.text)
}
val conn = member.activeConn
if (conn != null) {
SectionDivider()
val connLevelDesc =
if (conn.connLevel == 0) stringResource(R.string.conn_level_desc_direct)
else String.format(generalGetString(R.string.conn_level_desc_indirect), conn.connLevel)
InfoRow(stringResource(R.string.info_row_connection), connLevelDesc)
}
}
SectionSpacer()
if (connStats != null) {
SectionView(title = stringResource(R.string.conn_stats_section_title_servers)) {
SwitchAddressButton(switchMemberAddress)
SectionDivider()
val rcvServers = connStats.rcvServers
val sndServers = connStats.sndServers
if ((rcvServers != null && rcvServers.isNotEmpty()) || (sndServers != null && sndServers.isNotEmpty())) {
if (rcvServers != null && rcvServers.isNotEmpty()) {
SimplexServers(stringResource(R.string.receiving_via), rcvServers)
if (sndServers != null && sndServers.isNotEmpty()) {
SectionDivider()
SimplexServers(stringResource(R.string.sending_via), sndServers)
}
} else if (sndServers != null && sndServers.isNotEmpty()) {
SimplexServers(stringResource(R.string.sending_via), sndServers)
}
}
}
SectionSpacer()
}
if (member.canBeRemoved(groupInfo)) {
SectionView {
RemoveMemberButton(removeMember)
}
SectionSpacer()
}
if (developerTools) {
SectionView(title = stringResource(R.string.section_title_for_console)) {
InfoRow(stringResource(R.string.info_row_local_name), member.localDisplayName)
SectionDivider()
InfoRow(stringResource(R.string.info_row_database_id), member.groupMemberId.toString())
}
SectionSpacer()
}
}
}
@Composable
fun GroupMemberInfoHeader(member: GroupMember) {
Column(
Modifier.padding(horizontal = 8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
ProfileImage(size = 192.dp, member.image, color = if (isInDarkTheme()) GroupDark else SettingsSecondaryLight)
Row(verticalAlignment = Alignment.CenterVertically) {
if (member.verified) {
Icon(Icons.Outlined.VerifiedUser, null, Modifier.padding(end = 6.dp, top = 4.dp).size(24.dp), tint = HighOrLowlight)
}
Text(
member.displayName, style = MaterialTheme.typography.h1.copy(fontWeight = FontWeight.Normal),
color = MaterialTheme.colors.onBackground,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
if (member.fullName != "" && member.fullName != member.displayName) {
Text(
member.fullName, style = MaterialTheme.typography.h2,
color = MaterialTheme.colors.onBackground,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
}
}
}
@Composable
fun RemoveMemberButton(onClick: () -> Unit) {
SettingsActionItem(
Icons.Outlined.Delete,
stringResource(R.string.button_remove_member),
click = onClick,
textColor = Color.Red,
iconColor = Color.Red,
)
}
@Composable
fun OpenChatButton(onClick: () -> Unit) {
SettingsActionItem(
Icons.Outlined.Message,
stringResource(R.string.button_send_direct_message),
click = onClick,
textColor = MaterialTheme.colors.primary,
iconColor = MaterialTheme.colors.primary,
)
}
@Composable
private fun RoleSelectionRow(
roles: List<GroupMemberRole>,
selectedRole: MutableState<GroupMemberRole>,
onSelected: (GroupMemberRole) -> Unit
) {
Row(
Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
val values = remember { roles.map { it to it.text } }
ExposedDropDownSettingRow(
generalGetString(R.string.change_role),
values,
selectedRole,
icon = null,
enabled = remember { mutableStateOf(true) },
onSelected = onSelected
)
}
}
private fun updateMemberRoleDialog(
newRole: GroupMemberRole,
member: GroupMember,
onDismiss: () -> Unit,
onConfirm: () -> Unit
) {
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.change_member_role_question),
text = if (member.memberCurrent)
String.format(generalGetString(R.string.member_role_will_be_changed_with_notification), newRole.text)
else
String.format(generalGetString(R.string.member_role_will_be_changed_with_invitation), newRole.text),
confirmText = generalGetString(R.string.change_verb),
onDismiss = onDismiss,
onConfirm = onConfirm,
onDismissRequest = onDismiss
)
}
private fun switchMemberAddress(m: ChatModel, groupInfo: GroupInfo, member: GroupMember) = withApi {
m.controller.apiSwitchGroupMember(groupInfo.apiId, member.groupMemberId)
}
@Preview
@Composable
fun PreviewGroupMemberInfoLayout() {
SimpleXTheme {
GroupMemberInfoLayout(
groupInfo = GroupInfo.sampleData,
member = GroupMember.sampleData,
connStats = null,
newRole = remember { mutableStateOf(GroupMemberRole.Member) },
developerTools = false,
connectionCode = "123",
getContactChat = { Chat.sampleData },
openDirectChat = {},
removeMember = {},
onRoleSelected = {},
switchMemberAddress = {},
verifyClicked = {},
)
}
}

View File

@@ -1,94 +0,0 @@
package chat.simplex.app.views.chat.group
import SectionItemView
import SectionSpacer
import SectionView
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
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.helpers.*
@Composable
fun GroupWelcomeView(m: ChatModel, groupInfo: GroupInfo, close: () -> Unit) {
var groupInfo by remember { mutableStateOf(groupInfo) }
val welcomeText = remember { mutableStateOf(groupInfo.groupProfile.description ?: "") }
fun save(afterSave: () -> Unit = {}) {
withApi {
var welcome: String? = welcomeText.value.trim('\n', ' ')
if (welcome?.length == 0) {
welcome = null
}
val groupProfileUpdated = groupInfo.groupProfile.copy(description = welcome)
val res = m.controller.apiUpdateGroup(groupInfo.groupId, groupProfileUpdated)
if (res != null) {
groupInfo = res
m.updateGroup(res)
welcomeText.value = welcome ?: ""
}
afterSave()
}
}
ModalView(
close = {
if (welcomeText.value == groupInfo.groupProfile.description || (welcomeText.value == "" && groupInfo.groupProfile.description == null)) close()
else showUnsavedChangesAlert({ save(close) }, close)
},
background = if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
) {
GroupWelcomeLayout(
welcomeText,
groupInfo,
save = ::save
)
}
}
@Composable
private fun GroupWelcomeLayout(
welcomeText: MutableState<String>,
groupInfo: GroupInfo,
save: () -> Unit,
) {
Column(
Modifier.fillMaxWidth().verticalScroll(rememberScrollState()),
) {
AppBarTitle(stringResource(R.string.group_welcome_title))
val welcomeText = remember { welcomeText }
TextEditor(Modifier.padding(horizontal = DEFAULT_PADDING).height(160.dp), text = welcomeText)
SectionSpacer()
SaveButton(
save = save,
disabled = welcomeText.value == groupInfo.groupProfile.description || (welcomeText.value == "" && groupInfo.groupProfile.description == null)
)
}
}
@Composable
private fun SaveButton(save: () -> Unit, disabled: Boolean) {
SectionView {
SectionItemView(save, disabled = disabled) {
Text(stringResource(R.string.save_and_update_group_profile), color = if (disabled) HighOrLowlight else MaterialTheme.colors.primary)
}
}
}
private fun showUnsavedChangesAlert(save: () -> Unit, revert: () -> Unit) {
AlertManager.shared.showAlertDialogStacked(
title = generalGetString(R.string.save_welcome_message_question),
confirmText = generalGetString(R.string.save_and_update_group_profile),
dismissText = generalGetString(R.string.exit_without_saving),
onConfirm = save,
onDismiss = revert,
)
}

View File

@@ -1,34 +0,0 @@
package chat.simplex.app.views.chat.item
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.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.model.*
@Composable
fun CIChatFeatureView(
chatItem: ChatItem,
feature: Feature,
iconColor: Color,
icon: ImageVector? = null
) {
Row(
Modifier.padding(horizontal = 6.dp, vertical = 6.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
Icon(icon ?: feature.iconFilled, feature.text, Modifier.size(18.dp), tint = iconColor)
Text(
chatEventText(chatItem),
Modifier,
// this is important. Otherwise, aligning will be bad because annotated string has a Span with size 12.sp
fontSize = 12.sp
)
}
}

View File

@@ -1,65 +0,0 @@
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.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.compose.ui.unit.sp
import chat.simplex.app.model.ChatItem
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.ui.theme.SimpleXTheme
@Composable
fun CIEventView(ci: ChatItem) {
@Composable
fun chatEventTextView(text: AnnotatedString) {
Text(text, style = MaterialTheme.typography.body1.copy(lineHeight = 22.sp))
}
Surface {
Row(
Modifier.padding(horizontal = 6.dp, vertical = 6.dp),
verticalAlignment = Alignment.CenterVertically
) {
val memberDisplayName = ci.memberDisplayName
if (memberDisplayName != null) {
chatEventTextView(
buildAnnotatedString {
withStyle(chatEventStyle) { append(memberDisplayName) }
append(" ")
}.plus(chatEventText(ci))
)
} else {
chatEventTextView(chatEventText(ci))
}
}
}
}
val chatEventStyle = SpanStyle(fontSize = 12.sp, fontWeight = FontWeight.Light, color = HighOrLowlight)
fun chatEventText(ci: ChatItem): AnnotatedString =
buildAnnotatedString {
withStyle(chatEventStyle) { append(ci.content.text + " " + ci.timestampText) }
}
@Preview(showBackground = true)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
name = "Dark Mode"
)
@Composable
fun CIEventViewPreview() {
SimpleXTheme {
CIEventView(
ChatItem.getGroupEventSample()
)
}
}

View File

@@ -1,218 +0,0 @@
package chat.simplex.app.views.chat.item
import android.graphics.Bitmap
import android.os.Build.VERSION.SDK_INT
import androidx.annotation.StringRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
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.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.platform.*
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.core.content.FileProvider
import chat.simplex.app.*
import chat.simplex.app.R
import chat.simplex.app.model.*
import chat.simplex.app.views.helpers.*
import coil.ImageLoader
import coil.compose.rememberAsyncImagePainter
import coil.decode.GifDecoder
import coil.decode.ImageDecoderDecoder
import coil.request.ImageRequest
import java.io.File
@Composable
fun CIImageView(
image: String,
file: CIFile?,
imageProvider: () -> ImageGalleryProvider,
showMenu: MutableState<Boolean>,
receiveFile: (Long) -> Unit
) {
@Composable
fun progressIndicator() {
CircularProgressIndicator(
Modifier.size(16.dp),
color = Color.White,
strokeWidth = 2.dp
)
}
@Composable
fun fileIcon(icon: ImageVector, @StringRes stringId: Int) {
Icon(
icon,
stringResource(stringId),
Modifier.fillMaxSize(),
tint = Color.White
)
}
@Composable
fun loadingIndicator() {
if (file != null) {
Box(
Modifier
.padding(8.dp)
.size(20.dp),
contentAlignment = Alignment.Center
) {
when (file.fileStatus) {
is CIFileStatus.SndStored ->
when (file.fileProtocol) {
FileProtocol.XFTP -> progressIndicator()
FileProtocol.SMP -> {}
}
is CIFileStatus.SndTransfer -> progressIndicator()
is CIFileStatus.SndComplete -> fileIcon(Icons.Filled.Check, R.string.icon_descr_image_snd_complete)
is CIFileStatus.SndCancelled -> fileIcon(Icons.Outlined.Close, R.string.icon_descr_file)
is CIFileStatus.SndError -> fileIcon(Icons.Outlined.Close, R.string.icon_descr_file)
is CIFileStatus.RcvInvitation -> fileIcon(Icons.Outlined.ArrowDownward, R.string.icon_descr_asked_to_receive)
is CIFileStatus.RcvAccepted -> fileIcon(Icons.Outlined.MoreHoriz, R.string.icon_descr_waiting_for_image)
is CIFileStatus.RcvTransfer -> progressIndicator()
is CIFileStatus.RcvCancelled -> fileIcon(Icons.Outlined.Close, R.string.icon_descr_file)
is CIFileStatus.RcvError -> fileIcon(Icons.Outlined.Close, R.string.icon_descr_file)
else -> {}
}
}
}
}
@Composable
fun imageViewFullWidth(): Dp {
val approximatePadding = 100.dp
return with(LocalDensity.current) { minOf(1000.dp, LocalView.current.width.toDp() - approximatePadding) }
}
@Composable
fun imageView(imageBitmap: Bitmap, onClick: () -> Unit) {
Image(
imageBitmap.asImageBitmap(),
contentDescription = stringResource(R.string.image_descr),
// .width(1000.dp) is a hack for image to increase IntrinsicSize of FramedItemView
// if text is short and take all available width if text is long
modifier = Modifier
.width(if (imageBitmap.width * 0.97 <= imageBitmap.height) imageViewFullWidth() * 0.75f else 1000.dp)
.combinedClickable(
onLongClick = { showMenu.value = true },
onClick = onClick
),
contentScale = ContentScale.FillWidth,
)
}
@Composable
fun imageView(painter: Painter, onClick: () -> Unit) {
Image(
painter,
contentDescription = stringResource(R.string.image_descr),
// .width(1000.dp) is a hack for image to increase IntrinsicSize of FramedItemView
// if text is short and take all available width if text is long
modifier = Modifier
.width(if (painter.intrinsicSize.width * 0.97 <= painter.intrinsicSize.height) imageViewFullWidth() * 0.75f else 1000.dp)
.combinedClickable(
onLongClick = { showMenu.value = true },
onClick = onClick
),
contentScale = ContentScale.FillWidth,
)
}
fun fileSizeValid(): Boolean {
if (file != null) {
return file.fileSize <= getMaxFileSize(file.fileProtocol)
}
return false
}
fun imageAndFilePath(file: CIFile?): Pair<Bitmap?, String?> {
val imageBitmap: Bitmap? = getLoadedImage(SimplexApp.context, file)
val filePath = getLoadedFilePath(SimplexApp.context, file)
return imageBitmap to filePath
}
Box(
Modifier.layoutId(CHAT_IMAGE_LAYOUT_ID),
contentAlignment = Alignment.TopEnd
) {
val context = LocalContext.current
val (imageBitmap, filePath) = remember(file) { imageAndFilePath(file) }
if (imageBitmap != null && filePath != null) {
val uri = remember(filePath) { FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.provider", File(filePath)) }
val imagePainter = rememberAsyncImagePainter(
ImageRequest.Builder(context).data(data = uri).size(coil.size.Size.ORIGINAL).build(),
placeholder = BitmapPainter(imageBitmap.asImageBitmap()), // show original image while it's still loading by coil
imageLoader = imageLoader
)
val view = LocalView.current
imageView(imagePainter, onClick = {
hideKeyboard(view)
if (getLoadedFilePath(context, file) != null) {
ModalManager.shared.showCustomModal(animated = false) { close ->
ImageFullScreenView(imageProvider, close)
}
}
})
} else {
imageView(base64ToBitmap(image), onClick = {
if (file != null) {
when (file.fileStatus) {
CIFileStatus.RcvInvitation ->
if (fileSizeValid()) {
receiveFile(file.fileId)
} else {
AlertManager.shared.showAlertMsg(
generalGetString(R.string.large_file),
String.format(generalGetString(R.string.contact_sent_large_file), formatBytes(getMaxFileSize(file.fileProtocol)))
)
}
CIFileStatus.RcvAccepted ->
when (file.fileProtocol) {
FileProtocol.XFTP ->
AlertManager.shared.showAlertMsg(
generalGetString(R.string.waiting_for_image),
generalGetString(R.string.image_will_be_received_when_contact_completes_uploading)
)
FileProtocol.SMP ->
AlertManager.shared.showAlertMsg(
generalGetString(R.string.waiting_for_image),
generalGetString(R.string.image_will_be_received_when_contact_is_online)
)
}
CIFileStatus.RcvTransfer(rcvProgress = 7, rcvTotal = 10) -> {} // ?
CIFileStatus.RcvComplete -> {} // ?
CIFileStatus.RcvCancelled -> {} // TODO
else -> {}
}
}
})
}
loadingIndicator()
}
}
private val imageLoader = ImageLoader.Builder(SimplexApp.context)
.components {
if (SDK_INT >= 28) {
add(ImageDecoderDecoder.Factory())
} else {
add(GifDecoder.Factory())
}
}
.build()

View File

@@ -1,46 +0,0 @@
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

@@ -1,24 +0,0 @@
package chat.simplex.app.views.chat.item
import androidx.compose.runtime.Composable
import chat.simplex.app.R
import chat.simplex.app.model.*
import chat.simplex.app.views.helpers.AlertManager
import chat.simplex.app.views.helpers.generalGetString
@Composable
fun CIRcvDecryptionError(msgDecryptError: MsgDecryptError, msgCount: UInt, ci: ChatItem, timedMessagesTTL: Int?, showMember: Boolean) {
CIMsgError(ci, timedMessagesTTL, showMember) {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.decryption_error),
text = when (msgDecryptError) {
MsgDecryptError.RatchetHeader -> String.format(generalGetString(R.string.alert_text_decryption_error_header), msgCount.toLong()) + "\n" +
generalGetString(R.string.alert_text_fragment_encryption_out_of_sync_old_database) + "\n" +
generalGetString(R.string.alert_text_fragment_permanent_error_reconnect)
MsgDecryptError.TooManySkipped -> String.format(generalGetString(R.string.alert_text_decryption_error_too_many_skipped), msgCount.toLong()) + "\n" +
generalGetString(R.string.alert_text_fragment_encryption_out_of_sync_old_database) + "\n" +
generalGetString(R.string.alert_text_fragment_permanent_error_reconnect)
}
)
}
}

View File

@@ -1,438 +0,0 @@
package chat.simplex.app.views.chat.item
import android.Manifest
import android.os.Build
import androidx.compose.foundation.combinedClickable
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.filled.Edit
import androidx.compose.material.icons.outlined.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.*
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
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.chat.ComposeContextItem
import chat.simplex.app.views.chat.ComposeState
import chat.simplex.app.views.helpers.*
import com.google.accompanist.permissions.rememberPermissionState
import kotlinx.datetime.Clock
// TODO refactor so that FramedItemView can show all CIContent items if they're deleted (see Swift code)
@Composable
fun ChatItemView(
cInfo: ChatInfo,
cItem: ChatItem,
composeState: MutableState<ComposeState>,
imageProvider: (() -> ImageGalleryProvider)? = null,
showMember: Boolean = false,
useLinkPreviews: Boolean,
linkMode: SimplexLinkMode,
deleteMessage: (Long, CIDeleteMode) -> Unit,
receiveFile: (Long) -> Unit,
cancelFile: (Long) -> Unit,
joinGroup: (Long) -> Unit,
acceptCall: (Contact) -> Unit,
scrollToItem: (Long) -> Unit,
acceptFeature: (Contact, ChatFeature, Int?) -> Unit
) {
val context = LocalContext.current
val uriHandler = LocalUriHandler.current
val sent = cItem.chatDir.sent
val alignment = if (sent) Alignment.CenterEnd else Alignment.CenterStart
val showMenu = remember { mutableStateOf(false) }
val revealed = remember { mutableStateOf(false) }
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
.padding(bottom = 4.dp)
.fillMaxWidth(),
contentAlignment = alignment,
) {
val onClick = {
when (cItem.meta.itemStatus) {
is CIStatus.SndErrorAuth -> {
showMsgDeliveryErrorAlert(generalGetString(R.string.message_delivery_error_desc))
}
is CIStatus.SndError -> {
showMsgDeliveryErrorAlert(generalGetString(R.string.unknown_error) + ": ${cItem.meta.itemStatus.agentError}")
}
else -> {}
}
}
Column(
Modifier
.clip(RoundedCornerShape(18.dp))
.combinedClickable(onLongClick = { showMenu.value = true }, onClick = onClick),
) {
@Composable
fun framedItemView() {
FramedItemView(cInfo, cItem, uriHandler, imageProvider, showMember = showMember, linkMode = linkMode, showMenu, receiveFile, onLinkLongClick, scrollToItem)
}
fun deleteMessageQuestionText(): String {
return if (fullDeleteAllowed) {
generalGetString(R.string.delete_message_cannot_be_undone_warning)
} else {
generalGetString(R.string.delete_message_mark_deleted_warning)
}
}
fun moderateMessageQuestionText(): String {
return if (fullDeleteAllowed) {
generalGetString(R.string.moderate_message_will_be_deleted_warning)
} else {
generalGetString(R.string.moderate_message_will_be_marked_warning)
}
}
@Composable
fun MsgContentItemDropdownMenu() {
DefaultDropdownMenu(showMenu) {
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)
} else {
composeState.value = composeState.value.copy(contextItem = ComposeContextItem.QuotedItem(cItem))
}
showMenu.value = false
})
}
ItemAction(stringResource(R.string.share_verb), Icons.Outlined.Share, onClick = {
val filePath = getLoadedFilePath(SimplexApp.context, cItem.file)
when {
filePath != null -> shareFile(context, cItem.text, filePath)
else -> shareText(context, cItem.content.text)
}
showMenu.value = false
})
ItemAction(stringResource(R.string.copy_verb), Icons.Outlined.ContentCopy, onClick = {
copyText(context, cItem.content.text)
showMenu.value = false
})
if (cItem.content.msgContent is MsgContent.MCImage || cItem.content.msgContent is MsgContent.MCVideo || cItem.content.msgContent is MsgContent.MCFile || cItem.content.msgContent is MsgContent.MCVoice) {
val filePath = getLoadedFilePath(context, cItem.file)
if (filePath != null) {
val writePermissionState = rememberPermissionState(permission = Manifest.permission.WRITE_EXTERNAL_STORAGE)
ItemAction(stringResource(R.string.save_verb), Icons.Outlined.SaveAlt, onClick = {
when (cItem.content.msgContent) {
is MsgContent.MCImage -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R || writePermissionState.hasPermission) {
saveImage(context, cItem.file)
} else {
writePermissionState.launchPermissionRequest()
}
}
is MsgContent.MCFile, is MsgContent.MCVoice, is MsgContent.MCVideo -> saveFileLauncher.launch(cItem.file?.fileName)
else -> {}
}
showMenu.value = false
})
}
}
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 != null && revealed.value) {
ItemAction(
stringResource(R.string.hide_verb),
Icons.Outlined.VisibilityOff,
onClick = {
revealed.value = false
showMenu.value = false
}
)
}
if (cItem.meta.itemDeleted == null && cItem.file != null && cItem.file.cancelAction != null) {
CancelFileItemAction(cItem.file.fileId, showMenu, cancelFile = cancelFile, cancelAction = cItem.file.cancelAction)
}
if (!(live && cItem.meta.isLive)) {
DeleteItemAction(cItem, showMenu, questionText = deleteMessageQuestionText(), deleteMessage)
}
val groupInfo = cItem.memberToModerate(cInfo)?.first
if (groupInfo != null) {
ModerateItemAction(cItem, questionText = moderateMessageQuestionText(), showMenu, deleteMessage)
}
}
}
@Composable
fun MarkedDeletedItemDropdownMenu() {
DefaultDropdownMenu(showMenu) {
if (!cItem.isDeletedContent) {
ItemAction(
stringResource(R.string.reveal_verb),
Icons.Outlined.Visibility,
onClick = {
revealed.value = true
showMenu.value = false
}
)
}
DeleteItemAction(cItem, showMenu, questionText = deleteMessageQuestionText(), deleteMessage)
}
}
@Composable
fun ContentItem() {
val mc = cItem.content.msgContent
if (cItem.meta.itemDeleted != null && !revealed.value) {
MarkedDeletedItemView(cItem, cInfo.timedMessagesTTL, showMember = showMember)
MarkedDeletedItemDropdownMenu()
} 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()
} else if (mc is MsgContent.MCVoice && cItem.content.text.isEmpty()) {
CIVoiceView(mc.duration, cItem.file, cItem.meta.itemEdited, cItem.chatDir.sent, hasText = false, cItem, cInfo.timedMessagesTTL, longClick = { onLinkLongClick("") })
MsgContentItemDropdownMenu()
} else {
framedItemView()
MsgContentItemDropdownMenu()
}
} else {
framedItemView()
MsgContentItemDropdownMenu()
}
}
@Composable fun DeletedItem() {
DeletedItemView(cItem, cInfo.timedMessagesTTL, showMember = showMember)
DefaultDropdownMenu(showMenu) {
DeleteItemAction(cItem, showMenu, questionText = deleteMessageQuestionText(), deleteMessage)
}
}
@Composable fun CallItem(status: CICallStatus, duration: Int) {
CICallItemView(cInfo, cItem, status, duration, acceptCall)
}
when (val c = cItem.content) {
is CIContent.SndMsgContent -> ContentItem()
is CIContent.RcvMsgContent -> ContentItem()
is CIContent.SndDeleted -> DeletedItem()
is CIContent.RcvDeleted -> DeletedItem()
is CIContent.SndCall -> CallItem(c.status, c.duration)
is CIContent.RcvCall -> CallItem(c.status, c.duration)
is CIContent.RcvIntegrityError -> IntegrityErrorItemView(c.msgError, cItem, cInfo.timedMessagesTTL, showMember = showMember)
is CIContent.RcvDecryptionError -> CIRcvDecryptionError(c.msgDecryptError, c.msgCount, cItem, cInfo.timedMessagesTTL, showMember = showMember)
is CIContent.RcvGroupInvitation -> CIGroupInvitationView(cItem, c.groupInvitation, c.memberRole, joinGroup = joinGroup, chatIncognito = cInfo.incognito)
is CIContent.SndGroupInvitation -> CIGroupInvitationView(cItem, c.groupInvitation, c.memberRole, joinGroup = joinGroup, chatIncognito = cInfo.incognito)
is CIContent.RcvGroupEventContent -> CIEventView(cItem)
is CIContent.SndGroupEventContent -> CIEventView(cItem)
is CIContent.RcvConnEventContent -> CIEventView(cItem)
is CIContent.SndConnEventContent -> CIEventView(cItem)
is CIContent.RcvChatFeature -> CIChatFeatureView(cItem, c.feature, c.enabled.iconColor)
is CIContent.SndChatFeature -> CIChatFeatureView(cItem, c.feature, c.enabled.iconColor)
is CIContent.RcvChatPreference -> {
val ct = if (cInfo is ChatInfo.Direct) cInfo.contact else null
CIFeaturePreferenceView(cItem, ct, c.feature, c.allowed, acceptFeature)
}
is CIContent.SndChatPreference -> CIChatFeatureView(cItem, c.feature, HighOrLowlight, icon = c.feature.icon,)
is CIContent.RcvGroupFeature -> CIChatFeatureView(cItem, c.groupFeature, c.preference.enable.iconColor)
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 -> MarkedDeletedItemView(cItem, cInfo.timedMessagesTTL, showMember = showMember)
is CIContent.RcvModerated -> MarkedDeletedItemView(cItem, cInfo.timedMessagesTTL, showMember = showMember)
is CIContent.InvalidJSON -> CIInvalidJSONView(c.json)
}
}
}
}
@Composable
fun CancelFileItemAction(
fileId: Long,
showMenu: MutableState<Boolean>,
cancelFile: (Long) -> Unit,
cancelAction: CancelAction
) {
ItemAction(
stringResource(cancelAction.uiActionId),
Icons.Outlined.Close,
onClick = {
showMenu.value = false
cancelFileAlertDialog(fileId, cancelFile = cancelFile, cancelAction = cancelAction)
},
color = Color.Red
)
}
@Composable
fun DeleteItemAction(
cItem: ChatItem,
showMenu: MutableState<Boolean>,
questionText: String,
deleteMessage: (Long, CIDeleteMode) -> Unit
) {
ItemAction(
stringResource(R.string.delete_verb),
Icons.Outlined.Delete,
onClick = {
showMenu.value = false
deleteMessageAlertDialog(cItem, questionText, deleteMessage = deleteMessage)
},
color = Color.Red
)
}
@Composable
fun ModerateItemAction(
cItem: ChatItem,
questionText: String,
showMenu: MutableState<Boolean>,
deleteMessage: (Long, CIDeleteMode) -> Unit
) {
ItemAction(
stringResource(R.string.moderate_verb),
Icons.Outlined.Flag,
onClick = {
showMenu.value = false
moderateMessageAlertDialog(cItem, questionText, deleteMessage = deleteMessage)
},
color = Color.Red
)
}
@Composable
fun ItemAction(text: String, icon: ImageVector, onClick: () -> Unit, color: Color = Color.Unspecified) {
val finalColor = if (color == Color.Unspecified) {
if (isInDarkTheme()) MenuTextColorDark else Color.Black
} else color
DropdownMenuItem(onClick, contentPadding = PaddingValues(horizontal = DEFAULT_PADDING * 1.5f)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text,
modifier = Modifier
.fillMaxWidth()
.weight(1F)
.padding(end = 15.dp),
color = finalColor
)
Icon(icon, text, tint = finalColor)
}
}
}
fun cancelFileAlertDialog(fileId: Long, cancelFile: (Long) -> Unit, cancelAction: CancelAction) {
AlertManager.shared.showAlertDialog(
title = generalGetString(cancelAction.alert.titleId),
text = generalGetString(cancelAction.alert.messageId),
confirmText = generalGetString(cancelAction.alert.confirmId),
destructive = true,
onConfirm = {
cancelFile(fileId)
}
)
}
fun deleteMessageAlertDialog(chatItem: ChatItem, questionText: String, deleteMessage: (Long, CIDeleteMode) -> Unit) {
AlertManager.shared.showAlertDialogButtons(
title = generalGetString(R.string.delete_message__question),
text = questionText,
buttons = {
Row(
Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp, vertical = 2.dp),
horizontalArrangement = Arrangement.Center,
) {
TextButton(onClick = {
deleteMessage(chatItem.id, CIDeleteMode.cidmInternal)
AlertManager.shared.hideAlert()
}) { Text(stringResource(R.string.for_me_only)) }
if (chatItem.meta.editable) {
Spacer(Modifier.padding(horizontal = 4.dp))
TextButton(onClick = {
deleteMessage(chatItem.id, CIDeleteMode.cidmBroadcast)
AlertManager.shared.hideAlert()
}) { Text(stringResource(R.string.for_everybody)) }
}
}
}
)
}
fun moderateMessageAlertDialog(chatItem: ChatItem, questionText: String, deleteMessage: (Long, CIDeleteMode) -> Unit) {
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.delete_member_message__question),
text = questionText,
confirmText = generalGetString(R.string.delete_verb),
destructive = true,
onConfirm = {
deleteMessage(chatItem.id, CIDeleteMode.cidmBroadcast)
}
)
}
private fun showMsgDeliveryErrorAlert(description: String) {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.message_delivery_error_title),
text = description,
)
}
@Preview
@Composable
fun PreviewChatItemView() {
SimpleXTheme {
ChatItemView(
ChatInfo.Direct.sampleData,
ChatItem.getSampleData(
1, CIDirection.DirectSnd(), Clock.System.now(), "hello"
),
useLinkPreviews = true,
linkMode = SimplexLinkMode.DESCRIPTION,
composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) },
deleteMessage = { _, _ -> },
receiveFile = {},
cancelFile = {},
joinGroup = {},
acceptCall = { _ -> },
scrollToItem = {},
acceptFeature = { _, _, _ -> }
)
}
}
@Preview
@Composable
fun PreviewChatItemViewDeletedContent() {
SimpleXTheme {
ChatItemView(
ChatInfo.Direct.sampleData,
ChatItem.getDeletedContentSampleData(),
useLinkPreviews = true,
linkMode = SimplexLinkMode.DESCRIPTION,
composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) },
deleteMessage = { _, _ -> },
receiveFile = {},
cancelFile = {},
joinGroup = {},
acceptCall = { _ -> },
scrollToItem = {},
acceptFeature = { _, _, _ -> }
)
}
}

View File

@@ -1,72 +0,0 @@
package chat.simplex.app.views.chat.item
import android.content.res.Configuration
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontStyle
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.CIDeleted
import chat.simplex.app.model.ChatItem
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.helpers.generalGetString
@Composable
fun MarkedDeletedItemView(ci: ChatItem, timedMessagesTTL: Int?, showMember: Boolean = false) {
Surface(
shape = RoundedCornerShape(18.dp),
color = if (ci.chatDir.sent) SentColorLight else ReceivedColorLight,
) {
Row(
Modifier.padding(horizontal = 12.dp, vertical = 6.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(Modifier.weight(1f, false)) {
if (ci.meta.itemDeleted is CIDeleted.Moderated) {
MarkedDeletedText(String.format(generalGetString(R.string.moderated_item_description), ci.meta.itemDeleted.byGroupMember.chatViewName))
} else {
MarkedDeletedText(generalGetString(R.string.marked_deleted_description))
}
}
CIMetaView(ci, timedMessagesTTL)
}
}
}
@Composable
private fun MarkedDeletedText(text: String) {
Text(
buildAnnotatedString {
// appendSender(this, if (showMember) ci.memberDisplayName else null, true) // TODO font size
withStyle(SpanStyle(fontSize = 12.sp, fontStyle = FontStyle.Italic, color = HighOrLowlight)) { append(text) }
},
style = MaterialTheme.typography.body1.copy(lineHeight = 22.sp),
modifier = Modifier.padding(end = 8.dp),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
@Preview(showBackground = true)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
name = "Dark Mode"
)
@Composable
fun PreviewMarkedDeletedItemView() {
SimpleXTheme {
DeletedItemView(
ChatItem.getSampleData(itemDeleted = CIDeleted.Deleted()),
null
)
}
}

View File

@@ -1,104 +0,0 @@
package chat.simplex.app.views.chatlist
import android.content.res.Configuration
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.PersonAdd
import androidx.compose.runtime.Composable
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.SpanStyle
import androidx.compose.ui.text.font.FontWeight
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.ui.theme.SimpleXTheme
import chat.simplex.app.views.helpers.annotatedStringResource
import chat.simplex.app.views.helpers.openUriCatching
import chat.simplex.app.views.usersettings.MarkdownHelpView
import chat.simplex.app.views.usersettings.simplexTeamUri
val bold = SpanStyle(fontWeight = FontWeight.Bold)
@Composable
fun ChatHelpView(addContact: (() -> Unit)? = null) {
Column(
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
val uriHandler = LocalUriHandler.current
Text(stringResource(R.string.thank_you_for_installing_simplex), lineHeight = 22.sp)
Text(
annotatedStringResource(R.string.you_can_connect_to_simplex_chat_founder),
modifier = Modifier.clickable(onClick = {
uriHandler.openUriCatching(simplexTeamUri)
}),
lineHeight = 22.sp
)
Column(
Modifier.padding(top = 24.dp),
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
Text(
stringResource(R.string.to_start_a_new_chat_help_header),
style = MaterialTheme.typography.h2,
lineHeight = 22.sp
)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(stringResource(R.string.chat_help_tap_button))
Icon(
Icons.Outlined.PersonAdd,
stringResource(R.string.add_contact),
modifier = if (addContact != null) Modifier.clickable(onClick = addContact) else Modifier,
)
Text(stringResource(R.string.above_then_preposition_continuation))
}
Text(annotatedStringResource(R.string.add_new_contact_to_create_one_time_QR_code), lineHeight = 22.sp)
Text(annotatedStringResource(R.string.scan_QR_code_to_connect_to_contact_who_shows_QR_code), lineHeight = 22.sp)
}
Column(
Modifier.padding(top = 24.dp),
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
Text(stringResource(R.string.to_connect_via_link_title), style = MaterialTheme.typography.h2)
Text(stringResource(R.string.if_you_received_simplex_invitation_link_you_can_open_in_browser), lineHeight = 22.sp)
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()
}
}
}
@Preview(showBackground = true)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
fun PreviewChatHelpLayout() {
SimpleXTheme {
ChatHelpView {}
}
}

View File

@@ -1,687 +0,0 @@
package chat.simplex.app.views.chatlist
import android.content.res.Configuration
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.*
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.*
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
import kotlinx.coroutines.delay
import kotlinx.datetime.Clock
@Composable
fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) {
val showMenu = remember { mutableStateOf(false) }
val showMarkRead = remember(chat.chatStats.unreadCount, chat.chatStats.unreadChat) {
chat.chatStats.unreadCount > 0 || chat.chatStats.unreadChat
}
val stopped = chatModel.chatRunning.value == false
val linkMode by remember { chatModel.controller.appPrefs.simplexLinkMode.state }
LaunchedEffect(chat.id) {
showMenu.value = false
delay(500L)
}
when (chat.chatInfo) {
is ChatInfo.Direct -> {
val contactNetworkStatus = chatModel.contactNetworkStatus(chat.chatInfo.contact)
ChatListNavLinkLayout(
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.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,
stopped
)
is ChatInfo.ContactRequest ->
ChatListNavLinkLayout(
chatLinkPreview = { ContactRequestView(chatModel.incognito.value, chat.chatInfo) },
click = { contactRequestAlertDialog(chat.chatInfo, chatModel) },
dropdownMenuItems = { ContactRequestMenuItems(chat.chatInfo, chatModel, showMenu) },
showMenu,
stopped
)
is ChatInfo.ContactConnection ->
ChatListNavLinkLayout(
chatLinkPreview = { ContactConnectionView(chat.chatInfo.contactConnection) },
click = {
ModalManager.shared.showModalCloseable(true) { close ->
ContactConnectionInfoView(chatModel, chat.chatInfo.contactConnection.connReqInv, chat.chatInfo.contactConnection, false, close)
}
},
dropdownMenuItems = { ContactConnectionMenuItems(chat.chatInfo, chatModel, showMenu) },
showMenu,
stopped
)
is ChatInfo.InvalidJSON ->
ChatListNavLinkLayout(
chatLinkPreview = {
InvalidDataView()
},
click = {
ModalManager.shared.showModal(true) { InvalidJSONView(chat.chatInfo.json) }
},
dropdownMenuItems = null,
showMenu,
stopped
)
}
}
fun directChatAction(chatInfo: ChatInfo, chatModel: ChatModel) {
if (chatInfo.ready) {
withApi { openChat(chatInfo, chatModel) }
} else {
pendingContactAlertDialog(chatInfo, chatModel)
}
}
fun groupChatAction(groupInfo: GroupInfo, chatModel: ChatModel) {
when (groupInfo.membership.memberStatus) {
GroupMemberStatus.MemInvited -> acceptGroupInvitationAlertDialog(groupInfo, chatModel)
GroupMemberStatus.MemAccepted -> groupInvitationAcceptedAlert()
else -> withApi { openChat(ChatInfo.Group(groupInfo), chatModel) }
}
}
suspend fun openChat(chatInfo: ChatInfo, chatModel: ChatModel) {
val chat = chatModel.controller.apiGetChat(chatInfo.chatType, chatInfo.apiId)
if (chat != null) {
chatModel.chatItems.clear()
chatModel.chatItems.addAll(chat.chatItems)
chatModel.chatId.value = chatInfo.id
}
}
suspend fun apiLoadPrevMessages(chatInfo: ChatInfo, chatModel: ChatModel, beforeChatItemId: Long, search: String) {
val pagination = ChatPagination.Before(beforeChatItemId, ChatPagination.PRELOAD_COUNT)
val chat = chatModel.controller.apiGetChat(chatInfo.chatType, chatInfo.apiId, pagination, search) ?: return
chatModel.chatItems.addAll(0, chat.chatItems)
}
suspend fun apiFindMessages(chatInfo: ChatInfo, chatModel: ChatModel, search: String) {
val chat = chatModel.controller.apiGetChat(chatInfo.chatType, chatInfo.apiId, search = search) ?: return
chatModel.chatItems.clear()
chatModel.chatItems.addAll(0, chat.chatItems)
}
suspend fun setGroupMembers(groupInfo: GroupInfo, chatModel: ChatModel) {
val groupMembers = chatModel.controller.apiListMembers(groupInfo.groupId)
chatModel.groupMembers.clear()
chatModel.groupMembers.addAll(groupMembers)
}
@Composable
fun ContactMenuItems(chat: Chat, chatModel: ChatModel, showMenu: MutableState<Boolean>, showMarkRead: Boolean) {
if (showMarkRead) {
MarkReadChatAction(chat, chatModel, showMenu)
} else {
MarkUnreadChatAction(chat, chatModel, showMenu)
}
ToggleNotificationsChatAction(chat, chatModel, chat.chatInfo.ntfsEnabled, showMenu)
ClearChatAction(chat, chatModel, showMenu)
DeleteContactAction(chat, chatModel, showMenu)
}
@Composable
fun GroupMenuItems(chat: Chat, groupInfo: GroupInfo, chatModel: ChatModel, showMenu: MutableState<Boolean>, showMarkRead: Boolean) {
when (groupInfo.membership.memberStatus) {
GroupMemberStatus.MemInvited -> {
JoinGroupAction(chat, groupInfo, chatModel, showMenu)
if (groupInfo.canDelete) {
DeleteGroupAction(chat, groupInfo, chatModel, showMenu)
}
}
else -> {
if (showMarkRead) {
MarkReadChatAction(chat, chatModel, showMenu)
} else {
MarkUnreadChatAction(chat, chatModel, showMenu)
}
ToggleNotificationsChatAction(chat, chatModel, chat.chatInfo.ntfsEnabled, showMenu)
ClearChatAction(chat, chatModel, showMenu)
if (groupInfo.membership.memberCurrent) {
LeaveGroupAction(groupInfo, chatModel, showMenu)
}
if (groupInfo.canDelete) {
DeleteGroupAction(chat, groupInfo, chatModel, showMenu)
}
}
}
}
@Composable
fun MarkReadChatAction(chat: Chat, chatModel: ChatModel, showMenu: MutableState<Boolean>) {
ItemAction(
stringResource(R.string.mark_read),
Icons.Outlined.Check,
onClick = {
markChatRead(chat, chatModel)
chatModel.controller.ntfManager.cancelNotificationsForChat(chat.id)
showMenu.value = false
}
)
}
@Composable
fun MarkUnreadChatAction(chat: Chat, chatModel: ChatModel, showMenu: MutableState<Boolean>) {
ItemAction(
stringResource(R.string.mark_unread),
Icons.Outlined.MarkChatUnread,
onClick = {
markChatUnread(chat, chatModel)
showMenu.value = false
}
)
}
@Composable
fun ToggleNotificationsChatAction(chat: Chat, chatModel: ChatModel, ntfsEnabled: Boolean, showMenu: MutableState<Boolean>) {
ItemAction(
if (ntfsEnabled) stringResource(R.string.mute_chat) else stringResource(R.string.unmute_chat),
if (ntfsEnabled) Icons.Outlined.NotificationsOff else Icons.Outlined.Notifications,
onClick = {
changeNtfsStatePerChat(!ntfsEnabled, mutableStateOf(ntfsEnabled), chat, chatModel)
showMenu.value = false
}
)
}
@Composable
fun ClearChatAction(chat: Chat, chatModel: ChatModel, showMenu: MutableState<Boolean>) {
ItemAction(
stringResource(R.string.clear_chat_menu_action),
Icons.Outlined.Restore,
onClick = {
clearChatDialog(chat.chatInfo, chatModel)
showMenu.value = false
},
color = WarningOrange
)
}
@Composable
fun DeleteContactAction(chat: Chat, chatModel: ChatModel, showMenu: MutableState<Boolean>) {
ItemAction(
stringResource(R.string.delete_contact_menu_action),
Icons.Outlined.Delete,
onClick = {
deleteContactDialog(chat.chatInfo, chatModel)
showMenu.value = false
},
color = Color.Red
)
}
@Composable
fun DeleteGroupAction(chat: Chat, groupInfo: GroupInfo, chatModel: ChatModel, showMenu: MutableState<Boolean>) {
ItemAction(
stringResource(R.string.delete_group_menu_action),
Icons.Outlined.Delete,
onClick = {
deleteGroupDialog(chat.chatInfo, groupInfo, chatModel)
showMenu.value = false
},
color = Color.Red
)
}
@Composable
fun JoinGroupAction(chat: Chat, groupInfo: GroupInfo, chatModel: ChatModel, showMenu: MutableState<Boolean>) {
val joinGroup: () -> Unit = { withApi { chatModel.controller.apiJoinGroup(groupInfo.groupId) } }
ItemAction(
if (chat.chatInfo.incognito) stringResource(R.string.join_group_incognito_button) else stringResource(R.string.join_group_button),
if (chat.chatInfo.incognito) Icons.Filled.TheaterComedy else Icons.Outlined.Login,
color = if (chat.chatInfo.incognito) Indigo else MaterialTheme.colors.onBackground,
onClick = {
joinGroup()
showMenu.value = false
}
)
}
@Composable
fun LeaveGroupAction(groupInfo: GroupInfo, chatModel: ChatModel, showMenu: MutableState<Boolean>) {
ItemAction(
stringResource(R.string.leave_group_button),
Icons.Outlined.Logout,
onClick = {
leaveGroupDialog(groupInfo, chatModel)
showMenu.value = false
},
color = Color.Red
)
}
@Composable
fun ContactRequestMenuItems(chatInfo: ChatInfo.ContactRequest, chatModel: ChatModel, showMenu: MutableState<Boolean>) {
ItemAction(
if (chatModel.incognito.value) stringResource(R.string.accept_contact_incognito_button) else stringResource(R.string.accept_contact_button),
if (chatModel.incognito.value) Icons.Filled.TheaterComedy else Icons.Outlined.Check,
color = if (chatModel.incognito.value) Indigo else MaterialTheme.colors.onBackground,
onClick = {
acceptContactRequest(chatInfo.apiId, chatInfo, true, chatModel)
showMenu.value = false
}
)
ItemAction(
stringResource(R.string.reject_contact_button),
Icons.Outlined.Close,
onClick = {
rejectContactRequest(chatInfo, chatModel)
showMenu.value = false
},
color = Color.Red
)
}
@Composable
fun ContactConnectionMenuItems(chatInfo: ChatInfo.ContactConnection, chatModel: ChatModel, showMenu: MutableState<Boolean>) {
ItemAction(
stringResource(R.string.set_contact_name),
Icons.Outlined.Edit,
onClick = {
ModalManager.shared.showModalCloseable(true) { close ->
ContactConnectionInfoView(chatModel, chatInfo.contactConnection.connReqInv, chatInfo.contactConnection, true, close)
}
showMenu.value = false
},
)
ItemAction(
stringResource(R.string.delete_verb),
Icons.Outlined.Delete,
onClick = {
deleteContactConnectionAlert(chatInfo.contactConnection, chatModel) {}
showMenu.value = false
},
color = Color.Red
)
}
@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 {
if (chat.chatStats.unreadCount > 0) {
val minUnreadItemId = chat.chatStats.minUnreadItemId
chatModel.markChatItemsRead(chat.chatInfo)
chatModel.controller.apiChatRead(
chat.chatInfo.chatType,
chat.chatInfo.apiId,
CC.ItemRange(minUnreadItemId, chat.chatItems.last().id)
)
chat = chatModel.getChat(chat.id) ?: return@withApi
}
if (chat.chatStats.unreadChat) {
val success = chatModel.controller.apiChatUnread(
chat.chatInfo.chatType,
chat.chatInfo.apiId,
false
)
if (success) {
chatModel.replaceChat(chat.id, chat.copy(chatStats = chat.chatStats.copy(unreadChat = false)))
}
}
}
}
fun markChatUnread(chat: Chat, chatModel: ChatModel) {
// Just to be sure
if (chat.chatStats.unreadChat) return
withApi {
val success = chatModel.controller.apiChatUnread(
chat.chatInfo.chatType,
chat.chatInfo.apiId,
true
)
if (success) {
chatModel.replaceChat(chat.id, chat.copy(chatStats = chat.chatStats.copy(unreadChat = true)))
}
}
}
fun contactRequestAlertDialog(contactRequest: ChatInfo.ContactRequest, chatModel: ChatModel) {
AlertManager.shared.showAlertDialog(
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.apiId, contactRequest, true, chatModel) },
dismissText = generalGetString(R.string.reject_contact_button),
onDismiss = { rejectContactRequest(contactRequest, chatModel) }
)
}
fun acceptContactRequest(apiId: Long, contactRequest: ChatInfo.ContactRequest?, isCurrentUser: Boolean, chatModel: ChatModel) {
withApi {
val contact = chatModel.controller.apiAcceptContactRequest(apiId)
if (contact != null && isCurrentUser && contactRequest != null) {
val chat = Chat(ChatInfo.Direct(contact), listOf())
chatModel.replaceChat(contactRequest.id, chat)
}
}
}
fun rejectContactRequest(contactRequest: ChatInfo.ContactRequest, chatModel: ChatModel) {
withApi {
chatModel.controller.apiRejectContactRequest(contactRequest.apiId)
chatModel.removeChat(contactRequest.id)
}
}
fun contactConnectionAlertDialog(connection: PendingContactConnection, chatModel: ChatModel) {
AlertManager.shared.showAlertDialogButtons(
title = generalGetString(
if (connection.initiated) R.string.you_invited_your_contact
else R.string.you_accepted_connection
),
text = generalGetString(
if (connection.viaContactUri) R.string.you_will_be_connected_when_your_connection_request_is_accepted
else R.string.you_will_be_connected_when_your_contacts_device_is_online
),
buttons = {
Row(
Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp, vertical = 2.dp),
horizontalArrangement = Arrangement.Center,
) {
TextButton(onClick = {
AlertManager.shared.hideAlert()
deleteContactConnectionAlert(connection, chatModel) {}
}) {
Text(stringResource(R.string.delete_verb))
}
Spacer(Modifier.padding(horizontal = 4.dp))
TextButton(onClick = { AlertManager.shared.hideAlert() }) {
Text(stringResource(R.string.ok))
}
}
}
)
}
fun deleteContactConnectionAlert(connection: PendingContactConnection, chatModel: ChatModel, onSuccess: () -> Unit) {
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.delete_pending_connection__question),
text = generalGetString(
if (connection.initiated) R.string.contact_you_shared_link_with_wont_be_able_to_connect
else R.string.connection_you_accepted_will_be_cancelled
),
confirmText = generalGetString(R.string.delete_verb),
onConfirm = {
withApi {
AlertManager.shared.hideAlert()
if (chatModel.controller.apiDeleteChat(ChatType.ContactConnection, connection.apiId)) {
chatModel.removeChat(connection.id)
onSuccess()
}
}
}
)
}
fun pendingContactAlertDialog(chatInfo: ChatInfo, chatModel: ChatModel) {
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.alert_title_contact_connection_pending),
text = generalGetString(R.string.alert_text_connection_pending_they_need_to_be_online_can_delete_and_retry),
confirmText = generalGetString(R.string.button_delete_contact),
onConfirm = {
withApi {
val r = chatModel.controller.apiDeleteChat(chatInfo.chatType, chatInfo.apiId)
if (r) {
chatModel.removeChat(chatInfo.id)
chatModel.chatId.value = null
}
}
},
dismissText = generalGetString(R.string.cancel_verb),
)
}
fun acceptGroupInvitationAlertDialog(groupInfo: GroupInfo, chatModel: ChatModel) {
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.join_group_question),
text = generalGetString(R.string.you_are_invited_to_group_join_to_connect_with_group_members),
confirmText = if (groupInfo.membership.memberIncognito) generalGetString(R.string.join_group_incognito_button) else generalGetString(R.string.join_group_button),
onConfirm = { withApi { chatModel.controller.apiJoinGroup(groupInfo.groupId) } },
dismissText = generalGetString(R.string.delete_verb),
onDismiss = { deleteGroup(groupInfo, chatModel) }
)
}
fun cantInviteIncognitoAlert() {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.alert_title_cant_invite_contacts),
text = generalGetString(R.string.alert_title_cant_invite_contacts_descr),
confirmText = generalGetString(R.string.ok),
)
}
fun deleteGroup(groupInfo: GroupInfo, chatModel: ChatModel) {
withApi {
val r = chatModel.controller.apiDeleteChat(ChatType.Group, groupInfo.apiId)
if (r) {
chatModel.removeChat(groupInfo.id)
chatModel.chatId.value = null
chatModel.controller.ntfManager.cancelNotificationsForChat(groupInfo.id)
}
}
}
fun groupInvitationAcceptedAlert() {
AlertManager.shared.showAlertMsg(
generalGetString(R.string.joining_group),
generalGetString(R.string.youve_accepted_group_invitation_connecting_to_inviting_group_member)
)
}
fun changeNtfsStatePerChat(enabled: Boolean, currentState: MutableState<Boolean>, chat: Chat, chatModel: ChatModel) {
val newChatInfo = when(chat.chatInfo) {
is ChatInfo.Direct -> with (chat.chatInfo) {
ChatInfo.Direct(contact.copy(chatSettings = contact.chatSettings.copy(enableNtfs = enabled)))
}
is ChatInfo.Group -> with(chat.chatInfo) {
ChatInfo.Group(groupInfo.copy(chatSettings = groupInfo.chatSettings.copy(enableNtfs = enabled)))
}
else -> null
}
withApi {
val res = when (newChatInfo) {
is ChatInfo.Direct -> with(newChatInfo) {
chatModel.controller.apiSetSettings(chatType, apiId, contact.chatSettings)
}
is ChatInfo.Group -> with(newChatInfo) {
chatModel.controller.apiSetSettings(chatType, apiId, groupInfo.chatSettings)
}
else -> false
}
if (res && newChatInfo != null) {
chatModel.updateChatInfo(newChatInfo)
if (!enabled) {
chatModel.controller.ntfManager.cancelNotificationsForChat(chat.id)
}
currentState.value = enabled
}
}
}
@Composable
fun ChatListNavLinkLayout(
chatLinkPreview: @Composable () -> Unit,
click: () -> Unit,
dropdownMenuItems: (@Composable () -> Unit)?,
showMenu: MutableState<Boolean>,
stopped: Boolean
) {
var modifier = Modifier.fillMaxWidth()
if (!stopped) modifier = modifier.combinedClickable(onClick = click, onLongClick = { showMenu.value = true })
Box(modifier) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(start = 8.dp, top = 8.dp, end = 12.dp, bottom = 8.dp),
verticalAlignment = Alignment.Top
) {
chatLinkPreview()
}
if (dropdownMenuItems != null) {
DefaultDropdownMenu(showMenu, dropdownMenuItems = dropdownMenuItems)
}
}
Divider(Modifier.padding(horizontal = 8.dp))
}
@Preview
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
fun PreviewChatListNavLinkDirect() {
SimpleXTheme {
ChatListNavLinkLayout(
chatLinkPreview = {
ChatPreviewView(
chat = Chat(
chatInfo = ChatInfo.Direct.sampleData,
chatItems = listOf(
ChatItem.getSampleData(
1,
CIDirection.DirectSnd(),
Clock.System.now(),
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
)
),
chatStats = Chat.ChatStats()
),
null,
null,
false,
null,
null,
stopped = false,
linkMode = SimplexLinkMode.DESCRIPTION
)
},
click = {},
dropdownMenuItems = null,
showMenu = remember { mutableStateOf(false) },
stopped = false
)
}
}
@Preview
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
fun PreviewChatListNavLinkGroup() {
SimpleXTheme {
ChatListNavLinkLayout(
chatLinkPreview = {
ChatPreviewView(
Chat(
chatInfo = ChatInfo.Group.sampleData,
chatItems = listOf(
ChatItem.getSampleData(
1,
CIDirection.DirectSnd(),
Clock.System.now(),
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
)
),
chatStats = Chat.ChatStats()
),
null,
null,
false,
null,
null,
stopped = false,
linkMode = SimplexLinkMode.DESCRIPTION
)
},
click = {},
dropdownMenuItems = null,
showMenu = remember { mutableStateOf(false) },
stopped = false
)
}
}
@Preview
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
fun PreviewChatListNavLinkContactRequest() {
SimpleXTheme {
ChatListNavLinkLayout(
chatLinkPreview = {
ContactRequestView(false, ChatInfo.ContactRequest.sampleData)
},
click = {},
dropdownMenuItems = null,
showMenu = remember { mutableStateOf(false) },
stopped = false
)
}
}

View File

@@ -1,312 +0,0 @@
package chat.simplex.app.views.chatlist
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.*
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
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,
chatModelDraft: ComposeState?,
chatModelDraftChatId: ChatId?,
chatModelIncognito: Boolean,
currentUserProfileDisplayName: String?,
contactNetworkStatus: NetworkStatus?,
stopped: Boolean,
linkMode: SimplexLinkMode
) {
val cInfo = chat.chatInfo
@Composable
fun groupInactiveIcon() {
Icon(
Icons.Filled.Cancel,
stringResource(R.string.icon_descr_group_inactive),
Modifier.size(18.dp).background(MaterialTheme.colors.background, CircleShape),
tint = HighOrLowlight
)
}
@Composable
fun chatPreviewImageOverlayIcon() {
if (cInfo is ChatInfo.Group) {
when (cInfo.groupInfo.membership.memberStatus) {
GroupMemberStatus.MemLeft -> groupInactiveIcon()
GroupMemberStatus.MemRemoved -> groupInactiveIcon()
GroupMemberStatus.MemGroupDeleted -> groupInactiveIcon()
else -> {}
}
}
}
@Composable
fun chatPreviewTitleText(color: Color = Color.Unspecified) {
Text(
cInfo.chatViewName,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.h3,
fontWeight = FontWeight.Bold,
color = color
)
}
@Composable
fun VerifiedIcon() {
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.MediaPreview -> 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) {
is ChatInfo.Direct ->
Row(verticalAlignment = Alignment.CenterVertically) {
if (cInfo.contact.verified) {
VerifiedIcon()
}
chatPreviewTitleText(if (cInfo.ready) Color.Unspecified else HighOrLowlight)
}
is ChatInfo.Group ->
when (cInfo.groupInfo.membership.memberStatus) {
GroupMemberStatus.MemInvited -> chatPreviewTitleText(if (chat.chatInfo.incognito) Indigo else MaterialTheme.colors.primary)
GroupMemberStatus.MemAccepted -> chatPreviewTitleText(HighOrLowlight)
else -> chatPreviewTitleText()
}
else -> chatPreviewTitleText()
}
}
@Composable
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(
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 {
when (cInfo) {
is ChatInfo.Direct ->
if (!cInfo.ready) {
Text(stringResource(R.string.contact_connection_pending), color = HighOrLowlight)
}
is ChatInfo.Group ->
when (cInfo.groupInfo.membership.memberStatus) {
GroupMemberStatus.MemInvited -> Text(groupInvitationPreviewText(chatModelIncognito, currentUserProfileDisplayName, cInfo.groupInfo))
GroupMemberStatus.MemAccepted -> Text(stringResource(R.string.group_connection_pending), color = HighOrLowlight)
else -> {}
}
else -> {}
}
}
}
Row {
Box(contentAlignment = Alignment.BottomEnd) {
ChatInfoImage(cInfo, size = 72.dp)
Box(Modifier.padding(end = 6.dp, bottom = 6.dp)) {
chatPreviewImageOverlayIcon()
}
}
Column(
modifier = Modifier
.padding(horizontal = 8.dp)
.weight(1F)
) {
chatPreviewTitle()
val height = with(LocalDensity.current) { 46.sp.toDp() }
Row(Modifier.heightIn(min = height)) {
chatPreviewText(chatModelIncognito)
}
}
val ts = chat.chatItems.lastOrNull()?.timestampText ?: getTimestampText(chat.chatInfo.updatedAt)
Box(
contentAlignment = Alignment.TopEnd
) {
Text(
ts,
color = HighOrLowlight,
style = MaterialTheme.typography.body2,
modifier = Modifier.padding(bottom = 5.dp)
)
val n = chat.chatStats.unreadCount
val showNtfsIcon = !chat.chatInfo.ntfsEnabled && (chat.chatInfo is ChatInfo.Direct || chat.chatInfo is ChatInfo.Group)
if (n > 0 || chat.chatStats.unreadChat) {
Box(
Modifier.padding(top = 24.dp),
contentAlignment = Alignment.Center
) {
Text(
if (n > 0) unreadCountStr(n) else "",
color = MaterialTheme.colors.onPrimary,
fontSize = 11.sp,
modifier = Modifier
.background(if (stopped || showNtfsIcon) HighOrLowlight else MaterialTheme.colors.primary, shape = CircleShape)
.badgeLayout()
.padding(horizontal = 3.dp)
.padding(vertical = 1.dp)
)
}
} else if (showNtfsIcon) {
Box(
Modifier.padding(top = 24.dp),
contentAlignment = Alignment.Center
) {
Icon(
Icons.Filled.NotificationsOff,
contentDescription = generalGetString(R.string.notifications),
tint = HighOrLowlight,
modifier = Modifier
.padding(horizontal = 3.dp)
.padding(vertical = 1.dp)
.size(17.dp)
)
}
}
if (cInfo is ChatInfo.Direct) {
Box(
Modifier.padding(top = 52.dp),
contentAlignment = Alignment.Center
) {
ChatStatusImage(contactNetworkStatus)
}
}
}
}
}
@Composable
private fun groupInvitationPreviewText(chatModelIncognito: Boolean, currentUserProfileDisplayName: String?, groupInfo: GroupInfo): String {
return if (groupInfo.membership.memberIncognito)
String.format(stringResource(R.string.group_preview_join_as), groupInfo.membership.memberProfile.displayName)
else if (chatModelIncognito)
String.format(stringResource(R.string.group_preview_join_as), currentUserProfileDisplayName ?: "")
else
stringResource(R.string.group_preview_you_are_invited)
}
@Composable
fun unreadCountStr(n: Int): String {
return if (n < 1000) "$n" else "${n / 1000}" + stringResource(R.string.thousand_abbreviation)
}
@Composable
fun ChatStatusImage(s: NetworkStatus?) {
val descr = s?.statusString
if (s is NetworkStatus.Error) {
Icon(
Icons.Outlined.ErrorOutline,
contentDescription = descr,
tint = HighOrLowlight,
modifier = Modifier
.size(19.dp)
)
} else if (s !is NetworkStatus.Connected) {
CircularProgressIndicator(
Modifier
.padding(horizontal = 2.dp)
.size(15.dp),
color = HighOrLowlight,
strokeWidth = 1.5.dp
)
}
}
@Preview(showBackground = true)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
fun PreviewChatPreviewView() {
SimpleXTheme {
ChatPreviewView(Chat.sampleData, null, null, false, "", contactNetworkStatus = NetworkStatus.Connected(), stopped = false, linkMode = SimplexLinkMode.DESCRIPTION)
}
}

View File

@@ -1,142 +0,0 @@
package chat.simplex.app.views.database
import SectionDivider
import SectionTextFooter
import SectionView
import android.content.Context
import android.content.res.Configuration
import android.net.Uri
import android.util.Log
import android.widget.Toast
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
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.Alignment
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.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.TAG
import chat.simplex.app.model.ChatModel
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.usersettings.*
import kotlinx.datetime.*
import java.io.BufferedOutputStream
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
@Composable
fun ChatArchiveView(m: ChatModel, title: String, archiveName: String, archiveTime: Instant) {
val context = LocalContext.current
val archivePath = "${getFilesDirectory(context)}/$archiveName"
val saveArchiveLauncher = rememberSaveArchiveLauncher(cxt = context, archivePath)
ChatArchiveLayout(
title,
archiveTime,
saveArchive = { saveArchiveLauncher.launch(archivePath.substringAfterLast("/")) },
deleteArchiveAlert = { deleteArchiveAlert(m, archivePath) }
)
}
@Composable
fun ChatArchiveLayout(
title: String,
archiveTime: Instant,
saveArchive: () -> Unit,
deleteArchiveAlert: () -> Unit
) {
Column(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.Start,
) {
AppBarTitle(title)
SectionView(stringResource(R.string.chat_archive_section)) {
SettingsActionItem(
Icons.Outlined.IosShare,
stringResource(R.string.save_archive),
saveArchive,
textColor = MaterialTheme.colors.primary,
iconColor = MaterialTheme.colors.primary,
)
SectionDivider()
SettingsActionItem(
Icons.Outlined.Delete,
stringResource(R.string.delete_archive),
deleteArchiveAlert,
textColor = Color.Red,
iconColor = Color.Red,
)
}
val archiveTs = SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.US).format(Date.from(archiveTime.toJavaInstant()))
SectionTextFooter(
String.format(generalGetString(R.string.archive_created_on_ts), archiveTs)
)
}
}
@Composable
private fun rememberSaveArchiveLauncher(cxt: Context, chatArchivePath: String): ManagedActivityResultLauncher<String, Uri?> =
rememberLauncherForActivityResult(
contract = ActivityResultContracts.CreateDocument(),
onResult = { destination ->
try {
destination?.let {
val contentResolver = cxt.contentResolver
contentResolver.openOutputStream(destination)?.let { stream ->
val outputStream = BufferedOutputStream(stream)
File(chatArchivePath).inputStream().use { it.copyTo(outputStream) }
outputStream.close()
Toast.makeText(cxt, generalGetString(R.string.file_saved), Toast.LENGTH_SHORT).show()
}
}
} catch (e: Error) {
Toast.makeText(cxt, generalGetString(R.string.error_saving_file), Toast.LENGTH_SHORT).show()
Log.e(TAG, "rememberSaveArchiveLauncher error saving archive $e")
}
}
)
private fun deleteArchiveAlert(m: ChatModel, archivePath: String) {
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.delete_chat_archive_question),
confirmText = generalGetString(R.string.delete_verb),
onConfirm = {
val fileDeleted = File(archivePath).delete()
if (fileDeleted) {
m.controller.appPrefs.chatArchiveName.set(null)
m.controller.appPrefs.chatArchiveTime.set(null)
ModalManager.shared.closeModal()
} else {
Log.e(TAG, "deleteArchiveAlert delete() error")
}
}
)
}
@Preview(showBackground = true)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
fun PreviewChatArchiveLayout() {
SimpleXTheme {
ChatArchiveLayout(
"New database archive",
archiveTime = Clock.System.now(),
saveArchive = {},
deleteArchiveAlert = {}
)
}
}

View File

@@ -1,762 +0,0 @@
package chat.simplex.app.views.database
import SectionDivider
import SectionTextFooter
import SectionItemView
import SectionSpacer
import SectionView
import android.content.Context
import android.content.res.Configuration
import android.net.Uri
import android.util.Log
import android.widget.Toast
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
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.*
import androidx.compose.ui.Alignment
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
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.usersettings.*
import kotlinx.coroutines.*
import kotlinx.datetime.*
import org.apache.commons.io.IOUtils
import java.io.*
import java.text.SimpleDateFormat
import java.util.*
import kotlin.collections.ArrayList
@Composable
fun DatabaseView(
m: ChatModel,
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit)
) {
val context = LocalContext.current
val progressIndicator = remember { mutableStateOf(false) }
val runChat = remember { m.chatRunning }
val prefs = m.controller.appPrefs
val useKeychain = remember { mutableStateOf(prefs.storeDBPassphrase.get()) }
val chatArchiveName = remember { mutableStateOf(prefs.chatArchiveName.get()) }
val chatArchiveTime = remember { mutableStateOf(prefs.chatArchiveTime.get()) }
val chatLastStart = remember { mutableStateOf(prefs.chatLastStart.get()) }
val chatArchiveFile = remember { mutableStateOf<String?>(null) }
val saveArchiveLauncher = rememberSaveArchiveLauncher(cxt = context, chatArchiveFile)
val appFilesCountAndSize = remember { mutableStateOf(directoryFileCountAndSize(getAppFilesDirectory(context))) }
val importArchiveLauncher = rememberGetContentLauncher { uri: Uri? ->
if (uri != null) {
importArchiveAlert(m, context, uri, appFilesCountAndSize, progressIndicator)
}
}
val chatDbDeleted = remember { m.chatDbDeleted }
LaunchedEffect(m.chatRunning) {
runChat.value = m.chatRunning.value ?: true
}
val chatItemTTL = remember { mutableStateOf(m.chatItemTTL.value) }
Box(
Modifier.fillMaxSize(),
) {
DatabaseLayout(
progressIndicator.value,
runChat.value != false,
m.chatDbChanged.value,
useKeychain.value,
m.chatDbEncrypted.value,
m.controller.appPrefs.initialRandomDBPassphrase,
importArchiveLauncher,
chatArchiveName,
chatArchiveTime,
chatLastStart,
chatDbDeleted.value,
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) },
deleteChatAlert = { deleteChatAlert(m, progressIndicator) },
deleteAppFilesAndMedia = { deleteFilesAndMediaAlert(context, appFilesCountAndSize) },
onChatItemTTLSelected = {
val oldValue = chatItemTTL.value
chatItemTTL.value = it
if (it < oldValue) {
setChatItemTTLAlert(m, chatItemTTL, progressIndicator, appFilesCountAndSize, context)
} else if (it != oldValue) {
setCiTTL(m, chatItemTTL, progressIndicator, appFilesCountAndSize, context)
}
},
showSettingsModal
)
if (progressIndicator.value) {
Box(
Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator(
Modifier
.padding(horizontal = 2.dp)
.size(30.dp),
color = HighOrLowlight,
strokeWidth = 2.5.dp
)
}
}
}
}
@Composable
fun DatabaseLayout(
progressIndicator: Boolean,
runChat: Boolean,
chatDbChanged: Boolean,
useKeyChain: Boolean,
chatDbEncrypted: Boolean?,
initialRandomDBPassphrase: SharedPreference<Boolean>,
importArchiveLauncher: ManagedActivityResultLauncher<String, Uri?>,
chatArchiveName: MutableState<String?>,
chatArchiveTime: MutableState<Instant?>,
chatLastStart: MutableState<Instant?>,
chatDbDeleted: Boolean,
privacyFullBackup: SharedPreference<Boolean>,
appFilesCountAndSize: MutableState<Pair<Int, Long>>,
chatItemTTL: MutableState<ChatItemTTL>,
currentUser: User?,
users: List<UserInfo>,
startChat: () -> Unit,
stopChatAlert: () -> Unit,
exportArchive: () -> Unit,
deleteChatAlert: () -> Unit,
deleteAppFilesAndMedia: () -> Unit,
onChatItemTTLSelected: (ChatItemTTL) -> Unit,
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit)
) {
val stopped = !runChat
val operationsDisabled = !stopped || progressIndicator
Column(
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)
}
SectionSpacer()
SectionView(stringResource(R.string.chat_database_section)) {
val unencrypted = chatDbEncrypted == false
SettingsActionItem(
if (unencrypted) Icons.Outlined.LockOpen else if (useKeyChain) Icons.Filled.VpnKey else Icons.Outlined.Lock,
stringResource(R.string.database_passphrase),
click = showSettingsModal() { DatabaseEncryptionView(it) },
iconColor = if (unencrypted) WarningOrange else HighOrLowlight,
disabled = operationsDisabled
)
SectionDivider()
AppDataBackupPreference(privacyFullBackup, initialRandomDBPassphrase)
SectionDivider()
SettingsActionItem(
Icons.Outlined.IosShare,
stringResource(R.string.export_database),
click = {
if (initialRandomDBPassphrase.get()) {
exportProhibitedAlert()
} else {
exportArchive()
}
},
textColor = MaterialTheme.colors.primary,
iconColor = MaterialTheme.colors.primary,
disabled = operationsDisabled
)
SectionDivider()
SettingsActionItem(
Icons.Outlined.FileDownload,
stringResource(R.string.import_database),
{ importArchiveLauncher.launch("application/zip") },
textColor = Color.Red,
iconColor = Color.Red,
disabled = operationsDisabled
)
SectionDivider()
val chatArchiveNameVal = chatArchiveName.value
val chatArchiveTimeVal = chatArchiveTime.value
val chatLastStartVal = chatLastStart.value
if (chatArchiveNameVal != null && chatArchiveTimeVal != null && chatLastStartVal != null) {
val title = chatArchiveTitle(chatArchiveTimeVal, chatLastStartVal)
SettingsActionItem(
Icons.Outlined.Inventory2,
title,
click = showSettingsModal { ChatArchiveView(it, title, chatArchiveNameVal, chatArchiveTimeVal) },
disabled = operationsDisabled
)
SectionDivider()
}
SettingsActionItem(
Icons.Outlined.DeleteForever,
stringResource(R.string.delete_database),
deleteChatAlert,
textColor = Color.Red,
iconColor = Color.Red,
disabled = operationsDisabled
)
}
SectionTextFooter(
if (stopped) {
stringResource(R.string.you_must_use_the_most_recent_version_of_database)
} else {
stringResource(R.string.stop_chat_to_enable_database_actions)
}
)
SectionSpacer()
SectionView(stringResource(R.string.files_and_media_section).uppercase()) {
val deleteFilesDisabled = operationsDisabled || appFilesCountAndSize.value.first == 0
SectionItemView(
deleteAppFilesAndMedia,
disabled = deleteFilesDisabled
) {
Text(
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
)
}
}
val (count, size) = appFilesCountAndSize.value
SectionTextFooter(
if (count == 0) {
stringResource(R.string.no_received_app_files)
} else {
String.format(stringResource(R.string.total_files_count_and_size), count, formatBytes(size))
}
)
}
}
@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>,
appFilesCountAndSize: MutableState<Pair<Int, Long>>,
context: Context
) {
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.enable_automatic_deletion_question),
text = generalGetString(R.string.enable_automatic_deletion_message),
confirmText = generalGetString(R.string.delete_messages),
onConfirm = { setCiTTL(m, selectedChatItemTTL, progressIndicator, appFilesCountAndSize, context) },
onDismiss = { selectedChatItemTTL.value = m.chatItemTTL.value }
)
}
@Composable
private fun TtlOptions(current: State<ChatItemTTL>, enabled: State<Boolean>, onSelected: (ChatItemTTL) -> Unit) {
val values = remember {
val all: ArrayList<ChatItemTTL> = arrayListOf(ChatItemTTL.None, ChatItemTTL.Month, ChatItemTTL.Week, ChatItemTTL.Day)
if (current.value is ChatItemTTL.Seconds) {
all.add(current.value)
}
all.map {
when (it) {
is ChatItemTTL.None -> it to generalGetString(R.string.chat_item_ttl_none)
is ChatItemTTL.Day -> it to generalGetString(R.string.chat_item_ttl_day)
is ChatItemTTL.Week -> it to generalGetString(R.string.chat_item_ttl_week)
is ChatItemTTL.Month -> it to generalGetString(R.string.chat_item_ttl_month)
is ChatItemTTL.Seconds -> it to String.format(generalGetString(R.string.chat_item_ttl_seconds), it.secs)
}
}
}
ExposedDropDownSettingRow(
generalGetString(R.string.delete_messages_after),
values,
current,
icon = null,
enabled = enabled,
onSelected = onSelected
)
}
@Composable
fun RunChatSetting(
runChat: Boolean,
stopped: Boolean,
chatDbDeleted: Boolean,
startChat: () -> Unit,
stopChatAlert: () -> Unit
) {
SectionItemView() {
Row(verticalAlignment = Alignment.CenterVertically) {
val chatRunningText = if (stopped) stringResource(R.string.chat_is_stopped) else stringResource(R.string.chat_is_running)
Icon(
if (stopped) Icons.Filled.Report else Icons.Filled.PlayArrow,
chatRunningText,
tint = if (stopped) Color.Red else MaterialTheme.colors.primary
)
Spacer(Modifier.padding(horizontal = 4.dp))
Text(
chatRunningText,
Modifier.padding(end = 24.dp)
)
Spacer(Modifier.fillMaxWidth().weight(1f))
Switch(
enabled = !chatDbDeleted,
checked = runChat,
onCheckedChange = { runChatSwitch ->
if (runChatSwitch) {
startChat()
} else {
stopChatAlert()
}
},
colors = SwitchDefaults.colors(
checkedThumbColor = MaterialTheme.colors.primary,
uncheckedThumbColor = HighOrLowlight
),
)
}
}
}
@Composable
fun chatArchiveTitle(chatArchiveTime: Instant, chatLastStart: Instant): String {
return stringResource(if (chatArchiveTime < chatLastStart) R.string.old_database_archive else R.string.new_database_archive)
}
private fun startChat(m: ChatModel, runChat: MutableState<Boolean?>, chatLastStart: MutableState<Instant?>, chatDbChanged: MutableState<Boolean>) {
withApi {
try {
if (chatDbChanged.value) {
SimplexApp.context.initChatController()
chatDbChanged.value = false
}
if (m.chatDbStatus.value !is DBMigrationResult.OK) {
/** Hide current view and show [DatabaseErrorView] */
ModalManager.shared.closeModals()
return@withApi
}
m.controller.apiStartChat()
runChat.value = true
m.chatRunning.value = true
val ts = Clock.System.now()
m.controller.appPrefs.chatLastStart.set(ts)
chatLastStart.value = ts
when (m.controller.appPrefs.notificationsMode.get()) {
NotificationsMode.SERVICE.name -> CoroutineScope(Dispatchers.Default).launch { SimplexService.start(SimplexApp.context) }
NotificationsMode.PERIODIC.name -> SimplexApp.context.schedulePeriodicWakeUp()
}
} catch (e: Error) {
runChat.value = false
AlertManager.shared.showAlertMsg(generalGetString(R.string.error_starting_chat), e.toString())
}
}
}
private fun stopChatAlert(m: ChatModel, runChat: MutableState<Boolean?>, context: Context) {
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.stop_chat_question),
text = generalGetString(R.string.stop_chat_to_export_import_or_delete_chat_database),
confirmText = generalGetString(R.string.stop_chat_confirmation),
onConfirm = { authStopChat(m, runChat, context) },
onDismiss = { runChat.value = true }
)
}
private fun exportProhibitedAlert() {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.set_password_to_export),
text = generalGetString(R.string.set_password_to_export_desc),
)
}
private fun authStopChat(m: ChatModel, runChat: MutableState<Boolean?>, context: Context) {
if (m.controller.appPrefs.performLA.get()) {
authenticate(
generalGetString(R.string.auth_stop_chat),
generalGetString(R.string.auth_log_in_using_credential),
context as FragmentActivity,
completed = { laResult ->
when (laResult) {
LAResult.Success, is LAResult.Unavailable -> {
stopChat(m, runChat, context)
}
is LAResult.Error -> {
runChat.value = true
}
is LAResult.Failed -> {
runChat.value = true
}
}
}
)
} else {
stopChat(m, runChat, context)
}
}
private fun stopChat(m: ChatModel, runChat: MutableState<Boolean?>, context: Context) {
withApi {
try {
m.controller.apiStopChat()
runChat.value = false
m.chatRunning.value = false
SimplexService.safeStopService(context)
MessagesFetcherWorker.cancelAll()
} catch (e: Error) {
runChat.value = true
AlertManager.shared.showAlertMsg(generalGetString(R.string.error_starting_chat), e.toString())
}
}
}
private fun exportArchive(
context: Context,
m: ChatModel,
progressIndicator: MutableState<Boolean>,
chatArchiveName: MutableState<String?>,
chatArchiveTime: MutableState<Instant?>,
chatArchiveFile: MutableState<String?>,
saveArchiveLauncher: ManagedActivityResultLauncher<String, Uri?>
) {
progressIndicator.value = true
withApi {
try {
val archiveFile = exportChatArchive(m, context, chatArchiveName, chatArchiveTime, chatArchiveFile)
chatArchiveFile.value = archiveFile
saveArchiveLauncher.launch(archiveFile.substringAfterLast("/"))
progressIndicator.value = false
} catch (e: Error) {
AlertManager.shared.showAlertMsg(generalGetString(R.string.error_exporting_chat_database), e.toString())
progressIndicator.value = false
}
}
}
private suspend fun exportChatArchive(
m: ChatModel,
context: Context,
chatArchiveName: MutableState<String?>,
chatArchiveTime: MutableState<Instant?>,
chatArchiveFile: MutableState<String?>
): String {
val archiveTime = Clock.System.now()
val ts = SimpleDateFormat("yyyy-MM-dd'T'HHmmss", Locale.US).format(Date.from(archiveTime.toJavaInstant()))
val archiveName = "simplex-chat.$ts.zip"
val archivePath = "${getFilesDirectory(context)}/$archiveName"
val config = ArchiveConfig(archivePath, parentTempDirectory = context.cacheDir.toString())
m.controller.apiExportArchive(config)
deleteOldArchive(m, context)
m.controller.appPrefs.chatArchiveName.set(archiveName)
chatArchiveName.value = archiveName
m.controller.appPrefs.chatArchiveTime.set(archiveTime)
chatArchiveTime.value = archiveTime
chatArchiveFile.value = archivePath
return archivePath
}
private fun deleteOldArchive(m: ChatModel, context: Context) {
val chatArchiveName = m.controller.appPrefs.chatArchiveName.get()
if (chatArchiveName != null) {
val file = File("${getFilesDirectory(context)}/$chatArchiveName")
val fileDeleted = file.delete()
if (fileDeleted) {
m.controller.appPrefs.chatArchiveName.set(null)
m.controller.appPrefs.chatArchiveTime.set(null)
} else {
Log.e(TAG, "deleteOldArchive file.delete() error")
}
}
}
@Composable
private fun rememberSaveArchiveLauncher(cxt: Context, chatArchiveFile: MutableState<String?>): ManagedActivityResultLauncher<String, Uri?> =
rememberLauncherForActivityResult(
contract = ActivityResultContracts.CreateDocument(),
onResult = { destination ->
try {
destination?.let {
val filePath = chatArchiveFile.value
if (filePath != null) {
val contentResolver = cxt.contentResolver
contentResolver.openOutputStream(destination)?.let { stream ->
val outputStream = BufferedOutputStream(stream)
File(filePath).inputStream().use { it.copyTo(outputStream) }
outputStream.close()
Toast.makeText(cxt, generalGetString(R.string.file_saved), Toast.LENGTH_SHORT).show()
}
} else {
Toast.makeText(cxt, generalGetString(R.string.file_not_found), Toast.LENGTH_SHORT).show()
}
}
} catch (e: Error) {
Toast.makeText(cxt, generalGetString(R.string.error_saving_file), Toast.LENGTH_SHORT).show()
Log.e(TAG, "rememberSaveArchiveLauncher error saving archive $e")
} finally {
chatArchiveFile.value = null
}
}
)
private fun importArchiveAlert(
m: ChatModel,
context: Context,
importedArchiveUri: Uri,
appFilesCountAndSize: MutableState<Pair<Int, Long>>,
progressIndicator: MutableState<Boolean>
) {
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.import_database_question),
text = generalGetString(R.string.your_current_chat_database_will_be_deleted_and_replaced_with_the_imported_one),
confirmText = generalGetString(R.string.import_database_confirmation),
onConfirm = { importArchive(m, context, importedArchiveUri, appFilesCountAndSize, progressIndicator) }
)
}
private fun importArchive(
m: ChatModel,
context: Context,
importedArchiveUri: Uri,
appFilesCountAndSize: MutableState<Pair<Int, Long>>,
progressIndicator: MutableState<Boolean>
) {
progressIndicator.value = true
val archivePath = saveArchiveFromUri(context, importedArchiveUri)
if (archivePath != null) {
withApi {
try {
m.controller.apiDeleteStorage()
try {
val config = ArchiveConfig(archivePath, parentTempDirectory = context.cacheDir.toString())
m.controller.apiImportArchive(config)
DatabaseUtils.ksDatabasePassword.remove()
appFilesCountAndSize.value = directoryFileCountAndSize(getAppFilesDirectory(context))
operationEnded(m, progressIndicator) {
AlertManager.shared.showAlertMsg(generalGetString(R.string.chat_database_imported), generalGetString(R.string.restart_the_app_to_use_imported_chat_database))
}
} catch (e: Error) {
operationEnded(m, progressIndicator) {
AlertManager.shared.showAlertMsg(generalGetString(R.string.error_importing_database), e.toString())
}
}
} catch (e: Error) {
operationEnded(m, progressIndicator) {
AlertManager.shared.showAlertMsg(generalGetString(R.string.error_deleting_database), e.toString())
}
} finally {
File(archivePath).delete()
}
}
}
}
private fun saveArchiveFromUri(context: Context, importedArchiveUri: Uri): String? {
return try {
val inputStream = context.contentResolver.openInputStream(importedArchiveUri)
val archiveName = getFileName(context, importedArchiveUri)
if (inputStream != null && archiveName != null) {
val archivePath = "${context.cacheDir}/$archiveName"
val destFile = File(archivePath)
IOUtils.copy(inputStream, FileOutputStream(destFile))
archivePath
} else {
Log.e(TAG, "saveArchiveFromUri null inputStream")
null
}
} catch (e: Exception) {
Log.e(TAG, "saveArchiveFromUri error: ${e.message}")
null
}
}
private fun deleteChatAlert(m: ChatModel, progressIndicator: MutableState<Boolean>) {
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.delete_chat_profile_question),
text = generalGetString(R.string.delete_chat_profile_action_cannot_be_undone_warning),
confirmText = generalGetString(R.string.delete_verb),
onConfirm = { deleteChat(m, progressIndicator) }
)
}
private fun deleteChat(m: ChatModel, progressIndicator: MutableState<Boolean>) {
progressIndicator.value = true
withApi {
try {
m.controller.apiDeleteStorage()
m.chatDbDeleted.value = true
DatabaseUtils.ksDatabasePassword.remove()
m.controller.appPrefs.storeDBPassphrase.set(true)
operationEnded(m, progressIndicator) {
AlertManager.shared.showAlertMsg(generalGetString(R.string.chat_database_deleted), generalGetString(R.string.restart_the_app_to_create_a_new_chat_profile))
}
} catch (e: Error) {
operationEnded(m, progressIndicator) {
AlertManager.shared.showAlertMsg(generalGetString(R.string.error_deleting_database), e.toString())
}
}
}
}
private fun setCiTTL(
m: ChatModel,
chatItemTTL: MutableState<ChatItemTTL>,
progressIndicator: MutableState<Boolean>,
appFilesCountAndSize: MutableState<Pair<Int, Long>>,
context: Context
) {
Log.d(TAG, "DatabaseView setChatItemTTL ${chatItemTTL.value.seconds ?: -1}")
progressIndicator.value = true
withApi {
try {
m.controller.setChatItemTTL(chatItemTTL.value)
// Update model on success
m.chatItemTTL.value = chatItemTTL.value
afterSetCiTTL(m, progressIndicator, appFilesCountAndSize, context)
} catch (e: Exception) {
// Rollback to model's value
chatItemTTL.value = m.chatItemTTL.value
afterSetCiTTL(m, progressIndicator, appFilesCountAndSize, context)
AlertManager.shared.showAlertMsg(generalGetString(R.string.error_changing_message_deletion), e.stackTraceToString())
}
}
}
private fun afterSetCiTTL(
m: ChatModel,
progressIndicator: MutableState<Boolean>,
appFilesCountAndSize: MutableState<Pair<Int, Long>>,
context: Context
) {
progressIndicator.value = false
appFilesCountAndSize.value = directoryFileCountAndSize(getAppFilesDirectory(context))
withApi {
try {
val chats = m.controller.apiGetChats()
m.updateChats(chats)
} catch (e: Exception) {
Log.e(TAG, "apiGetChats error: ${e.message}")
}
}
}
private fun deleteFilesAndMediaAlert(context: Context, appFilesCountAndSize: MutableState<Pair<Int, Long>>) {
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.delete_files_and_media_question),
text = generalGetString(R.string.delete_files_and_media_desc),
confirmText = generalGetString(R.string.delete_verb),
onConfirm = { deleteFiles(appFilesCountAndSize, context) },
destructive = true
)
}
private fun deleteFiles(appFilesCountAndSize: MutableState<Pair<Int, Long>>, context: Context) {
deleteAppFiles(context)
appFilesCountAndSize.value = directoryFileCountAndSize(getAppFilesDirectory(context))
}
private fun operationEnded(m: ChatModel, progressIndicator: MutableState<Boolean>, alert: () -> Unit) {
m.chatDbChanged.value = true
progressIndicator.value = false
alert.invoke()
}
@Preview(showBackground = true)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
fun PreviewDatabaseLayout() {
SimpleXTheme {
DatabaseLayout(
progressIndicator = false,
runChat = true,
chatDbChanged = false,
useKeyChain = false,
chatDbEncrypted = false,
initialRandomDBPassphrase = SharedPreference({ true }, {}),
importArchiveLauncher = rememberGetContentLauncher {},
chatArchiveName = remember { mutableStateOf("dummy_archive") },
chatArchiveTime = remember { mutableStateOf(Clock.System.now()) },
chatLastStart = remember { mutableStateOf(Clock.System.now()) },
chatDbDeleted = false,
privacyFullBackup = SharedPreference({ true }, {}),
appFilesCountAndSize = remember { mutableStateOf(0 to 0L) },
chatItemTTL = remember { mutableStateOf(ChatItemTTL.None) },
currentUser = User.sampleData,
users = listOf(UserInfo.sampleData),
startChat = {},
stopChatAlert = {},
exportArchive = {},
deleteChatAlert = {},
deleteAppFilesAndMedia = {},
showSettingsModal = { {} },
onChatItemTTLSelected = {},
)
}
}

View File

@@ -1,80 +0,0 @@
package chat.simplex.app.views.helpers
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.model.ChatInfo
import chat.simplex.app.ui.theme.SimpleXTheme
@Composable
fun ChatInfoImage(chatInfo: ChatInfo, size: Dp, iconColor: Color = MaterialTheme.colors.secondary) {
val icon =
if (chatInfo is ChatInfo.Group) Icons.Filled.SupervisedUserCircle
else Icons.Filled.AccountCircle
ProfileImage(size, chatInfo.image, icon, iconColor)
}
@Composable
fun IncognitoImage(size: Dp, iconColor: Color = MaterialTheme.colors.secondary) {
Box(Modifier.size(size)) {
Icon(
Icons.Filled.TheaterComedy, stringResource(R.string.incognito),
modifier = Modifier.size(size).padding(size / 12),
iconColor
)
}
}
@Composable
fun ProfileImage(
size: Dp,
image: String? = null,
icon: ImageVector = Icons.Filled.AccountCircle,
color: Color = MaterialTheme.colors.secondary
) {
Box(Modifier.size(size)) {
if (image == null) {
Icon(
icon,
contentDescription = stringResource(R.string.icon_descr_profile_image_placeholder),
tint = color,
modifier = Modifier.fillMaxSize()
)
} else {
val imageBitmap = base64ToBitmap(image).asImageBitmap()
Image(
imageBitmap,
stringResource(R.string.image_descr_profile_image),
contentScale = ContentScale.Crop,
modifier = Modifier.size(size).padding(size / 12).clip(CircleShape)
)
}
}
}
@Preview
@Composable
fun PreviewChatInfoImage() {
SimpleXTheme {
ChatInfoImage(
chatInfo = ChatInfo.Direct.sampleData,
size = 55.dp
)
}
}

View File

@@ -1,54 +0,0 @@
package chat.simplex.app.views.helpers
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.views.newchat.ActionButton
sealed class AttachmentOption {
object TakePhoto: AttachmentOption()
object PickMedia: AttachmentOption()
object PickFile: AttachmentOption()
}
@Composable
fun ChooseAttachmentView(
attachmentOption: MutableState<AttachmentOption?>,
hide: () -> Unit
) {
Box(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.onFocusChanged { focusState ->
if (!focusState.hasFocus) hide()
}
) {
Row(
Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp, vertical = 30.dp),
horizontalArrangement = Arrangement.SpaceEvenly
) {
ActionButton(null, stringResource(R.string.use_camera_button), icon = Icons.Outlined.PhotoCamera) {
attachmentOption.value = AttachmentOption.TakePhoto
hide()
}
ActionButton(null, stringResource(R.string.from_gallery_button), icon = Icons.Outlined.Collections) {
attachmentOption.value = AttachmentOption.PickMedia
hide()
}
ActionButton(null, stringResource(R.string.choose_file), icon = Icons.Outlined.InsertDriveFile) {
attachmentOption.value = AttachmentOption.PickFile
hide()
}
}
}
}

View File

@@ -1,83 +0,0 @@
package chat.simplex.app.views.helpers
import android.content.res.Configuration
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.res.stringResource
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.ui.theme.*
@Composable
fun CloseSheetBar(close: (() -> Unit)?, endButtons: @Composable RowScope.() -> Unit = {}) {
Column(
Modifier
.fillMaxWidth()
.heightIn(min = AppBarHeight)
.padding(horizontal = AppBarHorizontalPadding),
) {
Row(
Modifier
.padding(top = 4.dp), // Like in DefaultAppBar
content = {
Row(
Modifier.fillMaxWidth().height(TextFieldDefaults.MinHeight),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
NavigationButtonBack(onButtonClicked = close)
Row {
endButtons()
}
}
}
)
}
}
@Composable
fun AppBarTitle(title: String, withPadding: Boolean = true) {
val padding = if (withPadding)
PaddingValues(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING)
else
PaddingValues(bottom = DEFAULT_PADDING)
Text(
title,
Modifier
.fillMaxWidth()
.padding(padding),
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.h1
)
}
@Composable
fun ColumnScope.AppBarTitleCentered(title: String, withPadding: Boolean = true) {
Text(
title,
Modifier
.padding(bottom = if (withPadding) DEFAULT_PADDING * 1.5f else 0.dp)
.align(Alignment.CenterHorizontally),
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.h1,
color = MaterialTheme.colors.primary
)
}
@Preview(showBackground = true)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
fun PreviewCloseSheetBar() {
SimpleXTheme {
CloseSheetBar(close = {})
}
}

View File

@@ -1,140 +0,0 @@
package chat.simplex.app.views.helpers
import android.os.Build.VERSION.SDK_INT
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricManager.Authenticators.*
import androidx.biometric.BiometricPrompt
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.ui.Modifier
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import chat.simplex.app.R
import chat.simplex.app.SimplexApp
import chat.simplex.app.views.helpers.DatabaseUtils.ksAppPassword
import chat.simplex.app.views.localauth.LocalAuthView
import chat.simplex.app.views.usersettings.LAMode
sealed class LAResult {
object Success: LAResult()
class Error(val errString: CharSequence): LAResult()
class Failed(val errString: CharSequence? = null): LAResult()
class Unavailable(val errString: CharSequence? = null): LAResult()
}
data class LocalAuthRequest (
val title: String?,
val reason: String,
val password: String,
val completed: (LAResult) -> Unit
) {
companion object {
val sample = LocalAuthRequest(generalGetString(R.string.la_enter_app_passcode), generalGetString(R.string.la_authenticate), "") { }
}
}
fun authenticate(
promptTitle: String,
promptSubtitle: String,
activity: FragmentActivity,
usingLAMode: LAMode = SimplexApp.context.chatModel.controller.appPrefs.laMode.get(),
completed: (LAResult) -> Unit
) {
when (usingLAMode) {
LAMode.SYSTEM -> when {
SDK_INT in 28..29 ->
// KeyguardManager.isDeviceSecure()? https://developer.android.com/training/sign-in/biometric-auth#declare-supported-authentication-types
authenticateWithBiometricManager(promptTitle, promptSubtitle, activity, completed, BIOMETRIC_WEAK or DEVICE_CREDENTIAL)
SDK_INT > 29 ->
authenticateWithBiometricManager(promptTitle, promptSubtitle, activity, completed, BIOMETRIC_STRONG or DEVICE_CREDENTIAL)
else -> completed(LAResult.Unavailable())
}
LAMode.PASSCODE -> {
val password = ksAppPassword.get() ?: return completed(LAResult.Unavailable(generalGetString(R.string.la_no_app_password)))
ModalManager.shared.showCustomModal(animated = false) { close ->
Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
LocalAuthView(SimplexApp.context.chatModel, LocalAuthRequest(promptTitle, promptSubtitle, password) {
close()
completed(it)
})
}
}
}
}
}
private fun authenticateWithBiometricManager(
promptTitle: String,
promptSubtitle: String,
activity: FragmentActivity,
completed: (LAResult) -> Unit,
authenticators: Int
) {
val biometricManager = BiometricManager.from(activity)
when (biometricManager.canAuthenticate(authenticators)) {
BiometricManager.BIOMETRIC_SUCCESS -> {
val executor = ContextCompat.getMainExecutor(activity)
val biometricPrompt = BiometricPrompt(
activity,
executor,
object: BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(
errorCode: Int,
errString: CharSequence
) {
super.onAuthenticationError(errorCode, errString)
completed(LAResult.Error(errString))
}
override fun onAuthenticationSucceeded(
result: BiometricPrompt.AuthenticationResult
) {
super.onAuthenticationSucceeded(result)
completed(LAResult.Success)
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
completed(LAResult.Failed())
}
}
)
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle(promptTitle)
.setSubtitle(promptSubtitle)
.setAllowedAuthenticators(authenticators)
.setConfirmationRequired(false)
.build()
biometricPrompt.authenticate(promptInfo)
}
else -> completed(LAResult.Unavailable())
}
}
fun laTurnedOnAlert() = AlertManager.shared.showAlertMsg(
generalGetString(R.string.auth_simplex_lock_turned_on),
generalGetString(R.string.auth_you_will_be_required_to_authenticate_when_you_start_or_resume)
)
fun laPasscodeNotSetAlert() = AlertManager.shared.showAlertMsg(
generalGetString(R.string.lock_not_enabled),
generalGetString(R.string.you_can_turn_on_lock)
)
fun laFailedAlert() {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.la_auth_failed),
text = generalGetString(R.string.la_could_not_be_verified)
)
}
fun laUnavailableInstructionAlert() = AlertManager.shared.showAlertMsg(
generalGetString(R.string.auth_unavailable),
generalGetString(R.string.auth_device_authentication_is_not_enabled_you_can_turn_on_in_settings_once_enabled)
)
fun laUnavailableTurningOffAlert() = AlertManager.shared.showAlertMsg(
generalGetString(R.string.auth_unavailable),
generalGetString(R.string.auth_device_authentication_is_disabled_turning_off)
)

View File

@@ -1,107 +0,0 @@
package chat.simplex.app.views.helpers
import android.Manifest
import android.content.*
import android.net.Uri
import android.provider.MediaStore
import android.webkit.MimeTypeMap
import android.widget.Toast
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.Composable
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import chat.simplex.app.*
import chat.simplex.app.model.CIFile
import java.io.BufferedOutputStream
import java.io.File
fun shareText(cxt: Context, text: String) {
val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, text)
type = "text/plain"
}
val shareIntent = Intent.createChooser(sendIntent, null)
cxt.startActivity(shareIntent)
}
fun shareFile(cxt: Context, text: String, filePath: String) {
val uri = FileProvider.getUriForFile(SimplexApp.context, "${BuildConfig.APPLICATION_ID}.provider", File(filePath))
val ext = filePath.substringAfterLast(".")
val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext) ?: return
val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
/*if (text.isNotEmpty()) {
putExtra(Intent.EXTRA_TEXT, text)
}*/
putExtra(Intent.EXTRA_STREAM, uri)
type = mimeType
}
val shareIntent = Intent.createChooser(sendIntent, null)
cxt.startActivity(shareIntent)
}
fun copyText(cxt: Context, text: String) {
val clipboard = ContextCompat.getSystemService(cxt, ClipboardManager::class.java)
clipboard?.setPrimaryClip(ClipData.newPlainText("text", text))
}
@Composable
fun rememberSaveFileLauncher(cxt: Context, ciFile: CIFile?): ManagedActivityResultLauncher<String, Uri?> =
rememberLauncherForActivityResult(
contract = ActivityResultContracts.CreateDocument(),
onResult = { destination ->
destination?.let {
val filePath = getLoadedFilePath(cxt, ciFile)
if (filePath != null) {
val contentResolver = cxt.contentResolver
contentResolver.openOutputStream(destination)?.let { stream ->
val outputStream = BufferedOutputStream(stream)
File(filePath).inputStream().use { it.copyTo(outputStream) }
outputStream.close()
Toast.makeText(cxt, generalGetString(R.string.file_saved), Toast.LENGTH_SHORT).show()
}
} else {
Toast.makeText(cxt, generalGetString(R.string.file_not_found), Toast.LENGTH_SHORT).show()
}
}
}
)
fun imageMimeType(fileName: String): String {
val lowercaseName = fileName.lowercase()
return when {
lowercaseName.endsWith(".png") -> "image/png"
lowercaseName.endsWith(".gif") -> "image/gif"
lowercaseName.endsWith(".webp") -> "image/webp"
lowercaseName.endsWith(".avif") -> "image/avif"
lowercaseName.endsWith(".svg") -> "image/svg+xml"
else -> "image/jpeg"
}
}
/** Before calling, make sure the user allows to write to external storage [Manifest.permission.WRITE_EXTERNAL_STORAGE] */
fun saveImage(cxt: Context, ciFile: CIFile?) {
val filePath = getLoadedFilePath(cxt, ciFile)
val fileName = ciFile?.fileName
if (filePath != null && fileName != null) {
val values = ContentValues()
values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis())
values.put(MediaStore.Images.Media.MIME_TYPE, imageMimeType(fileName))
values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
values.put(MediaStore.MediaColumns.TITLE, fileName)
val uri = cxt.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
uri?.let {
cxt.contentResolver.openOutputStream(uri)?.let { stream ->
val outputStream = BufferedOutputStream(stream)
File(filePath).inputStream().use { it.copyTo(outputStream) }
outputStream.close()
Toast.makeText(cxt, generalGetString(R.string.image_saved), Toast.LENGTH_SHORT).show()
}
}
} else {
Toast.makeText(cxt, generalGetString(R.string.file_not_found), Toast.LENGTH_SHORT).show()
}
}

View File

@@ -1,65 +0,0 @@
package chat.simplex.app.views.helpers
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.*
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.unit.*
import chat.simplex.app.ui.theme.DEFAULT_PADDING
import chat.simplex.app.ui.theme.HighOrLowlight
@Composable
fun TextEditor(
modifier: Modifier,
text: MutableState<String>,
border: Boolean = true,
fontSize: TextUnit = 14.sp,
background: Color = MaterialTheme.colors.background,
onChange: ((String) -> Unit)? = null
) {
BasicTextField(
value = text.value,
onValueChange = { text.value = it; onChange?.invoke(it) },
textStyle = TextStyle(
fontFamily = FontFamily.Monospace, fontSize = fontSize,
color = MaterialTheme.colors.onBackground
),
keyboardOptions = KeyboardOptions.Default.copy(
capitalization = KeyboardCapitalization.None,
autoCorrect = false
),
modifier = modifier,
cursorBrush = SolidColor(HighOrLowlight),
decorationBox = { innerTextField ->
Surface(
shape = if (border) RoundedCornerShape(10.dp) else RectangleShape,
border = if (border) BorderStroke(1.dp, MaterialTheme.colors.secondary) else null
) {
Row(
Modifier.background(background),
verticalAlignment = Alignment.Top
) {
Box(
Modifier
.weight(1f)
.padding(vertical = 5.dp, horizontal = if (border) 7.dp else DEFAULT_PADDING)
) {
innerTextField()
}
}
}
}
)
}

View File

@@ -1,659 +0,0 @@
package chat.simplex.app.views.helpers
import android.app.Activity
import android.app.Application
//import android.app.LocaleManager
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.res.Configuration
import android.content.res.Resources
import android.graphics.*
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.*
import android.provider.OpenableColumns
import android.text.Spanned
import android.text.SpannedString
import android.text.style.*
import android.util.Base64
import android.util.Log
import android.view.View
import android.view.ViewTreeObserver
import android.view.inputmethod.InputMethodManager
import androidx.annotation.StringRes
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.Saver
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.*
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.*
import androidx.compose.ui.text.style.BaselineShift
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.*
import androidx.core.content.FileProvider
import androidx.core.text.HtmlCompat
import chat.simplex.app.*
import chat.simplex.app.R
import chat.simplex.app.model.*
import kotlinx.coroutines.*
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import org.apache.commons.io.IOUtils
import java.io.*
import java.text.SimpleDateFormat
import java.util.*
import kotlin.math.*
fun withApi(action: suspend CoroutineScope.() -> Unit): Job = withScope(GlobalScope, action)
fun withScope(scope: CoroutineScope, action: suspend CoroutineScope.() -> Unit): Job =
scope.launch { withContext(Dispatchers.Main, action) }
fun withBGApi(action: suspend CoroutineScope.() -> Unit): Job =
CoroutineScope(Dispatchers.Default).launch(block = action)
enum class KeyboardState {
Opened, Closed
}
@Composable
fun getKeyboardState(): State<KeyboardState> {
val keyboardState = remember { mutableStateOf(KeyboardState.Closed) }
val view = LocalView.current
DisposableEffect(view) {
val onGlobalListener = ViewTreeObserver.OnGlobalLayoutListener {
val rect = Rect()
view.getWindowVisibleDisplayFrame(rect)
val screenHeight = view.rootView.height
val keypadHeight = screenHeight - rect.bottom
keyboardState.value = if (keypadHeight > screenHeight * 0.15) {
KeyboardState.Opened
} else {
KeyboardState.Closed
}
}
view.viewTreeObserver.addOnGlobalLayoutListener(onGlobalListener)
onDispose {
view.viewTreeObserver.removeOnGlobalLayoutListener(onGlobalListener)
}
}
return keyboardState
}
fun hideKeyboard(view: View) =
(SimplexApp.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).hideSoftInputFromWindow(view.windowToken, 0)
// Resource to annotated string from
// https://stackoverflow.com/questions/68549248/android-jetpack-compose-how-to-show-styled-text-from-string-resources
fun generalGetString(id: Int): String {
// prefer stringResource in Composable items to retain preview abilities
return SimplexApp.context.getString(id)
}
@Composable
@ReadOnlyComposable
private fun resources(): Resources {
LocalConfiguration.current
return LocalContext.current.resources
}
fun Spanned.toHtmlWithoutParagraphs(): String {
return HtmlCompat.toHtml(this, HtmlCompat.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE)
.substringAfter("<p dir=\"ltr\">").substringBeforeLast("</p>")
}
fun Resources.getText(@StringRes id: Int, vararg args: Any): CharSequence {
val escapedArgs = args.map {
if (it is Spanned) it.toHtmlWithoutParagraphs() else it
}.toTypedArray()
val resource = SpannedString(getText(id))
val htmlResource = resource.toHtmlWithoutParagraphs()
val formattedHtml = String.format(htmlResource, *escapedArgs)
return HtmlCompat.fromHtml(formattedHtml, HtmlCompat.FROM_HTML_MODE_LEGACY)
}
@Composable
fun annotatedStringResource(@StringRes id: Int): AnnotatedString {
val resources = resources()
val density = LocalDensity.current
return remember(id) {
val text = resources.getText(id)
spannableStringToAnnotatedString(text, density)
}
}
private fun spannableStringToAnnotatedString(
text: CharSequence,
density: Density,
): AnnotatedString {
return if (text is Spanned) {
with(density) {
buildAnnotatedString {
append((text.toString()))
text.getSpans(0, text.length, Any::class.java).forEach {
val start = text.getSpanStart(it)
val end = text.getSpanEnd(it)
when (it) {
is StyleSpan -> when (it.style) {
Typeface.NORMAL -> addStyle(
SpanStyle(
fontWeight = FontWeight.Normal,
fontStyle = FontStyle.Normal,
),
start,
end
)
Typeface.BOLD -> addStyle(
SpanStyle(
fontWeight = FontWeight.Bold,
fontStyle = FontStyle.Normal
),
start,
end
)
Typeface.ITALIC -> addStyle(
SpanStyle(
fontWeight = FontWeight.Normal,
fontStyle = FontStyle.Italic
),
start,
end
)
Typeface.BOLD_ITALIC -> addStyle(
SpanStyle(
fontWeight = FontWeight.Bold,
fontStyle = FontStyle.Italic
),
start,
end
)
}
is TypefaceSpan -> addStyle(
SpanStyle(
fontFamily = when (it.family) {
FontFamily.SansSerif.name -> FontFamily.SansSerif
FontFamily.Serif.name -> FontFamily.Serif
FontFamily.Monospace.name -> FontFamily.Monospace
FontFamily.Cursive.name -> FontFamily.Cursive
else -> FontFamily.Default
}
),
start,
end
)
is AbsoluteSizeSpan -> addStyle(
SpanStyle(fontSize = if (it.dip) it.size.dp.toSp() else it.size.toSp()),
start,
end
)
is RelativeSizeSpan -> addStyle(
SpanStyle(fontSize = it.sizeChange.em),
start,
end
)
is StrikethroughSpan -> addStyle(
SpanStyle(textDecoration = TextDecoration.LineThrough),
start,
end
)
is UnderlineSpan -> addStyle(
SpanStyle(textDecoration = TextDecoration.Underline),
start,
end
)
is SuperscriptSpan -> addStyle(
SpanStyle(baselineShift = BaselineShift.Superscript),
start,
end
)
is SubscriptSpan -> addStyle(
SpanStyle(baselineShift = BaselineShift.Subscript),
start,
end
)
is ForegroundColorSpan -> addStyle(
SpanStyle(color = Color(it.foregroundColor)),
start,
end
)
else -> addStyle(SpanStyle(color = Color.White), start, end)
}
}
}
}
} else {
AnnotatedString(text.toString())
}
}
// maximum image file size to be auto-accepted
const val MAX_IMAGE_SIZE: Long = 236700
const val MAX_IMAGE_SIZE_AUTO_RCV: Long = MAX_IMAGE_SIZE * 2
const val MAX_VOICE_SIZE_AUTO_RCV: Long = MAX_IMAGE_SIZE
const val MAX_VOICE_SIZE_FOR_SENDING: Long = 94680 // 6 chunks * 15780 bytes per chunk
const val MAX_VOICE_MILLIS_FOR_SENDING: Int = 43_000
const val MAX_FILE_SIZE_SMP: Long = 8000000
const val MAX_FILE_SIZE_XFTP: Long = 1_073_741_824
fun getFilesDirectory(context: Context): String {
return context.filesDir.toString()
}
fun getTempFilesDirectory(context: Context): String {
return "${getFilesDirectory(context)}/temp_files"
}
fun getAppFilesDirectory(context: Context): String {
return "${getFilesDirectory(context)}/app_files"
}
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)
if (File(filePath).exists()) filePath else null
} else {
null
}
}
// https://developer.android.com/training/data-storage/shared/documents-files#bitmap
fun getLoadedImage(context: Context, file: CIFile?): Bitmap? {
val filePath = getLoadedFilePath(context, file)
return if (filePath != null) {
try {
val uri = FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.provider", File(filePath))
val parcelFileDescriptor = context.contentResolver.openFileDescriptor(uri, "r")
val fileDescriptor = parcelFileDescriptor?.fileDescriptor
val image = decodeSampledBitmapFromFileDescriptor(fileDescriptor, 1000, 1000)
parcelFileDescriptor?.close()
image
} catch (e: Exception) {
null
}
} else {
null
}
}
// https://developer.android.com/topic/performance/graphics/load-bitmap#load-bitmap
private fun decodeSampledBitmapFromFileDescriptor(fileDescriptor: FileDescriptor?, reqWidth: Int, reqHeight: Int): Bitmap {
// First decode with inJustDecodeBounds=true to check dimensions
return BitmapFactory.Options().run {
inJustDecodeBounds = true
BitmapFactory.decodeFileDescriptor(fileDescriptor, null, this)
// Calculate inSampleSize
inSampleSize = calculateInSampleSize(this, reqWidth, reqHeight)
// Decode bitmap with inSampleSize set
inJustDecodeBounds = false
BitmapFactory.decodeFileDescriptor(fileDescriptor, null, this)
}
}
private fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
// Raw height and width of image
val (height: Int, width: Int) = options.run { outHeight to outWidth }
var inSampleSize = 1
if (height > reqHeight || width > reqWidth) {
val halfHeight: Int = height / 2
val halfWidth: Int = width / 2
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
inSampleSize *= 2
}
}
return inSampleSize
}
fun getFileName(context: Context, uri: Uri): String? {
return context.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
cursor.moveToFirst()
cursor.getString(nameIndex)
}
}
fun getAppFilePath(context: Context, uri: Uri): String? {
return context.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
cursor.moveToFirst()
getAppFilePath(context, cursor.getString(nameIndex))
}
}
fun getFileSize(context: Context, uri: Uri): Long? {
return context.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
cursor.moveToFirst()
cursor.getLong(sizeIndex)
}
}
fun getBitmapFromUri(uri: Uri, withAlertOnException: Boolean = true): Bitmap? {
return if (Build.VERSION.SDK_INT >= 28) {
val source = ImageDecoder.createSource(SimplexApp.context.contentResolver, uri)
try {
ImageDecoder.decodeBitmap(source)
} catch (e: android.graphics.ImageDecoder.DecodeException) {
Log.e(TAG, "Unable to decode the image: ${e.stackTraceToString()}")
if (withAlertOnException) {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.image_decoding_exception_title),
text = generalGetString(R.string.image_decoding_exception_desc)
)
}
null
}
} else {
BitmapFactory.decodeFile(getAppFilePath(SimplexApp.context, uri))
}
}
fun getDrawableFromUri(uri: Uri, withAlertOnException: Boolean = true): Drawable? {
return if (Build.VERSION.SDK_INT >= 28) {
val source = ImageDecoder.createSource(SimplexApp.context.contentResolver, uri)
try {
ImageDecoder.decodeDrawable(source)
} catch (e: android.graphics.ImageDecoder.DecodeException) {
if (withAlertOnException) {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.image_decoding_exception_title),
text = generalGetString(R.string.image_decoding_exception_desc)
)
}
Log.e(TAG, "Error while decoding drawable: ${e.stackTraceToString()}")
null
}
} else {
Drawable.createFromPath(getAppFilePath(SimplexApp.context, uri))
}
}
fun saveImage(context: Context, uri: Uri): String? {
val bitmap = getBitmapFromUri(uri) ?: return null
return saveImage(context, bitmap)
}
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 fileToSave = generateNewFileName(context, "IMG", ext)
val file = File(getAppFilePath(context, fileToSave))
val output = FileOutputStream(file)
dataResized.writeTo(output)
output.flush()
output.close()
fileToSave
} catch (e: Exception) {
Log.e(chat.simplex.app.TAG, "Util.kt saveImage error: ${e.message}")
null
}
}
fun saveAnimImage(context: Context, uri: Uri): String? {
return try {
val filename = getFileName(context, uri)?.lowercase()
var ext = when {
// remove everything but extension
filename?.contains(".") == true -> filename.replaceBeforeLast('.', "").replace(".", "")
else -> "gif"
}
// Just in case the image has a strange extension
if (ext.length < 3 || ext.length > 4) ext = "gif"
val fileToSave = generateNewFileName(context, "IMG", ext)
val file = File(getAppFilePath(context, fileToSave))
val output = FileOutputStream(file)
context.contentResolver.openInputStream(uri)!!.use { input ->
output.use { output ->
input.copyTo(output)
}
}
fileToSave
} catch (e: Exception) {
Log.e(chat.simplex.app.TAG, "Util.kt saveAnimImage error: ${e.message}")
null
}
}
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)
val fileToSave = getFileName(context, uri)
if (inputStream != null && fileToSave != null) {
val destFileName = uniqueCombine(context, fileToSave)
val destFile = File(getAppFilePath(context, destFileName))
IOUtils.copy(inputStream, FileOutputStream(destFile))
destFileName
} else {
Log.e(chat.simplex.app.TAG, "Util.kt saveFileFromUri null inputStream")
null
}
} catch (e: Exception) {
Log.e(chat.simplex.app.TAG, "Util.kt saveFileFromUri error: ${e.message}")
null
}
}
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 {
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(n + 1) else f
}
return tryCombine(0)
}
fun formatBytes(bytes: Long): String {
if (bytes == 0.toLong()) {
return "0 bytes"
}
val bytesDouble = bytes.toDouble()
val k = 1024.toDouble()
val units = arrayOf("bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
val i = floor(log2(bytesDouble) / log2(k))
val size = bytesDouble / k.pow(i)
val unit = units[i.toInt()]
return if (i <= 1) {
String.format("%.0f %s", size, unit)
} else {
String.format("%.2f %s", size, unit)
}
}
fun removeFile(context: Context, fileName: String): Boolean {
val file = File(getAppFilePath(context, fileName))
val fileDeleted = file.delete()
if (!fileDeleted) {
Log.e(chat.simplex.app.TAG, "Util.kt removeFile error")
}
return fileDeleted
}
fun deleteAppFiles(context: Context) {
val dir = File(getAppFilesDirectory(context))
try {
dir.list()?.forEach {
removeFile(context, it)
}
} catch (e: java.lang.Exception) {
Log.e(TAG, "Util deleteAppFiles error: ${e.stackTraceToString()}")
}
}
fun directoryFileCountAndSize(dir: String): Pair<Int, Long> { // count, size in bytes
var fileCount = 0
var bytes = 0L
try {
File(dir).listFiles()?.forEach {
fileCount++
bytes += it.length()
}
} catch (e: java.lang.Exception) {
Log.e(TAG, "Util directoryFileCountAndSize error: ${e.stackTraceToString()}")
}
return fileCount to bytes
}
fun getMaxFileSize(fileProtocol: FileProtocol): Long {
return when (fileProtocol) {
FileProtocol.XFTP -> MAX_FILE_SIZE_XFTP
FileProtocol.SMP -> MAX_FILE_SIZE_SMP
}
}
fun getBitmapFromVideo(uri: Uri, timestamp: Long? = null, random: Boolean = true): VideoPlayer.PreviewAndDuration {
val mmr = MediaMetadataRetriever()
mmr.setDataSource(SimplexApp.context, uri)
val durationMs = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLong()
val image = when {
timestamp != null -> mmr.getFrameAtTime(timestamp * 1000, MediaMetadataRetriever.OPTION_CLOSEST)
random -> mmr.frameAtTime
else -> mmr.getFrameAtIndex(0)
}
mmr.release()
return VideoPlayer.PreviewAndDuration(image, durationMs, timestamp ?: 0)
}
fun Color.darker(factor: Float = 0.1f): Color =
Color(max(red * (1 - factor), 0f), max(green * (1 - factor), 0f), max(blue * (1 - factor), 0f), alpha)
fun ByteArray.toBase64String() = Base64.encodeToString(this, Base64.DEFAULT)
fun String.toByteArrayFromBase64() = Base64.decode(this, Base64.DEFAULT)
val LongRange.Companion.saver
get() = Saver<MutableState<LongRange>, Pair<Long, Long>>(
save = { it.value.first to it.value.last },
restore = { mutableStateOf(it.first..it.second) }
)
/* Make sure that T class has @Serializable annotation */
inline fun <reified T> serializableSaver(): Saver<T, *> = Saver(
save = { json.encodeToString(it) },
restore = { json.decodeFromString(it) }
)
fun saveAppLocale(pref: SharedPreference<String?>, activity: Activity, languageCode: String? = null) {
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// val localeManager = SimplexApp.context.getSystemService(LocaleManager::class.java)
// localeManager.applicationLocales = LocaleList(Locale.forLanguageTag(languageCode ?: return))
// } else {
pref.set(languageCode)
if (languageCode == null) {
activity.applyLocale(SimplexApp.context.defaultLocale)
}
activity.recreate()
// }
}
fun Activity.applyAppLocale(pref: SharedPreference<String?>) {
// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
val lang = pref.get()
if (lang == null || lang == Locale.getDefault().language) return
applyLocale(Locale.forLanguageTag(lang))
// }
}
private fun Activity.applyLocale(locale: Locale) {
Locale.setDefault(locale)
val appConf = Configuration(SimplexApp.context.resources.configuration).apply { setLocale(locale) }
val activityConf = Configuration(resources.configuration).apply { setLocale(locale) }
@Suppress("DEPRECATION")
SimplexApp.context.resources.updateConfiguration(appConf, resources.displayMetrics)
@Suppress("DEPRECATION")
resources.updateConfiguration(activityConf, resources.displayMetrics)
}
fun UriHandler.openUriCatching(uri: String) {
try {
openUri(uri)
} catch (e: ActivityNotFoundException) {
Log.e(TAG, e.stackTraceToString())
}
}
fun IntSize.Companion.Saver(): Saver<IntSize, *> = Saver(
save = { it.width to it.height },
restore = { IntSize(it.first, it.second) }
)
@Composable
fun DisposableEffectOnGone(always: () -> Unit = {}, whenDispose: () -> Unit = {}, whenGone: () -> Unit) {
val context = LocalContext.current
DisposableEffect(Unit) {
always()
val activity = context as? Activity ?: return@DisposableEffect onDispose {}
val orientation = activity.resources.configuration.orientation
onDispose {
whenDispose()
if (orientation == activity.resources.configuration.orientation) {
whenGone()
}
}
}
}
@Composable
fun DisposableEffectOnRotate(always: () -> Unit = {}, whenDispose: () -> Unit = {}, whenRotate: () -> Unit) {
val context = LocalContext.current
DisposableEffect(Unit) {
always()
val activity = context as? Activity ?: return@DisposableEffect onDispose {}
val orientation = activity.resources.configuration.orientation
onDispose {
whenDispose()
if (orientation != activity.resources.configuration.orientation) {
whenRotate()
}
}
}
}

View File

@@ -1,22 +0,0 @@
package chat.simplex.app.views.localauth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.res.stringResource
import chat.simplex.app.R
import chat.simplex.app.model.ChatModel
import chat.simplex.app.views.helpers.*
@Composable
fun LocalAuthView(m: ChatModel, authRequest: LocalAuthRequest) {
val passcode = rememberSaveable { mutableStateOf("") }
PasscodeView(passcode, authRequest.title ?: stringResource(R.string.la_enter_app_passcode), authRequest.reason, stringResource(R.string.submit_passcode),
submit = {
val r: LAResult = if (passcode.value == authRequest.password) LAResult.Success else LAResult.Error(generalGetString(R.string.incorrect_passcode))
authRequest.completed(r)
},
cancel = {
authRequest.completed(LAResult.Error(generalGetString(R.string.authentication_cancelled)))
})
}

View File

@@ -1,100 +0,0 @@
package chat.simplex.app.views.localauth
import android.content.res.Configuration
import androidx.compose.foundation.layout.*
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Done
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.ui.theme.DEFAULT_PADDING
import chat.simplex.app.ui.theme.SimpleButton
import chat.simplex.app.views.helpers.*
@Composable
fun PasscodeView(
passcode: MutableState<String>,
title: String,
reason: String? = null,
submitLabel: String,
submitEnabled: ((String) -> Boolean)? = null,
submit: () -> Unit,
cancel: () -> Unit,
) {
@Composable
fun VerticalLayout() {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceEvenly
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(title, style = MaterialTheme.typography.h1)
if (reason != null) {
Text(reason, Modifier.padding(top = 5.dp), style = MaterialTheme.typography.subtitle1)
}
}
PasscodeEntry(passcode, true)
Row {
SimpleButton(generalGetString(R.string.cancel_verb), icon = Icons.Default.Close, click = cancel)
Spacer(Modifier.size(20.dp))
SimpleButton(submitLabel, icon = Icons.Default.Done, disabled = submitEnabled?.invoke(passcode.value) == false || passcode.value.length < 4, click = submit)
}
}
}
@Composable
fun HorizontalLayout() {
Row(Modifier.padding(horizontal = DEFAULT_PADDING), horizontalArrangement = Arrangement.Center) {
Column(
Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, top = DEFAULT_PADDING * 4),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceBetween
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(title, style = MaterialTheme.typography.h1)
if (reason != null) {
Text(reason, Modifier.padding(top = 5.dp), style = MaterialTheme.typography.subtitle1)
}
}
PasscodeEntry(passcode, false)
}
Column(
Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, top = DEFAULT_PADDING * 4),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceBetween
) {
// Just to fill space to correctly calculate the height
Column {
Text("", style = MaterialTheme.typography.h1)
if (reason != null) {
Text("", Modifier.padding(top = 5.dp), style = MaterialTheme.typography.subtitle1)
}
PasscodeView(remember { mutableStateOf("") })
}
BoxWithConstraints {
val s = minOf(maxWidth, maxHeight) / 3.5f
Column(
Modifier.padding(start = 30.dp).height(s * 3),
verticalArrangement = Arrangement.SpaceEvenly
) {
SimpleButton(generalGetString(R.string.cancel_verb), icon = Icons.Default.Close, click = cancel)
SimpleButton(submitLabel, icon = Icons.Default.Done, disabled = submitEnabled?.invoke(passcode.value) == false || passcode.value.length < 4, click = submit)
}
}
}
}
}
if (LocalContext.current.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
VerticalLayout()
} else {
HorizontalLayout()
}
}

View File

@@ -1,142 +0,0 @@
package chat.simplex.app.views.newchat
import android.content.res.Configuration
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.TheaterComedy
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material.icons.outlined.Share
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.style.TextAlign
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.ChatModel
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.*
@Composable
fun AddContactView(connReqInvitation: String, connIncognito: Boolean) {
val cxt = LocalContext.current
AddContactLayout(
connReq = connReqInvitation,
connIncognito = connIncognito,
share = { shareText(cxt, connReqInvitation) }
)
}
@Composable
fun AddContactLayout(connReq: String, connIncognito: Boolean, share: () -> Unit) {
BoxWithConstraints {
val screenHeight = maxHeight
Column(
Modifier
.verticalScroll(rememberScrollState())
.padding(bottom = 16.dp),
verticalArrangement = Arrangement.SpaceBetween,
) {
AppBarTitle(stringResource(R.string.add_contact), false)
Text(
stringResource(R.string.show_QR_code_for_your_contact_to_scan_from_the_app__multiline),
)
Row {
InfoAboutIncognito(
connIncognito,
true,
generalGetString(R.string.incognito_random_profile_description),
generalGetString(R.string.your_profile_will_be_sent)
)
}
if (connReq.isNotEmpty()) {
QRCode(
connReq, Modifier
.aspectRatio(1f)
.padding(vertical = 3.dp)
)
} else {
CircularProgressIndicator(
Modifier
.size(36.dp)
.padding(4.dp)
.align(Alignment.CenterHorizontally),
color = HighOrLowlight,
strokeWidth = 3.dp
)
}
Text(
annotatedStringResource(R.string.if_you_cannot_meet_in_person_show_QR_in_video_call_or_via_another_channel),
lineHeight = 22.sp,
modifier = Modifier
.padding(top = 16.dp, bottom = if (screenHeight > 600.dp) 16.dp else 0.dp)
)
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
SimpleButton(stringResource(R.string.share_invitation_link), icon = Icons.Outlined.Share, click = share)
}
}
}
}
@Composable
fun InfoAboutIncognito(chatModelIncognito: Boolean, supportedIncognito: Boolean = true, onText: String, offText: String, centered: Boolean = false) {
if (chatModelIncognito) {
Row(
Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = if (centered) Arrangement.Center else Arrangement.Start
) {
Icon(
if (supportedIncognito) Icons.Filled.TheaterComedy else Icons.Outlined.Info,
stringResource(R.string.incognito),
tint = if (supportedIncognito) Indigo else WarningOrange,
modifier = Modifier.padding(end = 10.dp).size(20.dp)
)
Text(onText, textAlign = if (centered) TextAlign.Center else TextAlign.Left, style = MaterialTheme.typography.body2)
}
} else {
Row(
Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = if (centered) Arrangement.Center else Arrangement.Start
) {
Icon(
Icons.Outlined.Info,
stringResource(R.string.incognito),
tint = HighOrLowlight,
modifier = Modifier.padding(end = 10.dp).size(20.dp)
)
Text(offText, textAlign = if (centered) TextAlign.Center else TextAlign.Left, style = MaterialTheme.typography.body2)
}
}
}
@Preview
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
fun PreviewAddContactView() {
SimpleXTheme {
AddContactLayout(
connReq = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D",
connIncognito = false,
share = {}
)
}
}

View File

@@ -1,157 +0,0 @@
package chat.simplex.app.views.newchat
import SectionDivider
import SectionView
import android.content.res.Configuration
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
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.tooling.preview.Preview
import chat.simplex.app.R
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.chat.LocalAliasEditor
import chat.simplex.app.views.chatlist.deleteContactConnectionAlert
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.usersettings.SettingsActionItem
@Composable
fun ContactConnectionInfoView(
chatModel: ChatModel,
connReqInvitation: String?,
contactConnection: PendingContactConnection,
focusAlias: Boolean,
close: () -> Unit
) {
LaunchedEffect(connReqInvitation) {
chatModel.connReqInv.value = connReqInvitation
}
/** When [AddContactView] is open, we don't need to drop [chatModel.connReqInv].
* Otherwise, it will be called here AFTER [AddContactView] is launched and will clear the value too soon.
* It will be dropped automatically when connection established or when user goes away from this screen.
**/
DisposableEffect(Unit) {
onDispose {
if (!ModalManager.shared.hasModalsOpen()) {
chatModel.connReqInv.value = null
}
}
}
ContactConnectionInfoLayout(
connReq = connReqInvitation,
contactConnection,
focusAlias,
deleteConnection = { deleteContactConnectionAlert(contactConnection, chatModel, close) },
onLocalAliasChanged = { setContactAlias(contactConnection, it, chatModel) },
showQr = {
ModalManager.shared.showModal {
Column(
Modifier
.fillMaxHeight()
.padding(horizontal = DEFAULT_PADDING),
verticalArrangement = Arrangement.SpaceBetween
) {
AddContactView(connReqInvitation ?: return@showModal, contactConnection.incognito)
}
}
}
)
}
@Composable
private fun ContactConnectionInfoLayout(
connReq: String?,
contactConnection: PendingContactConnection,
focusAlias: Boolean,
deleteConnection: () -> Unit,
onLocalAliasChanged: (String) -> Unit,
showQr: () -> Unit,
) {
Column(
Modifier
.verticalScroll(rememberScrollState()),
) {
AppBarTitle(
stringResource(
if (contactConnection.initiated) R.string.you_invited_your_contact
else R.string.you_accepted_connection
)
)
if (contactConnection.groupLinkId == null) {
Row(Modifier.padding(bottom = DEFAULT_PADDING)) {
LocalAliasEditor(contactConnection.localAlias, center = false, leadingIcon = true, focus = focusAlias, updateValue = onLocalAliasChanged)
}
}
Text(
stringResource(
if (contactConnection.viaContactUri)
if (contactConnection.groupLinkId != null) R.string.you_will_be_connected_when_group_host_device_is_online
else R.string.you_will_be_connected_when_your_connection_request_is_accepted
else R.string.you_will_be_connected_when_your_contacts_device_is_online
),
Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING)
)
SectionView {
if (!connReq.isNullOrEmpty() && contactConnection.initiated) {
ShowQrButton(contactConnection.incognito, showQr)
SectionDivider()
}
DeleteButton(deleteConnection)
}
}
}
@Composable
fun ShowQrButton(incognito: Boolean, onClick: () -> Unit) {
SettingsActionItem(
Icons.Outlined.QrCode,
stringResource(R.string.show_QR_code),
click = onClick,
textColor = if (incognito) Indigo else MaterialTheme.colors.primary,
iconColor = if (incognito) Indigo else MaterialTheme.colors.primary,
)
}
@Composable
fun DeleteButton(onClick: () -> Unit) {
SettingsActionItem(
Icons.Outlined.Delete,
stringResource(R.string.delete_verb),
click = onClick,
textColor = Color.Red,
iconColor = Color.Red,
)
}
private fun setContactAlias(contactConnection: PendingContactConnection, localAlias: String, chatModel: ChatModel) = withApi {
chatModel.controller.apiSetConnectionAlias(contactConnection.pccConnId, localAlias)?.let {
chatModel.updateContactConnection(it)
}
}
@Preview
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
private fun PreviewContactConnectionInfoView() {
SimpleXTheme {
ContactConnectionInfoLayout(
connReq = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D",
PendingContactConnection.getSampleData(),
focusAlias = false,
deleteConnection = {},
onLocalAliasChanged = {},
showQr = {},
)
}
}

View File

@@ -1,137 +0,0 @@
package chat.simplex.app.views.newchat
import android.content.ClipboardManager
import android.content.res.Configuration
import android.net.Uri
import android.util.Log
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text
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.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat.getSystemService
import chat.simplex.app.R
import chat.simplex.app.TAG
import chat.simplex.app.model.ChatModel
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.*
@Composable
fun PasteToConnectView(chatModel: ChatModel, close: () -> Unit) {
val connectionLink = remember { mutableStateOf("") }
val context = LocalContext.current
val clipboard = getSystemService(context, ClipboardManager::class.java)
PasteToConnectLayout(
chatModel.incognito.value,
connectionLink = connectionLink,
pasteFromClipboard = {
connectionLink.value = clipboard?.primaryClip?.getItemAt(0)?.coerceToText(context) as? String ?: return@PasteToConnectLayout
},
connectViaLink = { connReqUri ->
try {
val uri = Uri.parse(connReqUri)
withUriAction(uri) { linkType ->
val action = suspend {
Log.d(TAG, "connectViaUri: connecting")
if (connectViaUri(chatModel, linkType, uri)) {
close()
}
}
if (linkType == ConnectionLinkType.GROUP) {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.connect_via_group_link),
text = generalGetString(R.string.you_will_join_group),
confirmText = generalGetString(R.string.connect_via_link_verb),
onConfirm = { withApi { action() } }
)
} else action()
}
} catch (e: RuntimeException) {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.invalid_connection_link),
text = generalGetString(R.string.this_string_is_not_a_connection_link)
)
}
},
)
}
@Composable
fun PasteToConnectLayout(
chatModelIncognito: Boolean,
connectionLink: MutableState<String>,
pasteFromClipboard: () -> Unit,
connectViaLink: (String) -> Unit,
) {
Column(
Modifier.verticalScroll(rememberScrollState()).padding(horizontal = DEFAULT_PADDING),
verticalArrangement = Arrangement.SpaceBetween,
) {
AppBarTitle(stringResource(R.string.connect_via_link), false)
Text(stringResource(R.string.paste_connection_link_below_to_connect))
InfoAboutIncognito(
chatModelIncognito,
true,
generalGetString(R.string.incognito_random_profile_from_contact_description),
generalGetString(R.string.profile_will_be_sent_to_contact_sending_link)
)
Box(Modifier.padding(top = 16.dp, bottom = 6.dp)) {
TextEditor(Modifier.height(180.dp), text = connectionLink)
}
Row(
Modifier.fillMaxWidth().padding(bottom = 6.dp),
horizontalArrangement = Arrangement.Start,
) {
if (connectionLink.value == "") {
SimpleButton(text = stringResource(R.string.paste_button), icon = Icons.Outlined.ContentPaste) {
pasteFromClipboard()
}
} else {
SimpleButton(text = stringResource(R.string.clear_verb), icon = Icons.Outlined.Clear) {
connectionLink.value = ""
}
}
Spacer(Modifier.weight(1f).fillMaxWidth())
SimpleButton(text = stringResource(R.string.connect_button), icon = Icons.Outlined.Link) {
connectViaLink(connectionLink.value)
}
}
Text(annotatedStringResource(R.string.you_can_also_connect_by_clicking_the_link))
}
}
@Preview(showBackground = true)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
name = "Dark Mode"
)
@Composable
fun PreviewPasteToConnectTextbox() {
SimpleXTheme {
PasteToConnectLayout(
chatModelIncognito = false,
connectionLink = remember { mutableStateOf("") },
pasteFromClipboard = {},
connectViaLink = { link ->
try {
println(link)
// withApi { chatModel.controller.apiConnect(link) }
} catch (e: Exception) {
e.printStackTrace()
}
},
)
}
}

View File

@@ -1,161 +0,0 @@
package chat.simplex.app.views.newchat
import android.Manifest
import android.content.res.Configuration
import android.net.Uri
import android.util.Log
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.net.toUri
import chat.simplex.app.R
import chat.simplex.app.TAG
import chat.simplex.app.model.ChatModel
import chat.simplex.app.model.json
import chat.simplex.app.ui.theme.DEFAULT_PADDING
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.helpers.*
import com.google.accompanist.permissions.rememberPermissionState
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Composable
fun ScanToConnectView(chatModel: ChatModel, close: () -> Unit) {
val cameraPermissionState = rememberPermissionState(permission = Manifest.permission.CAMERA)
LaunchedEffect(Unit) {
cameraPermissionState.launchPermissionRequest()
}
ConnectContactLayout(
chatModelIncognito = chatModel.incognito.value,
qrCodeScanner = {
QRCodeScanner { connReqUri ->
try {
val uri = Uri.parse(connReqUri)
withUriAction(uri) { linkType ->
val action = suspend {
Log.d(TAG, "connectViaUri: connecting")
if (connectViaUri(chatModel, linkType, uri)) {
close()
}
}
if (linkType == ConnectionLinkType.GROUP) {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.connect_via_group_link),
text = generalGetString(R.string.you_will_join_group),
confirmText = generalGetString(R.string.connect_via_link_verb),
onConfirm = { withApi { action() } }
)
} else action()
}
} catch (e: RuntimeException) {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.invalid_QR_code),
text = generalGetString(R.string.this_QR_code_is_not_a_link)
)
}
}
},
)
}
enum class ConnectionLinkType {
CONTACT, INVITATION, GROUP
}
@Serializable
sealed class CReqClientData {
@Serializable @SerialName("group") data class Group(val groupLinkId: String): CReqClientData()
}
fun withUriAction(uri: Uri, run: suspend (ConnectionLinkType) -> Unit) {
val action = uri.path?.drop(1)?.replace("/", "")
val data = uri.toString().replaceFirst("#/", "/").toUri().getQueryParameter("data")
val type = when {
data != null -> {
val parsed = runCatching {
json.decodeFromString(CReqClientData.serializer(), data)
}
when {
parsed.getOrNull() is CReqClientData.Group -> ConnectionLinkType.GROUP
else -> null
}
}
action == "contact" -> ConnectionLinkType.CONTACT
action == "invitation" -> ConnectionLinkType.INVITATION
else -> null
}
if (type != null) {
withApi { run(type) }
} else {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.invalid_contact_link),
text = generalGetString(R.string.this_link_is_not_a_valid_connection_link)
)
}
}
suspend fun connectViaUri(chatModel: ChatModel, action: ConnectionLinkType, uri: Uri): Boolean {
val r = chatModel.controller.apiConnect(uri.toString())
if (r) {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.connection_request_sent),
text =
when (action) {
ConnectionLinkType.CONTACT -> generalGetString(R.string.you_will_be_connected_when_your_connection_request_is_accepted)
ConnectionLinkType.INVITATION -> generalGetString(R.string.you_will_be_connected_when_your_contacts_device_is_online)
ConnectionLinkType.GROUP -> generalGetString(R.string.you_will_be_connected_when_group_host_device_is_online)
}
)
}
return r
}
@Composable
fun ConnectContactLayout(chatModelIncognito: Boolean, qrCodeScanner: @Composable () -> Unit) {
Column(
Modifier.verticalScroll(rememberScrollState()).padding(horizontal = DEFAULT_PADDING),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
AppBarTitle(stringResource(R.string.scan_QR_code), false)
InfoAboutIncognito(
chatModelIncognito,
true,
generalGetString(R.string.incognito_random_profile_description),
generalGetString(R.string.your_profile_will_be_sent)
)
Box(
Modifier
.fillMaxWidth()
.aspectRatio(ratio = 1F)
.padding(bottom = 12.dp)
) { qrCodeScanner() }
Text(
annotatedStringResource(R.string.if_you_cannot_meet_in_person_scan_QR_in_video_call_or_ask_for_invitation_link),
lineHeight = 22.sp
)
}
}
@Preview
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
fun PreviewConnectContactLayout() {
SimpleXTheme {
ConnectContactLayout(
chatModelIncognito = false,
qrCodeScanner = { Surface {} },
)
}
}

View File

@@ -1,79 +0,0 @@
package chat.simplex.app.views.onboarding
import android.content.res.Configuration
import androidx.annotation.StringRes
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
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.style.TextAlign
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.User
import chat.simplex.app.ui.theme.DEFAULT_PADDING
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.helpers.*
@Composable
fun HowItWorks(user: User?, onboardingStage: MutableState<OnboardingStage?>? = null) {
Column(Modifier
.fillMaxWidth()
.padding(horizontal = DEFAULT_PADDING),
horizontalAlignment = Alignment.Start
) {
AppBarTitle(stringResource(R.string.how_simplex_works), false)
ReadableText(R.string.many_people_asked_how_can_it_deliver)
ReadableText(R.string.to_protect_privacy_simplex_has_ids_for_queues)
ReadableText(R.string.you_control_servers_to_receive_your_contacts_to_send)
ReadableText(R.string.only_client_devices_store_contacts_groups_e2e_encrypted_messages)
if (onboardingStage == null) {
val uriHandler = LocalUriHandler.current
Text(
annotatedStringResource(R.string.read_more_in_github_with_link),
modifier = Modifier.padding(bottom = 12.dp).clickable { uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat#readme") },
lineHeight = 22.sp
)
} else {
ReadableText(R.string.read_more_in_github)
}
Spacer(Modifier.fillMaxHeight().weight(1f))
if (onboardingStage != null) {
Box(Modifier.fillMaxWidth().padding(bottom = 16.dp), contentAlignment = Alignment.Center) {
OnboardingActionButton(user, onboardingStage, onclick = { ModalManager.shared.closeModal() })
}
Spacer(Modifier.fillMaxHeight().weight(1f))
}
}
}
@Composable
fun ReadableText(@StringRes stringResId: Int, textAlign: TextAlign = TextAlign.Start, padding: PaddingValues = PaddingValues(bottom = 12.dp)) {
Text(annotatedStringResource(stringResId), modifier = Modifier.padding(padding), textAlign = textAlign, lineHeight = 22.sp)
}
@Composable
fun ReadableText(text: String, textAlign: TextAlign = TextAlign.Start, padding: PaddingValues = PaddingValues(bottom = 12.dp)) {
Text(text, modifier = Modifier.padding(padding), textAlign = textAlign, lineHeight = 22.sp)
}
@Preview(showBackground = true)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
fun PreviewHowItWorks() {
SimpleXTheme {
HowItWorks(user = null)
}
}

View File

@@ -1,51 +0,0 @@
package chat.simplex.app.views.onboarding
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import chat.simplex.app.model.ChatModel
import chat.simplex.app.views.CreateProfilePanel
import chat.simplex.app.views.helpers.getKeyboardState
import com.google.accompanist.insets.ProvideWindowInsets
import kotlinx.coroutines.launch
enum class OnboardingStage {
Step1_SimpleXInfo,
Step2_CreateProfile,
Step3_SetNotificationsMode,
OnboardingComplete
}
@Composable
fun CreateProfile(chatModel: ChatModel, close: () -> Unit) {
val scope = rememberCoroutineScope()
val scrollState = rememberScrollState()
val keyboardState by getKeyboardState()
var savedKeyboardState by remember { mutableStateOf(keyboardState) }
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
Box(
modifier = Modifier
.fillMaxSize()
.background(color = MaterialTheme.colors.background)
.padding(20.dp)
) {
CreateProfilePanel(chatModel, close)
LaunchedEffect(Unit) {
setLastVersionDefault(chatModel)
}
if (savedKeyboardState != keyboardState) {
LaunchedEffect(keyboardState) {
scope.launch {
savedKeyboardState = keyboardState
scrollState.animateScrollTo(scrollState.maxValue)
}
}
}
}
}
}

View File

@@ -1,76 +0,0 @@
package chat.simplex.app.views.onboarding
import androidx.annotation.StringRes
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
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.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.R
import chat.simplex.app.model.ChatModel
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.usersettings.NotificationsMode
import chat.simplex.app.views.usersettings.changeNotificationsMode
@Composable
fun SetNotificationsMode(m: ChatModel) {
Column(
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())
) {
//CloseSheetBar(null)
AppBarTitleCentered(stringResource(R.string.onboarding_notifications_mode_title))
val currentMode = rememberSaveable { mutableStateOf(NotificationsMode.default) }
Column(Modifier.padding(horizontal = DEFAULT_PADDING * 1f)) {
Text(stringResource(R.string.onboarding_notifications_mode_subtitle), Modifier.fillMaxWidth(), textAlign = TextAlign.Center)
Spacer(Modifier.height(DEFAULT_PADDING * 2f))
NotificationButton(currentMode, NotificationsMode.OFF, R.string.onboarding_notifications_mode_off, R.string.onboarding_notifications_mode_off_desc)
NotificationButton(currentMode, NotificationsMode.PERIODIC, R.string.onboarding_notifications_mode_periodic, R.string.onboarding_notifications_mode_periodic_desc)
NotificationButton(currentMode, NotificationsMode.SERVICE, R.string.onboarding_notifications_mode_service, R.string.onboarding_notifications_mode_service_desc)
}
Spacer(Modifier.fillMaxHeight().weight(1f))
Box(Modifier.fillMaxWidth().padding(bottom = 16.dp), contentAlignment = Alignment.Center) {
OnboardingActionButton(R.string.use_chat, OnboardingStage.OnboardingComplete, m.onboardingStage, false) {
changeNotificationsMode(currentMode.value, m)
}
}
Spacer(Modifier.fillMaxHeight().weight(1f))
}
}
@Composable
private fun NotificationButton(currentMode: MutableState<NotificationsMode>, mode: NotificationsMode, @StringRes title: Int, @StringRes description: Int) {
TextButton(
onClick = { currentMode.value = mode },
border = BorderStroke(1.dp, color = if (currentMode.value == mode) MaterialTheme.colors.primary else HighOrLowlight.copy(alpha = 0.5f)),
shape = RoundedCornerShape(35.dp),
) {
Column(Modifier.padding(14.dp)) {
Text(
stringResource(title),
style = MaterialTheme.typography.h2,
fontWeight = FontWeight.Medium,
color = if (currentMode.value == mode) MaterialTheme.colors.primary else HighOrLowlight,
modifier = Modifier.padding(bottom = 14.dp).align(Alignment.CenterHorizontally),
textAlign = TextAlign.Center
)
Text(annotatedStringResource(description),
Modifier.align(Alignment.CenterHorizontally),
color = MaterialTheme.colors.onBackground,
lineHeight = 24.sp,
textAlign = TextAlign.Center
)
}
}
Spacer(Modifier.height(14.dp))
}

View File

@@ -1,162 +0,0 @@
package chat.simplex.app.views.onboarding
import android.content.res.Configuration
import androidx.annotation.StringRes
import androidx.compose.foundation.*
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.ArrowForwardIos
import androidx.compose.material.icons.outlined.Info
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
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.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.*
import chat.simplex.app.R
import chat.simplex.app.model.ChatModel
import chat.simplex.app.model.User
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.ModalManager
@Composable
fun SimpleXInfo(chatModel: ChatModel, onboarding: Boolean = true) {
SimpleXInfoLayout(
user = chatModel.currentUser.value,
onboardingStage = if (onboarding) chatModel.onboardingStage else null,
showModal = { modalView -> { ModalManager.shared.showModal { modalView(chatModel) } } },
)
}
@Composable
fun SimpleXInfoLayout(
user: User?,
onboardingStage: MutableState<OnboardingStage?>?,
showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
) {
Column(
Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(start = DEFAULT_PADDING * 1.5f, end = DEFAULT_PADDING * 1.5f, top = DEFAULT_PADDING * 4,/* bottom = DEFAULT_PADDING * 4*/),
) {
Box(Modifier.fillMaxWidth().padding(top = 8.dp, bottom = 10.dp), contentAlignment = Alignment.Center) {
SimpleXLogo()
}
Text(stringResource(R.string.next_generation_of_private_messaging), style = MaterialTheme.typography.h2, modifier = Modifier.padding(bottom = 60.dp).padding(horizontal = 48.dp), textAlign = TextAlign.Center)
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(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))
if (onboardingStage != null) {
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
OnboardingActionButton(user, onboardingStage)
}
Spacer(Modifier.fillMaxHeight().weight(1f))
}
Box(
Modifier
.fillMaxWidth()
.padding(bottom = DEFAULT_PADDING, top = DEFAULT_PADDING), contentAlignment = Alignment.Center
) {
SimpleButtonDecorated(text = stringResource(R.string.how_it_works), icon = Icons.Outlined.Info,
click = showModal { HowItWorks(user, onboardingStage) })
}
Spacer(Modifier.weight(1f))
}
}
@Composable
fun SimpleXLogo() {
Image(
painter = painterResource(if (isInDarkTheme()) R.drawable.logo_light else R.drawable.logo),
contentDescription = stringResource(R.string.image_descr_simplex_logo),
modifier = Modifier
.padding(vertical = DEFAULT_PADDING)
.fillMaxWidth(0.60f)
)
}
@Composable
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(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.body1)
}
}
}
@Composable
fun OnboardingActionButton(user: User?, onboardingStage: MutableState<OnboardingStage?>, onclick: (() -> Unit)? = null) {
if (user == null) {
OnboardingActionButton(R.string.create_your_profile, onboarding = OnboardingStage.Step2_CreateProfile, onboardingStage, true, onclick)
} else {
OnboardingActionButton(R.string.make_private_connection, onboarding = OnboardingStage.OnboardingComplete, onboardingStage, true, onclick)
}
}
@Composable
fun OnboardingActionButton(
@StringRes labelId: Int,
onboarding: OnboardingStage?,
onboardingStage: MutableState<OnboardingStage?>,
border: Boolean,
onclick: (() -> Unit)?
) {
val modifier = if (border) {
Modifier
.border(border = BorderStroke(1.dp, MaterialTheme.colors.primary), shape = RoundedCornerShape(50))
.padding(
horizontal = DEFAULT_PADDING * 3,
vertical = 4.dp
)
} else {
Modifier
}
SimpleButtonFrame(click = {
onclick?.invoke()
onboardingStage.value = onboarding
}, modifier) {
Text(stringResource(labelId), style = MaterialTheme.typography.h2, color = MaterialTheme.colors.primary, fontSize = 20.sp)
Icon(
Icons.Outlined.ArrowForwardIos, "next stage", tint = MaterialTheme.colors.primary,
modifier = Modifier
.padding(start = 16.dp, top = 5.dp)
.size(15.dp)
)
}
}
@Preview(showBackground = true)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
fun PreviewSimpleXInfo() {
SimpleXTheme {
SimpleXInfoLayout(
user = null,
onboardingStage = null,
showModal = { {} }
)
}
}

View File

@@ -1,362 +0,0 @@
package chat.simplex.app.views.onboarding
import android.content.res.Configuration
import androidx.compose.foundation.*
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.openUriCatching(link) }
)
}
Column(
horizontalAlignment = Alignment.Start,
modifier = Modifier.padding(bottom = 12.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.padding(bottom = 4.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
.fillMaxSize()
.padding(horizontal = DEFAULT_PADDING)
.verticalScroll(rememberScrollState()),
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"
)
)
),
VersionDescription(
version = "v4.6",
features = listOf(
FeatureDescription(
icon = Icons.Outlined.Lock,
titleId = R.string.v4_6_hidden_chat_profiles,
descrId = R.string.v4_6_hidden_chat_profiles_descr
),
FeatureDescription(
icon = Icons.Outlined.Flag,
titleId = R.string.v4_6_group_moderation,
descrId = R.string.v4_6_group_moderation_descr
),
FeatureDescription(
icon = Icons.Outlined.MapsUgc,
titleId = R.string.v4_6_group_welcome_message,
descrId = R.string.v4_6_group_welcome_message_descr
),
FeatureDescription(
icon = Icons.Outlined.Call,
titleId = R.string.v4_6_audio_video_calls,
descrId = R.string.v4_6_audio_video_calls_descr
),
FeatureDescription(
icon = Icons.Outlined.Battery3Bar,
titleId = R.string.v4_6_reduced_battery_usage,
descrId = R.string.v4_6_reduced_battery_usage_descr
),
FeatureDescription(
icon = Icons.Outlined.Translate,
titleId = R.string.v4_6_chinese_spanish_interface,
descrId = R.string.v4_6_chinese_spanish_interface_descr,
link = "https://github.com/simplex-chat/simplex-chat/tree/stable#translate-the-apps"
)
)
),
VersionDescription(
version = "v5.0",
features = listOf(
FeatureDescription(
icon = Icons.Outlined.UploadFile,
titleId = R.string.v5_0_large_files_support,
descrId = R.string.v5_0_large_files_support_descr
),
FeatureDescription(
icon = Icons.Outlined.Lock,
titleId = R.string.v5_0_app_passcode,
descrId = R.string.v5_0_app_passcode_descr
),
FeatureDescription(
icon = Icons.Outlined.Translate,
titleId = R.string.v5_0_polish_interface,
descrId = R.string.v5_0_polish_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

@@ -1,167 +0,0 @@
package chat.simplex.app.views.usersettings
import SectionCustomFooter
import SectionDivider
import SectionItemView
import SectionView
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.TheaterComedy
import androidx.compose.material.icons.outlined.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.*
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.*
@Composable
fun AcceptRequestsView(m: ChatModel, contactLink: UserContactLinkRec) {
var contactLink by remember { mutableStateOf(contactLink) }
AcceptRequestsLayout(
contactLink,
saveState = { new: MutableState<AutoAcceptState>, old: MutableState<AutoAcceptState> ->
withApi {
val link = m.controller.userAddressAutoAccept(new.value.autoAccept)
if (link != null) {
contactLink = link
m.userAddress.value = link
old.value = new.value
}
}
}
)
}
@Composable
private fun AcceptRequestsLayout(
contactLink: UserContactLinkRec,
saveState: (new: MutableState<AutoAcceptState>, old: MutableState<AutoAcceptState>) -> Unit,
) {
Column(
Modifier.fillMaxWidth().verticalScroll(rememberScrollState()),
) {
AppBarTitle(stringResource(R.string.contact_requests))
val autoAcceptState = remember { mutableStateOf(AutoAcceptState(contactLink)) }
val autoAcceptStateSaved = remember { mutableStateOf(autoAcceptState.value) }
SectionView(stringResource(R.string.accept_requests).uppercase()) {
SectionItemView {
PreferenceToggleWithIcon(stringResource(R.string.accept_automatically), Icons.Outlined.Check, checked = autoAcceptState.value.enable) {
autoAcceptState.value = if (!it)
AutoAcceptState()
else
AutoAcceptState(it, autoAcceptState.value.incognito, autoAcceptState.value.welcomeText)
}
}
if (autoAcceptState.value.enable) {
SectionDivider()
SectionItemView {
PreferenceToggleWithIcon(
stringResource(R.string.incognito),
if (autoAcceptState.value.incognito) Icons.Filled.TheaterComedy else Icons.Outlined.TheaterComedy,
if (autoAcceptState.value.incognito) Indigo else HighOrLowlight,
autoAcceptState.value.incognito,
) {
autoAcceptState.value = AutoAcceptState(autoAcceptState.value.enable, it, autoAcceptState.value.welcomeText)
}
}
}
}
val welcomeText = remember { mutableStateOf(autoAcceptState.value.welcomeText) }
SectionCustomFooter(PaddingValues(horizontal = DEFAULT_PADDING)) {
ButtonsFooter(
cancel = {
autoAcceptState.value = autoAcceptStateSaved.value
welcomeText.value = autoAcceptStateSaved.value.welcomeText
},
save = { saveState(autoAcceptState, autoAcceptStateSaved) },
disabled = autoAcceptState.value == autoAcceptStateSaved.value
)
}
Spacer(Modifier.height(DEFAULT_PADDING))
if (autoAcceptState.value.enable) {
Text(
stringResource(R.string.section_title_welcome_message), color = HighOrLowlight, style = MaterialTheme.typography.body2,
modifier = Modifier.padding(start = DEFAULT_PADDING, bottom = 5.dp), fontSize = 12.sp
)
TextEditor(Modifier.padding(horizontal = DEFAULT_PADDING).height(160.dp), text = welcomeText)
LaunchedEffect(welcomeText.value) {
if (welcomeText.value != autoAcceptState.value.welcomeText) {
autoAcceptState.value = AutoAcceptState(autoAcceptState.value.enable, autoAcceptState.value.incognito, welcomeText.value)
}
}
}
}
}
@Composable
private fun ButtonsFooter(cancel: () -> Unit, save: () -> Unit, disabled: Boolean) {
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
FooterButton(Icons.Outlined.Replay, stringResource(R.string.cancel_verb), cancel, disabled)
FooterButton(Icons.Outlined.Check, stringResource(R.string.save_verb), save, disabled)
}
}
private class AutoAcceptState {
var enable: Boolean = false
private set
var incognito: Boolean = false
private set
var welcomeText: String = ""
private set
constructor(enable: Boolean = false, incognito: Boolean = false, welcomeText: String = "") {
this.enable = enable
this.incognito = incognito
this.welcomeText = welcomeText
}
constructor(contactLink: UserContactLinkRec) {
contactLink.autoAccept?.let { aa ->
enable = true
incognito = aa.acceptIncognito
aa.autoReply?.let { msg ->
welcomeText = msg.text
} ?: run {
welcomeText = ""
}
}
}
val autoAccept: AutoAccept?
get() {
if (enable) {
var autoReply: MsgContent? = null
val s = welcomeText.trim()
if (s != "") {
autoReply = MsgContent.MCText(s)
}
return AutoAccept(incognito, autoReply)
}
return null
}
override fun equals(other: Any?): Boolean {
if (other !is AutoAcceptState) return false
return this.enable == other.enable && this.incognito == other.incognito && this.welcomeText == other.welcomeText
}
override fun hashCode(): Int {
var result = enable.hashCode()
result = 31 * result + incognito.hashCode()
result = 31 * result + welcomeText.hashCode()
return result
}
}

View File

@@ -1,289 +0,0 @@
package chat.simplex.app.views.usersettings
import SectionCustomFooter
import SectionDivider
import SectionItemView
import SectionItemViewSpaceBetween
import SectionItemWithValue
import SectionSpacer
import SectionView
import android.app.Activity
import android.content.ComponentName
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED
import android.net.Uri
import android.os.Build
import android.provider.Settings
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.material.*
import androidx.compose.material.MaterialTheme.colors
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Circle
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
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.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmap
import chat.simplex.app.*
import chat.simplex.app.R
import chat.simplex.app.model.ChatModel
import chat.simplex.app.model.SharedPreference
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.*
import com.godaddy.android.colorpicker.*
import kotlinx.coroutines.delay
import java.util.*
enum class AppIcon(val resId: Int) {
DEFAULT(R.mipmap.icon),
DARK_BLUE(R.mipmap.icon_dark_blue),
}
@Composable
fun AppearanceView(m: ChatModel) {
val appIcon = remember { mutableStateOf(findEnabledIcon()) }
fun setAppIcon(newIcon: AppIcon) {
if (appIcon.value == newIcon) return
val newComponent = ComponentName(BuildConfig.APPLICATION_ID, "chat.simplex.app.MainActivity_${newIcon.name.lowercase()}")
val oldComponent = ComponentName(BuildConfig.APPLICATION_ID, "chat.simplex.app.MainActivity_${appIcon.value.name.lowercase()}")
SimplexApp.context.packageManager.setComponentEnabledSetting(
newComponent,
COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP
)
SimplexApp.context.packageManager.setComponentEnabledSetting(
oldComponent,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP
)
appIcon.value = newIcon
}
AppearanceLayout(
appIcon,
m.controller.appPrefs.appLanguage,
changeIcon = ::setAppIcon,
editPrimaryColor = { primary ->
ModalManager.shared.showModalCloseable { close ->
ColorEditor(primary, close)
}
},
)
}
@Composable fun AppearanceLayout(
icon: MutableState<AppIcon>,
languagePref: SharedPreference<String?>,
changeIcon: (AppIcon) -> Unit,
editPrimaryColor: (Color) -> Unit,
) {
Column(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.Start,
) {
AppBarTitle(stringResource(R.string.appearance_settings))
SectionView(stringResource(R.string.settings_section_title_language), padding = PaddingValues()) {
val context = LocalContext.current
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// SectionItemWithValue(
// generalGetString(R.string.settings_section_title_language).lowercase().replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.US) else it.toString() },
// remember { mutableStateOf("system") },
// listOf(ValueTitleDesc("system", generalGetString(R.string.change_verb), "")),
// onSelected = { openSystemLangPicker(context as? Activity ?: return@SectionItemWithValue) }
// )
// } else {
val state = rememberSaveable { mutableStateOf(languagePref.get() ?: "system") }
SectionItemView {
LangSelector(state) {
state.value = it
withApi {
delay(200)
val activity = context as? Activity
if (activity != null) {
if (it == "system") {
saveAppLocale(languagePref, activity)
} else {
saveAppLocale(languagePref, activity, it)
}
}
}
}
}
// }
}
SectionSpacer()
SectionView(stringResource(R.string.settings_section_title_icon), padding = PaddingValues(horizontal = DEFAULT_PADDING_HALF)) {
LazyRow {
items(AppIcon.values().size, { index -> AppIcon.values()[index] }) { index ->
val item = AppIcon.values()[index]
val mipmap = ContextCompat.getDrawable(LocalContext.current, item.resId)!!
Image(
bitmap = mipmap.toBitmap().asImageBitmap(),
contentDescription = "",
contentScale = ContentScale.Fit,
modifier = Modifier
.shadow(if (item == icon.value) 1.dp else 0.dp, ambientColor = colors.secondary)
.size(70.dp)
.clickable { changeIcon(item) }
.padding(10.dp)
)
if (index + 1 != AppIcon.values().size) {
Spacer(Modifier.padding(horizontal = 4.dp))
}
}
}
}
SectionSpacer()
val currentTheme by CurrentColors.collectAsState()
SectionView(stringResource(R.string.settings_section_title_themes)) {
SectionItemViewSpaceBetween {
val darkTheme = isSystemInDarkTheme()
val state = remember { derivedStateOf { currentTheme.second } }
ThemeSelector(state) {
ThemeManager.applyTheme(it.name, darkTheme)
}
}
SectionDivider()
SectionItemViewSpaceBetween({ editPrimaryColor(currentTheme.first.primary) }) {
val title = generalGetString(R.string.color_primary)
Text(title)
Icon(Icons.Filled.Circle, title, tint = colors.primary)
}
}
if (currentTheme.first.primary != LightColorPalette.primary) {
SectionCustomFooter(PaddingValues(start = 7.dp, end = 7.dp, top = 5.dp)) {
TextButton(
onClick = {
ThemeManager.saveAndApplyPrimaryColor(LightColorPalette.primary)
},
) {
Text(generalGetString(R.string.reset_color))
}
}
}
}
}
@Composable
fun ColorEditor(
initialColor: Color,
close: () -> Unit,
) {
Column(
Modifier
.fillMaxWidth()
) {
AppBarTitle(stringResource(R.string.color_primary))
var currentColor by remember { mutableStateOf(initialColor) }
ColorPicker(initialColor) {
currentColor = it
}
SectionSpacer()
TextButton(
onClick = {
ThemeManager.saveAndApplyPrimaryColor(currentColor)
close()
},
Modifier.align(Alignment.CenterHorizontally),
colors = ButtonDefaults.textButtonColors(contentColor = currentColor)
) {
Text(generalGetString(R.string.save_color))
}
}
}
@Composable
fun ColorPicker(initialColor: Color, onColorChanged: (Color) -> Unit) {
ClassicColorPicker(
color = initialColor,
modifier = Modifier
.fillMaxWidth()
.height(300.dp),
showAlphaBar = false,
onColorChanged = { color: HsvColor ->
onColorChanged(color.toColor())
}
)
}
@Composable
private fun LangSelector(state: State<String>, onSelected: (String) -> Unit) {
// Should be the same as in app/build.gradle's `android.defaultConfig.resConfigs`
val supportedLanguages = mapOf(
"system" to generalGetString(R.string.language_system),
"en" to "English",
"cs" to "Čeština",
"de" to "Deutsch",
"es" to "Español",
"fr" to "Français",
"it" to "Italiano",
"nl" to "Nederlands",
"pl" to "Polski",
"ru" to "Русский",
"zh-CN" to "简体中文"
)
val values by remember { mutableStateOf(supportedLanguages.map { it.key to it.value }) }
ExposedDropDownSettingRow(
generalGetString(R.string.settings_section_title_language).lowercase().replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.US) else it.toString() },
values,
state,
icon = null,
enabled = remember { mutableStateOf(true) },
onSelected = onSelected
)
}
@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 openSystemLangPicker(activity: Activity) {
// activity.startActivity(Intent(Settings.ACTION_APP_LOCALE_SETTINGS, Uri.parse("package:" + SimplexApp.context.packageName)))
//}
private fun findEnabledIcon(): AppIcon = AppIcon.values().first { icon ->
SimplexApp.context.packageManager.getComponentEnabledSetting(
ComponentName(BuildConfig.APPLICATION_ID, "chat.simplex.app.MainActivity_${icon.name.lowercase()}")
).let { it == COMPONENT_ENABLED_STATE_DEFAULT || it == COMPONENT_ENABLED_STATE_ENABLED }
}
@Preview(showBackground = true)
@Composable
fun PreviewAppearanceSettings() {
SimpleXTheme {
AppearanceLayout(
icon = remember { mutableStateOf(AppIcon.DARK_BLUE) },
languagePref = SharedPreference({ null }, {}),
changeIcon = {},
editPrimaryColor = {},
)
}
}

View File

@@ -1,58 +0,0 @@
package chat.simplex.app.views.usersettings
import SectionDivider
import SectionSpacer
import SectionTextFooter
import SectionView
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
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.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import chat.simplex.app.R
import chat.simplex.app.model.ChatModel
import chat.simplex.app.views.TerminalView
import chat.simplex.app.views.helpers.*
@Composable
fun DeveloperView(
m: ChatModel,
showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit),
withAuth: (block: () -> Unit) -> Unit
) {
Column(Modifier.fillMaxWidth()) {
val uriHandler = LocalUriHandler.current
AppBarTitle(stringResource(R.string.settings_developer_tools))
val developerTools = m.controller.appPrefs.developerTools
val devTools = remember { mutableStateOf(developerTools.get()) }
SectionView() {
InstallTerminalAppItem(uriHandler)
SectionDivider()
ChatConsoleItem { withAuth(showCustomModal { it, close -> TerminalView(it, close) }) }
SectionDivider()
SettingsPreferenceItem(Icons.Outlined.DriveFolderUpload, stringResource(R.string.confirm_database_upgrades), m.controller.appPrefs.confirmDBUpgrades)
SectionDivider()
SettingsPreferenceItem(Icons.Outlined.Code, stringResource(R.string.show_developer_options), developerTools, devTools)
}
SectionTextFooter(
generalGetString(if (devTools.value) R.string.show_dev_options else R.string.hide_dev_options) + " " +
generalGetString(R.string.developer_options)
)
SectionSpacer()
// val xftpSendEnabled = m.controller.appPrefs.xftpSendEnabled
// val xftpEnabled = remember { mutableStateOf(xftpSendEnabled.get()) }
// SectionView(generalGetString(R.string.settings_section_title_experimenta)) {
// SettingsPreferenceItem(Icons.Outlined.UploadFile, stringResource(R.string.settings_send_files_via_xftp), xftpSendEnabled, xftpEnabled) {
// withApi { m.controller.apiSetXFTPConfig(m.controller.getXFTPCfg()) }
// }
// }
// if (xftpEnabled.value) {
// SectionTextFooter(generalGetString(R.string.xftp_requires_v461))
// }
}
}

View File

@@ -1,37 +0,0 @@
package chat.simplex.app.views.usersettings
import SectionView
import androidx.compose.foundation.layout.*
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.UploadFile
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.model.ChatModel
import chat.simplex.app.views.helpers.withApi
@Composable
fun ExperimentalFeaturesView(chatModel: ChatModel) {
Column(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.Start
) {
Text(
stringResource(R.string.settings_experimental_features),
style = MaterialTheme.typography.h1,
modifier = Modifier.padding(start = 16.dp, bottom = 24.dp)
)
SectionView("") {
SettingsPreferenceItem(Icons.Outlined.UploadFile, stringResource(R.string.settings_send_files_via_xftp), chatModel.controller.appPrefs.xftpSendEnabled) {
withApi {
chatModel.controller.apiSetXFTPConfig(chatModel.controller.getXFTPCfg())
}
}
}
}
}

View File

@@ -1,36 +0,0 @@
package chat.simplex.app.views.usersettings
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
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.AppBarTitle
import chat.simplex.app.views.helpers.generalGetString
@Composable
fun IncognitoView() {
IncognitoLayout()
}
@Composable
fun IncognitoLayout() {
Column {
AppBarTitle(stringResource(R.string.settings_section_title_incognito))
Column(
Modifier
.verticalScroll(rememberScrollState())
.padding(horizontal = DEFAULT_PADDING),
verticalArrangement = Arrangement.spacedBy(20.dp)
) {
Text(generalGetString(R.string.incognito_info_protects))
Text(generalGetString(R.string.incognito_info_allows))
Text(generalGetString(R.string.incognito_info_share))
Text(generalGetString(R.string.incognito_info_find))
}
}
}

View File

@@ -1,214 +0,0 @@
package chat.simplex.app.views.usersettings
import SectionDivider
import SectionItemViewSpaceBetween
import SectionView
import SectionViewSelectable
import android.os.Build
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
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 androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import chat.simplex.app.*
import chat.simplex.app.R
import chat.simplex.app.model.ChatModel
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.*
import kotlinx.coroutines.*
import kotlin.collections.ArrayList
enum class NotificationsMode(private val requiresIgnoringBatterySinceSdk: Int) {
OFF(Int.MAX_VALUE), PERIODIC(Build.VERSION_CODES.M), SERVICE(Build.VERSION_CODES.S), /*INSTANT(Int.MAX_VALUE) - for Firebase notifications */;
val requiresIgnoringBattery
get() = requiresIgnoringBatterySinceSdk <= Build.VERSION.SDK_INT
companion object {
val default: NotificationsMode = SERVICE
}
}
enum class NotificationPreviewMode {
MESSAGE, CONTACT, HIDDEN;
companion object {
val default: NotificationPreviewMode = MESSAGE
}
}
@Composable
fun NotificationsSettingsView(
chatModel: ChatModel,
) {
val onNotificationPreviewModeSelected = { mode: NotificationPreviewMode ->
chatModel.controller.appPrefs.notificationPreviewMode.set(mode.name)
chatModel.notificationPreviewMode.value = mode
}
NotificationsSettingsLayout(
notificationsMode = chatModel.notificationsMode,
notificationPreviewMode = chatModel.notificationPreviewMode,
showPage = { page ->
ModalManager.shared.showModalCloseable(true) {
when (page) {
CurrentPage.NOTIFICATIONS_MODE -> NotificationsModeView(chatModel.notificationsMode) { changeNotificationsMode(it, chatModel) }
CurrentPage.NOTIFICATION_PREVIEW_MODE -> NotificationPreviewView(chatModel.notificationPreviewMode, onNotificationPreviewModeSelected)
}
}
},
)
}
enum class CurrentPage {
NOTIFICATIONS_MODE, NOTIFICATION_PREVIEW_MODE
}
@Composable
fun NotificationsSettingsLayout(
notificationsMode: State<NotificationsMode>,
notificationPreviewMode: State<NotificationPreviewMode>,
showPage: (CurrentPage) -> Unit,
) {
val modes = remember { notificationModes() }
val previewModes = remember { notificationPreviewModes() }
Column(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.Start,
) {
AppBarTitle(stringResource(R.string.notifications))
SectionView(null) {
SectionItemViewSpaceBetween({ showPage(CurrentPage.NOTIFICATIONS_MODE) }) {
Text(stringResource(R.string.settings_notifications_mode_title))
Spacer(Modifier.padding(horizontal = 10.dp))
Text(
modes.first { it.value == notificationsMode.value }.title,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = HighOrLowlight
)
}
SectionDivider()
SectionItemViewSpaceBetween({ showPage(CurrentPage.NOTIFICATION_PREVIEW_MODE) }) {
Text(stringResource(R.string.settings_notification_preview_mode_title))
Spacer(Modifier.padding(horizontal = 10.dp))
Text(
previewModes.first { it.value == notificationPreviewMode.value }.title,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = HighOrLowlight
)
}
}
}
}
@Composable
fun NotificationsModeView(
notificationsMode: State<NotificationsMode>,
onNotificationsModeSelected: (NotificationsMode) -> Unit,
) {
val modes = remember { notificationModes() }
Column(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.Start,
) {
AppBarTitle(stringResource(R.string.settings_notifications_mode_title).lowercase().capitalize(Locale.current))
SectionViewSelectable(null, notificationsMode, modes, onNotificationsModeSelected)
}
}
@Composable
fun NotificationPreviewView(
notificationPreviewMode: State<NotificationPreviewMode>,
onNotificationPreviewModeSelected: (NotificationPreviewMode) -> Unit,
) {
val previewModes = remember { notificationPreviewModes() }
Column(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.Start,
) {
AppBarTitle(stringResource(R.string.settings_notification_preview_title))
SectionViewSelectable(null, notificationPreviewMode, previewModes, onNotificationPreviewModeSelected)
}
}
// mode, name, description
private fun notificationModes(): List<ValueTitleDesc<NotificationsMode>> {
val res = ArrayList<ValueTitleDesc<NotificationsMode>>()
res.add(
ValueTitleDesc(
NotificationsMode.OFF,
generalGetString(R.string.notifications_mode_off),
generalGetString(R.string.notifications_mode_off_desc),
)
)
res.add(
ValueTitleDesc(
NotificationsMode.PERIODIC,
generalGetString(R.string.notifications_mode_periodic),
generalGetString(R.string.notifications_mode_periodic_desc),
)
)
res.add(
ValueTitleDesc(
NotificationsMode.SERVICE,
generalGetString(R.string.notifications_mode_service),
generalGetString(R.string.notifications_mode_service_desc),
)
)
return res
}
// preview mode, name, description
fun notificationPreviewModes(): List<ValueTitleDesc<NotificationPreviewMode>> {
val res = ArrayList<ValueTitleDesc<NotificationPreviewMode>>()
res.add(
ValueTitleDesc(
NotificationPreviewMode.MESSAGE,
generalGetString(R.string.notification_preview_mode_message),
generalGetString(R.string.notification_preview_mode_message_desc),
)
)
res.add(
ValueTitleDesc(
NotificationPreviewMode.CONTACT,
generalGetString(R.string.notification_preview_mode_contact),
generalGetString(R.string.notification_preview_mode_contact_desc),
)
)
res.add(
ValueTitleDesc(
NotificationPreviewMode.HIDDEN,
generalGetString(R.string.notification_preview_mode_hidden),
generalGetString(R.string.notification_display_mode_hidden_desc),
)
)
return res
}
fun changeNotificationsMode(mode: NotificationsMode, chatModel: ChatModel) {
chatModel.controller.appPrefs.notificationsMode.set(mode.name)
if (mode.requiresIgnoringBattery && !chatModel.controller.isIgnoringBatteryOptimizations(chatModel.controller.appContext)) {
chatModel.controller.appPrefs.backgroundServiceNoticeShown.set(false)
}
chatModel.notificationsMode.value = mode
SimplexService.StartReceiver.toggleReceiver(mode == NotificationsMode.SERVICE)
CoroutineScope(Dispatchers.Default).launch {
if (mode == NotificationsMode.SERVICE)
SimplexService.start(SimplexApp.context)
else
SimplexService.safeStopService(SimplexApp.context)
}
if (mode != NotificationsMode.PERIODIC) {
MessagesFetcherWorker.cancelAll()
}
chatModel.controller.showBackgroundServiceNoticeIfNeeded()
}

View File

@@ -1,339 +0,0 @@
package chat.simplex.app.views.usersettings
import SectionDivider
import SectionItemView
import SectionSpacer
import SectionTextFooter
import SectionView
import android.view.WindowManager
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Lock
import androidx.compose.material.icons.outlined.*
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.unit.dp
import androidx.fragment.app.FragmentActivity
import chat.simplex.app.R
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.ui.theme.SimplexGreen
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.helpers.DatabaseUtils.ksAppPassword
import chat.simplex.app.views.localauth.SetAppPasscodeView
enum class LAMode {
SYSTEM,
PASSCODE;
val text: String
get() = when (this) {
SYSTEM -> generalGetString(R.string.la_mode_system)
PASSCODE -> generalGetString(R.string.la_mode_passcode)
}
}
@Composable
fun PrivacySettingsView(
chatModel: ChatModel,
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
setPerformLA: (Boolean, FragmentActivity) -> Unit
) {
Column(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.Start
) {
val simplexLinkMode = chatModel.controller.appPrefs.simplexLinkMode
AppBarTitle(stringResource(R.string.your_privacy))
SectionView(stringResource(R.string.settings_section_title_device)) {
ChatLockItem(chatModel, showSettingsModal, setPerformLA)
SectionDivider()
val context = LocalContext.current
SettingsPreferenceItem(Icons.Outlined.VisibilityOff, stringResource(R.string.protect_app_screen), chatModel.controller.appPrefs.privacyProtectScreen) { on ->
if (on) {
(context as? FragmentActivity)?.window?.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE
)
} else {
(context as? FragmentActivity)?.window?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
}
}
SectionSpacer()
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.TravelExplore, stringResource(R.string.send_link_previews), chatModel.controller.appPrefs.privacyLinkPreviews)
SectionDivider()
SectionItemView {
SimpleXLinkOptions(chatModel.simplexLinkMode, onSelected = {
simplexLinkMode.set(it)
chatModel.simplexLinkMode.value = it
})
}
}
if (chatModel.simplexLinkMode.value == SimplexLinkMode.BROWSER) {
SectionTextFooter(stringResource(R.string.simplex_link_mode_browser_warning))
}
}
}
@Composable
private fun SimpleXLinkOptions(simplexLinkModeState: State<SimplexLinkMode>, onSelected: (SimplexLinkMode) -> Unit) {
val values = remember {
SimplexLinkMode.values().map {
when (it) {
SimplexLinkMode.DESCRIPTION -> it to generalGetString(R.string.simplex_link_mode_description)
SimplexLinkMode.FULL -> it to generalGetString(R.string.simplex_link_mode_full)
SimplexLinkMode.BROWSER -> it to generalGetString(R.string.simplex_link_mode_browser)
}
}
}
ExposedDropDownSettingRow(
generalGetString(R.string.simplex_link_mode),
values,
simplexLinkModeState,
icon = null,
enabled = remember { mutableStateOf(true) },
onSelected = onSelected
)
}
private val laDelays = listOf(10, 30, 60, 180, 0)
@Composable
fun SimplexLockView(
chatModel: ChatModel,
currentLAMode: SharedPreference<LAMode>,
setPerformLA: (Boolean, FragmentActivity) -> Unit
) {
val performLA = remember { chatModel.performLA }
val laMode = remember { chatModel.controller.appPrefs.laMode.state }
val laLockDelay = remember { chatModel.controller.appPrefs.laLockDelay }
val showChangePasscode = remember { derivedStateOf { performLA.value && currentLAMode.state.value == LAMode.PASSCODE } }
val activity = LocalContext.current as FragmentActivity
fun resetLAEnabled(onOff: Boolean) {
chatModel.controller.appPrefs.performLA.set(onOff)
chatModel.performLA.value = onOff
}
fun disableUnavailableLA() {
resetLAEnabled(false)
currentLAMode.set(LAMode.SYSTEM)
laUnavailableInstructionAlert()
}
fun toggleLAMode(toLAMode: LAMode) {
authenticate(
if (toLAMode == LAMode.SYSTEM) {
generalGetString(R.string.la_enter_app_passcode)
} else {
generalGetString(R.string.chat_lock)
},
generalGetString(R.string.change_lock_mode), activity
) { laResult ->
when (laResult) {
is LAResult.Error -> {
laFailedAlert()
}
is LAResult.Failed -> { /* Can be called multiple times on every failure */ }
LAResult.Success -> {
when (toLAMode) {
LAMode.SYSTEM -> {
authenticate(generalGetString(R.string.auth_enable_simplex_lock), promptSubtitle = "", activity, toLAMode) { laResult ->
when (laResult) {
LAResult.Success -> {
currentLAMode.set(toLAMode)
ksAppPassword.remove()
laTurnedOnAlert()
}
is LAResult.Unavailable, is LAResult.Error -> {
laFailedAlert()
}
is LAResult.Failed -> { /* Can be called multiple times on every failure */ }
}
}
}
LAMode.PASSCODE -> {
ModalManager.shared.showCustomModal { close ->
Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
SetAppPasscodeView(
submit = {
laLockDelay.set(30)
currentLAMode.set(toLAMode)
passcodeAlert(generalGetString(R.string.passcode_set))
},
cancel = {},
close
)
}
}
}
}
}
is LAResult.Unavailable -> disableUnavailableLA()
}
}
}
fun changeLAPassword() {
authenticate(generalGetString(R.string.la_current_app_passcode), generalGetString(R.string.la_change_app_passcode), activity) { laResult ->
when (laResult) {
LAResult.Success -> {
ModalManager.shared.showCustomModal { close ->
Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
SetAppPasscodeView(
submit = {
passcodeAlert(generalGetString(R.string.passcode_changed))
}, cancel = {
passcodeAlert(generalGetString(R.string.passcode_not_changed))
}, close
)
}
}
}
is LAResult.Error -> laFailedAlert()
is LAResult.Failed -> {}
is LAResult.Unavailable -> disableUnavailableLA()
}
}
}
Column(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.Start
) {
AppBarTitle(stringResource(R.string.chat_lock))
SectionView {
EnableLock(performLA) { performLAToggle ->
performLA.value = performLAToggle
chatModel.controller.appPrefs.laNoticeShown.set(true)
if (performLAToggle) {
when (currentLAMode.state.value) {
LAMode.SYSTEM -> {
setPerformLA(true, activity)
}
LAMode.PASSCODE -> {
ModalManager.shared.showCustomModal { close ->
Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
SetAppPasscodeView(
submit = {
laLockDelay.set(30)
chatModel.controller.appPrefs.performLA.set(true)
passcodeAlert(generalGetString(R.string.passcode_set))
},
cancel = {
resetLAEnabled(false)
}, close
)
}
}
}
}
} else {
setPerformLA(false, activity)
}
}
SectionDivider()
SectionItemView {
LockModeSelector(laMode) { newLAMode ->
if (laMode.value == newLAMode) return@LockModeSelector
if (chatModel.controller.appPrefs.performLA.get()) {
toggleLAMode(newLAMode)
} else {
currentLAMode.set(newLAMode)
}
}
}
if (performLA.value) {
SectionDivider()
SectionItemView {
LockDelaySelector(remember { laLockDelay.state }) { laLockDelay.set(it) }
}
if (showChangePasscode.value && laMode.value == LAMode.PASSCODE) {
SectionDivider()
SectionItemView({ changeLAPassword() }) {
Text(generalGetString(R.string.la_change_app_passcode))
}
}
}
}
}
}
@Composable
private fun EnableLock(performLA: MutableState<Boolean>, onCheckedChange: (Boolean) -> Unit) {
SectionItemView {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
stringResource(R.string.enable_lock), Modifier
.padding(end = 24.dp)
.fillMaxWidth()
.weight(1F)
)
Switch(
checked = performLA.value,
onCheckedChange = onCheckedChange,
colors = SwitchDefaults.colors(
checkedThumbColor = MaterialTheme.colors.primary,
uncheckedThumbColor = HighOrLowlight
)
)
}
}
}
@Composable
private fun LockModeSelector(state: State<LAMode>, onSelected: (LAMode) -> Unit) {
val values by remember { mutableStateOf(LAMode.values().map { it to it.text }) }
ExposedDropDownSettingRow(
generalGetString(R.string.lock_mode),
values,
state,
icon = null,
enabled = remember { mutableStateOf(true) },
onSelected = onSelected
)
}
@Composable
private fun LockDelaySelector(state: State<Int>, onSelected: (Int) -> Unit) {
val delays = remember { if (laDelays.contains(state.value)) laDelays else listOf(state.value) + laDelays }
val values by remember { mutableStateOf(delays.map { it to laDelayText(it) }) }
ExposedDropDownSettingRow(
generalGetString(R.string.lock_after),
values,
state,
icon = null,
enabled = remember { mutableStateOf(true) },
onSelected = onSelected
)
}
private fun laDelayText(t: Int): String {
val m = t / 60
val s = t % 60
return if (t == 0) {
generalGetString(R.string.la_immediately)
} else if (m == 0 || s != 0) {
// there are no options where both minutes and seconds are needed
generalGetString(R.string.la_seconds).format(s)
} else {
generalGetString(R.string.la_minutes).format(m)
}
}
private fun passcodeAlert(title: String) {
AlertManager.shared.showAlertMsg(
title = title,
text = generalGetString(R.string.la_please_remember_to_store_password)
)
}

View File

@@ -1,54 +0,0 @@
package chat.simplex.app.views.usersettings
import android.Manifest
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.model.ServerAddress.Companion.parseServerAddress
import chat.simplex.app.model.ServerCfg
import chat.simplex.app.ui.theme.DEFAULT_PADDING
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.newchat.QRCodeScanner
import com.google.accompanist.permissions.rememberPermissionState
@Composable
fun ScanProtocolServer(onNext: (ServerCfg) -> Unit) {
val cameraPermissionState = rememberPermissionState(permission = Manifest.permission.CAMERA)
LaunchedEffect(Unit) {
cameraPermissionState.launchPermissionRequest()
}
ScanProtocolServerLayout(onNext)
}
@Composable
private fun ScanProtocolServerLayout(onNext: (ServerCfg) -> Unit) {
Column(
Modifier
.fillMaxSize()
.padding(horizontal = DEFAULT_PADDING)
) {
AppBarTitle(stringResource(R.string.smp_servers_scan_qr), false)
Box(
Modifier
.fillMaxWidth()
.aspectRatio(ratio = 1F)
.padding(bottom = 12.dp)
) {
QRCodeScanner { text ->
val res = parseServerAddress(text)
if (res != null) {
onNext(ServerCfg(text, false, null, true))
} else {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.smp_servers_invalid_address),
text = generalGetString(R.string.smp_servers_check_address)
)
}
}
}
}
}

View File

@@ -1,550 +0,0 @@
package chat.simplex.app.views.usersettings
import SectionDivider
import SectionItemView
import SectionSpacer
import SectionView
import android.content.Context
import android.content.res.Configuration
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
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.*
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.vector.ImageVector
import androidx.compose.ui.platform.*
import androidx.compose.ui.res.painterResource
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.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.*
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.TerminalView
import chat.simplex.app.views.database.DatabaseView
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, FragmentActivity) -> Unit) {
val user = chatModel.currentUser.value
val stopped = chatModel.chatRunning.value == false
MaintainIncognitoState(chatModel)
if (user != null) {
val requireAuth = remember { chatModel.controller.appPrefs.performLA.state }
val context = LocalContext.current
SettingsLayout(
profile = user.profile,
stopped,
chatModel.chatDbEncrypted.value == true,
chatModel.incognito,
chatModel.controller.appPrefs.incognito,
user.displayName,
setPerformLA = setPerformLA,
showModal = { modalView -> { ModalManager.shared.showModal { modalView(chatModel) } } },
showSettingsModal = { modalView -> { ModalManager.shared.showModal(true) { modalView(chatModel) } } },
showSettingsModalWithSearch = { modalView ->
ModalManager.shared.showCustomModal { close ->
val search = rememberSaveable { mutableStateOf("") }
ModalView(
{ close() },
if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight,
endButtons = {
SearchTextField(Modifier.fillMaxWidth(), stringResource(android.R.string.search_go), alwaysVisible = true) { search.value = it }
},
content = { modalView(chatModel, search) })
}
},
showCustomModal = { modalView -> { ModalManager.shared.showCustomModal { close -> modalView(chatModel, 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)
}
)
}
}
}
},
)
}
}
val simplexTeamUri =
"simplex:/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D"
@Composable
fun SettingsLayout(
profile: LocalProfile,
stopped: Boolean,
encrypted: Boolean,
incognito: MutableState<Boolean>,
incognitoPref: SharedPreference<Boolean>,
userDisplayName: String,
setPerformLA: (Boolean, FragmentActivity) -> Unit,
showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
showSettingsModalWithSearch: (@Composable (ChatModel, MutableState<String>) -> Unit) -> Unit,
showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit),
showVersion: () -> Unit,
withAuth: (block: () -> Unit) -> Unit
) {
val uriHandler = LocalUriHandler.current
Surface(Modifier.fillMaxSize().verticalScroll(rememberScrollState())) {
Column(
Modifier
.fillMaxSize()
.background(if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight)
.padding(top = DEFAULT_PADDING)
) {
Text(
stringResource(R.string.your_settings),
style = MaterialTheme.typography.h1,
modifier = Modifier.padding(horizontal = DEFAULT_PADDING),
overflow = TextOverflow.Ellipsis,
)
Spacer(Modifier.height(30.dp))
SectionView(stringResource(R.string.settings_section_title_you)) {
SectionItemView(showCustomModal { chatModel, close -> UserProfileView(chatModel, close) }, 80.dp, disabled = stopped) {
ProfilePreview(profile, stopped = stopped)
}
SectionDivider()
val profileHidden = rememberSaveable { mutableStateOf(false) }
SettingsActionItem(Icons.Outlined.ManageAccounts, stringResource(R.string.your_chat_profiles), { withAuth { showSettingsModalWithSearch { it, search -> UserProfilesView(it, search, profileHidden) } } }, 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, stopped = stopped)
}
SectionSpacer()
SectionView(stringResource(R.string.settings_section_title_settings)) {
SettingsActionItem(Icons.Outlined.Bolt, stringResource(R.string.notifications), showSettingsModal { NotificationsSettingsView(it) }, disabled = stopped)
SectionDivider()
SettingsActionItem(Icons.Outlined.WifiTethering, stringResource(R.string.network_and_servers), showSettingsModal { NetworkAndServersView(it, showModal, showSettingsModal, showCustomModal) }, disabled = stopped)
SectionDivider()
SettingsActionItem(Icons.Outlined.Videocam, stringResource(R.string.settings_audio_video_calls), showSettingsModal { CallSettingsView(it, showModal) }, disabled = stopped)
SectionDivider()
SettingsActionItem(Icons.Outlined.Lock, stringResource(R.string.privacy_and_security), showSettingsModal { PrivacySettingsView(it, showSettingsModal, setPerformLA) }, disabled = stopped)
SectionDivider()
SettingsActionItem(Icons.Outlined.LightMode, stringResource(R.string.appearance_settings), showSettingsModal { AppearanceView(it) }, disabled = stopped)
SectionDivider()
DatabaseItem(encrypted, showSettingsModal { DatabaseView(it, showSettingsModal) }, stopped)
}
SectionSpacer()
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.Add, stringResource(R.string.whats_new), showCustomModal { _, close -> WhatsNewView(viaSettings = true, close) }, disabled = stopped)
SectionDivider()
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.openUriCatching(simplexTeamUri) }, textColor = MaterialTheme.colors.primary, disabled = stopped)
SectionDivider()
SettingsActionItem(Icons.Outlined.Email, stringResource(R.string.send_us_an_email), { uriHandler.openUriCatching("mailto:chat@simplex.chat") }, textColor = MaterialTheme.colors.primary)
}
SectionSpacer()
SectionView(stringResource(R.string.settings_section_title_support)) {
ContributeItem(uriHandler)
SectionDivider()
RateAppItem(uriHandler)
SectionDivider()
StarOnGithubItem(uriHandler)
}
SectionSpacer()
SectionView(stringResource(R.string.settings_section_title_develop)) {
SettingsActionItem(Icons.Outlined.Code, stringResource(R.string.settings_developer_tools), showSettingsModal { DeveloperView(it, showCustomModal, withAuth) })
SectionDivider()
// SettingsActionItem(Icons.Outlined.Science, stringResource(R.string.settings_experimental_features), showSettingsModal { ExperimentalFeaturesView(it) })
// SectionDivider()
AppVersionItem(showVersion)
}
}
}
}
@Composable
fun SettingsIncognitoActionItem(
incognitoPref: SharedPreference<Boolean>,
incognito: MutableState<Boolean>,
stopped: Boolean,
onClickInfo: () -> Unit,
) {
SettingsPreferenceItemWithInfo(
if (incognito.value) Icons.Filled.TheaterComedy else Icons.Outlined.TheaterComedy,
if (incognito.value) Indigo else HighOrLowlight,
stringResource(R.string.incognito),
stopped,
onClickInfo,
incognitoPref,
incognito
)
}
@Composable
fun MaintainIncognitoState(chatModel: ChatModel) {
// Cache previous value and once it changes in background, update it via API
var cachedIncognito by remember { mutableStateOf(chatModel.incognito.value) }
LaunchedEffect(chatModel.incognito.value) {
// Don't do anything if nothing changed
if (cachedIncognito == chatModel.incognito.value) return@LaunchedEffect
try {
chatModel.controller.apiSetIncognito(chatModel.incognito.value)
} catch (e: Exception) {
// Rollback the state
chatModel.controller.appPrefs.incognito.set(cachedIncognito)
// Crash the app
throw e
}
cachedIncognito = chatModel.incognito.value
}
}
@Composable private fun DatabaseItem(encrypted: Boolean, openDatabaseView: () -> Unit, stopped: Boolean) {
SectionItemView(openDatabaseView) {
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Row {
Icon(
Icons.Outlined.FolderOpen,
contentDescription = stringResource(R.string.database_passphrase_and_export),
tint = if (encrypted) HighOrLowlight else WarningOrange,
)
Spacer(Modifier.padding(horizontal = 4.dp))
Text(stringResource(R.string.database_passphrase_and_export))
}
if (stopped) {
Icon(
Icons.Filled.Report,
contentDescription = stringResource(R.string.chat_is_stopped),
tint = Color.Red,
modifier = Modifier.padding(end = 6.dp)
)
}
}
}
}
@Composable fun ChatPreferencesItem(showCustomModal: ((@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit)), stopped: Boolean) {
SettingsActionItem(
Icons.Outlined.ToggleOn,
stringResource(R.string.chat_preferences),
click = if (stopped) null else ({
withApi {
showCustomModal { m, close ->
PreferencesView(m, m.currentUser.value ?: return@showCustomModal, close)
}()
}
}),
disabled = stopped
)
}
@Composable
fun ChatLockItem(
chatModel: ChatModel,
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
setPerformLA: (Boolean, FragmentActivity) -> Unit
) {
val performLA = remember { chatModel.performLA }
val currentLAMode = remember { chatModel.controller.appPrefs.laMode }
SectionItemView(showSettingsModal { SimplexLockView(chatModel, currentLAMode, setPerformLA) }) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
if (performLA.value) Icons.Filled.Lock else Icons.Outlined.Lock,
contentDescription = stringResource(R.string.chat_lock),
tint = if (performLA.value) SimplexGreen else HighOrLowlight,
)
Spacer(Modifier.padding(horizontal = 4.dp))
Text(
stringResource(R.string.chat_lock), Modifier
.padding(end = 24.dp)
.fillMaxWidth()
.weight(1F)
)
Text(if (performLA.value) remember { currentLAMode.state }.value.text else generalGetString(androidx.compose.ui.R.string.off), color = HighOrLowlight)
}
}
}
@Composable private fun ContributeItem(uriHandler: UriHandler) {
SectionItemView({ uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat#contribute") }) {
Icon(
Icons.Outlined.Keyboard,
contentDescription = "GitHub",
tint = HighOrLowlight,
)
Spacer(Modifier.padding(horizontal = 4.dp))
Text(generalGetString(R.string.contribute), color = MaterialTheme.colors.primary)
}
}
@Composable private fun RateAppItem(uriHandler: UriHandler) {
SectionItemView({
runCatching { uriHandler.openUriCatching("market://details?id=chat.simplex.app") }
.onFailure { uriHandler.openUriCatching("https://play.google.com/store/apps/details?id=chat.simplex.app") }
}
) {
Icon(
Icons.Outlined.StarOutline,
contentDescription = "Google Play",
tint = HighOrLowlight,
)
Spacer(Modifier.padding(horizontal = 4.dp))
Text(generalGetString(R.string.rate_the_app), color = MaterialTheme.colors.primary)
}
}
@Composable private fun StarOnGithubItem(uriHandler: UriHandler) {
SectionItemView({ uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat") }) {
Icon(
painter = painterResource(id = R.drawable.ic_github),
contentDescription = "GitHub",
tint = HighOrLowlight,
)
Spacer(Modifier.padding(horizontal = 4.dp))
Text(generalGetString(R.string.star_on_github), color = MaterialTheme.colors.primary)
}
}
@Composable fun ChatConsoleItem(showTerminal: () -> Unit) {
SectionItemView(showTerminal) {
Icon(
painter = painterResource(id = R.drawable.ic_outline_terminal),
contentDescription = stringResource(R.string.chat_console),
tint = HighOrLowlight,
)
Spacer(Modifier.padding(horizontal = 4.dp))
Text(stringResource(R.string.chat_console))
}
}
@Composable fun InstallTerminalAppItem(uriHandler: UriHandler) {
SectionItemView({ uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat") }) {
Icon(
painter = painterResource(id = R.drawable.ic_github),
contentDescription = "GitHub",
tint = HighOrLowlight,
)
Spacer(Modifier.padding(horizontal = 4.dp))
Text(generalGetString(R.string.install_simplex_chat_for_terminal), color = MaterialTheme.colors.primary)
}
}
@Composable private fun AppVersionItem(showVersion: () -> Unit) {
SectionItemView(showVersion) { AppVersionText() }
}
@Composable fun AppVersionText() {
Text("v${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})")
}
@Composable fun ProfilePreview(profileOf: NamedChat, size: Dp = 60.dp, color: Color = MaterialTheme.colors.secondary, stopped: Boolean = false) {
ProfileImage(size = size, image = profileOf.image, color = color)
Spacer(Modifier.padding(horizontal = 4.dp))
Column {
Text(
profileOf.displayName,
style = MaterialTheme.typography.caption,
fontWeight = FontWeight.Bold,
color = if (stopped) HighOrLowlight else Color.Unspecified,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Text(
profileOf.fullName,
color = if (stopped) HighOrLowlight else Color.Unspecified,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
@Composable
fun SettingsActionItem(icon: ImageVector, text: String, click: (() -> Unit)? = null, textColor: Color = Color.Unspecified, iconColor: Color = HighOrLowlight, disabled: Boolean = false) {
SectionItemView(click, disabled = disabled) {
Icon(icon, text, tint = if (disabled) HighOrLowlight else iconColor)
Spacer(Modifier.padding(horizontal = 4.dp))
Text(text, color = if (disabled) HighOrLowlight else textColor)
}
}
@Composable
fun SettingsPreferenceItem(
icon: ImageVector,
text: String,
pref: SharedPreference<Boolean>,
prefState: MutableState<Boolean>? = null,
onChange: ((Boolean) -> Unit)? = null,
) {
SectionItemView {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(icon, text, tint = HighOrLowlight)
Spacer(Modifier.padding(horizontal = 4.dp))
SharedPreferenceToggle(text, pref, prefState, onChange)
}
}
}
@Composable
fun SettingsPreferenceItemWithInfo(
icon: ImageVector,
iconTint: Color,
text: String,
stopped: Boolean,
onClickInfo: () -> Unit,
pref: SharedPreference<Boolean>,
prefState: MutableState<Boolean>? = null
) {
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))
SharedPreferenceToggleWithIcon(text, Icons.Outlined.Info, stopped, onClickInfo, pref, prefState)
}
}
}
@Composable
fun PreferenceToggle(
text: String,
checked: Boolean,
onChange: (Boolean) -> Unit = {},
) {
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Text(text)
Spacer(Modifier.fillMaxWidth().weight(1f))
Switch(
checked = checked,
onCheckedChange = onChange,
colors = SwitchDefaults.colors(
checkedThumbColor = MaterialTheme.colors.primary,
uncheckedThumbColor = HighOrLowlight
)
)
}
}
@Composable
fun PreferenceToggleWithIcon(
text: String,
icon: ImageVector? = null,
iconColor: Color? = HighOrLowlight,
checked: Boolean,
onChange: (Boolean) -> Unit = {},
) {
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
if (icon != null) {
Icon(
icon,
null,
tint = iconColor ?: HighOrLowlight
)
Spacer(Modifier.padding(horizontal = 4.dp))
}
Text(text)
Spacer(Modifier.fillMaxWidth().weight(1f))
Switch(
checked = checked,
onCheckedChange = {
onChange(it)
},
colors = SwitchDefaults.colors(
checkedThumbColor = MaterialTheme.colors.primary,
uncheckedThumbColor = HighOrLowlight
)
)
}
}
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 is LAResult.Unavailable)
}
)
}
@Preview(showBackground = true)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
fun PreviewSettingsLayout() {
SimpleXTheme {
SettingsLayout(
profile = LocalProfile.sampleData,
stopped = false,
encrypted = false,
incognito = remember { mutableStateOf(false) },
incognitoPref = SharedPreference({ false }, {}),
userDisplayName = "Alice",
setPerformLA = { _, _ -> },
showModal = { {} },
showSettingsModal = { {} },
showSettingsModalWithSearch = { },
showCustomModal = { {} },
showVersion = {},
withAuth = {},
)
}
}

View File

@@ -1,152 +0,0 @@
package chat.simplex.app.views.usersettings
import android.content.res.Configuration
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
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.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.ChatModel
import chat.simplex.app.model.UserContactLinkRec
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.newchat.QRCode
@Composable
fun UserAddressView(chatModel: ChatModel) {
val cxt = LocalContext.current
UserAddressLayout(
userAddress = remember { chatModel.userAddress }.value,
createAddress = {
withApi {
val connReqContact = chatModel.controller.apiCreateUserAddress()
if (connReqContact != null) {
chatModel.userAddress.value = UserContactLinkRec(connReqContact)
}
}
},
share = { userAddress: String -> shareText(cxt, userAddress) },
acceptRequests = {
chatModel.userAddress.value?.let { address ->
ModalManager.shared.showModal(settings = true) { AcceptRequestsView(chatModel, address) }
}
},
deleteAddress = {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.delete_address__question),
text = generalGetString(R.string.all_your_contacts_will_remain_connected),
confirmText = generalGetString(R.string.delete_verb),
onConfirm = {
withApi {
chatModel.controller.apiDeleteUserAddress()
chatModel.userAddress.value = null
}
}
)
}
)
}
@Composable
fun UserAddressLayout(
userAddress: UserContactLinkRec?,
createAddress: () -> Unit,
share: (String) -> Unit,
acceptRequests: () -> Unit,
deleteAddress: () -> Unit
) {
Column(
Modifier.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.Top
) {
AppBarTitle(stringResource(R.string.your_contact_address), false)
Text(
stringResource(R.string.you_can_share_your_address_anybody_will_be_able_to_connect),
Modifier.padding(bottom = 12.dp),
lineHeight = 22.sp
)
Column(
Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING_HALF),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceEvenly
) {
if (userAddress == null) {
SimpleButton(stringResource(R.string.create_address), icon = Icons.Outlined.QrCode, click = createAddress)
} else {
QRCode(userAddress.connReqContact, Modifier.aspectRatio(1f))
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = 16.dp)
) {
SimpleButton(
stringResource(R.string.share_link),
icon = Icons.Outlined.Share,
click = { share(userAddress.connReqContact) })
SimpleButtonIconEnded(
stringResource(R.string.contact_requests),
icon = Icons.Outlined.ChevronRight,
click = acceptRequests
)
}
SimpleButton(
stringResource(R.string.delete_address),
icon = Icons.Outlined.Delete,
color = Color.Red,
click = deleteAddress
)
}
}
}
}
@Preview(showBackground = true)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
fun PreviewUserAddressLayoutNoAddress() {
SimpleXTheme {
UserAddressLayout(
userAddress = null,
createAddress = {},
share = { _ -> },
acceptRequests = {},
deleteAddress = {},
)
}
}
@Preview(showBackground = true)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
fun PreviewUserAddressLayoutAddressCreated() {
SimpleXTheme {
UserAddressLayout(
userAddress = UserContactLinkRec("https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D"),
createAddress = {},
share = { _ -> },
acceptRequests = {},
deleteAddress = {},
)
}
}

View File

@@ -1,29 +0,0 @@
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))
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))
}
}

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Android drawable generated by fa5ad-free project:
https://github.com/diwanoczko/fa5ad-free
Resource generated base on Font Awesome 5 Free icons set:
https://fontawesome.com/
All brand icons are trademarks of their respective owners.
Please do not use brand logos for any purpose except to represent the
company, product, or service to which they refer.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="23.25dp"
android:height="24dp"
android:viewportWidth="496"
android:viewportHeight="512">
<path
android:fillColor="#FF000000"
android:pathData="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"
/>
</vector>

View File

@@ -1,170 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M20,4H4C2.89,4 2,4.9 2,6v12c0,1.1 0.89,2 2,2h16c1.1,0 2,-0.9 2,-2V6C22,4.9 21.11,4 20,4zM20,18H4V8h16V18zM18,17h-6v-2h6V17zM7.5,17l-1.41,-1.41L8.67,13l-2.59,-2.59L7.5,9l4,4L7.5,17z"/>
</vector>

View File

@@ -1,50 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="accept_contact_button">اقبل</string>
<string name="about_simplex_chat">عن <xliff:g id="appNameFull"> ٍSimpleX </xliff:g></string>
<string name="a_plus_b">a + b</string>
<string name="accept">اقبل</string>
<string name="chat_item_ttl_week">اسبوع 1</string>
<string name="chat_item_ttl_month">شهر 1</string>
<string name="color_primary">لون تمييزي</string>
<string name="chat_item_ttl_day">يوم 1</string>
<string name="accept_feature">اقبل</string>
<string name="about_simplex">عن SimpleX</string>
<string name="above_then_preposition_continuation">أعلاه ، ثم:</string>
<string name="accept_call_on_lock_screen">اقبل</string>
<string name="delete_chat_profile_action_cannot_be_undone_warning">لا يمكن التراجع عن هذا الإجراء - سيتم فقد ملف التعريف وجهات الاتصال والرسائل والملفات الخاصة بك بشكل نهائي.</string>
<string name="alert_message_no_group">هذه المجموعة لم تعد موجودة.</string>
<string name="this_QR_code_is_not_a_link">رمز QR هذا ليس رابطًا!</string>
<string name="next_generation_of_private_messaging">الجيل القادم من الرسائل الخاصة</string>
<string name="delete_files_and_media_desc">لا يمكن التراجع عن هذا الإجراء - سيتم حذف جميع الملفات والوسائط المستلمة والمرسلة. ستبقى الصور منخفضة الدقة.</string>
<string name="enable_automatic_deletion_message">لا يمكن التراجع عن هذا الإجراء - سيتم حذف الرسائل المرسلة والمستلمة قبل التحديد. قد تأخذ عدة دقائق.</string>
<string name="messages_section_description">ينطبق هذا الإعداد على الرسائل الموجودة في ملف تعريف الدردشة الحالي الخاص بك</string>
<string name="the_messaging_and_app_platform_protecting_your_privacy_and_security">منصة الرسائل والتطبيقات تحمي خصوصيتك وأمنك.</string>
<string name="profile_is_only_shared_with_your_contacts">يتم مشاركة ملف التعريف مع جهات الاتصال الخاصة بك فقط.</string>
<string name="member_role_will_be_changed_with_notification">سيتم تغيير الدور إلى \"%s\". سيتم إبلاغ كل فرد في المجموعة.</string>
<string name="member_role_will_be_changed_with_invitation">سيتم تغيير الدور إلى \"%s\". سيتلقى العضو دعوة جديدة.</string>
<string name="smp_servers_per_user">خوادم الاتصالات الجديدة لملف تعريف الدردشة الحالي الخاص بك</string>
<string name="switch_receiving_address_desc">هذه الميزة تجريبية! ستعمل فقط إذا كان لدى العميل الآخر الإصدار 4.2 مثبتًا. يجب أن ترى الرسالة في المحادثة بمجرد اكتمال تغيير العنوان - يرجى التحقق من أنه لا يزال بإمكانك تلقي الرسائل من جهة الاتصال هذه (أو عضو المجموعة).</string>
<string name="this_link_is_not_a_valid_connection_link">هذا الارتباط ليس ارتباط اتصال صالح!</string>
<string name="allow_verb">يسمح</string>
<string name="smp_servers_preset_add">أضف خوادم محددة مسبقًا</string>
<string name="smp_servers_add_to_another_device">أضف إلى جهاز آخر</string>
<string name="users_delete_all_chats_deleted">سيتم حذف جميع الدردشات والرسائل - لا يمكن التراجع عن هذا!</string>
<string name="network_enable_socks_info">الوصول إلى الخوادم عبر بروكسي SOCKS على المنفذ 9050؟ يجب بدء تشغيل الوكيل قبل تمكين هذا الخيار.</string>
<string name="accept_requests">قبول طلبات</string>
<string name="smp_servers_add">إضافة خادم …</string>
<string name="network_settings">إعدادات الشبكة المتقدمة</string>
<string name="all_group_members_will_remain_connected">سيبقى جميع أعضاء المجموعة على اتصال.</string>
<string name="allow_disappearing_messages_only_if">السماح باختفاء الرسائل فقط إذا سمحت جهة الاتصال الخاصة بك بذلك.</string>
<string name="allow_irreversible_message_deletion_only_if">السماح بحذف الرسائل بشكل لا رجوع فيه فقط إذا سمحت لك جهة الاتصال بذلك.</string>
<string name="group_member_role_admin">مسؤل</string>
<string name="users_add">إضافة ملف التعريف</string>
<string name="allow_direct_messages">السماح بإرسال رسائل مباشرة إلى الأعضاء.</string>
<string name="accept_contact_incognito_button">قبول التخفي</string>
<string name="button_add_welcome_message">أضف رسالة ترحيب</string>
<string name="v4_3_improved_server_configuration_desc">أضف الخوادم عن طريق مسح رموز QR.</string>
<string name="v4_2_group_links_desc">يمكن للمسؤولين إنشاء روابط للانضمام إلى المجموعات.</string>
<string name="accept_connection_request__question">قبول طلب الاتصال؟</string>
<string name="clear_chat_warning">سيتم حذف جميع الرسائل - لا يمكن التراجع عن هذا! سيتم حذف الرسائل فقط من أجلك.</string>
<string name="callstatus_accepted">مكالمة مقبولة</string>
</resources>

View File

@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View File

@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View File

@@ -1,360 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="chat_item_ttl_month">1 mėnuo</string>
<string name="chat_item_ttl_week">1 savaitė</string>
<string name="about_simplex_chat">Apie <xliff:g id="appNameFull">SimpleX Chat</xliff:g></string>
<string name="chat_item_ttl_day">1 diena</string>
<string name="a_plus_b">a + b</string>
<string name="about_simplex">Apie SimpleX</string>
<string name="smp_servers_add">Pridėti serverį…</string>
<string name="v4_3_improved_server_configuration_desc">Pridėti serverius skenuojant QR kodus.</string>
<string name="appearance_settings">Išvaizda</string>
<string name="app_version_title">Programėlės versija</string>
<string name="app_version_name">Programėlės versija: v%s</string>
<string name="app_version_code">Programėlės darinys: %s</string>
<string name="accept_automatically">Automatiškai</string>
<string name="callstatus_calling">skambinama…</string>
<string name="callstatus_error">skambučio klaida</string>
<string name="call_already_ended">Skambutis jau baigtas!</string>
<string name="answer_call">Atsiliepti</string>
<string name="icon_descr_call_ended">Skambutis baigtas</string>
<string name="settings_section_title_calls">SKAMBUČIAI</string>
<string name="allow_your_contacts_irreversibly_delete">Leisti jūsų adresatams negrįžtamai ištrinti išsiųstas žinutes.</string>
<string name="back">Atgal</string>
<string name="settings_section_title_icon">PROGRAMĖLĖS PIKTOGRAMA</string>
<string name="incognito_random_profile_from_contact_description">Adresatui, iš kurio gavote šią nuorodą, bus išsiųstas atsitiktinis profilis</string>
<string name="chat_preferences_always">visada</string>
<string name="allow_your_contacts_to_send_voice_messages">Leisti jūsų adresatams siųsti balso žinutes.</string>
<string name="allow_irreversible_message_deletion_only_if">Leisti negrįžtamą žinučių ištrynimą tik tuo atveju, jei jūsų adresatas jums tai leidžia.</string>
<string name="allow_voice_messages_only_if">Leisti balso žinutes tik tuo atveju, jei jūsų adresatas jas leidžia.</string>
<string name="allow_verb">Leisti</string>
<string name="allow_voice_messages_question">Leisti balso žinutes\?</string>
<string name="bold">pusjuodis</string>
<string name="callstatus_ended">skambutis baigtas <xliff:g id="duration" example="01:15">%1$s</xliff:g></string>
<string name="icon_descr_audio_call">garso skambutis</string>
<string name="settings_audio_video_calls">Garso ir vaizdo skambučiai</string>
<string name="integrity_msg_bad_hash">bloga žinutės maiša</string>
<string name="integrity_msg_bad_id">blogas žinutės ID</string>
<string name="incognito_random_profile_description">Jūsų adresatui bus išsiųstas atsitiktinis profilis</string>
<string name="allow_disappearing_messages_only_if">Leisti išnykstančias žinutes tik tuo atveju, jei jūsų adresatas jas leidžia.</string>
<string name="clear_chat_warning">Visos žinutės bus ištrintos to neįmanoma bus atšaukti! Žinutės bus ištrintos TIK jums.</string>
<string name="allow_to_delete_messages">Leisti negrįžtamai ištrinti išsiųstas žinutes.</string>
<string name="allow_to_send_disappearing">Leisti siųsti išnykstančias žinutes.</string>
<string name="allow_to_send_voice">Leisti siųsti balso žinutes.</string>
<string name="allow_direct_messages">Leisti siųsti tiesiogines žinutes nariams.</string>
<string name="v4_6_audio_video_calls">Garso ir vaizdo skambučiai</string>
<string name="allow_your_contacts_to_send_disappearing_messages">Leisti jūsų adresatams siųsti išnykstančias žinutes.</string>
<string name="auth_unavailable">Tapatybės nustatymas neprieinamas</string>
<string name="impossible_to_recover_passphrase"><b>Turėkite omenyje</b>: jeigu prarasite slaptafrazę, NEBEGALĖSITE jos atkurti ar pakeisti.</string>
<string name="cancel_verb">Atsisakyti</string>
<string name="callstate_connecting">jungiamasi…</string>
<string name="connect_via_link_verb">Prisijungti</string>
<string name="server_connected">prisijungta</string>
<string name="server_connecting">jungiamasi</string>
<string name="display_name_connection_established">ryšys užmegztas</string>
<string name="display_name_connecting">jungiasi…</string>
<string name="connection_error">Ryšio klaida</string>
<string name="smp_server_test_connect">Prisijungti</string>
<string name="notification_contact_connected">Prisijungė</string>
<string name="connect_button">Prisijungti</string>
<string name="group_member_status_connected">prisijungė</string>
<string name="contact_connection_pending">jungiasi…</string>
<string name="group_connection_pending">jungiasi…</string>
<string name="icon_descr_server_status_connected">Prisijungta</string>
<string name="confirm_verb">Patvirtinti</string>
<string name="configure_ICE_servers">Konfigūruoti ICE serverius</string>
<string name="callstate_connected">prisijungta</string>
<string name="rcv_group_event_member_connected">prisijungė</string>
<string name="info_row_connection">Ryšys</string>
<string name="confirm_database_upgrades">Patvirtinti duomenų bazių naujinimus</string>
<string name="group_member_status_connecting">jungiasi</string>
<string name="network_session_mode_entity">Ryšys</string>
<string name="database_passphrase_will_be_updated">Duomenų bazės šifravimo slaptafrazė bus atnaujinta.</string>
<string name="core_version">Branduolio versija: v%s</string>
<string name="delete_address__question">Ištrinti adresą\?</string>
<string name="save_preferences_question">Įrašyti nuostatas\?</string>
<string name="create_profile">Sukurti profilį</string>
<string name="callstatus_rejected">atmestas skambutis</string>
<string name="callstate_ended">užbaigtas</string>
<string name="onboarding_notifications_mode_title">Privatūs pranešimai</string>
<string name="ignore">Nepaisyti</string>
<string name="icon_descr_flip_camera">Apversti kamerą</string>
<string name="icon_descr_call_rejected">Atmestas skambutis</string>
<string name="privacy_and_security">Privatumas ir saugumas</string>
<string name="settings_section_title_device">ĮRENGINYS</string>
<string name="settings_section_title_help">PAGALBA</string>
<string name="encrypt_database">Šifruoti</string>
<string name="remove_passphrase">Šalinti</string>
<string name="button_delete_group">Ištrinti grupę</string>
<string name="v4_2_group_links">Grupių nuorodos</string>
<string name="server_error">klaida</string>
<string name="simplex_link_mode_description">Aprašas</string>
<string name="error_saving_smp_servers">Klaida įrašant SMP serverius</string>
<string name="error_setting_network_config">Klaida atnaujinant tinklo konfigūraciją</string>
<string name="failed_to_create_user_title">Klaida kuriant profilį!</string>
<string name="failed_to_parse_chat_title">Nepavyko įkelti pokalbio</string>
<string name="failed_to_parse_chats_title">Nepavyko įkelti pokalbių</string>
<string name="failed_to_active_user_title">Klaida perjungiant profilį!</string>
<string name="error_sending_message">Klaida siunčiant žinutę</string>
<string name="error_creating_address">Klaida kuriant adresą</string>
<string name="error_joining_group">Klaida prisijungiant prie grupės</string>
<string name="error_receiving_file">Klaida gaunant failą</string>
<string name="error_changing_address">Klaida keičiant adresą</string>
<string name="error_deleting_contact">Klaida ištrinant adresatą</string>
<string name="error_deleting_group">Klaida ištrinant grupę</string>
<string name="smp_server_test_disconnect">Atsijungti</string>
<string name="error_deleting_user">Klaida ištrinant naudotojo profilį</string>
<string name="notification_display_mode_hidden_desc">Slėpti adresatą ir žinutę</string>
<string name="copy_verb">Kopijuoti</string>
<string name="reply_verb">Atsakyti</string>
<string name="delete_message__question">Ištrinti žinutę\?</string>
<string name="icon_descr_file">Failas</string>
<string name="maximum_supported_file_size">Šiuo metu didžiausias palaikomas failo dydis yra <xliff:g id="maxFileSize">%1$s</xliff:g>.</string>
<string name="file_not_found">Failas nerastas</string>
<string name="file_saved">Failas įrašytas</string>
<string name="icon_descr_server_status_disconnected">Atsijungta</string>
<string name="icon_descr_server_status_error">Klaida</string>
<string name="ask_your_contact_to_enable_voice">Paprašykite adresato, kad įjungtų balso žinučių siuntimą.</string>
<string name="reset_verb">Atstatyti</string>
<string name="delete_group_menu_action">Ištrinti</string>
<string name="image_descr_qr_code">QR kodas</string>
<string name="icon_descr_help">pagalba</string>
<string name="icon_descr_email">El. paštas</string>
<string name="create_one_time_link">Sukurti vienkartinio pakvietimo nuorodą</string>
<string name="scan_code">Skenuoti kodą</string>
<string name="database_passphrase_and_export">Duomenų bazės slaptafrazė ir eksportavimas</string>
<string name="smp_servers_delete_server">Ištrinti serverį</string>
<string name="error_saving_ICE_servers">Klaida įrašant ICE serverius</string>
<string name="save_servers_button">Įrašyti</string>
<string name="files_and_media_section">Failai ir medija</string>
<string name="delete_files_and_media_all">Ištrinti visus failus</string>
<string name="delete_files_and_media_question">Ištrinti failus ir mediją\?</string>
<string name="delete_archive">Ištrinti archyvą</string>
<string name="delete_chat_archive_question">Ištrinti pokalbio archyvą\?</string>
<string name="snd_group_event_group_profile_updated">grupės profilis atnaujintas</string>
<string name="info_row_group">Grupė</string>
<string name="users_delete_question">Ištrinti pokalbio profilį\?</string>
<string name="network_options_revert">Sugrąžinti</string>
<string name="network_options_save">Įrašyti</string>
<string name="feature_enabled">įjungta</string>
<string name="delete_after">Ištrinti po</string>
<string name="error_updating_user_privacy">Klaida atnaujinant naudotojo privatumą</string>
<string name="simplex_service_notification_text">Gaunamos žinutės…</string>
<string name="hide_notification">Slėpti</string>
<string name="save_verb">Įrašyti</string>
<string name="delete_verb">Ištrinti</string>
<string name="edit_verb">Taisyti</string>
<string name="hide_verb">Slėpti</string>
<string name="reveal_verb">Atskleisti</string>
<string name="for_me_only">Ištrinti man</string>
<string name="for_everybody">Visiems</string>
<string name="icon_descr_edited">taisyta</string>
<string name="observer_cant_send_message_desc">Susisiekite su grupės administratoriumi.</string>
<string name="button_delete_contact">Ištrinti adresatą</string>
<string name="delete_contact_question">Ištrinti adresatą\?</string>
<string name="create_group">Sukurti slaptą grupę</string>
<string name="from_gallery_button">Iš galerijos</string>
<string name="toast_permission_denied">Leidimas atmestas!</string>
<string name="reject_contact_button">Atmesti</string>
<string name="incorrect_code">Neteisingas saugumo kodas!</string>
<string name="smp_servers_save">Įrašyti serverius</string>
<string name="smp_save_servers_question">Įrašyti serverius\?</string>
<string name="create_address">Sukurti adresą</string>
<string name="delete_address">Ištrinti adresą</string>
<string name="error_saving_user_password">Klaida įrašant naudotojo slaptažodį</string>
<string name="create_profile_button">Sukurti</string>
<string name="how_it_works">Kaip tai veikia</string>
<string name="incoming_audio_call">Gaunamas garso skambutis</string>
<string name="incoming_video_call">Gaunamas vaizdo skambutis</string>
<string name="reject">Atmesti</string>
<string name="no_call_on_lock_screen">Išjungti</string>
<string name="settings_experimental_features">Eksperimentinės ypatybės</string>
<string name="database_passphrase">Duomenų bazės slaptafrazė</string>
<string name="delete_database">Ištrinti duomenų bazę</string>
<string name="export_database">Eksportuoti duomenų bazę</string>
<string name="import_database">Importuoti duomenų bazę</string>
<string name="set_password_to_export_desc">Duomenų bazė yra šifruota naudojant atsitiktinę slaptafrazę. Prieš eksportuodami duomenų bazę, pakeiskite slaptafrazę.</string>
<string name="error_exporting_chat_database">Klaida eksportuojant pokalbio duomenų bazę</string>
<string name="error_stopping_chat">Klaida sustabdant pokalbį</string>
<string name="import_database_question">Importuoti pokalbio duomenų bazę\?</string>
<string name="current_passphrase">Dabartinė slaptafrazė…</string>
<string name="encrypted_with_random_passphrase">Duomenų bazė yra šifruota naudojant atsitiktinę slaptafrazę, kurią galite pakeisti.</string>
<string name="encrypt_database_question">Šifruoti duomenų bazę\?</string>
<string name="error_with_info">Klaida: %s</string>
<string name="button_edit_group_profile">Taisyti grupės profilį</string>
<string name="group_link">Grupės nuoroda</string>
<string name="button_create_group_link">Sukurti nuorodą</string>
<string name="info_row_database_id">Duomenų bazės ID</string>
<string name="error_changing_role">Klaida keičiant vaidmenį</string>
<string name="conn_level_desc_direct">tiesioginis</string>
<string name="conn_level_desc_indirect">netiesioginis (<xliff:g id="conn_level">%1$s</xliff:g>)</string>
<string name="create_secret_group_title">Sukurti slaptą grupę</string>
<string name="error_saving_group_profile">Klaida įrašant grupės profilį</string>
<string name="dont_show_again">Daugiau neberodyti</string>
<string name="theme_dark">Tamsus</string>
<string name="reset_color">Atstatyti spalvas</string>
<string name="save_color">Įrašyti spalvą</string>
<string name="group_preferences">Grupės nuostatos</string>
<string name="full_deletion">Ištrinti visiems</string>
<string name="direct_messages">Tiesioginės žinutės</string>
<string name="v4_4_disappearing_messages">Išnykstančios žinutės</string>
<string name="timed_messages">Išnykstančios žinutės</string>
<string name="v4_4_french_interface">Sąsaja prancūzų kalba</string>
<string name="v4_3_improved_privacy_and_security">Patobulintas privatumas ir saugumas</string>
<string name="num_contacts_selected">Pažymėta adresatų: %d</string>
<string name="delete_chat_profile_question">Ištrinti pokalbio profilį\?</string>
<string name="error_importing_database">Klaida importuojant pokalbio duomenų bazę</string>
<string name="delete_messages_after">Ištrinti žinutes po</string>
<string name="database_will_be_encrypted">Duomenų bazė bus šifruota.</string>
<string name="alert_title_no_group">Grupė nerasta!</string>
<string name="error_deleting_link_for_group">Klaida ištrinant grupės nuorodą</string>
<string name="error_updating_link_for_group">Klaida atnaujinant grupės nuorodą</string>
<string name="database_upgrade">Duomenų bazės naujinimas</string>
<string name="incompatible_database_version">Nesuderinama duomenų bazės versija</string>
<string name="group_member_status_group_deleted">grupė ištrinta</string>
<string name="save_and_update_group_profile">Įrašyti ir atnaujinti grupės profilį</string>
<string name="delete_messages">Ištrinti žinutes</string>
<string name="error_changing_message_deletion">Klaida keičiant nustatymą</string>
<string name="error_encrypting_database">Klaida šifruojant duomenų bazę</string>
<string name="encrypted_database">Šifruota duomenų bazė</string>
<string name="restore_database_alert_confirm">Atkurti</string>
<string name="create_group_link">Sukurti grupės nuorodą</string>
<string name="delete_link">Ištrinti nuorodą</string>
<string name="delete_link_question">Ištrinti nuorodą\?</string>
<string name="error_creating_link_for_group">Klaida kuriant grupės nuorodą</string>
<string name="how_simplex_works">Kaip <xliff:g id="appName">SimpleX</xliff:g> veikia</string>
<string name="database_encrypted">Duomenų bazė šifruota!</string>
<string name="group_member_status_creator">kūrėjas</string>
<string name="delete_group_question">Ištrinti grupę\?</string>
<string name="user_hide">Slėpti</string>
<string name="error_saving_file">Klaida įrašant failą</string>
<string name="hide_dev_options">Slėpti:</string>
<string name="database_error">Duomenų bazės klaida</string>
<string name="file_with_path">Failas: %s</string>
<string name="profile_password">Profilio slaptažodis</string>
<string name="remove_member_confirmation">Šalinti</string>
<string name="v4_3_improved_server_configuration">Patobulinta serverio konfigūracija</string>
<string name="delete_chat_profile">Ištrinti pokalbio profilį</string>
<string name="delete_profile">Ištrinti profilį</string>
<string name="share_one_time_link">Sukurti vienkartinio pakvietimo nuorodą</string>
<string name="delete_contact_menu_action">Ištrinti</string>
<string name="save_and_notify_contact">Įrašyti ir pranešti adresatui</string>
<string name="enable_automatic_deletion_question">Įjungti automatinį žinučių ištrynimą\?</string>
<string name="error_deleting_database">Klaida ištrinant pokalbio duomenų bazę</string>
<string name="error_starting_chat">Klaida pradedant pokalbį</string>
<string name="hide_profile">Slėpti profilį</string>
<string name="import_database_confirmation">Importuoti</string>
<string name="save_and_notify_contacts">Įrašyti ir pranešti adresatams</string>
<string name="callstate_starting">pradedama…</string>
<string name="use_chat">Naudoti pokalbį</string>
<string name="icon_descr_speaker_off">Išjungti garsiakalbį</string>
<string name="icon_descr_speaker_on">Įjungti garsiakalbį</string>
<string name="alert_title_skipped_messages">Praleistos žinutės</string>
<string name="settings_section_title_settings">NUSTATYMAI</string>
<string name="theme_system">Sistemos</string>
<string name="unknown_message_format">nežinomas žinutės formatas</string>
<string name="simplex_link_contact">SimpleX adresato adresas</string>
<string name="simplex_link_group">SimpleX grupės nuoroda</string>
<string name="simplex_link_invitation">SimpleX vienkartinis pakvietimas</string>
<string name="simplex_link_mode">SimpleX nuorodos</string>
<string name="settings_notification_preview_mode_title">Rodyti peržiūrą</string>
<string name="auth_unlock">Atrakinti</string>
<string name="share_verb">Bendrinti</string>
<string name="tap_to_start_new_chat">Bakstelėkite, norėdami pradėti naują pokalbį</string>
<string name="share_file">Bendrinti failą…</string>
<string name="icon_descr_settings">Nustatymai</string>
<string name="show_QR_code">Rodyti QR kodą</string>
<string name="share_invitation_link">Bendrinti pakvietimo nuorodą</string>
<string name="smp_servers">SMP serveriai</string>
<string name="use_simplex_chat_servers__question">Naudoti <xliff:g id="appNameFull">SimpleX Chat</xliff:g> serverius\?</string>
<string name="network_disable_socks">Naudoti tiesioginį interneto ryšį\?</string>
<string name="update_network_settings_confirmation">Atnaujinti</string>
<string name="update_network_settings_question">Atnaujinti tinklo nustatymus\?</string>
<string name="ntf_channel_messages">SimpleX Chat žinutės</string>
<string name="ntf_channel_calls">SimpleX Chat skambučiai</string>
<string name="notification_preview_mode_message_desc">Rodyti adresatą ir žinutę</string>
<string name="notification_preview_mode_contact_desc">Rodyti tik adresatą</string>
<string name="auth_stop_chat">Stabdyti pokalbį</string>
<string name="share_message">Bendrinti žinutę…</string>
<string name="use_camera_button">Naudoti kamerą</string>
<string name="thank_you_for_installing_simplex">Dėkojame, kad įdiegėte <xliff:g id="appNameFull">SimpleX Chat</xliff:g>!</string>
<string name="smp_servers_use_server">Naudoti serverį</string>
<string name="network_socks_toggle">Naudoti SOCKS įgaliotąjį serverį (prievadas 9050)</string>
<string name="network_enable_socks">Naudoti SOCKS įgaliotąjį serverį\?</string>
<string name="core_simplexmq_version">simplexmq: v%s (%2s)</string>
<string name="share_link">Bendrinti nuorodą</string>
<string name="tap_to_activate_profile">Bakstelėkite, norėdami aktyvuoti profilį.</string>
<string name="language_system">Sistemos</string>
<string name="v4_6_audio_video_calls_descr">Bluetooth palaikymas ir kiti patobulinimai.</string>
<string name="group_invitation_tap_to_join">Bakstelėkite, norėdami prisijungti</string>
<string name="show_call_on_lock_screen">Rodyti</string>
<string name="stop_chat_confirmation">Stabdyti</string>
<string name="upgrade_and_open_chat">Naujinti ir atverti pokalbį</string>
<string name="switch_verb">Perjungti</string>
<string name="icon_descr_received_msg_status_unread">neskaityta</string>
<string name="show_dev_options">Rodyti:</string>
<string name="stop_chat_question">Stabdyti pokalbį\?</string>
<string name="update_database">Atnaujinti</string>
<string name="unknown_error">Nežinoma klaida</string>
<string name="add_contact_or_create_group">Pradėti naują pokalbį</string>
<string name="callstate_waiting_for_answer">laukiama, kol bus atsiliepta…</string>
<string name="callstate_waiting_for_confirmation">laukiama patvirtinimo…</string>
<string name="icon_descr_video_off">Išjungti vaizdą</string>
<string name="icon_descr_video_on">Įjungti vaizdą</string>
<string name="your_privacy">Jūsų privatumas</string>
<string name="settings_section_title_you">JŪS</string>
<string name="wrong_passphrase_title">Neteisinga slaptafrazė!</string>
<string name="app_name"><xliff:g id="appName">SimpleX</xliff:g></string>
<string name="sender_you_pronoun">jūs</string>
<string name="description_via_group_link">per grupės nuorodą</string>
<string name="description_via_contact_address_link">per adresato adreso nuorodą</string>
<string name="description_via_one_time_link">per vienkartinę nuorodą</string>
<string name="simplex_link_connection">per <xliff:g id="serverHost" example="smp.simplex.im">%1$s</xliff:g></string>
<string name="simplex_link_mode_browser">Per naršyklę</string>
<string name="waiting_for_file">Laukiama failo</string>
<string name="voice_message_with_duration">Balso žinutė (<xliff:g id="duration">%1$s</xliff:g>)</string>
<string name="voice_message_send_text">Balso žinutė…</string>
<string name="view_security_code">Rodyti saugumo kodą</string>
<string name="icon_descr_address"><xliff:g id="appName">SimpleX</xliff:g> adresas</string>
<string name="your_SMP_servers">Jūsų SMP serveriai</string>
<string name="your_ICE_servers">Jūsų ICE serveriai</string>
<string name="users_add">Pridėti profilį</string>
<string name="users_delete_all_chats_deleted">Visi pokalbiai ir žinutės bus ištrinti to neįmanoma bus atšaukti!</string>
<string name="welcome">Sveiki!</string>
<string name="attach">Pridėti</string>
<string name="observer_cant_send_message_title">Jūs negalite siųsti žinučių!</string>
<string name="image_descr_simplex_logo"><xliff:g id="appName">SimpleX</xliff:g> logotipas</string>
<string name="your_settings">Jūsų nustatymai</string>
<string name="smp_servers_your_server">Jūsų serveris</string>
<string name="smp_servers_your_server_address">Jūsų serverio adresas</string>
<string name="smp_servers_add_to_another_device">Pridėti į kitą įrenginį</string>
<string name="using_simplex_chat_servers">Naudojami <xliff:g id="appNameFull">SimpleX Chat</xliff:g> serveriai.</string>
<string name="network_settings">Išplėstiniai tinklo nustatymai</string>
<string name="your_current_profile">Jūsų dabartinis profilis</string>
<string name="your_profile_is_stored_on_device_and_shared_only_with_contacts_simplex_cannot_see_it">Jūsų profilis yra saugomas jūsų įrenginyje ir bendrinamas tik su jūsų adresatais.
\n
\n<xliff:g id="appName">SimpleX</xliff:g> serveriai negali matyti jūsų profilio.</string>
<string name="icon_descr_video_call">vaizdo skambutis</string>
<string name="your_calls">Jūsų skambučiai</string>
<string name="webrtc_ice_servers">WebRTC ICE serveriai</string>
<string name="your_ice_servers">Jūsų ICE serveriai</string>
<string name="snd_group_event_user_left">jūs išėjote</string>
<string name="group_member_role_admin">administratorius</string>
<string name="incognito_random_profile">Jūsų atsitiktinis profilis</string>
<string name="chat_preferences_you_allow">Jūs leidžiate</string>
<string name="your_preferences">Jūsų nuostatos</string>
<string name="v4_3_voice_messages">Balso žinutės</string>
<string name="voice_messages">Balso žinutės</string>
<string name="chat_preferences_yes">taip</string>
<string name="you_joined_this_group">Jūs prisijungėte prie šios grupės</string>
<string name="database_downgrade_warning">Įspėjimas: galite prarasti tam tikrus duomenis!</string>
<string name="you_will_stop_receiving_messages_from_this_group_chat_history_will_be_preserved">Jūs nustosite gauti žinutes iš šios grupės. Pokalbio istorija bus išsaugota.</string>
<string name="group_info_member_you">jūs: <xliff:g id="group_info_you">%1$s</xliff:g></string>
<string name="personal_welcome">Sveiki, <xliff:g>%1$s</xliff:g>!</string>
<string name="your_chats">Jūsų pokalbiai</string>
<string name="wrong_passphrase">Neteisinga duomenų bazės slaptafrazė</string>
<string name="icon_descr_video_snd_complete">Vaizdo įrašas išsiųstas</string>
<string name="voice_message">Balso žinutė</string>
<string name="icon_descr_simplex_team"><xliff:g id="appName">SimpleX</xliff:g> komanda</string>
<string name="whats_new">Kas naujo</string>
</resources>

View File

@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View File

@@ -1,50 +0,0 @@
buildscript {
Properties localProperties = new Properties()
if (rootProject.file('local.properties').canRead()) {
localProperties.load(rootProject.file("local.properties").newDataInputStream())
}
ext {
compose_version = localProperties['compose_version'] ?: '1.2.0-beta02'
kotlin_version = localProperties['kotlin_version'] ?: '1.6.21'
gradle_plugin_version = localProperties['gradle_plugin_version'] ?: '7.2.0'
abi_filter = localProperties['abi_filter'] ?: 'arm64-v8a'
// Name that will be shown for debug build. By default it is from strings
app_name = localProperties['app_name'] ?: "@string/app_name"
// Whether the app is debuggable or not. Specify `false` if you want good performance in debug builds
enable_debuggable = localProperties['debuggable'] ?: true
// Ending part of package name.
// Provide, for example, `application_id_suffix=.debug` in local.properties
// to allow debug & release versions to coexist
application_id_suffix = localProperties['application_id_suffix'] ?: ''
// Compression level for debug AND release apk. 0 = disable compression. Max is 9
compression_level = localProperties['compression_level'] ?: '0'
// NOTE: If you need a different version of something, provide it in `local.properties`
// like so: compose_version=123, or gradle_plugin_version=1.2.3, etc
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:$gradle_plugin_version"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:1.3.2"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version "$gradle_plugin_version" apply false
id 'com.android.library' version "$gradle_plugin_version" apply false
id 'org.jetbrains.kotlin.android' version "$kotlin_version" apply false
id 'org.jetbrains.kotlin.plugin.serialization' version "$kotlin_version"
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@@ -1,16 +0,0 @@
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "SimpleX"
include ':app'

View File

@@ -14,9 +14,28 @@ class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
logger.debug("AppDelegate: didFinishLaunchingWithOptions")
application.registerForRemoteNotifications()
if #available(iOS 17.0, *) { trackKeyboard() }
return true
}
@available(iOS 17.0, *)
private func trackKeyboard() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
@available(iOS 17.0, *)
@objc func keyboardWillShow(_ notification: Notification) {
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
ChatModel.shared.keyboardHeight = keyboardFrame.cgRectValue.height
}
}
@available(iOS 17.0, *)
@objc func keyboardWillHide(_ notification: Notification) {
ChatModel.shared.keyboardHeight = 0
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let token = deviceToken.map { String(format: "%02hhx", $0) }.joined()
logger.debug("AppDelegate: didRegisterForRemoteNotificationsWithDeviceToken \(token)")
@@ -36,13 +55,12 @@ class AppDelegate: NSObject, UIApplicationDelegate {
didReceiveRemoteNotification userInfo: [AnyHashable : Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
logger.debug("AppDelegate: didReceiveRemoteNotification")
print("*** userInfo", userInfo)
let m = ChatModel.shared
if let ntfData = userInfo["notificationData"] as? [AnyHashable : Any],
m.notificationMode != .off {
if let verification = ntfData["verification"] as? String,
let nonce = ntfData["nonce"] as? String {
if let token = ChatModel.shared.deviceToken {
if let token = m.deviceToken {
logger.debug("AppDelegate: didReceiveRemoteNotification: verification, confirming \(verification)")
Task {
do {
@@ -62,7 +80,7 @@ class AppDelegate: NSObject, UIApplicationDelegate {
}
} else if let checkMessages = ntfData["checkMessages"] as? Bool, checkMessages {
logger.debug("AppDelegate: didReceiveRemoteNotification: checkMessages")
if appStateGroupDefault.get().inactive {
if appStateGroupDefault.get().inactive && m.ntfEnablePeriodic {
receiveMessages(completionHandler)
} else {
completionHandler(.noData)
@@ -76,7 +94,7 @@ class AppDelegate: NSObject, UIApplicationDelegate {
}
func applicationWillTerminate(_ application: UIApplication) {
logger.debug("AppDelegate: applicationWillTerminate")
logger.debug("DEBUGGING: AppDelegate: applicationWillTerminate")
ChatModel.shared.filesToDelete.forEach {
removeFile($0)
}
@@ -102,6 +120,10 @@ class AppDelegate: NSObject, UIApplicationDelegate {
BGManager.shared.receiveMessages(complete)
}
static func keepScreenOn(_ on: Bool) {
UIApplication.shared.isIdleTimerDisabled = on
}
}
class SceneDelegate: NSObject, ObservableObject, UIWindowSceneDelegate {

View File

@@ -18,6 +18,7 @@ struct ContentView: View {
@Binding var userAuthorized: Bool?
@Binding var canConnectCall: Bool
@Binding var lastSuccessfulUnlock: TimeInterval?
@Binding var showInitializationView: Bool
@AppStorage(DEFAULT_SHOW_LA_NOTICE) private var prefShowLANotice = false
@AppStorage(DEFAULT_LA_NOTICE_SHOWN) private var prefLANoticeShown = false
@AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false
@@ -27,6 +28,17 @@ struct ContentView: View {
@State private var showWhatsNew = false
@State private var showChooseLAMode = false
@State private var showSetPasscode = false
@State private var chatListActionSheet: ChatListActionSheet? = nil
private enum ChatListActionSheet: Identifiable {
case planAndConnectSheet(sheet: PlanAndConnectActionSheet)
var id: String {
switch self {
case let .planAndConnectSheet(sheet): return sheet.id
}
}
}
var body: some View {
ZStack {
@@ -69,6 +81,8 @@ struct ContentView: View {
@ViewBuilder private func contentView() -> some View {
if prefPerformLA && userAuthorized != true {
lockButton()
} else if chatModel.chatDbStatus == nil && showInitializationView {
initializationView()
} else if let status = chatModel.chatDbStatus, status != .ok {
DatabaseErrorView(status: status)
} else if !chatModel.v3DBMigration.startChat {
@@ -77,6 +91,11 @@ struct ContentView: View {
if case .onboardingComplete = step,
chatModel.currentUser != nil {
mainView()
.actionSheet(item: $chatListActionSheet) { sheet in
switch sheet {
case let .planAndConnectSheet(sheet): return planAndConnectActionSheet(sheet, dismiss: false)
}
}
} else {
OnboardingView(onboarding: step)
}
@@ -104,6 +123,14 @@ struct ContentView: View {
Button(action: runAuthenticate) { Label("Unlock", systemImage: "lock") }
}
private func initializationView() -> some View {
VStack {
ProgressView().scaleEffect(2)
Text("Opening database…")
.padding()
}
}
private func mainView() -> some View {
ZStack(alignment: .top) {
ChatListView(showSettings: $showSettings).privacySensitive(protectScreen)
@@ -121,10 +148,15 @@ struct ContentView: View {
}
}
prefShowLANotice = true
connectViaUrl()
}
.onChange(of: chatModel.appOpenUrl) { _ in connectViaUrl() }
.sheet(isPresented: $showWhatsNew) {
WhatsNewView()
}
if chatModel.setDeliveryReceipts {
SetDeliveryReceiptsView()
}
IncomingCallView()
}
.onContinueUserActivity("INStartCallIntent", perform: processUserActivity)
@@ -165,10 +197,13 @@ struct ContentView: View {
}
private func runAuthenticate() {
logger.debug("DEBUGGING: runAuthenticate")
if !prefPerformLA {
userAuthorized = true
} else {
logger.debug("DEBUGGING: before dismissAllSheets")
dismissAllSheets(animated: false) {
logger.debug("DEBUGGING: in dismissAllSheets callback")
chatModel.chatId = nil
justAuthenticate()
}
@@ -178,8 +213,8 @@ struct ContentView: View {
private func justAuthenticate() {
userAuthorized = false
let laMode = privacyLocalAuthModeDefault.get()
authenticate(reason: NSLocalizedString("Unlock app", comment: "authentication reason")) { laResult in
logger.debug("authenticate callback: \(String(describing: laResult))")
authenticate(reason: NSLocalizedString("Unlock app", comment: "authentication reason"), selfDestruct: true) { laResult in
logger.debug("DEBUGGING: authenticate callback: \(String(describing: laResult))")
switch (laResult) {
case .success:
userAuthorized = true
@@ -248,36 +283,32 @@ struct ContentView: View {
secondaryButton: .cancel()
)
}
}
func connectViaUrl() {
let m = ChatModel.shared
if let url = m.appOpenUrl {
m.appOpenUrl = nil
AlertManager.shared.showAlert(connectViaUrlAlert(url))
func connectViaUrl() {
dismissAllSheets() {
let m = ChatModel.shared
if let url = m.appOpenUrl {
m.appOpenUrl = nil
var path = url.path
if (path == "/contact" || path == "/invitation") {
path.removeFirst()
let link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)")
planAndConnect(
link,
showAlert: showPlanAndConnectAlert,
showActionSheet: { chatListActionSheet = .planAndConnectSheet(sheet: $0) },
dismiss: false,
incognito: nil
)
} else {
AlertManager.shared.showAlert(Alert(title: Text("Error: URL is invalid")))
}
}
}
}
}
func connectViaUrlAlert(_ url: URL) -> Alert {
var path = url.path
logger.debug("ChatListView.connectViaUrlAlert path: \(path)")
if (path == "/contact" || path == "/invitation") {
path.removeFirst()
let action: ConnReqType = path == "contact" ? .contact : .invitation
let link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)")
let title: LocalizedStringKey
if case .contact = action { title = "Connect via contact link?" }
else { title = "Connect via one-time link?" }
return Alert(
title: Text(title),
message: Text("Your profile will be sent to the contact that you received this link from"),
primaryButton: .default(Text("Connect")) {
connectViaLink(link)
},
secondaryButton: .cancel()
)
} else {
return Alert(title: Text("Error: URL is invalid"))
private func showPlanAndConnectAlert(_ alert: PlanAndConnectAlert) {
AlertManager.shared.showAlert(planAndConnectAlert(alert, dismiss: false))
}
}

View File

@@ -1,20 +1,11 @@
//import UIKit
import SimpleXChat
let s = """
{
"contactConnection" : {
"contactConnection" : {
"viaContactUri" : false,
"pccConnId" : 456,
"pccAgentConnId" : "cTdjbmR4ZzVzSmhEZHdzMQ==",
"pccConnStatus" : "new",
"updatedAt" : "2022-04-24T11:59:23.703162Z",
"createdAt" : "2022-04-24T11:59:23.703162Z"
}
}
}
let s =
"""
{}
"""
//let s = "\"2022-04-24T11:59:23.703162Z\""
//let json = getJSONDecoder()
//let d = s.data(using: .utf8)!
//print (try! json.decode(ChatInfo.self, from: d))
let json = getJSONDecoder()
let d = s.data(using: .utf8)!
print (try! json.decode(APIResponse.self, from: d))

View File

@@ -36,8 +36,9 @@ class AudioRecorder {
try av.setActive(true)
let settings: [String : Any] = [
AVFormatIDKey: kAudioFormatMPEG4AAC,
AVSampleRateKey: 12000,
AVEncoderBitRateKey: 12000,
AVSampleRateKey: 16000,
AVEncoderBitRateKey: 32000,
AVEncoderBitRateStrategyKey: AVAudioBitRateStrategy_VariableConstrained,
AVNumberOfChannelsKey: 1
]
let url = getAppFilePath(fileName)
@@ -45,6 +46,7 @@ class AudioRecorder {
audioRecorder?.record(forDuration: MAX_VOICE_MESSAGE_LENGTH)
await MainActor.run {
AppDelegate.keepScreenOn(true)
recordingTimer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { timer in
guard let time = self.audioRecorder?.currentTime else { return }
self.onTimer?(time)
@@ -56,6 +58,10 @@ class AudioRecorder {
}
return nil
} catch let error {
await MainActor.run {
AppDelegate.keepScreenOn(false)
}
try? av.setCategory(AVAudioSession.Category.soloAmbient)
logger.error("AudioRecorder startAudioRecording error \(error.localizedDescription)")
return .error(error.localizedDescription)
}
@@ -70,6 +76,8 @@ class AudioRecorder {
timer.invalidate()
}
recordingTimer = nil
AppDelegate.keepScreenOn(false)
try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.soloAmbient)
}
private func checkPermission() async -> Bool {
@@ -102,32 +110,59 @@ class AudioPlayer: NSObject, AVAudioPlayerDelegate {
self.onFinishPlayback = onFinishPlayback
}
func start(fileName: String) {
let url = getAppFilePath(fileName)
audioPlayer = try? AVAudioPlayer(contentsOf: url)
func start(fileSource: CryptoFile, at: TimeInterval?) {
let url = getAppFilePath(fileSource.filePath)
if let cfArgs = fileSource.cryptoArgs {
if let data = try? readCryptoFile(path: url.path, cryptoArgs: cfArgs) {
audioPlayer = try? AVAudioPlayer(data: data)
}
} else {
audioPlayer = try? AVAudioPlayer(contentsOf: url)
}
audioPlayer?.delegate = self
audioPlayer?.prepareToPlay()
if let at = at {
audioPlayer?.currentTime = at
}
audioPlayer?.play()
playbackTimer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { timer in
if self.audioPlayer?.isPlaying ?? false {
AppDelegate.keepScreenOn(true)
guard let time = self.audioPlayer?.currentTime else { return }
self.onTimer?(time)
AudioPlayer.changeAudioSession(true)
} else {
AudioPlayer.changeAudioSession(false)
}
}
}
func pause() {
audioPlayer?.pause()
AppDelegate.keepScreenOn(false)
}
func play() {
audioPlayer?.play()
}
func seek(_ to: TimeInterval) {
if audioPlayer?.isPlaying == true {
audioPlayer?.pause()
audioPlayer?.currentTime = to
audioPlayer?.play()
} else {
audioPlayer?.currentTime = to
}
self.onTimer?(to)
}
func stop() {
if let player = audioPlayer {
player.stop()
AppDelegate.keepScreenOn(false)
AudioPlayer.changeAudioSession(false)
}
audioPlayer = nil
if let timer = playbackTimer {
@@ -136,6 +171,24 @@ class AudioPlayer: NSObject, AVAudioPlayerDelegate {
playbackTimer = nil
}
static func changeAudioSession(_ playback: Bool) {
// When there is a audio recording, setting any other category will disable sound
if AVAudioSession.sharedInstance().category == .playAndRecord {
return
}
if playback {
if AVAudioSession.sharedInstance().category != .playback {
logger.log("AudioSession: playback")
try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, options: .duckOthers)
}
} else {
if AVAudioSession.sharedInstance().category != .soloAmbient {
logger.log("AudioSession: soloAmbient")
try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.soloAmbient)
}
}
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
stop()
self.onFinishPlayback?()

View File

@@ -34,6 +34,10 @@ class BGManager {
}
func schedule() {
if !ChatModel.shared.ntfEnableLocal {
logger.debug("BGManager.schedule: disabled")
return
}
logger.debug("BGManager.schedule")
let request = BGAppRefreshTaskRequest(identifier: receiveTaskId)
request.earliestBeginDate = Date(timeIntervalSinceNow: bgRefreshInterval)
@@ -45,6 +49,10 @@ class BGManager {
}
private func handleRefresh(_ task: BGAppRefreshTask) {
if !ChatModel.shared.ntfEnableLocal {
logger.debug("BGManager.handleRefresh: disabled")
return
}
logger.debug("BGManager.handleRefresh")
schedule()
if appStateGroupDefault.get().inactive {

View File

@@ -11,8 +11,41 @@ import Combine
import SwiftUI
import SimpleXChat
actor TerminalItems {
private var terminalItems: [TerminalItem] = []
static let shared = TerminalItems()
func items() -> [TerminalItem] {
terminalItems
}
func add(_ item: TerminalItem) async {
addTermItem(&terminalItems, item)
let m = ChatModel.shared
if m.showingTerminal {
await MainActor.run {
addTermItem(&m.terminalItems, item)
}
}
}
func addCommand(_ start: Date, _ cmd: ChatCommand, _ resp: ChatResponse) async {
await add(.cmd(start, cmd))
await add(.resp(.now, resp))
}
}
private func addTermItem(_ items: inout [TerminalItem], _ item: TerminalItem) {
if items.count >= 200 {
items.removeFirst()
}
items.append(item)
}
final class ChatModel: ObservableObject {
@Published var onboardingStage: OnboardingStage?
@Published var setDeliveryReceipts = false
@Published var v3DBMigration: V3DBMigrationState = v3DBMigrationDefault.get()
@Published var currentUser: User?
@Published var users: [UserInfo] = []
@@ -29,9 +62,11 @@ final class ChatModel: ObservableObject {
// current chat
@Published var chatId: String?
@Published var reversedChatItems: [ChatItem] = []
var chatItemStatuses: Dictionary<Int64, CIStatus> = [:]
@Published var chatToTop: String?
@Published var groupMembers: [GroupMember] = []
@Published var groupMembers: [GMember] = []
// items in the terminal view
@Published var showingTerminal = false
@Published var terminalItems: [TerminalItem] = []
@Published var userAddress: UserContactLink?
@Published var chatItemTTL: ChatItemTTL = .none
@@ -41,10 +76,9 @@ final class ChatModel: ObservableObject {
@Published var tokenRegistered = false
@Published var tokenStatus: NtfTknStatus?
@Published var notificationMode = NotificationsMode.off
@Published var notificationPreview: NotificationPreviewMode? = ntfPreviewModeGroupDefault.get()
@Published var incognito: Bool = incognitoGroupDefault.get()
@Published var notificationPreview: NotificationPreviewMode = ntfPreviewModeGroupDefault.get()
// pending notification actions
@Published var ntfContactRequest: ChatId?
@Published var ntfContactRequest: NTFContactRequest?
@Published var ntfCallInvitationAction: (ChatId, NtfCallAction)?
// current WebRTC call
@Published var callInvitations: Dictionary<ChatId, RcvCallInvitation> = [:]
@@ -57,6 +91,8 @@ final class ChatModel: ObservableObject {
@Published var stopPreviousRecPlay: URL? = nil // coordinates currently playing source
@Published var draft: ComposeState?
@Published var draftChatId: String?
// tracks keyboard height via subscription in AppDelegate
@Published var keyboardHeight: CGFloat = 0
var messageDelivery: Dictionary<Int64, () -> Void> = [:]
@@ -66,6 +102,14 @@ final class ChatModel: ObservableObject {
static var ok: Bool { ChatModel.shared.chatDbStatus == .ok }
var ntfEnableLocal: Bool {
notificationMode == .off || ntfEnableLocalGroupDefault.get()
}
var ntfEnablePeriodic: Bool {
notificationMode == .periodic || ntfEnablePeriodicGroupDefault.get()
}
func getUser(_ userId: Int64) -> User? {
currentUser?.userId == userId
? currentUser
@@ -109,6 +153,20 @@ final class ChatModel: ObservableObject {
}
}
func getGroupChat(_ groupId: Int64) -> Chat? {
chats.first { chat in
if case let .group(groupInfo) = chat.chatInfo {
return groupInfo.groupId == groupId
} else {
return false
}
}
}
func getGroupMember(_ groupMemberId: Int64) -> GMember? {
groupMembers.first { $0.groupMemberId == groupMemberId }
}
private func getChatIndex(_ id: String) -> Int? {
chats.firstIndex(where: { $0.id == id })
}
@@ -122,6 +180,7 @@ final class ChatModel: ObservableObject {
func updateChatInfo(_ cInfo: ChatInfo) {
if let i = getChatIndex(cInfo.id) {
chats[i].chatInfo = cInfo
chats[i].created = Date.now
}
}
@@ -133,6 +192,14 @@ final class ChatModel: ObservableObject {
updateChat(.direct(contact: contact), addMissing: contact.directOrUsed)
}
func updateContactConnectionStats(_ contact: Contact, _ connectionStats: ConnectionStats) {
var updatedConn = contact.activeConn
updatedConn?.connectionStats = connectionStats
var updatedContact = contact
updatedContact.activeConn = updatedConn
updateContact(updatedContact)
}
func updateGroup(_ groupInfo: GroupInfo) {
updateChat(.group(groupInfo: groupInfo))
}
@@ -238,21 +305,18 @@ final class ChatModel: ObservableObject {
}
private func _upsertChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) -> Bool {
if let i = reversedChatItems.firstIndex(where: { $0.id == cItem.id }) {
let ci = reversedChatItems[i]
if let i = getChatItemIndex(cItem) {
withAnimation {
self.reversedChatItems[i] = cItem
self.reversedChatItems[i].viewTimestamp = .now
// on some occasions the confirmation of message being accepted by the server (tick)
// arrives earlier than the response from API, and item remains without tick
if case .sndNew = cItem.meta.itemStatus {
self.reversedChatItems[i].meta.itemStatus = ci.meta.itemStatus
}
_updateChatItem(at: i, with: cItem)
}
return false
} else {
withAnimation(itemAnimation()) {
reversedChatItems.insert(cItem, at: hasLiveDummy ? 1 : 0)
var ci = cItem
if let status = chatItemStatuses.removeValue(forKey: ci.id), case .sndNew = ci.meta.itemStatus {
ci.meta.itemStatus = status
}
reversedChatItems.insert(ci, at: hasLiveDummy ? 1 : 0)
}
return true
}
@@ -264,7 +328,26 @@ final class ChatModel: ObservableObject {
}
}
}
func updateChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem, status: CIStatus? = nil) {
if chatId == cInfo.id, let i = getChatItemIndex(cItem) {
withAnimation {
_updateChatItem(at: i, with: cItem)
}
} else if let status = status {
chatItemStatuses.updateValue(status, forKey: cItem.id)
}
}
private func _updateChatItem(at i: Int, with cItem: ChatItem) {
reversedChatItems[i] = cItem
reversedChatItems[i].viewTimestamp = .now
}
func getChatItemIndex(_ cItem: ChatItem) -> Int? {
reversedChatItems.firstIndex(where: { $0.id == cItem.id })
}
func removeChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) {
if cItem.isRcvNew {
decreaseUnreadCounter(cInfo)
@@ -277,7 +360,7 @@ final class ChatModel: ObservableObject {
}
// remove from current chat
if chatId == cInfo.id {
if let i = reversedChatItems.firstIndex(where: { $0.id == cItem.id }) {
if let i = getChatItemIndex(cItem) {
_ = withAnimation {
self.reversedChatItems.remove(at: i)
}
@@ -357,7 +440,7 @@ final class ChatModel: ObservableObject {
func markChatItemsRead(_ cInfo: ChatInfo, aboveItem: ChatItem? = nil) {
if let cItem = aboveItem {
if chatId == cInfo.id, let i = reversedChatItems.firstIndex(where: { $0.id == cItem.id }) {
if chatId == cInfo.id, let i = getChatItemIndex(cItem) {
markCurrentChatRead(fromIndex: i)
_updateChat(cInfo.id) { chat in
var unreadBelow = 0
@@ -397,6 +480,7 @@ final class ChatModel: ObservableObject {
}
// clear current chat
if chatId == cInfo.id {
chatItemStatuses = [:]
reversedChatItems = []
}
}
@@ -405,7 +489,7 @@ final class ChatModel: ObservableObject {
// update preview
decreaseUnreadCounter(cInfo)
// update current chat
if chatId == cInfo.id, let i = reversedChatItems.firstIndex(where: { $0.id == cItem.id }) {
if chatId == cInfo.id, let i = getChatItemIndex(cItem) {
markChatItemRead_(i)
}
}
@@ -428,18 +512,18 @@ final class ChatModel: ObservableObject {
}
}
func increaseUnreadCounter(user: User) {
func increaseUnreadCounter(user: any UserLike) {
changeUnreadCounter(user: user, by: 1)
NtfManager.shared.incNtfBadgeCount()
}
func decreaseUnreadCounter(user: User, by: Int = 1) {
func decreaseUnreadCounter(user: any UserLike, by: Int = 1) {
changeUnreadCounter(user: user, by: -by)
NtfManager.shared.decNtfBadgeCount(by: by)
}
private func changeUnreadCounter(user: User, by: Int) {
if let i = users.firstIndex(where: { $0.user.id == user.id }) {
private func changeUnreadCounter(user: any UserLike, by: Int) {
if let i = users.firstIndex(where: { $0.user.userId == user.userId }) {
users[i].unreadCount += by
}
}
@@ -449,14 +533,62 @@ final class ChatModel: ObservableObject {
users.filter { !$0.user.activeUser }.reduce(0, { unread, next -> Int in unread + next.unreadCount })
}
func getPrevChatItem(_ ci: ChatItem) -> ChatItem? {
if let i = reversedChatItems.firstIndex(where: { $0.id == ci.id }), i < reversedChatItems.count - 1 {
return reversedChatItems[i + 1]
// this function analyses "connected" events and assumes that each member will be there only once
func getConnectedMemberNames(_ chatItem: ChatItem) -> (Int, [String]) {
var count = 0
var ns: [String] = []
if let ciCategory = chatItem.mergeCategory,
var i = getChatItemIndex(chatItem) {
while i < reversedChatItems.count {
let ci = reversedChatItems[i]
if ci.mergeCategory != ciCategory { break }
if let m = ci.memberConnected {
ns.append(m.displayName)
}
count += 1
i += 1
}
}
return (count, ns)
}
// returns the index of the passed item and the next item (it has smaller index)
func getNextChatItem(_ ci: ChatItem) -> (Int?, ChatItem?) {
if let i = getChatItemIndex(ci) {
(i, i > 0 ? reversedChatItems[i - 1] : nil)
} else {
return nil
(nil, nil)
}
}
// returns the index of the first item in the same merged group (the first hidden item)
// and the previous visible item with another merge category
func getPrevShownChatItem(_ ciIndex: Int?, _ ciCategory: CIMergeCategory?) -> (Int?, ChatItem?) {
guard var i = ciIndex else { return (nil, nil) }
let fst = reversedChatItems.count - 1
while i < fst {
i = i + 1
let ci = reversedChatItems[i]
if ciCategory == nil || ciCategory != ci.mergeCategory {
return (i - 1, ci)
}
}
return (i, nil)
}
// returns the previous member in the same merge group and the count of members in this group
func getPrevHiddenMember(_ member: GroupMember, _ range: ClosedRange<Int>) -> (GroupMember?, Int) {
var prevMember: GroupMember? = nil
var memberIds: Set<Int64> = []
for i in range {
if case let .groupRcv(m) = reversedChatItems[i].chatDir {
if prevMember == nil && m.groupMemberId != member.groupMemberId { prevMember = m }
memberIds.insert(m.groupMemberId)
}
}
return (prevMember, memberIds.count)
}
func popChat(_ id: String) {
if let i = getChatIndex(id) {
popChat_(i)
@@ -491,13 +623,14 @@ final class ChatModel: ObservableObject {
}
// update current chat
if chatId == groupInfo.id {
if let i = groupMembers.firstIndex(where: { $0.id == member.id }) {
if let i = groupMembers.firstIndex(where: { $0.groupMemberId == member.groupMemberId }) {
withAnimation(.default) {
self.groupMembers[i] = member
self.groupMembers[i].wrapped = member
self.groupMembers[i].created = Date.now
}
return false
} else {
withAnimation { groupMembers.append(member) }
withAnimation { groupMembers.append(GMember(member)) }
return true
}
} else {
@@ -505,6 +638,15 @@ final class ChatModel: ObservableObject {
}
}
func updateGroupMemberConnectionStats(_ groupInfo: GroupInfo, _ member: GroupMember, _ connectionStats: ConnectionStats) {
if var conn = member.activeConn {
conn.connectionStats = connectionStats
var updatedMember = member
updatedMember.activeConn = conn
_ = upsertGroupMember(groupInfo, updatedMember)
}
}
func unreadChatItemCounts(itemsInView: Set<String>) -> UnreadChatItemCounts {
var i = 0
var totalBelow = 0
@@ -529,21 +671,25 @@ final class ChatModel: ObservableObject {
}
func setContactNetworkStatus(_ contact: Contact, _ status: NetworkStatus) {
networkStatuses[contact.activeConn.agentConnId] = status
if let conn = contact.activeConn {
networkStatuses[conn.agentConnId] = status
}
}
func contactNetworkStatus(_ contact: Contact) -> NetworkStatus {
networkStatuses[contact.activeConn.agentConnId] ?? .unknown
}
func addTerminalItem(_ item: TerminalItem) {
if terminalItems.count >= 500 {
terminalItems.remove(at: 0)
if let conn = contact.activeConn {
networkStatuses[conn.agentConnId] ?? .unknown
} else {
.unknown
}
terminalItems.append(item)
}
}
struct NTFContactRequest {
var incognito: Bool
var chatId: String
}
struct UnreadChatItemCounts {
var totalBelow: Int
var unreadBelow: Int
@@ -567,6 +713,14 @@ final class Chat: ObservableObject, Identifiable {
self.chatStats = chatStats
}
func copy(chatInfo: ChatInfo? = nil, chatItems: [ChatItem]? = nil, chatStats: ChatStats? = nil) -> Chat {
Chat(
chatInfo: chatInfo ?? self.chatInfo,
chatItems: chatItems ?? self.chatItems,
chatStats: chatStats ?? self.chatStats
)
}
var userCanSend: Bool {
switch chatInfo {
case .direct: return true
@@ -593,40 +747,18 @@ final class Chat: ObservableObject, Identifiable {
public static var sampleData: Chat = Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: [])
}
enum NetworkStatus: Decodable, Equatable {
case unknown
case connected
case disconnected
case error(String)
final class GMember: ObservableObject, Identifiable {
@Published var wrapped: GroupMember
var created = Date.now
var statusString: LocalizedStringKey {
get {
switch self {
case .connected: return "connected"
case .error: return "error"
default: return "connecting"
}
}
init(_ member: GroupMember) {
self.wrapped = member
}
var statusExplanation: LocalizedStringKey {
get {
switch self {
case .connected: return "You are connected to the server used to receive messages from this contact."
case let .error(err): return "Trying to connect to the server used to receive messages from this contact (error: \(err))."
default: return "Trying to connect to the server used to receive messages from this contact."
}
}
}
var imageName: String {
get {
switch self {
case .unknown: return "circle.dotted"
case .connected: return "circle.fill"
case .disconnected: return "ellipsis.circle.fill"
case .error: return "exclamationmark.circle.fill"
}
}
}
var id: String { wrapped.id }
var groupId: Int64 { wrapped.groupId }
var groupMemberId: Int64 { wrapped.groupMemberId }
var displayName: String { wrapped.displayName }
var viewId: String { get { "\(wrapped.id) \(created.timeIntervalSince1970)" } }
static let sampleData = GMember(GroupMember.sampleData)
}

View File

@@ -11,42 +11,43 @@ import SimpleXChat
import SwiftUI
import AVKit
func getLoadedFilePath(_ file: CIFile?) -> String? {
if let fileName = getLoadedFileName(file) {
return getAppFilePath(fileName).path
}
return nil
}
func getLoadedFileName(_ file: CIFile?) -> String? {
if let file = file,
file.loaded,
let fileName = file.filePath {
return fileName
func getLoadedFileSource(_ file: CIFile?) -> CryptoFile? {
if let file = file, file.loaded {
return file.fileSource
}
return nil
}
func getLoadedImage(_ file: CIFile?) -> UIImage? {
let loadedFilePath = getLoadedFilePath(file)
if let loadedFilePath = loadedFilePath, let fileName = file?.filePath {
let filePath = getAppFilePath(fileName)
if let fileSource = getLoadedFileSource(file) {
let filePath = getAppFilePath(fileSource.filePath)
do {
let data = try Data(contentsOf: filePath)
let data = try getFileData(filePath, fileSource.cryptoArgs)
let img = UIImage(data: data)
try img?.setGifFromData(data, levelOfIntegrity: 1.0)
return img
do {
try img?.setGifFromData(data, levelOfIntegrity: 1.0)
return img
} catch {
return UIImage(data: data)
}
} catch {
return UIImage(contentsOfFile: loadedFilePath)
return nil
}
}
return nil
}
func getFileData(_ path: URL, _ cfArgs: CryptoFileArgs?) throws -> Data {
if let cfArgs = cfArgs {
return try readCryptoFile(path: path.path, cryptoArgs: cfArgs)
} else {
return try Data(contentsOf: path)
}
}
func getLoadedVideo(_ file: CIFile?) -> URL? {
let loadedFilePath = getLoadedFilePath(file)
if loadedFilePath != nil, let fileName = file?.filePath {
let filePath = getAppFilePath(fileName)
if let fileSource = getLoadedFileSource(file) {
let filePath = getAppFilePath(fileSource.filePath)
if FileManager.default.fileExists(atPath: filePath.path) {
return filePath
}
@@ -54,18 +55,18 @@ func getLoadedVideo(_ file: CIFile?) -> URL? {
return nil
}
func saveAnimImage(_ image: UIImage) -> String? {
func saveAnimImage(_ image: UIImage) -> CryptoFile? {
let fileName = generateNewFileName("IMG", "gif")
guard let imageData = image.imageData else { return nil }
return saveFile(imageData, fileName)
return saveFile(imageData, fileName, encrypted: privacyEncryptLocalFilesGroupDefault.get())
}
func saveImage(_ uiImage: UIImage) -> String? {
func saveImage(_ uiImage: UIImage) -> CryptoFile? {
let hasAlpha = imageHasAlpha(uiImage)
let ext = hasAlpha ? "png" : "jpg"
if let imageDataResized = resizeImageToDataSize(uiImage, maxDataSize: MAX_IMAGE_SIZE, hasAlpha: hasAlpha) {
let fileName = generateNewFileName("IMG", ext)
return saveFile(imageDataResized, fileName)
return saveFile(imageDataResized, fileName, encrypted: privacyEncryptLocalFilesGroupDefault.get())
}
return nil
}
@@ -157,13 +158,19 @@ func imageHasAlpha(_ img: UIImage) -> Bool {
return false
}
func saveFileFromURL(_ url: URL) -> String? {
let savedFile: String?
func saveFileFromURL(_ url: URL, encrypted: Bool) -> CryptoFile? {
let savedFile: CryptoFile?
if url.startAccessingSecurityScopedResource() {
do {
let fileData = try Data(contentsOf: url)
let fileName = uniqueCombine(url.lastPathComponent)
savedFile = saveFile(fileData, fileName)
let toPath = getAppFilePath(fileName).path
if encrypted {
let cfArgs = try encryptCryptoFile(fromPath: url.path, toPath: toPath)
savedFile = CryptoFile(filePath: fileName, cryptoArgs: cfArgs)
} else {
try FileManager.default.copyItem(atPath: url.path, toPath: toPath)
savedFile = CryptoFile.plain(fileName)
}
} catch {
logger.error("FileUtils.saveFileFromURL error: \(error.localizedDescription)")
savedFile = nil
@@ -176,18 +183,16 @@ func saveFileFromURL(_ url: URL) -> String? {
return savedFile
}
func saveFileFromURLWithoutLoad(_ url: URL) -> String? {
let savedFile: String?
func moveTempFileFromURL(_ url: URL) -> CryptoFile? {
do {
let fileName = uniqueCombine(url.lastPathComponent)
try FileManager.default.moveItem(at: url, to: getAppFilePath(fileName))
ChatModel.shared.filesToDelete.remove(url)
savedFile = fileName
return CryptoFile.plain(fileName)
} catch {
logger.error("FileUtils.saveFileFromURLWithoutLoad error: \(error.localizedDescription)")
savedFile = nil
logger.error("ImageUtils.moveTempFileFromURL error: \(error.localizedDescription)")
return nil
}
return savedFile
}
func generateNewFileName(_ prefix: String, _ ext: String) -> String {
@@ -288,4 +293,4 @@ extension UIImage {
}
return self
}
}
}

View File

@@ -12,6 +12,7 @@ import UIKit
import SimpleXChat
let ntfActionAcceptContact = "NTF_ACT_ACCEPT_CONTACT"
let ntfActionAcceptContactIncognito = "NTF_ACT_ACCEPT_CONTACT_INCOGNITO"
let ntfActionAcceptCall = "NTF_ACT_ACCEPT_CALL"
let ntfActionRejectCall = "NTF_ACT_REJECT_CALL"
@@ -41,12 +42,13 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
userId != chatModel.currentUser?.userId {
changeActiveUser(userId, viewPwd: nil)
}
if content.categoryIdentifier == ntfCategoryContactRequest && action == ntfActionAcceptContact,
let chatId = content.userInfo["chatId"] as? String {
if content.categoryIdentifier == ntfCategoryContactRequest && (action == ntfActionAcceptContact || action == ntfActionAcceptContactIncognito),
let chatId = content.userInfo["chatId"] as? String {
let incognito = action == ntfActionAcceptContactIncognito
if case let .contactRequest(contactRequest) = chatModel.getChat(chatId)?.chatInfo {
Task { await acceptContactRequest(contactRequest) }
Task { await acceptContactRequest(incognito: incognito, contactRequest: contactRequest) }
} else {
chatModel.ntfContactRequest = chatId
chatModel.ntfContactRequest = NTFContactRequest(incognito: incognito, chatId: chatId)
}
} else if let (chatId, ntfAction) = ntfCallAction(content, action) {
if let invitation = chatModel.callInvitations.removeValue(forKey: chatId) {
@@ -107,8 +109,8 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
// in another chat
return recent ? [.banner, .list] : [.sound, .banner, .list]
}
// this notification is deliverd from the notifications server
// when the app is in foreground it does not need to be shown
// this notification is deliverd from the notifications server
// when the app is in foreground it does not need to be shown
case ntfCategoryCheckMessage: return []
case ntfCategoryCallInvitation: return []
default: return [.sound, .banner, .list]
@@ -134,11 +136,17 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
UNUserNotificationCenter.current().setNotificationCategories([
UNNotificationCategory(
identifier: ntfCategoryContactRequest,
actions: [UNNotificationAction(
identifier: ntfActionAcceptContact,
title: NSLocalizedString("Accept", comment: "accept contact request via notification"),
options: .foreground
)],
actions: [
UNNotificationAction(
identifier: ntfActionAcceptContact,
title: NSLocalizedString("Accept", comment: "accept contact request via notification"),
options: .foreground
), UNNotificationAction(
identifier: ntfActionAcceptContactIncognito,
title: NSLocalizedString("Accept incognito", comment: "accept contact request via notification"),
options: .foreground
)
],
intentIdentifiers: [],
hiddenPreviewsBodyPlaceholder: NSLocalizedString("New contact request", comment: "notification")
),
@@ -203,17 +211,17 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
center.delegate = self
}
func notifyContactRequest(_ user: User, _ contactRequest: UserContactRequest) {
func notifyContactRequest(_ user: any UserLike, _ contactRequest: UserContactRequest) {
logger.debug("NtfManager.notifyContactRequest")
addNotification(createContactRequestNtf(user, contactRequest))
}
func notifyContactConnected(_ user: User, _ contact: Contact) {
func notifyContactConnected(_ user: any UserLike, _ contact: Contact) {
logger.debug("NtfManager.notifyContactConnected")
addNotification(createContactConnectedNtf(user, contact))
}
func notifyMessageReceived(_ user: User, _ cInfo: ChatInfo, _ cItem: ChatItem) {
func notifyMessageReceived(_ user: any UserLike, _ cInfo: ChatInfo, _ cItem: ChatItem) {
logger.debug("NtfManager.notifyMessageReceived")
if cInfo.ntfsEnabled {
addNotification(createMessageReceivedNtf(user, cInfo, cItem))
@@ -247,8 +255,12 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
}
}
func removeNotifications(_ ids : [String]){
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: ids)
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ids)
func removeAllNotifications() async {
let nc = UNUserNotificationCenter.current()
let settings = await nc.notificationSettings()
if settings.authorizationStatus == .authorized {
nc.removeAllPendingNotificationRequests()
nc.removeAllDeliveredNotifications()
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -19,7 +19,11 @@ let bgSuspendTimeout: Int = 5 // seconds
let terminationTimeout: Int = 3 // seconds
private func _suspendChat(timeout: Int) {
if ChatModel.ok {
// this is a redundant check to prevent logical errors, like the one fixed in this PR
let state = appStateGroupDefault.get()
if !state.canSuspend {
logger.error("_suspendChat called, current state: \(state.rawValue, privacy: .public)")
} else if ChatModel.ok {
appStateGroupDefault.set(.suspending)
apiSuspendChat(timeoutMicroseconds: timeout * 1000000)
let endTask = beginBGTask(chatSuspended)
@@ -31,9 +35,7 @@ private func _suspendChat(timeout: Int) {
func suspendChat() {
suspendLockQueue.sync {
if appStateGroupDefault.get() != .stopped {
_suspendChat(timeout: appSuspendTimeout)
}
_suspendChat(timeout: appSuspendTimeout)
}
}
@@ -45,15 +47,25 @@ func suspendBgRefresh() {
}
}
private var terminating = false
func terminateChat() {
logger.debug("terminateChat")
suspendLockQueue.sync {
switch appStateGroupDefault.get() {
case .suspending:
// suspend instantly if already suspending
_chatSuspended()
// when apiSuspendChat is called with timeout 0, it won't send any events on suspension
if ChatModel.ok { apiSuspendChat(timeoutMicroseconds: 0) }
case .stopped: ()
chatCloseStore()
case .suspended:
chatCloseStore()
case .stopped:
chatCloseStore()
default:
terminating = true
// the store will be closed in _chatSuspended when event is received
_suspendChat(timeout: terminationTimeout)
}
}
@@ -73,16 +85,23 @@ private func _chatSuspended() {
if ChatModel.shared.chatRunning == true {
ChatReceiver.shared.stop()
}
if terminating {
chatCloseStore()
}
}
func activateChat(appState: AppState = .active) {
logger.debug("DEBUGGING: activateChat")
terminating = false
suspendLockQueue.sync {
appStateGroupDefault.set(appState)
if ChatModel.ok { apiActivateChat() }
logger.debug("DEBUGGING: activateChat: after apiActivateChat")
}
}
func initChatAndMigrate(refreshInvitations: Bool = true) {
terminating = false
let m = ChatModel.shared
if (!m.chatInitialized) {
do {
@@ -95,10 +114,15 @@ func initChatAndMigrate(refreshInvitations: Bool = true) {
}
func startChatAndActivate() {
terminating = false
logger.debug("DEBUGGING: startChatAndActivate")
if ChatModel.shared.chatRunning == true {
ChatReceiver.shared.start()
logger.debug("DEBUGGING: startChatAndActivate: after ChatReceiver.shared.start")
}
if .active != appStateGroupDefault.get() {
logger.debug("DEBUGGING: startChatAndActivate: before activateChat")
activateChat()
logger.debug("DEBUGGING: startChatAndActivate: after activateChat")
}
}

View File

@@ -23,6 +23,7 @@ struct SimpleXApp: App {
@State private var enteredBackground: TimeInterval? = nil
@State private var canConnectCall = false
@State private var lastSuccessfulUnlock: TimeInterval? = nil
@State private var showInitializationView = false
init() {
hs_init(0, nil)
@@ -36,14 +37,23 @@ struct SimpleXApp: App {
var body: some Scene {
return WindowGroup {
ContentView(doAuthenticate: $doAuthenticate, userAuthorized: $userAuthorized, canConnectCall: $canConnectCall, lastSuccessfulUnlock: $lastSuccessfulUnlock)
ContentView(
doAuthenticate: $doAuthenticate,
userAuthorized: $userAuthorized,
canConnectCall: $canConnectCall,
lastSuccessfulUnlock: $lastSuccessfulUnlock,
showInitializationView: $showInitializationView
)
.environmentObject(chatModel)
.onOpenURL { url in
logger.debug("ContentView.onOpenURL: \(url)")
chatModel.appOpenUrl = url
}
.onAppear() {
initChatAndMigrate()
showInitializationView = true
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
initChatAndMigrate()
}
}
.onChange(of: scenePhase) { phase in
logger.debug("scenePhase was \(String(describing: scenePhase)), now \(String(describing: phase))")
@@ -129,10 +139,10 @@ struct SimpleXApp: App {
let chat = chatModel.getChat(id) {
loadChat(chat: chat)
}
if let chatId = chatModel.ntfContactRequest {
if let ncr = chatModel.ntfContactRequest {
chatModel.ntfContactRequest = nil
if case let .contactRequest(contactRequest) = chatModel.getChat(chatId)?.chatInfo {
Task { await acceptContactRequest(contactRequest) }
if case let .contactRequest(contactRequest) = chatModel.getChat(ncr.chatId)?.chatInfo {
Task { await acceptContactRequest(incognito: ncr.incognito, contactRequest: contactRequest) }
}
}
} catch let error {

View File

@@ -39,6 +39,7 @@ struct ActiveCallView: View {
}
.onAppear {
logger.debug("ActiveCallView: appear client is nil \(client == nil), scenePhase \(String(describing: scenePhase), privacy: .public), canConnectCall \(canConnectCall)")
AppDelegate.keepScreenOn(true)
createWebRTCClient()
dismissAllSheets()
}
@@ -48,6 +49,7 @@ struct ActiveCallView: View {
}
.onDisappear {
logger.debug("ActiveCallView: disappear")
AppDelegate.keepScreenOn(false)
client?.endCall()
}
.onChange(of: m.callCommand) { _ in sendCommandToClient()}

View File

@@ -108,7 +108,6 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
try audioSession.setActive(true)
logger.debug("audioSession activated")
} catch {
print(error)
logger.error("failed activating audio session")
}
}
@@ -121,7 +120,6 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
try audioSession.setActive(false)
logger.debug("audioSession deactivated")
} catch {
print(error)
logger.error("failed deactivating audio session")
}
suspendOnEndCall()

View File

@@ -13,6 +13,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
RTCInitializeSSL()
let videoEncoderFactory = RTCDefaultVideoEncoderFactory()
let videoDecoderFactory = RTCDefaultVideoDecoderFactory()
videoEncoderFactory.preferredCodec = RTCVideoCodecInfo(name: kRTCVp8CodecName)
return RTCPeerConnectionFactory(encoderFactory: videoEncoderFactory, decoderFactory: videoDecoderFactory)
}()
private static let ivTagBytes: Int = 28
@@ -301,6 +302,17 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
}
func startCaptureLocalVideo(_ activeCall: Call) {
#if targetEnvironment(simulator)
guard
let capturer = activeCall.localCamera as? RTCFileVideoCapturer
else {
logger.error("Unable to work with a file capturer")
return
}
capturer.stopCapture()
// Drag video file named `video.mp4` to `sounds` directory in the project from any other path in filesystem
capturer.startCapturing(fromFileNamed: "sounds/video.mp4")
#else
guard
let capturer = activeCall.localCamera as? RTCCameraVideoCapturer,
let camera = (RTCCameraVideoCapturer.captureDevices().first { $0.position == activeCall.device })
@@ -328,6 +340,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
capturer.startCapture(with: camera,
format: format,
fps: Int(min(24, fps.maxFrameRate)))
#endif
}
private func createAudioSender(_ connection: RTCPeerConnection) {

View File

@@ -36,9 +36,8 @@ func localizedInfoRow(_ title: LocalizedStringKey, _ value: LocalizedStringKey)
}
}
@ViewBuilder func smpServers(_ title: LocalizedStringKey, _ servers: [String]?) -> some View {
if let servers = servers,
servers.count > 0 {
@ViewBuilder func smpServers(_ title: LocalizedStringKey, _ servers: [String]) -> some View {
if servers.count > 0 {
HStack {
Text(title).frame(width: 120, alignment: .leading)
Button(serverHost(servers[0])) {
@@ -58,6 +57,37 @@ private func serverHost(_ s: String) -> String {
}
}
enum SendReceipts: Identifiable, Hashable {
case yes
case no
case userDefault(Bool)
var id: Self { self }
var text: LocalizedStringKey {
switch self {
case .yes: return "yes"
case .no: return "no"
case let .userDefault(on): return on ? "default (yes)" : "default (no)"
}
}
func bool() -> Bool? {
switch self {
case .yes: return true
case .no: return false
case .userDefault: return nil
}
}
static func fromBool(_ enable: Bool?, userDefault def: Bool) -> SendReceipts {
if let enable = enable {
return enable ? .yes : .no
}
return .userDefault(def)
}
}
struct ChatInfoView: View {
@EnvironmentObject var chatModel: ChatModel
@Environment(\.dismiss) var dismiss: DismissAction
@@ -69,21 +99,26 @@ struct ChatInfoView: View {
@Binding var connectionCode: String?
@FocusState private var aliasTextFieldFocused: Bool
@State private var alert: ChatInfoViewAlert? = nil
@State private var showDeleteContactActionSheet = false
@State private var sendReceipts = SendReceipts.userDefault(true)
@State private var sendReceiptsUserDefault = true
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
enum ChatInfoViewAlert: Identifiable {
case deleteContactAlert
case clearChatAlert
case networkStatusAlert
case switchAddressAlert
case abortSwitchAddressAlert
case syncConnectionForceAlert
case error(title: LocalizedStringKey, error: LocalizedStringKey = "")
var id: String {
switch self {
case .deleteContactAlert: return "deleteContactAlert"
case .clearChatAlert: return "clearChatAlert"
case .networkStatusAlert: return "networkStatusAlert"
case .switchAddressAlert: return "switchAddressAlert"
case .abortSwitchAddressAlert: return "abortSwitchAddressAlert"
case .syncConnectionForceAlert: return "syncConnectionForceAlert"
case let .error(title, _): return "error \(title)"
}
}
@@ -107,26 +142,70 @@ struct ChatInfoView: View {
if let customUserProfile = customUserProfile {
Section("Incognito") {
infoRow("Your random profile", customUserProfile.chatViewName)
HStack {
Text("Your random profile")
Spacer()
Text(customUserProfile.chatViewName)
.foregroundStyle(.indigo)
}
}
}
Section {
if let code = connectionCode { verifyCodeButton(code) }
contactPreferencesButton()
sendReceiptsOption()
if let connStats = connectionStats,
connStats.ratchetSyncAllowed {
synchronizeConnectionButton()
}
// } else if developerTools {
// synchronizeConnectionButtonForce()
// }
}
.disabled(!contact.ready || !contact.active)
if let contactLink = contact.contactLink {
Section {
SimpleXLinkQRCode(uri: contactLink)
Button {
showShareSheet(items: [simplexChatLink(contactLink)])
} label: {
Label("Share address", systemImage: "square.and.arrow.up")
}
} header: {
Text("Address")
} footer: {
Text("You can share this address with your contacts to let them connect with **\(contact.displayName)**.")
}
}
Section("Servers") {
networkStatusRow()
.onTapGesture {
alert = .networkStatusAlert
if contact.ready && contact.active {
Section("Servers") {
networkStatusRow()
.onTapGesture {
alert = .networkStatusAlert
}
if let connStats = connectionStats {
Button("Change receiving address") {
alert = .switchAddressAlert
}
.disabled(
connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil }
|| connStats.ratchetSyncSendProhibited
)
if connStats.rcvQueuesInfo.contains(where: { $0.rcvSwitchStatus != nil }) {
Button("Abort changing address") {
alert = .abortSwitchAddressAlert
}
.disabled(
connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil && !$0.canAbortSwitch }
|| connStats.ratchetSyncSendProhibited
)
}
smpServers("Receiving via", connStats.rcvQueuesInfo.map { $0.rcvServer })
smpServers("Sending via", connStats.sndQueuesInfo.map { $0.sndServer })
}
Button("Change receiving address") {
alert = .switchAddressAlert
}
if let connStats = connectionStats {
smpServers("Receiving via", connStats.rcvServers)
smpServers("Sending via", connStats.sndServers)
}
}
@@ -145,15 +224,42 @@ struct ChatInfoView: View {
.navigationBarHidden(true)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.onAppear {
if let currentUser = chatModel.currentUser {
sendReceiptsUserDefault = currentUser.sendRcptsContacts
}
sendReceipts = SendReceipts.fromBool(contact.chatSettings.sendRcpts, userDefault: sendReceiptsUserDefault)
}
.alert(item: $alert) { alertItem in
switch(alertItem) {
case .deleteContactAlert: return deleteContactAlert()
case .clearChatAlert: return clearChatAlert()
case .networkStatusAlert: return networkStatusAlert()
case .switchAddressAlert: return switchAddressAlert(switchContactAddress)
case .abortSwitchAddressAlert: return abortSwitchAddressAlert(abortSwitchContactAddress)
case .syncConnectionForceAlert: return syncConnectionForceAlert({ syncContactConnection(force: true) })
case let .error(title, error): return mkAlert(title: title, message: error)
}
}
.actionSheet(isPresented: $showDeleteContactActionSheet) {
if contact.ready && contact.active {
return ActionSheet(
title: Text("Delete contact?\nThis cannot be undone!"),
buttons: [
.destructive(Text("Delete and notify contact")) { deleteContact(notify: true) },
.destructive(Text("Delete")) { deleteContact(notify: false) },
.cancel()
]
)
} else {
return ActionSheet(
title: Text("Delete contact?\nThis cannot be undone!"),
buttons: [
.destructive(Text("Delete")) { deleteContact() },
.cancel()
]
)
}
}
}
private func contactInfoHeader() -> some View {
@@ -163,20 +269,30 @@ struct ChatInfoView: View {
.frame(width: 192, height: 192)
.padding(.top, 12)
.padding()
HStack {
if contact.verified {
Image(systemName: "checkmark.shield")
if contact.verified {
(
Text(Image(systemName: "checkmark.shield"))
.foregroundColor(.secondary)
}
.font(.title2)
+ Text(" ")
+ Text(contact.profile.displayName)
.font(.largeTitle)
)
.multilineTextAlignment(.center)
.lineLimit(2)
.padding(.bottom, 2)
} else {
Text(contact.profile.displayName)
.font(.largeTitle)
.lineLimit(1)
.multilineTextAlignment(.center)
.lineLimit(2)
.padding(.bottom, 2)
}
if cInfo.fullName != "" && cInfo.fullName != cInfo.displayName && cInfo.fullName != contact.profile.displayName {
Text(cInfo.fullName)
.font(.title2)
.lineLimit(2)
.multilineTextAlignment(.center)
.lineLimit(4)
}
}
.frame(maxWidth: .infinity, alignment: .center)
@@ -222,7 +338,7 @@ struct ChatInfoView: View {
verify: { code in
if let r = apiVerifyContact(chat.chatInfo.apiId, connectionCode: code) {
let (verified, existingCode) = r
contact.activeConn.connectionCode = verified ? SecurityCode(securityCode: existingCode, verifiedAt: .now) : nil
contact.activeConn?.connectionCode = verified ? SecurityCode(securityCode: existingCode, verifiedAt: .now) : nil
connectionCode = existingCode
DispatchQueue.main.async {
chat.chatInfo = .direct(contact: contact)
@@ -256,6 +372,44 @@ struct ChatInfoView: View {
}
}
private func sendReceiptsOption() -> some View {
Picker(selection: $sendReceipts) {
ForEach([.yes, .no, .userDefault(sendReceiptsUserDefault)]) { (opt: SendReceipts) in
Text(opt.text)
}
} label: {
Label("Send receipts", systemImage: "checkmark.message")
}
.frame(height: 36)
.onChange(of: sendReceipts) { _ in
setSendReceipts()
}
}
private func setSendReceipts() {
var chatSettings = chat.chatInfo.chatSettings ?? ChatSettings.defaults
chatSettings.sendRcpts = sendReceipts.bool()
updateChatSettings(chat, chatSettings: chatSettings)
}
private func synchronizeConnectionButton() -> some View {
Button {
syncContactConnection(force: false)
} label: {
Label("Fix connection", systemImage: "exclamationmark.arrow.triangle.2.circlepath")
.foregroundColor(.orange)
}
}
private func synchronizeConnectionButtonForce() -> some View {
Button {
alert = .syncConnectionForceAlert
} label: {
Label("Renegotiate encryption", systemImage: "exclamationmark.triangle")
.foregroundColor(.red)
}
}
private func networkStatusRow() -> some View {
HStack {
Text("Network status")
@@ -278,7 +432,7 @@ struct ChatInfoView: View {
private func deleteContactButton() -> some View {
Button(role: .destructive) {
alert = .deleteContactAlert
showDeleteContactActionSheet = true
} label: {
Label("Delete contact", systemImage: "trash")
.foregroundColor(Color.red)
@@ -294,30 +448,23 @@ struct ChatInfoView: View {
}
}
private func deleteContactAlert() -> Alert {
Alert(
title: Text("Delete contact?"),
message: Text("Contact and all messages will be deleted - this cannot be undone!"),
primaryButton: .destructive(Text("Delete")) {
Task {
do {
try await apiDeleteChat(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId)
await MainActor.run {
chatModel.removeChat(chat.chatInfo.id)
chatModel.chatId = nil
dismiss()
}
} catch let error {
logger.error("deleteContactAlert apiDeleteChat error: \(responseError(error))")
let a = getErrorAlert(error, "Error deleting contact")
await MainActor.run {
alert = .error(title: a.title, error: a.message)
}
}
private func deleteContact(notify: Bool? = nil) {
Task {
do {
try await apiDeleteChat(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId, notify: notify)
await MainActor.run {
dismiss()
chatModel.chatId = nil
chatModel.removeChat(chat.chatInfo.id)
}
},
secondaryButton: .cancel()
)
} catch let error {
logger.error("deleteContactAlert apiDeleteChat error: \(responseError(error))")
let a = getErrorAlert(error, "Error deleting contact")
await MainActor.run {
alert = .error(title: a.title, error: a.message)
}
}
}
}
private func clearChatAlert() -> Alert {
@@ -344,7 +491,12 @@ struct ChatInfoView: View {
private func switchContactAddress() {
Task {
do {
try await apiSwitchContact(contactId: contact.apiId)
let stats = try apiSwitchContact(contactId: contact.apiId)
connectionStats = stats
await MainActor.run {
chatModel.updateContactConnectionStats(contact, stats)
dismiss()
}
} catch let error {
logger.error("switchContactAddress apiSwitchContact error: \(responseError(error))")
let a = getErrorAlert(error, "Error changing address")
@@ -354,13 +506,68 @@ struct ChatInfoView: View {
}
}
}
private func abortSwitchContactAddress() {
Task {
do {
let stats = try apiAbortSwitchContact(contact.apiId)
connectionStats = stats
await MainActor.run {
chatModel.updateContactConnectionStats(contact, stats)
}
} catch let error {
logger.error("abortSwitchContactAddress apiAbortSwitchContact error: \(responseError(error))")
let a = getErrorAlert(error, "Error aborting address change")
await MainActor.run {
alert = .error(title: a.title, error: a.message)
}
}
}
}
private func syncContactConnection(force: Bool) {
Task {
do {
let stats = try apiSyncContactRatchet(contact.apiId, force)
connectionStats = stats
await MainActor.run {
chatModel.updateContactConnectionStats(contact, stats)
dismiss()
}
} catch let error {
logger.error("syncContactConnection apiSyncContactRatchet error: \(responseError(error))")
let a = getErrorAlert(error, "Error synchronizing connection")
await MainActor.run {
alert = .error(title: a.title, error: a.message)
}
}
}
}
}
func switchAddressAlert(_ switchAddress: @escaping () -> Void) -> Alert {
Alert(
title: Text("Change receiving address?"),
message: Text("This feature is experimental! It will only work if the other client has version 4.2 installed. You should see the message in the conversation once the address change is completed please check that you can still receive messages from this contact (or group member)."),
primaryButton: .destructive(Text("Change"), action: switchAddress),
message: Text("Receiving address will be changed to a different server. Address change will complete after sender comes online."),
primaryButton: .default(Text("Change"), action: switchAddress),
secondaryButton: .cancel()
)
}
func abortSwitchAddressAlert(_ abortSwitchAddress: @escaping () -> Void) -> Alert {
Alert(
title: Text("Abort changing address?"),
message: Text("Address change will be aborted. Old receiving address will be used."),
primaryButton: .destructive(Text("Abort"), action: abortSwitchAddress),
secondaryButton: .cancel()
)
}
func syncConnectionForceAlert(_ syncConnectionForce: @escaping () -> Void) -> Alert {
Alert(
title: Text("Renegotiate encryption?"),
message: Text("The encryption is working and the new encryption agreement is not required. It may result in connection errors!"),
primaryButton: .destructive(Text("Renegotiate"), action: syncConnectionForce),
secondaryButton: .cancel()
)
}

View File

@@ -11,7 +11,7 @@ import SimpleXChat
struct CICallItemView: View {
@EnvironmentObject var m: ChatModel
var chatInfo: ChatInfo
@ObservedObject var chat: Chat
var chatItem: ChatItem
var status: CICallStatus
var duration: Int
@@ -60,7 +60,7 @@ struct CICallItemView: View {
@ViewBuilder private func acceptCallButton() -> some View {
if case let .direct(contact) = chatInfo {
if case let .direct(contact) = chat.chatInfo {
Button {
if let invitation = m.callInvitations[contact.id] {
CallController.shared.answerCall(invitation: invitation)

View File

@@ -10,20 +10,92 @@ import SwiftUI
import SimpleXChat
struct CIChatFeatureView: View {
@EnvironmentObject var m: ChatModel
var chatItem: ChatItem
@Binding var revealed: Bool
var feature: Feature
var icon: String? = nil
var iconColor: Color
var body: some View {
if !revealed, let fs = mergedFeautures() {
HStack {
ForEach(fs, content: featureIconView)
}
.padding(.horizontal, 6)
.padding(.vertical, 6)
} else {
fullFeatureView
}
}
private struct FeatureInfo: Identifiable {
var icon: String
var scale: CGFloat
var color: Color
var param: String?
init(_ f: Feature, _ color: Color, _ param: Int?) {
self.icon = f.iconFilled
self.scale = f.iconScale
self.color = color
self.param = f.hasParam && param != nil ? timeText(param) : nil
}
var id: String {
"\(icon) \(color) \(param ?? "")"
}
}
private func mergedFeautures() -> [FeatureInfo]? {
var fs: [FeatureInfo] = []
var icons: Set<String> = []
if var i = m.getChatItemIndex(chatItem) {
while i < m.reversedChatItems.count,
let f = featureInfo(m.reversedChatItems[i]) {
if !icons.contains(f.icon) {
fs.insert(f, at: 0)
icons.insert(f.icon)
}
i += 1
}
}
return fs.count > 1 ? fs : nil
}
private func featureInfo(_ ci: ChatItem) -> FeatureInfo? {
switch ci.content {
case let .rcvChatFeature(feature, enabled, param): FeatureInfo(feature, enabled.iconColor, param)
case let .sndChatFeature(feature, enabled, param): FeatureInfo(feature, enabled.iconColor, param)
case let .rcvGroupFeature(feature, preference, param): FeatureInfo(feature, preference.enable.iconColor, param)
case let .sndGroupFeature(feature, preference, param): FeatureInfo(feature, preference.enable.iconColor, param)
default: nil
}
}
@ViewBuilder private func featureIconView(_ f: FeatureInfo) -> some View {
let i = Image(systemName: f.icon)
.foregroundColor(f.color)
.scaleEffect(f.scale)
if let param = f.param {
HStack {
i
chatEventText(Text(param)).lineLimit(1)
}
} else {
i
}
}
private var fullFeatureView: some View {
HStack(alignment: .bottom, spacing: 4) {
Image(systemName: icon ?? feature.iconFilled)
.foregroundColor(iconColor)
.scaleEffect(feature.iconScale)
chatEventText(chatItem)
}
.padding(.leading, 6)
.padding(.bottom, 6)
.padding(.horizontal, 6)
.padding(.vertical, 4)
.textSelection(.disabled)
}
}
@@ -31,6 +103,6 @@ struct CIChatFeatureView: View {
struct CIChatFeatureView_Previews: PreviewProvider {
static var previews: some View {
let enabled = FeatureEnabled(forUser: false, forContact: false)
CIChatFeatureView(chatItem: ChatItem.getChatFeatureSample(.fullDelete, enabled), feature: ChatFeature.fullDelete, iconColor: enabled.iconColor)
CIChatFeatureView(chatItem: ChatItem.getChatFeatureSample(.fullDelete, enabled), revealed: Binding.constant(true), feature: ChatFeature.fullDelete, iconColor: enabled.iconColor)
}
}

Some files were not shown because too many files have changed in this diff Show More