Compare commits

...

2211 Commits

Author SHA1 Message Date
Evgeny Poberezkin
c7f5443920 fix 2023-04-07 11:01:33 +01:00
Evgeny Poberezkin
ccff79aa59 core: allow messages with contact requests 2023-04-07 10:15:19 +01:00
Evgeny Poberezkin
ccb52e0acd ios: validate server protocol 2023-04-06 23:25:40 +01:00
Evgeny Poberezkin
d84b30c071 core: update simplemq (preset xftp servers) 2023-04-06 23:16:16 +01:00
Evgeny Poberezkin
5ae0afe1fe ios: update servers API/UI (#2149)
* ios: update servers API/UI

* fix UI

* fix
2023-04-06 22:48:32 +01:00
Evgeny Poberezkin
d250e503b0 core: update simplexmq (fix file reception on 32 bit CPUs) 2023-04-06 21:06:46 +01:00
Stanislav Dmitrenko
afb0ae3d03 ios: video support (#2115)
* ios: video support

* made video experience prettier

* line reordering

* fix warning

* remove playback speed

* fullscreen player

* removed unused code

* fix conflict

* setting playing status better

* thumbnail dimensions and loading indicator

* fill under video

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-04-06 18:26:48 +01:00
Evgeny Poberezkin
1a3f0bed47 core: update servers API to include XFTP servers, ios: generalize UI to manage servers (#2140)
* core: update servers API to include XFTP servers, ios: generalize UI to manage servers

* add test

* update migrations to pass tests

* fix readme

* update simplexmq
2023-04-05 21:59:12 +01:00
Stanislav Dmitrenko
1e280fb7e1 android: prevent possible race in chat items (#2148)
* android: prevent possible race in chat items

* change
2023-04-05 18:53:35 +01:00
Evgeny Poberezkin
6feac55380 core: update http2 library 2023-04-05 10:23:35 +01:00
Evgeny Poberezkin
93d8eac037 4.6.1-beta.2: Android 111, iOS 138 2023-04-04 23:48:11 +01:00
Evgeny Poberezkin
a11f99be3d mobile: ignore spaces around password (#2144) 2023-04-04 21:53:25 +01:00
Evgeny Poberezkin
da17639309 core: 4.6.1.1 2023-04-04 17:31:30 +01:00
Evgeny Poberezkin
10301aa742 terminal: autocomplete contacts, groups and commands (#2125)
* terminal: autocomplete contacts, groups and commands

* autocomplete for commands and member names

* update commands

* show variants

* improve

* improve

* do not show user in contacts, better state machine for tab states

* update CI runners
2023-04-04 14:58:26 +01:00
Evgeny Poberezkin
2148d50393 core: use Int64 in time calculations (#2143)
* core: use Int64 in time calculations

* remove import

* make interval Int64
2023-04-04 14:26:31 +01:00
Evgeny Poberezkin
12fb2a4ec5 ci: move to ubuntu 20/22, disable 2 tests in CI (#2142)
* ci: move to ubuntu 20/22

* skip test on mac

* skip some tests on mac CI

* skip test on CI

* skip test unconditionally

* skip on CI only
2023-04-04 13:09:07 +01:00
Stanislav Dmitrenko
8085e5b85c android: change active user after chat started (#2141) 2023-04-03 20:11:27 +01:00
Stanislav Dmitrenko
4ba310ec16 android: open direct chat simplified (#2139) 2023-04-03 19:59:50 +01:00
Stanislav Dmitrenko
865c56f400 scripts: adapted compress-and-sign-apk script to case-insensitive file systems (#2138)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-04-03 19:41:42 +01:00
Stanislav Dmitrenko
c510e73256 android: disallow to reply on service messages (#2136) 2023-04-03 18:53:21 +01:00
spaced4ndy
73638129bc core: cancel file transfer when chat item is marked deleted (#2137) 2023-04-03 18:49:22 +04:00
spaced4ndy
1a7a79d504 core: allow repeat receive after cancel for XFTP files (#2134) 2023-04-03 16:31:18 +04:00
spaced4ndy
d3268e4a72 mobile: delete XFTP files after uploading (#2133) 2023-04-03 16:31:09 +04:00
Evgeny Poberezkin
15a93014a5 core: update http2 2023-04-01 17:27:11 +01:00
Evgeny Poberezkin
e7735329bc 4.6.1-beta.1: Android 110, iOS 137, update library 2023-04-01 16:04:44 +01:00
ishi_sama
3e222c68eb docs: FR Update (#2063)
* docs: FR update

* fix android.md

* fix rev dates

fix revision dates ; ANDROID.md & TRANSLATION.md rev dates added

* fix links

* update

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-04-01 14:55:25 +01:00
M Sarmad Qadeer
a596bd9011 website: nav scrolling & direction issue (#2120)
* website: add dir to blog template

* website: remove scroll in mobile dropdown menu
2023-04-01 14:31:15 +01:00
Evgeny Poberezkin
21a49710a8 ios: scripts to import/export localizations 2023-04-01 14:28:12 +01:00
Evgeny Poberezkin
ce6fdb2558 mobile: translations (#2121)
* Translated using Weblate (Russian)

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

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

* Translated using Weblate (German)

Currently translated at 99.3% (962 of 968 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 99.3% (962 of 968 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 99.3% (962 of 968 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 99.2% (961 of 968 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 99.3% (962 of 968 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.3% (962 of 968 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 99.3% (962 of 968 strings)

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

* ios: export/import localizations
2023-04-01 14:27:24 +01:00
Evgeny Poberezkin
0baee848a6 mobile: translations (#2114)
* Added translation using Weblate (Korean)

* Added translation using Weblate (Korean)

* Translated using Weblate (French)

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

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

* Translated using Weblate (Czech)

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

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

* Translated using Weblate (Korean)

Currently translated at 20.8% (210 of 1008 strings)

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

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

Currently translated at 21.1% (213 of 1008 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% (1008 of 1008 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% (940 of 940 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% (1008 of 1008 strings)

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (Chinese (Simplified))

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

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

* Translated using Weblate (Spanish)

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

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

* Translated using Weblate (Korean)

Currently translated at 27.4% (277 of 1009 strings)

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

* Translated using Weblate (Spanish)

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

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

* Translated using Weblate (Korean)

Currently translated at 31.3% (320 of 1020 strings)

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

* Translated using Weblate (French)

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

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (1020 of 1020 strings)

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (French)

Currently translated at 100.0% (1028 of 1028 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% (1028 of 1028 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 98.9% (1017 of 1028 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.1% (1019 of 1028 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1028 of 1028 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1028 of 1028 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 1.3% (13 of 940 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 99.5% (1023 of 1028 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1028 of 1028 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 4.1% (39 of 940 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% (1028 of 1028 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (1028 of 1028 strings)

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

* Translated using Weblate (Korean)

Currently translated at 36.2% (373 of 1028 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 4.5% (47 of 1033 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% (1035 of 1035 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% (1035 of 1035 strings)

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

* Translated using Weblate (Dutch)

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

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1035 of 1035 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1035 of 1035 strings)

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

* Translated using Weblate (Czech)

Currently translated at 99.8% (1033 of 1035 strings)

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

* Translated using Weblate (Korean)

Currently translated at 45.0% (466 of 1035 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1044 of 1044 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1044 of 1044 strings)

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

* Translated using Weblate (Korean)

Currently translated at 50.9% (532 of 1044 strings)

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

* Translated using Weblate (Korean)

Currently translated at 51.0% (533 of 1044 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1044 of 1044 strings)

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

* Translated using Weblate (Korean)

Currently translated at 53.6% (560 of 1044 strings)

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

* Added translation using Weblate (Korean)

* Added translation using Weblate (Korean)

* Translated using Weblate (French)

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

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

* Translated using Weblate (Czech)

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

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

* Translated using Weblate (Korean)

Currently translated at 20.8% (210 of 1008 strings)

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

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

Currently translated at 21.1% (213 of 1008 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% (1008 of 1008 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% (940 of 940 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% (1008 of 1008 strings)

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (Chinese (Simplified))

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

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

* Translated using Weblate (Spanish)

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

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

* Translated using Weblate (Korean)

Currently translated at 27.4% (277 of 1009 strings)

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

* Translated using Weblate (Spanish)

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

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

* Translated using Weblate (Korean)

Currently translated at 31.3% (320 of 1020 strings)

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

* Translated using Weblate (French)

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

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (1020 of 1020 strings)

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (French)

Currently translated at 100.0% (1028 of 1028 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% (1028 of 1028 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 98.9% (1017 of 1028 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.1% (1019 of 1028 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1028 of 1028 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1028 of 1028 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 1.3% (13 of 940 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 99.5% (1023 of 1028 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1028 of 1028 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 4.1% (39 of 940 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% (1028 of 1028 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (1028 of 1028 strings)

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

* Translated using Weblate (Korean)

Currently translated at 36.2% (373 of 1028 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 4.5% (47 of 1033 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% (1035 of 1035 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% (1035 of 1035 strings)

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

* Translated using Weblate (Dutch)

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

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1035 of 1035 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1035 of 1035 strings)

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

* Translated using Weblate (Czech)

Currently translated at 99.8% (1033 of 1035 strings)

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

* Translated using Weblate (Korean)

Currently translated at 45.0% (466 of 1035 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1044 of 1044 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1044 of 1044 strings)

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

* Translated using Weblate (Korean)

Currently translated at 50.9% (532 of 1044 strings)

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

* Translated using Weblate (Korean)

Currently translated at 51.0% (533 of 1044 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (1044 of 1044 strings)

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

* Translated using Weblate (Korean)

Currently translated at 53.6% (560 of 1044 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (1044 of 1044 strings)

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

* Translated using Weblate (Korean)

Currently translated at 56.4% (589 of 1044 strings)

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

* ios: import/export localizations

---------

Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: zenobit <zen@osowoso.xyz>
Co-authored-by: 5olivetree <5olivetree+github.com@mailbox.org>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: 橙子 <legiorange@163.com>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: sith-on-mars <groguko36@pm.me>
Co-authored-by: Bdd55oo <giggzuv9z.eofjx@aleeas.com>
Co-authored-by: Doocter <Undocked1040@proton.me>
2023-04-01 09:00:15 +01:00
Evgeny Poberezkin
6f304bc9e6 Merge branch 'stable' 2023-04-01 08:12:58 +01:00
Evgeny Poberezkin
1ca0dfffa0 docs: update the process to move profile 2023-04-01 08:12:48 +01:00
Evgeny Poberezkin
1420084f5e website: simplex icon in footer 2023-03-31 22:58:57 +01:00
Evgeny Poberezkin
3e03474437 readme: update translation contributors 2023-03-31 22:19:27 +01:00
Evgeny Poberezkin
95366e4d1b readme: update translations 2023-03-31 19:25:59 +01:00
Evgeny Poberezkin
df1775a1e6 website: enable Arabic, Chinese, Spanish 2023-03-31 19:14:39 +01:00
Evgeny Poberezkin
30ccea18ab website: translations (#2113)
* Translated using Weblate (Spanish)

Currently translated at 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 46.4% (98 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 58.2% (123 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 (German)

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

* Translated using Weblate (Italian)

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

* Translated using Weblate (French)

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

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

Currently translated at 97.1% (205 of 211 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 99.0% (209 of 211 strings)

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

* fix strong tag in ar.json

---------

Co-authored-by: 4 Bi 5aYzVk 93FCVjWLWxh44XH3984teVSfjwFYmUGUrbvnHwGirk9 <userfifteen.seventeen@mailfence.com>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: jonnysemon <johndevand@tutanota.com>
2023-03-31 19:13:19 +01:00
M Sarmad Qadeer
4cd90d74ad website: add RTL languages compatibility (#2056)
* website: add RTL languages compatibility

* website: add few changes

- update tailwindcss version
- add few stylings
- move to rtl true false approach

* website: set lang:en to rtl:true for testing

* website: add arabic key values & textual flag

* website: fix strong tag issues in ar translation.

* website: flip navbar for rtl languages

* disable Arabic

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-03-31 17:14:20 +01:00
spaced4ndy
7f1214688a core: 4.6.1.0 2023-03-31 19:19:51 +04:00
spaced4ndy
aa89d0d156 android: video progress, video & image cancelled indicators; ios: image cancelled indicator (#2111) 2023-03-31 19:15:37 +04:00
spaced4ndy
787cd94362 core: support fallback to SMP file transfer for backwards compatibility (#2110) 2023-03-31 17:33:52 +04:00
Evgeny Poberezkin
ec61a7fc51 android: reduce video player opacity in the gallery 2023-03-31 13:59:40 +01:00
Evgeny Poberezkin
9b627534f5 android: update video item layout, add video behind experimental toggle (#2109)
* android: update video item layout, add video behind experimental toggle

* video duration / size design

* refactor, fix duration box size

* more readable

* reuse box modifier

* Revert "reuse box modifier"

This reverts commit d0d2d3e402.
2023-03-31 13:40:25 +01:00
Stanislav Dmitrenko
400a3707b2 android: video support (#2102)
* android: video support

* better landscape videos, UI styling

* removed volume control

* quote for video

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-03-31 12:25:13 +01:00
Evgeny Poberezkin
38a5676b37 4.6.1-beta.0: Android 109, iOS 136 2023-03-30 20:12:48 +01:00
spaced4ndy
f00cfa9108 core, mobile: CRSndFileCompleteXFTP event (#2107) 2023-03-30 19:45:18 +04:00
Evgeny Poberezkin
afa24722b2 Merge branch 'stable' 2023-03-30 15:45:45 +01:00
Evgeny Poberezkin
ea5cec53bc update readme (#2106)
* update readme

* corrections

* update roadmap

* link to guide

* remove duplication
2023-03-30 15:44:57 +01:00
Evgeny Poberezkin
61dc649c70 guide: initial readme (#2006)
* guide: initial readme

* update guide

* guide:  initial documentation for audio and video calls (#2104)

* Added documentation for audio and video calls.

* Minor update

* corrections

---------

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

* remove trailing spaces and typos

* docs: update audio-video calls guide

* add app settings

* edit guide, link images and posts

* fix image links

* update

* update 2

* remove link

* remove spaces

* move images

* bold

* add image

---------

Co-authored-by: Silent-Ninja <128339587+silent-ninja-1@users.noreply.github.com>
2023-03-30 15:39:35 +01:00
spaced4ndy
b20824e16c core: notify about xftp errors (#2105) 2023-03-30 18:36:39 +04:00
Evgeny Poberezkin
39330fdce3 guide: initial readme (#2006)
* guide: initial readme

* update guide

* guide:  initial documentation for audio and video calls (#2104)

* Added documentation for audio and video calls.

* Minor update

* corrections

---------

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

* remove trailing spaces and typos

* docs: update audio-video calls guide

* add app settings

* edit guide, link images and posts

* fix image links

* update

* update 2

* remove link

* remove spaces

* move images

* bold

* add image

---------

Co-authored-by: Silent-Ninja <128339587+silent-ninja-1@users.noreply.github.com>
2023-03-30 15:17:13 +01:00
spaced4ndy
6b725a8ef7 ios, android: cancel file UI; core: cancel file fixes (#2100)
backend fixes:
- check file is not complete on CancelFile,
- check file is not cancelled when processing XFTP events,
- mark SMP file cancelled if recipient cancelled in direct chat.
2023-03-30 14:10:13 +04:00
Evgeny Poberezkin
cbcdeb2b43 ios: 4.6 (135), remove bluetooth-central from background modes (#2086) 2023-03-30 09:07:28 +01:00
Evgeny Poberezkin
4351610eca android: confirm password when deleting/unhiding inactive hidden user profile (#2103) 2023-03-30 09:02:57 +01:00
Evgeny Poberezkin
935d826a21 core, ios: unhiding user profiles always requires password (#2101) 2023-03-29 19:28:06 +01:00
Evgeny Poberezkin
a8c8137ade core: fix current user becoming incorrect after hiding or (un)muting inactive user profile (#2098)
* core: fix current user becoming incorrect after hiding or (un)muting inactive user profile

* refactor test
2023-03-29 17:39:04 +01:00
spaced4ndy
7b33e1fba8 core: update cancel file api (#2097) 2023-03-29 17:18:44 +04:00
Evgeny Poberezkin
ade7bba97b ios: confirm password when deleting active hidden user (#2095) 2023-03-29 14:01:24 +01:00
spaced4ndy
08dd321311 android: rcv & snd files progress, distinguish XFTP and SMP; ios: files UI improvements (#2096) 2023-03-29 15:48:00 +04:00
Evgeny Poberezkin
67961180c9 android: developer tools page (#2094) 2023-03-29 09:34:55 +01:00
Evgeny Poberezkin
1093892ede ios: update developer options (#2091)
* ios: update developer options

* move XFTP option, make chat console usable

* update footer

* typo

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

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2023-03-29 08:41:13 +01:00
spaced4ndy
ef05fa4905 core: file protocol field; ios: distinguish behavior and look of XFTP and SMP files (#2090)
* core: file protocol field; ios: distinguish behavior and look of XFTP and SMP files

* remove unused method

* count style

* corrections

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-03-28 19:20:06 +01:00
Evgeny Poberezkin
6a99a4f1ae Merge pull request #2087 from simplex-chat/ep/android-down-migrations
android: support down migrations
2023-03-28 16:53:57 +01:00
Evgeny Poberezkin
4895f396a2 corrections 2023-03-28 16:00:18 +01:00
Evgeny Poberezkin
c3dffc5909 fix 2023-03-28 14:14:09 +01:00
spaced4ndy
af73e5993d core: test xftp group file transfer (#2088) 2023-03-28 15:22:42 +04:00
spaced4ndy
dfec1cbb02 core: add protocol field to files table (#2089) 2023-03-28 14:38:36 +04:00
Evgeny Poberezkin
8d8f7b2524 migrations UI 2023-03-28 11:26:45 +01:00
Evgeny Poberezkin
db7b81587f Merge branch 'master' into ep/android-down-migrations 2023-03-27 23:21:41 +01:00
Evgeny Poberezkin
31bb744ba7 android: support down migrations 2023-03-27 23:20:47 +01:00
Evgeny Poberezkin
f4b349162f Merge pull request #1998 from simplex-chat/xftp
core: transfer files via XFTP
2023-03-27 23:18:34 +01:00
Evgeny Poberezkin
7f8adf8f03 Merge branch 'master' into xftp 2023-03-27 20:49:47 +01:00
Evgeny Poberezkin
1f4bb8a224 ios: fix picker heights 2023-03-27 20:43:39 +01:00
Evgeny Poberezkin
0c3dc8a6e9 core: add down migrations and fix test 2023-03-27 19:39:22 +01:00
Evgeny Poberezkin
1f15cf54af Merge branch 'master' into xftp 2023-03-27 18:57:14 +01:00
Evgeny Poberezkin
c96ba30018 core: support down migrations to allow reverting to the previous version (#2072)
* core: support down migrations to allow reverting to the previous version

* update schema

* update simplexmq

* rename errors

* remove unused functions

* migration UI, test migration

* update migration UI

* return current migrations in CRVersionInfo

* update simplexmq

* test down migrations

* cleanup ios

* show migrations in log
2023-03-27 18:34:48 +01:00
spaced4ndy
ffea61917d ios: display rcv & snd files progress (#2085)
* ios: display rcv & snd files progress

* remove animation
2023-03-27 18:02:54 +01:00
Stanislav Dmitrenko
f5c11b8faf android: ability to change profile from share dialog, mobile: do not show profile dropdown when there is only one visible profile (#2084)
* android: ability to change profile from share dialog

* icons swap
2023-03-27 17:58:14 +01:00
Stanislav Dmitrenko
48b4b23204 android: make lint happy (#2081) 2023-03-27 15:35:35 +01:00
Evgeny Poberezkin
9df78c8ac8 core: fix video message JSON encoding (#2082) 2023-03-27 15:35:01 +01:00
Stanislav Dmitrenko
450bfe2e17 android: small layout change in moderated item (#2083) 2023-03-27 15:11:17 +01:00
Evgeny Poberezkin
c79eb36a7a core: update file status on XFTP progress events (#2079)
* core: update file status on XFTP progress events

* update simplexmq
2023-03-27 12:37:22 +01:00
Evgeny Poberezkin
a58b3a42db Merge branch 'stable' 2023-03-27 12:23:42 +01:00
Evgeny Poberezkin
e344958224 blog: v4.6 announcement (#2078)
* blog: v4.6 announcement

* update post

* corrections

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

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2023-03-27 12:23:23 +01:00
Evgeny Poberezkin
05c4a6c682 android: support for ARMv7a and Android 8+ (#2038)
* add armv7a

* disable armv6l, that is lacking SMP atomics

* Add Android 8 setting (API Ver 26)

* Drop x86_64-linux, this makes no sense with `pkgs' = androidPkgs`.

* Drop mis-labled x86_64-linux:lib:support (it was aarch64-android)

* Drop x86_64-android, these do not exist in nixpkgs

The ones set up were aarch64-android anyway (pkgs' = androidPkgs)

* android: support Android 8+, armeabi-v7a (32 bit) (#2012)

* test

* stubs for allowing to launch the app

* more stubs and minSdk lowered to 26

* replaced functions that supported on higher API levels with other functions

* animated images on lower API levels and write permission

* updated abi filter and scripts for downloading libs

* changed compression script for multiple apks

* cmake changes

---------

Co-authored-by: Avently <7953703+avently@users.noreply.github.com>

* update haskell.nix ref

* bump hackage

* bump haskell.nix (again)

* build-android: add armv7

* flake.nix: remove local nixconf

This change to flake.nix breaks build-android.sh script by forcing user
to input y/n. AFAIK, this cannot be automated and I rather not include
workarounds like piping "yes n | nix build ...".

* build-android.sh: update nix version

* flake.{nix,lock}: testing

* flake.{nix,lock}: restore to original

* update android/prepare script to use zip archives

* update gradle file

* android: 4.6-beta.0 (104)

* android: abi filter for bundle (#2075)

* android: abi filter for bundle

* removed log

---------

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

---------

Co-authored-by: Moritz Angermann <moritz.angermann@gmail.com>
Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
Co-authored-by: shum <shum@liber.li>
2023-03-25 21:51:27 +00:00
Evgeny Poberezkin
b2aec6d6a7 4.6: Android 107, iOS 134 2023-03-25 17:40:37 +00:00
Evgeny Poberezkin
09c4609b6c website: translations (#2077)
* 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 (Spanish)

Currently translated at 14.2% (30 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 14.2% (30 of 211 strings)

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

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

Currently translated at 14.6% (31 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 14.6% (31 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 15.1% (32 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 15.1% (32 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 15.6% (33 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 15.6% (33 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 26.5% (56 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 26.5% (56 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 28.9% (61 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 28.9% (61 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 30.3% (64 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 30.3% (64 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 31.2% (66 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 32.7% (69 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 32.7% (69 of 211 strings)

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

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

Currently translated at 14.2% (30 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 14.2% (30 of 211 strings)

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

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

Currently translated at 14.6% (31 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 14.6% (31 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 15.1% (32 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 15.1% (32 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 15.6% (33 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 15.6% (33 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 26.5% (56 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 26.5% (56 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 28.9% (61 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 28.9% (61 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 30.3% (64 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 30.3% (64 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 31.2% (66 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 32.7% (69 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 32.7% (69 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 46.4% (98 of 211 strings)

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

---------

Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: 4 Bi 5aYzVk 93FCVjWLWxh44XH3984teVSfjwFYmUGUrbvnHwGirk9 <userfifteen.seventeen@mailfence.com>
2023-03-25 17:24:40 +00:00
Evgeny Poberezkin
f6d2aa7aae mobile: translations (#2076)
* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1008 of 1008 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% (940 of 940 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 99.2% (933 of 940 strings)

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

* Translated using Weblate (Chinese (Simplified))

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

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

* Translated using Weblate (German)

Currently translated at 100.0% (940 of 940 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (940 of 940 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% (940 of 940 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 99.9% (1007 of 1008 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% (940 of 940 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% (940 of 940 strings)

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

* ios: import/export localizations

---------

Co-authored-by: sith-on-mars <groguko36@tuta.io>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: John m <jvanmanen@gmail.com>
2023-03-25 17:21:38 +00:00
Evgeny Poberezkin
92facf58f7 android: fix layout of moderated item 2023-03-25 17:02:15 +00:00
Evgeny Poberezkin
15c36c5a84 android: fix JSON parsing for errors not defined in the UI 2023-03-25 16:27:39 +00:00
Evgeny Poberezkin
cea0543e98 android: make search field always visible in user profiles view 2023-03-25 16:10:46 +00:00
Evgeny Poberezkin
c0bbe77788 android: fix deleting active and hidden user profile, fix incorrect log 2023-03-25 15:25:52 +00:00
Evgeny Poberezkin
9f8cbe140d android: minor lint fixes 2023-03-25 10:25:17 +00:00
Evgeny Poberezkin
d0cf550b51 4.6-beta.2: Android 133, iOS 106 2023-03-25 08:28:29 +00:00
Evgeny Poberezkin
a86725480f Translated using Weblate (Dutch) (#2073)
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/

Co-authored-by: John m <jvanmanen@gmail.com>
2023-03-24 22:51:50 +00:00
Evgeny Poberezkin
9ad22e1f6d mobile: translations (#2062)
* Translated using Weblate (Russian)

Currently translated at 99.8% (1007 of 1009 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 96.8% (977 of 1009 strings)

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

* Translated using Weblate (Russian)

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

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

* Translated using Weblate (Dutch)

Currently translated at 98.1% (990 of 1009 strings)

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

* Translated using Weblate (Russian)

Currently translated at 99.8% (1007 of 1009 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 96.8% (977 of 1009 strings)

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

* Translated using Weblate (Russian)

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

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

* Translated using Weblate (Dutch)

Currently translated at 98.1% (990 of 1009 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 98.4% (993 of 1009 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 99.2% (1001 of 1009 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1009 of 1009 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% (939 of 939 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 98.6% (995 of 1009 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% (906 of 939 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% (1009 of 1009 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% (939 of 939 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 99.1% (1000 of 1009 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1009 of 1009 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% (939 of 939 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 96.0% (969 of 1009 strings)

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

* Translated using Weblate (Dutch)

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

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

* Added translation using Weblate (Finnish)

* Added translation using Weblate (Finnish)

* Translated using Weblate (German)

Currently translated at 96.6% (975 of 1009 strings)

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

* Translated using Weblate (Spanish)

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

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (Lithuanian)

Currently translated at 4.7% (48 of 1009 strings)

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

* Translated using Weblate (Lithuanian)

Currently translated at 5.6% (53 of 939 strings)

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

* Translated using Weblate (Chinese (Simplified))

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

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (1009 of 1009 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1009 of 1009 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% (939 of 939 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% (1009 of 1009 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% (939 of 939 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 99.9% (1007 of 1008 strings)

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

* Translated using Weblate (Russian)

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

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 99.9% (1007 of 1008 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 99.9% (1007 of 1008 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1008 of 1008 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 99.9% (1007 of 1008 strings)

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

* Translated using Weblate (Czech)

Currently translated at 99.3% (1001 of 1008 strings)

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

* ios: import/export localizations

---------

Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: sith-on-mars <groguko36@tuta.io>
Co-authored-by: zenobit <zen@osowoso.xyz>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: Moo <hazap@hotmail.com>
Co-authored-by: Float <float.hu+@gmail.com>
2023-03-24 22:50:09 +00:00
Stanislav Dmitrenko
a266bcbae7 android: hidden and muted user profiles (#2069)
* android: hidden and muted user profiles

* swap buttons

* smaller delay

* remove unused type

* some fixes of issues

* small visual changes

* removed delay

* re-appeared calls

* update icons and colors

* disable all notifications for muted users

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-03-24 21:48:34 +00:00
spaced4ndy
1ba210fe77 android: support XFTP files (#2070) 2023-03-24 19:06:36 +04:00
spaced4ndy
7898395359 Merge branch 'master' into xftp 2023-03-24 16:27:23 +04:00
spaced4ndy
aeb732c2f6 ios: support XFTP files (#2064) 2023-03-24 15:20:15 +04:00
Evgeny Poberezkin
b665dce383 ios: show muted user profiles in user menu, do not show badge on messages in hidden profiles (#2068) 2023-03-23 23:09:37 +00:00
Evgeny Poberezkin
f349f124d8 4.6-beta.1: Android 105, iOS 132 2023-03-23 22:02:45 +00:00
Evgeny Poberezkin
8212d7a00e mobile: fix "delete for me" moderating the received item in group (#2067) 2023-03-23 18:47:55 +00:00
Stanislav Dmitrenko
36bcb1b26e android: downgrade target sdk (#2065) 2023-03-23 16:04:53 +00:00
spaced4ndy
8d6fe2be99 core: restore stateTVar imports 2023-03-23 17:29:04 +04:00
Evgeny Poberezkin
d9571c70f2 update script to unpack ios libs 2023-03-23 10:06:13 +00:00
spaced4ndy
babbca48f8 Merge branch 'master' into xftp 2023-03-23 13:58:23 +04:00
Stanislav Dmitrenko
8c4e2e57f9 android: Show lockscreen faster (#1822)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-03-23 09:51:23 +00:00
Evgeny Poberezkin
8308651f44 android: 4.6-beta.0 (104) - for f-droid, the build is in armv7a branch 2023-03-22 22:40:35 +00:00
Evgeny Poberezkin
ad881bd46a ios: 4.6 (131) 2023-03-22 22:10:31 +00:00
Evgeny Poberezkin
c037eb2d24 Revert "mobile: hide observer role from UI (to be reverted after v4.5.4 is released)"
This reverts commit 37d0bc2f14.
2023-03-22 21:38:12 +00:00
Evgeny Poberezkin
8b4353deba ios: 4.6 (130) 2023-03-22 21:35:57 +00:00
Evgeny Poberezkin
a2be0d35fb readme: Spanish translation 2023-03-22 20:50:50 +00:00
Evgeny Poberezkin
1909bdc702 android: update string 2023-03-22 20:14:52 +00:00
Evgeny Poberezkin
63909defaf website: translations (#2061)
* Translated using Weblate (Arabic)

Currently translated at 96.2% (203 of 211 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 96.2% (203 of 211 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 97.1% (205 of 211 strings)

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

* Added translation using Weblate (Ukrainian)

* Added translation using Weblate (Chinese (Simplified))

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

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

* Translated using Weblate (Spanish)

Currently translated at 11.8% (25 of 211 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% (211 of 211 strings)

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

* Deleted translation using Weblate (Norwegian Bokmål)

* Translated using Weblate (German)

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

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

Currently translated at 13.7% (29 of 211 strings)

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

---------

Co-authored-by: jonnysemon <johndevand@tutanota.com>
Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: 4 Bi 5aYzVk 93FCVjWLWxh44XH3984teVSfjwFYmUGUrbvnHwGirk9 <userfifteen.seventeen@mailfence.com>
Co-authored-by: Float <float.hu+@gmail.com>
Co-authored-by: mlanp <github@lang.xyz>
2023-03-22 19:42:54 +00:00
Evgeny Poberezkin
8544984b17 core: 4.6.0.0 2023-03-22 19:37:02 +00:00
Stanislav Dmitrenko
563984c0df android: strings for user privacy settings (#2060) 2023-03-22 19:34:43 +00:00
Evgeny Poberezkin
6500ee5fc9 mobile app translations (#2029)
* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (907 of 907 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 1.2% (11 of 907 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 1.2% (11 of 907 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 71.7% (692 of 965 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (965 of 965 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 99.6% (904 of 907 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% (965 of 965 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% (907 of 907 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% (907 of 907 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 1.2% (11 of 907 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 1.2% (11 of 907 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 71.7% (692 of 965 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (965 of 965 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 99.6% (904 of 907 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% (965 of 965 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% (907 of 907 strings)

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

* Added translation using Weblate (Ukrainian)

* Added translation using Weblate (Ukrainian)

* Added translation using Weblate (Lithuanian)

* Added translation using Weblate (Lithuanian)

* Translated using Weblate (Spanish)

Currently translated at 84.8% (819 of 965 strings)

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

* Translated using Weblate (Lithuanian)

Currently translated at 0.8% (8 of 965 strings)

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

* Translated using Weblate (Lithuanian)

Currently translated at 0.9% (9 of 907 strings)

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

* Translated using Weblate (Chinese (Simplified))

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

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (907 of 907 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% (965 of 965 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% (907 of 907 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% (965 of 965 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% (965 of 965 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 41.7% (379 of 907 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% (907 of 907 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% (967 of 967 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 44.9% (408 of 907 strings)

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

* Deleted translation using Weblate (Norwegian Bokmål)

* Deleted translation using Weblate (Polish)

* Deleted translation using Weblate (Norwegian Bokmål)

* Deleted translation using Weblate (Polish)

* Deleted translation using Weblate (Bulgarian)

* Deleted translation using Weblate (Bulgarian)

* Translated using Weblate (German)

Currently translated at 99.2% (900 of 907 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% (967 of 967 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% (967 of 967 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (967 of 967 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 73.2% (664 of 907 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% (967 of 967 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% (907 of 907 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% (967 of 967 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% (967 of 967 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 44.8% (434 of 967 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (967 of 967 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 99.4% (902 of 907 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% (967 of 967 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 85.4% (775 of 907 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% (967 of 967 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% (907 of 907 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% (967 of 967 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% (907 of 907 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% (967 of 967 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% (907 of 907 strings)

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

* Translated using Weblate (Spanish)

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

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

* ios: add Spanish

* ios: import localizations

* export localizations

* fix typo

* android: add Spanish

---------

Co-authored-by: sith-on-mars <groguko36@tuta.io>
Co-authored-by: Nick Lai <nick20080808@gmail.com>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: Moo <hazap@hotmail.com>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: Luis Morillo Najarro <luis_cnnvd@hotmail.com>
Co-authored-by: zenobit <zen@osowoso.xyz>
Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: Pedro Licio <amaralrj@gmail.com>
Co-authored-by: mlanp <github@lang.xyz>
2023-03-22 19:33:24 +00:00
spaced4ndy
2a9c138a23 xftp: set xftp config (#2059) 2023-03-22 22:20:12 +04:00
Evgeny Poberezkin
61d8fa02d4 ios: export localizations 2023-03-22 17:50:41 +00:00
Evgeny Poberezkin
0fac2187f0 mobile: what's new in 4.6 (#2058)
* ios: what's new in 4.6

* android: what's new in 4.6
2023-03-22 17:45:55 +00:00
Evgeny Poberezkin
1db61be860 ios: remove unused type 2023-03-22 15:59:48 +00:00
Evgeny Poberezkin
06a0dbd0f2 core, iOS: hidden and muted user profiles (#2025)
* core, ios: profile privacy design

* migration

* core: user profile privacy

* update nix dependencies

* update simplexmq

* import stateTVar

* update core library

* update UI

* update hide/show user profile

* update API, UI, fix test

* update api, UI, test

* update api call

* fix api

* update UI for hidden profiles

* filter notifications on hidden/muted profiles when inactive, alerts

* updates

* update schema, test, icon
2023-03-22 15:58:01 +00:00
spaced4ndy
47c6daf0cc xftp: set app tmp directory (#2054) 2023-03-22 18:48:38 +04:00
Evgeny Poberezkin
bcdf502ce6 core: update simplexmq 2023-03-22 09:10:54 +00:00
Stanislav Dmitrenko
f9e2f4931a android: group welcome message (#2042) 2023-03-21 23:00:20 +00:00
Stanislav Dmitrenko
8929d15df0 ios: ability to specify welcome message in a group (#2041)
* ios: ability to specify welcome message in a group

* update state in model
2023-03-21 15:15:48 +00:00
spaced4ndy
60d6a47bdb xftp: delete agent rcv files on completion, error, item delete (#2040) 2023-03-21 15:21:14 +04:00
Stanislav Dmitrenko
2f529535b1 android: turn of screen in call (#2037) 2023-03-20 21:17:13 +00:00
Stanislav Dmitrenko
90c9eae283 android: night mode splash screen (#2036) 2023-03-20 18:04:48 +00:00
Stanislav Dmitrenko
added6105b android: relay server footer (#2035) 2023-03-20 17:12:15 +00:00
Stanislav Dmitrenko
2df39b5e24 android: catch exceptions while opening a URL (#2034) 2023-03-20 16:18:41 +00:00
Stanislav Dmitrenko
3477dd9400 android: system and in-app language selector (#2033)
* android: system language selector

* in-app language selector

* refactor

* refactor

* different value for Chinese

* change language order/names

* different translation

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-03-20 15:47:09 +00:00
Evgeny Poberezkin
5282551f3d Merge branch 'stable' 2023-03-19 22:59:36 +00:00
Evgeny Poberezkin
dcadaaf29b docs: readme (#2031) 2023-03-19 22:58:17 +00:00
Evgeny Poberezkin
e9d6baa6ba docs: translations (#2030)
* docs: translations

* update table

* update heading/links

* update table

* update table

* update heading
2023-03-19 20:28:39 +00:00
zenobit
6ae052a7a1 Added translated docs to czech (#1963)
* Added translated docs to czech

* Dates reverted, Added link to CZ WEBRTC.md
2023-03-19 18:10:32 +00:00
Stanislav Dmitrenko
9f750c2516 android: audio session management in calls (#2026)
* android: audio session management in calls

* audio session compatibility with old APIs

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-03-19 17:20:43 +00:00
Evgeny Poberezkin
1fe46834f2 mobile: add Chinese interface language 2023-03-19 17:17:27 +00:00
Evgeny Poberezkin
3db85c7d37 mobile app translations (#2028)
* Translated using Weblate (Russian)

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

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (903 of 903 strings)

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

* Translated using Weblate (Russian)

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

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (903 of 903 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 59.1% (571 of 965 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 2.9% (27 of 903 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% (965 of 965 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% (903 of 903 strings)

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

* ios: remove unused translations

* ios: import/export localizations

---------

Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: John m <jvanmanen@gmail.com>
2023-03-19 13:35:51 +00:00
Evgeny Poberezkin
1e4f1b8891 ios: export localizations 2023-03-19 12:53:41 +00:00
Evgeny Poberezkin
0fcd6d40ee Merge pull request #2001 from simplex-chat/callkit
iOS: native calls using WebRTC library and CallKit
2023-03-19 12:25:44 +00:00
Evgeny Poberezkin
aaa4ffe789 Merge branch 'master' into callkit 2023-03-19 12:14:34 +00:00
Evgeny Poberezkin
8c4720d0cb ios: link to set app interface language 2023-03-19 12:14:10 +00:00
Evgeny Poberezkin
94b97f6097 cli: /db export command 2023-03-19 11:49:30 +00:00
Evgeny Poberezkin
85800d96c8 ios: import/export localizations 2023-03-18 18:02:34 +00:00
Evgeny Poberezkin
3b4c06111a ios: update core library 2023-03-18 17:54:40 +00:00
Evgeny Poberezkin
548d695a82 mobile app translations (#2027)
* Translated using Weblate (Dutch)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 92.6% (884 of 954 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 16.8% (161 of 954 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 16.8% (161 of 954 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 69.1% (660 of 954 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 72.7% (694 of 954 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 90.1% (860 of 954 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Portuguese (Brazil))

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

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 0.1% (1 of 893 strings)

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

* Translated using Weblate (Chinese (Simplified))

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

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 27.1% (260 of 959 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% (959 of 959 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% (959 of 959 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% (959 of 959 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 1.1% (11 of 959 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (959 of 959 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 35.8% (344 of 959 strings)

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (965 of 965 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 2.5% (25 of 965 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 4.8% (43 of 893 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (965 of 965 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (965 of 965 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 0.6% (6 of 893 strings)

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

* Translated using Weblate (Chinese (Simplified))

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

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (965 of 965 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Chinese (Simplified))

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

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

* Translated using Weblate (Spanish)

Currently translated at 32.5% (314 of 965 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 32.5% (314 of 965 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 1.7% (16 of 893 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% (965 of 965 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 45.5% (440 of 965 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 92.6% (884 of 954 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 16.8% (161 of 954 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 16.8% (161 of 954 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 69.1% (660 of 954 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 72.7% (694 of 954 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 90.1% (860 of 954 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Portuguese (Brazil))

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

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 0.1% (1 of 893 strings)

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

* Translated using Weblate (Chinese (Simplified))

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

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 27.1% (260 of 959 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% (959 of 959 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% (959 of 959 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% (959 of 959 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 1.1% (11 of 959 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (959 of 959 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 35.8% (344 of 959 strings)

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

* Translated using Weblate (German)

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

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (965 of 965 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 2.5% (25 of 965 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 4.8% (43 of 893 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (965 of 965 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (965 of 965 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 0.6% (6 of 893 strings)

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

* Translated using Weblate (Chinese (Simplified))

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

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (965 of 965 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Chinese (Simplified))

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

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

* Translated using Weblate (Spanish)

Currently translated at 32.5% (314 of 965 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 32.5% (314 of 965 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 1.7% (16 of 893 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% (965 of 965 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 45.5% (440 of 965 strings)

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

---------

Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: zenobit <zen@osowoso.xyz>
Co-authored-by: Bdd55oo <giggzuv9z.eofjx@aleeas.com>
Co-authored-by: Pedro Licio <amaralrj@gmail.com>
Co-authored-by: Pedro Licio <pedro@agenciaregex.com>
Co-authored-by: Float <float.hu+@gmail.com>
Co-authored-by: sith-on-mars <groguko36@tuta.io>
Co-authored-by: jonnysemon <johndevand@tutanota.com>
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: Nick Lai <nick20080808@gmail.com>
Co-authored-by: M Sarmad Qadeer <msarmadqadeer@gmail.com>
Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: Luis Morillo Najarro <luis_cnnvd@hotmail.com>
2023-03-18 17:46:50 +00:00
Evgeny Poberezkin
c986a4b88b website: enable Italian and Dutch translations 2023-03-18 17:20:08 +00:00
Evgeny Poberezkin
09940ccf8d website translations (#1960)
* Added translation using Weblate (Arabic)

* Translated using Weblate (French)

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

* Translated using Weblate (French)

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

* Translated using Weblate (Arabic)

Currently translated at 20.3% (43 of 211 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 52.6% (111 of 211 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 61.6% (130 of 211 strings)

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

* Added translation using Weblate (Arabic)

* Translated using Weblate (French)

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

* Translated using Weblate (French)

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

* Translated using Weblate (Arabic)

Currently translated at 20.3% (43 of 211 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 52.6% (111 of 211 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 61.6% (130 of 211 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 69.1% (146 of 211 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% (211 of 211 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 76.7% (162 of 211 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 46.4% (98 of 211 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 50.2% (106 of 211 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 95.7% (202 of 211 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 96.6% (204 of 211 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% (211 of 211 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% (211 of 211 strings)

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

* Translated using Weblate (German)

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

* Translated using Weblate (Arabic)

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

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

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

* Added translation using Weblate (Italian)

* Translated using Weblate (Italian)

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

* Added translation using Weblate (Spanish)

* remove "coming soon"

---------

Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: jonnysemon <johndevand@tutanota.com>
Co-authored-by: ManeraKai <manerakai@protonmail.com>
Co-authored-by: zenobit <zen@osowoso.xyz>
Co-authored-by: John m <jvanmanen@gmail.com>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: random r <epsilin@yopmail.com>
2023-03-18 17:12:25 +00:00
Evgeny Poberezkin
cfc323862f update simplexmq 2023-03-18 16:28:07 +00:00
Evgeny Poberezkin
d8cc867099 update simplexmq 2023-03-18 16:18:59 +00:00
Evgeny Poberezkin
dce8a1dff9 update simplexmq 2023-03-18 13:43:01 +00:00
Evgeny Poberezkin
5bc9e014c2 update simplexmq 2023-03-18 13:26:54 +00:00
Evgeny Poberezkin
b0c9ba05f3 Merge branch 'master' into xftp 2023-03-18 11:00:30 +00:00
Evgeny Poberezkin
8a2876fca9 core: uncomment simplexmq QQ git reference, ios: update core 2023-03-18 11:00:19 +00:00
Evgeny Poberezkin
00d5f3b769 Merge branch 'master' into xftp 2023-03-18 09:59:25 +00:00
Evgeny Poberezkin
17f39ec6a0 Merge branch 'stable' 2023-03-18 09:59:08 +00:00
Evgeny Poberezkin
498ffe8a71 4.5.4: Android 103, iOS 128 2023-03-18 09:57:19 +00:00
Evgeny Poberezkin
858f0f2650 Merge branch 'master' into xftp 2023-03-18 08:38:27 +00:00
Evgeny Poberezkin
66ea2d5d71 Merge branch 'stable' 2023-03-18 08:38:10 +00:00
Evgeny Poberezkin
3dd5b5d835 core: 4.5.4.2 (add stateTVar imports) 2023-03-18 07:59:43 +00:00
Evgeny Poberezkin
9127b1bbc6 Merge pull request #2022 from simplex-chat/ep/v454
v4.5.4: add support for observer role
2023-03-17 17:14:43 +00:00
Evgeny Poberezkin
1657bcf97d core: 4.5.4.1 2023-03-17 15:02:41 +00:00
Evgeny Poberezkin
428db2f8f4 core: fix unused contact deletion (#2023)
* core: failing test for leaving and deleting the group joined via link

* fix test

* merge logic

* fix

* add condition

* refactor

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

* compiles

---------

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

* padding

* disabled tint for buttons

* proper layout for long display name

---------

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

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

* fix tests

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

* show group link when role changes

* amend test

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

* update mobile api

* update help
2023-03-16 22:19:22 +00:00
Evgeny Poberezkin
9db1924268 ios: optionally show callkit calls in recents and update settings (#2021)
* ios: optionally show callkit calls in recents and update settings

* refactor, fix call error when starting from recents
2023-03-16 22:08:58 +00:00
Evgeny Poberezkin
7a9f220290 ios: do not suspend chat when switching to another callkit call (#2020) 2023-03-16 20:19:53 +00:00
Evgeny Poberezkin
8145387f77 ios: CallKit changed reporting logic (#2019)
* ios: CallKit changed reporting logic

* refactor, suspend chat after call when app is in background

---------

Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
2023-03-16 19:57:43 +00:00
Evgeny Poberezkin
063440e735 ios: remove sheets in ActiveCallView (does not work when call accepted from background via callkit) 2023-03-16 17:18:25 +00:00
Evgeny Poberezkin
6724de09c9 ios: dismiss sheets on IncomingCallView, send notification if reportNewIncomingVoIPPushPayload fails 2023-03-16 16:59:05 +00:00
Evgeny Poberezkin
f379fd0f8c xftp: sending file completion status (#2016)
* xftp: sending file completion status

* fix type

* fix type 2

* fix
2023-03-16 13:58:01 +00:00
spaced4ndy
34a3387830 core: xftp servers option; use local xftp server in tests (#2015) 2023-03-16 14:12:19 +04:00
Evgeny Poberezkin
809cc1f234 ios: different speaker buttons on call screen 2023-03-16 08:46:13 +00:00
spaced4ndy
12200a74ff core: XFTP file transfer test (#2009) 2023-03-16 10:49:57 +04:00
Stanislav Dmitrenko
2643ea9066 ios: reverted some changes related to lockScreen (#2011)
* Revert "ios: CallKit enhancements (#2010)"

This reverts commit 840df89ca6.

* Revert "ios: CallKit integrated with app lock and screen protect (#2007)"

This reverts commit 0404b020e6.

* ios: reverted some changes related to lockScreen

* undo delay

* better support of appLock + call

* refactor

* refactor 2

* refactor 3

* refactor 4

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-03-15 21:09:33 +00:00
Stanislav Dmitrenko
840df89ca6 ios: CallKit enhancements (#2010)
* ios: CallKit enhancements

* better checks
2023-03-15 15:32:27 +00:00
Stanislav Dmitrenko
0404b020e6 ios: CallKit integrated with app lock and screen protect (#2007)
* ios: CallKit integrated with app lock and screen protect

* better lock mechanics

* background color

* logs

* refactor, revert auth changes

* additional state variable to allow connecting call

* fix lock screen, public logs

* show callkit option without dev tools

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-03-15 10:21:21 +00:00
spaced4ndy
fda41817e9 core: XFTP accept; provide save path to agent (#2005) 2023-03-14 21:51:35 +04:00
Stanislav Dmitrenko
f48cabcc0a ios: CallKit double call in background fix (#2004) 2023-03-14 15:28:34 +00:00
Stanislav Dmitrenko
f123a905d5 app icon in CallKit screen (#2003) 2023-03-14 15:19:54 +00:00
spaced4ndy
9b7fbfd513 core: rcv file events (#2002) 2023-03-14 15:26:40 +04:00
Evgeny Poberezkin
e21b4d4236 xftp: send file descriptions when ready (#1999)
* xftp: send file descriptions when ready

* remove comments, update progress on completion

* update simplexmq

* fix error condition

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

* fix conflict

* saveMemberFD

* more efficient list merging

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2023-03-14 13:28:54 +04:00
Stanislav Dmitrenko
9ec6911005 ios: CallKit integration (#1969)
* ios: CallKit integration

* notifying CallKit about outgoing call

* changes

* switching calls with CallKit

* string

* add NSE filtering entitlement

* add NSE build scheme

* remove some call limitations

* calls enhancments

* fixed calls on lockscreen

* don't display useless notification

* fix app state

* ability to answer on call from chat item via CallKit

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-03-14 08:12:40 +00:00
spaced4ndy
bfc178faf3 core: process rcv file description (#1997)
* core: process rcv file description

* refactor, groups

* view

* refactor

* update simplexmq

* refactor

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-03-14 11:42:44 +04:00
darkmaster
c4c93f881d docs: fixes listening port (server) [FR] (#2000) 2023-03-13 23:57:37 +00:00
Evgeny Poberezkin
d7f9e17bcb core: use XFTP to send and receive files (#1993)
* core: use XFTP to send and receive files

* xftp files progress

* xftp reception stubs, migration

* update simplexmq

* xftp sequence diagram

* additional chat events

* send file via XFTP

* send XFTP file description inline when file is uploaded
2023-03-13 10:30:32 +00:00
Evgeny Poberezkin
13706c4f64 website: remove "coming soon" from released features 2023-03-12 23:44:16 +00:00
Evgeny Poberezkin
f2f4b26c35 core: update agent protocol to parameterize by entity type (#1988)
* core: update agent protocol to parameterize by entity type

* update simplexmq
2023-03-10 17:23:04 +00:00
Evgeny Poberezkin
1b7b9da07c ios: update core library 2023-03-09 23:08:53 +00:00
Evgeny Poberezkin
2817306659 core: types to support xftp (#1971)
* core: types to support xftp

* migration, amend types

* update protocol / types

* update protocol, types

* update schema, simplexmq
2023-03-09 11:01:22 +00:00
Stanislav Dmitrenko
5f587c2104 android: group link role, add observer role (#1981)
* android: group link role, add observer role

* padding

* disabled tint for buttons

* proper layout for long display name

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-03-07 22:27:28 +00:00
M Sarmad Qadeer
f5670c39da website: add support for overlay hash in URL (#1974)
* website: add support for overlay hash in URL

* website: update the overlay hashes

* website: fix the ui of donate button in join simplex section

* website: make the text selectable of unique & explained swiper

* scroll to popup context

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-03-06 22:25:54 +00:00
Stanislav Dmitrenko
c0105d135c android: UI to moderate messages to other members (#1982)
* android: UI to moderate messages to other members

* do not show moderate button on moderated, show alert

* changed item

* limiting number of lines in header

* limit text height
2023-03-06 21:58:44 +00:00
Evgeny Poberezkin
f1a9814faa ios: UI to moderate messages of other members (#1980)
* ios: UI to moderate messages of other members

* split moderate action

* do not show moderate button on moderated, show alert
2023-03-06 21:57:58 +00:00
Evgeny Poberezkin
8f0e7512be ios: group link role, add observer role (#1978)
* ios: group link role, add observer role

* prevent observers from sending in UI, clear compose state on role change
2023-03-06 13:54:43 +00:00
Evgeny Poberezkin
7d49209f79 core: initial group member role when joining via link (#1975)
* core: initial group member role when joining via link

* fix tests

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

* show group link when role changes

* amend test

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

* update mobile api

* update help
2023-03-04 22:33:17 +00:00
Stanislav Dmitrenko
54020250dc ios: native WebRTC (#1933)
* ios: native WebRTC

* add video showing

* make async function better working with main thread

* wrapped code in main actor, just in case

* small change

* a little better

* enable relay

* removed unused code

* allow switching calls

* testing

* enable encryption

* testing more

* another test

* one more test

* fix remote unencrypted video

* deleted unused code related to PixelBuffer

* added MediaEncryption playground

* better playground

* better playground

* fixes

* use new encryption api

* media encryption works

* small changes

* added lib dependency

* use commit reference for lib instead of version

* video format, PIP size

* remove sample.js

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-03-02 13:17:01 +00:00
Evgeny Poberezkin
01acbb970a blog: typos 2023-03-01 20:03:06 +00:00
Evgeny Poberezkin
36cad35d46 blog: SimpleX File Transfer Protocol (XFTP) (#1965)
* blog: SimpleX File Transfer Protocol (XFTP)

* update blog

* simplify quick start

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

* re-export localizations

* add Czech language

* fix Czech strings

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

Currently translated at 73.6% (658 of 893 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 99.7% (891 of 893 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 78.2% (699 of 893 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 82.5% (737 of 893 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 96.5% (862 of 893 strings)

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

* Added translation using Weblate (Arabic)

* Added translation using Weblate (Arabic)

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 73.6% (658 of 893 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 99.7% (891 of 893 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 78.2% (699 of 893 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 82.5% (737 of 893 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 96.5% (862 of 893 strings)

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

* Added translation using Weblate (Arabic)

* Added translation using Weblate (Arabic)

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 59.2% (565 of 954 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (893 of 893 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 0.6% (6 of 954 strings)

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

---------

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

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

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

* added devcontainer config

* internationalization under dev

* internationalization of _data done

* overlays internationalization done

* improved routing

* updated gitignore

* remove .devcontainer

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

* remaining website strings are added to translation

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

* internationalization UI done

* a quick fix

* remove jq installation

* Added translation using Weblate (German)

* Translated using Weblate (French)

Currently translated at 100.0% (212 of 212 strings)

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

* Translated using Weblate (German)

Currently translated at 42.9% (91 of 212 strings)

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

* fixes of web Internationalization (#1925)

* a quick fix

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

* wrote merge_translations.js

* remove language label from dropdown

* update language names

* refactor scripts

* remove catch from script

* Added translation using Weblate (Dutch)

* Added translation using Weblate (Norwegian Bokmål)

* Translated using Weblate (Dutch)

Currently translated at 33.0% (70 of 212 strings)

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

* website: internationalization fixes (#1931)

* added devcontainer config

* internationalization under dev

* internationalization of _data done

* overlays internationalization done

* improved routing

* updated gitignore

* remove .devcontainer

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

* remaining website strings are added to translation

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

* internationalization UI done

* a quick fix

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

* wrote merge_translations.js

* remove flag from blog

* updated nav stylings & logo links

* add enabled key

* updated nav dropdown styling

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

* enable nl & nb_NO

* updated nav stylings

* updated contact.js

---------

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

* Translated using Weblate (German)

Currently translated at 47.6% (101 of 212 strings)

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

* fixed internationalization issues

* updated strings, refactor contact.js

* updated nav stylings for mobile

* a bit smaller padding on mobile

* Added translation using Weblate (Czech)

* Translated using Weblate (Czech)

Currently translated at 100.0% (212 of 212 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (212 of 212 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (212 of 212 strings)

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

* enabled languages

* check/correct (#1949)

---------

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

* ios: update C header

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

* scroll view (fixes trimmed texts)

* layout

* bigger frame

* minHeight, to allow scroling with large font

---------

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

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

* ios: re-export localizations

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

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 46.5% (444 of 953 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 46.5% (444 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 50.8% (485 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 0.3% (3 of 891 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 57.0% (544 of 953 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 51.6% (460 of 891 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 15.1% (144 of 953 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 15.2% (145 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 14.3% (128 of 891 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 15.1% (135 of 891 strings)

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

* Translated using Weblate (Hindi)

Currently translated at 14.3% (137 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 25.1% (224 of 891 strings)

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

* Translated using Weblate (Hindi)

Currently translated at 15.1% (144 of 953 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 59.4% (567 of 953 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 53.5% (477 of 891 strings)

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

* Translated using Weblate (Hindi)

Currently translated at 15.5% (148 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 29.2% (261 of 891 strings)

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

* Translated using Weblate (Hindi)

Currently translated at 15.6% (149 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 31.6% (282 of 891 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 36.2% (323 of 891 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 7.4% (71 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 41.5% (370 of 891 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 53.9% (481 of 891 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 57.5% (513 of 891 strings)

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

* Added translation using Weblate (Bulgarian)

* Added translation using Weblate (Bulgarian)

* Translated using Weblate (Dutch)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 77.2% (688 of 891 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 83.9% (748 of 891 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 15.9% (152 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (891 of 891 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 7.9% (76 of 954 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 99.8% (953 of 954 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (891 of 891 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 99.8% (953 of 954 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (891 of 891 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 99.8% (953 of 954 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (891 of 891 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (954 of 954 strings)

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

* Added translation using Weblate (Norwegian Bokmål)

* Added translation using Weblate (Norwegian Bokmål)

* Translated using Weblate (German)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (891 of 891 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 67.1% (641 of 954 strings)

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

* Translated using Weblate (Hindi)

Currently translated at 15.7% (150 of 954 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 99.3% (948 of 954 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 61.4% (586 of 954 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 55.5% (495 of 891 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 27.2% (260 of 954 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 42.5% (406 of 954 strings)

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

* Translated using Weblate (Croatian)

Currently translated at 2.0% (18 of 891 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 66.2% (632 of 954 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 61.3% (547 of 891 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (891 of 891 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 46.5% (444 of 953 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 46.5% (444 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 50.8% (485 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 0.3% (3 of 891 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 57.0% (544 of 953 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 51.6% (460 of 891 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 15.1% (144 of 953 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 15.2% (145 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 14.3% (128 of 891 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 15.1% (135 of 891 strings)

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

* Translated using Weblate (Hindi)

Currently translated at 14.3% (137 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 25.1% (224 of 891 strings)

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

* Translated using Weblate (Hindi)

Currently translated at 15.1% (144 of 953 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 59.4% (567 of 953 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 53.5% (477 of 891 strings)

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

* Translated using Weblate (Hindi)

Currently translated at 15.5% (148 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 29.2% (261 of 891 strings)

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

* Translated using Weblate (Hindi)

Currently translated at 15.6% (149 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 31.6% (282 of 891 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 36.2% (323 of 891 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 7.4% (71 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 41.5% (370 of 891 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 53.9% (481 of 891 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 57.5% (513 of 891 strings)

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

* Added translation using Weblate (Bulgarian)

* Added translation using Weblate (Bulgarian)

* Translated using Weblate (Dutch)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 77.2% (688 of 891 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 83.9% (748 of 891 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 15.9% (152 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (891 of 891 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (953 of 953 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 7.9% (76 of 954 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 99.8% (953 of 954 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (891 of 891 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 99.8% (953 of 954 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (891 of 891 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 99.8% (953 of 954 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (891 of 891 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (954 of 954 strings)

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

* Added translation using Weblate (Norwegian Bokmål)

* Added translation using Weblate (Norwegian Bokmål)

* Translated using Weblate (German)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (891 of 891 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 67.1% (641 of 954 strings)

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

* Translated using Weblate (Hindi)

Currently translated at 15.7% (150 of 954 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 99.3% (948 of 954 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 61.4% (586 of 954 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 55.5% (495 of 891 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 27.2% (260 of 954 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 42.5% (406 of 954 strings)

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

* Translated using Weblate (Croatian)

Currently translated at 2.0% (18 of 891 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 66.2% (632 of 954 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 61.3% (547 of 891 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (891 of 891 strings)

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

---------

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

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

* fix

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

* swift API

* add decrypt stub

* change name

* remove unused type

* move functions

* update cabal file

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

* CLI readme

* broadcast bot

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

* typo

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

* change

---------

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

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

* different way of doing things

* change

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

* fix the fix

* fix img src

* CONTRIBUTING_fr

* SQL_fr ; fix rev date

* WEBRTC_fr.md ; rev date

* CLI_fr ; rev date

* fix table content link and sum img src

* fix

* fixing the fix

am i dumb?

* polishing...

* README_fr.md (save)

* Update README_fr.md

* README_fr ; starting SIMPLEX_FR ; link fix

* update README (en+fr)

* Blog README_fr ; translators link

* typo

* SIMPLEX_fr ; fixes

* last fixes

* rename folder

* rename files/links

* update line

* remove ...

---------

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

* refactor

* added comments and renamed function

* rename

* rename

---------

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

* allow message deletion to admins/owners

* deleted by types, schema

* check role

* fix test, view

* view, tests

* comment

* test timed deletion events

* refactor

* refactor

* refactor

---------

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

* add doc

* add images, corrections

* spellcheck

* link to translation guide from readme

* change image

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

* naming

* spelling

* spelling

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

* better place for code

* ios: including fully localized languages only

* Revert "ios: including fully localized languages only"

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

* border

* automatic version

* modifier

* make image instead of screenshot

* code folding

* don't use deprecated method

* function refactor

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

* size of circle

* same padding as in android

* add padding to logo

---------

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

* save qr code as jpg for better quality

* bigger logo

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

---------

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

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 26.7% (255 of 954 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (895 of 895 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 8.5% (82 of 954 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 24.4% (219 of 895 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 1.8% (18 of 954 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 56.4% (538 of 953 strings)

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

* Translated using Weblate (Czech)

Currently translated at 1.6% (15 of 891 strings)

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

* Added translation using Weblate (Croatian)

* Added translation using Weblate (Croatian)

* Translated using Weblate (Dutch)

Currently translated at 63.6% (607 of 953 strings)

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

* Translated using Weblate (Czech)

Currently translated at 99.6% (950 of 953 strings)

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

* Translated using Weblate (Czech)

Currently translated at 66.1% (589 of 891 strings)

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

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

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

* ios: import localizations

* ios: export localizations

---------

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

* update JS

* show protocol, support TURNS

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

* remove enums for protocol strings in ICE candidates

* 0.2.3

---------

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

* update post, images

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

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

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

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (German)

Currently translated at 98.5% (882 of 895 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (895 of 895 strings)

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

* Translated using Weblate (French)

Currently translated at 98.5% (882 of 895 strings)

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

* Translated using Weblate (Italian)

Currently translated at 98.5% (882 of 895 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 42.1% (402 of 954 strings)

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

* Translated using Weblate (Czech)

Currently translated at 3.8% (37 of 954 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (895 of 895 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (954 of 954 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (895 of 895 strings)

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

* Translated using Weblate (Czech)

Currently translated at 3.9% (38 of 954 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (954 of 954 strings)

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

---------

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

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

* refactore

* terminate background taks on chat item update

* refactor

* refactor2

* refactor3

* refactor 4

* refactor5

* fix chat item update in Android

---------

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

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

* Added translation using Weblate (Dutch)

* Added translation using Weblate (Dutch)

* Translated using Weblate (Russian)

Currently translated at 100.0% (936 of 936 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (878 of 878 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 11.0% (103 of 936 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (936 of 936 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (878 of 878 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 13.8% (130 of 936 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 14.9% (131 of 878 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 13.7% (129 of 936 strings)

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

* Added translation using Weblate (Japanese)

* Added translation using Weblate (Japanese)

* Added translation using Weblate (Hindi)

* Added translation using Weblate (Hindi)

* Added translation using Weblate (Czech)

* Added translation using Weblate (Czech)

* Added translation using Weblate (Polish)

* Added translation using Weblate (Polish)

* Added translation using Weblate (Portuguese (Brazil))

* Added translation using Weblate (Portuguese (Brazil))

* Added translation using Weblate (Spanish)

* Added translation using Weblate (Dutch)

* Added translation using Weblate (Dutch)

* Translated using Weblate (Russian)

Currently translated at 100.0% (936 of 936 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (878 of 878 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 11.0% (103 of 936 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (936 of 936 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (878 of 878 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 13.8% (130 of 936 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 14.9% (131 of 878 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 13.7% (129 of 936 strings)

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

* Added translation using Weblate (Japanese)

* Added translation using Weblate (Japanese)

* Added translation using Weblate (Hindi)

* Added translation using Weblate (Hindi)

* Added translation using Weblate (Czech)

* Added translation using Weblate (Czech)

* Added translation using Weblate (Polish)

* Added translation using Weblate (Polish)

* Added translation using Weblate (Portuguese (Brazil))

* Added translation using Weblate (Portuguese (Brazil))

* Translated using Weblate (French)

Currently translated at 100.0% (936 of 936 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (878 of 878 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 17.3% (162 of 936 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 16.2% (143 of 878 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 15.7% (147 of 936 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (939 of 939 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (878 of 878 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 21.9% (206 of 939 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 20.7% (182 of 878 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (939 of 939 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 24.3% (229 of 939 strings)

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

* Translated using Weblate (Hindi)

Currently translated at 7.8% (74 of 939 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 31.2% (294 of 940 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (940 of 940 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (940 of 940 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (940 of 940 strings)

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

* Translated using Weblate (Hindi)

Currently translated at 12.6% (119 of 940 strings)

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

* Translated using Weblate (Hindi)

Currently translated at 13.5% (127 of 940 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 22.7% (214 of 940 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 21.7% (191 of 878 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 2.5% (24 of 940 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 23.6% (222 of 940 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 24.3% (214 of 878 strings)

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

---------

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

* better way of continue connection and end with timeout

* making data classes instead of classes for making logs informative

* refactor

* update webrtc package version

* refactor

* fix

* clear conneciton timeout on disconnection

* refactor

* v0.2.1

---------

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

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

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

* updated texts

---------

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

* updated texts

* refactor

---------

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

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

* enable only failing tests

* start/stop server for every test

* log message that failed to parse

* stop chat synchronously

* print call stack

* add HasCallStack

* increase test timeout

* add call stacks

* more call stacks

* fix test

* disable failing test

* add delay between the tests

* make delay more visible

* remove change in error message

* reduce test delay, increase timeout

* increase delay between the tests

* run each test with a database in a different folder

* folder name

* refactor

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

* show meaningful alert

* update alert messages

---------

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

* different logic in asking to auth

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

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

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

* Added translation using Weblate (Chinese (Simplified))

* Translated using Weblate (Chinese (Simplified))

Currently translated at 2.7% (25 of 911 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 2.6% (23 of 854 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 2.7% (25 of 911 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 7.0% (60 of 854 strings)

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

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

* Added translation using Weblate (Chinese (Simplified))

* Added translation using Weblate (Chinese (Simplified))

* Translated using Weblate (Chinese (Simplified))

Currently translated at 2.7% (25 of 911 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 2.6% (23 of 854 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 2.7% (25 of 911 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 7.0% (60 of 854 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (917 of 917 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (859 of 859 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (917 of 917 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (859 of 859 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (917 of 917 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (859 of 859 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 10.7% (92 of 859 strings)

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

* Added translation using Weblate (Chinese (Traditional))

* Added translation using Weblate (Chinese (Traditional))

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

* Added translation using Weblate (Spanish)

* Translated using Weblate (Chinese (Simplified))

Currently translated at 10.1% (95 of 936 strings)

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

* remove UK English file

---------

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

* empty line

* delete unused voice files

* finish recording properly

* refactor

* fix

---------

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

* save and restore images/file/voice for draft

* refactor image

* it was a wrong value

* use param label

* proper stop of voice recording

* safe draft logic

* different way of finishing recording

* keep condition

* refactor

* fix live

* fix

* refactor

* fix

* simplify

* add space after filename in draft

---------

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

* padding

* bigger padding

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

* updated the design of Comparison section

* updated the design of Simplex explained

* updated the design of Simplex network

* updated the design of private section

* updated the design of features section

* updated the design of unique section

* updated the design of privacy matters

* added improvements in design

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

* tint color of icon

* userId from function

* better line

* missing question

* bigger avatar

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

* update paddings

* progressIndicator

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

* sizes of buttons

* update paddings

* change names

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

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

* fix layout

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

* show attachment icon and formatting in draft

* button to remove message text

* show icon for active draft, refactor

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

* another try

* complete

* added file

* enable swipe to go back from ChatView

* Revert "enable swipe to go back from ChatView"

This reverts commit 22de79505c.

* ios: enable swipe to go back from ChatView

* remove title change

* remove unused

* remove unused variable

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

* update text

* refactor, disable button

* darker profile icon colors

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

* add line in try-catch block

* changed lines position

* when -> if

* take condition outside

* mutable version of objects and usage of a new function

* changed additional places in code

* added toMap() so state will be updated

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

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

* another try

* complete

* added file

* enable swipe to go back from ChatView

* Revert "enable swipe to go back from ChatView"

This reverts commit 22de79505c.

* refactor

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

* add comments

* typos

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

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

* query

* check snd cancel

* Revert "check snd cancel"

This reverts commit f16651345d.

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

* update scroll

* update current user and update users list

* refactor

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

* counter update on badge

* padding before profile info in call view

* underline in name

* change after merge

* do not show Simplex Info button if users already created

* unread counter

* do not increase badge counter when chat has disabled notifications

* update incoming call

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

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

* add simplexmq commit and version to version info

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

* typo

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

* ios: update alerts

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

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

* remove comment

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

* export ios translations

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

* return regardless

* refactor

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

Currently translated at 100.0% (907 of 907 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (853 of 853 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (907 of 907 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (853 of 853 strings)

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

* import localizations

* re-export localizations

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

* Logging of user in responses

* UserId

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

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

* Do not set active user if it's unchanged

* Blank line

* if

* Change active user function

* refactor

* refactor

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

* Alert

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

* update build

* parens

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

* doSendCancel

* Apply suggestions from code review

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

* sendCancel

* refactor

* error to view

* refactor

* refactor

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

* Change

* Changes

* update view

* fix layout/refactor

* fix preview

* wider menu, update label

* hide Your profiles button

* Clickable background that hides userChooser

* No click listener

* Better animation

* Disabled scrolling for small number of items

* Separated scrollview and buttons

* No transition

* Re-indent

* Limiting width by the longest label

* UserManagerView

* Adapted API

* Hide user chooser after selection

* Top counter,  users refactor

* Padding

* use VStack to fix layout bug

* eol

* rename: rename to getUserChatData

* update layout

* s/semibold/medium

* remove SettingsButton view

* rename

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

* split

* tests

* refactor

* viewUserInfo

* refactor

* remove omit nothing

* corrections

* fix

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

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

* indent for chats without messages, help

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

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

* Added translation using Weblate (Italian)

* Translated using Weblate (French)

Currently translated at 100.0% (906 of 906 strings)

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

* Translated using Weblate (French)

Currently translated at 99.6% (850 of 853 strings)

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

* Translated using Weblate (Italian)

Currently translated at 24.9% (226 of 906 strings)

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

* Translated using Weblate (Italian)

Currently translated at 26.3% (225 of 853 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (906 of 906 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (853 of 853 strings)

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

* Translated using Weblate (Italian)

Currently translated at 28.8% (261 of 906 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (853 of 853 strings)

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

* Translated using Weblate (Italian)

Currently translated at 58.2% (528 of 906 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (906 of 906 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (907 of 907 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (853 of 853 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (907 of 907 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (853 of 853 strings)

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

* Added translation using Weblate (Italian)

* Added translation using Weblate (Italian)

* Translated using Weblate (French)

Currently translated at 100.0% (906 of 906 strings)

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

* Translated using Weblate (French)

Currently translated at 99.6% (850 of 853 strings)

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

* Translated using Weblate (Italian)

Currently translated at 24.9% (226 of 906 strings)

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

* Translated using Weblate (Italian)

Currently translated at 26.3% (225 of 853 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (906 of 906 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (853 of 853 strings)

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

* Translated using Weblate (Italian)

Currently translated at 28.8% (261 of 906 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (853 of 853 strings)

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

* Translated using Weblate (Italian)

Currently translated at 58.2% (528 of 906 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (906 of 906 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (907 of 907 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (853 of 853 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (907 of 907 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (853 of 853 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (907 of 907 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (907 of 907 strings)

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

* Translated using Weblate (Italian)

Currently translated at 38.5% (329 of 853 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (907 of 907 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (853 of 853 strings)

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

* correction

* correction

* correction

* ios: import localizations

* ios: re-export localizations

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

* Better

* Separate function

* rename

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

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

* Test

* Revert "Test"

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

* update

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

* remove binding parameter

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

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

* Test

* Revert "Test"

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

* android: improve live message logic

* fix, refactor

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

* rename view

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

* Allow non-transparent pixels

* optimize

* remove prints

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

* Custom Equatable

* Changes

* Change

* Fix liveMessage not hiding

* Refactoring

* Refactoring

* No animation when removing dummy live message item

* Check

* Anim

* Animation

* whitespace

* refactor

* Fix race

* Better fix of race

* fix race condition

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

* Better quoted messages handling

* Do not add item into preview

* Change

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

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

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

Currently translated at 100.0% (906 of 906 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (850 of 850 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (906 of 906 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (850 of 850 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (906 of 906 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (850 of 850 strings)

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

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

* UCR

* Revert "UCR"

This reverts commit 1f98d25192.

* comment

* events User

* events in api User

* CRActiveUser in APISetActiveUser

* process message with/without connection

* refactor

* mute error

* user in api responses

* name

* lost response

* user in CRChatCmdError

* compiles

* user in CRChatError

* -- UserId

* mute unused warning

* catch in withUser

* remove comment

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

* add images

* update post

* update readme, roadmap

* corrections

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

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

Currently translated at 100.0% (845 of 845 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (904 of 904 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (845 of 845 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (904 of 904 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (845 of 845 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (906 of 906 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (845 of 845 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (845 of 845 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (904 of 904 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (845 of 845 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (904 of 904 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (845 of 845 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (906 of 906 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (845 of 845 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (850 of 850 strings)

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

* Translated using Weblate (French)

Currently translated at 99.6% (847 of 850 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (906 of 906 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (850 of 850 strings)

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

* Translated using Weblate (German)

Currently translated at 90.9% (824 of 906 strings)

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

* Translated using Weblate (German)

Currently translated at 92.4% (786 of 850 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (845 of 845 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (904 of 904 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (845 of 845 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (904 of 904 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (845 of 845 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (906 of 906 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (845 of 845 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (850 of 850 strings)

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

* Translated using Weblate (French)

Currently translated at 99.6% (847 of 850 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (906 of 906 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (850 of 850 strings)

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

* Translated using Weblate (German)

Currently translated at 90.9% (824 of 906 strings)

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

* Translated using Weblate (German)

Currently translated at 92.4% (786 of 850 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (906 of 906 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (850 of 850 strings)

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

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

* Different icon

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

* mobile: items with feature offers

* ios interactive contact/user preference change items

* android: interactive preference items

* Translated using Weblate (French)

Currently translated at 8.1% (68 of 831 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (784 of 784 strings)

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

* Translated using Weblate (French)

Currently translated at 10.4% (87 of 831 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (785 of 785 strings)

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

* Translated using Weblate (French)

Currently translated at 10.5% (88 of 831 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (785 of 785 strings)

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

* Translated using Weblate (French)

Currently translated at 14.2% (122 of 855 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (785 of 785 strings)

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

* Translated using Weblate (French)

Currently translated at 15.9% (137 of 858 strings)

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

* Translated using Weblate (French)

Currently translated at 18.2% (157 of 858 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (785 of 785 strings)

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

* Translated using Weblate (French)

Currently translated at 36.3% (312 of 858 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (785 of 785 strings)

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

* Translated using Weblate (French)

Currently translated at 54.0% (464 of 858 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (785 of 785 strings)

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

* Translated using Weblate (French)

Currently translated at 81.0% (695 of 858 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (785 of 785 strings)

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

* Translated using Weblate (French)

Currently translated at 82.1% (705 of 858 strings)

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

* Translated using Weblate (French)

Currently translated at 85.9% (756 of 880 strings)

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

* import/export localizations

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

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

* add OK button to WhatsNew

* separate state for nav buttons
2022-12-26 14:08:01 +00:00
JRoberts
4370012b8a ios: fix navigation to member info view (#1648) 2022-12-26 13:45:02 +00:00
JRoberts
20c33aea72 android: show connect via url alert in chat list instead of notifications mode on fresh app (#1649) 2022-12-26 13:43:26 +00:00
Evgeny Poberezkin
c11a1aa0e6 ios: disallow editing text attributes 2022-12-25 13:58:07 +00:00
Evgeny Poberezkin
166b789f3c ios: v4.4 beta (104) 2022-12-25 09:48:25 +00:00
Evgeny Poberezkin
bbc26e272c v4.4-beta.2: android (81) 2022-12-24 21:59:55 +00:00
Evgeny Poberezkin
6c839f8075 android: fix voice recording in groups 2022-12-24 21:47:58 +00:00
Evgeny Poberezkin
be91f97c83 ios: disable screen protection by default 2022-12-24 15:41:31 +00:00
Evgeny Poberezkin
e085cb7350 4.4-beta.1: iOS 103, Android 80 2022-12-24 11:59:28 +00:00
Evgeny Poberezkin
12574bed96 ios: move image utils to app (#1642)
* ios: move image utils to app

* name in comments
2022-12-24 11:38:59 +00:00
Evgeny Poberezkin
2137893111 ios: change disappearing messages icon (#1641) 2022-12-24 09:48:27 +00:00
Evgeny Poberezkin
e552a28a4d ios: re-export French translations 2022-12-23 22:24:27 +00:00
Evgeny Poberezkin
b20031d875 ios: add French language (#1640) 2022-12-23 22:14:47 +00:00
Evgeny Poberezkin
558b3fa356 translations (#1639)
* Translated using Weblate (German)

Currently translated at 95.0% (783 of 824 strings)

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

* Translated using Weblate (Russian)

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

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/
2022-12-23 21:49:34 +00:00
Stanislav Dmitrenko
cb337cef10 ios: Download libs script (#1634) 2022-12-23 21:24:08 +00:00
Stanislav Dmitrenko
cd63f81292 ios: Animated images (GIF) support (#1636)
* ios: Animated images (GIF) support

* Moved from String path to UIImage param

* Aspect ratio

* Image frame

* gif image size

* refactor

* refactor

* fix fullscreen scroll animation

* rename UploadContent -> AnyImage

* refactor, allow using gifs in profiles

* rename back

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-12-23 21:22:12 +00:00
Evgeny Poberezkin
6205b03943 ios: localize ttl in disappearing messages, translations (#1638)
* ios: localize ttl in disappearing messages, translations

* more translation keys
2022-12-23 19:55:45 +00:00
Evgeny Poberezkin
82924ce8c6 translations (#1637)
* Translated using Weblate (German)

Currently translated at 93.5% (823 of 880 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (880 of 880 strings)

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

* Translated using Weblate (German)

Currently translated at 96.2% (774 of 804 strings)

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

* Translated using Weblate (Russian)

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

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

* Translated using Weblate (French)

Currently translated at 100.0% (804 of 804 strings)

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

* Translated using Weblate (Russian)

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

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (880 of 880 strings)

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

Co-authored-by: J R <jr@simplex.chat>
Co-authored-by: Ophiushi <ptlfr@pm.me>
2022-12-23 18:55:14 +00:00
Evgeny Poberezkin
b1067c339c android: fix meta layout/reserved space (#1635) 2022-12-23 17:27:41 +00:00
Evgeny Poberezkin
0d6e4b48f6 translations (#1633)
* Translated using Weblate (French)

Currently translated at 97.6% (785 of 804 strings)

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

* Translated using Weblate (French)

Currently translated at 97.6% (785 of 804 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/
2022-12-23 14:50:23 +00:00
JRoberts
84d2c408ce core: optimize chat loading time - faster chat previews queries (item_status index for chat stats), fix live file transfers queries (#1630)
* core: optimize get chat previews queries (item_status index for chat stats)

* cleanup

* optimize chat loading time

* cleanup

* schema

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-12-23 18:37:02 +04:00
Evgeny Poberezkin
de434b730e weblate/translations (#1632)
* mobile: items with feature offers (#1623)

* mobile: items with feature offers

* ios interactive contact/user preference change items

* android: interactive preference items

* Translated using Weblate (French)

Currently translated at 8.1% (68 of 831 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (784 of 784 strings)

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

* Translated using Weblate (French)

Currently translated at 10.4% (87 of 831 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (785 of 785 strings)

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

* Translated using Weblate (French)

Currently translated at 10.5% (88 of 831 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (785 of 785 strings)

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

* Translated using Weblate (French)

Currently translated at 14.2% (122 of 855 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (785 of 785 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/

* Translated using Weblate (French)

Currently translated at 15.9% (137 of 858 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fr/

* Translated using Weblate (French)

Currently translated at 18.2% (157 of 858 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fr/

* Translated using Weblate (French)

Currently translated at 100.0% (785 of 785 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/

* Translated using Weblate (French)

Currently translated at 36.3% (312 of 858 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fr/

* Translated using Weblate (French)

Currently translated at 100.0% (785 of 785 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/

* Translated using Weblate (French)

Currently translated at 54.0% (464 of 858 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fr/

* Translated using Weblate (French)

Currently translated at 100.0% (785 of 785 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/

* Translated using Weblate (French)

Currently translated at 81.0% (695 of 858 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fr/

* Translated using Weblate (French)

Currently translated at 100.0% (785 of 785 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/

* Translated using Weblate (French)

Currently translated at 82.1% (705 of 858 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fr/

* Translated using Weblate (French)

Currently translated at 85.9% (756 of 880 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fr/

* revert changes

Co-authored-by: Ophiushi <ptlfr@pm.me>
2022-12-23 14:11:12 +00:00
Evgeny Poberezkin
1251dbc4b0 ios: export localizations 2022-12-23 13:13:37 +00:00
sh
d115ad228b build-android: disable external repo (#1615) 2022-12-23 13:10:36 +00:00
Evgeny Poberezkin
28d6f62b74 mobile: better layout for feature/preference items (#1631)
* mobile: better layout for feature/preference items

* refactor
2022-12-23 13:10:00 +00:00
Evgeny Poberezkin
2b9238144b ios: fix double unread status (#1629) 2022-12-23 09:14:12 +00:00
Evgeny Poberezkin
a2e1b7ae0a v4.4: iOS build 102, android build 79 2022-12-23 08:33:57 +00:00
Evgeny Poberezkin
a00bb6d5ef android: fix enabling voice cancelling disappearing messages 2022-12-22 22:08:21 +00:00
Evgeny Poberezkin
da12b651e4 4.4.0 2022-12-22 21:31:07 +00:00
Evgeny Poberezkin
a936c14cf2 mobile: items with feature offers (#1627)
* mobile: items with feature offers

* ios interactive contact/user preference change items

* android: interactive preference items

* add missing view

* revert change
2022-12-22 21:01:29 +00:00
Stanislav Dmitrenko
e6aad24e5f android: Timed messages TTL logic in preferences (#1624)
* android: Timed messages TTL logic in preferences

* do not set ttl in global timed message prefs

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-12-22 15:39:24 +00:00
JRoberts
8dac96f415 core: wait chat started in deleteTimedItem threads (#1626) 2022-12-22 19:18:38 +04:00
Evgeny Poberezkin
aae0802ec8 core: chat items with offered feature (#1620)
* core: chat items with offered feature

* texts

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* new preference items

* test

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-12-22 14:56:29 +00:00
JRoberts
74a20ef70c core: sort chat previews by chat ts (#1625) 2022-12-22 17:43:10 +04:00
Evgeny Poberezkin
a2a29628a7 ios: remove unused code (#1621) 2022-12-21 22:36:05 +00:00
Stanislav Dmitrenko
0b046315ac android: Disappearing messages (#1619)
* android: Disappearing messages

* remove unused func

* remove paren

* outlined timer in meta

* reserving space for meta takes into account ttl text

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-12-21 22:07:37 +00:00
Stanislav Dmitrenko
372d7ffaa9 ios: Better check for alpha channel existing (#1616)
* ios: Better check for PNG

* Renamed
2022-12-21 16:05:45 +00:00
JRoberts
ece928d57e core: update ttl in contact user preference on profile update, fix api, tests; fix global user preferences not being updated in controller state (#1617) 2022-12-21 19:54:44 +04:00
Evgeny Poberezkin
e1740a8be4 ios: disappearing messages (#1614)
* ios: disappearing messages

* show ttl in meta if different

* mark messages as disappearing when read

* previews
2022-12-21 12:59:45 +00:00
Evgeny Poberezkin
36eba01ef4 ios: fix padding in send button context menu (#1618) 2022-12-21 12:55:59 +00:00
JRoberts
9e045a44db Revert "core: confirm ttl change to ensure consistent setting (#1587)"
This reverts commit 34e08b2058.
2022-12-21 14:10:05 +04:00
Stanislav Dmitrenko
b7d42ef889 ios: Png images support with alpha (#1613)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-12-21 00:15:18 +00:00
Stanislav Dmitrenko
e55cd82ec3 android: Live messages (#1612)
* android: Live messages

* White color

* Spacer

* button sizes

* Do not show voice button in live mode

* Add text to the last image in a row

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-12-20 23:55:01 +00:00
JRoberts
34e08b2058 core: confirm ttl change to ensure consistent setting (#1587)
* core: confirm ttl change to ensure consistent setting

* wip

* confirm_pref_pending

* xInfo

* test api

* send confirmPrefProfile

* refactor

* don't return contact

* refactor profile update

* refactor further

* refactor further

* refactor xInfo

* refactor xInfo further

* refactor
2022-12-20 22:00:46 +04:00
Evgeny Poberezkin
5e9b7366cc core: refactor chat item updates (#1611)
* core: refactor chat item updates

* removed unused function

* refactor

* refactor
2022-12-20 12:58:15 +00:00
Evgeny Poberezkin
64fb1f0b85 core: do not start disappearing timer for live messages until they stop being live, and start it on item update instead, provided they are read (#1609)
* core: do not start disappearing timer for live messages until they stop being live, and start it on item update instead, provided they are read

* change delays in tests

* diffInSeconds
2022-12-20 10:17:29 +00:00
JRoberts
84e43c57f6 core: ttl in feature chat items, view responses (#1595)
* core: ttl in feature chat items, view responses

* fix tests

* fix test

* view

* refactor

* use prefChangedValue

* use groupPrefChangedValue

* use cupIntValue

* simplify types

* groupFeatureState

* groupPrefToText

* prefToText, view

* remove prefFeature

* rename intValue -> param

* int -> param

* timedTTLText

* remove pragma

* restore pragma

* simplify

* timedTTLText

* fix tests

* off, after

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-12-19 21:18:59 +04:00
Evgeny Poberezkin
ffa37b1684 terminal: use Ctrl-L to start/continue live message, as Alt-Enter is not always supported (#1607)
* terminal: use Ctrl-L to start/continue live message, as Alt-Enter is not always supported

* refactor
2022-12-19 13:05:54 +00:00
Evgeny Poberezkin
86271fe109 terminal: support live messages (#1597)
* terminal: toggle live message updates

* terminal: send live messages (#1599)

* terminal: send live messages

* show edited messages

* send and continue live message with Alt-Enter

* truncate live messages to full words

* remove comments

* refactor

* refactor to avoid clearing live message prompt and show it faster

* $

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-12-19 11:16:50 +00:00
JRoberts
5dab099b5c core: increase thread delays in timed messages tests 2022-12-19 11:21:51 +04:00
sh
199e61e5c6 docs/webrtc: add troubleshoot section (#1602) 2022-12-18 23:42:37 +00:00
Evgeny Poberezkin
76b4fd34c1 ios: fix live messages sending incomplete words, refactor (#1604) 2022-12-18 21:20:39 +00:00
Evgeny Poberezkin
b159496257 mobile: allow ending live message with an empty string (#1603) 2022-12-18 19:21:13 +00:00
Evgeny Poberezkin
c0fb29d5f7 ios: remove unused package from project (#1598) 2022-12-17 18:52:02 +00:00
Evgeny Poberezkin
4ab7e5e1c8 terminal: command to get last item ID (to reference it in the tests) (#1596)
* terminal: command to get last item ID (to reference it in the tests)

* lastItemId

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-12-17 19:33:58 +04:00
Evgeny Poberezkin
9e847c2e1f ios: live messages (#1569)
* ios: live messages

* remove comments

* remove conflict

* live message buttons and alert

* only send full words

* fix double sending

* typing indicator in live items

* add live parameter to API

* typing indication, pass live parameter to API

* refactor to support live messages with attachments

* disable attachments
2022-12-17 14:02:07 +00:00
Evgeny Poberezkin
d105e59655 core: set item that was live as 0, that was never live as NULL (Maybe Bool type) (#1594)
* core: set item that was live as 0, that was never live as NULL (Maybe Bool type)

* fix
2022-12-17 13:58:16 +00:00
JRoberts
f128ebac87 core: timed messages terminal api, tests (#1591) 2022-12-17 14:49:03 +04:00
Stanislav Dmitrenko
b4de9c266b ios: Ability to add stickers (#1593)
* ios: Ability to add stickers

* fix text alignment for correct input field height

* refactor

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-12-17 09:01:49 +00:00
JRoberts
e410fc7736 core: fix mark read queries (#1592) 2022-12-16 14:32:37 +00:00
Evgeny Poberezkin
f5bd6eb4c3 core: do not mark live items as editied until it's no longer live (#1590)
* core: do not mark live items as editied

* Update src/Simplex/Chat/Store.hs

* mark item as edited when it stops being live
2022-12-16 12:31:35 +00:00
JRoberts
cee403c1ed core: simplify terminal mark messages read logic (#1589) 2022-12-16 15:56:16 +04:00
Evgeny Poberezkin
8786e2147a core, mobile: logic for enabling disappearing messages (#1588)
* core: logic for enabled for disappearing messages

* refactor

* update feature enabled in UI
2022-12-16 10:27:59 +00:00
Evgeny Poberezkin
6b8705e9f4 core: support for live messages (#1577) 2022-12-16 11:51:04 +04:00
Stanislav Dmitrenko
acfb98bd81 android: Optimized chats snapshotFlow (#1578)
* android: Optimized chats snapshotFlow

* Concurrency test

* Revert "Concurrency test"

This reverts commit 911dd0c2ef.

* Comment

* Better catch
2022-12-15 19:57:57 +00:00
Stanislav Dmitrenko
c240456b80 android: Progress indicator and group members loading (#1579)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-12-15 19:29:17 +00:00
Stanislav Dmitrenko
9e1641a154 android: Changed a path in script for downloading libs (#1580) 2022-12-15 19:25:49 +00:00
JRoberts
17cd3cdca4 core: make ttl optional in TimedMessagesPreference (#1583)
* core: make ttl Maybe in TimedMessagesPreference

* omitNothingFields
2022-12-15 18:11:08 +00:00
JRoberts
aa264690ab core: add ttl to XMsgUpdate (#1581) 2022-12-15 17:29:46 +04:00
JRoberts
0e837ae392 core: timed messages (#1561)
* docs: disappearing messages rfc

* change schema

* word

* wip

* wip

* todos

* todos

* remove cancel, refactor

* revert prefs

* CITimed

* schema

* time on send direct

* time on send group

* add ttl to msg container, refactor

* timed on receive

* time on read

* getTimedItems, fix tests

* mark read in terminal - view, input, output, fix tests

* refactor

* comment

* util

* insert atomically

* refactor

* use guards

* refactor startTimedItemThread

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-12-15 15:17:29 +04:00
Stanislav Dmitrenko
68525b4131 android: Show error instead of crashing after failed to parse chats (#1573)
* android: Show error instead of crashing after failed to parse chats

* Just for test

* update strings

* Revert "Just for test"

This reverts commit f9c9a20ab6.

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-12-15 10:56:20 +00:00
Stanislav Dmitrenko
8775db7c97 android: Create group link with one click (#1575)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-12-14 22:47:24 +00:00
Stanislav Dmitrenko
f266debd56 android: Verify connection security code (#1567)
* android: Verify connection security code

* Dividers

* Changes

* Padding

* Share connection code

* Share connection code

* Unused

* icon sizes

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-12-14 14:44:26 +00:00
Evgeny Poberezkin
044c7a8191 mobile: update types for timed messages preference (#1574) 2022-12-14 13:53:31 +00:00
Evgeny Poberezkin
677c6aeb2e core: types for timed and live messages (#1572)
* core: types for timed and live messages

* add protocol tests
2022-12-14 16:16:11 +04:00
Evgeny Poberezkin
7b8f5be821 core: type for group preference for timed messages (#1568)
* core: type for group preference for timed messages

* remove unused func
2022-12-14 12:30:24 +04:00
Evgeny Poberezkin
21765905a7 ios: create group link with one click (#1566)
* ios: create group link with one click

* line break

* move call
2022-12-13 17:15:45 +00:00
Evgeny Poberezkin
70a9c01477 translations (#1563)
* Translated using Weblate (Russian)

Currently translated at 100.0% (831 of 831 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 98.4% (772 of 784 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/
2022-12-13 14:53:06 +00:00
Evgeny Poberezkin
678dbec3e2 core: different types for chat preferences, to allow parameters (#1565) 2022-12-13 14:52:34 +00:00
Stanislav Dmitrenko
bd4c7dffbf android: Notification mode selection in onboarding stage (#1535)
* android: Notification mode selection in onboarding stage

* Change

* Different texts

* Disable service starting until on-boarding finishes

* refactor, change strings

* update layout

* update layout

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-12-12 19:27:28 +00:00
Stanislav Dmitrenko
1eb4030080 android: Fix crash on multiple selection of images (#1560)
* android: Fix crash on multiple selection of images

* Revert "android: Fix crash on multiple selection of images"

This reverts commit 11a6113b4f.

* Disable image selection when there are images already selected

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-12-12 17:27:14 +00:00
sh
bcc64442e9 nix: merge android and ios (#1551) 2022-12-12 15:44:49 +00:00
Stanislav Dmitrenko
1246b9e376 android: Chat auto-scrolling behaviour (#1556)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-12-12 15:34:26 +00:00
Evgeny Poberezkin
d6e9a87d58 ios: verify connection translations (#1558) 2022-12-12 12:50:15 +00:00
Evgeny Poberezkin
cddd3cd673 FR translations (#1559)
* Translated using Weblate (German)

Currently translated at 100.0% (822 of 822 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/

* Added translation using Weblate (French)

* Added translation using Weblate (French)

* Translated using Weblate (French)

Currently translated at 0.0% (0 of 773 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/

* Translated using Weblate (French)

Currently translated at 2.0% (16 of 773 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/

* Translated using Weblate (French)

Currently translated at 2.0% (16 of 773 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/

* Translated using Weblate (French)

Currently translated at 0.3% (3 of 822 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% (773 of 773 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/

* Translated using Weblate (French)

Currently translated at 0.9% (8 of 822 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% (773 of 773 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/

Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Ophiushi <ptlfr@pm.me>
2022-12-12 12:33:50 +00:00
JRoberts
e00ef7c7da core: improve stability of file transfer handshake by using async agent commands (#1541) 2022-12-12 16:33:07 +04:00
Evgeny Poberezkin
1a201cfadf translations: corrections to DE, add FR (#1557)
* Translated using Weblate (German)

Currently translated at 100.0% (822 of 822 strings)

Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/

* Added translation using Weblate (French)

* Added translation using Weblate (French)

* Translated using Weblate (French)

Currently translated at 0.0% (0 of 773 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/

* Translated using Weblate (French)

Currently translated at 2.0% (16 of 773 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/

* Translated using Weblate (French)

Currently translated at 2.0% (16 of 773 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/

* Translated using Weblate (French)

Currently translated at 0.3% (3 of 822 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% (773 of 773 strings)

Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/

Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Ophiushi <ptlfr@pm.me>
2022-12-12 11:49:08 +00:00
JRoberts
a4ecb41743 ios, android: show send direct message button only for active members (#1554) 2022-12-12 15:27:52 +04:00
Stanislav Dmitrenko
e347f5329c android: Different style for voice button when no permissions granted (#1555)
* android: Different style for voice button when no permissions granted

* linebreaks

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-12-12 10:25:49 +00:00
Stanislav Dmitrenko
741b3e8848 android: Voice messages refactoring (#1511)
* android: Voice messages refactoring

* Different way to block text field from editing while recording voice

* Limited voice record max duration

* Better end of recording when it reaches timeout

* New way of doing things

* Change

* Change

* Stop event refactor

* Stopped state

* Replaced some helpers

* Replaced calls in when()

* Comments

* Change

* Change

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-12-12 09:36:37 +00:00
Evgeny Poberezkin
7b4710d198 ios: verify connection security code (#1542)
* ios: verify connection security code

* verification in member sheet (still crashes)

* use navigation view for members list

* ios: show verified status in the lists

* update verification status in the list of members

* verified shield layout

* update icon, make add member navigation to right

* refactor chatPreviewTitle
2022-12-12 08:59:35 +00:00
sh
c77f6100c5 fdroid: fix metadata (#1550)
* fdroid: fix description

* add header

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-12-11 18:33:56 +00:00
Evgeny Poberezkin
138dc7fe8f 4.3.2: terminal, ios (101), android (78) 2022-12-11 15:51:23 +00:00
Evgeny Poberezkin
0535d84719 website: add F-Droid key hash to open SimpleX links in the app 2022-12-11 15:39:11 +00:00
Evgeny Poberezkin
f4447ffe89 readme: add BCH address for donations 2022-12-11 14:06:22 +00:00
Evgeny Poberezkin
146d5f99bc core: clear connection verification status (#1540) 2022-12-10 12:09:45 +00:00
Evgeny Poberezkin
73e5fff8f5 core: fix parser 2022-12-10 08:43:54 +00:00
Evgeny Poberezkin
33e7538172 core: group description (#1538)
* core: group description

* support multi-line welcome message

* fix
2022-12-10 08:27:32 +00:00
Stanislav Dmitrenko
49c9c501aa android: Fix for AddressAlreadyInUse exception (#1534)
* android: Fix for AddressAlreadyInUse exception

* simplify loop

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-12-09 22:15:50 +00:00
Evgeny Poberezkin
a177dc5a13 core: refactor parser (#1537)
* core: refactor parser

* fix
2022-12-09 21:50:01 +00:00
Evgeny Poberezkin
a4f207875f show /create link command when group is created (#1536) 2022-12-09 18:22:03 +00:00
JRoberts
bcca0998d5 core: optimize group deletion (#1529) 2022-12-09 20:01:31 +04:00
Evgeny Poberezkin
95cc9e1e55 core: verify connection (#1530)
* core: verify connection

* update commands

* api to get/set verification code/status

* add migration

* refactor

* change command / response names

* reset verified status if code from agent doesn't match
2022-12-09 15:26:43 +00:00
sh
ab5ae2d2cb build-android: add skip flag and update logic (#1525)
* build-android: add skip flag and update logic

* build-android: change equal
2022-12-08 08:55:37 +00:00
JRoberts
40a91a7273 android: version 4.3.1 (77) 2022-12-08 10:48:13 +04:00
JRoberts
1240b31df8 ios: version 4.3.1 (100) 2022-12-08 10:41:59 +04:00
Evgeny Poberezkin
ff14730738 mobile, core: fix voice message reception in groups (#1524) 2022-12-07 22:18:22 +00:00
JRoberts
0cba3a4bb3 4.3.1 2022-12-07 21:10:45 +04:00
JRoberts
208f8a3346 android: version 4.3.1 (76) 2022-12-07 21:09:52 +04:00
JRoberts
caa3efb9ed ios: version 4.3.1 (99) 2022-12-07 21:04:43 +04:00
JRoberts
4beb916754 ios: deleted item preview; android: refactor removeChatItem (#1523) 2022-12-07 20:46:38 +04:00
Stanislav Dmitrenko
c1ee04eed1 android: Cancel notification after message deletion (#1512)
* android: Cancel notification after message deletion

* Improve

* Temporary chat item

* Better

* Changes

* cInfo, cItem

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-12-07 19:49:17 +04:00
JRoberts
0ad3bc9993 android: show open direct chat button for direct contacts (#1521) 2022-12-07 19:07:31 +04:00
JRoberts
9893aa665a core: don't mark contacts as used on api get chat (#1522) 2022-12-07 19:05:32 +04:00
JRoberts
fda8836ab8 ios: show open direct chat button for direct contacts (#1518) 2022-12-07 17:30:15 +04:00
Evgeny Poberezkin
05fdd07409 website: add SHA256 of the key signing GitHub android APK to open links in the app 2022-12-07 10:27:31 +00:00
Evgeny Poberezkin
fb8f5facd0 terminal: only set contact/group as active for terminal input if it is not muted (#1514) 2022-12-07 09:58:01 +00:00
Stanislav Dmitrenko
8bdb784a14 android: Added rememberSaveable in pref screens (fix merge) (#1517) 2022-12-07 09:57:23 +00:00
Stanislav Dmitrenko
5d785aad2e android: Added rememberSaveable in pref screens (#1509) 2022-12-06 21:04:15 +00:00
Stanislav Dmitrenko
ce11d58a76 android: Saving prefs alert on exit with unsaved changes (#1508)
* android: Saving prefs alert on exit with unsaved changes

* DIfferent implementation for AlertDialog with long buttons

* Braces

* Change

* Alignment

* Rename

* small changes

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-12-06 20:48:15 +00:00
Evgeny Poberezkin
887b374bfc readme: add mastodon link 2022-12-06 19:53:08 +00:00
Evgeny Poberezkin
94dc967197 readme: update screenshots 2022-12-06 17:30:32 +00:00
JRoberts
4319a581ca core: more test cases checking deletion of unused contacts and incognito profiles (#1513) 2022-12-06 20:19:01 +04:00
JRoberts
fb05218558 core: delete unused contacts after deleting group (#1503) 2022-12-06 17:12:39 +04:00
Evgeny Poberezkin
edf2d02a0d blog: v4.3 release announcement (#1510)
* blog: v4.3 release announcement

* add images

* update image URIs

* update post

* typos

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* correction

* website preview, readme update

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-12-06 12:53:14 +00:00
Evgeny Poberezkin
87ba429dfd Merge branch 'stable' 2022-12-05 20:47:16 +00:00
Evgeny Poberezkin
7af1a7cf76 docs: update f-droid store info (#1507) 2022-12-05 20:46:11 +00:00
sh
df619acdd4 build-android: update nix install (#1506) 2022-12-05 18:45:18 +00:00
Evgeny Poberezkin
503d0cd451 android: make backup disabled by default (#1505) 2022-12-05 15:05:56 +00:00
Stanislav Dmitrenko
1294a00ee7 android: Vibration pattern (#1504)
* android: Vibration pattern

* update pattern

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-12-05 14:56:37 +00:00
Stanislav Dmitrenko
0a8069ada2 android: Notification sound (#1468)
* android: fix full screen call notification (#1466)

* android: Closing call means canceling notification too

* show full screen call when screen is off OR locked

* make notification non-silent and set category

* remove call notification category

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* android: Notification sound

* Log

* Ringtone channel

* rename call channel

* Non-hideable headsUp notification and reject button

* Removed LockScreenCallChannel

* call channel name

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-12-05 13:13:48 +00:00
Evgeny Poberezkin
c167f594b9 website: add .well-known folder to allow mobile apps process URLs 2022-12-05 11:28:22 +00:00
Evgeny Poberezkin
ce5124594d blog: permalink for v4.3 post (#1499) 2022-12-04 18:18:23 +00:00
Evgeny Poberezkin
5de96aa7c4 android: v4.3 (75) 2022-12-04 18:07:41 +00:00
Evgeny Poberezkin
cdbf8e2715 ios: v4.3 (98) 2022-12-04 17:36:41 +00:00
Evgeny Poberezkin
69b2f8f535 mobile: german translations (#1498) 2022-12-04 15:18:35 +00:00
Evgeny Poberezkin
ff17f89551 android: improve UX to create groups and UI of group preferences (#1496) 2022-12-04 15:16:41 +00:00
Evgeny Poberezkin
358712fa31 ios: translations (#1495) 2022-12-04 11:41:45 +00:00
Evgeny Poberezkin
75cad8a6bf ios: improve UX for contact/group preferences (#1494)
* ios: improve UX for contact/group preferences

* refactor
2022-12-04 11:30:51 +00:00
Evgeny Poberezkin
e5969e197a mobile: "delete for everyone" feature, translations (#1491) 2022-12-04 09:29:00 +00:00
Evgeny Poberezkin
a9ffe4e039 android: function to call api on background thread, use it for marking items read (#1493) 2022-12-04 08:36:19 +00:00
Stanislav Dmitrenko
bf2129c4ae android: Making full backup optional (#1477)
* android: Making full backup optional

* move to database settings

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-12-04 07:50:36 +00:00
Evgeny Poberezkin
04f10aede7 ios: fix screen protection in sheets, remove screen protection from settings and image pickers (#1492) 2022-12-03 21:42:12 +00:00
Evgeny Poberezkin
ffbff93374 ios: menu to hide revealed chat item (#1490) 2022-12-03 19:21:47 +00:00
JRoberts
f3630d934c android: marked deleted / reveal ui (#1488)
* android: marked deleted / reveal ui

* marked deleted, reveal

* fix ios

* different alerts
2022-12-03 18:21:32 +00:00
Evgeny Poberezkin
6f59df4e33 prohibit direct messages to group contacts unless group preferences allow them (#1476)
* prohibit direct messages to group contacts unless group preferences allow them

* tests

* refactor

* more test
2022-12-03 18:06:21 +00:00
Evgeny Poberezkin
e44e9a0940 mobile: broker error type (#1475)
* mobile: broker error type

* fix

* ios: update libraries

* change AgentErrorType to String
2022-12-03 18:05:32 +00:00
Evgeny Poberezkin
c43ba7bf23 ios: fix item deletion in groups (#1487) 2022-12-03 15:21:14 +00:00
JRoberts
9e48e1f74a android: refactor CIVoiceView usage in FramedItemView (latter accounts only for framed voice messages) (#1486) 2022-12-03 18:28:07 +04:00
JRoberts
0001885971 obsolete comment 2022-12-03 18:24:20 +04:00
Evgeny Poberezkin
e0c932c04e core: change AgentErrorType to String to preserve backward compatibility with stored errors (#1485) 2022-12-03 13:28:51 +00:00
JRoberts
01a86336c0 android: simplify logic for allowing voice messages on alert; fix non exhaustive when in SendMsgView (#1484) 2022-12-03 16:19:13 +04:00
JRoberts
48d24d3582 ios: simplify chat item context menu (#1483) 2022-12-03 15:53:46 +04:00
JRoberts
07ef6e4090 ios: marked deleted chat items, full deletion preference; android: types (#1473)
* ios: marked deleted chat items; full deletion preference

* text_, menu, backend

* android types

* more android types

* fix

* refactor ios

* restore previews

* box

* refactor menu

* revert unnecessary content.text changes

* Update apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* revert layered framed items

* clever framed view

* improve look

* restore previews

* restore previews

* refactor

* refactoring, almost looks good

* look

* add previews

* more previews

* remove preview of legacy item

* ChatItemDeleted

* flip if

* remove text_

* refactor

* abstract pref property

* move marked deleted

* revert pref change

* undo menu

* fix - change to constants

* undo pref logic

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-12-03 15:40:31 +04:00
Evgeny Poberezkin
19163776e3 android: fix 2022-12-03 09:06:39 +00:00
Stanislav Dmitrenko
62b1f786f1 android: Remove runningAppProcesses check (#1478)
* android: Remove runningAppProcesses check

* simplify

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-12-03 06:29:52 +00:00
Stanislav Dmitrenko
d479e9b2bf android: Change of launchMode in an activity and different behavior of back button (#1480)
* android: Change of launchMode in an activity and different behavior of back button
- Android versions <= 10 are vulnerable to StrandHogg 1. This commit fixes the behavior of the app on affected versions of Android

* simplify condition

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-12-02 23:07:21 +00:00
Evgeny Poberezkin
0beb260b00 android: json parsing settings explicitNulls = false 2022-12-02 21:36:58 +00:00
Evgeny Poberezkin
bc28568c63 core: update broker error type (#1474)
* core: update broker error type

* fix test

* fix test
2022-12-02 15:01:26 +00:00
Stanislav Dmitrenko
a4dd520248 android: Better shared preference handling (#1471)
* android: Better shared preference handling

* To make sure we return real value, not untransformed one

* Revert "To make sure we return real value, not untransformed one"

This reverts commit 5a268e2cf4.
2022-11-30 22:20:08 +00:00
JRoberts
9ad29aa17e core: full deletion by sender based on preference; don't overwrite item content on "mark deleted" (#1470) 2022-11-30 19:42:33 +04:00
Stanislav Dmitrenko
6f24281671 android: prevent crash when decrypting DB after restore (#1469) 2022-11-30 12:20:49 +00:00
Evgeny Poberezkin
eb81b62892 terminal: allow trailing spaces in terminal commands (e.g., to drag and drop files) (#1467) 2022-11-30 08:25:42 +00:00
Stanislav Dmitrenko
ef1133ee98 android: fix full screen call notification (#1466)
* android: Closing call means canceling notification too

* show full screen call when screen is off OR locked

* make notification non-silent and set category

* remove call notification category

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-11-29 15:53:47 +00:00
Evgeny Poberezkin
1872744543 core, mobile: add group feature to allow direct messages (#1465)
* core, mobile: split group features to a separate type (to add directAllowed later)

* add directMessages group feature, update tests
2022-11-29 15:19:20 +00:00
Stanislav Dmitrenko
303aeaaba5 android: Instantly apply screen protection (#1464) 2022-11-29 14:21:41 +00:00
Stanislav Dmitrenko
c5359d698c android: Voice messages enhancements (#1451)
* android: Vocie messages enhancements

* Canceling voice record when it was disabled in prefs

* Quote placement in voice message chat item

* Ordering of checks

* Showing progress logic was changed

* Showing progress logic was changed

* Update group prefs without reenter

* Optimization of voice chat items

* Stop audio playing and recoring when in call
2022-11-29 13:41:04 +00:00
JRoberts
acd72fb269 ios: remove voice message preview and stop rec/play if preference change prohibits it; more robust logic to stop playback on send (#1463) 2022-11-29 15:23:54 +04:00
sh
8d096f469d docs: corrections (#1456)
* docs: corrections

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* docs: onion instead of hostname2

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-11-29 09:17:26 +00:00
Stanislav Dmitrenko
b204d21d9e android: No crash when clicking on a link with unknown activity action (#1460)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-11-29 09:16:03 +00:00
JRoberts
c9620a594e ios: fix - stop voice message preview playback on send (#1461) 2022-11-29 13:06:45 +04:00
Stanislav Dmitrenko
538024de61 android: Show user's camera when system camera is unavailable (#1458)
* android: Show user's camera when system camera is unavailable

* Multiple places of camera launcher

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-11-29 08:50:54 +00:00
JRoberts
5c9a14fdb6 ios: show button for opening settings when asking for microphone permission to record voice message (#1459) 2022-11-29 12:41:48 +04:00
JRoberts
9295bdca3e ios, android: disable save and test buttons if all servers are disabled (#1457) 2022-11-29 12:28:26 +04:00
Evgeny Poberezkin
00466f4654 android: v4.3-beta.3 2022-11-28 18:05:25 +00:00
Evgeny Poberezkin
5d976d3c67 docs: SMP servers (#1387)
* docs: SMP servers

* docs: add server configuration

* docs: small fixes

* docs: fix markdown rendering

* docs: csv file instead of URL

* docs: small fixes

* docs: check if log exist

* docs: no i allowed

* docs: correction

* docs: no need to source profile

* docs: no calue allowed

* docs: systemd fix

* docs: small corrections

* docs: some more small fixes

* docs: expand monitoring

* docs: apply suggestions

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* client configuration

* images

Co-authored-by: shum <shum@liber.li>
Co-authored-by: sh <37271604+shumvgolove@users.noreply.github.com>
2022-11-28 17:34:43 +00:00
JRoberts
7360bd098a ios: version 4.3 (97) 2022-11-28 20:46:06 +04:00
JRoberts
3a755286c1 ios, android: improve preference change chat items layout (#1454) 2022-11-28 20:03:39 +04:00
JRoberts
e5f07993a7 mobile: don't show notifications for certain chat items (#1453) 2022-11-28 19:11:26 +04:00
JRoberts
56a3f98dc0 core: create certain informational chat items as read (#1452) 2022-11-28 16:27:22 +04:00
JRoberts
9949ac073f ios: decrease voice message progress bar width (#1450)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-11-28 08:50:05 +00:00
Stanislav Dmitrenko
c102a884d1 android: Stickers fix (#1449)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-11-28 08:47:33 +00:00
Evgeny Poberezkin
1e6e9ad5e2 mobile: update version ios v4.3 (96), android v4.3-beta.2 (73) 2022-11-28 08:39:50 +00:00
JRoberts
e6c5ad5833 ios: fix image context item background color (#1448) 2022-11-28 11:23:40 +04:00
Evgeny Poberezkin
8af0229f52 terminal: set voice message preferences (#1447)
* terminal: set voice message preferences

* enable all tests
2022-11-27 13:54:34 +00:00
Evgeny Poberezkin
7f0355ec67 core: only send voice messages without acceptance (#1444)
* core: only send voice messages without acceptance

* remove some unnecessary changes

* update

* refactor receiveInlineMode
2022-11-26 22:39:56 +00:00
JRoberts
ade8c97b16 ios: version 4.3 (95) 2022-11-26 18:54:54 +04:00
JRoberts
ee18bce964 mobile: change link to servers 2022-11-26 18:52:08 +04:00
JRoberts
7e204127b8 ios: hide voice message button in chat console (#1442) 2022-11-26 18:43:49 +04:00
JRoberts
5619152810 android: version 4.3-beta.1 (72) 2022-11-26 14:30:52 +04:00
JRoberts
6f463c16a5 ios: version 4.3 (94) 2022-11-26 14:10:55 +04:00
JRoberts
33b3557950 mobile: suppress fileAlreadyReceiving error (#1441) 2022-11-26 13:55:36 +04:00
Evgeny Poberezkin
e5912e58f5 mobile: show "transfer faster" and "switch address" without dev tools (#1440) 2022-11-26 09:45:10 +00:00
JRoberts
7a0d2add17 android: add missing translation 2022-11-26 13:22:26 +04:00
JRoberts
ac30602a50 4.3.0 2022-11-26 13:09:09 +04:00
JRoberts
6cc4e2e801 android: version 4.3 (71) 2022-11-26 13:07:34 +04:00
JRoberts
5650898c2c ios: version 4.3 (93) 2022-11-26 13:00:43 +04:00
JRoberts
336a170b3a mobile: hide full delete preferences (#1438) 2022-11-26 12:51:56 +04:00
JRoberts
9c06acd4bc ios: voice message repeat receive ui workaround (#1437) 2022-11-26 12:43:26 +04:00
mlanp
1d819a4af3 mobile: German translations for 4.3 (#1432)
* android/iOS: added and fixed german translations for v4.3

* import localization

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-11-26 11:56:10 +04:00
Evgeny Poberezkin
5263698e64 ios: show notifications alert only once, each time after notifications are disabled (#1428) 2022-11-25 21:43:10 +00:00
Evgeny Poberezkin
5d0d9a1c18 android: add context menu to voice message (aka play button) (#1427) 2022-11-25 20:53:43 +00:00
Evgeny Poberezkin
7407884223 android: protect screen (#1425)
* android: protect screen

* hide screen, translations
2022-11-25 19:33:29 +00:00
Evgeny Poberezkin
07acbfe743 mobile: remove BETA from strings 2022-11-25 17:56:56 +00:00
JRoberts
5e2c868612 core: silence repeat file receive error (#1426) 2022-11-25 21:20:55 +04:00
JRoberts
f6ed099f17 ios: voice messages - improve hold to record button logic, alert if not allowed (#1424) 2022-11-25 21:05:14 +04:00
Stanislav Dmitrenko
098cbf33b6 android: Group chat items (#1423)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-11-25 16:42:53 +00:00
Stanislav Dmitrenko
f8cf35879f android: Voice messages UI changes (#1422)
* android: Voice messages UI changes

* Alert for groups

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-11-25 16:31:04 +00:00
Evgeny Poberezkin
60fedbf5d2 core: only create feature items in used contacts (#1421)
* core: only create feature items in used contacts

* fix, test
2022-11-25 15:37:36 +00:00
Evgeny Poberezkin
87d306383c ios: protect screen (#1420)
* ios: protect screen

* AppSheet

* translations

* correction

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-11-25 14:31:37 +00:00
Evgeny Poberezkin
18b772a80b ios: translations (#1411) 2022-11-25 13:50:26 +00:00
Stanislav Dmitrenko
789c54bd5f android: Voice messages playing logic overhaul (#1418)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-11-25 13:06:56 +00:00
Stanislav Dmitrenko
f8214b0604 android: Chat items for preferences (#1409)
* android: Chat items for preferences

* fix filled

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-11-25 12:50:22 +00:00
Stanislav Dmitrenko
2eec81c35e android: Duration formatter (#1419) 2022-11-25 12:23:30 +00:00
JRoberts
eb099c526a core: reuse mergedPreferences/fullGroupPreferences for determining prohibited features and creating chat items instead of re-calculating (#1417) 2022-11-25 15:16:55 +04:00
JRoberts
e18bb74bfd ios: show voice message button based on preference (#1416) 2022-11-25 15:16:37 +04:00
Evgeny Poberezkin
9225f437e9 android: simplex link mode setting, ios: untrusted simplex links (#1412)
* android: simplex link mode setting, ios: untrusted simplex links

* correction

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-11-25 09:55:51 +00:00
JRoberts
de7548a9a8 ios: alert on error when recording voice message (#1415) 2022-11-25 12:30:24 +04:00
JRoberts
a58a0fae29 android: preferences - fix reset & save buttons being enabled when displayed as disabled, fix info rows being interactive (#1414) 2022-11-25 11:53:29 +04:00
JRoberts
e32b24ef70 android: move preferences files to respective packages; dividers (#1413) 2022-11-25 11:27:22 +04:00
Evgeny Poberezkin
5d73b364d8 android: fix type name 2022-11-24 17:34:01 +00:00
Stanislav Dmitrenko
fa2f303547 android: Global preferences and per contact (#1290)
* android: Global preferences and per contact

* Fixes

* Changes in UI

* Fix

* Strings and more descriptions

* Spelling error

* No dots at the end

* Adapting changes from core

* Adapting changes from core

* Change

* Simplified user's choice with toggle

* Changes after merge

* Updated preferences to the latest changes in core

* Strings

* Changes

* Small changes

* Contact will be updated in UI too

* bigger icons in section headers

* Icons and colors

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-11-24 17:30:58 +00:00
JRoberts
a6e4e68bc5 ios: voice messages (#1389)
* experiments

* audio recording in swiftui

* recording encapsulated

* permission + playback

* stopAudioPlayback on cancel

* method names

* check permission in recording start

* run timer on main thread

* remove obsolete view

* don't call playback timer callback unless player is playing

* compose + send view + preview + send

* animation + improve state + quality

* fix recording not stopping in time

* animate to end

* remove recorder delegate, fix cancelling during recording

* replace print with log

* recording start error constructor

* CIVoiceView file

* chat item wip

* chat item wip

* refactor settings

* layout

* send correct duration

* item previews

* more background, animation

* more layout

* more layout, send button conditions

* context, preview, quote, notification texts

* chat item actions

* use isEmpty

* remove comment

* uncomment file.loaded

* more layout, hold to record

* more layout

* preview player stop on disappear

* more layout

* comment

* only one player or recording

* remove voice message on chat close

* fix state bug

* remove commented code

* length 30

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-11-24 21:18:28 +04:00
Evgeny Poberezkin
4485d46307 mobile: simplex links in UI, core: trusted uri for simplex links (#1410) 2022-11-24 17:14:56 +00:00
Evgeny Poberezkin
a7345ee4d9 core: markdown for simplex invitation links (#1408)
* core: markdown for simplex invitation links

* update markdown for simplex links

* update markdown

* update

* stabilize test
2022-11-24 13:13:26 +00:00
Evgeny Poberezkin
388aaec80b core: config to send inline files (#1406)
* core: config to send inline files

* update config

* add/update tests

* fix tests
2022-11-23 16:08:33 +00:00
Stanislav Dmitrenko
21722b3417 android: Advanced server config (#1403)
* android: Advanced server config

* Update apps/android/app/src/main/java/chat/simplex/app/views/usersettings/ScanSMPServer.kt

* Camera permission, dropping tested value, different font

* For review

* Partial redraw of the view in testing stage

* Comment

* Icon

* Icon

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-11-23 11:13:24 +00:00
Evgeny Poberezkin
e6e5faeb9c core: chat items for group preferences (#1402)
* core: chat items for group preferences

* chat items for group preference changes and sent item for contact/user prerences changes

* prohibited features, tests

* enable all tests

* fix
2022-11-23 11:04:08 +00:00
Evgeny Poberezkin
67d78e14be Merge branch 'stable' 2022-11-22 14:12:18 +00:00
Evgeny Poberezkin
0d1a70af34 android: v4.2.2 (70) 2022-11-22 14:11:23 +00:00
Evgeny Poberezkin
2b09fb425d core: chat items showing preference changes (#1399) 2022-11-22 12:50:56 +00:00
Evgeny Poberezkin
ab91c54080 ios: version 4.2.2 (92) 2022-11-22 12:44:40 +00:00
Evgeny Poberezkin
c7f70f0ed0 ios: resolved packages 2022-11-22 09:47:42 +00:00
Evgeny Poberezkin
5806a2ceb4 Merge branch 'stable' 2022-11-22 08:55:01 +00:00
Evgeny Poberezkin
6b71cc59c8 blog: updates (#1400) 2022-11-22 08:49:33 +00:00
Evgeny Poberezkin
33a866463d core: fix sql that was doubling a group in the list of chats when member joined the group twice (#1378) 2022-11-21 10:37:11 +00:00
Stanislav Dmitrenko
fb165622aa android: Wrapped code in SideEffect (#1396) 2022-11-21 10:24:18 +00:00
Evgeny Poberezkin
7e3d53b621 ios: advanced server configuration (#1388)
* ios: advanced server configuration

* UI is mostly working, QR code scan

* refactor

* error alerts

* fixes

* remove old view

* rename view

* translations

* only show valid QR code, spinner during server test

* update tested status on edit

* space wtf

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* moar space

* translation

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* translations

* translations

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-11-21 08:37:13 +00:00
Evgeny Poberezkin
7544d2f9e7 core: fix preset servers (#1392)
* core: fix preset servers

* simplify

* fix
2022-11-21 07:43:41 +00:00
Evgeny Poberezkin
02fa81e8aa mobile: show unknown content with attached file as file item (for partial forward compatibility with voice and video messages) (#1394)
* mobile: show unknown content with attached file as file item (for partial forward compatibility with voice and video messages)

* fix

* android: show unknown files
2022-11-21 07:42:36 +00:00
Evgeny Poberezkin
4296b6c622 android: import function to parse server address (#1391) 2022-11-21 07:39:36 +00:00
sh
b5652bce81 docker: enable sqlcipher (#1390) 2022-11-20 11:56:01 +00:00
Evgeny Poberezkin
b8298aa458 Merge branch 'stable' 2022-11-20 11:20:10 +00:00
solus-hq
c3244f1b76 Add OpenSSL for macOS description (#1370)
Co-authored-by: shark <shark@shark.work>
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-11-18 19:29:02 +00:00
Evgeny Poberezkin
0ad74d9538 docs: CLI compilation (#1359)
* docs: CLI compilation

* update

* remove BETA

* amend CLI build steps
2022-11-18 17:54:57 +00:00
Stanislav Dmitrenko
a4be68f4bd android: Audio messages (#1070)
* Audio messages testing

* Without Vorbis

* Naming

* Voice message auto-receive, voice message composing

* Experiments with audio

* More recording features

* Unused code

* Merge master

* UI

* Stability

* Size limitation

* Tap and hold && tap and wait and click logics

* Deleted unused lib

* Voice type

* Refactoring

* Refactoring

* Adapting to the latest changes

* Mini player in preview

* Different UI for some elements

* send msg view style

* *** in translation

* Animation

* Fixes animation performance

* Smaller font for recording time

* File names

* Renaming

* No edit possible for audio messages

* Prevent adding text to edittext

* Bubble layout

* Layout

* Refactor

* Paddings

* No crash, please

* Draw progress as a ring

* Padding

* Faster status updates while listening voice

* Faster status updates while listening voice

* Quote

* backend comment

* Align

* Stability

* Review

* Strings

* Just better

* Sync of recorder and players

* Replaced Icon's with ImageButton's

* Icons size

* Error processing

* Update apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt

* rename composable

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-11-18 21:02:24 +04:00
JRoberts
0cb8f8ad82 core: fix group preferences update (#1385) 2022-11-18 16:07:40 +04:00
sh
9d7bb06396 docker: update build (#1375) 2022-11-18 09:00:43 +00:00
Daniel Nathan Gray
a8b9200c9a Fix dead links to audit (#1374) (#1374) 2022-11-17 17:26:19 +00:00
JRoberts
a9c2a7dcaa ios: remove accent color on chat info views navigation links (#1382) 2022-11-17 20:53:02 +04:00
Evgeny Poberezkin
38b28f866c sdk: update version 0.1.1 2022-11-17 14:46:49 +00:00
Evgeny Poberezkin
bfa7ff16ff sdk: fix typescript client (#1380) 2022-11-17 14:45:48 +00:00
JRoberts
5c2b70a214 core: fix test name 2022-11-17 14:42:28 +04:00
JRoberts
7e3f91f87c core: add sanity checks in sql to include quoted items only from the same chat (#1379) 2022-11-17 14:38:14 +04:00
Evgeny Poberezkin
f54faebff3 core: fix sql that was doubling a group in the list of chats when member joined the group twice (#1378) 2022-11-17 09:58:52 +00:00
JRoberts
4e5aa3dcbc ios: adjust preferences UX; fix group profile not updating; fix servers api (#1377) 2022-11-17 12:59:13 +04:00
Evgeny Poberezkin
56f3874a93 ios: move preferences, icon (#1376)
* ios: move preferences, icon

* don't disable database settings on chat stop

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-11-17 10:57:27 +04:00
JRoberts
828b502431 ios: load and save preferences (#1373) 2022-11-16 20:26:43 +04:00
Evgeny Poberezkin
491fe4a9bf core, ios: advanced server config (#1371)
* ios: advanced server config

* simplify UI

* core: ServerCfg

* commit migration, update schema

* add preset servers to response

* return default servers if none saved

* fix test
2022-11-16 15:37:20 +00:00
Evgeny Poberezkin
f8302e2030 core: SMP server connection test (#1367)
* core: SMP server connection test

* fix test

* update simplexmq
2022-11-15 18:31:29 +00:00
JRoberts
fd34c39552 core: fix voice msg content text representation 2022-11-15 15:56:38 +04:00
JRoberts
b1fa1a84fe core: voice msg content type (#1368) 2022-11-15 15:24:55 +04:00
mlanp
cf23399262 android / iOS: german translations for 4.2.1 (#1366)
* android/iOS: fixed german translations for v4.2.1

* Update apps/android/app/src/main/res/values-de/strings.xml

* Update apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff

* Apply suggestions from code review

* strings

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-11-15 11:31:07 +04:00
JRoberts
b5a812769b core: full/merged preferences in User, Contact, GroupInfo types (#1365)
* core: preferences in User, Contact, GroupInfo types

* user and group preferences

* refactor

* linebreak

* remove synonyms

* refactor

* refactor

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-11-15 10:31:44 +04:00
JRoberts
40e1b01baf android: version 4.3-beta.0 (69) 2022-11-14 17:01:29 +04:00
Stanislav Dmitrenko
9c925ab040 android: Animated switch between chat and chatList (#1175)
* android: Animated switch between chat and chatList

* Correct animation

* Testing idea

* Revert "Testing idea"

This reverts commit ecda083883.

* Experiments

* Experiments

* Experiments

* Revert "Experiments"

This reverts commit 4390de1e92.

* Revert "Experiments"

This reverts commit 0b3048aeef.

* Revert "Experiments"

This reverts commit b692803cea.

* Merge

* Gorgeous animation performance

* Undo optimization

* Formatting

* Sharing

* Box

* Continue

* Launch on Main thread only specific call to WebView

* Launch on Main thread only specific call to WebView

* Temporary made withApi() running on Main thread only

* Unneeded code

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-11-14 16:35:38 +04:00
Evgeny Poberezkin
faceeb6fce ios: chat preferences, UI and types (#1360) 2022-11-14 10:12:17 +00:00
JRoberts
07e8c1d76e Fixing ForegroundServiceDidNotStartInTimeException (#1349)
(cherry picked from commit a5d235e559)

Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
2022-11-14 14:02:04 +04:00
Evgeny Poberezkin
b1d8600215 cli: message search in CLI app (#1362)
* cli: message search in CLI app

* type synonym
2022-11-14 08:42:54 +00:00
Evgeny Poberezkin
e14ab0fed0 core: support SMP basic auth / server password (#1358) 2022-11-14 08:04:11 +00:00
Evgeny Poberezkin
cb0c499f57 core: send broadcast to direct contacts only (#1361) 2022-11-14 07:59:59 +00:00
Evgeny Poberezkin
002a081b3b readme: user groups (#1357)
* readme: user groups

* corrections

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-11-12 14:21:33 +00:00
JRoberts
c2b76a75b5 android: fix crash on restoring from backup (#1350)
* Restoring app's data from backup tools will still allow to enter passphrase instead of just crashing

(cherry picked from commit 256243dc8c)

* corrections

Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
2022-11-12 17:19:56 +04:00
Evgeny Poberezkin
2742fc3ca9 site: make onion location http 2022-11-12 12:08:37 +00:00
Evgeny Poberezkin
f3731799bc readme: update roadmap (#1355) 2022-11-12 12:07:11 +00:00
Evgeny Poberezkin
7a78dfd3e3 site: onion location (#1356) 2022-11-12 12:04:23 +00:00
JRoberts
5a2dd7b4bc 4.2.1 2022-11-12 16:01:27 +04:00
JRoberts
1a4d2b6de6 android: version 4.2.1 (68) 2022-11-12 15:59:27 +04:00
JRoberts
e4b46a45d3 android: restore Develop section divider 2022-11-12 15:45:04 +04:00
JRoberts
d61a7fb4d8 ios: version 4.2.1 (91) 2022-11-12 15:07:35 +04:00
JRoberts
bddb37593c mobile: 4.2.1 German translations (#1354) 2022-11-12 14:37:04 +04:00
JRoberts
8b794b2285 core: fix group link tests sporadically failing due to non deterministic events order (#1353) 2022-11-12 14:13:34 +04:00
Evgeny Poberezkin
8d0ec01a9b site: shorten anchor links 2022-11-12 09:49:07 +00:00
JRoberts
d85aa655cb mobile: fix translation 2022-11-12 11:54:28 +04:00
JRoberts
ba0cffb511 android: catch DecodeException (#1351)
* Catched drawable's DecodeException

(cherry picked from commit 90ed170f61)

* missing import

* texts

Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
2022-11-12 11:44:20 +04:00
JRoberts
d029ce9817 ios: version 4.2.1 (90) 2022-11-11 21:48:52 +04:00
JRoberts
678f4f5e87 android: prevent exception on concurrent attempts to add members to group (#1348)
* android: Random crashes fix

(cherry picked from commit 0f7789c411)

* Better way of disabling members adding ato a group

(cherry picked from commit 96ca7f0d85)

* check outside

Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
2022-11-11 21:08:02 +04:00
JRoberts
b780a41272 core: client that joins via group link to probe contacts, not host (#1343) 2022-11-11 18:34:32 +04:00
Evgeny Poberezkin
1caaca83cb blog: update group link 2022-11-11 14:26:51 +00:00
Stanislav Dmitrenko
c8b2bcb064 android: Fix of StackoverflowError (#1322)
* android: Fix of StackoverflowError

* Break

* Comment

* Update apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt

* Revert "Update apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt"

This reverts commit ea8015e01d.

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-11-11 13:33:45 +00:00
Stanislav Dmitrenko
adfe20b54c android: Support info (#1347)
* android: Support info

* Dirrectly to Play Store

* Different icon

* text

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-11-11 10:50:49 +00:00
JRoberts
b9d625da18 ios: support (#1346)
* ios: update settings

* translation

* redundant item

* fix stopped chat buttons

* corrections

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* translations

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-11-11 08:30:10 +00:00
JRoberts
8631cf1471 android: remove local authentication error toasts, rename Retry -> Unlock (#1339) 2022-11-10 16:58:31 +04:00
Evgeny Poberezkin
0b6b8bd327 website: update video 2022-11-10 12:41:46 +00:00
JRoberts
e83ed30a49 ios: update library 2022-11-10 11:31:13 +04:00
Evgeny Poberezkin
29f919b3d6 blog: update post 2022-11-09 20:45:37 +00:00
JRoberts
2636f2ce1c mobile: increase default tcp timeouts (#1336) 2022-11-09 21:24:07 +04:00
JRoberts
f80f56de61 core: allow repeat connection via group link if group was deleted but contact with host is present (#1335) 2022-11-09 21:11:05 +04:00
JRoberts
941660625d website: phrasing, link (#1333) 2022-11-09 15:32:45 +04:00
Evgeny Poberezkin
ad1432e0ee core: make parsing independent of the order (#1332)
* core: make parsing independent of the order

* test

* fix

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-11-09 14:48:24 +04:00
JRoberts
1cfbbd3115 core: update simplexmq (3.4.0) (#1328) 2022-11-09 14:13:39 +04:00
JRoberts
21ffe0ad49 core: repeated invite correctly updates role if changed (#1327) 2022-11-09 14:12:42 +04:00
JRoberts
54ad071655 website: fix typo 2022-11-09 10:25:09 +04:00
Evgeny Poberezkin
992c934fd1 add image 2022-11-08 20:42:00 +00:00
Evgeny Poberezkin
a6c6f1dbff website: update image 2022-11-08 20:29:00 +00:00
Evgeny Poberezkin
a652a14d58 site: fix date in blog index 2022-11-08 19:57:10 +00:00
Evgeny Poberezkin
9e77f05e58 website: fix images, update blog headline 2022-11-08 19:45:56 +00:00
Evgeny Poberezkin
355a3c429c site: fix share image in articles 2022-11-08 18:41:00 +00:00
Evgeny Poberezkin
a1ce3b9c69 blog: update headers 2022-11-08 17:54:42 +00:00
Evgeny Poberezkin
00af82cb19 add github button 2022-11-08 17:42:39 +00:00
Evgeny Poberezkin
68b6d9e966 site: update domain 2022-11-08 17:20:03 +00:00
Evgeny Poberezkin
75165cc70a blog: fix date 2022-11-08 17:01:40 +00:00
Evgeny Poberezkin
e5bf5092b1 readme: fix link 2022-11-08 17:00:07 +00:00
Evgeny Poberezkin
e5a4cca5e0 docs: update readme, blog 2022-11-08 16:59:14 +00:00
Evgeny Poberezkin
41f4f11155 website: fix popups and blog bullets 2022-11-08 16:43:04 +00:00
Evgeny Poberezkin
99bfd446d1 ci: remove website branch from web.yml 2022-11-08 15:36:53 +00:00
Evgeny Poberezkin
dd740e82cf a quick blog fix (#1324)
Co-authored-by: M Sarmad Qadeer <MSarmadQadeer@gmail.com>
2022-11-08 15:23:36 +00:00
Evgeny Poberezkin
5fabeff1fa docs: update readme (#1318)
* docs: update readme

* update roadmap

* typo

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* update doc

* update readme

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-11-08 13:20:23 +00:00
Evgeny Poberezkin
324730f8ae blog: readme, index (#1321)
* blog: readme, index

* add blog preview to the site
2022-11-08 13:19:32 +00:00
Evgeny Poberezkin
ed1faff500 blog: security audit, the new website, v4.2 release (#1315)
* blog: security audit, the new website, v4.2 release

* update text

* correction

* link to report

* more text (#1317)

* more text

* more text

* readme

* more text

* add logo

* image

* more images

* fix image link

* image size

* more images

* update images

* correction

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* correction

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* corrections

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* correction

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* remove note

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-11-08 12:59:28 +00:00
Stanislav Dmitrenko
ce7d0ab8cf android: fix keyboard not closing between views, photo not saved on camera rotation, repeat authentication on opening chat via notification, other minor issues (#1314)
* android: User's problems fix

* Hiding keyboard, state preserving while rotating camera orientation

* Allows to select image from camera multiple times without crashing

* Images sending after orientation change

* Do not ask again about auth when switching between dialogs
2022-11-08 15:58:54 +04:00
Evgeny Poberezkin
75dccf95c4 website: typo 2022-11-08 11:45:21 +00:00
Evgeny Poberezkin
fa5a70cd19 new website (#1307)
* nav in process

* edited web.yml

* navbar issue fixed

* added theme switcher

* added privacy matters section

* added features section

* updated nav padding

* added network section

* improved sidebar dark mode colors

* added footer

* simplex private section added

* added some improvements

* nav issue fixed

* simplex unique section added

* a small fix

* added overlay & data to some sections

* added overlay to simplex unique section
added some improvements to other sections too

* added a small fix

* updated CNAME

* markdown files for why simplex is unique

* Revert "markdown files for why simplex is unique"

This reverts commit ef728218f7.

* added hero section

* added comparison and simplex explained section

* added blogs page

* added articles page

* a small fix in hero section

* added contact page

* updated contact

* created files for overlay content

* a light update

* hero animation

* working on hero

* added responsiveness for mobile

* a quick fix

* added responsiveness to tablet screen

* added responsiveness for desktop screen on hero section

* switch theme of hero

* nav color update

* set comparisons sections

* switch theme of comparisons section

* added responsiveness in simplex explained section

* add logic to simplex explained

* added theme switcher to simplex explained

* manage join simplex section

* update what makes simplex private

* a quick update

* add improvements

* a bit update

* add improvements

* texts for why privacy matters section

* update headers

* texts for "why unique" and "features" sections

* EOLs

* update swipers

* update & add transitions to simplex unique section

* updated overlays

* increase the size of cross on overlays

* add overlays to hero

* website: texts for "private" and "explained" sections (#5)

* website: texts for "private" section

* texts for simplex explained

* blog previews and images (#6)

* blog previews and images

* text for dark mode

* add link style

* add overlay to -> unlike p2p networks

* add picture with blue arrows to simplex explained

* update blog list layout

* remove extra css

* bigger navigation circles & center positions

* make bullets (dots) bigger

* make private scroll thicker

* update hero & footer mobile download btns

* fix dark mode animation files (#7)

* improved contrast for light animation in hero section (#8)

* remove old animation

* Made Hero Pixel Perfect to Desktop

* texts in hero section overlays (#10)

* texts in hero section overlays

* replace hero video

* eol

* update footer links (#11)

* update footer links

* eol

* texts, links, fix layout (#12)

* mailchimp form (#13)

* site meta tags (#14)

* site meta tags

* update blog og:url

* amend texts

* font

* update text

* contact page

* Making things Polished in Hero (#15)

* Made Video Responsive on Tablet

* Fixed the issues

* remove extra files for home & contact page

* update invitation

* refactoring

* fix nav for dark

* quick fix

* update blog list layout

* refactoring

* disable inactive nav circles

* contact page

* fix mobile

* detect platform & show btns according to it

* contact & invitation page setting

* complete contact/invitation page

* create variables for download btns

* fixes for hero - for tablet & mobile

* update hero layout

* update footer layout

* increase the size of logo in navbar

* updated nav & footer logos

* add links to join simplex section

* text for p2p networks section

* text on contact page about link

* add touchstart handler to close popup

* update APK links

* update CNAME

Co-authored-by: M Sarmad Qadeer <MSarmadQadeer@gmail.com>
Co-authored-by: Ojas Shukla <54703305+whizzbbig@users.noreply.github.com>
2022-11-08 11:04:02 +00:00
Evgeny Poberezkin
dd9e94eefd site: move APK url (#1320) 2022-11-08 10:28:40 +00:00
JRoberts
0f65a001c8 ios: fix current conversation not opening after authentication (#1319) 2022-11-08 12:39:41 +04:00
JRoberts
f3e59aa3c3 ios: dismiss re-opened contact connection view upon connection (#1316) 2022-11-07 21:05:59 +04:00
JRoberts
655041c657 ios: fix changed member role resetting in view (#1312) 2022-11-07 20:44:04 +04:00
Stanislav Dmitrenko
4ca118666a android: Connect via group link alert (#1313)
* android: Connect via group link alert

* Rename

* Replace first

* rename CRData into CReqClientData to match haskell type

* Alert

* Shorter

* Strings

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-11-07 20:28:11 +04:00
Stanislav Dmitrenko
365f92e958 android: Alert about deleted contact and errors in chat item (#1289) 2022-11-07 19:42:00 +04:00
Stanislav Dmitrenko
ddecd847e5 android: update available group actions on role change; connect via external link when app was closed; other fixes (#1311)
* android: Fixes for tests

* console item bottom padding

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-11-07 16:46:15 +04:00
JRoberts
18677cec63 mobile: repeat group invitations don't duplicate chat preview (#1310) 2022-11-07 12:08:37 +04:00
Evgeny Poberezkin
4e8dcab020 update readme 2022-11-06 20:48:58 +00:00
Evgeny Poberezkin
eb0f78bd80 blog: reserve permalink for 4.2 release 2022-11-06 15:27:59 +00:00
Evgeny Poberezkin
cf1bd0d467 mobile: version 4.2 (ios: 89, android: 67) 2022-11-06 15:10:15 +00:00
Evgeny Poberezkin
00f712dc59 ios: fix group role (#1308) 2022-11-06 14:20:15 +00:00
mlanp
0a27f8834d android / iOS: german translations for 4.2 (#1306)
* android/iOS: added and fixed german translations for v4.2

* ios localizations

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-11-06 14:09:44 +00:00
Stanislav Dmitrenko
f8678af261 android: Some small fixes (#1305) 2022-11-06 13:30:10 +00:00
JRoberts
b2f663dde3 android: version 4.2-beta.3 (66) 2022-11-05 20:25:29 +04:00
JRoberts
038b936bfe ios: version 4.2 (88) 2022-11-05 20:14:26 +04:00
JRoberts
20def8c7a0 android: shorten user address and group link texts (#1303) 2022-11-05 19:56:28 +04:00
JRoberts
a9b4489f4f ios: more group link translations (#1302) 2022-11-05 19:56:07 +04:00
JRoberts
5ca21dea13 mobile: seamless transition from group link to group; ios: group link alert (#1296) 2022-11-05 17:48:57 +04:00
JRoberts
80ca80f6d8 core: rename CRGroupData constructor to CRDataGroup (#1299) 2022-11-05 15:04:39 +04:00
JRoberts
687a741723 core: fix CReqClientData JSON encoding 2022-11-05 12:53:41 +04:00
Stanislav Dmitrenko
54ab4e979a android: fix pending connection and contact request previews height (#1297) 2022-11-04 22:35:51 +04:00
Evgeny Poberezkin
b6696e901b core: update receiveChunks option to 6 (~90kb) 2022-11-04 17:51:57 +00:00
Evgeny Poberezkin
89de5497ef core: update chat preferences (#1292)
* core: update chat preferences

* refactor, types

* rename types

* rename types

* make voice on by default

* create new user with empty preferences

* fix test
2022-11-04 21:05:21 +04:00
JRoberts
1bf3154488 core: add hostContact to CRUserAcceptedGroupSent (to transition from pending connection to group in ui) (#1295) 2022-11-04 19:46:27 +04:00
JRoberts
c78acfda33 mobile: group link ux on joining side (#1294) 2022-11-04 15:33:29 +04:00
Stanislav Dmitrenko
1432a04927 android: Make ChatList item the same height as it would be with two lines message (#1293)
* android: Make ChatList item the same height as it would be with two lines message

* Different way of doing the same
2022-11-04 13:07:53 +04:00
JRoberts
d432dfba21 core: include pending group link connections into chat previews on joining side (#1291) 2022-11-04 12:00:03 +04:00
JRoberts
5243613045 core: group link connection request uri data; automatically join groups over group links (#1275) 2022-11-03 14:46:36 +04:00
Stanislav Dmitrenko
83599adc80 android: Switching connection (#1287)
* android: Switching connection

* Dividers

* Strings

* Strings2

* Strings3
2022-11-02 16:38:59 +00:00
JRoberts
538992eb95 ios: 4.2-beta.3 translations (#1286) 2022-11-02 20:37:14 +04:00
Stanislav Dmitrenko
658daf56bb android: Inline file transfers (#1288) 2022-11-02 16:26:53 +00:00
Evgeny Poberezkin
7d31862576 ios: refactor group default (#1285) 2022-11-02 10:47:18 +00:00
Evgeny Poberezkin
7a1d0eac9d ios: option to transfer files faster (inline) (#1284) 2022-11-02 10:32:08 +00:00
Stanislav Dmitrenko
d851396113 android: Better scroll of zoomed images in gallery (#1283) 2022-11-02 10:26:19 +00:00
Evgeny Poberezkin
cbdd9b9e37 ios: switch contact and member to another address (#1282)
* ios: switch contact and member to another address

* update simplexmq (JSON encoding)
2022-11-02 09:48:20 +00:00
Evgeny Poberezkin
d5fc0d7dfc core: update event name, ios: types/api/ui (wip) to switch connection to another address, fix contact/member info view, fix setting multiple servers (#1281)
* core: update event name, ios: types/api/ui (wip) to switch connection to another address, fix contact/member info view, fix setting multiple servers

* fix

* update strings

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-11-01 20:30:53 +00:00
Stanislav Dmitrenko
0d0de1da86 core: Test for incognito mode (#1280) 2022-11-01 16:05:05 +00:00
Stanislav Dmitrenko
4e5a5c11dc core: Chat preferences (#1261)
* core: Preferences

* Changes

* fix types

* Follow up

* Review

* Review

* update logic

* update

* update 2

* Tests

* Fixed a bug and tests

* Voice -> voice messages

* refactor

* fix

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-11-01 14:32:49 +00:00
Stanislav Dmitrenko
14038ce370 android: Chat preferences (#1278) 2022-11-01 13:43:58 +00:00
Evgeny Poberezkin
a72f603e13 core: switch connection (#1277)
* core: switch connection

* chat items for SWITCH

* additional events for connection switch

* update simplexmq

* test

* comment test output

* update messages for connection switch

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-11-01 13:26:08 +00:00
JRoberts
85609ef217 android: version 4.2-beta.2 (65) 2022-11-01 10:34:17 +04:00
JRoberts
7631d59695 ios: version 4.2 (87) 2022-11-01 10:09:34 +04:00
Evgeny Poberezkin
38f305bb34 run nix build 2022-10-31 22:02:56 +00:00
JRoberts
290ef9de61 core: update simplexmq (queue rotation) (#1276) 2022-10-31 14:29:38 +04:00
mlanp
f38d3b4d7f android / iOS: german translations for v4.2 (#1266)
* android / iOS: german translations for v4.2

* corrections

* Localizable.strings

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-10-29 14:54:49 +04:00
JRoberts
8f638df7a9 mobile: merge contacts (#1271) 2022-10-28 20:05:04 +04:00
JRoberts
8a121b4442 ci: disable test step for ubuntu-20.04 (#1270) 2022-10-28 15:27:34 +04:00
JRoberts
1bb7a954f5 ios: update library 2022-10-28 15:21:12 +04:00
JRoberts
ddd8bd9061 ci: github build tests timeout (#1267) 2022-10-28 14:46:20 +04:00
JRoberts
179b9e093f core: merge contacts when connecting via group link (#1265) 2022-10-27 23:38:03 +04:00
JRoberts
352a4f3d2a core: clean up incognito profiles (#1262) 2022-10-27 14:25:48 +04:00
JRoberts
e06d4e5c85 ios: translations for auto-accept (#1263) 2022-10-27 10:47:48 +04:00
JRoberts
385ebd2298 core: update deleteGroupMember logic and its usages (no items & expiration) (#1258) 2022-10-26 13:37:17 +04:00
JRoberts
a20f0050b9 4.2.0 2022-10-26 11:36:06 +04:00
JRoberts
e17e9c641d android: version 4.2-beta.0 (64) 2022-10-26 11:33:51 +04:00
JRoberts
2ac6866638 ios: version 4.2 (86) 2022-10-26 11:13:08 +04:00
JRoberts
d7f319aa9e core: mark group contacts as used on send, receive, api (#1253) 2022-10-25 12:50:26 +04:00
Evgeny Poberezkin
1e10b0a49c website: test pages for hero animations (#1255) 2022-10-24 21:00:35 +01:00
JRoberts
e9c321eaad android: mark untranslated texts 2022-10-24 20:48:23 +04:00
Stanislav Dmitrenko
c58d069fbd android: Sender name in chatlist (#1251)
* android: Sender name in chatlist

* Sender is bold

* RTL for sender text
2022-10-24 16:47:03 +01:00
Stanislav Dmitrenko
15c8945fd3 android: Settings to auto-accept contact requests (#1250)
* android: Settings to auto-accept contact requests

* Link will not be recreated when not needed

* Layout

* Layout

* Button rename

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-10-24 16:45:12 +01:00
JRoberts
2109dca1c7 android: allow ContactConnected response to have omitted field (#1252) 2022-10-24 17:40:20 +04:00
JRoberts
7ef7d08c00 mobile: use viaGroupLink to avoid adding missing chats on subscriptions (#1249) 2022-10-24 16:22:00 +04:00
Evgeny Poberezkin
e463e19114 rfc: chat settings (#1096)
* rfc: chat settings

* update doc

* update doc

* update doc

* comment in JSON
2022-10-24 12:45:33 +01:00
JRoberts
deeb2e891a core: add viaGroupLink to Connection (#1248) 2022-10-24 14:28:58 +04:00
Evgeny Poberezkin
5265667c0c ios: settings to auto-accept contact requests (#1246)
* ios: settings to auto-accept contact requests

* use NavigationView

* fix share sheet, layout

* move buttons
2022-10-24 11:25:36 +01:00
JRoberts
15c1f9f9c8 core: group link contact connecting(ed) events to avoid adding previews in ui (#1242)
* core: group link contact connecting(ed) events to avoid adding ui previews

* fix test

* refactor

* ios types

* android types

* type in bot
2022-10-23 21:18:15 +01:00
Evgeny Poberezkin
341199d599 mobile: update type for UserContactLink changes, add addressAutoAccept API (#1245) 2022-10-23 11:16:56 +01:00
Evgeny Poberezkin
dfb6dafd6f core: record constructors for UserContactLink (#1244) 2022-10-22 22:33:59 +01:00
Evgeny Poberezkin
7f544da6cf core: debug chat and agent locks, update simplexmq (#1243)
* core: debug chat and agent locks, update simplexmq

* add connId

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* update lock strings

* fix encoding test

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-10-22 21:22:44 +01:00
JRoberts
d0a0a0461f mobile: change role item texts, uncomment button (#1241)
* mobile: change role item texts, uncomment button

* Update apps/android/app/src/main/res/values-de/strings.xml

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-10-22 18:40:21 +04:00
JRoberts
1dca577ca8 mobile: invited via group link chat item (#1239) 2022-10-22 14:23:23 +04:00
JRoberts
26984b62fe core: delete broken chat item when removing invited member connected via group link; test removing invited member 2022-10-22 14:23:03 +04:00
Stanislav Dmitrenko
1470b8d128 core: auto accept via address and incognito mode specified (#1233)
* core: Auto accept via address and incognito mode specified

* Fix test

* Refactoring

* No forcing

* Apply suggestions from code review

* refactor

* refactor AutoAccept

* Test

* Test

* allow different test output order

* rename

* rename

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-10-21 17:14:12 +01:00
JRoberts
5bcb725ea5 core: exclude contacts accepted via group link from chat previews (#1234)
* RGEInvitedViaGroupLink

* CRSentGroupInvitationViaLink

* via_group_link filtering

* reset

* refactor

* remove brackets
2022-10-21 17:35:07 +04:00
Stanislav Dmitrenko
7f9c4ede02 android: Mark chat unread (#1235)
* android: Mark chat unread

* Fix

* Fix2

* Fix3

* Refactoring

* Icon

* update icon

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-10-21 13:29:08 +01:00
Evgeny Poberezkin
34a74da0b9 ios: mark chat unread (#1237)
* ios: mark unread (wip)

* mark unread works
2022-10-21 12:32:11 +01:00
JRoberts
98cb1c39f2 core: allow to delete contacts that are in groups; group contacts management rfc (#1229) 2022-10-20 19:27:00 +04:00
Evgeny Poberezkin
c4fc8a97b1 core: option to receive file inline up to maximum "offered" size (#1232)
* core: option to receive file inline up to maximum "offered" size

* comment
2022-10-20 14:32:20 +01:00
Stanislav Dmitrenko
9edb54b45c android: Unencrypted all fix (#1231) 2022-10-20 10:16:15 +01:00
Stanislav Dmitrenko
213b586f8f core: Forcing chat unread (#1228)
* core: Forcing chat unread

* Implementation

* Renaming

* Removed unused code

* test

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-10-19 19:38:44 +01:00
Stanislav Dmitrenko
e0582d656e android: Different width for image in a message based on orientation (#1227)
* android: Different width for image in a message based on orientation

* Remember

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-10-18 19:49:27 +01:00
JRoberts
65f8ad4423 Merge branch 'stable' 2022-10-18 18:18:36 +04:00
JRoberts
b115bda8f5 4.1.1 2022-10-18 18:06:47 +04:00
JRoberts
85ab37ae5d android: version 4.1 (63) 2022-10-18 18:05:08 +04:00
JRoberts
dc5f6673a2 ios: version 4.1 (85) 2022-10-18 17:54:37 +04:00
Evgeny Poberezkin
ada4c1d2ec Merge branch 'stable' 2022-10-18 13:11:56 +01:00
Stanislav Dmitrenko
1f8bfbe3f5 android: Fixed conflics (#1225)
* ios: use transparent background for images without text and quote (#1224)

* android: Some small fixes (#1221)

* android: Some small fixes

* Alpha in preview image

* ImageView width limitation for portrait images

* Sharing files with a text

* Do not create new link on orientation change

* Skipping quoted item when applying transparent background

* Commented out sharing a text of image message

* Revert "ImageView width limitation for portrait images"

This reverts commit b1f20b51da.

* White color

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-10-18 13:01:37 +01:00
Stanislav Dmitrenko
cf67c951fc android: Scrolling state will be preserved (#1222)
* android: Scrolling state will be preserved

* Auth changes in terminal view

* Changes
2022-10-18 11:05:31 +01:00
Stanislav Dmitrenko
b78a5931e9 android: Some small fixes (#1221)
* android: Some small fixes

* Alpha in preview image

* ImageView width limitation for portrait images

* Sharing files with a text

* Do not create new link on orientation change

* Skipping quoted item when applying transparent background

* Commented out sharing a text of image message

* Revert "ImageView width limitation for portrait images"

This reverts commit b1f20b51da.

* White color
2022-10-18 10:45:49 +01:00
JRoberts
e57b9f4cea core: don't calculate chat stats to speed up loading individual chats (#1218) 2022-10-18 10:16:28 +01:00
Evgeny Poberezkin
104e1040bf ios: use transparent background for images without text and quote (#1224) 2022-10-18 10:07:45 +01:00
JRoberts
2c347cb7b9 core: CRSubscriptionEnd (#1223)
* core: CRSubscriptionEnd

* refactor

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-10-17 21:35:29 +01:00
Stanislav Dmitrenko
fad60a1d24 android: EditText instead of BasicTextView with ability to get images… (#1213)
* android: EditText instead of BasicTextView with ability to get images from keyboard

* Padding

* Empty, not blank

* Keyboard handling

* Setting text of edited message and bigger padding
2022-10-17 21:25:42 +01:00
Stanislav Dmitrenko
61ed866c24 android: Better way of saving even big files (#1212) 2022-10-16 13:26:10 +01:00
Stanislav Dmitrenko
656c10976b android: Fix autoscroll to bottom when going from group to direct (#1209)
* android: Fix autoscroll to bottom when going from group to direct
- and makes scrolling smoother because of -1 number of recompose

* Rename
2022-10-16 12:41:01 +01:00
Evgeny Poberezkin
63f40f030a android: change indigo color to improve dark mode readability (#1219) 2022-10-15 21:55:37 +01:00
Evgeny Poberezkin
dec8e136f9 android: increase paddings in link views (#1217)
* android: increase paddings in link views

* increase padding on scan screen

* Update apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddContactView.kt

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* Update apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddContactView.kt

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-10-15 18:49:10 +01:00
JRoberts
a525b4e5db ios: group links (#1214)
* libraries

* api

* ui

* via nav link

* translations

* align android translations
2022-10-15 18:09:25 +04:00
JRoberts
2f8b4a3e93 Merge branch 'stable' 2022-10-15 16:15:07 +04:00
JRoberts
39818b6fbb ios: version 4.1 (84) 2022-10-15 16:14:32 +04:00
JRoberts
3523c7ebd7 ios: fix ContactConnectionInfo share sheet (#1215) 2022-10-15 15:47:04 +04:00
JRoberts
1b3984f52f core: create group invitation connection asynchronously on group link auto-accept (#1211) 2022-10-15 14:48:07 +04:00
Stanislav Dmitrenko
560807b4b7 android: Group links (#1210)
* android: Group links

* Alerts with network errors

* Alert for get link too

* Layout changes

* Divider

* Revert "Alert for get link too"

This reverts commit b0eaf50cdc.

* Update apps/android/app/src/main/res/values-de/strings.xml

* Update apps/android/app/src/main/res/values/strings.xml

* Update apps/android/app/src/main/res/values/strings.xml

* Update apps/android/app/src/main/res/values-ru/strings.xml

* Update apps/android/app/src/main/res/values-ru/strings.xml

* Update apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt

* apiDeleteGroupLink call

* names

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-10-14 19:30:09 +04:00
Evgeny Poberezkin
fb03a119ea core: support inline file transfers (#1187)
* core: support inline file transfers

* parameterize ChatMessage

* send files inline when accepted

* accept inline file transfers (almost works)

* db error SERcvFileInvalid

* inline file transfer works (TODO fix test)

* inline file transfer tests, change encodings

* fixture

* combine messages into x.file.acpt.inv, refactor

* inline file mode

* decide whether to receive file inline on the recipient side, not only via file invitation

* test inline files "sent" mode

* check that file was offered inline

* update schema

* enable encryption tests

* test name

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* fix the list of rcv files to subscribe too

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-10-14 13:06:33 +01:00
JRoberts
f7da034cf1 core: use acceptContactAsync on auto-accept, reuse incognito profile for contacts accepted via group link (#1208)
* update simplexmq (acceptContactAsync)

* acceptContactRequestAsync, single profile

* refactor

* refactor 2

* refactor

* Update src/Simplex/Chat/Store.hs

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* refactor

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-10-14 14:57:01 +04:00
Stanislav Dmitrenko
4a259e44b1 android: Link preview with image URLs and more options for finding image on a site (#1207) 2022-10-13 19:11:59 +01:00
Stanislav Dmitrenko
799d9443bd android: Multiple images sharing (#1206) 2022-10-13 18:39:24 +01:00
JRoberts
3bf8361911 core: group links (#1194) 2022-10-13 17:12:22 +04:00
JRoberts
9670da4646 android: version 4.1 (62) 2022-10-13 14:14:01 +04:00
Stanislav Dmitrenko
1e22028189 android: Fix connecting via link from Firefox (#1205)
* android: Fix connecting via link from Firefox

* Replace slash
2022-10-13 10:49:57 +01:00
Stanislav Dmitrenko
74421a5f52 android: Hide role changing (#1204) 2022-10-12 22:52:56 +01:00
JRoberts
8c3c639d7f android: missing translations (#1201) 2022-10-12 20:29:06 +04:00
Evgeny Poberezkin
91460932fe website: fix contact page 2022-10-12 17:16:10 +01:00
Evgeny Poberezkin
f70a203f0e website: fix connection page for firefox, fix contact page (#1202) 2022-10-12 17:12:01 +01:00
JRoberts
3113aab8a8 4.1.0 2022-10-12 19:04:37 +04:00
JRoberts
d0b5f16be6 android: version 4.1 (61) 2022-10-12 18:58:50 +04:00
JRoberts
41a4e13ff6 ios: version 4.1 (83) 2022-10-12 18:51:45 +04:00
JRoberts
3ac7d5977b android: german translations for v4.1 (#1200) 2022-10-12 18:47:33 +04:00
mlanp
0f0cb234bd ios: german translations for v4.1 (#1199)
* modified:   apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff

* Localizable.strings

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-10-12 18:07:33 +04:00
Stanislav Dmitrenko
c14c289ba7 android: Image gallery (#1198)
* android: Image gallery

* Do not scroll to active item

* Infinity pager

* Fixed small issue with the first page

* Limited image height

* Revert "Limited image height"

This reverts commit d5733da6a3.

* Scroll at top position after click on quote

* Better scrolling
2022-10-12 14:59:59 +01:00
Stanislav Dmitrenko
c20c7085b0 android: Scroll to quoted message (#1195)
* android: Scroll to quoted message

* Click only on quote makes the list scrolling

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-10-12 13:39:17 +01:00
Stanislav Dmitrenko
05122fc476 android: Update chats when enter the app from background (#1196)
* android: Update chats when enter the app from background

* Nullify current chat id if the chat was deleted in background
2022-10-11 11:35:47 +01:00
Stanislav Dmitrenko
ee5997cdf7 android: Multiple images can be sent at the same time in different messages (#1193)
* android: Multiple images can be sent at the same time in different messages

* Padding

* Removing an ability to delete selected image, filtered out videos

* Optimization
2022-10-11 09:15:47 +01:00
JRoberts
563687f9e4 ios: version 4.1 (82) 2022-10-10 17:46:14 +04:00
JRoberts
3d3503498a ios: fix muted notification (#1192) 2022-10-10 17:35:28 +04:00
Stanislav Dmitrenko
4b905a80e6 android: New chat sheet animation (#1174)
* Color blending on buttons

* android: New chat sheet animation

* Faster animation

* Another try

* Experiment

* Experiment

* Animation of the year

* Faster animation

* Faster animation

* Faster animation

* Useless

* Top bar

* combine animations

* Experiment

* Fastest animation ever

* Animation per row

* Removed logs

* Combined animations

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-10-10 14:09:07 +01:00
JRoberts
fee9b3ae84 ios: version 4.1 (81) 2022-10-10 13:54:19 +04:00
Evgeny Poberezkin
4c8bc19182 ios: send multiple images (#1188)
* ios: send multiple images

* multi-select works (TODO race conditions)

* send multiple images, progress indicator in compose view

* scroll between fullscreen images, scroll to quoted item

* add swipe animation

* fix model state when sending the image

* fix sending multiple images

* use MainActor

* improve scrolling

* faster scroll

* improve scroll animation

* fix model updates
2022-10-10 13:40:30 +04:00
JRoberts
f9be6e6434 update simplexmq (lock on async commands retries) (#1191) 2022-10-10 13:40:06 +04:00
Stanislav Dmitrenko
cf001054c2 core: Fix member role that wasn't applied to a member object (#1190) 2022-10-08 19:49:42 +01:00
Stanislav Dmitrenko
54705c07bd android: Fixed bug with modal views (#1189) 2022-10-07 21:40:37 +01:00
Stanislav Dmitrenko
ca423024c5 android: Change member role (#1186)
* android: Change member role

* Don't do anything when role is unchanged
2022-10-07 21:05:48 +01:00
JRoberts
af17daafaa ios: version 4.1 (80) 2022-10-07 19:39:02 +04:00
JRoberts
f0b551cffd ios: comment member roles ui (#1185) 2022-10-07 19:07:08 +04:00
JRoberts
56727a8672 ios: version 4.1 (79) 2022-10-07 18:08:58 +04:00
JRoberts
5fd522c79c mobile: change ttl setting text (#1184) 2022-10-07 17:20:09 +04:00
JRoberts
b66eb5b67f mobile: disable setting chat item TTL if chat db changed (#1183) 2022-10-07 15:01:17 +04:00
JRoberts
88645cb003 core: check chat store hasn't changed in APISetChatItemTTL (#1182) 2022-10-07 13:53:05 +04:00
Evgeny Poberezkin
fff0659b1e ios: fix picker heights (#1181) 2022-10-07 13:31:31 +04:00
Stanislav Dmitrenko
04719ff8df android: Automatic message deletion (#1171)
* android: Automatic message deletion

* Disable changing TTL when this operation is already happening

* corrections

* update translations

* afterSetCiTTL

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-10-07 13:29:13 +04:00
JRoberts
83c1340830 ios: update chats after setting chat item TTL (#1179) 2022-10-07 10:55:54 +04:00
JRoberts
6d06dda645 core: allow to call APIGetChats with stopped chat (#1180) 2022-10-07 10:55:47 +04:00
Evgeny Poberezkin
868f0abaaf ios: connection information/alias sheet (#1178)
* ios: connection information/alias sheet

* add swipe button

* add localizations

* fix padding

* fix intermittent bug with multiple edits
2022-10-06 15:02:58 +01:00
JRoberts
3649321b67 core: catch errors during chat item expiration (#1177) 2022-10-06 14:00:02 +04:00
JRoberts
135bdf3842 core: optimize bulk chat item deletion 2 (#1172) 2022-10-05 19:54:28 +04:00
Evgeny Poberezkin
22edd92079 android: change scrim color for new chat button in dark mode 2022-10-05 08:50:24 +01:00
Stanislav Dmitrenko
059f1e1e79 android: New chat dialog (#1173)
* android: New chat dialog

* Click indication was disabled

* Color experiments

* Padding

* Padding

* Second variant

* Third variant

* Another style

* Another part

* update buttons

* Another try

* Elevation

* Empty view when no chats available

* update buttons

* do not hide "you have no chats" with new chat dialog

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-10-05 08:37:04 +01:00
Stanislav Dmitrenko
b263d7e547 android: Connection info (#1169)
* android: Connection info

* UI changes

* Icon in text field

* Alias text field

* Revert "Alias text field"

This reverts commit 2ac694db4d.

* Padding

* Layout changes

* Bigger delay

* UI changes

* UI changes
2022-10-04 17:25:53 +01:00
Evgeny Poberezkin
7a54351f15 core: update simplexmq (connection-level locks) (#1170)
* core: update simplexmq (connection-level locks)

* update simplexmq

* update simplexmq

* update simplexmq

* only run file tests

* update simplexmq

* enable all tests

* update simplexmq
2022-10-04 17:19:00 +01:00
Evgeny Poberezkin
f9c691cab1 ios: change member role (#1164)
* ios: change member role

* chat item types, error alerts

* update alert

* translations

* update messages

* translation

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* translation

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* translation

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* update translations

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-10-04 09:53:43 +01:00
JRoberts
cd6cad9a96 core: optimize bulk chat item deletion (#1168)
* core: optimize bulk chat item deletion

* test file deletion

* refactor

* refactor
2022-10-03 22:33:36 +01:00
Stanislav Dmitrenko
e8ad216b26 Dividers and padding (#1166) 2022-10-03 15:16:04 +01:00
JRoberts
3808b7415b core: allow to set chat item ttl when chat is stopped (#1165) 2022-10-03 17:44:56 +04:00
JRoberts
e1454b1445 ios: refactor delete after x seconds option 2022-10-03 16:47:45 +04:00
JRoberts
6e9e6057af ios: automatic message deletion (#1160) 2022-10-03 16:42:43 +04:00
JRoberts
575706c7c7 core: platform independent json encoding for MsgErrorType (#1163) 2022-10-03 12:55:59 +04:00
Evgeny Poberezkin
58f6b168e6 core: protocol/commands to change member role (#1159)
* core: protocol/commands to change member role

* change member roles

* add test

* correction

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* add member profile to group member role events

* resend invitation when invited member role changes

* test role change with invitation, fix

* add delays to tests

* add test delay

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-10-03 09:00:47 +01:00
Stanislav Dmitrenko
841afa1e80 android: UI overhaul start (#1146)
* android: UI overhaul start

* Moved title upper in a view and modified UI of some elements

* AppBar refactoring

* Color for settings modal in a light theme

* Returned big title

* Animation between screens

* Title placement

* Animation between screens

* Properly removing a value

* Properly removing a value

* make entry/exit animation the same

* fix first screen

* update first screen logo

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-10-02 16:05:50 +01:00
Evgeny Poberezkin
9c5acd609c core: author role, allow member to add new members on the receiving end (#1149)
* core: author role, allow member to add new members on the receiving end

* remove unused name

* remove author role from parser
2022-10-01 20:30:47 +01:00
Evgeny Poberezkin
23212def51 update simplexmq v3.3.0 2022-10-01 15:19:41 +01:00
Evgeny Poberezkin
06e46b0bea mobile: disable notifications on group events, make member role default (#1158)
* ios: disable notifications on group events, make member role default

* same for android

* change mute criteria

* update
2022-10-01 14:46:48 +01:00
JRoberts
a3bd51a5fa core: speed up tests (#1157) 2022-10-01 14:54:02 +04:00
Evgeny Poberezkin
5c49e8f7ea core: add localAlias to Contact when it is created 2022-10-01 11:38:54 +01:00
JRoberts
ef28215284 core: fully delete group chat items instead of overwriting content (#1154) 2022-10-01 14:31:21 +04:00
JRoberts
7f70fe4d64 core: fix /image command error messages (closes #1153) (#1156) 2022-10-01 14:29:02 +04:00
Evgeny Poberezkin
05385ce997 ios: set alias on connection link, see link again, remove QR code on connection (#1155)
* ios: set alias on connection link, see link again, remove QR code on connection

* update UX for connection alias

* change layout

* layout

* return pencil

* incognito

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* color

* style

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* fix

* pencil color

* update

* remove UB sanitizer

* exit edit mode

* fix flicker

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-10-01 10:57:18 +01:00
Evgeny Poberezkin
f0f7226fa5 ios: update library 2022-09-30 13:48:04 +01:00
JRoberts
428d3cdba5 core: delete connections asynchronously (#1151) 2022-09-30 16:18:43 +04:00
Stanislav Dmitrenko
dd5e99ea42 android: Allowed to route audio to earpiece on Android S+ (#1148)
* android: Allowed to route audio to earpiece on Android S+

* Clear communication device after call ends
2022-09-30 09:00:44 +01:00
Evgeny Poberezkin
628f119151 core: lints (#1144) 2022-09-29 16:26:43 +01:00
Stanislav Dmitrenko
0e411b0eac android: Prevent shutting down a microphone when screen is off (#1145) 2022-09-28 20:46:57 +01:00
Stanislav Dmitrenko
7fd4b7edae android: Sharing text and files from other apps and from Simplex itself to Simplex (#1140)
* android: Sharing text and files from other apps and from Simplex itself to Simplex

* Different look and feel of share list

* Strings
2022-09-28 18:03:31 +01:00
JRoberts
9cb2542079 core: scheduled deletion (#1075)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-09-28 20:47:06 +04:00
Evgeny Poberezkin
07d2c9ff49 update readme 2022-09-28 11:25:35 +01:00
Evgeny Poberezkin
6730a687e2 readme: release announcement (#1142)
* readme: release announcement

* roadmap

* correction

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* update

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-09-28 09:23:28 +01:00
Stanislav Dmitrenko
86c782d08b android: Playing call sound in different stream type (#1138)
* android: Playing call sound in different stream type

* Changes
2022-09-27 22:09:14 +01:00
Evgeny Poberezkin
fe6cd91a61 core: connection alias, store invitation link with connection (#1137)
* core: connection alias, store invitation link with connection

* update contact with connection alias, test

* use type alias

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-09-27 20:45:46 +01:00
JRoberts
bf46e0dc57 ios: don't show clear swipe action for empty chats (#1139) 2022-09-27 20:24:25 +04:00
Evgeny Poberezkin
937492c204 blog: v4 (#1078) 2022-09-27 14:46:37 +04:00
JRoberts
c0878769be 4.0.1 2022-09-27 12:36:29 +04:00
JRoberts
e2c332c230 android: version 4.0.1 (60) 2022-09-27 10:02:41 +04:00
mlanp
629c5d7434 ios: update german translation (#1136)
* iOS German translations corrections

* import translations

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-09-26 22:24:16 +01:00
Evgeny Poberezkin
090e54c8d8 ios: fix translations on user profile page (#1134) 2022-09-26 20:32:24 +01:00
Stanislav Dmitrenko
1a2efc3b4f android: RTL support when device language is NOT RTL (#1133)
* android: RTL support when device language is NOT RTL

* Follow up

* Refactoring

* Removed unused changes

* Comment
2022-09-26 20:31:38 +01:00
Evgeny Poberezkin
4e57f7c488 ci: fix windows build (#1132)
* ci: fix windows build

* pass bin_path via temp file
2022-09-26 19:33:54 +01:00
Evgeny Poberezkin
31d8f73eac core: disable connection handshake notifications for groups with disabled notifications (#1131) 2022-09-26 18:09:45 +01:00
Stanislav Dmitrenko
409982ad71 android: Two more notifications when contact connected or connection request received (#1129)
* Two more notifications when contact connected or connection request received

* Large image in notification

* Do not show contact's icon when it't not permitted

* Sound and vibrate for notification

* No large icon in other cases

* Small change
2022-09-26 17:28:51 +01:00
Evgeny Poberezkin
6c7e81777e ci: windows build (#1128)
* ci: windows build

* enable windows build

* fix intermittently failing test
2022-09-26 17:02:06 +01:00
Stanislav Dmitrenko
4934045f4e Closing qr scanner/connect via link screens after successfull request (#1130) 2022-09-26 17:01:47 +01:00
Evgeny Poberezkin
aeebb89dc2 ios: version 4.0 (78) 2022-09-26 00:37:08 +01:00
Evgeny Poberezkin
1246ad2437 ios: German localizations (#1111)
* ios: German localizations

* add some translations

* more translations

* translations

* copied all translations

* update android translations

* update ios

* iOS German translations completed

* update de.xliff

* update translations

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
Co-authored-by: mlanp <github@lang.xyz>
2022-09-26 00:32:57 +01:00
Evgeny Poberezkin
f46b813235 android: change german strings for clear/delete chat 2022-09-25 22:38:21 +01:00
Evgeny Poberezkin
83b77748b6 ios: fix: storing passphrase makes in available in NSE, do not allow empty current passphrase, reset files count after deletion (#1125) 2022-09-25 20:53:32 +01:00
Evgeny Poberezkin
4dde46a646 ios: verion 4.0 (77) 2022-09-25 14:31:24 +01:00
Evgeny Poberezkin
b7dd787043 ios: remove key from log (#1123) 2022-09-25 13:17:04 +01:00
Evgeny Poberezkin
bce8af16de website: add call page back (#1121) 2022-09-25 11:16:28 +01:00
Evgeny Poberezkin
96b8f0e979 package: update @simplex-chat/webrtc version 0.1.1 2022-09-25 11:01:58 +01:00
Evgeny Poberezkin
36f635f6f0 ios: prevent messages from being sent twice (#1120) 2022-09-24 22:20:56 +01:00
sh
d15fec4fab docs/WEBRTC.md: new docs (#1118)
* docs/WEBRTC.md: new docs

* app configuration

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-09-24 21:15:49 +01:00
Evgeny Poberezkin
1b435d89f3 ios: fix chatlist search bar appearing in chat view (#1119) 2022-09-24 21:02:01 +01:00
Evgeny Poberezkin
bbaa45a0e1 ios: fix layout for messages in right-to-left languages, #1032 (#1117)
* ios: fix layout for messages in right-to-left languages, #1032

* text alignment in compose message view

* more right-to-left alignment
2022-09-24 19:26:55 +01:00
JRoberts
5578183777 android: version 4.0 (59) 2022-09-24 20:02:02 +04:00
JRoberts
520800ded0 android: disable new chat buttons and notifications settings when chat is stopped (#1116) 2022-09-24 19:59:35 +04:00
JRoberts
47a6a81854 android: version 4.0 (58) 2022-09-24 18:57:47 +04:00
JRoberts
2a4b7b83a4 android: fix 'choose attachment' layout for German language (#1115) 2022-09-24 18:39:02 +04:00
Stanislav Dmitrenko
792c442aa3 Fixes #1105 (#1108) 2022-09-24 15:29:42 +01:00
JRoberts
54b39e8d00 android: ui fixes (#1114)
* new chat buttons alignment

* translations
2022-09-24 15:24:54 +01:00
Stanislav Dmitrenko
39f82e9e1a android: single call to initialize chat, fix (#1109)
* JNI experiments

* Next try

* Next try

* Final JNI code for the new library method

* remove unused functions

* android: refactor, fix; ios: change entropy bounds

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-09-24 13:45:10 +01:00
Evgeny Poberezkin
ab7fed1628 ios: use single call to initialize chat controller (#1110)
* ios: use single call to initialize chat controller

* update logs

* comments
2022-09-24 09:28:22 +01:00
Evgeny Poberezkin
c94691c89e android: fix string 2022-09-23 23:01:02 +01:00
Evgeny Poberezkin
3c95b76e5a android: add German translations (#1079)
* android: add file for German translations

* translations

* complete translation

* translations (up to line 630)

* update german strings file

* remove unused strings

* translations for Android completed

Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: M Sarmad Qadeer <MSarmadQadeer@gmail.com>
2022-09-23 22:57:00 +01:00
Evgeny Poberezkin
a977a0dd17 core: single function to initialize the chat controller only if encryption key is correct (#1107) 2022-09-23 22:22:56 +04:00
JRoberts
e1a7b02e59 android: version 4.0-beta.3 (57) 2022-09-23 18:40:28 +04:00
JRoberts
3537d3871c android: version 4.0-beta.3 (56) 2022-09-23 18:01:26 +04:00
JRoberts
093e7b4c78 ios: version 4.0 (75) 2022-09-23 16:52:47 +04:00
Evgeny Poberezkin
6e9cf2ba91 ios: fix background refresh crash, memoize migrateChatDatabase (#1103)
* ios: fix background refresh crash, memoize migrateChatDatabase

* store returned value
2022-09-23 12:51:40 +01:00
Evgeny Poberezkin
7c06961ff8 android: update/remove strings (#1104) 2022-09-23 12:51:27 +01:00
Stanislav Dmitrenko
2b53406ccf android: floating button for creating a new chat (#1102)
* Floating button for creating a new chat
- also fixed two-line preview in a chat list

* Button color in light theme

* update button color, remove elevation

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-09-23 00:03:06 +01:00
Stanislav Dmitrenko
06c79cc2bc android: Icons (#1100)
* Icons

* Icons in app's info

* Icon foreground

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-09-22 23:28:17 +01:00
Evgeny Poberezkin
5e870ec30f ios: fix chat list layout (#1094)
* ios: fix chat list layout

* set chat row height depending on dynamic type size

* update chat list layout sizes for ios15/16
2022-09-22 22:27:20 +01:00
Stanislav Dmitrenko
6a6d246dc5 android: UI fixes (#1099)
* UI fixes

* eol
2022-09-22 21:50:12 +01:00
Stanislav Dmitrenko
07191bfb61 android: UX for making connections (#1098)
* android: UX for making connections

* Tabs background color was specified

* Different icon and description

* update texts, fix layout on ios small screens

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-09-22 21:27:46 +01:00
JRoberts
b62895ca76 android: version 4.0-beta.2 (55) 2022-09-22 22:08:15 +04:00
Evgeny Poberezkin
bd2a748169 ios: allow export of unencrypted database (#1097) 2022-09-22 13:10:25 +01:00
Evgeny Poberezkin
06c7b9a995 update simplemq (sql function) 2022-09-22 12:29:50 +01:00
JRoberts
42b6bf96ff ios: version 4.0 (74) 2022-09-22 14:20:41 +04:00
JRoberts
aa6fb2fc12 core: ignore file cancel errors when deleting chat/chat item (#1093) 2022-09-22 13:06:50 +04:00
Evgeny Poberezkin
8bfeab7071 ios: update simplex library 2022-09-22 10:04:26 +01:00
Stanislav Dmitrenko
97662b040e android: Replace "make connection" screen with two buttons (#1091) 2022-09-22 09:35:44 +01:00
Stanislav Dmitrenko
a9ba16b07a android: Allow configuring WebRTC ICE servers (#1090)
* Allow configuring WebRTC ICE servers

* refactor

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-09-22 09:16:25 +01:00
JRoberts
36d93f5b0e android: alerts on connection errors (#1087) 2022-09-22 11:55:11 +04:00
Evgeny Poberezkin
ba25850b73 ios: update icons and logos (#1089) 2022-09-22 08:37:45 +01:00
Evgeny Poberezkin
9b75553ddc ios: UX for making connections (#1088)
* ios: UX for making connections

* combine paste and scan into one view

* translations
2022-09-22 08:36:39 +01:00
Evgeny Poberezkin
b390630f4b ios: fix occasionally broken QR code when creating invitation link (#1086) 2022-09-21 16:28:01 +01:00
Evgeny Poberezkin
df329d305b ios: replace "make connection" screen with two buttons (#1084)
* ios: replace "make connection" screen with two buttons

* add "no chats", update translations
2022-09-21 15:11:52 +01:00
JRoberts
59b4ce2474 mobile: decrease mark read delay (#1085) 2022-09-21 17:39:29 +04:00
JRoberts
9442656bbe ios: alerts on connection errors (#1080)
* ios: alerts on connection errors

* fixes

* refactor

* refactor

* refactor

* translations

* delete contact with groups error

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-09-21 17:18:48 +04:00
Evgeny Poberezkin
0494cce77d ios: remove destructive role from swipe actions to avoid pre-emptive row deletion before confirmations (#1083) 2022-09-21 11:52:28 +01:00
Evgeny Poberezkin
909a8aaf9c webrtc: change server ports to 443, ios: allow configuring WebRTC ICE servers (#1077)
* webrtc: change server ports to 443

* pass stun/turn servers from user default

* ios: configure WebRTC ICE servers

* translations

* update servers

* translations
2022-09-21 10:19:13 +01:00
JRoberts
edf217111f 4.0.0 2022-09-20 19:11:52 +04:00
Evgeny Poberezkin
f0e18c62fe code: update simplexmq (async secure) 2022-09-20 15:42:36 +01:00
Evgeny Poberezkin
a615dbec91 support direct file invitations without contact requests (#1076) 2022-09-20 14:46:30 +01:00
JRoberts
a8ef92a933 android: version 4.0-beta.1 (54) 2022-09-20 14:38:05 +04:00
JRoberts
c1cca9385a ios: version 4.0 (73) 2022-09-20 14:13:04 +04:00
Stanislav Dmitrenko
267207cc15 android: Ability to delete app files and media (#1072)
* Ability to delete app files and media

* section title, corrections

* remove icon

* change translation

* revert disabled unless stopped

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-09-20 12:36:11 +04:00
JRoberts
012115b330 ios: disable files deletion unless chat is stopped (#1074) 2022-09-20 12:35:25 +04:00
JRoberts
c4aa988fb3 ios: version 4.0 (72) 2022-09-19 19:49:39 +04:00
JRoberts
c236a759d5 ios: clear storage translations (#1071) 2022-09-19 19:35:59 +04:00
JRoberts
67323a41eb ios: clear storage (#1069)
* wip

* display current storage state

* alert

* fix

* simplify

* remove unused function

* fix log

* replace prints with logger

* Apply suggestions from code review

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* low res will remain text

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-09-19 19:05:29 +04:00
JRoberts
962166c2ef mobile: prohibit /sql commands if unauthorized (#1068)
* ios: prohibit /sql commands if unauthorized

* refactor

* move check to send command

* revert diff

* refactor

* android

* Apply suggestions from code review

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* fix

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-09-19 13:02:48 +04:00
Evgeny Poberezkin
b0ed64533f update simplexmq 2022-09-18 13:54:33 +01:00
Evgeny Poberezkin
bc7fe4ec75 ios: update version to v4.0 2022-09-18 10:03:15 +01:00
Evgeny Poberezkin
923f7cbfd8 mobile: v4.0-beta.0 (ios: 71, android: 53) 2022-09-17 23:09:28 +01:00
Evgeny Poberezkin
5d55657186 core: support sql queries (#1066)
* core: support sql queries

* remove gradle change
2022-09-17 16:06:27 +01:00
JRoberts
f2067a047f ios: missing translations (#1064) 2022-09-17 18:57:57 +04:00
JRoberts
7dfd6e9a99 android: copy backup instead of moving (#1067) 2022-09-17 18:36:57 +04:00
JRoberts
2eca3e789c ios: restore db (#1063)
* wip

* wip

* wip

* refactor

* clean up

* simplify

* simplify

* refactor

* rename

* rename consts

* refactor

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-09-17 16:41:20 +04:00
Evgeny Poberezkin
3351503744 update simplexmq (fix stopping agent) 2022-09-17 00:30:49 +01:00
JRoberts
107fa37aa6 core: don't check agent msg matches expected response of async cmd if it's ERR (#1062) 2022-09-16 19:41:53 +04:00
JRoberts
e8c14896aa core: process ERR response to async command (#1061) 2022-09-16 19:30:02 +04:00
JRoberts
d5b9f4014e ci: cabal build (#1057) 2022-09-16 17:52:23 +04:00
Evgeny Poberezkin
29b333cf0c update simplexmq 2022-09-16 13:43:37 +01:00
Stanislav Dmitrenko
7e340af48e System theme fix (#1059) 2022-09-15 21:39:19 +01:00
Stanislav Dmitrenko
568c9201d6 android: Restore database from a backup when encryption fails for some reason (#1058)
* Restore database from a backup when encryption fails for some reason

* Removed unused code

* Safer way of doing some things

* Ordering

* Increased possible diff in time to 10 seconds

* update strings

* Alert confirmation

* update strings

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-09-15 20:59:54 +01:00
Stanislav Dmitrenko
98ccab394a android: require to disable battery optimizations for periodic notifications mode (#1053)
* Changed requirements for ignoring battery for Periodic notifications mode

* Add delay before running ON_RESUME events because some data is not ready yet on that stage

* Better idea of when to show background notice

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-09-15 16:34:20 +01:00
Stanislav Dmitrenko
392b1028b3 android: Gif file size limit and download button (#1050)
* Gif file size limit and download button

* update icon

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-09-15 07:39:38 +01:00
Evgeny Poberezkin
d32e0d330f Merge pull request #985 from simplex-chat/sqlcipher
core: switch from SQLite to SQLCipher
2022-09-14 22:46:14 +01:00
Evgeny Poberezkin
d1571798f4 update simplexmq 2022-09-14 21:50:44 +01:00
JRoberts
ff35a3fee5 core: sqlcipher stack build (#991)
* wip

* uncomment

* comment

* ci split build step

* check openssl

* openssl version

* add stack params

* update mac openssl parameters

* ls openssl

* clean stack.yaml

* clean up build.yml

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-09-14 21:28:21 +01:00
Stanislav Dmitrenko
29b27fa602 android: progress indicator in database related views (#1052)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-09-14 21:27:17 +01:00
Evgeny Poberezkin
08e0d7339f Merge branch 'master' into sqlcipher 2022-09-14 18:46:03 +01:00
JRoberts
6a05a56e3e mobile: fix group delete alert text for local deletion (#1051) 2022-09-14 21:45:59 +04:00
JRoberts
f1e34531c2 mobile: fix db encryption translations (#1049) 2022-09-14 20:16:13 +04:00
JRoberts
c07d4a5e4e core: use async agent commands when establishing connections w/t user action (#977)
* wip

* wip

* wip

* schema

* schema

* wip

* wip

* rework

* revert

* update simplexmq

* async commands

* corr id wip

* wip

* update simplexmq

* corr id

* wip

* rename variable

* wip

* refactor

* ACK continuation

* wip

* fix queries

* fix queries

* clean up schema

* update simplexmq, do not lock on stopping chat

* clean up

* refactor

* refactor

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-09-14 19:45:21 +04:00
Evgeny Poberezkin
76a7dfeabb ios: localize database encryption (#1048)
* ios: localize database encryption

* fix incorrect language in NSE localizations

* corrections

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* translations

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-09-14 14:04:41 +01:00
Evgeny Poberezkin
63a98fa9d3 Merge pull request #1028 from simplex-chat/sqlcipher-android
android: add SQLCipher
2022-09-14 12:08:06 +01:00
Stanislav Dmitrenko
78f854e2c5 android: database encryption support with a passphrase (#1021)
* Ability to encrypt credentials and to store them securelly

* Don't regenerate key if it exists

* Made code shorter

* Refactoring

* Initial support of encryped database

* Changes in UI and notifications about database problems

* Small changes to how we use chatController instance

* Show unlock view in console automatically

* Fixed wrong place of saving a key

* Fixed a crash

* update icons

* Changing controller correctly

* Enable migration

* fix JNI

* Fixed startup

* Show database error view when password is wrong while enabling a chat

* Chat controller re-init in one more place
- also added one more alert

* Scrollable columns and restarted service and worker

* translations

* database passphrase

* update translations

* translations

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* update translations

* update translations

* update icon colors, show empty passphrase as not stored

* update translation

* update translations

* shared section footer, bigger font, layout, change entropy bounds

* correction

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* update translations

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-09-14 12:06:12 +01:00
Evgeny Poberezkin
17f806e7a2 Merge branch 'sqlcipher' into sqlcipher-android 2022-09-13 22:34:45 +01:00
Evgeny Poberezkin
7c9f351849 Merge branch 'master' into sqlcipher 2022-09-13 22:32:43 +01:00
Evgeny Poberezkin
41e5bea8d6 Merge branch 'sqlcipher' into sqlcipher-android 2022-09-13 19:25:28 +01:00
Evgeny Poberezkin
933f2ce614 Merge branch 'sqlcipher' of github.com:simplex-chat/simplex-chat into sqlcipher 2022-09-13 19:20:47 +01:00
JRoberts
5089dfdada mobile: fix quote sender name interaction with incognito membership and alias (#1041) 2022-09-13 20:33:18 +04:00
Evgeny Poberezkin
7f9e68c58d remove duplicate patch 2022-09-13 12:05:18 +01:00
Evgeny Poberezkin
c7dcdb1186 Merge branch 'master' into sqlcipher 2022-09-13 08:39:24 +01:00
Evgeny Poberezkin
69138a24de nix: patch out android logging from sqlcipher 2022-09-13 08:38:31 +01:00
Stanislav Dmitrenko
bf0fdf6d42 android: animated images support (#1038)
* Animated images support

* Provide correct mime type when saving an image

* Higher limit size for images auto download
2022-09-12 22:47:44 +01:00
Evgeny Poberezkin
9ff7dc8977 Typescript chat client SDK (#1036)
* add typescript chat commands

* add chat responses

* add client API

* fix chat bot example, readme

* update readme

* update main readme

* readme

* corrections

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* correction

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-09-12 18:33:34 +01:00
Evgeny Poberezkin
7aedd3d9e9 inline files rfc (#1024) 2022-09-11 19:07:29 +01:00
Evgeny Poberezkin
42088fc78d Merge branch 'master' into sqlcipher 2022-09-11 13:49:44 +01:00
Evgeny Poberezkin
4be50fc923 nix: refactor ios parameters 2022-09-11 09:38:40 +01:00
Evgeny Poberezkin
e080690c2e nix: remove ERR_error_string patch 2022-09-11 09:23:16 +01:00
Evgeny Poberezkin
f6e2f11299 nix: patch to replace ERR_error_string with ERR_func_error_string 2022-09-10 23:02:37 +01:00
Evgeny Poberezkin
9d70cf1e7b nix: update patches 2022-09-10 22:10:26 +01:00
Evgeny Poberezkin
35af0786c0 nix: fix syntax error 2022-09-10 21:55:26 +01:00
Evgeny Poberezkin
2a32810182 nix: android/log.h patch 2022-09-10 21:49:51 +01:00
Evgeny Poberezkin
2bc591783d Merge branch 'sqlcipher' into sqlcipher-android 2022-09-10 18:27:33 +01:00
Evgeny Poberezkin
0c716da346 nix: fix patchelf 2022-09-10 18:18:47 +01:00
Evgeny Poberezkin
ca0a51a485 nix: add commoncrypto flag to tagged json builds 2022-09-10 18:02:19 +01:00
Evgeny Poberezkin
6e8aa5595d Merge branch 'master' into sqlcipher 2022-09-10 17:52:13 +01:00
Evgeny Poberezkin
0af5031790 nix: re-use ios post install script (#1035) 2022-09-10 16:57:40 +01:00
Evgeny Poberezkin
e5e8d95ba4 nix: fix condition syntax 2022-09-10 16:12:34 +01:00
Evgeny Poberezkin
33011b5d48 nix: skip libsimplex.so when patching so name 2022-09-10 14:17:08 +01:00
Evgeny Poberezkin
8085515f56 nix: set -x 2022-09-10 11:08:28 +01:00
Evgeny Poberezkin
f3f661ee40 Merge branch 'master' into sqlcipher 2022-09-10 11:07:50 +01:00
Evgeny Poberezkin
a26f2a58d1 update paths in web.yml 2022-09-10 10:42:04 +01:00
Evgeny Poberezkin
7fa78de6d4 add paths to trigger build in web.yml 2022-09-10 10:35:52 +01:00
Evgeny Poberezkin
46241c31e1 update website on master branch changes 2022-09-10 10:29:16 +01:00
Evgeny Poberezkin
c06cef9727 update CNAME 2022-09-10 10:27:50 +01:00
M Sarmad Qadeer
43adb7de82 migrate website to 11ty site generator (#913)
* readme: fix link

* add 11ty files to website folder

* add web.yml

* add simplex web files

* add font matter to some blogs

* remove unnecessary things

* change few settings

* add a web script

* update web.yml

* update image format & add an image

* add font matter to blogs

* update blog.html

* add article layout & give that layout to blogs

* update the location of _includes

* update article layout

* change original blog links

* add styling to blog

* improve the links of blogs

* update web.sh

* add favicon

* update a tag in a blog

* improve stylings of article page

* improve styling of blog page

* update the theme

* update font matter and update links in new blog.

* add style changes

* apply reverse chronology sort on articles

* shift blogs links back to hashes

* add ids to headers & smooth scrolling

* make all blog links relative

* add smooth scrolling & add relative to absolute links converter

* add navigation

* improve mobile nav

* change desktop header style

* convert blogs link text to "Read More"

* change desktop header style

* style mobile nav

* fix landing page styling

* update web workflow

* update web workflow

* nav setting

* add tailwind links

* update web workflow

* remove app demo folder

* remove special characters from the links

* fix the issue of links

* make web.sh executable

* update blog links

* move web.sh to website folder

* code style

* EOLs

* format index.css & contact.css

* add markdown-it configuration

* add outline none on focus

* remove extra Javascript

* make mobile nav display none by default

* add permalinks to markdown files

* update 11ty config

* update web.sh

* update article

* resolve issue of special characters in header ids
introduce slugify

* add target _blank to whitepaper link

* add last post

* EOLs

* try to resolve bullets issue

* use markdown-it-replace-link
to convert relative .md extension to .html extension

* add missing images, simpligy link parsing

* add CNAME file

* add CNAME file, rename config

* fix jumping table issue

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-09-10 10:26:21 +01:00
Evgeny Poberezkin
06835ee3fc ios: additional db encryption UX (#1031)
* ios: additional db encryption UX

* typo

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* fixes

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-09-08 17:36:16 +01:00
Evgeny Poberezkin
9eb244f9c1 nix: set so name 2022-09-08 14:04:33 +01:00
Evgeny Poberezkin
f3a3fe0710 Merge branch 'sqlcipher' into sqlcipher-android 2022-09-08 13:31:29 +01:00
Evgeny Poberezkin
22ee465d3b nix: update to rename libs dependencies and to remove libunwind from .so files 2022-09-08 07:35:32 +01:00
Evgeny Poberezkin
8097611207 ios: NSE without passphrase in keychain (#1030) 2022-09-07 20:06:16 +01:00
Evgeny Poberezkin
85e62c4f79 Merge branch 'master' into sqlcipher 2022-09-07 17:26:05 +01:00
Evgeny Poberezkin
05417fd1e8 nix: ls androidPkgs 2022-09-07 17:23:24 +01:00
Evgeny Poberezkin
3f5ca84c84 core: fix error reporting of sqlcipher export errors (#1029) 2022-09-07 17:20:47 +01:00
Evgeny Poberezkin
0fc3453f20 extend JNI bridge 2022-09-07 13:20:28 +01:00
Evgeny Poberezkin
6904ad68d9 android: add libcrypto.so 2022-09-07 12:58:01 +01:00
Evgeny Poberezkin
766009269e ios: use SQLCipher (#1012)
* use sqlcipher build, hardcoded encryption key

* UI for db encryption

* database passphrase UI

* show orange icon when database is not encrypted

* call encrypt

* more ios ux

* basic UX for passphrase complete

* with animation

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* passphrase complexity, fixes

* fix moving entry field

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-09-07 12:49:41 +01:00
Stanislav Dmitrenko
aa79a3058c android: mute/unmute in a chat menu (#1026)
* Mute/unmute in a chat menu

* Better naming
2022-09-07 10:36:00 +01:00
Evgeny Poberezkin
00471a095d update flake.nix to export openssl libs 2022-09-07 10:24:42 +01:00
Evgeny Poberezkin
de2d169fbc core: report passphrase error separately from others (#1027) 2022-09-06 23:14:58 +01:00
Evgeny Poberezkin
7072dd4f7e core: fix api for encryption (#1025) 2022-09-06 21:25:07 +01:00
Stanislav Dmitrenko
65f3fe8afc Fix counter when message is updated (#1023) 2022-09-06 20:26:52 +01:00
Stanislav Dmitrenko
03b4bea82a ci: script for downloading and unpacking prebuilt aarch64 libs for Android (#864)
* Script for downloading and unpacking prebuilt aarch64 libs for Android

* set -e

* Script for downloading libs supports macOs
2022-09-06 19:13:27 +01:00
Evgeny Poberezkin
039f810f4f Merge branch 'master' into sqlcipher 2022-09-06 19:06:42 +01:00
Stanislav Dmitrenko
51bb2fe60b android: onion hosts description (#1020)
* Onion hosts description

* Removed line

* Added text to alert before changing network settings

* Strings

* change alert title

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-09-06 19:05:57 +01:00
Stanislav Dmitrenko
6586e45d86 android: Option for periodically fetching new messages without starting a service (#1013)
* Option for periodically fetching new messages without starting a service
- also user can hide some content from notification, like it's text or/and author

* More stable notification worker

* Allowed to run periodic notifications when battery optimization is on

* corrections

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* correction

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* correction

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* Changes to notifications flow

* correction

* Made delay for receiving messages in worker longer

* correction

* check interval

* Update SimplexApp.kt, SimplexService.kt, and SimpleXAPI.kt

* update strings

* Strings

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-09-06 16:45:21 +01:00
Evgeny Poberezkin
0d220d63ea update flake.nix 2022-09-05 22:14:35 +01:00
Evgeny Poberezkin
9d009663ba update flake.nix to export libs from androidPkgs.openssl 2022-09-05 15:40:40 +01:00
Evgeny Poberezkin
a611040c41 Merge branch 'master' into sqlcipher 2022-09-05 15:37:08 +01:00
Evgeny Poberezkin
b232b6132f terminal: commands to mute/unmute contacts and groups (#1018)
* terminal: commands to mute/unmute contacts and groups

* tests
2022-09-05 15:23:38 +01:00
Stanislav Dmitrenko
f512298d10 Search in a list of chats (#1009)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-09-05 15:22:28 +01:00
Evgeny Poberezkin
082e12683b core: change database encryption API to require current passphrase on all changes (#1019) 2022-09-05 14:54:39 +01:00
Evgeny Poberezkin
229f385f42 Merge branch 'master' into sqlcipher 2022-09-04 19:07:19 +01:00
Evgeny Poberezkin
4dd2b1d88b readme, typo 2022-09-04 09:45:34 +01:00
Evgeny Poberezkin
da4e103cec Merge branch 'master' into sqlcipher 2022-09-03 20:52:26 +01:00
Evgeny Poberezkin
619d58900c mobile: enable chat console when chat is stopped (#1017) 2022-09-03 20:51:59 +01:00
Evgeny Poberezkin
a8216bbd54 core: add error strings to SQLCipher encrypt/decrypt commands (#1014) 2022-09-03 19:32:21 +01:00
Evgeny Poberezkin
19f3890bed update flake.nix 2022-09-03 09:22:19 +01:00
Evgeny Poberezkin
4734758be0 fix flake.nix 2022-09-02 22:44:49 +01:00
Evgeny Poberezkin
ed97518a53 Merge branch 'master' into sqlcipher 2022-09-02 22:13:46 +01:00
Evgeny Poberezkin
ddde821064 nix: direct-sqlcipher patch, openssl flag for android (#1011) 2022-09-02 22:03:53 +01:00
Evgeny Poberezkin
7999e88554 core: fix chatInitKey to pass database key to agent store (#1010) 2022-09-02 20:20:43 +01:00
Evgeny Poberezkin
2b5e3a9459 core: C API to migrate and check database (#1008)
* core: C API to migrate and check database

* update simplexmq
2022-09-02 16:38:41 +01:00
Evgeny Poberezkin
38b3965e68 use commoncrypto flag in ios nix build (for sqlcipher) (#1006)
* use commoncrypto flag in ios nix build (for sqlcipher)

* remove openssl flag from cabal.project
2022-09-02 11:26:14 +01:00
Evgeny Poberezkin
f68d5e1e60 android: fix alias layout (#986)
* android: fix alias layout

* Small changes to layout of alias text field

Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
2022-09-02 10:13:21 +01:00
sh
32c133d6f8 build-android: fix script (#1005) 2022-09-01 22:32:57 +01:00
Stanislav Dmitrenko
5371ad82c2 Reversed terminal layout (#1004) 2022-09-01 21:57:05 +01:00
Stanislav Dmitrenko
6eb6004706 android: pick image from gallery if it exists or from files otherwise (#1003)
* Pick image from gallery if it exists or from files otherwise

* Remove toast if gallery is not found
2022-09-01 21:01:59 +01:00
Stanislav Dmitrenko
fc31b404d7 Swiping over a link will not trigger browser opening (#1002) 2022-09-01 20:28:58 +01:00
Stanislav Dmitrenko
4c60309d1d android: screen lock with rotation (#1001)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-09-01 20:26:30 +01:00
Evgeny Poberezkin
6597400f61 Merge branch 'master' into sqlcipher 2022-09-01 17:46:56 +01:00
Evgeny Poberezkin
8356d7858f ios: update library 2022-09-01 17:46:41 +01:00
Evgeny Poberezkin
944c502101 update blog 2022-09-01 14:58:31 +01:00
Evgeny Poberezkin
0244f2da9a blog: v3.2 - incognito mode (#996)
* blog: v3.2 - incognito mode

* typo

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* add images

* images

* fix link

* update blog links

* correction

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-09-01 12:42:13 +01:00
JRoberts
79d891e5bc android: version 3.2.1 (52) 2022-09-01 15:06:34 +04:00
JRoberts
313963dab6 android: fix incoming call view (#999)
* different implementation

* layout

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-09-01 11:54:34 +01:00
Evgeny Poberezkin
6727613dc1 ios: missing localizations (#1000) 2022-09-01 11:43:17 +01:00
Stanislav Dmitrenko
e54688ad89 android: disable compression of res/raw directory to avoid crash on incoming call (opening mp3) (#997)
* Disable compression of `res/raw` directory. Otherwise Android crashes the app when trying to open .mp3 file from the directory

* do not compress all files in res folder

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-09-01 09:34:56 +01:00
Stanislav Dmitrenko
1a36c88f72 android: prevent orientation change in calls (#995)
- orientation will be locked in calls to portrait and then returned back
- calls ends audio session when they will be ended
2022-08-31 21:50:08 +01:00
Stanislav Dmitrenko
1e587df3d4 State preserving for some UI elements which otherwise would be lost on orientation change (#994)
- restore message text as well as reply state
- restore search view
- don't display blank view on orientation change for a moment
- better saving of local user name while typing. Prevent loosing state on orientation change and hard killing the app
- don't display same messages in MainActivity from old intents on orientation change (no double processing of intent)
2022-08-31 21:49:19 +01:00
Evgeny Poberezkin
3613fc953e core: encrypt chat database (#988)
* core: encrypt chat database

* check DB key error on start

* function to encrypt database

* encrypt database command

* decrypt, rekey

* remove rekey, refactor

* test for db encryption/decryption

* update simplexmq
2022-08-31 18:07:34 +01:00
JRoberts
74b11d1c5d 3.2.1 (#992) 2022-08-31 16:13:40 +04:00
JRoberts
a1562bf0e7 android: restore footer counter (#990) 2022-08-31 12:26:41 +04:00
JRoberts
73447ce22b android: version 3.2 (51) 2022-08-31 09:55:21 +04:00
JRoberts
956cf6b203 android: version 3.2 (50) 2022-08-31 09:45:18 +04:00
Stanislav Dmitrenko
378118b82e Options when using .onion hosts (#989)
* Options when using .onion hosts

* Confirmation alert before applying network settings

* Useless new line was removed

* Different ordering of options in enum
2022-08-30 22:24:33 +01:00
Stanislav Dmitrenko
92abdde69e android: start direct chat button inside MemberInfo (#987)
* Start direct chat button inside GroupInfo

* More code for better understanding
2022-08-30 18:37:44 +01:00
Evgeny Poberezkin
5e5c851173 update simplexmq 2022-08-30 16:35:56 +01:00
Evgeny Poberezkin
ed519a5cfe Merge branch 'master' into sqlcipher 2022-08-30 16:35:12 +01:00
Evgeny Poberezkin
69758971af update simplexmq (to fix network-transport at 0.5.4) 2022-08-30 16:17:48 +01:00
Evgeny Poberezkin
025f838f43 update dependencies 2022-08-30 14:33:43 +01:00
Evgeny Poberezkin
e651952a34 Merge branch 'master' into sqlcipher 2022-08-30 12:52:06 +01:00
Evgeny Poberezkin
02ca7234fb use SQLCipher (#981)
* use SQLCipher

* pass encryption key via CLI options

* update dependencies to use git

* add CONTRIBUTING.md

* move flag, enable build in sqlcipher branch

* update dependencies
2022-08-30 12:49:07 +01:00
JRoberts
5fa2d7f7ce ios: disable incognito toggle when chat is stopped (#983) 2022-08-30 15:33:36 +04:00
Stanislav Dmitrenko
0169589e7c Incognito mode (#974)
* Incognito mode

* Incognito icon color and state applying

* Added a spacer under username

* Local contact aliases support

* Simplified incognito

* update help title

* ChatInfo layout

* corrections

* color

* icon

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-08-30 15:17:28 +04:00
Stanislav Dmitrenko
2d4348c50d Regex for Emoji (#982) 2022-08-30 08:50:44 +01:00
JRoberts
14f6e5c16f ios: version 3.2 (70) 2022-08-29 20:10:27 +04:00
Evgeny Poberezkin
51a2fa8c28 ios: programmatic navigation between list/chat (#980)
* ios: programmatic navigation between list/chat

* prevent chat info sheet from showing when switching conversation

* add direct chat with member to model

* set status to connected

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-08-29 17:08:46 +04:00
JRoberts
7343e4a51a ios: simplify incognito feature (#979) 2022-08-29 14:47:29 +04:00
Evgeny Poberezkin
b4d7afb4c1 ios: dark/light mode toggle (#975) 2022-08-28 09:14:55 +01:00
JRoberts
2fc6873c42 core: simplify incognito feature - remove host/invitee incognito profiles communication; remove incognito mode group creation and join; use same incognito profile known to host when joining (#978) 2022-08-27 19:56:03 +04:00
JRoberts
7683254de2 ios: group member navigation (#973) 2022-08-26 17:27:38 +04:00
JRoberts
3a077d927d ios: contact aliases (#970)
* ios: contact aliases

* wip

* wip

* wip

* move onTapGesture

* revert test

* improve search

* corrections

* font size

* remove parameter

* clear button

* button style

* remove clear button

* ternary

* refactor search

* rename

* ios: contact aliases translations (#972)

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-08-25 17:36:26 +04:00
Stanislav Dmitrenko
a5e74ea2f0 android: choosing theme and accent color (#967)
* Theme selector
- ability to select from three default choices: system theme, light, dark
- ability to choose color accent (primary color)

* Removed unused code and made small changes to colors and buttons
2022-08-24 21:15:26 +01:00
JRoberts
53a71cf28c core: contact aliases (#968) 2022-08-24 19:03:43 +04:00
JRoberts
e6551abc68 ios: incognito mode (#945)
* ios: incognito types

* wip

* wip

* wip

* wip

* wip

* cleaner interface

* CIGroupInvitationView logic

* masks not filled

* ui improvements

* wip

* wip

* incognito may be compromised alerts

* help

* remove modifier

* Update apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* Update apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* Update apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* Update apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* Update apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* Update apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* Update apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* Update apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* contact request

* texts

* ;

* prepare for merge

* restore help

* wip

* update help

* wip

* update incognito help

* the

* Update apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* wording

* translations

* secondary color

* translations

* translations

* fix Your Chats title

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-08-23 18:18:12 +04:00
Evgeny Poberezkin
bd7aa81625 core: DB json encoding for group events (platform independent) (#966)
* core: DB json encoding for group events (platform independent)

* comment, migration

* shorter constructors

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-08-23 16:24:43 +04:00
Stanislav Dmitrenko
04592f52de Ability to disable notifications per chat (#964)
* Ability to disable notifications per chat

* All Buttons in AlertDialog replaced with TextButtons

* update icon

* update strings

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-08-22 21:16:01 +01:00
JRoberts
a06499d710 core: add host_conn_custom_user_profile_id to groups to replace join with connections causing duplicates (avoids complex subqueries) (#965) 2022-08-22 23:12:09 +04:00
Stanislav Dmitrenko
6d1414af71 UI tweaks (#963)
* UI tweaks

* correction
2022-08-22 17:36:39 +01:00
Stanislav Dmitrenko
9cd7a7fdb0 Re-apply new chat instance on chatId changes (#962)
* Re-apply new chat instance on chatId changes

* Fixed incorrectly calculated counters on floating buttons

* Show chat at the bottom of a view instead of at the top
2022-08-22 14:02:46 +01:00
Stanislav Dmitrenko
3c2f5d14f5 Made scrolling faster (#961) 2022-08-22 12:14:46 +01:00
JRoberts
985f3dffd3 core: read host's connection custom_user_profile_id into group info (#960) 2022-08-22 11:04:34 +04:00
Evgeny Poberezkin
0a048eb286 ios: fix typo, add information to settings (#958) 2022-08-20 23:03:56 +01:00
Evgeny Poberezkin
2cffe91e0b ios: choose accent color (#956) 2022-08-20 21:55:06 +01:00
Evgeny Poberezkin
9f94c6f98a 3.2.0 (#957) 2022-08-20 19:52:25 +01:00
Evgeny Poberezkin
164426db49 core: catch error when toggling notifications (#954)
* core: catch error when toggling notifications

* filter current members

* filter active members
2022-08-20 14:47:24 +01:00
Evgeny Poberezkin
307db450d8 ios: mute notifications per chat (#950)
* mute notifications per chat

* toggle notifications

* update settings api

* move model changes to main thread

* add mute indication, remove swipe buttons

* icon

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-08-20 12:47:48 +01:00
JRoberts
e6233722db core: create incognito membership if direct connection with host is incognito when processing invitation instead of on join (#953) 2022-08-20 13:12:20 +04:00
Evgeny Poberezkin
d26083d8b7 core: fix settings api (#952) 2022-08-19 22:44:00 +01:00
Evgeny Poberezkin
f561698fb9 mobile: update version: 3.2, android 49 / iOS 69 2022-08-19 20:36:29 +01:00
Stanislav Dmitrenko
74966b1425 Two fixes, better performance too (#951)
- on orientation change scroll position in chat wasn't preserved
- the app had been making multiple same queries to a database when tried to preload more messages
2022-08-19 20:02:28 +01:00
Stanislav Dmitrenko
51b8ce10ae Better performance in FloatingButtons function (#949) 2022-08-19 17:17:02 +01:00
Stanislav Dmitrenko
8c716962fb Different level of APK compression (#947)
* Different level of APK compression
- can reduce from 200mb to 50mb with level 5 of compression. Supports Intellij IDEA and command line gradle invocation
- by default, this feature is disabled. To enable create a file local.properties in `apps/android` and paste this line: `compression_level=5`
- level can be from 0 (no compression) to 9 (slowest and the must effective)
- automatically enables `extractNativeLibs` AndroidManifest's flag since it's required in this case. Feel free to find an alternative that works with compression of .so libs and without enabling this flag
- Windows is not suppored, of course. Only Unix-like OSes

* script corrections

* Missing JAVA_HOME in some environments

* Rename release apk made by IDEA to simplex.apk

* Enhancements

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-08-19 17:16:52 +01:00
Evgeny Poberezkin
fee2b247e9 android: update search api (#948) 2022-08-19 16:02:08 +01:00
Evgeny Poberezkin
992ba75306 update simplexmq 2022-08-19 15:32:55 +01:00
Evgeny Poberezkin
70168967a3 core: commands to set chat notification settings (#946)
* core: commands to set chat notification settings

* add API
2022-08-19 15:17:05 +01:00
JRoberts
3221c0abb5 docs: optional profile in groupInvitation and x.grp.acpt (incognito connections) (#944) 2022-08-18 15:17:18 +04:00
Stanislav Dmitrenko
d8049d4bfc Fixes service issues (#942)
* Fixes service issues
- no more crashes after start of a service with battery optimization enabled
- no more service restarts after the app exit with disabled private notifications

* Disable service restart even after reboot if the user didn't allow this

* [Experimental] Disabling logic of start up process from application process. The same is enabled in a service anyway
- every application process creation makes the service running even in situations when it's not needed. For example, RescheduleReceiver from WorkManager receives boot completed event and it triggers service start up

* Returned unneeded part of code. It may be useful (in theory) if user's device doesn't allow any services to be started (it's something that should never happen)

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-08-18 12:14:22 +01:00
Evgeny Poberezkin
b15d39eb4a android: include default values in JSON encoding (#943) 2022-08-18 09:18:15 +01:00
Evgeny Poberezkin
85e36ac12c update simplexmq (servers update) 2022-08-18 08:43:50 +01:00
JRoberts
5e67654249 core: incognito connections (#926) 2022-08-18 11:35:31 +04:00
Evgeny Poberezkin
404b7093b7 core: update simplexmq (split transaction to fix android crash) 2022-08-17 22:50:46 +01:00
Evgeny Poberezkin
cad1ad87a8 android: search (#940)
* Search in chat messages

* align api with core/swift

* EOLs

* DefaultTopAppBar changes

Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
2022-08-17 16:58:57 +01:00
Evgeny Poberezkin
fd27839442 ios: search in chat (#938)
* ios: search in chat

* update libraries and search API

* layout
2022-08-17 11:43:18 +01:00
Evgeny Poberezkin
ae6fae5ced core: update simplexmq (servers migration) 2022-08-16 21:45:03 +01:00
Evgeny Poberezkin
e9cddd6ca3 core: add search parameter name to /_get chat api (#939) 2022-08-16 19:56:21 +01:00
Evgeny Poberezkin
76bde53206 ios: scroll buttons and unread counts (#937)
* ios: scroll buttons and unread counts

* floating buttons for unread counts

* remove commented code

* remove prints
2022-08-16 13:13:29 +01:00
Stanislav Dmitrenko
0a2f7681d8 Swipe to reply feature (#936)
* Swipe to reply feature
- ability to reply by swiping on a message from right-to-left or left-to-right
- keyboard will be open automatically

* Only one direction for swipe to reply action
2022-08-16 13:08:15 +01:00
Evgeny Poberezkin
3776e1c29c ios: chat pagination (#910)
* ios: chat pagination

* pagination hack

* rotationEffect

* more rotation

* the least broken context menu

* custom contect menu

* add context item menus

* fix context menu preview size

* fix content menu targeted previews

* subclass context menu view

* remove UIView subclass

* move coordinator class inside view

* context menu and clicks work

* reverse model

* update item view based on viewId

* hide underlying swiftui item

* cover swiftui item with solid color

* remove overlay

* move hostview to async block

* background overlay

* remove async hostview

* clear chat items on back buttom

* update viewId on status changes
2022-08-15 21:07:11 +01:00
Evgeny Poberezkin
2e4ffb7fe9 ios: setting to use .onion hosts (#934) 2022-08-15 08:25:41 +01:00
Evgeny Poberezkin
954338658f core: update simplexmq (fix ntf server hosts) 2022-08-14 21:13:57 +01:00
Evgeny Poberezkin
7f78b08100 mobile: add host mode in NetCfg (#933) 2022-08-14 17:34:11 +01:00
Stanislav Dmitrenko
5d8d636adc Floating button with unread counter and go to bottom action (#929)
* Floating buttons with unread counters and go to bottom action

* Fixed marking read of long chats without preloaded messages

* Apply suggestions from code review

* Counters fix

* update button size/color

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-08-13 22:00:26 +01:00
Evgeny Poberezkin
aac80dacf7 core: host connection events 2022-08-13 14:18:12 +01:00
Evgeny Poberezkin
e43be1ad8b core: support multiple hostnames in server addresses (#930)
* core: support multiple hostnames in server addresses

* add onion hosts

* update simplexmq, fix test

* fix parsing servers with multiple hostnames
2022-08-13 11:53:53 +01:00
Stanislav Dmitrenko
57000fa3f0 Endless scrolling in a chat view (#925)
* Endless scrolling in a chat view
- scroll position when you open keyboard/change screen orientation will remain the same
- scrolling to top will show messages from history
- unread messages will be positioned at the top of the screen

* Marking chat read message-per-message

* Prevent changing scroll position on orientation change

* Adapted new code to the old code

* refactor

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-08-12 20:16:33 +01:00
sh
db367b376b build-android: add bundle script (#931) 2022-08-12 11:30:51 +01:00
JRoberts
cc498572cd core: create indices on chat_items for faster pagination (#927) 2022-08-11 15:48:47 +04:00
Stanislav Dmitrenko
622ab549a3 Debug package suffix and ability to override Gradle variables (#920)
* Debug package suffix and ability to override Gradle variables
- now debug builds will have '.debug' suffix by default. It allows to have multiple app builds (debug and release) on the same device. If you don't need this, create a file local.properties and add `application_id_suffix=` into it
- now everyone can override some variables from top-level build.gradle file. For example, gradle_plugin_version, debuggable manifest attribute, and so on. Overriding Gradle plugin version is useful for those who uses Intellij IDEA with older Gradle plugin than in Android Studio

* Prevent socket name conflict from different packages

* Configurable app name for debug build. By default it's SimpleX Debug

* Changed defaults in build.gradle
2022-08-11 00:53:02 +01:00
JRoberts
f896c4453d mobile: update model on adding group member (#923) 2022-08-10 14:54:15 +04:00
JRoberts
38f65c82c3 core: send notification on XGrpMemFwd (#921) 2022-08-09 21:46:49 +04:00
JRoberts
22733f505d android: update group members in model (#919) 2022-08-09 19:50:29 +04:00
JRoberts
26a019d9d2 ios: update group members in model (#915) 2022-08-09 13:43:19 +04:00
JRoberts
7531791f1b core: chat search (#914) 2022-08-08 22:48:42 +04:00
JRoberts
cd28ba62a1 core: fix chat pagination filtering (#911) 2022-08-08 14:13:51 +04:00
Evgeny Poberezkin
32dff4e1a3 blog: v3.1, groups (#905)
* blog: v3.1, groups

* --amend

* add to blog TOC

* images

* update blog

* update image

* update image

* update blog

* update readme

* readme

* links to the protocol

* update heading
2022-08-08 10:05:43 +01:00
JRoberts
fd85026a0d docs: SimpleX Chat Protocol corrections (#909) 2022-08-08 09:49:40 +01:00
Evgeny Poberezkin
e3f63db5ab mobile: API for chat pagination (#908) 2022-08-07 19:23:33 +01:00
Evgeny Poberezkin
7f959103c1 docs: SimpleX Chat Protocol (#906)
* docs: SimpleX Chat Protocol

* chat message JTD schema, protocol draft

* update protocol, group diagram

* update heading

* add protocol reference to readme

* skip async group test
2022-08-07 16:43:09 +01:00
Evgeny Poberezkin
bd3d4467c7 3.1.0 2022-08-06 16:30:39 +01:00
Evgeny Poberezkin
5345199829 update readme image 2022-08-05 22:32:24 +01:00
Evgeny Poberezkin
481c4c0763 ios: version 3.1 (68) 2022-08-05 13:27:14 +01:00
Evgeny Poberezkin
bf2b3855b7 android: update version 3.1 (48) 2022-08-05 08:15:40 +01:00
sh
a254d5f050 build-android: specify commit (#904) 2022-08-05 08:14:32 +01:00
Evgeny Poberezkin
e8749debec ios: fix notification badge count (#903) 2022-08-04 22:25:52 +01:00
Evgeny Poberezkin
afbc7dd2c1 update f-droid description 2022-08-04 21:07:45 +01:00
Evgeny Poberezkin
7a00a3e324 core: remove logs, remove log for A_DUPLICATE error (#896) 2022-08-04 20:59:05 +01:00
Evgeny Poberezkin
03d9d86aba android: fix crash on invalid base64 image, show placeholder image instead (#902) 2022-08-04 20:32:01 +01:00
Evgeny Poberezkin
13e7925348 core: fully remove invited member (#901)
* core: fully remove invited member

* deleteMemberConnection
2022-08-04 18:39:31 +01:00
Evgeny Poberezkin
46319044f8 core: fix race condition in --execute option, closes #890 (#898) 2022-08-04 17:07:50 +01:00
JRoberts
8a7e320d12 ios: version 3.1 (67) 2022-08-04 19:54:30 +04:00
Evgeny Poberezkin
152ed96ac0 android: static vars for NetCfg (#900) 2022-08-04 16:23:59 +01:00
JRoberts
8dc7bea724 ios: advanced network settings translations (#899) 2022-08-04 19:20:00 +04:00
JRoberts
497cf86eb0 android: advanced network settings (#895) 2022-08-04 18:40:36 +04:00
Stanislav Dmitrenko
9508ea5c97 App icon chooser (#894)
* App icon chooser
- ability to choose an icon from a predefined list

* dark icons

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-08-04 14:16:35 +01:00
Evgeny Poberezkin
257133db3b ios: remove modal sheets before authentication (#897)
* ios: remove modal sheets before authentication

* line break

* add reference to source
2022-08-04 12:41:05 +01:00
Evgeny Poberezkin
c4bc88b49b Merge branch 'stable' 2022-08-04 12:05:57 +01:00
sh
80389ffe93 android: check nix hash (#893) 2022-08-04 11:20:58 +01:00
sh
e53540f43f android: remove cmake version pin from gradle (#889) 2022-08-04 11:20:37 +01:00
Evgeny Poberezkin
55adbb4692 core: clear group content on deletion, break transaction to prevent error on Android, more logs (#892)
* core: log group deletion

* clear group content, break transaction, add logs
2022-08-04 11:12:50 +01:00
Evgeny Poberezkin
91baf9f362 terminal: update active group when message is updated (#891)
* terminal: update active group when message is updated

* fix
2022-08-04 11:12:37 +01:00
sh
04b9243d7e android: change nix config logic (#888) 2022-08-04 09:36:36 +01:00
sh
b3d74933c2 build-android: fix git compatibility (#884)
* build-android: fix git compatibility

* move to scripts
2022-08-03 21:37:31 +01:00
sh
90ab6f34bf android: add fastlane metadata (#885)
* android: add fastlane metadata

* update fastlane info

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-08-03 21:04:27 +01:00
Stanislav Dmitrenko
57e7034b4d Update to Compose 1.2.0 beta2 (#874)
- fixes issue with multiple backspaces in a BasicTextField. Before that update text field stops deleting characters after long press on the backspace key
2022-08-03 18:46:38 +01:00
Stanislav Dmitrenko
8455cca9c3 Button in notification that routes to settings for that specific notification channel. Android O+ (#875) 2022-08-03 18:10:36 +01:00
Evgeny Poberezkin
9fdc2a4631 ios: remove option to not show pending contact connections (#883) 2022-08-03 18:02:59 +01:00
Evgeny Poberezkin
a5cdbc90f8 ios: alternative app icon (#881) 2022-08-03 17:46:05 +01:00
Evgeny Poberezkin
a5972c7de1 ios: register group defaults to correctly read network settings in NSE (#882) 2022-08-03 17:39:01 +01:00
Evgeny Poberezkin
c74a4fcbca update logos on SimpleX info page for dark mode (#880) 2022-08-03 15:17:42 +01:00
Stanislav Dmitrenko
4c6ee95eb7 Removed gesture interception while long clicking on a chat bubble (#871)
* Removed gesture interception while long clicking on a chat bubble with a link
- allowed to skip motion event consuming based on touch offset
- long clicking on a link copies it to a clipboard

* Long click on a link shows menu instead of copying to clipboard

* EOLs

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-08-03 14:33:19 +01:00
Evgeny Poberezkin
9e210256d2 core: add delete group logs (#879) 2022-08-03 16:56:35 +04:00
sh
d67f86ada5 install: add android build script (#877) 2022-08-03 13:52:16 +01:00
JRoberts
7a03f87822 mobile: update logo (#876)
* ios: logo

* logo

* bigger logo
2022-08-03 13:30:29 +01:00
JRoberts
d6a4a245dc update simplexmq (reconnect on network config change) (#878) 2022-08-03 15:49:31 +04:00
Evgeny Poberezkin
0fe7e64989 ios: advanced network settings (#873)
* ios: advanced network settings

* save network config

* update network settins, set in NSE

* update UI, update simplexmq

* show advanced network settings only with dev tools on
2022-08-03 15:36:51 +04:00
Stanislav Dmitrenko
e39f9bc251 QRCodeScanner will close camera on back press (#872) 2022-08-03 08:47:51 +01:00
JRoberts
cbd7882ff4 ios: group ui translations; android: empty lists ui fixes (#870) 2022-08-03 11:40:36 +04:00
Evgeny Poberezkin
4ad1abcbfa core: support passing all network configuration to the agent (#868)
* core: support passing all network configuration to the agent

* update simplexmq
2022-08-02 15:36:12 +01:00
JRoberts
a36c367b81 mobile: filter out members in statuses left and removed (#869) 2022-08-02 18:07:40 +04:00
JRoberts
a14859d8c0 mobile: developer tools (#867) 2022-08-02 17:00:12 +04:00
JRoberts
9e23150938 ios: fix Servers section flickering on info view; android: button text (#866) 2022-08-02 14:48:31 +04:00
JRoberts
35eeac194e core: split group deletion into two transactions to prevent crashes on android (#865) 2022-08-02 14:10:03 +04:00
Evgeny Poberezkin
0b4a6cf9eb readme: add monero wallet for donations (#863) 2022-08-01 21:12:06 +01:00
JRoberts
2422f36d61 android: version 3.1 (47) 2022-08-01 20:54:22 +04:00
JRoberts
60117d0853 ios: version 3.1 (66) 2022-08-01 18:22:37 +04:00
JRoberts
95757ed562 android: edit group profile (#862) 2022-08-01 16:32:42 +04:00
Evgeny Poberezkin
cc0a74fae4 mobile: show errors when joining group (#861)
* mobile: show errors when joining group

* correct titles

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* improvements

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-08-01 08:34:07 +01:00
Evgeny Poberezkin
ce91dcde7f android: save SOCKS setting to preference and enable on start (#848)
* android: save SOCKS setting to preference and enable on start

* use socks proxy preference
2022-07-31 20:46:09 +01:00
Evgeny Poberezkin
999923bcf9 core: allow creating groups with the same display name; mobile: update group status when group deleted by another member or user removed (#859) 2022-07-31 18:54:49 +01:00
JRoberts
30c345933b android: create group view (#855)
* android: create group view wip

* wip

* android: add group view image wip (#856)

* new chat sheet layout

* alternative layout for new chat sheet

* simpler layout for new chat sheet

* fix add image sheet

* fix creating group

* add members when creating a group

* update text

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-07-31 16:49:32 +01:00
Evgeny Poberezkin
1b8c55a0a3 ios: add group members when group is created (#857)
* ios: add group members when group is created

* refactor

* more refactor
2022-07-30 18:46:10 +01:00
JRoberts
4f4935256c ios: move GroupChatInfoView (#854) 2022-07-30 16:59:06 +04:00
JRoberts
1dd7520bbd mobile: refine allowed group actions; inactive group indicator (#852) 2022-07-30 16:49:34 +04:00
Evgeny Poberezkin
de0f231c60 ios: edit group profile (#853) 2022-07-30 16:03:44 +04:00
Evgeny Poberezkin
0c58adff08 core: editing group profiles (no conflict resolution) (#851)
* core: editing group profiles with conflict resolution

* update group profiles

* fix group update

* add test, add group profile to chat items, update terminal output

* Update apps/android/.idea/gradle.xml
2022-07-29 19:04:32 +01:00
JRoberts
e87c78e997 android: groups ui (#850) 2022-07-29 20:11:00 +04:00
Evgeny Poberezkin
ee6f6462cf ios: create group with profile image (#849)
* ios: create group with profile image

* update libs
2022-07-28 14:49:36 +04:00
Evgeny Poberezkin
7b9164f95a core: allow getting and setting network config when chat is not started (#847) 2022-07-28 11:12:23 +01:00
Evgeny Poberezkin
4a931bc145 ios: only show notification on received messages, do not remove non-current group members from contacts that can be added to the group (#846) 2022-07-28 10:11:16 +01:00
Evgeny Poberezkin
bf4072b365 trigger build 2022-07-28 08:39:19 +01:00
Evgeny Poberezkin
658cc1af56 update readme 2022-07-27 15:07:46 +01:00
Evgeny Poberezkin
68bc572800 trigger build 2022-07-27 14:40:40 +01:00
Evgeny Poberezkin
2286752fe0 core: create group with JSON profile, including image (#845) 2022-07-27 12:15:09 +01:00
JRoberts
9864533dae ios: update chat info view (#844) 2022-07-27 13:40:26 +04:00
JRoberts
aa7e377bce ios: groups miscellaneous (#843)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-07-27 11:16:07 +04:00
JRoberts
a4aaf36774 ios: group & group member info views (#841)
* ios: group member wip

* wip

* wip

* wip

* wip

* refactor alerts

* .navigationBarHidden(true)

* await MainActor.run

* refactor

* fix

* update layout

* tex

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-07-26 12:33:10 +04:00
JRoberts
608030dcaf ios: add member ui wip (#834)
* ios: add member ui wip

* AddGroupMembersView

* clean up

* cleanup

* change new chat button

* update adding members

* add group name and image to adding members view

* adjust layout

* layout

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-07-26 10:55:58 +04:00
Evgeny Poberezkin
6069108bb9 android: UI to access servers via SOCKS proxy (#840)
* android: UI to access servers via SOCKS proxy

* UI to connect via socks

* add server hosts to contact info

* ios: types for network/info commands
2022-07-26 07:29:48 +01:00
Evgeny Poberezkin
e7f3dc3f41 terminal: help for /i and /net commands (#842)
* terminal: help for /i and /net commands

* fix servers output

* update message

* EOL
2022-07-26 07:29:28 +01:00
Evgeny Poberezkin
f150932e44 core: commands to get/set network configuration (#839) 2022-07-25 17:04:27 +04:00
Evgeny Poberezkin
7dcde32680 update readme 2022-07-24 08:34:15 +01:00
Evgeny Poberezkin
552397d938 fix install.sh script 2022-07-23 22:42:07 +01:00
Evgeny Poberezkin
cfa4b44d1f update install.sh 2022-07-23 22:14:05 +01:00
Evgeny Poberezkin
9fcd127c48 update readme link 2022-07-23 21:15:51 +01:00
Evgeny Poberezkin
7c01ad7d4f blog: v3.1-beta release (#838)
* blog: v3.1-beta release

* corrections

* add images

* update post

* update TOC, readme
2022-07-23 21:13:41 +01:00
Evgeny Poberezkin
13b236f754 allow passing version to install.sh (#837)
* allow passing version to install.sh

* add echo
2022-07-23 17:02:05 +01:00
Evgeny Poberezkin
589f560dd6 3.1.0 2022-07-23 14:49:45 +01:00
Evgeny Poberezkin
4fd13c637c core: access messaging servers via SOCKS5 proxy (#835)
* core: access messaging servers via SOCKS5 proxy

* update option info

* update simplexmq
2022-07-23 14:49:04 +01:00
JRoberts
88d1d3448d android: version 3.1 (46) 2022-07-22 20:21:56 +04:00
JRoberts
852f4f25c4 ios: version 3.1 (65) 2022-07-22 20:18:16 +04:00
JRoberts
0da4651c3b android: improve group invitations design (2) (#833)
* android: improve group invitations design (2)

* group item layout

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-07-22 17:45:05 +04:00
Evgeny Poberezkin
10659c7c82 readme: technical details (#831)
* readme: technical details

* update readme

* corrections

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-07-22 14:38:42 +01:00
Evgeny Poberezkin
ce2e1b9eb9 android: fix contact spinners race condition (#832)
* android: fix contact spinners race condition

* always update

* remove log
2022-07-22 12:56:17 +01:00
JRoberts
b232226590 core: read group chat items in separate queries 2022-07-22 15:48:04 +04:00
JRoberts
348ff612e9 android: improve group invitations design (#830) 2022-07-22 12:28:02 +04:00
Evgeny Poberezkin
8469f921b7 ios: notification actions for calls and contact requests with NSE (#829)
* ios: notification actions for calls and contact requests with NSE

* update contact request if already in the list
2022-07-22 08:10:37 +01:00
Evgeny Poberezkin
e538a9e057 update simplexmq (fix GET for contact requests) 2022-07-21 19:54:53 +01:00
JRoberts
88c1f439c1 ios: version 3.1 (64) 2022-07-21 19:23:06 +04:00
JRoberts
3845904443 android: version 3.1 (45) 2022-07-21 19:16:45 +04:00
JRoberts
6542de619d ios: version 3.1 (63) 2022-07-21 18:56:18 +04:00
JRoberts
6f87a3bdb1 ios: groups ui translations (#828) 2022-07-21 18:16:04 +04:00
Evgeny Poberezkin
26e51a07c5 ios: improve concurrency of NSE, process multiple messages (#827) 2022-07-21 17:26:46 +04:00
JRoberts
4e3d83fe0c mobile: auxiliary group items (#826) 2022-07-21 17:01:13 +04:00
JRoberts
de9c112725 core: correct group event 2022-07-21 11:01:04 +04:00
JRoberts
a509e85195 core: fix group event chat items encoding (#825) 2022-07-20 20:59:09 +04:00
Evgeny Poberezkin
3c03c96a53 core: show contact and group member servers (#824)
* core: show contact and group member servers (WIP)

* contact and member information

* update simplexmq
2022-07-20 14:57:16 +01:00
JRoberts
5e71deaa3d core: auxiliary group chat items (#821) 2022-07-20 16:56:55 +04:00
Evgeny Poberezkin
1cb348c102 core: refactor parser (#823) 2022-07-20 09:36:43 +01:00
Evgeny Poberezkin
252897d0ff ios: notification badge count (#822) 2022-07-20 08:58:53 +01:00
Evgeny Poberezkin
add82d73fa android: fix notification service by partially reverting #790 and #792 (#820) 2022-07-19 16:29:15 +01:00
Evgeny Poberezkin
048387ce88 update core 2022-07-19 16:25:30 +01:00
JRoberts
931a5d928c ios: fix chat info toolbar layout 2022-07-19 18:31:08 +04:00
JRoberts
0e84e131cd mobile: leave & delete group; ios: fix group preview interaction (#819) 2022-07-19 18:21:15 +04:00
Evgeny Poberezkin
cf1f921aed update tls to 1.6.0 and change nix config (#780)
* update tls to 1.6.0 and change nix config

Revert "revert tls to 1.5.7 and nix config changes (#746)"

This reverts commit 976b1c919f.

* nix: update hackage index

* update hackage index
2022-07-19 13:34:03 +01:00
Evgeny Poberezkin
efa79bc1f9 update simplexmq 2022-07-19 09:32:00 +01:00
Evgeny Poberezkin
c7f9262d0e ios: update core 2022-07-18 21:06:06 +01:00
JRoberts
53f3ee1f50 mobile: group invitations ui (#816)
* ios: group invitations ui

* fix

* memberActive (crashes)

* adjustments

* android ui

* android - memberActive

* update group invitation item layout

* update texts

* typo

* update layout

* do not add contacts added via groups

* filter contacts by conn_level

* turn off address sanitizer

* fix layout

* android: filter on update chat

* android adjustments

* divider fix

* android chat previews

* ios previews

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-07-18 21:58:32 +04:00
Evgeny Poberezkin
54f8dd8a2e update simplexmq (batched resubscriptions) 2022-07-18 08:26:18 +01:00
Evgeny Poberezkin
e28bd907a1 ios: update libary 2022-07-17 18:54:09 +01:00
Evgeny Poberezkin
13fbb66a21 core: use batched subscriptions (#818)
* core: use batched subscriptions

* update simplexmq

* remove comments

* clean up

* refactor

* remove todo

* revert change

* revert change

* remove comment

* add delay to the async group test

* add more delay in test
2022-07-17 15:51:17 +01:00
Evgeny Poberezkin
e8da13c7ca Merge branch 'stable' 2022-07-16 09:18:15 +01:00
Evgeny Poberezkin
66a8267b11 android: version 3.0.1 (44) 2022-07-16 09:13:05 +01:00
JRoberts
fa703d3a1d android: scale bitmap down when loading, closes #805 (#812) 2022-07-16 08:54:25 +01:00
Evgeny Poberezkin
20e3acc7c2 Merge branch 'stable' 2022-07-16 08:51:46 +01:00
Evgeny Poberezkin
3fc64f0f40 ios: v3.0.1 (62) 2022-07-16 08:49:15 +01:00
Evgeny Poberezkin
00ca111be3 ios: version 3.0.1 (61) 2022-07-16 08:41:53 +01:00
JRoberts
eb89eec5b5 core: backend for group invitations UI (status, db, updates) (#815) 2022-07-15 17:49:29 +04:00
JRoberts
8e15460bdc core: use decodeLatin1 in ciGroupInvitationToText 2022-07-14 22:09:20 +04:00
JRoberts
db87984dda core: group invitation chat item (#814) 2022-07-14 22:04:23 +04:00
JRoberts
414b174e32 ios: groups ui wip (#809) 2022-07-14 16:40:32 +04:00
JRoberts
01eff43585 mobile: group types (#808) 2022-07-14 15:55:28 +04:00
JRoberts
9dd5a00a45 android: scale bitmap down when loading, closes #805 (#812) 2022-07-14 12:13:22 +04:00
Evgeny Poberezkin
a7445afbf7 Merge branch 'stable' 2022-07-13 20:12:19 +01:00
Evgeny Poberezkin
2995ecd53d update readme 2022-07-13 20:12:04 +01:00
Evgeny Poberezkin
a6cd3843de readme: fix link 2022-07-12 19:06:05 +01:00
Evgeny Poberezkin
e0be5fda90 readme: fix link 2022-07-12 19:05:26 +01:00
Evgeny Poberezkin
5c394f15a9 ios: version 3.0.1 (61) 2022-07-12 16:36:34 +01:00
JRoberts
ad5edeba6c core: groups api (#806) 2022-07-12 19:20:56 +04:00
JRoberts
494de9bc43 core: test async group connections (#804) 2022-07-12 14:59:53 +04:00
Evgeny Poberezkin
185be526ca ios: fix notification category 2022-07-12 11:11:53 +01:00
Evgeny Poberezkin
fec03c74f4 Merge pull request #803 from simplex-chat/master
Merge master to stable
2022-07-11 14:25:40 +01:00
Evgeny Poberezkin
33646f030e update readme (#798) 2022-07-11 14:22:46 +01:00
JRoberts
df04c4a1ea ios: remove failed authentication alert (#802) 2022-07-11 16:38:21 +04:00
Evgeny Poberezkin
d1bd7fbf4c ios: version 3.0 (60) 2022-07-10 17:18:04 +01:00
Evgeny Poberezkin
e070492d02 Merge branch 'stable' 2022-07-10 17:06:39 +01:00
Evgeny Poberezkin
aef113c8cd ios: version 3.0 (59) 2022-07-10 17:06:19 +01:00
Evgeny Poberezkin
f16d8842b2 iOS: accept images in NSE if enabled, reorder chats when coming from background (#800)
* ios: automatically accept images in NSE, if enabled in settings

* remove unnecessary TODOs

* reorder chat when coming from background
2022-07-10 14:28:00 +01:00
a1lu
4408495cfb Fix typo in blog (#799) 2022-07-10 11:00:20 +01:00
Evgeny Poberezkin
920014a06b update blog 2022-07-10 08:34:16 +01:00
Evgeny Poberezkin
fd9574b5aa android: version 3.0 (43) 2022-07-09 19:23:30 +01:00
Evgeny Poberezkin
890e6abf01 update blog 2022-07-09 16:32:05 +01:00
Evgeny Poberezkin
dc6ce8a2f5 Merge pull request #797 from simplex-chat/master
Merge master to stable
2022-07-09 16:03:58 +01:00
Evgeny Poberezkin
9278234540 android: v3.0 (42) 2022-07-09 16:00:07 +01:00
Evgeny Poberezkin
cf2424f319 android: fix incorrect states in database view (#796) 2022-07-09 15:18:54 +01:00
Evgeny Poberezkin
0797798136 update library, v3.0 (58) 2022-07-09 15:16:07 +01:00
Evgeny Poberezkin
013ee86899 Merge pull request #795 from simplex-chat/master
Merge master to stable
2022-07-09 13:53:13 +01:00
Evgeny Poberezkin
36f97b2ea9 v3.0.0 2022-07-09 13:04:18 +01:00
Evgeny Poberezkin
bf390dabd4 mobile: update version/build v3.0 (ios: 57, android: 41) 2022-07-09 10:32:24 +01:00
Evgeny Poberezkin
c73a28e7de blog: v3 announcement (#791)
* blog: v3 announcement draft

* update blog

* images, corrections

* update images

* update images

* update

* update blog
2022-07-09 10:19:03 +01:00
Evgeny Poberezkin
06cb564eae ios: ensure that device token is registered once (#794) 2022-07-09 09:29:56 +01:00
Evgeny Poberezkin
b1c732f3cc ios: translations (#793) 2022-07-08 22:42:38 +01:00
JRoberts
b58c880d4c android: version 3.0 (40) 2022-07-08 20:03:21 +04:00
JRoberts
ba9a6f3ab6 android: database export & import (#787)
* android: database export & import wip

* fix import

* import, delete

* disabled during in progress

* footer

* ChatArchiveView

* refactor

* disable settings

* more chat running interactions

* more chat running interactions

* fixes

* rename

* fixes

* fix

* change ts format

* chatWasStopped model variable

* remove logs

* reset chatWasStopped

* chat was stopped preference

* fixes

* unconditional chatRunning

* remove intermediary view

* refactor

* mkInstantPreference

* refactor

* refactor

* refactor

* DatabaseItem

* remove todos

* refactor

* refactor

* refactor

* translations

* translations

* refactor import

* refactor import
2022-07-08 17:16:28 +04:00
Evgeny Poberezkin
7ad173c5dc android: version 3.0 (39) 2022-07-08 14:03:24 +01:00
Evgeny Poberezkin
fdad58b0ee android: update service (#792) 2022-07-07 23:00:42 +01:00
Evgeny Poberezkin
2b7de2a7a6 android: only start service when app is in the background, change service icon (#790)
* android: only start service when app is in the background, change service icon

* update version v3.0 (38)

* set flag
2022-07-07 19:05:03 +01:00
Evgeny Poberezkin
2ae3748489 android: update version v3.0 (37) 2022-07-06 22:10:14 +01:00
Evgeny Poberezkin
36aae92c55 android: toggle speaker in audio call (#789) 2022-07-06 21:33:32 +01:00
JRoberts
e290309cd1 core: add optional parentTempDirectory to ArchiveConfig (#788)
* core: add optional parentTempDirectory to ArchiveConfig

* swift

* brackets

* Update src/Simplex/Chat/Archive.hs

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* logs

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-07-06 21:45:29 +04:00
Evgeny Poberezkin
d3dd6e5d1c fix android terminal scroll (#786) 2022-07-06 17:20:33 +01:00
Evgeny Poberezkin
51ea478acb ios: update version v3.0 (56) 2022-07-06 16:53:08 +01:00
Evgeny Poberezkin
8966a0e22f ios: NSE waits up to 10 sec until app is suspended (#785)
* ios: NSE waits up to 10 sec until app is suspended

* show default notification if the app is no longer suspending/suspended
2022-07-06 16:06:35 +01:00
Evgeny Poberezkin
6eb76315eb ios: NSE debug logging 2022-07-06 15:22:01 +01:00
Evgeny Poberezkin
eb35d81aba ios: make qr code pages scrollable (for small screens) (#784) 2022-07-06 15:01:41 +01:00
Evgeny Poberezkin
fb54841d76 update simplexmq (suspendAgent without delay) 2022-07-06 14:17:50 +01:00
Evgeny Poberezkin
95f518a582 ios: stopped state for DB management, suspend quicker/instantly on app termination (#783)
* ios: stopped state for DB management, suspend quicker/instantly on app termination

* update terminateChat
2022-07-06 14:07:27 +01:00
Evgeny Poberezkin
6b89eb872b mobile: update webrtc ICE servers (#782)
* mobile: update webrtc ICE servers

* update webrtc package version
2022-07-06 11:52:25 +01:00
Evgeny Poberezkin
ab6301c3e9 ios: notification preview mode, show connection entity notification (#781)
* ios: notification preview mode, show connection entity notification

* prepare connection entity notification as best attempt
2022-07-06 11:52:10 +01:00
JRoberts
36dc66d5d5 core: use NTF scheme for notification server address (Terminal.hs) 2022-07-06 11:48:38 +04:00
Evgeny Poberezkin
111acb0813 core: use NTF scheme for notification server address (#774)
* core: use NTF scheme for notification server address

* simplexmq
2022-07-06 08:46:04 +01:00
JRoberts
65fae747c3 android: make calls non expirmental feature, hide experimental features (#779) 2022-07-05 15:27:42 +04:00
JRoberts
935d5bfdd6 android: use RcvCallInvitation type in CallInvitation event (#778) 2022-07-05 15:25:29 +04:00
JRoberts
f7a27ff91b ios: make calls non expirmental feature, hide experimental features (#777) 2022-07-05 15:24:51 +04:00
JRoberts
ab848e8c13 ios: refresh call invitations and report call on start and activation; core: restore calls on activation (#776) 2022-07-05 15:15:15 +04:00
JRoberts
8c307c4675 Merge pull request #753 from simplex-chat/ios-notifications
iOS notifications
2022-07-04 16:27:53 +04:00
Evgeny Poberezkin
c323d6c61f ios: update version v3.0 (55) 2022-07-04 11:53:15 +01:00
Evgeny Poberezkin
03ab4612a5 ios: text correction 2022-07-04 11:46:56 +01:00
Evgeny Poberezkin
687e3be9ac iOS: update call invitations when exiting background (#771)
* core: communicate call invitations state between NSE and app via db

* enable tests

* delete calls, encoding

* load calls on start

* remove line

* remove table alias

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-07-04 14:15:25 +04:00
Evgeny Poberezkin
2c121b5731 ios: choose notifications mode during onboarding and after DB migration (#773) 2022-07-03 19:53:07 +01:00
Evgeny Poberezkin
c619092464 update simplexmq 2022-07-03 13:07:42 +01:00
Evgeny Poberezkin
e4c6d210c6 ios: fix updating chats when exiting background (#772) 2022-07-02 17:18:45 +01:00
Evgeny Poberezkin
60642317d0 ios: update version 3.0 (54) 2022-07-02 15:35:51 +01:00
JRoberts
01b9c16f1a update simplexmq (notify on errors) 2022-07-02 13:35:57 +04:00
Evgeny Poberezkin
15a17f3c13 core: subscribe to all connections concurrently (#770) 2022-07-02 10:13:06 +01:00
JRoberts
3450420b80 core: pass ERR responses to view (#768) 2022-07-02 12:35:04 +04:00
Evgeny Poberezkin
29c6d51e6a ios: fix background refresh (#769)
* ios: fix background refresh

* change app inactive check
2022-07-02 08:50:25 +01:00
Evgeny Poberezkin
a8ba4ede82 update simplexmq 2022-07-01 23:12:45 +01:00
Evgeny Poberezkin
f7f3f82090 ios: fix migration, refreshing chat list; disable periodic notifications (#767)
* ios: fix migration, disable refreshing chat list and periodic notifications

* fix refreshing chats when exiting background

* remove unused model property
2022-07-01 22:45:58 +01:00
Evgeny Poberezkin
a8a0f2db03 ios: disable notifications if not migrated (#766)
* ios: disable notifications if not migrated

* refactor, update text
2022-07-01 20:33:20 +01:00
JRoberts
e68cc23828 update simplexmq (v3.0.0-beta.2) (#765) 2022-07-01 16:12:29 +04:00
Evgeny Poberezkin
1e63eb3752 ios: update the list of chats when exiting background (#764) 2022-07-01 10:38:12 +01:00
Evgeny Poberezkin
96866c7a5d core: handle all DB errors (#763) 2022-07-01 10:37:26 +01:00
Evgeny Poberezkin
815981487b ios: register notification token/mode on app start (#761)
* ios: register notification token/mode on app start

* refactor

* register token on start

* update model on main thread
2022-07-01 09:49:30 +01:00
JRoberts
b2c455c301 update simplexmq (recover) (#762) 2022-06-30 20:35:28 +04:00
Evgeny Poberezkin
ca366d0b47 core: fix APINtfGetToken parsing (#760) 2022-06-30 08:16:22 +01:00
JRoberts
904945a67d fix simplexmq range 2022-06-29 17:35:59 +04:00
JRoberts
c8a85f651d update simplexmq (v3.0.0-beta.0) 2022-06-29 17:23:42 +04:00
Evgeny Poberezkin
13603f009b update simplexmq (fixes v2 connecting to v1 contact link) 2022-06-29 09:04:53 +01:00
Evgeny Poberezkin
043005d186 update simplexmq 2022-06-28 20:14:39 +01:00
Evgeny Poberezkin
785fab1653 ios: remove interval notifications method (#759) 2022-06-28 19:39:00 +01:00
Evgeny Poberezkin
7226e5d37a ios: notifications UI (#758)
* ios: notifications UI

* Apply suggestions from code review

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-06-28 19:03:39 +01:00
JRoberts
e8c9f6d5ab core: use domain name in ntf server address (#757) 2022-06-28 16:50:40 +04:00
Evgeny Poberezkin
54126eba6b APNS push environments (#756) 2022-06-27 23:03:27 +01:00
Evgeny Poberezkin
41c9742b0d core: auto-reply message for user contact addresses (#755)
* core: auto-reply message for user contact addresses

* terminal: show auto accept status and message

* test
2022-06-27 19:41:25 +01:00
Evgeny Poberezkin
6d25991417 ios: process notifications, suspend app, notifications settings UI (#754) 2022-06-27 10:28:30 +01:00
Evgeny Poberezkin
463f644bce core: change API to suspend agent (#752) 2022-06-26 15:04:44 +01:00
Evgeny Poberezkin
5367ffe418 core: update api to get/register tokens (#751) 2022-06-25 17:02:16 +01:00
Evgeny Poberezkin
313bc65457 core: start NSE without subscriptions, update simplexmq (fix agent phase) (#750) 2022-06-25 11:49:46 +01:00
Evgeny Poberezkin
4e979aee7e core: update simplexmq 2022-06-24 15:28:38 +01:00
Evgeny Poberezkin
6a2f2a512f ios: UI to export/import/delete chat database (#743)
* ios: UI to export/import/delete chat database

* move files

* ui for database migration

* migration screen layout

* ios: export archive and delete chat database

* import archive

* refactor, update texts

* database migration (almost works)

* fix missing import

* delete legacy database

* update migration errors
2022-06-24 13:52:20 +01:00
Evgeny Poberezkin
4d9e446489 core: set files folder without user (to allow archive import) (#748) 2022-06-23 21:20:56 +01:00
Evgeny Poberezkin
8d93f228b3 update simplexmq 2022-06-21 19:31:20 +01:00
Evgeny Poberezkin
af7d7e8303 Merge branch 'master' into ios-notifications 2022-06-21 19:30:11 +01:00
Evgeny Poberezkin
976b1c919f revert tls to 1.5.7 and nix config changes (#746)
* Revert "nix: update nix for new tls version (#744)"

This reverts commit 7df8c23c81.

* Revert "update flake.nix"

This reverts commit 4b263510ee.

* Revert "update tls"

This reverts commit 2e34ae3b1c.

* update simplexmq
2022-06-21 19:27:30 +01:00
Evgeny Poberezkin
29eafa9a74 update ntf server 2022-06-21 11:38:36 +01:00
Evgeny Poberezkin
7723e4ca7a core: allow starting chat without making SMP subscriptions (to use GET in NSE) (#745) 2022-06-21 11:25:12 +01:00
Evgeny Poberezkin
051726702b Merge branch 'master' into ios-notifications 2022-06-20 12:35:37 +01:00
Evgeny Poberezkin
7df8c23c81 nix: update nix for new tls version (#744) 2022-06-20 12:34:41 +01:00
Evgeny Poberezkin
a362fc734e Merge branch 'master' into ios-notifications 2022-06-19 21:49:08 +01:00
Evgeny Poberezkin
4b263510ee update flake.nix 2022-06-19 21:48:33 +01:00
Evgeny Poberezkin
291096d87f ios: receive message in NSE (#742) 2022-06-19 19:49:39 +01:00
Evgeny Poberezkin
c5c65f813b Merge branch 'master' into ios-notifications 2022-06-19 19:45:13 +01:00
Evgeny Poberezkin
2e34ae3b1c update tls 2022-06-19 19:44:57 +01:00
Evgeny Poberezkin
8432399458 update simplexmq 2022-06-19 19:26:47 +01:00
Evgeny Poberezkin
59ad220d93 Merge branch 'master' into ios-notifications 2022-06-19 19:24:48 +01:00
Evgeny Poberezkin
65369aa47b update simplexmq 2022-06-19 19:22:54 +01:00
Evgeny Poberezkin
60e9ed9476 core: api to get notification messages and set app phase (#741)
* core: api to get notification messages and set app phase

* update simplexmq

* update simplexmq
2022-06-19 14:44:13 +01:00
Evgeny Poberezkin
4bf5125c51 core: support combining store functions in one transaction (#740)
* refactor store functions (WIP - does not compile yet)

* update chat

* update simplexmq
2022-06-18 20:06:13 +01:00
Evgeny Poberezkin
b2a523c3fe core: fix dependencies, update nix (#739)
* core: fix dependencies, update nix

* update
2022-06-16 20:55:45 +01:00
Evgeny Poberezkin
2ae621792e update simplexmq 2022-06-16 20:04:21 +01:00
Evgeny Poberezkin
c62d99ab97 core: remove connection pool (#738)
* core: remove connection pool

* remove local ref from cabal.project

* update simplexmq

* log test

* fix test
2022-06-16 20:00:51 +01:00
Evgeny Poberezkin
1f1ed3f3dd core: remove dependency on zip algorithms bzip2/zstd (#737)
* core: remove dependency on zip algorithms bzip2/zstd

* enable test log

* disable test log
2022-06-12 11:28:30 +01:00
Evgeny Poberezkin
6f195c4167 core add chat_recv_msg_wait and chat_parse_markdown to published API (#736) 2022-06-11 11:52:55 +01:00
Evgeny Poberezkin
235fb8dc0c remove missing libs 2022-06-09 15:18:12 +01:00
Evgeny Poberezkin
44a9ea2102 Merge branch 'master' into ios-notifications 2022-06-09 15:16:21 +01:00
Evgeny Poberezkin
121541759b ios: update library 2022-06-09 14:52:55 +01:00
Evgeny Poberezkin
716a941dc6 core: use duplex handshake (agent v2) (#735)
* core: use duplex handshake (agent v2)

* version test matrix

* update simplexmq
2022-06-09 14:52:12 +01:00
Evgeny Poberezkin
16bd9ccc4f core: send SMP notification msg flag based on chat message (#733)
* core: send SMP notification msg flag based on chat message

* update simplexmq

* remove unnecessary condition

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-06-07 14:14:54 +01:00
Evgeny Poberezkin
33e702d453 Merge branch 'master' into ios-notifications 2022-06-06 22:04:05 +01:00
Milton
883bf768af fix typo (#732)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-06-06 16:24:33 +01:00
Evgeny Poberezkin
f341e54128 Export & import storage archive (#726)
* core: import and export of chat archive

* export chat archive

* import archive, support starting chat after it is stopped

* test for maintenance mode

* test/fix archive with files

* prevent starting chat after chat database was deleted or imported

* update simplexmq
2022-06-06 16:23:47 +01:00
Evgeny Poberezkin
b3f4645011 Merge pull request #730 from simplex-chat/master
docs: contents, faq (#729)
2022-06-04 16:35:52 +01:00
Evgeny Poberezkin
99bd3f6133 docs: contents, faq (#729)
* docs: contents, faq

* add app links to the bottom
2022-06-04 16:33:58 +01:00
Evgeny Poberezkin
7590502f29 Merge branch 'master' into ios-notifications 2022-06-04 15:08:31 +01:00
Evgeny Poberezkin
f770a8396e Merge pull request #727 from simplex-chat/master
merge v2.2.1 to stable
2022-06-04 15:06:04 +01:00
Evgeny Poberezkin
b0f3d59cb0 blog: v2.2 release (#728)
* blog: v2.2 release

* update readme

* update roadmap
2022-06-04 14:37:41 +01:00
Evgeny Poberezkin
cab5bc2daf Merge branch 'master' into ios-notifications 2022-06-03 13:40:03 +01:00
JRoberts
935c3987b3 update version v2.2.1 (53) 2022-06-03 16:18:34 +04:00
Evgeny Poberezkin
084d1d09a5 ios: fix closing chat info (#725) 2022-06-03 16:05:34 +04:00
Evgeny Poberezkin
47ec486201 update version v2.2.1 (52) 2022-06-03 12:46:11 +01:00
Evgeny Poberezkin
72103949a7 ios: fix purple warning on auth failure (#724)
* ios: fix purple warning on auth failure

* avoid showing chats

* avoid flicker

* fix exit

* bg task

* rename function

* remove bg task
2022-06-03 12:24:50 +01:00
JRoberts
3b708105a4 ios: fix modal views not closing (#723) 2022-06-03 13:19:41 +04:00
Evgeny Poberezkin
800efb3a34 ios: fix authentication (#722)
* ios: fix authentication

* Update apps/ios/Shared/ContentView.swift

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* remove doAuthenticate = false

* remove lock button

* moare fixos

* whitespace

* and more

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-06-03 09:16:07 +01:00
Evgeny Poberezkin
949fb17406 ios: mach messages to coordinate database acceess between app & NSE (#717) 2022-06-02 13:16:22 +01:00
Evgeny Poberezkin
b435c0145f Merge branch 'master' into ios-notifications 2022-06-02 12:39:51 +01:00
Evgeny Poberezkin
87c0c9de91 ios: update build to 51 2022-06-02 12:37:27 +01:00
Evgeny Poberezkin
cd1af400bb ios: remove callkit (#720)
* ios: remove callkit

* remove CallKit import
2022-06-02 12:10:41 +01:00
JRoberts
e1e161539d Merge pull request #718 from simplex-chat/master (version 2.2.0) 2022-06-01 19:11:01 +04:00
JRoberts
4db7e88ed8 terminal: version 2.2.0 2022-06-01 18:58:06 +04:00
JRoberts
82a4a8c6f8 mobile: update version 2.2 (ios - 50, android - 36) 2022-05-31 21:30:20 +04:00
JRoberts
15ddefe86b mobile: close modal views (#715) 2022-05-31 20:55:19 +04:00
Evgeny Poberezkin
fa844c48e9 ios: SimpleXChat framework to be shared by app/NSE (#714)
* ios: SimpleXChat framework to be shared by app/NSE

* remove bridging headers from pp/NSE

* embed & sign
2022-05-31 07:55:13 +01:00
JRoberts
7e96da95f9 ios: enable notifications (#713) 2022-05-30 16:15:17 +04:00
Evgeny Poberezkin
0bb5774ff1 mobile: update version 2.2 (ios - 49, android - 35) 2022-05-30 12:58:09 +01:00
Evgeny Poberezkin
866d84e7ac mobile: move calls to experimental features, refactor (#712) 2022-05-30 12:32:11 +01:00
Evgeny Poberezkin
d6262bc2a4 ios: move files 2022-05-30 09:12:57 +01:00
Evgeny Poberezkin
e5909d4e12 ios: SMP servers settings page layout (#711) 2022-05-30 09:05:02 +01:00
Evgeny Poberezkin
23b75f11fe ios: paste image (#710) 2022-05-30 09:03:56 +01:00
Evgeny Poberezkin
71fa2bfec0 android: settings sections (#709) 2022-05-30 09:03:41 +01:00
Evgeny Poberezkin
29e2c00811 mobile: settings for auto-accepting images, link previews, spinner for link previews; privacy settings (#708)
* ios: settings for auto-accepting images, link previews, spinner for link previews

* android: settings for auto-accepting images, link previews, spinner for link previews, privacy settings

* update translation

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* translation

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* translation

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-05-30 08:59:04 +01:00
Evgeny Poberezkin
7c1d573a17 mobile: show skipped messages in the UI (#707)
* mobile: show skipped messages in the UI

* ios: skipped messages alert and translations

* android: skipped messages alert

* android translation keys

* more keys

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-05-29 08:06:56 +01:00
Evgeny Poberezkin
89908ef5dc core: chat item on skipped messages (#705)
* core: chat item integrity

* create chat item on skipped messages (but only on content items)

* report skipped messages on all messages, not only content messages

* remove type signature

* remove migration

* update rfc
2022-05-28 19:13:07 +01:00
JRoberts
c3c712aa02 ios: show local authentication notice; ios & android: retry authentication button (#706)
* advertisement

* refactor

* advertisement state machine

* simplify

* ios: retry

* remove log

* android: retry

* Update apps/ios/Shared/ContentView.swift

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* Update apps/ios/Shared/Views/UserSettings/SettingsView.swift

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-05-28 22:09:46 +04:00
Evgeny Poberezkin
b56ad77502 core: mark accepted and rejected call items read (#704) 2022-05-28 12:34:40 +01:00
JRoberts
5e476516cb ios: lock toggle; android: fix lock timer (#702) 2022-05-28 14:58:52 +04:00
Evgeny Poberezkin
ce2f3c0371 mobile: timeout call invitations, more android options (#703)
* mobile: timeout call invitations, more android options

* close overlays when call is accepted via notification

* show incoming call above modals, dismiss modals when call is accepted

* fix clickable area of create profile button

* fix pending intent for rullscreen notification, update settings
2022-05-28 09:06:38 +01:00
Evgeny Poberezkin
da13e6614b mobile: call settings, request camera on iOS on call start (#701)
* mobile: call settings, request camera on iOS on call start

* refactor preferences

* fix typo
2022-05-27 16:36:33 +01:00
JRoberts
79d9e90ab7 mobile: local authentication (#696)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-05-27 18:21:35 +04:00
Evgeny Poberezkin
387aec8593 android: webrtc calls notification and alert (#699)
* android: webrtc calls notification and alert

* add ringtone to incoming call

* incoming call on full screen

* enable notification ringtone

* remove text

* use translated strings in call notification
2022-05-27 08:43:15 +01:00
Evgeny Poberezkin
a403f2051a core: add timestamp to call invitation (#700) 2022-05-27 09:30:01 +04:00
Evgeny Poberezkin
9e83b54b85 android: update colors (#698)
* android: update colors

* update color
2022-05-25 14:28:04 +01:00
Evgeny Poberezkin
2696086faa update webrtc npm package v0.0.5 2022-05-25 09:13:14 +01:00
Evgeny Poberezkin
546ad01fcb ios: integrating webrtc calls with callkit (#686)
* ios: integrating webrtc calls with callkit

* accept call via chat item (e.g. when DND is on, and callkit blocks the call); refactor

* fix remote video, support logging from ios

* use callkit depending on CallController setting

* call sound

* update incoming call view

* fixing audio encryption

* refactor encryption webrtc fix

* log ontrack success/error

* accept / ignore call via notification

* remove unused imports

* remove unused file

* remove comments
2022-05-24 19:34:27 +01:00
JRoberts
247e7f1ea7 Merge pull request #695 from simplex-chat/master (android version 2.1.1) 2022-05-24 09:47:42 +04:00
Evgeny Poberezkin
0290a687af android: version 2.1.1 (34) 2022-05-23 18:59:01 +01:00
Evgeny Poberezkin
97cab8b542 blog: v21 post (#693)
* blog post v2.1

* update images

* add image

* correction

* correction
2022-05-23 18:39:03 +01:00
Evgeny Poberezkin
4a42797d83 android: fix notifications to open correct chat (#692)
* android: fix notifications to open correct chat

* remove optimization
2022-05-23 18:33:28 +01:00
Evgeny Poberezkin
3051732622 android: remove notification when chat marked as read from the context menu (#691)
* android: remove notification when chat marked as read from the context menu

* remove chat notifications when chat is cleared or deleted
2022-05-23 17:49:58 +01:00
JRoberts
3fe005e252 Merge pull request #690 from simplex-chat/master (version 2.1.0, ios 48/android 33) 2022-05-23 15:33:09 +04:00
JRoberts
79dcada757 android: version 2.1.0 (33) 2022-05-23 13:56:08 +04:00
JRoberts
f7eeb4d1e3 android: fix compose view clearing state prematurely (#689) 2022-05-23 13:44:49 +04:00
JRoberts
d572cfbc09 ios: version 2.1.0 (48) 2022-05-23 10:38:37 +04:00
JRoberts
cb95c51fe1 ios: hide CallViewDebug 2022-05-23 10:24:14 +04:00
JRoberts
e057f9e407 Merge pull request #685 from simplex-chat/master (version 2.1.0) 2022-05-21 21:44:33 +04:00
JRoberts
6333a60103 android: version 2.1.0 (32) 2022-05-21 21:16:25 +04:00
JRoberts
3a539aec5b ios: version 2.1.0 (47) 2022-05-21 20:56:01 +04:00
JRoberts
40be12430e terminal: version 2.1.0 (amended) 2022-05-21 20:23:34 +04:00
JRoberts
53d66be910 mobile: disable calls (#683) 2022-05-21 20:14:43 +04:00
JRoberts
d6699ffb03 mobile: translations (#682)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-05-21 19:43:04 +04:00
JRoberts
6ad2eeec89 terminal: add /image command to send file as image (#681)
* terminal: add /image command sending file as image

* terminal: forward file commands

* terminal: update help

* terminal: update placeholder image

* Update src/Simplex/Chat/Help.hs

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* help

* allow forwarding sent file

* typo

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-05-21 15:17:15 +01:00
Evgeny Poberezkin
d971e7c31f mobile: call chat items (#680)
* mobile: call chat items

* android: call chat items
2022-05-21 12:13:37 +01:00
JRoberts
d2d8498258 android: show progress indicator on chat preview when connecting to server (#679)
* android: show progress indicator on chat preview when connecting to server

* move to right

* remove unused imports

* remove column
2022-05-21 12:13:04 +01:00
JRoberts
c1f67c08f7 android: close chat link dropdown menu when chat becomes unread and "mark read" menu item becomes available (#678) 2022-05-20 20:40:41 +04:00
JRoberts
70f78e7984 android: hide "mark read" menu item if chat is read (#677) 2022-05-20 20:24:35 +04:00
JRoberts
7f84057b86 android: add "mark read" action to chat link dropdown menu (#675)
* android: add "mark read" action to chat link dropdown menu

* Update apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-05-20 18:32:21 +04:00
JRoberts
718c8f826a android: fix connection statuses being reset when app comes back from background (closes #644) (#673) 2022-05-20 16:17:29 +04:00
Evgeny Poberezkin
e13cb6e2fd android: update gradle plugin to 7.2.0 (#674) 2022-05-20 12:56:27 +01:00
Evgeny Poberezkin
cb529e3202 Merge branch 'delete-profiles' into stable 2022-05-20 12:03:27 +01:00
Evgeny Poberezkin
b3e67efba0 mobile: fix statusbar in ios webrtc call, move PIP to top 2022-05-20 11:12:04 +01:00
JRoberts
3f2ca8f902 mobile: use updated_at in chat previews (#671)
* mobile: use updated_at in chat previews

* use updated_at returned from chat response

* refactor

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-05-20 12:00:58 +04:00
Evgeny Poberezkin
9c9f6d8443 webrtc: show connection information, mark call as missed if it ends while pending (#672) 2022-05-20 07:43:44 +01:00
JRoberts
d50ebbd061 core: mark cleared chat as updated to keep it in the same place on restart (#670) 2022-05-19 21:57:31 +04:00
Evgeny Poberezkin
6cc4323571 webrtc: call overlays for ios/android, support for flipping camera (#669) 2022-05-19 14:33:02 +01:00
JRoberts
5cddf8e2d3 mobile: clear conversation from chat info view (#668) 2022-05-19 16:56:34 +04:00
JRoberts
2c2ab98105 android: more long press interactions on ChatListNavLinkView (#667) 2022-05-19 13:37:12 +04:00
JRoberts
5fd75ee286 terminal: version 2.1.0 2022-05-19 09:21:37 +04:00
Evgeny Poberezkin
0a134e2ded terminal: update chat help (#666) 2022-05-19 09:18:38 +04:00
Evgeny Poberezkin
e4a66c767c core: fix webrtc state machine (#665) 2022-05-18 18:46:45 +01:00
JRoberts
e4b1ff5e0f android: fix race condition on taking photo resulting in null URI; ios: use fullScreenCover for camera (closes #662) (#663) 2022-05-18 21:32:30 +04:00
Evgeny Poberezkin
5f67c450b1 mobile: webrtc calls fix encryption status, translate alerts, report connection stats on connection (#664)
* mobile: webrtc calls fix encryption status, translate alerts, report connection stats on connection

* refactor, remove logger, make property into getter
2022-05-18 17:20:43 +01:00
Evgeny Poberezkin
dc418923ac mobile: webrtc calls (#661)
* mobile: webrtc calls work on Android and iOS separately

* core: only send public key with offer if invitation/current call supports e2ee

* update npm module
2022-05-18 10:01:32 +04:00
JRoberts
106dceabfc mobile: clear chat; allow to delete items deleted by sender (#660)
* ios: clear chat

* android: clear chat

* fix chat stats

* fixes

* check if deleted

* delete from files for groups

* android - fixes

* Update apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* Update apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-05-17 22:48:54 +04:00
Evgeny Poberezkin
295cec7c53 terminal: support to connect via webrtc (#659)
* terminal: support to connect via webrtc

* npm package

* update webrtc npm package

* remove console.log

* fix test
2022-05-17 08:37:00 +01:00
JRoberts
84bf815e5c core: fully delete direct chat items; clear chat API (#658) 2022-05-17 11:22:09 +04:00
Evgeny Poberezkin
82445ec8d5 android: refactor webrtc calls, compress webrtc session info, make compatible with Safari (with flag) (#642)
* use simplex.chat relay

* update webrtc settings

* WebRTCView to use command/response types

* compress WebRTC session descriptions, simple web UI for calls

* update webrtc ui

* use webworked in desktop browser

* use RTCRtpScriptTransform in safari

* update android type

* refactor

* add await
2022-05-16 19:27:58 +01:00
Evgeny Poberezkin
36ef6df9fb android: notification service and battery optimization on Android 12 (#656)
* Managing battery optimization on Android 12+ via in-app UI                                                                                                                                              
- in case of battery optimization is enabled a user will be asked to disable it if he wants to have a background service                                                                                
- when the service is enabled but the user don't want to disable the battery optimization, the service will be disabled with an alert for the user

* update service notice conditions

* android: update notification service logic

* update translations

Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
2022-05-16 07:59:41 +01:00
JRoberts
c000a1b924 docs: fix links in CLI.md (#655) 2022-05-14 21:00:54 +04:00
JRoberts
9bf7821444 core: delete profiles (#654) 2022-05-14 21:00:46 +04:00
JRoberts
03eaa94324 test: delay after mobile tests to fix SQLite IO error on Mac (#653) 2022-05-14 20:10:46 +04:00
Evgeny Poberezkin
7ad7f4f91a mobile: zoomable fullscreen image view (#651)
* ios: zoomable fullscreen image view

* android: zoomable images
2022-05-14 08:07:13 +01:00
JRoberts
8d53c569c7 core: fix createOrUpdateContactRequest logic (#650)
* core: fix createOrUpdateContactRequest logic

* remove do

* fix logic

* rename

* refactor

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-05-13 21:57:24 +01:00
Evgeny Poberezkin
b7860ad0e8 simplex-chat server & JavaScript/TypeScript SDK/client (#539)
* simplex-chat server

* typescript types for chat commands and command serialization

* typescript ChatResponse type

* more types

* more types

* websocket chat client

* aligb ts/haskell types

* chat server & TS client via websockets - it works

* TS chat client test

* TS chat client test

* update test

* more api functions

* more api methods, refactor, readme

* squaring chat bot example, fixes

* update readme

* remove console.log

* npm version 0.1.0
2022-05-13 19:44:03 +01:00
JRoberts
9f5ea49676 mobile: show markdown in quotes and context items; ios: fix markdown help (#649) 2022-05-13 12:57:30 +04:00
Evgeny Poberezkin
c1eed47463 core: mark chat as read without specifying the items range, closes #647 (#648)
* core: mark chat as read without specifying the items range, closes #647

* moar tests
2022-05-13 09:38:14 +01:00
JRoberts
91a0283a36 Merge pull request #646 from simplex-chat/master (version 2.0.1) 2022-05-13 10:07:48 +04:00
JRoberts
da793bbf4f android: version 2.0.1 (31) 2022-05-13 09:32:34 +04:00
JRoberts
e958e45652 terminal: version 2.0.1 2022-05-12 19:03:02 +04:00
JRoberts
66284a954b ios: version 2.0.1 (46) 2022-05-12 18:51:05 +04:00
Evgeny Poberezkin
6aebb93f7f mobile: prevent adding spaces to display name (#643)
* mobile: prevent adding spaces to display name

* android: save enabled

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-05-12 18:07:28 +04:00
JRoberts
53e330dac9 core: add missing status transitions for group file transfer; fix group file delivery race condition (#640) 2022-05-12 17:37:09 +04:00
JRoberts
e174c43bec android: fix compose/send message not resetting some state (#639) 2022-05-12 11:08:14 +04:00
Evgeny Poberezkin
18c3f49f96 Merge branch 'master' into stable 2022-05-11 18:36:26 +01:00
Evgeny Poberezkin
aa9c2f3228 fix links 2022-05-11 18:35:55 +01:00
Evgeny Poberezkin
1c5e6f52ec update readme links 2022-05-11 18:34:50 +01:00
JRoberts
1a653649ec Merge pull request #635 from simplex-chat/master (version 2) 2022-05-11 20:56:56 +04:00
Evgeny Poberezkin
50f06a3c55 blog: v2.0 images and files (#632)
* blog: v2.0 images and files

* update post

* update

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* update

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-05-11 17:20:22 +01:00
JRoberts
81ebf1b696 terminal: version 2.0.0 (#634)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-05-11 20:15:09 +04:00
Evgeny Poberezkin
885a4ea972 terminal: change default servers (#633) 2022-05-11 19:52:08 +04:00
JRoberts
0262ab53bf core: improve file cancel (#627)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-05-11 16:18:28 +04:00
Evgeny Poberezkin
89ea57e4b6 android: fix picking file from gallery (#631) 2022-05-10 16:15:11 +01:00
Evgeny Poberezkin
3d8ccdaa9f android: fix bug in chat list (#630) 2022-05-10 15:00:59 +01:00
Evgeny Poberezkin
058c0f5895 mobile: ios v2.0 (45), android (28) 2022-05-10 11:07:00 +01:00
Evgeny Poberezkin
1027bf923f mobile: update connect action icons 2022-05-10 09:57:37 +01:00
JRoberts
01467769bf mobile: remove icons on received files (#629) 2022-05-10 12:15:46 +04:00
Evgeny Poberezkin
3cd8f8f7dd android: version 2.0 (27) 2022-05-10 08:33:53 +01:00
Evgeny Poberezkin
05283d58b0 ios: version 2.0 (44) 2022-05-10 08:21:09 +01:00
Evgeny Poberezkin
c0528baba7 ios: show spinners when connecting contacts (#628)
* ios: show spinners when connecting contacts

* move status to the corner
2022-05-10 08:04:18 +01:00
Evgeny Poberezkin
412982cc01 android: onboarding (#624)
* android: onboarding views

* create profile

* creating profile works

* make connection view

* onboarding layout

* add translations
2022-05-10 08:03:43 +01:00
Evgeny Poberezkin
69e21781df ios: update libs, fix background refresh crash, remove NSE from app, v2.0 (43) (#626)
* ios: update libs, fix background refresh crash, remove NSE from app, v2.0 (43)

* remove geometryreader

* update translations

* update translations
2022-05-09 17:40:39 +01:00
JRoberts
4dcf1f8d15 test: add missing fields to ServerConfig 2022-05-09 18:53:39 +04:00
JRoberts
ee3d7ae97e core: update simplexmq hash (v2.0.0) (#625) 2022-05-09 16:24:24 +04:00
Evgeny Poberezkin
a3e6582a64 ios: update version 2.0 (42) 2022-05-09 13:17:30 +01:00
Evgeny Poberezkin
3e4826395e ios: fix onboarding info layout for small screen (#623)
* ios: fix onboarding info layout for small screen

* update make connection onboarding view
2022-05-09 13:13:53 +01:00
JRoberts
95c79c1b5c ios: version 2.0 (41) 2022-05-09 14:38:18 +04:00
JRoberts
c61707a358 android: save images to Gallery (#622) 2022-05-09 14:09:10 +04:00
Evgeny Poberezkin
5e9d72d309 ios: translations 2022-05-09 10:28:16 +01:00
Evgeny Poberezkin
dcaefd6566 mobile: onboarding (#618)
* mobile: onboarding

* ios onboarding: create profile and make connection

* how SimpleX works

* connect via link

* remove separate view for connecting via link, fix bugs

* remove unused files

* fix help on small screens, update how it works page

* layout

* add About to settings, tidy up

* rename function

* update layout

* translations

* translation corrections

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* correction

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* corrections

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* fix translations/layout

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-05-09 09:52:09 +01:00
Evgeny Poberezkin
3d2315a117 ios: update icons for edit/quote messages 2022-05-09 09:12:32 +01:00
JRoberts
1dc3190159 remove debug logs on deletion; android: context menu on contact link (#621) 2022-05-09 10:55:56 +04:00
JRoberts
ca4d1910db split delete transaction (#620) 2022-05-08 22:03:45 +04:00
Evgeny Poberezkin
58b37bf114 mobile: add v2 to "choose file" label (#616)
* ios: add v2 to "choose file" label

* android: add v2 to "choose file" label

* fix translations
2022-05-07 21:09:57 +01:00
JRoberts
8773a058bf delete from files; delete from chat_items 2022-05-07 21:25:29 +04:00
Evgeny Poberezkin
1530d1e12b mobile: translations (#615)
* mobile: translations

* change some android translations

* corrections

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* correction

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* correction

* picture

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* import updated

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-05-07 17:29:58 +01:00
JRoberts
2c61358cb9 add logs to deleteContact 2022-05-07 19:33:47 +04:00
Evgeny Poberezkin
b1024be74d mobile: hide notifications and webrtc calls (#612)
* mobile: hide notifications and webrtc calls

* android: remove webrtc link
2022-05-07 16:10:57 +01:00
JRoberts
b3641bdf83 fix deletion of contact with files wip (#614) 2022-05-07 18:24:38 +04:00
Evgeny Poberezkin
c4f4cd85c4 android: fix quotes with images and files (#611) 2022-05-07 14:00:17 +01:00
JRoberts
235bce8e2a android: remove unnecessary READ_EXTERNAL_STORAGE permission request, open image picker in Gallery; IMG timestamp format (#610) 2022-05-07 16:25:04 +04:00
Evgeny Poberezkin
fcb5c69281 android: types and messages for webrtc calls (#609)
* android: webrtc calls

* string localizations, more types
2022-05-07 13:23:20 +01:00
Evgeny Poberezkin
29990765e7 mobile: webrtc calls state machine (#606)
* mobile: webrtc calls state machine

* android: call api types

* android: call api methods

* ios: connect calls via chat UI (WIP)

* ios: webrtc call connects via UI

* core: update call duration/status when x.call.end is received

* improve call UX/UI

* audio calls

* different overlay for audio calls

* toggle video/audio in the call
2022-05-07 09:40:46 +04:00
JRoberts
884231369f mobile: files UI (#597)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-05-06 21:10:32 +04:00
Evgeny Poberezkin
e575e97019 core: update _send api, remove _send_quote, use new api in tests (#608) 2022-05-06 09:17:49 +01:00
JRoberts
9d47c8a3d4 core: use filename in quote when there is no message text (MCText) (#607)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-05-06 12:04:53 +04:00
Evgeny Poberezkin
c8aa8db973 mobile: switch to send_v2 in ios, add to android (#605) 2022-05-05 15:44:48 +01:00
Evgeny Poberezkin
e80f617840 core, ios: send_v2 api to send messages as JSON (to support filenames with spaces) (#604) 2022-05-05 17:04:03 +04:00
Evgeny Poberezkin
7928cdbfb8 core: update sent file status for direct chats when sending complete (#603)
* core: update sent file status for direct chats when sending complete

* update snd file status when started and completed
2022-05-05 16:50:19 +04:00
Evgeny Poberezkin
dbf6b1f673 core: use filename in quote when there is no message text (#602) 2022-05-05 14:52:32 +04:00
Evgeny Poberezkin
cf04a9fed3 add AChatItem to snd file events (#601) 2022-05-05 13:37:53 +04:00
Evgeny Poberezkin
76a9b5b8d4 Merge pull request #591 from simplex-chat/webrtc-calls
WebRTC calls
2022-05-05 08:31:33 +01:00
Evgeny Poberezkin
80c5a151a2 core: fix mac tests (#600) 2022-05-05 07:37:33 +01:00
Evgeny Poberezkin
96b514af45 core: update webrtc types (#599) 2022-05-04 23:32:46 +01:00
IanRDavies
f2036236f6 ios: webrtc calls started (#594)
* building towards ios calls

* minor ios changes

* linting

* RPC calls with webview

* tidy up

* more types

* ios: webrtc types, call works

* remove comment

* prettier

* add prettier to build

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-05-04 23:07:26 +01:00
Evgeny Poberezkin
3e19e495de Merge branch 'master' into webrtc-calls 2022-05-04 13:39:40 +01:00
Evgeny Poberezkin
1ddd17839b core: calls api - support multiple calls, process status updates from webview, refactor, tests (#595)
* core: tests for call api (WIP, test fails)

* fix test

* add APICallStatus, tests

* update call status based on webview events, refactor
2022-05-04 13:31:00 +01:00
JRoberts
70ea803a49 ios: move image utils to FileUtils (#598) 2022-05-04 16:08:40 +04:00
JRoberts
2c1ad9a641 mobile: support receiving files (#584)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-05-04 09:10:36 +04:00
JRoberts
260a758b82 test: fix race condition caused by small file transfer (sender cancelled) (#596) 2022-05-04 09:09:59 +04:00
Evgeny Poberezkin
8e002eed1c Merge branch 'master' into webrtc-calls 2022-05-03 10:57:00 +01:00
Evgeny Poberezkin
20d253ea35 core: webrtc calls API implementation (#593)
* core: webrtc calls API implementation

* process call messages, send events to the UI
2022-05-03 10:22:35 +01:00
Evgeny Poberezkin
3519032784 ios: notifications service extension - refactor model and API (#578)
* ios: notifications service extension

* create notifications in NSE (WIP)

* refactor notifications to use in NSE

* prepend team ID to shared defaults name to silence the warning

* remove whitespace
2022-05-03 08:20:19 +01:00
Evgeny Poberezkin
cdb919db96 core: webrtc calls api types (#590)
* core: webrtc calls api

* call: call state, chat items, update commands/responses

* update sequence diagram

* remove CRCallInvitationSent, add CISCallError
2022-05-02 17:06:49 +01:00
Evgeny Poberezkin
f78ec3584f webrtc calls in webview to typescript (#592)
* mobile: webrtc calls in webview (typescript WIP)

* typsecript compiles

* fix error messages

* TS works in chrome

* include ICE candidates into offer/answer, report connection state changes to host, end call on disconnection

* refactor, readme for .js file
2022-05-02 15:48:39 +01:00
Evgeny Poberezkin
18d1a0605e Merge branch 'master' into webrtc-calls 2022-05-02 10:57:45 +01:00
Evgeny Poberezkin
63f531259d fix test for /t 2022-05-02 10:57:35 +01:00
IanRDavies
94a4f33a1f id/video calls prototype (#570)
* initial UI framework

* limited javascrtipt interaction

* run some js

* try to resolve permissions issues

* some initial RTC javascript

* approaching a workable js file

* js fixes

* tidy up js

* add some ui to web call

* fixes

* ready to test

* typo

* refactor for readability

* tidy up before adding encryption

* add transform to video streams

* tidy a little, audio encoding works, video fails

* minor changes

* use variables consistently

* e2ee video calls
git push

* include IV in outgoing message, decrypt fails when trying to read back

* add different prefix retention for differing frame types

* e2ee video calls with iv passed in band

* enforce use of VP8 encoding

* allow plaintext chunk only for video frames

* tidy up kotlin. Android <> browser tested

* minor ios changes

* capture js logs in xcode

* typo

* linting

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-05-01 14:08:20 +01:00
Evgeny Poberezkin
26d3e71c4e fix test for /t 2022-05-01 14:07:37 +01:00
Evgeny Poberezkin
606eefa45d mobile: show contact connection pending status as ellipsis, error as gray circle (no green circles) (#588)
* mobile: show contact connection pending status as ellipsis, error as gray circle (no green circles)

* android: show contact status
2022-05-01 14:05:01 +01:00
IanRDavies
42959cc350 Video calls RFC (#542)
* draft RFC for video calls, own rolled WebRTC approach in comments

* formatting typo

* update

* update rfc (WIP)

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-05-01 09:30:41 +01:00
Evgeny Poberezkin
2b1ab01efe terminal: /tail all messages (for all chats) (#589)
* terminal: /tail all messages (for all chats)

* tests
2022-04-30 21:23:14 +01:00
JRoberts
dd592c7db3 core: discontinue old file protocol (send); use MCFile when sending and receiving files (#587) 2022-04-30 19:18:46 +04:00
Evgeny Poberezkin
305052ecaf core: api to resubscribe connections (#586)
* core: api to resubscribe connections

* update simplexmq
2022-04-30 12:47:50 +01:00
Evgeny Poberezkin
099f25c63f ios: update version 1.7 (40) 2022-04-29 17:03:22 +01:00
JRoberts
6c72db58f5 core: return AChatItem in FileAccepted and FileStart events (#585) 2022-04-29 15:56:56 +04:00
Evgeny Poberezkin
1df9a1ec2d core: refactor terminal commands (#583) 2022-04-28 08:34:21 +01:00
Evgeny Poberezkin
d9572cef86 terminal: command to show last N items in a chat (#582) 2022-04-28 07:26:43 +01:00
JRoberts
c48a516586 core: MCFile (#580) 2022-04-28 09:40:51 +04:00
JRoberts
a7554771a0 android: refactor compose (#579) 2022-04-27 20:54:21 +04:00
JRoberts
645587431d core: fix asynchronous file transfer (#572) 2022-04-26 12:52:41 +04:00
Evgeny Poberezkin
f02dcc851e core: fix mac tests (#576)
* core: fix mac tests

* enable all tests
2022-04-26 09:08:39 +01:00
JRoberts
762024dfd9 test: fix race condition caused by small file transfer (#577) 2022-04-26 11:51:46 +04:00
Evgeny Poberezkin
fc5cdc5eb1 mobile: use batched DOWN/UP events, core: include pending contacts (#573)
* mobile: use batched DOWN/UP events, core: include pending contacts

* query style
2022-04-26 07:51:06 +01:00
Evgeny Poberezkin
44de6297ee android: UI for pending contact connections, ios: translations, show profile picture in contact requests (#571)
* android: UI for pending contact connections, ios: translations, show profile picture in contact requests

* update translations

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* update translation

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-04-26 07:41:08 +01:00
Evgeny Poberezkin
cd2eb9c88e core: test async handshake (#569)
* core: test async handshake

* Update tests/ChatTests.hs

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-04-25 19:30:21 +04:00
IanRDavies
5fc1364fd3 refactor to use stringResource in composables (#568)
In order to retain the ability to have Previews in android studio we need to use stringResource over generalGetString in composable items.
2022-04-25 12:37:32 +01:00
IanRDavies
b5022b4d41 android: use opensource boofcv to scan QR codes (#562)
* open source qr code scanner

* refined error localisation - todo moved

* tidy up to make picking back up easier

* better errors

* scan images one at a time and once per second to avoid multiple requests being sent

* more sophisticated avoidance of repeat contact requests

* remove format specifier

* refinements

* update QR scanner to use boofcv

* remove dependence on zxing

* attribution

* Update apps/android/app/src/main/java/chat/simplex/app/views/newchat/QRCodeScanner.kt

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-04-25 11:05:09 +01:00
Evgeny Poberezkin
89c36d42e2 ios: pending contact connections UI, core: delete connections on the server when deleting in UI/db (#565)
* ios: started pending connections UI

* ios: UI for pending contact connections complete

* this has to be getter, or it would break JSON parsing

* ios: update "initiated" status of connection
2022-04-25 10:39:28 +01:00
JRoberts
db4731f19b mobile: correctly resize images, refine image sending UI (#546) 2022-04-25 12:44:24 +04:00
Evgeny Poberezkin
0470f9cf36 core: batch contact disconnections and re-subscriptions to one event per server (#564)
* core: batch contact disconnections and re-subscriptions to one event per server

* update simplexmq

* update query to use temp table

* remove old code
2022-04-25 09:17:12 +01:00
IanRDavies
e87660974e Paste link to connect (#551)
* initial implementation of textbox

* paste to connect box implemented (and tested) in android

* first pass at pastebox in iOS

* clean up iOS implementation

* put paste link page in for groups in android

* initial inclusion in iOS UI

* refactor naming

* lint kotlin

* fix typo

* ios: update "connect via link" ui, refactor connecting via link to use the one function

* android: update paste link UI

* add russian translations

* update translations

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* update translations

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-04-25 07:54:07 +01:00
Evgeny Poberezkin
48ba6472b6 core: add updatedAt to UserContactRequest and to PendingContactConnection, use it to sort the list of chats, tests (#563) 2022-04-24 09:05:54 +01:00
Evgeny Poberezkin
9de28c46a0 core: update test (#561) 2022-04-23 18:23:29 +01:00
Evgeny Poberezkin
14514050ae core: api for pending contact connections (#553)
* core: api for pending contact connections

* core: pending contact connection events / api
2022-04-23 17:32:40 +01:00
Evgeny Poberezkin
a525f24969 remove nl translations (will conflict with locale-nl branch) 2022-04-23 13:34:51 +01:00
Evgeny Poberezkin
4a2bfef4b3 ios: refactor views with ViewBuilder (#558) 2022-04-23 12:52:05 +01:00
Evgeny Poberezkin
0c37282cd3 ios: add translation for "Checking new messages..." test notification 2022-04-23 11:34:08 +01:00
Evgeny Poberezkin
e6fdb40c59 ios: fix opening direct chats (#555) 2022-04-23 11:35:56 +04:00
Evgeny Poberezkin
0091e9f162 ios: show notification token status in UI (#552)
* ios: show notification token status in UI

* show notification token status
2022-04-23 09:32:16 +04:00
Evgeny Poberezkin
8257842914 APIRegisterToken returns NtfTknStatus (#550) 2022-04-22 20:32:19 +01:00
Evgeny Poberezkin
dcffdf83b9 ios: show local notification when checking messages having received background notification (#549)
* ios: show local notification when checking messages having received background notification

* Update apps/ios/Shared/Model/NtfManager.swift

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* Update apps/ios/Shared/Model/NtfManager.swift

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-04-22 17:53:09 +01:00
IanRDavies
1932873776 Show pending contacts (#547)
* capture contact connecting event

* disable navigation to not ready chat

* update "pending contact" alert, Russian translations

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-04-22 17:26:17 +01:00
Evgeny Poberezkin
7c2edff81f ios: periodic notifications UI (#548)
* ios: periodic notifications UI

* add missing import

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-04-22 16:46:05 +04:00
Evgeny Poberezkin
f594774579 ios: push notifications (#482)
* ios: get device token for push notifications

* ios: receive messages when background notification is received

* add notifications API, update simplexmq

* chat API to register and verify notification token

* update AppDelegate to recognize different notification types, update simplexmq

* core: api to enable periodic background notifications

* update simplexmq

* chat API to delete device notification token

* use base64url encoding in verification code

* update simplexmq for notifications
2022-04-21 20:04:22 +01:00
IanRDavies
03b8cdea8d android: support app links via simplex.chat (#545)
* update manifest

* autoverify domain

* add path prefix to intent filter

* support contact links

* add android app readme

* only allow a single simplex task at a time to avoid opening simplex 'on top' of app with link
2022-04-21 14:57:36 +01:00
JRoberts
effd37402a SMP AUTH error processing and descriptions (#544)
* SMP AUTH error descriptions

* change text

* android error description

* Update apps/android/app/src/main/res/values/strings.xml

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* Update apps/android/app/src/main/res/values-ru/strings.xml

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* fix translations

* 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>
2022-04-21 11:50:24 +04:00
JRoberts
43e560c901 Merge pull request #543 from simplex-chat/master (version 1.6) 2022-04-20 22:17:45 +04:00
JRoberts
ec5aea0773 android: version 1.6 (26) 2022-04-20 12:44:31 +04:00
JRoberts
9a32bb8959 ios: version 1.6 (39) 2022-04-20 12:16:44 +04:00
JRoberts
4cac08cf51 mobile: fix translations, minor view bugs (#541) 2022-04-20 11:53:34 +04:00
JRoberts
3e1fa779b9 android: version 1.6 (25) 2022-04-19 21:44:05 +04:00
Evgeny Poberezkin
5119bb3625 fix: cancelled links previews, context menu width 2022-04-19 18:23:17 +01:00
JRoberts
d61c8a363a android: version 1.6 (24) 2022-04-19 16:57:05 +04:00
Evgeny Poberezkin
059a13576b ios: version 1.6 (38) 2022-04-19 12:52:44 +01:00
JRoberts
2e46092d92 ios: add english localization 2022-04-19 15:47:08 +04:00
Evgeny Poberezkin
44fd13b836 ios: update version 1.6 (37) 2022-04-19 12:22:37 +01:00
JRoberts
015329fcc7 ios 1.6 release 2022-04-19 15:17:47 +04:00
JRoberts
6835b6c1dd mobile: enable/disable features for release (#540) 2022-04-19 13:24:26 +04:00
JRoberts
1152b5d737 mobile: support images (#536)
* ios api

* ios wip

* android wip

* ios files folder

* ios get address on start

* android app files folder

* ios more backend

* android more backend

* translation

* ios image without text, remove preview

* android image without text, remove preview

* fix translation

* file name in previews and w/t text

* Revert "file name in previews and w/t text"

This reverts commit 0110570e55.

* ios filename in preview

* android filename in preview

* android wider images

* ios determine width on image for correct quote width

* ios images in previews wip

* ios square image in quote

* ios: update image layout

* android images in quotes

* android remove redundant modifier

* android clip to bounds

* android - image in right side of quote

* android refactor image view

* android - refactor, align quote text top

* android fix emoji view

* fix image layout

* full screen image view, fix quote layout

* android various size

* android fixed image width

* android meta on image

* ios: add drag gesture to hide full-screen image

* android: make image-only meta white

* refactor file.stored

* android: meta icon color

* android: open chat scrolled to last unread item

* copy/share image messages

* android: full screen image

* check file is loaded

* terminal: refactor view for messages with files

* android: change to onClick, only show stored file

* android: remove close sheet bar

* android: close image view on click

* translation

* android: pass showMenu to CIImageView to show menu on long click

* increase DropDown width

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-04-19 12:29:03 +04:00
Evgeny Poberezkin
de81afc727 ios: update haskell libs 2022-04-18 11:53:52 +01:00
Evgeny Poberezkin
ffb941ac4d update build badge to point to stable branch 2022-04-16 12:21:37 +01:00
Evgeny Poberezkin
4b39de6c4f Merge pull request #535 from simplex-chat/master
merge master to stable
2022-04-16 12:16:14 +01:00
Evgeny Poberezkin
bc9a8bc32c ios: remove "Tests MacOS" target 2022-04-16 12:09:10 +01:00
JRoberts
757ca74482 terminal: version 1.6.0 (#534) 2022-04-16 13:01:07 +04:00
Evgeny Poberezkin
87c688a739 ios: i18n (#533)
* ios: prepare for i18n

* commit localizations

* update Russian translations

* fix notifications and layouts after localizations

* localization docs

* update translations

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* fix typo

* update translations

* fix translations for different link types

* update translations

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* update translation

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* update translations

* update translations

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-04-16 09:37:01 +01:00
IanRDavies
d201c9528a android: i18n (#529)
* internationalization framework

* rearrange strings

* typo

* minor id & xliff changes

* response to comments

* colour comments and verb suffixes

* add russian language file

* fix interpolation error

* final strings

* russian translations

* update Russian translations, refactor strings to full sentences, add prefixes to content description names

* fix layouts, improve font spacing

* split sentence about User address, font line height

* typo

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* update Russian translations

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* remove an

* update Russian translations

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* commas

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-04-16 09:29:29 +01:00
Evgeny Poberezkin
2058e904e6 core: refactor files folder support (#532)
Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-04-15 16:16:34 +04:00
JRoberts
e560ed8327 core: support files folder for mobile, delete files, chat item in CRRcvFileComplete (#530) 2022-04-15 09:36:38 +04:00
Evgeny Poberezkin
5281871aa6 typo 2022-04-13 11:49:09 +01:00
JRoberts
f83704c964 fix typos in readme (#528) 2022-04-13 08:37:13 +01:00
IanRDavies
1431002829 add status icons to messages (#524)
* add status icons to messages

* prettier spacing

* tighten status icons

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-04-12 20:38:38 +01:00
IanRDavies
f1356ca642 readme changes (#527)
* readme changes

* more changes

* response to comments
2022-04-12 19:57:36 +01:00
Evgeny Poberezkin
07c7799523 reduce text in readme (#525)
* reduce text in readme

* update "why"

* typo

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* dot

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-04-12 19:30:17 +01:00
Evgeny Poberezkin
7ab76528a0 Merge branch 'stable' 2022-04-12 19:24:44 +01:00
Evgeny Poberezkin
a0a14889b1 android: update version 1.5.1 (23) 2022-04-12 19:11:44 +01:00
Evgeny Poberezkin
78133ff4d2 Merge branch 'stable' 2022-04-12 14:00:42 +01:00
Evgeny Poberezkin
34c513adeb core: update simplexmq (fixes SMP END from disconnected clients removing active subscriptions) (#523) 2022-04-12 12:24:34 +01:00
IanRDavies
31eabf07e4 android: notifications improvements (to stable) (#522)
* add intent to grouped notifications

* clear overlays on open from ntf

* cancel notifications alongside unmarked markers

* tidy up
2022-04-12 11:55:18 +01:00
Evgeny Poberezkin
af471d0077 update github content (#519)
* update github content

* update comparison

* update link

* move message_views.sql to scripts

* move section

* move news section

* typos

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* update readme

* update readme

* update readme

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-04-11 22:29:08 +01:00
Evgeny Poberezkin
0a17f5c491 ios: update package name in prepare script, update libs (#509)
* ios: update package name in prepare script, update libs (not working yet)

* ios: update/move prepare scripts
2022-04-11 18:43:09 +01:00
Evgeny Poberezkin
7f8afb0c12 move nix files to folder (#520)
* move nix files to folder

* move nix to scripts
2022-04-11 15:53:44 +01:00
IanRDavies
1b930e717a android: link previews (#510)
* wire up api for link metadata parsing

* add getLinkPreview (synchonous for now)

* api wiring fix

* get network requests off main thread

* copy over state machine logic from iOS

* filter api parsing calls from logs

* refactor of image processing

* remove image deepcopy

* minor change to log filtering

* mobile: link previews

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-04-11 09:39:04 +01:00
Evgeny Poberezkin
02d21145b2 core: replace quoted content with MCText if the message itself is not MCText (#517)
* core: replace quoted content with MCText if the message itself is not MCText

* core: quoteData in ChatMonad (#518)

* core: quoteData in ChatMonad

* use throwChatError

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-04-11 09:34:59 +01:00
Evgeny Poberezkin
fa313caa82 terminal: refactor chat core used in terminal app and in bot examples (#516)
* terminal: refactor chat core used in terminal app and in bot examples

* fix tests

* refactor
2022-04-10 17:13:06 +01:00
Evgeny Poberezkin
0ac9785e4b terminal: option to execute a single chat command via command line (#515) 2022-04-10 16:30:54 +01:00
Evgeny Poberezkin
fd69b673d8 terminal: use up arrow to edit the last message (#514)
* terminal: use up error to edit the last message

* update help
2022-04-10 12:18:53 +01:00
Evgeny Poberezkin
6c2fb822d7 nix: add the second x86 ios sim build with swift JSON 2022-04-10 10:52:36 +01:00
JRoberts
13f84f2a96 core: sending messages with files (#507)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-04-10 13:30:58 +04:00
Evgeny Poberezkin
150b4196ea ios: fix scrolling with link previews, fix large terminal item detail view (#512) 2022-04-08 19:58:02 +01:00
Evgeny Poberezkin
84a77de53c remove apiParseMarkdown commands from console (#511) 2022-04-08 18:58:09 +01:00
IanRDavies
d90c4261b8 ios: link previews (#503)
* refactor image utils and initial link metadata tools

* remove LPMetadata conversion as we will build our own view to avoid network calls

* initial very basic preview outline, remove icon loading

* connect preview view to compose view

* v0.1 barely working

* minor refactor

* refactor

* collect images effectively

* link up to api for send/receive

* rework async get metadata logic

* show previews in chat

* refactor resizing logic

* checkpoint before view editing

* ui changes

* housekeeping

* ui tweaks

* typo

* improve link preview design/logic

* resize image to target data size

* fix link preview state machine

* tidy up

* fix typo

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-04-08 18:17:10 +01:00
Evgeny Poberezkin
9fda89d0db update simplexmq (with swift flag) 2022-04-08 15:44:42 +01:00
Evgeny Poberezkin
8ef27de503 update simplexmq, cabal flag, fix tests 2022-04-08 09:21:56 +01:00
Evgeny Poberezkin
238cc8b90b ios: update libs 2022-04-07 16:11:18 +01:00
Evgeny Poberezkin
f12b5524fd fix flake.nix 2 2022-04-07 10:43:13 +01:00
Evgeny Poberezkin
3f86737d3f fix flake.nix 2022-04-07 10:33:32 +01:00
Moritz Angermann
082e62c56b Update flake.nix (#508)
* Update flake.nix

* update nix file, simplexmq sha

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-04-07 09:12:54 +01:00
JRoberts
8dd324b9b3 core: images api (#506)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-04-06 13:21:06 +04:00
JRoberts
de64f3a1a0 tests: maintain schema dump (#505) 2022-04-05 12:44:22 +04:00
JRoberts
a5ca2c2163 core: new files protocol (#492) 2022-04-05 10:01:08 +04:00
Evgeny Poberezkin
a17ddede53 ios: update binaries 2022-04-04 20:44:38 +01:00
Evgeny Poberezkin
7012005feb core: MsgContent for link previews, API to parse markdown (#504) 2022-04-04 19:51:49 +01:00
IanRDavies
0ecaa59df6 ios: update image picker (#495)
* switch to PHPicker for photos. TODO add back camera functionality. [rough]

* add back camera selection option

* remove force unwrap of optional

* response to comments

* rerun tests

* refactor naming
2022-04-04 19:19:54 +01:00
Evgeny Poberezkin
309fdf422f ios: scripts (#501) 2022-04-04 12:33:28 +01:00
Evgeny Poberezkin
9e88e4b940 blog: instant notifications design for Android and iOS (#463)
* blog: instant notifications design for Android and iOS

* fix blog diagram

* update blog post

* typo
2022-04-04 10:30:18 +01:00
Evgeny Poberezkin
7519884a5e Merge pull request #500 from simplex-chat/master
Merge to stable
2022-04-04 10:27:35 +01:00
Evgeny Poberezkin
852421315b SimpleX Chat bot example (#499)
* SimpleX Chat bot example

* extract repl bot

* update .cabal
2022-04-04 08:14:42 +01:00
JRoberts
33857d9aa7 Merge pull request #487 from simplex-chat/master (v1.5.0) 2022-04-03 14:10:02 +04:00
JRoberts
ef41034e17 Merge branch 'stable' 2022-04-03 14:05:57 +04:00
JRoberts
331269a186 terminal: version 1.5.0 (#498) 2022-04-03 13:58:38 +04:00
Evgeny Poberezkin
c14f692b68 terminal: edit and delete messages for everyone on the chat (#497) 2022-04-03 09:44:23 +01:00
Evgeny Poberezkin
4247dc4271 ios update build num 35 2022-04-02 16:09:49 +01:00
Evgeny Poberezkin
7f945d2530 ios: improve connection error alerts 2022-04-02 14:35:35 +01:00
Evgeny Poberezkin
3dc9eded54 ios: fix alert on contact deletion from chat info (#496) 2022-04-02 12:23:05 +01:00
Evgeny Poberezkin
5c13267b47 mobile: build nums ios 34, android 22 2022-04-02 10:41:26 +01:00
IanRDavies
e10c8c7234 android: minor text changes (#491)
* minor changes for readability

* update notifications popup wording

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-04-02 10:15:44 +01:00
IanRDavies
052963f19e ios: tidy up tmp images (#494)
* catch image URL and tidy up after the fact

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-04-01 17:13:54 +01:00
Evgeny Poberezkin
c7d7c6c608 mobile: build number ios 33, android 21 2022-04-01 16:54:55 +01:00
JRoberts
6c4c097150 core: update simplexmq hash (remove manual vacuum) (#493) 2022-04-01 17:23:12 +04:00
JRoberts
3eb4d5efdd core: update simplexmq hash (catch db error) (#490) 2022-04-01 13:00:35 +04:00
Evgeny Poberezkin
ea95912bd5 mobile: update lib and versions 1.5 (ios - 32, android - 20) 2022-04-01 09:58:00 +01:00
JRoberts
d080a3a87b mobile: hide broadcast delete button (#488) 2022-03-31 21:38:53 +04:00
JRoberts
54b501913c fix readme link (#485) 2022-03-31 16:41:08 +04:00
JRoberts
7f7abe1c62 terminal: version 1.4.1 (#486) 2022-03-31 16:08:07 +04:00
JRoberts
f1492f8889 core: update simplexmq hash (pragmas) (#484) 2022-03-31 15:32:42 +04:00
JRoberts
4c6800f1ff android: change font (#426) 2022-03-30 23:35:36 +04:00
Evgeny Poberezkin
b6c578ca77 ios: fix missing profile image on the first received item in the group (#483) 2022-03-30 20:04:25 +01:00
JRoberts
f388512592 mobile: message delete (#480)
* mobile: message delete

* ios

* android api

* meta

* android

* new ios libs

* bug fixes

* adjust alert

* fix deleted item upsert

* change border color for ios

* format

* android - red button

* ios: deleted item design

* android: deleted item design

* android alert msg

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-03-30 20:37:47 +04:00
Evgeny Poberezkin
8574674c2d android: notice about background service (#481)
* android: notice about background service

* update alert text
2022-03-30 12:33:31 +01:00
Evgeny Poberezkin
1b7cee9fcf ios: update lib and build version 31 / 1.4 2022-03-30 09:02:45 +01:00
Evgeny Poberezkin
12ee82808e ios: update lib and build version 31 / 1.4 2022-03-30 09:02:20 +01:00
Evgeny Poberezkin
5e964cf7e9 mobile: show group member images in the chat (#473)
* mobile: show group member images in the chat

* improve layout for group chat

* android: show member images in group chat

* do not repeat member name in group messages
2022-03-30 08:57:42 +01:00
Evgeny Poberezkin
8768d03e57 Merge branch 'stable' 2022-03-30 08:19:38 +01:00
Evgeny Poberezkin
75dfd725f4 android: build 19 (v1.4) 2022-03-30 08:18:08 +01:00
Evgeny Poberezkin
ea343b634d core: fix multiline mardown (#478)
* core: fix multiline mardown

* add test
2022-03-29 13:18:44 +01:00
Evgeny Poberezkin
41a2e0b1d5 Merge branch 'stable' 2022-03-29 12:54:42 +01:00
Evgeny Poberezkin
e52e516f5c core: update simplexmq (PING failure resets TCP connection, increase timeout to 5 sec) 2022-03-29 12:49:47 +01:00
Evgeny Poberezkin
ea29f93fb6 core: update simplexmq (PING failure resets TCP connection, increase timeout to 5 sec) 2022-03-29 11:36:52 +01:00
Evgeny Poberezkin
eaa2f4cf04 terminal: send broadcast messages (#477) 2022-03-29 08:53:30 +01:00
Evgeny Poberezkin
954f729a30 update simplexmq (parallel resubscriptions) 2022-03-28 22:01:52 +01:00
JRoberts
d35e7da3e4 trigger new CI job 2022-03-28 22:27:05 +04:00
JRoberts
692f37daa2 core: message delete (#470) 2022-03-28 20:35:57 +04:00
Evgeny Poberezkin
e0f4855d0d android: version 1.5 (18) - includes foreground service 2022-03-27 12:16:05 +01:00
Evgeny Poberezkin
a11784c615 android: foreground service to receive messages (#454)
* android: foreground service to receive messages

* android: fix duplicate chat (caused by persistent state of the service)

* option to turn off background service

* fix: foreground service failing to start when the new user is created

* remove unused background manager
2022-03-26 16:49:08 +00:00
JRoberts
922ec2c045 Merge pull request #476 from simplex-chat/master (v1.4.0 terminal app) 2022-03-26 19:25:03 +04:00
JRoberts
262c999e5c terminal: version 1.4.0 2022-03-26 18:22:45 +04:00
Evgeny Poberezkin
14a5b680d7 core: update simplexmq (#475)
* core: update simplexmq

* update sha256map.nix
2022-03-26 13:47:47 +00:00
Evgeny Poberezkin
a316a95754 android: version 1.4 (17) 2022-03-26 13:25:01 +00:00
Evgeny Poberezkin
a81de493fe ios: version 1.4 (30) 2022-03-26 12:23:14 +00:00
JRoberts
bdb3bc0bd7 mobile: hide edit button (#474) 2022-03-26 15:08:42 +04:00
JRoberts
8b2ae2d426 terminal: version 1.3.4 2022-03-26 10:49:36 +04:00
Evgeny Poberezkin
013a7322d2 ios: fix chat scrolling crashing the app (#472) 2022-03-25 20:02:40 +00:00
Evgeny Poberezkin
e92f960a87 clean-up logo (#471) 2022-03-25 19:34:04 +00:00
JRoberts
0b45ddfc79 mobile: message update (restore #460) (#469) 2022-03-25 22:26:05 +04:00
JRoberts
897c64e0ba core: use existential connection request type in file invitations to allow switching groups to "contact" requests (restore #464) (#468) 2022-03-25 22:23:51 +04:00
JRoberts
26558dfaca profile images (restore #423) (#466)
* core: configurable smp servers (#366)

* core: update simplexmq hash

* core: update simplexmq hash (fix SMPServer json encoding)

* core: fix crashing on supplying duplicate SMP servers

* core: update simplexmq hash (remove SMPServer FromJSON)

* core: update simplexmq hash (merged master)

* core: profile images (#384)

* adding initial RFC

* adding migration SQL

* update RFC

* linting

* Apply suggestions from code review

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* refine RFC

* add avatars db migration to Store.hs

* initial chages to have images in users/groups

* fix protocol tests

* update SQL & MobileTests

* minor bug fixes

* add missing comma

* fix query error

* refactor and update  functions

* bug fixes + testing

* update to parse base64 web format images

* fix parsing and use valid padded base64 encoded image

* fix typos

* respose to and suggestions from review

* fix: typo

* refactor: avatars -> profile_images

* fix: typo

* swap updateProfile parameters

* remove TODO

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* initial changes to show profile images

* simple set up complete

* add initial shape of image getting (needs work)

* redesign

* ios, android: configurable smp servers (only model and api for android) (#392)

* example image picker placed in edit profile screen

* tidy up and allow encoding

* more tidying

* update bottom modal bar

* v0.1 UI for upload ready

* add api calls

* refactor edit profile screen

* complete the refactor with connection back to api

* linting

* update encoding for hs compat

* no line wrapping and resize image

* refactor and tidy up for cleanest compatability with haskell

* ios: UI for editing images

* crop image to square

* update profile edit layout

* fixing image preview orientation etc

* allow expandable image in profile view

* handle case where user exits camera rather than take image

* housekeeping on when to call apiUpdateProfileImage

* improve scaling of large image

* linting

* spacing

* fix padding

* revert whitespace change

* tidy up, one remaining issue

* refactor to get parsing working

* add missed change

* use custom modal in user profile

* fix image size after scaling

* scale image iteratively

* add filter

* update profile editing view

* ios: edit profile image (TODO aspect ratio)

* ios: UI to manage profile images

* ios: use new profile api

* android: use new api to update profile

* android: scroll profile view up when editing

* revert change

* reduce profile image resolution to 104px to fit in 12.5kb

Co-authored-by: IanRDavies <ian_davies_@hotmail.co.uk>
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-03-25 22:13:01 +04:00
Evgeny Poberezkin
ff32a44345 trigger new CI job 2022-03-24 11:01:22 +00:00
Evgeny Poberezkin
d4925b7cdd core: api to update user profile in one request (#461) 2022-03-23 20:52:00 +00:00
Evgeny Poberezkin
3c81a44273 message update and delete (#451)
* core: message update and delete, protocol and command syntax

* edit logic wip

* message updates

* revert project.pbxproj

* corrections, dependency, editable

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
2022-03-23 15:37:51 +04:00
Evgeny Poberezkin
319b4dc841 bump haskell.nix (#459)
Co-authored-by: Moritz Angermann <moritz.angermann@gmail.com>
2022-03-23 08:47:36 +00:00
Evgeny Poberezkin
71483b0fc4 update simplexmq 2022-03-22 08:07:52 +00:00
Evgeny Poberezkin
366b84d3fa use simplexmq with TCP keep-alive instead of SMP PINGs (#457)
* use simplexmq with TCP keep-alive instead of SMP PINGs

* update simplexmq

* sha256nix
2022-03-21 17:15:25 +00:00
Evgeny Poberezkin
22dc68ff4e ios: update dummy.m to work with x86 sim, upgrade libraries (#458)
* ios: update dummy.m to work with x86 sim

* add condition for CPU arch to dummy.m
2022-03-21 08:43:34 +00:00
Evgeny Poberezkin
4903966bea update nix dependencies config 2022-03-20 16:41:04 +00:00
Evgeny Poberezkin
f43c462907 ios: load chat from db synchronously to avoid occasional empty chats (#453) 2022-03-19 17:20:27 +00:00
Evgeny Poberezkin
490dc17571 Merge PR #450 - v1.3 release
merge v1.3 to stable
2022-03-19 09:17:35 +00:00
Evgeny Poberezkin
b57a77c8f0 Merge branch 'stable' 2022-03-19 09:05:30 +00:00
Evgeny Poberezkin
fe0e5e8b89 terminal: version 1.3.3 (#447)
* terminal: show version from .cabal file

* update welcome message

* terminal: helo on message quotes

* terminal: allow replies in groups without specifying a member

* core: update version to 1.3.3
2022-03-19 09:04:53 +00:00
Evgeny Poberezkin
3340bea150 core: api to remove profile image (#448) 2022-03-19 07:42:54 +00:00
Evgeny Poberezkin
0e73697ea4 mobile: show app version/build, update settings, update build number (16: android, 28: ios) (#445) 2022-03-18 09:23:01 +00:00
sh
4fcbec49c9 readme: add fdroid badge (#446) 2022-03-18 08:21:36 +00:00
Evgeny Poberezkin
01994d8c6a android: fix message entry size after sending emoji, build 15 2022-03-17 18:01:47 +00:00
Evgeny Poberezkin
31de7fd0ee mobile: update version/build 1.3 (ios: 27, android 14) 2022-03-17 10:34:31 +00:00
Evgeny Poberezkin
744c451927 mobile: message actions (reply, share, copy) (#431)
* ios: add context menu to messages

* ios: UI for replies with quotes

* fix: scrolling crashing in chat

* ios: UI for message replies with quotes

* android: UI for message replies

* android: messages with quotes

* android: update imports

* android: refactor ChatItemView

* remove comments
2022-03-17 09:42:59 +00:00
Evgeny Poberezkin
148474e1ba core: change quoted messages types/protocol (#443)
* core: change quoted messages types/protocol

* remove comments and unused field

* rename CIQuote type

* change type for quote direction to allow unknown group member, use QuotedMsg to save received chat item

* change queries of quoted items when the sending group member is known

* refactor

* fix: make ciQuote polymorphic
2022-03-16 13:20:47 +00:00
Evgeny Poberezkin
d4765bcfec Merge branch 'stable' 2022-03-14 21:04:05 +00:00
Evgeny Poberezkin
e4ea2035ff android: fix app crashing on opening chats, build 12 (#439) 2022-03-14 21:03:36 +00:00
Evgeny Poberezkin
3a28bacf14 Merge branch 'stable' 2022-03-14 21:01:54 +00:00
Evgeny Poberezkin
6ba7d208c8 terminal: version 1.3.2 (#442) 2022-03-14 20:58:53 +00:00
Mark Aleksander Hil
102fdf3b18 mobile: update copy, fix typo (#440)
* Updated copy and fixed typo

* Updated copy and fixed typo
2022-03-14 20:58:19 +00:00
Evgeny Poberezkin
1f539fc8be hide secrets in notifications, closes #416 (#424)
* terminal: hide secrets in notifications #416

* ios: hide secrets in notifications

* android: hide secrets in notifications
2022-03-13 20:13:47 +00:00
Evgeny Poberezkin
806f417e99 message replies and chat item references (#394)
* rfc for message replies and chat item references

* update replies rfc

* save received/sent shared message ids, migration and types for replies

* include reply/forward into MsgContent type

* add sharedMsgId to CIMeta

* save/get shared_msg_id to/from chat items table

* parameterize CIRef by chat type

* add CIRef to ChatItem when it is read from the db

* terminal command to send message replies

* include quoted content into chat items

* quoted message direction in direct chats (TODO test)

* test for replies with quotes to group messages - own and others

* split MsgContainer from MsgContent

* make quoting usable in the terminal

* add formattedText to quotes

* rename migration

* update JSON encoding for MsgContainer

* allow quoted replies to messages from clients not supporting it/not sending msg IDs

* update rfc

* fix group replies

* add APISendMessageQuote and use it for terminal commands

* change how quoted messages are shown in groups
2022-03-13 19:34:03 +00:00
IanRDavies
6c04184a9c core: filter contacts on connection status before broadcasting profile updates (#430)
* filter contacts on connection status before broadcasting profile updates

* catch and report errors when notifying contacts about profile updates

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-03-12 20:57:11 +00:00
Evgeny Poberezkin
22ff17aec9 Merge PR #434 - v1.2 release 2022-03-12 20:28:43 +00:00
Evgeny Poberezkin
b2650947a9 android: update build (11) 2022-03-12 17:24:29 +00:00
Evgeny Poberezkin
604bf0c485 android: smaller fonts, bigger line height (#433) 2022-03-12 16:57:30 +00:00
Evgeny Poberezkin
b7bf3678e5 fix: markdown and links interaction/copy in messages (#432) 2022-03-12 16:52:04 +00:00
Evgeny Poberezkin
b0430f7eee android: update version 10 (1.2) 2022-03-11 19:11:52 +00:00
Evgeny Poberezkin
7d3e440a47 ios: update build (26) 2022-03-11 18:24:38 +00:00
Evgeny Poberezkin
6877261b9c ios: fit smaller screens (#429)
* ios: fit smaller screens

* s/or/and/
2022-03-11 17:52:11 +00:00
Evgeny Poberezkin
eef45a6015 ios: update haskell lib, version 1.2 (25) 2022-03-11 11:32:57 +00:00
Evgeny Poberezkin
0aee431527 update readme 2022-03-11 07:37:13 +00:00
John Roberts
90a18186d9 configurable smp servers (#366, #411); core: profile images (#384)
* core: configurable smp servers (#366)

* core: update simplexmq hash

* core: update simplexmq hash (fix SMPServer json encoding)

* core: fix crashing on supplying duplicate SMP servers

* core: update simplexmq hash (remove SMPServer FromJSON)

* core: update simplexmq hash (merged master)

* core: profile images (#384)

* adding initial RFC

* adding migration SQL

* update RFC

* linting

* Apply suggestions from code review

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* refine RFC

* add avatars db migration to Store.hs

* initial chages to have images in users/groups

* fix protocol tests

* update SQL & MobileTests

* minor bug fixes

* add missing comma

* fix query error

* refactor and update  functions

* bug fixes + testing

* update to parse base64 web format images

* fix parsing and use valid padded base64 encoded image

* fix typos

* respose to and suggestions from review

* fix: typo

* refactor: avatars -> profile_images

* fix: typo

* swap updateProfile parameters

* remove TODO

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* ios, android: configurable smp servers (only model and api for android) (#392)

* android: configurable smp servers (ui)

* fix thumb color, fix text field color in dark mode

* update simplexmq hash (configurable servers in master)

Co-authored-by: IanRDavies <ian_davies_@hotmail.co.uk>
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-03-10 15:45:40 +04:00
IanRDavies
38aea7c455 use relative sizing when scaling the QR code (#417)
* use relative sizing when scaling the QR code

* linting

* properly implement image scaling

* remove extra horizontal padding

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-03-10 10:36:21 +00:00
Evgeny Poberezkin
e272048f24 ios: date/time formatting now respects locale settings (#420) 2022-03-09 22:35:33 +00:00
Evgeny Poberezkin
6aa9f208ee Merge pull request #418 from simplex-chat/id/android/fix-chat-scrolling
id/android/fix chat scrolling
2022-03-09 20:06:01 +00:00
IanRDavies
b749bf7b08 fix scrolling with keyboard 2022-03-09 18:54:19 +00:00
IanRDavies
ff3daed4c6 fix scrolling issue using save/load state 2022-03-09 16:30:47 +00:00
IanRDavies
e90e10bd26 add variable to monitor scrolling as scroll fix 2022-03-09 15:56:08 +00:00
Evgeny Poberezkin
c6a49b048f Merge pull request #410 from simplex-chat/master
AppStore 1.1 release (build 24)
2022-03-08 15:57:56 +00:00
Evgeny Poberezkin
29af079a8f ios: update build number (24), app store 1.1 submission - fixing iPhone 7 etc. 2022-03-08 15:19:14 +00:00
Evgeny Poberezkin
9bb6be8e60 update readme (#409) 2022-03-08 13:16:15 +00:00
Evgeny Poberezkin
226daa990f blog: apps announcement draft (#405)
* blog: apps announcement draft

* update mobile apps post

* update blog post

* add "what is simplex"
2022-03-08 12:23:08 +00:00
Evgeny Poberezkin
b8e3809452 Merge pull request #408 from simplex-chat/stable
merge stable back to master
2022-03-08 11:58:29 +00:00
Evgeny Poberezkin
47881f77d9 Merge pull request #407 from simplex-chat/angerman/bump-haskell-nix
bump haskell.nix to support iPhone 7
2022-03-08 10:32:55 +00:00
Moritz Angermann
69d0a5286e bump haskell.nix 2022-03-08 10:03:12 +00:00
Evgeny Poberezkin
ebdd78edea remove iPad support, update build # (23) 2022-03-08 08:46:48 +00:00
Evgeny Poberezkin
eff7c363d4 Merge pull request #403 from simplex-chat/master
app release
2022-03-07 16:07:16 +00:00
Evgeny Poberezkin
44cd482695 android: update version/build 0.4.2 (9) 2022-03-06 08:59:43 +00:00
Evgeny Poberezkin
a801e0c5e9 ios: build 22, add iPad support 2022-03-05 22:33:44 +00:00
Efim Poberezkin
722f836714 core: sort group messages by timestamp (#400) 2022-03-05 20:32:29 +04:00
Efim Poberezkin
1dd62be4ef Merge pull request #387 from simplex-chat/master (v1.3.1 terminal app) 2022-03-05 14:01:39 +04:00
Efim Poberezkin
33f731e247 1.3.1 2022-03-05 13:01:16 +04:00
Efim Poberezkin
7cf139f856 prepare v1.3.1 (#398) 2022-03-05 12:34:48 +04:00
IanRDavies
fd28c939f5 android: disable create button when display name is not valid (#396) 2022-03-04 14:51:25 +00:00
Evgeny Poberezkin
1ab68172cb ios: update version 1.1 (build 21) 2022-03-04 10:34:39 +00:00
Evgeny Poberezkin
87e9ae5a3e mobile: update verion/build: ios 1.0/21, android 0.4.1/8 2022-03-04 10:30:47 +00:00
Evgeny Poberezkin
c47a7d78fe support for unknown message content types (#395)
* android: parse/serialize unknown chat items

* ios: more resilient decoding of MsgContent

* core: preserve JSON of unknown message content type in MCUknown, so it can be parsed once it is supported by the client
2022-03-03 08:32:25 +00:00
Evgeny Poberezkin
b10b3a3434 ios: update libs 2022-03-02 20:15:22 +00:00
Efim Poberezkin
9d4de4b295 core: correctly set "yes to migrations" in agent config (#393) 2022-03-02 22:18:14 +04:00
Evgeny Poberezkin
2e5b123749 update simplexmq 2022-03-02 16:04:06 +00:00
Evgeny Poberezkin
3a6eaa3ddd android: update build number (7) 2022-03-02 15:57:21 +00:00
Evgeny Poberezkin
eb42d739cb android: update version/build 0.4 (6) 2022-03-02 07:11:20 +00:00
Evgeny Poberezkin
e5d5bd5ec8 android: disable background loading 2022-03-01 21:39:37 +00:00
Evgeny Poberezkin
24166a4271 android: background loading 2022-03-01 21:26:47 +00:00
Evgeny Poberezkin
232149817e ios: fix alerts, build 20 (#390) 2022-03-01 18:05:05 +00:00
Moritz Angermann
a286834eb5 bump nixpkgs 2022-03-01 16:04:54 +00:00
Evgeny Poberezkin
c500616bb4 update privacy policy, build number (#389) 2022-03-01 08:45:54 +00:00
Evgeny Poberezkin
42faa2e75b patch getentropy (#388)
* bump nixpkgs

* patch entropy

* bump haskell.nix

* remove file

Co-authored-by: Moritz Angermann <moritz.angermann@gmail.com>
2022-03-01 07:01:52 +00:00
Evgeny Poberezkin
b19cf35d28 iOS: v1 AppStore release (#386)
* fix: disable create when display name is empty

* update version/build 1.0 (17)

* update build number (18)

* terms and privacy policy
2022-02-28 20:45:31 +00:00
Evgeny Poberezkin
4585c7b649 ios: update build version (16) 2022-02-28 14:31:55 +00:00
Evgeny Poberezkin
326a2a1877 ci: add x86 packages (#383) 2022-02-28 13:52:20 +00:00
Efim Poberezkin
8d057613f5 core: update default servers (#385) 2022-02-28 16:27:55 +04:00
Evgeny Poberezkin
0b00c2ad76 android: receiving messages in background; ios: background task completion (#382)
* android: receiving messages in background; ios: background task completion

* complete receiving and sending messages in background
2022-02-28 10:44:48 +00:00
Evgeny Poberezkin
310f56a9b3 update version/build: ios 0.4 (15), android 0.3 (5) 2022-02-27 19:25:40 +00:00
Evgeny Poberezkin
0a94e740d2 android: refactor modal views without navigation controller (#381)
* android: refactor modal views without navigation controller

* refactor navigation

* make alert manager global

* disable CRPendingSubSummary in terminal, hamburger menu instead of gear
2022-02-27 18:16:38 +00:00
IanRDavies
3f3a503def android: notifications (#369)
* minimal implementation of notifications and broken framework for background check for messages

* linting and need different id to have multiple messages

* working notification on new messages

* add autocancel to notifications

* add rudimentary linking to chat from notification

* group notifications from the same chat

* clarify comment

* revert to working version

* refactor

* minors

* two channels, silent and shouty

* rudimentary state control for notifications

* check if running in foreground

* more elegant solution to don't notify if in chat

* tidy up DisposableEffect use

* change message notification priority to high

* nuke opt-ins

* navigation via notification occasionally works with race condition (WIP)

* notification navigation is working; remove chat list/view from navigation; refactor ChatListNavLinkView

* group all simplex notifications, only show the latest message per chat, notification icons

* increase time to 30 sec

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-02-27 12:14:26 +00:00
Evgeny Poberezkin
0413865a3b ios, core: fix add contact screen, add logging, additional chat events (#380)
* ios, core: fix add contact screen, add logging, additional chat events

* fix alert dialogues

* fix precedence parsing error

* update alert messages
2022-02-26 20:21:32 +00:00
Efim Poberezkin
98268a95c2 Merge pull request #379 from simplex-chat/master (v1.3.0 terminal app) 2022-02-26 17:25:05 +04:00
Evgeny Poberezkin
1110a78e06 update versions/build #s: ios 0.4 (14), android 0.3 (4) 2022-02-26 13:10:47 +00:00
Efim Poberezkin
6086f76d95 1.3.0 2022-02-26 16:41:11 +04:00
Efim Poberezkin
268eaaa9ca prepare v1.3.0 (#378) 2022-02-26 16:24:56 +04:00
Evgeny Poberezkin
ad1612d84a android: markdown help (#376) 2022-02-26 15:35:51 +04:00
Evgeny Poberezkin
0389a58f64 core: fix failing subscriptions when user address is missing (#377)
* core: fix failing subscriptions when user address is missing

* set concurrency limit on subscriptions
2022-02-26 10:04:25 +00:00
Evgeny Poberezkin
6ee2f334f6 update build number (12) 2022-02-26 08:24:58 +00:00
Evgeny Poberezkin
a5afdf4e91 ios: update version/build 0.4 (11) 2022-02-25 21:57:05 +00:00
Evgeny Poberezkin
ecaa570ff3 free C strings (#375) 2022-02-25 21:07:36 +00:00
Evgeny Poberezkin
1d2d1e6df7 process subscription summaries in ios/android (#374) 2022-02-25 20:26:56 +00:00
Efim Poberezkin
c242f0079c core: add fks to messages (#368) 2022-02-25 21:59:35 +04:00
Evgeny Poberezkin
727c533f93 update build number to 0.2 (3) 2022-02-25 15:29:52 +00:00
Efim Poberezkin
5961b7d951 asynchronously subscribe to user connections (#310)
* asynchronously subscribe to user connections

* send subscription status summaries to view/api

* refactor

* add help messages in summaries

* update simplexmq

* rename config field

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-02-25 12:29:36 +00:00
Evgeny Poberezkin
bbab069bcd android: replace while true loop with async recursion (#371) 2022-02-25 11:37:47 +00:00
Evgeny Poberezkin
1cf3b776d7 ios: use core markdown parser, also make messages in android selectable (#372)
* ios: use core markdown parser, also make messages in android selectable

* remove bold font from members in previews

* markdown help

* text selection
2022-02-25 07:16:19 +00:00
Evgeny Poberezkin
1aa2643c18 android: show member names in group messages (#370)
* android: show member names in group messages

* refactor
2022-02-24 18:02:59 +00:00
Evgeny Poberezkin
1150c04298 ios: process commands and messages asynchronously, on the background thread (#367)
* ios: process commands and messages asynchronously, on the background thread

* move model updates to main thread
2022-02-24 17:16:41 +00:00
Efim Poberezkin
9c57ab5221 android: user address; fix add and connect contact views dark mode; chat list view styling (#359) 2022-02-24 12:58:59 +04:00
IanRDavies
3e61b8c21a ios: display name validation (#364)
* try to add warning text if display name has whitespace

* simplify

* layout/error icon

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-02-24 08:45:19 +00:00
IanRDavies
99bed645f3 android: more welcome styling (#363)
* spacing and size updates to welcome page

* spacing and allow space for keyboard
2022-02-24 08:01:41 +00:00
Evgeny Poberezkin
51f5982205 markdown: parse emails and phone numbers (#365)
* markdown: parse emails and phone numbers

* phone parsing

* refactor
2022-02-24 07:55:18 +00:00
Evgeny Poberezkin
b7a06dd0cf show date on the same line as the message if space allows (#362) 2022-02-23 21:48:48 +04:00
IanRDavies
e071e4cdbf add check for whitespace in display name (#360) 2022-02-23 12:40:50 +00:00
Evgeny Poberezkin
470b18786e android: show markdown in messages (#361)
* android: show markdown in messages

* empty line
2022-02-23 12:30:48 +00:00
Evgeny Poberezkin
8f21453e82 fix markdown type for Colored, add types/parsing for formatted text to iOS/android (#358) 2022-02-23 08:45:49 +00:00
Evgeny Poberezkin
fb76917ec3 android: update version/build 0.2 (2) 2022-02-23 07:35:22 +00:00
Evgeny Poberezkin
c53500812c android: fix bottom sheet delay and graying out the rest of the screen (#356) 2022-02-22 20:52:02 +00:00
Evgeny Poberezkin
5e6b9e578b smaller size for unread count, show 1000s as Ks (#355) 2022-02-22 19:43:29 +00:00
Efim Poberezkin
f9c495a596 android: help view (#351)
* android: help view

* chats loaded

* remove comment
2022-02-22 19:38:47 +00:00
IanRDavies
e4ec8cccfd android: theme welcome view page (#354)
* initial theming changes

* styling work round 1
2022-02-22 19:32:43 +00:00
IanRDavies
3be350786d android: update logo (#350)
* add updated icon assets

* pure white splash
2022-02-22 19:31:38 +00:00
Evgeny Poberezkin
7cd43de5d5 Merge pull request #353 from simplex-chat/master
v1.2.1 terminal app
2022-02-22 19:28:17 +00:00
Efim Poberezkin
f698a05d53 1.2.1 2022-02-22 22:21:12 +04:00
Efim Poberezkin
518a15934f prepare v1.2.1 2022-02-22 22:20:32 +04:00
Evgeny Poberezkin
48dbd079cf core: improve markdown parsing and recognise URIs (#352) 2022-02-22 22:18:35 +04:00
IanRDavies
efa22715d5 android: unread message counter (#348)
* add unread counter to chats

* run unread clear on message view for more than a second

* track minUnreadItemId
2022-02-22 15:07:55 +00:00
Evgeny Poberezkin
0d88fcc758 core: send parsed markdown via API (#349) 2022-02-22 14:05:45 +00:00
Efim Poberezkin
353e04bddd android: settings drawer, dark mode user profile view, dark mode previews (#347) 2022-02-22 17:08:42 +04:00
Evgeny Poberezkin
0a6c03079c android: use IconButton (#346) 2022-02-22 08:07:27 +00:00
Evgeny Poberezkin
a0a4549045 android: improve chat, chat info, console (#344)
* bigger fonts, text entry layout

* resize scroll area when keyboard appears; automatically scroll on new messages

* fix message entry in dark mode

* imporove console layout

* fix chat info with dark mode

* fix typo

* clean up

* remove unused time formatter
2022-02-22 07:46:42 +00:00
IanRDavies
69c79c5e0a android: splash screen (to avoid showing welcome screen before the user is loaded) (#345)
* initial attempt -- not recomposing

* change to mutable state, still not working

* two state works, why not three?

* fix so we actually change state

* remove unnecessary brackets

* refactor

* using Boolean? for userCreated

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-02-22 07:29:41 +00:00
Efim Poberezkin
1edf60362e android: UserProfileView (#341)
* android: update user profile view logic

* indentation

* format

* UserProfileView

* remove prints

* empty line

* undo format

* change by value

* separate layout

* layout

* unconditionally editProfile = false

* add header and close button to profile page, add links to "settings"

* use generic navigate in settings, remove terminal button from the list of chats

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-02-21 20:09:51 +00:00
Evgeny Poberezkin
739990c732 terminal: make input responsible for echo to keep commands synchronous (as in mobile) and avoid echo delays (#343)
* terminal: make input responsible for echo to keep commands synchronous (as in mobile) and avoid echo delays

* use echo

Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>

Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>
2022-02-21 12:05:00 +00:00
Evgeny Poberezkin
c9cfead9bc android: refactor sum types (#342) 2022-02-21 09:10:51 +00:00
Evgeny Poberezkin
d37f493c6a android: add chat info page, delete contacts, show network connection status for contacts, improve error handling 2022-02-20 21:17:24 +00:00
Evgeny Poberezkin
b3153ae0fd align time format with iOS app, use kotlix-datetime only (#340) 2022-02-20 16:33:02 +00:00
IanRDavies
7fc5b833aa android: use deep links to connect (#339)
* simple case

* version almost working with true links

* show alerts in imperative way, like they were meant to

* connecting via links works

* add error handling to connections

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-02-20 15:47:24 +00:00
Evgeny Poberezkin
d48d4ed8f9 android app: connect via QR code (#338)
* connecting via QR code works

* add contact/scan qr code pages

* new chat sheet layout

* remove unused imports and some warnings
2022-02-19 22:22:07 +00:00
Efim Poberezkin
f57a7009a3 chat view layout (#335)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-02-19 17:22:52 +04:00
Evgeny Poberezkin
6c4888d275 android app: API, add chat sheet and view with QR code (#336)
* add contact (WIP)

* basic UI to create new chat, finalize API classes and functions (TODO: process chatRecvMsg messages)

* add contact layout with QR code

* refactor NewChatSheet to split layout, refactor withApi

* add newlines

* Update apps/android/app/src/main/java/chat/simplex/app/views/helpers/SimpleButton.kt

Co-authored-by: IanRDavies <ian_davies_@hotmail.co.uk>

Co-authored-by: IanRDavies <ian_davies_@hotmail.co.uk>
2022-02-19 10:15:18 +00:00
IanRDavies
3820d08af8 chat list styling round 2 (#334)
* initial restyling:

* polish styling a little

* lint

* more linting

* add dependency

* add time to messages when they exist

* if no chat items show time from time chat created

* playing with colours

* rename shared colour

* flip title text colour in dark mode
2022-02-18 16:55:50 +00:00
Evgeny Poberezkin
bba2783aa4 update model when messages arrive (#333)
* update model when messages arrive

* update chat in the list when message is added

* copy methods with optional parameters

* use data classes to have pre-defined copy methods
2022-02-18 14:33:55 +00:00
IanRDavies
f650308986 initial chat list styling (#332) 2022-02-18 13:10:24 +00:00
Efim Poberezkin
bd13181042 platform independent json encoding for db (#330) 2022-02-18 14:05:11 +04:00
Evgeny Poberezkin
6daad10210 make condition depend on host os (#329) 2022-02-18 09:00:21 +00:00
Evgeny Poberezkin
52f758c6e1 make chat model not nullable (#328)
* make chat model not nullable

* parse datetimes

* smart constructors for TerminalItem
2022-02-17 21:52:37 +00:00
Evgeny Poberezkin
290a88fd90 list of chats and chat messages (#327) 2022-02-17 20:30:21 +00:00
Evgeny Poberezkin
423f54e95d chats in android app (#324)
* view placeholders for chats list and chat views

* classes for chats

* set the user to the model

* use Long for IDs

* chats/messages API (not working yet)

* android api works

* line breaks
2022-02-17 17:15:49 +00:00
IanRDavies
9e46b5117d Id/conditional nav on launch (#326)
* add initial conditional routing -- create user not working

* only one nav controller

* user check on launch works (kind of)

* 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>
2022-02-17 17:07:58 +00:00
IanRDavies
e8ff6f509b Id/android navigation edits (#325)
* add ids to terminalitems and work with these

* remove unnecessary logging
2022-02-17 10:52:56 +00:00
Evgeny Poberezkin
e7e777ec7b 2 spaces holy war (#323) 2022-02-17 09:15:54 +00:00
Evgeny Poberezkin
f74f932dcd pass IOS devine via GHC options in flake.nix (#322) 2022-02-17 08:40:08 +00:00
Evgeny Poberezkin
7fafb25821 rename file in android app 2022-02-17 08:22:16 +00:00
Evgeny Poberezkin
dd256be4ec use tagged JSON on android, update tests (#321) 2022-02-16 23:24:48 +00:00
Evgeny Poberezkin
d743804b1d update android api to call haskell off main thread (#320) 2022-02-16 21:31:22 +00:00
Evgeny Poberezkin
f8951b44fc use sync commands (#319) 2022-02-16 20:31:26 +00:00
Evgeny Poberezkin
ec70670630 update condition in cabal file 2022-02-16 20:11:29 +00:00
Evgeny Poberezkin
ee07921d42 update cabal file - GHC option for android 2022-02-16 18:49:48 +00:00
Efim Poberezkin
5548494a44 update simplexmq sha (#318) 2022-02-16 22:18:27 +04:00
IanRDavies
7c8ad4aee4 Android compose navigation (#316)
* initial rough ideas

* refactor and put in high level navigation

* refactor

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-02-16 18:00:59 +00:00
Evgeny Poberezkin
12b4325435 switch to the new API (does not work) (#317)
* switch to the new API (does not work)

* kind of works without parsing JSON
2022-02-16 17:36:49 +00:00
Evgeny Poberezkin
241d02584a use different names for different build bundles (#315) 2022-02-16 13:22:36 +00:00
Evgeny Poberezkin
0f450fd9bf update readme (#314)
* update readme

* update README.md

Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>

Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>
2022-02-16 13:00:27 +00:00
Evgeny Poberezkin
ce02c514cf started android / compose app (#301)
* new compose project

* classes for chat command and response

* use val with get() for commands and responses

* chat model

* initial jetpack compose set up

* wire it up with chat

* first ability to send and receive messages

* refactor model/controller interface

* JSON samples

* terminal view with items

* playing around with json

* JSON serialization works

* parsing API responses in the terminal

* add subclass for contactSubscribed reponse

* remove android-poc

* remove JSON example

Co-authored-by: IanRDavies <ian_davies_@hotmail.co.uk>
2022-02-16 12:49:47 +00:00
Efim Poberezkin
322ab9d854 use async commands (#313)
* switch to async

* make tests pass
2022-02-16 12:48:28 +00:00
Efim Poberezkin
d40ee71a2c update simplexmq sha (#312)
* update simplexmq sha

* package build for iOS/Intel simulator

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-02-16 09:38:49 +00:00
Evgeny Poberezkin
c81bb0f15d iOS: show dates in older messages 2022-02-15 08:14:50 +00:00
Evgeny Poberezkin
b7fda194c8 update binaries in iOS app and build number (10) 2022-02-14 21:38:12 +00:00
Evgeny Poberezkin
c37f41c171 use sync commands (#306) 2022-02-14 19:36:15 +00:00
Efim Poberezkin
ced8d2a45f Merge pull request #305 from simplex-chat/master (v1.2.0 terminal app) 2022-02-14 22:41:33 +04:00
Efim Poberezkin
c580c34a35 1.2.0 2022-02-14 21:55:39 +04:00
Efim Poberezkin
fdf312d9e1 ios: add contactNotReady error type (#304) 2022-02-14 21:52:01 +04:00
Evgeny Poberezkin
44d8b549c4 return version number to mobile (#303) 2022-02-14 21:51:50 +04:00
Efim Poberezkin
928dd27043 prepare v1.2.0 (#302) 2022-02-14 21:21:16 +04:00
Efim Poberezkin
4419051347 connection precedence logic in getContact_ (fixes asynchronous establishment of connection) (#300) 2022-02-14 18:49:42 +04:00
Evgeny Poberezkin
8cf88019e5 ios public beta announcement (#298)
* ios public beta announcement

* update post

* corrections

* corrections

* update blog links

Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>
2022-02-14 13:48:21 +00:00
Evgeny Poberezkin
710971a0cd show confirmation alert after the connection (#299)
* show confirmation alert after the connection

* update build number
2022-02-14 11:53:44 +00:00
Efim Poberezkin
dc306dfcd0 option to auto-accept contact requests (#296) 2022-02-14 14:59:11 +04:00
Mark Aleksander Hil
e90520a5ec update banner (#297) 2022-02-14 10:29:16 +00:00
Evgeny Poberezkin
7805bd1e45 show large unread numbers 2022-02-13 10:09:09 +00:00
Efim Poberezkin
c1c55ca700 deduplicate contact requests (#287)
* deprecate XContact

* XInfoId

* xInfoId tests

* merging

* saving on connection

* connectByAddress

* remove old connect

* deduplicate contact requests

* check on contact acceptance

* test

* rename response

* reuse CRContactRequestAlreadyAccepted

* Update src/Simplex/Chat.hs

* createConnReqConnection

* simplify controller logic

* store methods + profile change

* index

* more indices

* unXInfoId

* simplify

* XInfo with ID -> XContact

* sync reply to Connect when contact already exists

* update view for sync CRContactAlreadyExists command response

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-02-13 09:19:24 +00:00
Evgeny Poberezkin
8e34d2fbbc fix swift 2022-02-13 09:13:06 +00:00
Evgeny Poberezkin
61afb64dd7 search chats, longer emojis (#295)
* search chats, longer emojis

* simplify
2022-02-13 08:45:08 +00:00
Evgeny Poberezkin
aa2bc545db update build number (8) 2022-02-12 18:02:52 +00:00
Evgeny Poberezkin
067f122b05 iOS app version 0.3.1 2022-02-12 17:28:37 +00:00
Evgeny Poberezkin
9d9bb68d50 iOS: show message sent/unread status (#293)
* light github image for dark mode

* show message received status, remove chevrons in chat list

* show unread message status

* add message send error mark

* refactor alerts to use AlertManager

* show alert message on tapping undelivered message, simplify text-only alerts
2022-02-12 15:59:43 +00:00
Efim Poberezkin
af5abae558 fix group leave (#294)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-02-12 13:17:11 +04:00
Efim Poberezkin
c59caa5d7f Merge pull request #292 from simplex-chat/master
v1.1.1 terminal app, v0.3 iOS app
2022-02-11 13:06:40 +04:00
Efim Poberezkin
0ea8705014 1.1.1 2022-02-11 12:05:22 +04:00
Efim Poberezkin
92409820fb enable async commands (#290)
* enable async

* fix async command error response

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-02-11 12:03:34 +04:00
Evgeny Poberezkin
98fc6c6adf chat usage help and minor UI fixes (#291)
* chat usage help and minor UI fixes

* update version, build and binary
2022-02-11 07:42:00 +00:00
Efim Poberezkin
771bc6a14d prepare v1.1.1 (#289) 2022-02-10 20:08:29 +04:00
Evgeny Poberezkin
86c36f53e4 simplify and fix background loading (#288)
* simplify and fix background loading

* start receive loop in the main chat
2022-02-10 15:52:11 +00:00
Efim Poberezkin
5c24089f9f check group member connection status before delivery; best effort delivery per group member (#286) 2022-02-10 17:03:36 +04:00
Evgeny Poberezkin
516c8d79ad receiving messages in the background and sending local notifications (#284)
* receiving messages in the background and sending local notifications

* show notifications in foreground and background

* presentation logic for notification options when app is in the foreground

* background refresh works

* remove async dispatch
2022-02-09 22:53:06 +00:00
Efim Poberezkin
ff7a8cade1 test chat items (#285) 2022-02-09 20:58:02 +04:00
Efim Poberezkin
7af4cdffee add unreadCount and minUnreadItemId stats to Chat type (#283) 2022-02-08 20:38:57 +04:00
Efim Poberezkin
b06838b651 add APIChatRead chat command (#282) 2022-02-08 17:27:43 +04:00
Evgeny Poberezkin
b3a4c21c4b updated text items (#278)
* updated text items

* update version

* fix JSON parsing in CIDirection, refactor data samples

* show group member in received messages and chat preview

* use profile displayName instead of localDisplayName, do not show fullName if it is the same as displayName
2022-02-08 09:19:25 +00:00
Efim Poberezkin
855881094b add CRContactConnecting api response (#281) 2022-02-08 13:04:17 +04:00
Efim Poberezkin
82d02e923a ios: add CIStatus type (#280) 2022-02-08 11:20:41 +04:00
Efim Poberezkin
d11d66fa90 connection precedence logic in getDirectChatPreviews_; update item status in object (#279) 2022-02-07 18:34:54 +04:00
Efim Poberezkin
f5507436f3 chat item status, CRChatItemUpdated api response (#269) 2022-02-07 15:19:34 +04:00
Evgeny Poberezkin
eeea33c7cb fix loading chat, contact connection status info (#277) 2022-02-07 10:36:11 +00:00
Evgeny Poberezkin
7883ca7657 improve text message view (#276)
* show text and time on the same line

* convert emails and phones to links
2022-02-06 21:06:02 +00:00
Evgeny Poberezkin
8efb8b2f86 use simplified chat controller, fix keyboard removing on tap (#275) 2022-02-06 18:26:22 +00:00
Evgeny Poberezkin
408a30c25b simplify mobile API to have single controller (#274)
* simplify mobile API to have single controller

* update chat response in swift

* add async to stack
2022-02-06 16:18:01 +00:00
Evgeny Poberezkin
9b67aa537a each command takes lock if it needs it (#273) 2022-02-06 08:21:40 +00:00
Evgeny Poberezkin
5aabf87898 ios: highlight URLs in texts (#272)
* ios: highlight URLs in texts

* Apply suggestions from code review
2022-02-06 07:44:41 +00:00
Evgeny Poberezkin
67dbdcd257 contact and server connection info (#271) 2022-02-05 20:10:47 +00:00
Evgeny Poberezkin
3d137995d8 multiline message entry field (#270) 2022-02-05 14:24:23 +00:00
Evgeny Poberezkin
e424e9328b large emojis, full contact names, contact createdAt, process profile updates, etc. (#268) 2022-02-04 22:13:52 +00:00
Evgeny Poberezkin
214ecf605b minor UI improvements (#267) 2022-02-04 16:31:08 +00:00
Evgeny Poberezkin
7d06d0660d Merge pull request #266 from simplex-chat/ep/fix-utf8-api
fix utf8 encoding for C API requests
2022-02-04 12:46:45 +00:00
Evgeny Poberezkin
c34eddb82a fix utf8 encoding for C API requests 2022-02-04 12:41:43 +00:00
Efim Poberezkin
9969606432 fix utf8 encoding when writing to database 2022-02-04 14:30:00 +04:00
Evgeny Poberezkin
d8abdb7927 Merge pull request #265 from simplex-chat/ep/sync-cmd
fix C string UTF8 encoding, revert to sync commands
2022-02-04 08:50:52 +00:00
Evgeny Poberezkin
71a60795cf Merge pull request #263 from simplex-chat/ep/ios-fixes
configure build for device/simulator
2022-02-04 08:17:18 +00:00
Evgeny Poberezkin
d07ce0b8f4 use 8 byte characters, as encoding is handled elsewhere 2022-02-04 08:15:25 +00:00
Evgeny Poberezkin
565bc70843 sync commands 2022-02-04 08:02:48 +00:00
Efim Poberezkin
7924861810 sort chat items by id (#264) 2022-02-04 11:12:12 +04:00
Evgeny Poberezkin
08dd92b726 configure build for device/simulator 2022-02-03 18:22:05 +00:00
Evgeny Poberezkin
dca5dc4fce iOS version 1.0.1 2022-02-03 07:18:17 +00:00
Evgeny Poberezkin
24f3637199 add animations (#260)
* add animations

* improve settings screen

* app icons
2022-02-03 07:16:29 +00:00
Efim Poberezkin
4dd95c1639 create release as prerelease; fix windows build (#261) 2022-02-03 10:15:38 +04:00
Efim Poberezkin
4724669bce prepare v1.1.0 (#259) 2022-02-02 23:50:43 +04:00
Evgeny Poberezkin
292c334460 make slow commands asynchronous (#258) 2022-02-02 21:47:27 +04:00
Evgeny Poberezkin
dafdf66ada update entity connection status to report it correctly (#257) 2022-02-02 17:01:12 +00:00
Evgeny Poberezkin
38424af48e refactor files, auto-scrollback for messages (#256) 2022-02-02 16:46:05 +00:00
Efim Poberezkin
88a33990b7 sort chats w/t items by time of creation; created_at & updated_at in all tables; merge v1.1 migrations (#255)
* merge migrations; timestamps

* contact created_at

* group, contact request created_at

* sort

* redundant imports
2022-02-02 16:25:36 +00:00
Evgeny Poberezkin
7ce305e16f ios: fix message view updates (refactor model to make it shallow) (#254) 2022-02-02 12:51:39 +00:00
Evgeny Poberezkin
1d1ba8607e send message integrity errors to view as a separate notification (#253) 2022-02-02 11:43:52 +00:00
Evgeny Poberezkin
9f6385f763 update connection status in entity used in controller notifications (#252)
* update connection status in entity used in controller notifications

* remove unused code
2022-02-02 11:31:01 +00:00
Evgeny Poberezkin
a68b591029 connect via link with simplex: protocol (#251) 2022-02-01 20:30:33 +00:00
Evgeny Poberezkin
711207743b add support for user addresses (#246)
* add support for user addresses

* started processing contact requests

* update command syntax

* fix: make Profile Codable

* accept/reject contact requests

* update API, accept/reject contact requests
2022-02-01 17:34:06 +00:00
Efim Poberezkin
a8a7bb3c99 return accepted contact from APIAcceptContact (#250) 2022-02-01 17:04:44 +04:00
Efim Poberezkin
228c118714 api for chat pagination (#249) 2022-02-01 15:05:27 +04:00
Evgeny Poberezkin
0b86402ce3 fix constructor name for JSON encoding (#248) 2022-02-01 07:16:02 +00:00
Evgeny Poberezkin
2295f7a92b update commands (#247) 2022-02-01 09:31:34 +04:00
Evgeny Poberezkin
8e03eefa9b update API commands syntax 2022-01-31 23:20:52 +00:00
Evgeny Poberezkin
53040dbe1d iOS: chats and messages layout (#241)
* iOS: chats and messages layout

* model update for updated API

* improve chat list view

* chat view layouts

* delete contacts

* larger headers, clean up, move message reception loop to ContentView

* settings: user profile
2022-01-31 21:28:07 +00:00
Efim Poberezkin
6d5b5ab44f getContactRequestChatPreviews_ (#245) 2022-01-31 22:43:39 +04:00
Efim Poberezkin
0a18985e68 contact requests api (#244)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-01-31 21:53:53 +04:00
Efim Poberezkin
047aa7deef delete contact api (#243)
* delete contact api

* chat command
2022-01-31 11:14:56 +00:00
Efim Poberezkin
945ed3f7cb fix queries returning duplicate contacts (#242) 2022-01-31 13:20:26 +04:00
Evgeny Poberezkin
e29ea99d2c getChats returns [Chat] with 0-1 item instead of [ChatPreview] (#240) 2022-01-30 21:51:23 +00:00
Evgeny Poberezkin
3b19aaf1d1 iOS: send/receive messages in chats, connect via QR code (#238)
* send messages from chats

* update API to use chat IDs

* send messages to groups

* generate invitation QR code

* connect via QR code
2022-01-30 18:27:20 +00:00
Evgeny Poberezkin
15a91278d6 API to send direct and group messages (#239)
* API to send direct and group messages

* update API parsing
2022-01-30 10:49:13 +00:00
Evgeny Poberezkin
cb602dd377 show received messages in chat, send command on Enter, fix Date parsing (#237)
* refactor UI and API, send command on Enter, fix Date parsing

* UI sheets to create connection and groups

* show received messages

* readme
2022-01-29 23:37:02 +00:00
Efim Poberezkin
7e2f365c1c ios: group chat preview (#235) 2022-01-29 20:35:20 +00:00
Evgeny Poberezkin
8425be0612 use aeson fork with nullableToObject option to make JSON compatible with Swift (#236) 2022-01-29 20:21:37 +00:00
Efim Poberezkin
c0199a38fd add readme for ios setup (#234) 2022-01-29 16:53:24 +04:00
Efim Poberezkin
d97a8c1934 getGroupChat, getGroupChatPreviews_ (#233) 2022-01-29 16:06:08 +04:00
Evgeny Poberezkin
7c36ee7955 swift API for chat, started chat UI (#228)
* started swift API for chat

* skeleton UI

* show all chat responses in Terminal view

* show chat list in UI

* refactor swift API
2022-01-29 11:10:04 +00:00
Efim Poberezkin
55dde3531e most recent chat items in getDirectChatPreviews_ (#232) 2022-01-28 19:24:31 +04:00
Evgeny Poberezkin
c3a8ae1eb5 chats API for mobile (#230)
Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>
2022-01-28 14:41:09 +04:00
Efim Poberezkin
edc9560d36 getDirectChat (#227)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-01-28 11:52:10 +04:00
Evgeny Poberezkin
37cfb93217 switch to JSON single field encodings for sum types to align with Swift enums (#229) 2022-01-27 22:01:15 +00:00
Evgeny Poberezkin
28ee40074a update sha256map.nix 2022-01-26 22:49:44 +00:00
Evgeny Poberezkin
0ba4598ca2 JSON encoding for ChatResponse and all other types used in mobile API (#226)
* JSON encoding for ChatResponse and all other types used in mobile API

* omit null corrId in response, refactor

* more JSON field names
2022-01-26 21:20:08 +00:00
Efim Poberezkin
ecb5b0fdeb add getChatPreviews to Store (#225) 2022-01-26 21:19:46 +04:00
Efim Poberezkin
6cf23f1fd1 chat items (#223)
* add chat items migration

* chat and chat items types

* queries draft

* ChatInfo with optional ChatItem

* schema adjustments

* flat schema and queries

* refactor ChatResponse using ChatItem types

* schema adjustments

* refactor GroupInfo to include GroupMember of the user

* remove Message

* createNewChatItem, sendDirectChatItem

* refactor to use GroupInfo in Chat type and all ChatResponses

* replace ContactName with Contact in some ChatResponse constructors

* remove Group selectors

* minor correction

* refactor

* refactor 2

* nullable created_by_msg_id

* remove normalized schema and queries

* ON DELETE CASCADE / SET NULL

* CIContent to Text

* files chat_item_id

* fix

* apply ciContentToText

* queries folder

* refactor

* moar refactor

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-01-26 16:18:27 +04:00
Evgeny Poberezkin
b86f034c0b update C api to return JSON messages via chat_recv_msg (#224) 2022-01-24 22:52:55 +00:00
Evgeny Poberezkin
ce3d7f21b0 haskell nix flake CI (#222)
* Adds preliminary flake

This nix flake should be enough to try and build an android library.

* add sha256map

* bump

* bump index-state

Co-authored-by: Moritz Angermann <moritz.angermann@gmail.com>
2022-01-24 19:42:41 +00:00
Evgeny Poberezkin
b38d5f3465 started chat model (#221)
* started chat model

* refactor processing commands and UI events

* message chat event processing

* groups: delayed delivery of messages and introductions to announced members (#217)

* combine migrations, rename fields

* show all view messages vis ChatResponse type

* serialize chat response

* update C api

* remove unused extensions, fix typos

Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>
2022-01-24 16:07:17 +00:00
Evgeny Poberezkin
a5ad0b185c use Haskell library (#220) 2022-01-22 17:54:22 +00:00
Evgeny Poberezkin
4f5e135992 test android app (#219) 2022-01-22 17:54:06 +00:00
Evgeny Poberezkin
50d83d2374 prepare v1.0.2 (#218)
* update dependencies

* update version and dependencies

* add tls@1.5.7 to stack.yaml

* update readme

Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>
2022-01-21 18:58:43 +00:00
Evgeny Poberezkin
64381be91d export C interface, started mobile app (#210)
* initial mobile app design draft

* add proposals

* xcode project

* refactor function to send to view as parameter

* export C interface

* remove unused files

* run chat from chatInit

* split chatStart to a separate function

* replace file-embed with QQ

* add mobile views

* server using IP address

* pass dbFilePrefix as parameter to chatInit

* comment on enabling logging

* fix mobile db config

* update C API, make user non-optional in ChatController

* restore SMP server addresses

* revert the change in the tests

* flip dependency - now Controller depends on Terminal

* make ChatController independent of terminal package

* fix Main.hs

* add iOS .gitignore

* refactor Simplex.Chat.Terminal

Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>
2022-01-21 11:09:33 +00:00
Evgeny Poberezkin
f47494e5c8 add loggin option to test 2022-01-20 20:23:21 +00:00
Efim Poberezkin
32a105bac8 fix Windows CI to fail when commands fail, use fixed terminal package (#214)
* fix windows CI

* use fixed terminal package

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-01-20 20:19:39 +00:00
Efim Poberezkin
65b17c9d18 add option to enable logging (#216)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-01-20 12:18:00 +04:00
Evgeny Poberezkin
aef159b097 readme: building from stable branch 2022-01-18 20:39:16 +00:00
Evgeny Poberezkin
d29a6542de 1.0.1 2022-01-18 20:19:05 +00:00
Evgeny Poberezkin
aef697e30a make tests independent of JSON fields order (#212) 2022-01-17 12:24:55 +00:00
Evgeny Poberezkin
fca063e131 Fork/fix terminal libary to work on Apple M1 (#211)
* use forked terminal with CapiFFI (fails to compile)

* update terminal package git tag

* add terminal fork to stack.yaml
2022-01-16 15:22:58 +00:00
Efim Poberezkin
8a859044cb fix explanation of server fingerprint (#207) 2022-01-13 10:23:34 +04:00
Evgeny Poberezkin
895e3878f9 add cabal.project (#205)
* add cabal.project

* update meta-data in package.yaml
2022-01-12 21:18:54 +00:00
Evgeny Poberezkin
b2556e3306 add blog (#187)
* blog: v1 release notes

* Update 20220112-simplex-chat-v1-released.md (#181)

* Update 20220112-simplex-chat-v1-released.md (#183)

updated intro and "journalist" description.

* add blog posts

* make corrections to v1 blog (#188)

* update 20210512-simplex-chat-terminal-ui.md (#192)

* Update 20210512-simplex-chat-terminal-ui.md

* Update 20210914-simplex-chat-v0.4-released.md

* Update 20210914-simplex-chat-v0.4-released.md

* update blog post

* add blog toc and old post

Co-authored-by: Mark Aleksander Hil <32651095+markaleksanderh@users.noreply.github.com>
Co-authored-by: Rob Chandhok <rob@chandhok.net>
Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>
2022-01-12 19:52:40 +00:00
Efim Poberezkin
eebc24086b fix installation script (#204) 2022-01-12 23:16:05 +04:00
Evgeny Poberezkin
94bbc44960 1.0.0 2022-01-12 18:06:30 +00:00
Evgeny Poberezkin
78712541f0 update "incompatible link" message 2022-01-12 18:01:57 +00:00
Evgeny Poberezkin
2b4bdf39fb Merge pull request #196 from simplex-chat/v1
v1
2022-01-12 17:44:05 +00:00
Evgeny Poberezkin
a8faaef54e team user address, remove onboarding 2022-01-12 17:37:46 +00:00
Evgeny Poberezkin
44bad8e093 rename migration file 2022-01-12 16:42:08 +00:00
Efim Poberezkin
a988ab84f9 restore bracket in readme 2022-01-12 20:40:29 +04:00
Evgeny Poberezkin
85e2013639 update simplexmq version 2022-01-12 16:39:08 +00:00
Evgeny Poberezkin
1978801561 add tests for group deletion; remove restrict constraints (#203)
* add tests for group deletion

* update constraints

* move index

Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>
2022-01-12 16:32:22 +00:00
Mark Aleksander Hil
95a4da71cb update banner image (#202)
* Add files via upload

* Updated logo

Added line back in
2022-01-12 16:31:05 +00:00
Efim Poberezkin
f13a65ca85 update chat README for v1 (#201)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-01-12 16:24:41 +04:00
Evgeny Poberezkin
e87be44134 update server addresses 2022-01-12 11:54:40 +00:00
Evgeny Poberezkin
fb8dfa02f2 merge database migration, rename field in group_members: inv_queue_info to sent_inv_queue_info (#200) 2022-01-12 10:35:52 +00:00
Evgeny Poberezkin
67e0ca28a9 additional notifications (#199) 2022-01-12 06:55:04 +00:00
Evgeny Poberezkin
7438db0a7d update file chunk size (#198) 2022-01-12 06:07:49 +00:00
Evgeny Poberezkin
b47f064115 Simplex chat logo (#197)
* Updated SimpleX Chat logo

* Updated logo

Co-authored-by: Mark Aleksander Hil <32651095+markaleksanderh@users.noreply.github.com>
2022-01-11 21:28:15 +00:00
Efim Poberezkin
d9afc47993 update install.sh to check for v0 and ask to continue (#184)
* update install.sh to check for v0 and ask to continue

* pseudo code

* pseudo

* continue

* continue

* continue

* implement logic

* tab

* full path to agent db

* Update install.sh

* Update install.sh

* Update install.sh

* Update install.sh

* Update install.sh

* Update install.sh

Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>

* Update install.sh

* Update install.sh

Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-01-11 21:26:51 +00:00
Evgeny Poberezkin
fcee108863 Merge branch 'master' into v1 2022-01-11 21:25:18 +00:00
Evgeny Poberezkin
5a74b8066f prepare v1 release (#189)
* update servers

* update version

* update simplexmq version

* update database file names

* update server fingerprints and simlexmq

* update simplexmq commit

* fix port in tests

* update tls fixtures (#193)

* add -v cli option; print update instructions on -v and /v (#194)

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>
2022-01-11 21:23:57 +00:00
Efim Poberezkin
809a87ce61 script to add message views to database (#195) 2022-01-11 23:22:59 +04:00
Evgeny Poberezkin
c2c05816f3 binary encoding for file chunks (#191) 2022-01-11 12:41:38 +00:00
Evgeny Poberezkin
cc4fff0ae5 tests for JSON message encoding/decoding (#190)
* tests for JSON message encoding/decoding

* fix XContact parsing to allow absent field "content"

* update XContact JSON encoding
2022-01-11 12:27:57 +00:00
Evgeny Poberezkin
be537f3a24 update chat protocol to use JSON encoding for chat messages (#182)
* started chat protocol

* text message example

* events json

* same style comments

* jsonc

* num for rendering

* try to fix comment rendering

* revert num

* chat protocol: make msg params closer to types

* AppMessage type

* combine new and old simplexmq dependencies

* json parsers

* version-compatible types for connection requests

* more parsers

* remove import

* decode/encode from/to AppMessage

* make group invitation a property in params

* switch chat to the new agent

* remove "compatibility" attempt

* new JSON encoding for chat messages

* simplexmq from github

* update MsgContent name

Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>
2022-01-11 08:50:44 +00:00
Efim Poberezkin
7498cd4432 0.5.5 (#179) 2022-01-07 11:32:06 +04:00
Efim Poberezkin
5e545b639f update simplex-chat.cabal (#178) 2022-01-07 11:28:39 +04:00
Evgeny Poberezkin
1093b01e7e update simplex.md (#133)
* switch to ghc-8.10.7 (lts-18.17 resolver) (#125)

* update simplex.md

Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>
2022-01-06 23:11:53 +00:00
Evgeny Poberezkin
44845ad563 refactor closure (#177) 2022-01-06 20:29:57 +00:00
Efim Poberezkin
1bfa7f1104 allow to repeat group invitation using saved queue info; recognize it's the same group at invitee (#176)
* naming; full names on start for groups

* allow to re-add member

* save and reuse connection request

* TODO

* wording

* index

* user id

* revert to listToMaybe . map fromOnly

* add to test

* fix null conversion

* Update src/Simplex/Chat.hs

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>

* Update src/Simplex/Chat.hs

* fix

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-01-06 23:39:58 +04:00
Evgeny Poberezkin
79658b3d8d update simplexmq to 0.5.2, update resolver (#175)
* groups when in status invited - list as invitations on /gs

* don't list on start

* test

* refactor

* getUserGroupDetails

* update simplexmq to 0.5.2, update resolver

Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>
2022-01-06 16:03:45 +00:00
Efim Poberezkin
962287c439 unprocessed group invitations - highlight, print on start (#174) 2022-01-06 14:24:33 +04:00
Efim Poberezkin
ea89c9d8c8 groups when in status invited - list as invitations on /gs; do not list on start (#173) 2022-01-06 13:09:03 +04:00
Efim Poberezkin
7c723213c2 allow invitee to delete group when in status invited (#172) 2022-01-05 20:46:35 +04:00
Efim Poberezkin
f29614058a 0.5.4 (#171) 2021-12-30 18:35:39 +04:00
Efim Poberezkin
8033c8648b update README for v0.5.4 (#170) 2021-12-30 18:27:19 +04:00
Efim Poberezkin
3160a9559a don't broadcast x.grp.mem.del when removing group member with status "invited" (#169) 2021-12-30 17:36:24 +04:00
Efim Poberezkin
74cb3a3cc0 fix contact field in all_messages_plain view (#168) 2021-12-30 14:22:13 +04:00
Efim Poberezkin
f2735020e3 improve README instructions on querying messages (#167) 2021-12-30 13:21:34 +04:00
Efim Poberezkin
81f29d679b store messages (#166)
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2021-12-29 23:11:55 +04:00
Efim Poberezkin
a7703209f2 change tests port (fix for port 5000 now in use on macOS) (#165) 2021-12-27 15:15:35 +04:00
Evgeny Poberezkin
6e48fe3f72 0.5.3 2021-12-24 11:36:04 +00:00
Evgeny Poberezkin
29b683329d show "upgrade" message on invalid connection request (#164) 2021-12-24 11:12:08 +00:00
Evgeny Poberezkin
e7f9e5a834 only use notify-send when present (#163) 2021-12-20 12:24:28 +00:00
Evgeny Poberezkin
66ab5bc424 0.5.2 2021-12-19 15:43:39 +00:00
Evgeny Poberezkin
279f8c5453 add CODEOWNERS (#160) 2021-12-19 15:25:19 +00:00
Evgeny Poberezkin
0e91f10851 fix welcome message type (#159) 2021-12-19 15:11:08 +00:00
Evgeny Poberezkin
4856f6e3e4 Update FUNDING.yml 2021-12-18 16:27:27 +00:00
Evgeny Poberezkin
0ccf431002 add simplex-chat.cabal file (#158) 2021-12-18 13:59:06 +00:00
Evgeny Poberezkin
433200bab9 0.5.1 2021-12-18 12:56:34 +00:00
Evgeny Poberezkin
9513a47860 update version to 0.5.1 (#157) 2021-12-18 12:54:38 +00:00
Evgeny Poberezkin
96176936e6 update welcome messages (#156)
* simple welcome message

* show welcome message only once

* show onboarding progress

* admin and groups

* show full group names with /gs command

* Update src/Simplex/Chat/Help.hs

Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>

* Update src/Simplex/Chat/Help.hs

Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>
2021-12-18 10:23:47 +00:00
Evgeny Poberezkin
20e7feb953 simple welcome message (#152)
* simple welcome message

* show welcome message only once

* show onboarding progress
2021-12-13 12:05:57 +00:00
Evgeny Poberezkin
7fa671f829 show confirmation when invitation accepted or contact request sent (#150)
* show confirmation when invitation accepted or contact request sent

* refactor
2021-12-11 12:57:12 +00:00
Evgeny Poberezkin
1c2e49ae83 trim trailing whitespace, additional commands to list contacts and groups (#149) 2021-12-10 11:45:58 +00:00
Mark Aleksander Hil
2e56b3cb58 Added Reddit badge (#148)
* Added Reddit badge

* Update README.md

Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>

Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>
2021-12-09 12:45:42 +00:00
Evgeny Poberezkin
642cec3092 0.5.0 2021-12-08 20:21:27 +00:00
Evgeny Poberezkin
1564424f0d update readme and version in code (#147)
* update readme and version in code

* Update README.md

* Update README.md

Co-authored-by: Patryk Laurent <plaurent@me.com>

Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>
Co-authored-by: Patryk Laurent <plaurent@me.com>
2021-12-08 20:18:48 +00:00
Evgeny Poberezkin
177c007edc Permanent user addresses (aka contact links) (#139)
* update for ConectionMode parameters

* update with CONF notification and different ConnectionRequest types

* high level flow for contact requests, add x.con to chat protocol

* store functions for user contact links and contact requests

* contact links work

* subscribe to user contact link connection

* subscribe to user contact address: messages

* send rejectContact to the agents when rejected in chat

* user contact link (address) test

* Update src/Simplex/Chat/View.hs

* Update tests/ChatTests.hs

* user address help, fix tests

* delete connection requests when contact link deleted

Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>
2021-12-08 13:09:51 +00:00
Mark Aleksander Hil
d279c144a6 Removed horizontal lines (#144)
* Removed horizontal lines

Removed horizontal lines - page looked a bit cluttered

* remove extra line breaks

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2021-12-07 21:12:25 +00:00
Efim Poberezkin
ba2378e5d6 add user addresses section to readme (#145) 2021-12-07 23:42:06 +04:00
Mark Aleksander Hil
b7b393b993 Replace logo image (#143) 2021-12-07 08:47:47 +00:00
Mark Aleksander Hil
d5e66e2284 Edited readme and added new logo image (#142)
* Edited readme and added new logo image

* Update README.md

Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>

* update readme

* update readme

* extra spaces

Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2021-12-07 07:03:54 +00:00
Efim Poberezkin
2ce3cd2fad install script: exit on failure, conditional curl/wget (#140) 2021-12-05 20:01:34 +04:00
Evgeny Poberezkin
e4328cb98d use ConnectionRequest syntax instead of "queue information" (#137) 2021-12-02 11:17:09 +00:00
Efim Poberezkin
498181b2e9 add quick installation & welcome sections to readme (#135) 2021-12-01 02:59:32 +10:00
Eliaz Bobadilla
6c8fb9e6d0 📝 Update readme install instructions (#134)
* 📝 Update readme install instructions

* Update README.md

* Update README.md

* Update README.md

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2021-11-29 22:33:22 +00:00
Efim Poberezkin
e5f13adc2a fix cla workflow trigger (#132) 2021-11-28 11:35:04 +00:00
Evgeny Poberezkin
d9b3742f62 add install.sh script (#129)
*  add script installer

* Update install.sh

Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>

* ♻️ Update

* ✏️ Typos

* ♻️ Move export to .profile

* ♻️ Update

* ♻️ Update

* Update install.sh

* Update install.sh

* Update install.sh

* Update install.sh

* install.sh: add simplex-chat folder to path conditionally

Co-authored-by: Eliaz Bobadilla <eliaz.bobadilladev@gmail.com>
Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>
2021-11-28 10:08:26 +00:00
Evgeny Poberezkin
800a4f90bf add version command (#123)
* Add /version and /v flags

* Move version info from C Chat.Help to Chat.View

* Move version string, use it in Main startup

* use "SimpleX Chat" consistently

Co-authored-by: TheTaoOfSu <TheTaoOfSu@protonmail.com>
2021-11-07 21:57:05 +00:00
Evgeny Poberezkin
deaea44024 CLA workflow (#122)
* CLA: doc and workflow

* update CLA version and signatures path

* update workflow target

* update cla signatures and doc location to simplex-chat/cla repo

* remove CLA.md
2021-11-07 19:40:52 +00:00
Efim Poberezkin
23468f0afd add section Troubleshooting on Unix to README (#113) 2021-10-05 01:44:36 +10:00
Evgeny Poberezkin
8b7d6e5f19 0.4.2 2021-09-26 16:57:52 +01:00
Evgeny Poberezkin
eb1ab8f561 fix sending notification containing apostrophe on mac, wrap notification in exception handler (#107) 2021-09-26 16:36:05 +01:00
Vsevolod Mineev
883887c569 update readme (#106)
Added clarification that you are able to create multiple invitations by entering /connect multiple times without invalidating the previously created invitations.
2021-09-26 15:48:52 +01:00
Evgeny Poberezkin
62a8ac4b21 0.4.1 2021-09-25 10:12:22 +01:00
Evgeny Poberezkin
e9180ed0dc fix small file transfer, closes #104 (#105) 2021-09-25 10:09:49 +01:00
Efim Poberezkin
2bf6d08a16 update gifs for larger font; readme fixes (#103)
* update gifs for larger font; readme fixes

* return articles

* return articles
2021-09-12 14:20:13 +01:00
Evgeny Poberezkin
43fc819f77 0.4.0 2021-09-11 20:44:01 +01:00
Evgeny Poberezkin
471652a042 Merge pull request #65 from simplex-chat/v4
v0.4.0
2021-09-11 20:18:53 +01:00
Evgeny Poberezkin
3a2c7927e1 update simplexmq version (#102)
* update simplexmq version

* update simplexmq version 0.4.1
2021-09-11 19:50:00 +01:00
Efim Poberezkin
4360d34847 update readme (#99)
* update readme

* case

* fixes, roadmap

* wording

* features

* update gifs and images

* block warning

* add link from announcement to groups

* update default servers

* update readme

* update roadmap, image, etc.

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2021-09-11 17:00:59 +01:00
Efim Poberezkin
46cf314403 update default smp servers (#101)
* update default smp servers

* add smp3 server
2021-09-11 13:45:22 +01:00
Efim Poberezkin
fe5769156c correctly print both db files (#100) 2021-09-07 01:08:29 +10:00
Evgeny Poberezkin
28103825fa send files to groups (#97)
* add sender/recipient info to file types

* send file to group (WIP)

* send file to group, test

* show file status when sending file to group

* notification when cancelled sending to group, remove chunks when file complete or canceleld
2021-09-05 14:08:29 +01:00
Efim Poberezkin
4bbdcc1d06 update help - file transfer and groups (#98)
* update help - file transfer and groups

* update help

* update help

* update help

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2021-09-04 20:38:11 +01:00
Evgeny Poberezkin
c51493e016 send files to contacts (#94)
* schema for sending files

* send file "invitation"

* receive file "invitation"

* send/receive file flow (with stubs)

* update simplexmq

* send and receive the file (WIP - only the first chunk)

* sending and receiving file works (but it is slow)

* use correct terminal output for file sending/receiving

* improve file transfer, support cancellation

* command to show file transfer status and progress

* file transfer tests

* resume file transfer on restart (WIP)

* stabilize test of recipient cancelling file transfer

* trying to improve file transfer on restart

* update SMP block size and file chunk size

* acquire agent lock before chat lock to test whether it avoids deadlock

* fix resuming sending file on client restart

* manual message ACK (prevents losing messages between agent and chat client and stabilizes resuming file reception after restart)

* do NOT send file chunk after receiving it before it is appended to the file

* update file chunk size for SMP block size 8192 (set in smpDefaultConfig)

* save received files to ~/Downloads folder by default; create empty file when file is accepted

* keep file handle used to create empty file

* check message integrity

* fix trying to resume sending file when it was not yet accepted

* fix subscribing to pending connections on start

* update simplexmq (fixes smp-server syntax parser)
2021-09-04 07:32:56 +01:00
Efim Poberezkin
97fde7ecd0 subscribe pending connections on chat start (#95) 2021-08-28 20:54:53 +10:00
Evgeny Poberezkin
9cfca4ed35 update user profile (and notify contacts) (#93)
* update user profile (and notify contacts)

* add concurrently to profile update test for better layout
2021-08-22 15:56:36 +01:00
Evgeny Poberezkin
e5b9cdef9d update for asynchronous message delivery (#92) 2021-08-14 21:04:51 +01:00
Evgeny Poberezkin
f3c64f3fc7 Merge branch 'master' into v4 2021-08-06 21:51:21 +01:00
Nikita Poberezkin
2884bf73b7 escape/remove symbols in notifications for all platforms (#88)
* escape backtick, backslash, double quotes wsl/win

* remove quotes, backtick from win notifications to prevent chat from crashing

* put notification title and text in single quotes and escape single quotes inside

* escape notifications for mac (draft)

* add replaceAll func

* remove unused import

* imports

* refactor replaceAll

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2021-08-06 21:41:35 +01:00
Evgeny Poberezkin
d23417596e TMVar lock to avoid subscriber and client processing in parallel, fix the test (#90)
* TMVar lock to avoid subscriber and client processing in parallel, fix the test

* run SMP server as part of the test

* stabilize tests

* update simplexmq

* test: stabilize getting invitation from terminal

* remove unused import

* simplify test
2021-08-05 20:51:48 +01:00
Evgeny Poberezkin
a9d32db404 update for SMP agent protocol changes (#89) 2021-08-05 08:38:39 +01:00
Evgeny Poberezkin
b798342c61 group commands (remove member, leave group, delete group) (#87)
* remove group member

* leave group, fix remove member, tests for leave group/remove member

* delete group with test

* prevent contact deletion error when it is a group member

* support inviting the group member who left or was removed

* use small retry interval in the tests

* test multiline outputs
2021-08-02 20:10:24 +01:00
Evgeny Poberezkin
b7c4a6e195 Merge branch 'master' into v4 2021-07-27 08:10:20 +01:00
Evgeny Poberezkin
2d1ff5fb4b Merge branch 'master' into v4 2021-07-27 08:09:48 +01:00
Evgeny Poberezkin
b3af93e0ad merge profiles using contact probe (#86)
* chat commands to list members and to quit chat

* merge profiles using probe

* merge contacts connected to the same user based on successful profile probe

* delete display name after merging contacts

* probe: rename "existing" contacts to "matching"
2021-07-27 08:08:05 +01:00
Evgeny Poberezkin
cc4cb78209 subscribe all user contacts and group members (#85) 2021-07-25 20:23:52 +01:00
Evgeny Poberezkin
488df1aa3c refactor groups (#84)
* refactor groups

* disable chat test

* remove comments
2021-07-24 18:11:04 +01:00
Evgeny Poberezkin
189cd7e09d core chat groups protocol for adding members (#78)
* add category and local display name to group members, extend member status

* additional chat commands, serialization

* parse all chat messages

* draft group protocol implementation

* group protocol: connect new member to existing members (TODO fix race condition with contact connection)

* send/receive group messages (race condition still there - the 3rd member cannot send either group or direct messages to the 2nd member - CONN SIMPLEX)

* send x.grp.mem.info and x.ok in SMP confirmation

* fix host user adding new member, update simplexmq to fix sqlite concurrency, remove logs, make # optional in chat commands

* more precise view messages about members joining and connecting

* track connection status; only send messages to active members (TODO change to current members); group name autocomplete after joining the group

* track via which group the contact was added; show only one message when a contact fully connected; group tests

* test sending messages to the new direct contacts created via the group

* update simplexmq to include .cabal file

* remove unused import
2021-07-24 10:26:28 +01:00
Nikita Poberezkin
94f89ed8f7 merge master to v4 (#83)
* update ghc version to 8.10.4 for Docker build (#67)

* make broader check for WSL on notifications (#68)

* update readme: network topology and disclaimer on encryption design (#73)

* update readme with the disclaimer on encryption design and explanation of the network topology

* corrections

* remove old disclaimer

Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>

* create appDir if absent (#79)

Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2021-07-24 09:57:10 +01:00
Nikita Poberezkin
3c942f6f3e create appDir if absent (#79) 2021-07-23 07:41:10 +01:00
Evgeny Poberezkin
f1a44383fa chat groups: establish connection between host and invitee members (#77)
* create group after invitation

* add group invitation to db, show sent and received group invitations

* test creating group and sending invitation

* establish group connections (WIP)

* connect user to the inviter, notification, member classification
2021-07-16 07:40:55 +01:00
Evgeny Poberezkin
e9d931059b use shared namespace for usernames, contact names and group names (#76)
* test adding same contact, add display_names table and functions

* rename display_name -> full_name

* use shared namespace for usernames, contact names and group names
2021-07-14 20:11:41 +01:00
Evgeny Poberezkin
e99c4bda1e started chat groups protocol (#75)
* create group

* add user as member, store methods to get group and to create group member

* add group member and send member invitation

* fix ci: use simplexmq from github

* chat protocol: create SMP agent connection when inviting member

* update protocol, started group invitation receiving
2021-07-12 19:00:03 +01:00
Evgeny Poberezkin
24c62584fc simplify chat protocol (#74)
* groups protocol and some group commands

* simplify chat message format, refactor types to include parsed message body

* disable chat test
2021-07-11 12:22:22 +01:00
Evgeny Poberezkin
44496bc003 update readme: network topology and disclaimer on encryption design (#73)
* update readme with the disclaimer on encryption design and explanation of the network topology

* corrections

* remove old disclaimer

Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>
2021-07-07 22:58:53 +01:00
Evgeny Poberezkin
d21abbdec1 chat test with VirtualTerminal (#72)
* chat test with VirtualTerminal

* disable chat test

* fix intermittently failing test

* simplify test
2021-07-07 22:46:38 +01:00
Evgeny Poberezkin
25ac250d37 use chat message format to pass profile information, refactor (#71) 2021-07-06 19:07:03 +01:00
Evgeny Poberezkin
85727bfbf1 move files to src folder (to allow testing) (#70) 2021-07-05 20:05:07 +01:00
Evgeny Poberezkin
58889be83d establish connection using user profiles (#69)
* establish connection using user profiles (TODO: delete contact and send message)

* delete contact and send message with the updated schema

* comment

* refactor, remove old code
2021-07-05 19:54:44 +01:00
Evgeny Poberezkin
2f604d91ba use chat protocol and contacts in chat commands/messages (#66)
* chat types, chat protocol syntax idea

* chat message syntax, raw message type

* chat message format and parsing

* raw chat message parsing test

* add message parsing tests

* interpret RawChatMessage

* use chat message format when sending messages

* save contacts and related connections to DB (WIP)

* use contacts in all chat commands (add, connect, send, delete)

* use contacts when receiving messages and notifications

* handle contact not found error

* automatically accept connection when CONF is received from the agent
2021-07-04 18:42:24 +01:00
Efim Poberezkin
c6f1858ca0 make broader check for WSL on notifications (#68) 2021-07-02 00:37:19 +10:00
Efim Poberezkin
321f4bbe9d update ghc version to 8.10.4 for Docker build (#67) 2021-07-01 00:37:47 +10:00
Evgeny Poberezkin
c3d5797a0b Merge branch 'master' into v4 2021-06-26 20:20:33 +01:00
Nikita Poberezkin
32d90580e7 desktop notifications (#64)
* send notifications

* support for linux notifications (draft)

* add support for linux, win (draft) and wsl (draft) notifications

* add support for windows/wsl notifications

* add unix to extra-deps

* add alternative linux notification method

* remove unused cpp conditions

* fix notification commands for win/lin

* remove dbus package and code

* remove fdo-notify from extra-deps

* move script running logic to common method + add lacking quotes

* remove unrelated workspace file

* corrections

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2021-06-26 19:48:08 +01:00
Evgeny Poberezkin
5a2ded775d rename app folder (#63)
* rename app folder

* clean up package.yaml
2021-06-25 18:34:29 +01:00
Evgeny Poberezkin
eb2404c9ce simplex-chat schema, refactor chat to use SMP agent functions (#62)
* chat messages namespace and types

* initial schema (WIP)

* schema for messages (WIP)

* fix schema, add migrations, remove broadcast

* simplex-chat spike (WIP)

* chat client design

* update chat schema

* more chat schema updates

* simplex-chat app structure

* chat app layout demo

* update schema

* refactor dog-food (WIP)

* refactor / simplify

* refactor output of sent message to avoid separate parsing

* refactor inputSubscriber

* remove unused simplex-chat code

* update simplexmq commit

* update schema

* remove ncurses
2021-06-25 18:18:24 +01:00
Evgeny Poberezkin
4232f73ed2 support ad-hoc groups (broadcasts) (#61)
* support ad-hoc groups (broadcasts)

* fake group chat

* use simplexmq latest
2021-06-10 20:34:52 +01:00
Efim Poberezkin
e4f3414b0b add missing dot (#58) 2021-05-16 18:58:19 +04:00
Evgeny Poberezkin
d4ecd27067 add gif to readme (#59) 2021-05-12 19:33:50 +01:00
Evgeny Poberezkin
723c787edc 0.3.1 (#57) 2021-05-10 19:49:21 +01:00
1346 changed files with 246454 additions and 1330 deletions

1
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1 @@
* @epoberezkin @jr-simplex

1
.github/FUNDING.yml vendored
View File

@@ -1 +1,2 @@
github: simplex-chat
open_collective: simplex-chat

View File

@@ -1,4 +1,4 @@
{
"template": "${{UNCATEGORIZED}}",
"pr_template": "- ${{TITLE}}\n"
"template": "Commits:\n${{UNCATEGORIZED}}",
"pr_template": "- ${{TITLE}}"
}

View File

@@ -4,6 +4,8 @@ on:
push:
branches:
- master
- stable
- users
tags:
- "v*"
pull_request:
@@ -31,6 +33,7 @@ jobs:
uses: softprops/action-gh-release@v1
with:
body: ${{ steps.build_changelog.outputs.changelog }}
prerelease: true
files: |
LICENSE
fail_on_unmatched_files: true
@@ -47,54 +50,106 @@ jobs:
matrix:
include:
- os: ubuntu-20.04
cache_path: ~/.stack
stack_args: "--test"
artifact_rel_path: /bin/dog-food
asset_name: dog-food-ubuntu-20_04-x86-64
- os: ubuntu-18.04
cache_path: ~/.stack
stack_args: "--test"
artifact_rel_path: /bin/dog-food
asset_name: dog-food-ubuntu-18_04-x86-64
cache_path: ~/.cabal/store
asset_name: simplex-chat-ubuntu-20_04-x86-64
- os: ubuntu-22.04
cache_path: ~/.cabal/store
asset_name: simplex-chat-ubuntu-22_04-x86-64
- os: macos-latest
cache_path: ~/.stack
stack_args: "--test"
artifact_rel_path: /bin/dog-food
asset_name: dog-food-macos-x86-64
# TODO enable tests for windows once fixed (remove stack_args altogether)
cache_path: ~/.cabal/store
asset_name: simplex-chat-macos-x86-64
- os: windows-latest
cache_path: C:/sr
stack_args: ""
artifact_rel_path: /bin/dog-food.exe
asset_name: dog-food-windows-x86-64
cache_path: C:/cabal
asset_name: simplex-chat-windows-x86-64
steps:
- name: Clone project
uses: actions/checkout@v2
- name: Setup Stack
- name: Setup Haskell
uses: haskell/actions/setup@v1
with:
ghc-version: '8.8.4'
enable-stack: true
stack-version: 'latest'
ghc-version: "8.10.7"
cabal-version: "latest"
- name: Cache dependencies
uses: actions/cache@v2
with:
path: ${{ matrix.cache_path }}
key: ${{ matrix.os }}-${{ hashFiles('stack.yaml') }}
path: |
${{ matrix.cache_path }}
dist-newstyle
key: ${{ matrix.os }}-${{ hashFiles('cabal.project', 'simplex-chat.cabal') }}
- name: Build & test
id: build_test
# / Unix
- name: Unix prepare cabal.project.local for Mac
if: matrix.os == 'macos-latest'
shell: bash
run: |
stack build ${{ matrix.stack_args }}
echo "::set-output name=LOCAL_INSTALL_ROOT::$(stack path --local-install-root)"
echo "ignore-project: False" >> cabal.project.local
echo "package direct-sqlcipher" >> cabal.project.local
echo " extra-include-dirs: /usr/local/opt/openssl@1.1/include" >> cabal.project.local
echo " extra-lib-dirs: /usr/local/opt/openssl@1.1/lib" >> cabal.project.local
echo " flags: +openssl" >> cabal.project.local
- name: Upload binaries to release
if: startsWith(github.ref, 'refs/tags/v')
- name: Install pkg-config for Mac
if: matrix.os == 'macos-latest'
run: brew install pkg-config
- name: Unix prepare cabal.project.local for Ubuntu
if: matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04'
shell: bash
run: |
echo "ignore-project: False" >> cabal.project.local
echo "package direct-sqlcipher" >> cabal.project.local
echo " flags: +openssl" >> cabal.project.local
- name: Unix build
id: unix_build
if: matrix.os != 'windows-latest'
shell: bash
run: |
cabal build --enable-tests
echo "::set-output name=bin_path::$(cabal list-bin simplex-chat)"
- name: Unix test
if: matrix.os != 'windows-latest'
timeout-minutes: 30
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.build_test.outputs.LOCAL_INSTALL_ROOT }}${{ matrix.artifact_rel_path }}
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
- name: Windows build
id: windows_build
if: matrix.os == 'windows-latest'
shell: cmd
run: |
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%
- name: Windows 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.windows_build.outputs.bin_path }}
asset_name: ${{ matrix.asset_name }}
tag: ${{ github.ref }}
# Windows /

36
.github/workflows/cla.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: "CLA Assistant"
on:
issue_comment:
types: [created]
pull_request_target:
types: [opened, closed, synchronize]
jobs:
CLAssistant:
runs-on: ubuntu-latest
steps:
- name: "CLA Assistant"
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request'
# Beta Release
uses: cla-assistant/github-action@v2.1.3-beta
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# the below token should have repo scope and must be manually added by you in the repository's secret
PERSONAL_ACCESS_TOKEN : ${{ secrets.PERSONAL_ACCESS_TOKEN }}
with:
path-to-signatures: 'signatures/v1.1/cla.json'
path-to-document: 'https://github.com/simplex-chat/cla/blob/master/CLA.md'
# branch should not be protected
remote-organization-name: simplex-chat
remote-repository-name: cla
branch: 'master'
# allowlist: user1,bot*
#below are the optional inputs - If the optional inputs are not given, then default values will be taken
#create-file-commit-message: 'For example: Creating file for storing CLA Signatures'
#signed-commit-message: 'For example: $contributorName has signed the CLA in #$pullRequestNo'
#custom-notsigned-prcomment: 'pull request comment with Introductory message to ask new contributors to sign'
#custom-pr-sign-comment: 'The signature to be committed in order to sign the CLA'
#custom-allsigned-prcomment: 'pull request comment when all contributors has signed, defaults to **CLA Assistant Lite bot** All Contributors have signed the CLA.'
#lock-pullrequest-aftermerge: false - if you don't want this bot to automatically lock the pull request after merging (default - true)
#use-dco-flag: true - If you are using DCO instead of CLA

38
.github/workflows/web.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: Build Eleventy
on:
push:
branches:
- master
- stable
paths:
- website/**
- images/**
- blog/**
- .github/workflows/web.yml
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies & build
run: |
./website/web.sh
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
publish_dir: ./website/_site
github_token: ${{ secrets.GITHUB_TOKEN }}

50
.gitignore vendored
View File

@@ -5,12 +5,6 @@
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
@@ -40,11 +34,45 @@ cabal.project.local
cabal.project.local~
.HTF/
.ghc.environment.*
*.cabal
stack.yaml.lock
# Idris
*.ibc
# chat database
# Chat database
*.db
*.db.bak
# Temporary test files
tests/tmp
tests/tmp*
logs/
*.devcontainer
# for website
website/node_modules/
website/src/blog/
website/translations.json
website/src/_data/supported_languages.json
website/src/img/images/
website/src/images/
# Generated files
website/package/generated*
# Ignore build tool output, e.g. code coverage
website/.nyc_output/
website/coverage/
# Ignore API documentation
website/api-docs/
# Ignore folders from source code editors
website/.vscode
website/.idea
# Ignore eleventy output when doing manual tests
website/_site/
website/package-lock.json
# Ignore test files
website/.cache
website/test/stubs-layout-cache/_includes/*.js
apps/android/app/release

View File

@@ -1,10 +1,32 @@
FROM haskell:8.8.4 AS build-stage
# if you encounter "version `GLIBC_2.28' not found" error when running
# chat client executable, build with the following base image instead:
# FROM haskell:8.8.4-stretch AS build-stage
FROM ubuntu:focal AS build
# Install curl and simplex-chat-related dependencies
RUN apt-get update && apt-get install -y curl git build-essential libgmp3-dev zlib1g-dev libssl-dev
# Install ghcup
RUN a=$(arch); curl https://downloads.haskell.org/~ghcup/$a-linux-ghcup -o /usr/bin/ghcup && \
chmod +x /usr/bin/ghcup
# Install ghc
RUN ghcup install ghc 8.10.7
# Install cabal
RUN ghcup install cabal
# Set both as default
RUN ghcup set ghc 8.10.7 && \
ghcup set cabal
COPY . /project
WORKDIR /project
RUN stack install
# Adjust PATH
ENV PATH="/root/.cabal/bin:/root/.ghcup/bin:$PATH"
# Adjust build
RUN cp ./scripts/cabal.project.local.linux ./cabal.project.local
# Compile simplex-chat
RUN cabal update
RUN cabal install
FROM scratch AS export-stage
COPY --from=build-stage /root/.local/bin/dog-food /
COPY --from=build /root/.cabal/bin/simplex-chat /

96
PRIVACY.md Normal file
View File

@@ -0,0 +1,96 @@
# SimpleX Chat Terms & Privacy Policy
SimpleX Chat is the first communication platform that has no user profile IDs of any kind, not even random numbers. Not only it has no access to your messages (thanks to open-source double-ratchet end-to-end encryption protocol and additional encryption layers), it also has no access to your profile and contacts - we cannot observe your connections graph.
If you believe that some of the clauses in this document are not aligned with our mission or principles, please raise it with us via [email](chat@simplex.chat) or [chat](https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23%2F%3Fv%3D1%26dh%3DMCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion).
## 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 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).
### 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.
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.
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).
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.
### 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.
The cases when SimpleX Chat may need to share the data we temporarily store on the servers:
- To meet any applicable law, regulation, legal process or enforceable governmental request.
- To enforce applicable Terms, including investigation of potential violations.
- To detect, prevent, or otherwise address fraud, security, or technical issues.
- To protect against harm to the rights, property, or safety of SimpleX Chat, our users, or the public as required or permitted by law.
At the time of updating this document, we have never provided or have been requested the access to our servers or any information from our servers by any third parties. If we are ever requested to provide such access or information, we will follow the due legal process.
### 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.
Please also read our Terms of Service.
If you have questions about our Privacy Policy please contact us at chat@simplex.chat.
## Terms of Service
You accept to 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.
**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.
**Operating our services**. For the purpose of operating our Services, you agree that your end-to-end encrypted messages are transferred via our servers in the United Kingdom, the United States and other countries where we have or use facilities and service providers or partners.
**Software**. You agree to downloading and installing updates to our Services when they are available; they would only be automatic if you configure your devices in this way.
**Traffic and device costs**. You are solely responsible for the traffic and device costs on which you use our Services, and any associated taxes.
**Legal and acceptable usage**. You agree to use our Services only for legal and acceptable purposes. You will not use (or assist others in using) our Services in ways that: 1) violate or infringe the rights of SimpleX Chat, our users, or others, including privacy, publicity, intellectual property, or other proprietary rights; 2) involve sending illegal or impermissible communications, e.g. spam.
**Damage to SimpleX Chat**. You must not (or assist others to) access, use, modify, distribute, transfer, or exploit our Services in unauthorized manners, or in ways that harm SimpleX Chat, our Services, or systems. For example, you must not 1) access our Services or systems without authorization, other than by using the apps; 2) disrupt the integrity or performance of our Services; 3) collect information about our users in any manner; or 4) sell, rent, or charge for our Services.
**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.
**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.
**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)
**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.
**Disclaimers**. YOU USE OUR SERVICES AT YOUR OWN RISK AND SUBJECT TO THE FOLLOWING DISCLAIMERS. WE PROVIDE OUR SERVICES ON AN “AS IS” BASIS WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, NON-INFRINGEMENT, AND FREEDOM FROM COMPUTER VIRUS OR OTHER HARMFUL CODE. SIMPLEX DOES NOT WARRANT THAT ANY INFORMATION PROVIDED BY US IS ACCURATE, COMPLETE, OR USEFUL, THAT OUR SERVICES WILL BE OPERATIONAL, ERROR-FREE, SECURE, OR SAFE, OR THAT OUR SERVICES WILL FUNCTION WITHOUT DISRUPTIONS, DELAYS, OR IMPERFECTIONS. WE DO NOT CONTROL, AND ARE NOT RESPONSIBLE FOR, CONTROLLING HOW OR WHEN OUR USERS USE OUR SERVICES. WE ARE NOT RESPONSIBLE FOR THE ACTIONS OR INFORMATION (INCLUDING CONTENT) OF OUR USERS OR OTHER THIRD PARTIES. YOU RELEASE US, AFFILIATES, DIRECTORS, OFFICERS, EMPLOYEES, PARTNERS, AND AGENTS ("SIMPLEX PARTIES") FROM ANY CLAIM, COMPLAINT, CAUSE OF ACTION, CONTROVERSY, OR DISPUTE (TOGETHER, "CLAIM") AND DAMAGES, KNOWN AND UNKNOWN, RELATING TO, ARISING OUT OF, OR IN ANY WAY CONNECTED WITH ANY SUCH CLAIM YOU HAVE AGAINST ANY THIRD PARTIES.
**Limitation of liability**. THE SIMPLEX PARTIES WILL NOT BE LIABLE TO YOU FOR ANY LOST PROFITS OR CONSEQUENTIAL, SPECIAL, PUNITIVE, INDIRECT, OR INCIDENTAL DAMAGES RELATING TO, ARISING OUT OF, OR IN ANY WAY IN CONNECTION WITH OUR TERMS, US, OR OUR SERVICES, EVEN IF THE SIMPLEX PARTIES HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. OUR AGGREGATE LIABILITY RELATING TO, ARISING OUT OF, OR IN ANY WAY IN CONNECTION WITH OUR TERMS, US, OR OUR SERVICES WILL NOT EXCEED ONE DOLLAR ($1). THE FOREGOING DISCLAIMER OF CERTAIN DAMAGES AND LIMITATION OF LIABILITY WILL APPLY TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW. THE LAWS OF SOME JURISDICTIONS MAY NOT ALLOW THE EXCLUSION OR LIMITATION OF CERTAIN DAMAGES, SO SOME OR ALL OF THE EXCLUSIONS AND LIMITATIONS SET FORTH ABOVE MAY NOT APPLY TO YOU. NOTWITHSTANDING ANYTHING TO THE CONTRARY IN OUR TERMS, IN SUCH CASES, THE LIABILITY OF THE SIMPLEX PARTIES WILL BE LIMITED TO THE EXTENT PERMITTED BY APPLICABLE LAW.
**Availability**. Our Services may be interrupted, including for maintenance, upgrades, or network or equipment failures. We may discontinue some or all of our Services, including certain features and the support for certain devices and platforms, at any time.
**Resolving disputes**. You agree to resolve any Claim you have with us relating to or arising from our Terms, us, or our Services in the courts of England and Wales. You also agree to submit to the personal jurisdiction of such courts for the purpose of resolving all such disputes. The laws of England govern our Terms, as well as any disputes, whether in court or arbitration, which might arise between SimpleX Chat and you, without regard to conflict of law provisions.
**Changes to the terms**. SimpleX Chat may update the Terms from time to time. Your continued use of our Services confirms your acceptance of our updated Terms and supersedes any prior Terms. You will comply with all applicable export control and trade sanctions laws. Our Terms cover the entire agreement between you and SimpleX Chat regarding our Services. If you do not agree with our Terms, you should stop using our Services.
**Enforcing the terms**. If we fail to enforce any of our Terms, that does not mean we waive the right to enforce them. If any provision of the Terms is deemed unlawful, void, or unenforceable, that provision shall be deemed severable from our Terms and shall not affect the enforceability of the remaining provisions. Our Services are not intended for distribution to or use in any country where such distribution or use would violate local law or would subject us to any regulations in another country. We reserve the right to limit our Services in any country. If you have specific questions about these Terms, please contact us at chat@simplex.chat.
**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

451
README.md
View File

@@ -1,189 +1,354 @@
<img align="right" src="images/logo.svg" alt="SimpleX logo" height="90">
# SimpleX chat
## Federated, private, secure, decentralized
[![GitHub build](https://github.com/simplex-chat/simplex-chat/workflows/build/badge.svg)](https://github.com/simplex-chat/simplex-chat/actions?query=workflow%3Abuild)
[![build](https://github.com/simplex-chat/simplex-chat/actions/workflows/build.yml/badge.svg?branch=stable)](https://github.com/simplex-chat/simplex-chat/actions/workflows/build.yml)
[![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)
The motivation for SimpleX chat is [presented here](./simplex.md)
| 30/03/2023 | EN, [FR](/docs/lang/fr/README.md), [CZ](/docs/lang/cs/README.md) |
SimpleX chat prototype is a thin terminal UI on top of [SimpleXMQ](https://github.com/simplex-chat/simplexmq) message broker that uses [SMP protocols](https://github.com/simplex-chat/simplexmq/blob/master/protocol).
<img src="images/simplex-chat-logo.svg" alt="SimpleX logo" width="100%">
See [simplex.chat](https://simplex.chat) website for chat demo and the explanations of the system and how SMP protocol works.
# SimpleX - the first messaging platform that has no user identifiers of any kind - 100% private by design!
## Table of contents
[<img src="./images/trail-of-bits.jpg" height="100">](http://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html) &nbsp;&nbsp;&nbsp; [<img src="./images/privacy-guides.jpg" height="80">](https://www.privacyguides.org/en/real-time-communication/#simplex-chat) &nbsp;&nbsp;&nbsp; [<img src="./images/kuketz-blog.jpg" height="80">](https://www.kuketz-blog.de/simplex-eindruecke-vom-messenger-ohne-identifier/)
- [Features](#features)
- [Installation](#installation)
- [Download chat client](#download-chat-client)
- [Build from source](#build-from-source)
- [Using Docker](#using-docker)
- [Using Haskell stack](#using-haskell-stack)
- [Usage](#usage)
- [Running the chat client](#running-the-chat-client)
- [How to use SimpleX chat](#how-to-use-simplex-chat)
- [Access chat history](#access-chat-history)
## 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).
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).
[Learn more about SimpleX Chat](#contents).
## Install the app
[<img src="https://github.com/simplex-chat/.github/blob/master/profile/images/apple_store.svg" alt="iOS app" height="42">](https://apps.apple.com/us/app/simplex-chat/id1605771084)
&nbsp;
[![Android app](https://github.com/simplex-chat/.github/blob/master/profile/images/google_play.svg)](https://play.google.com/store/apps/details?id=chat.simplex.app)
&nbsp;
[<img src="https://github.com/simplex-chat/.github/blob/master/profile/images/f_droid.svg" alt="F-Droid" height="41">](https://app.simplex.chat)
&nbsp;
[<img src="https://github.com/simplex-chat/.github/blob/master/profile/images/testflight.png" alt="iOS TestFlight" height="41">](https://testflight.apple.com/join/DWuT2LQu)
&nbsp;
[<img src="https://github.com/simplex-chat/.github/blob/master/profile/images/apk_icon.png" alt="APK" height="41">](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex.apk)
- 🖲 Protects your messages and metadata - who you talk to and when.
- 🔐 Double ratchet end-to-end encryption, with additional encryption layer.
- 📱 Mobile apps for Android ([Google Play](https://play.google.com/store/apps/details?id=chat.simplex.app), [APK](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex.apk)) and [iOS](https://apps.apple.com/us/app/simplex-chat/id1605771084).
- 🚀 [TestFlight preview for iOS](https://testflight.apple.com/join/DWuT2LQu) with the new features 1-2 weeks earlier - **limited to 10,000 users**!
- 🖥 Available as a terminal (console) [app / CLI](#zap-quick-installation-of-a-terminal-app) on Linux, MacOS, Windows.
## Connect to the team via the app
- to ask any questions
- to suggest any improvements
- to share anything relevant
## 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)
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).
You can join either by opening these links in the app or by opening them in a desktop browser and scanning the QR code.
## 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.
The channel through which you share the link does not have to be secure - it is enough that you can confirm who sent you the message and that your SimpleX connection is established.
<img src="https://github.com/simplex-chat/.github/blob/master/profile/images/app1.png" alt="Make a private connection" height="360"> <img src="https://github.com/simplex-chat/.github/blob/master/profile/images/arrow.png" height="360"> <img src="https://github.com/simplex-chat/.github/blob/master/profile/images/app2.png" alt="Conversation" height="360"> <img src="https://github.com/simplex-chat/.github/blob/master/profile/images/arrow.png" height="360"> <img src="https://github.com/simplex-chat/.github/blob/master/profile/images/app3.png" alt="Video call" height="360">
After you connect, you can [verify connection security code](./blog/20230103-simplex-chat-v4.4-disappearing-messages.md#connection-security-verification).
## User guide (NEW)
Read about the app features and settings in the new [User guide](./docs/guide/README.md).
## Help translating SimpleX Chat
Thanks to our users and [Weblate](https://hosted.weblate.org/engage/simplex-chat/), SimpleX Chat apps, website and documents are translated to many other languages.
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/)||
|🇨🇿 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/)||
|🇫🇷 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)|
|🇮🇹 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/)||
|🇳🇱 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/)||
|🇷🇺 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/)|||
|🇨🇳 zh-CHS|简体中文|[sith-on-mars](https://github.com/sith-on-mars)|[![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/)|[![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, Hindi, Japanese, Spanish and [many others](https://hosted.weblate.org/projects/simplex-chat/#languages). We will be adding more languages as some of the already added are completed please suggest new languages, review the [translation guide](./docs/TRANSLATIONS.md) and get in touch with us!
## Contribute
We would love to have you join the development! You can help us with:
- writing a tutorial or recipes about hosting servers, chat bot automations, etc.
- contributing to SimpleX Chat knowledge-base.
- developing features - please connect to us via chat so we can help you get started.
## Help us with donations
Huge thank you to everybody who donated to SimpleX Chat!
We are prioritizing users privacy and security - it would be impossible without your support.
Our pledge to our users is that SimpleX protocols are and will remain open, and in public domain, - so anybody can build the future implementations of the clients and the servers. We are building SimpleX platform based on the same principles as email and web, but much more private and secure.
Your donations help us raise more funds any amount, even the price of the cup of coffee, would make a big difference for us.
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
Thank you,
Evgeny
SimpleX Chat founder
## Contents
- [Why privacy matters](#why-privacy-matters)
- [SimpleX approach to privacy and security](#simplex-approach-to-privacy-and-security)
- [Complete privacy](#complete-privacy-of-your-identity-profile-contacts-and-metadata)
- [Protection against spam and abuse](#the-best-protection-against-spam-and-abuse)
- [Ownership and security of your data](#complete-ownership-control-and-security-of-your-data)
- [Users own SimpleX network](#users-own-simplex-network)
- [Frequently asked questions](#frequently-asked-questions)
- [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)
- [For developers](#for-developers)
- [Roadmap](#roadmap)
- [Disclaimer](#disclaimer)
- [License](#license)
- [Disclaimers, Security contact, License](#disclaimers)
## Features
## Why privacy matters
- 1-to-1 chat with multiple people in the same terminal window.
- Auto-populated recipient name - just type your messages to reply to the sender once the connection is established.
- Demo SMP server available at `smp1.simplex.im:5223` - you can deploy your own server (`smp-server` executable in [simplexmq](https://github.com/simplex-chat/simplexmq) repo).
- No global identity or any names visible to the server(s), ensuring full privacy of your contacts and conversations.
- E2E encryption, with RSA public key that has to be passed out-of-band (see [How to use SimpleX chat](#how-to-use-simplex-chat)).
- Message signing and verification with automatically generated RSA keys.
- Message integrity validation (via including the digests of the previous messages).
- Authentication of each command/message by SMP servers with automatically generated RSA key pairs.
- TCP transport encryption using SMP transport protocol.
Everyone should care about privacy and security of their communications - innocuous conversations can put you in danger even if there is nothing to hide.
RSA keys are not used as identity, they are randomly generated for each contact. 2048 bit keys are used, it can be changed to 4096-bit in code via [rsaKeySize setting](https://github.com/simplex-chat/simplex-chat/blob/master/apps/dog-food/Main.hs).
One of the most shocking stories is the experience of [Mohamedou Ould Salahi](https://en.wikipedia.org/wiki/Mohamedou_Ould_Slahi) that he wrote about in his memoir and that is shown in The Mauritanian movie. He was put into Guantanamo camp, without trial, and was tortured there for 15 years after a phone call to his relative in Afghanistan, under suspicion of being involved in 9/11 attacks, even though he lived in Germany for the 10 years prior to the attacks.
## Installation
It is not enough to use an end-to-end encrypted messenger, we all should use the messengers that protect the privacy of our personal networks - who we are connected with.
### Download chat client
## SimpleX approach to privacy and security
Download the chat binary for your system from the [latest stable release](https://github.com/simplex-chat/simplex-chat/releases) and make it executable as shown below.
### Complete privacy of your identity, profile, contacts and metadata
#### Linux and MacOS
**Unlike any other existing messaging platform, SimpleX has no identifiers assigned to the users** - not even random numbers. This protects the privacy of who are you communicating with, hiding it from SimpleX platform servers and from any observers. [Read more](./docs/SIMPLEX.md#full-privacy-of-your-identity-profile-contacts-and-metadata).
### The best protection against spam and abuse
As you have no identifier on SimpleX platform, you cannot be contacted unless you share a one-time invitation link or an optional temporary user address. [Read more](./docs/SIMPLEX.md#the-best-protection-against-spam-and-abuse).
### Complete ownership, control and security of your data
SimpleX stores all user data on client devices, the messages are only held temporarily on SimpleX relay servers until they are received. [Read more](./docs/SIMPLEX.md#complete-ownership-control-and-security-of-your-data).
### Users own SimpleX network
You can use SimpleX with your own servers and still communicate with people using the servers that are pre-configured in the apps or any other SimpleX servers. [Read more](./docs/SIMPLEX.md#users-own-simplex-network).
## Frequently asked questions
1. _How SimpleX can deliver messages without any user identifiers?_ See [v2 release announcement](./blog/20220511-simplex-chat-v2-images-files.md#the-first-messaging-platform-without-user-identifiers) explaining how SimpleX works.
2. _Why should I not just use Signal?_ Signal is a centralized platform that uses phone numbers to identify its users and their contacts. It means that while the content of your messages on Signal is protected with robust end-to-end encryption, there is a large amount of meta-data visible to Signal - who you talk with and when.
3. _How is it different from Matrix, Session, Ricochet, Cwtch, etc., that also don't require user identities?_ Although these platforms do not require a _real identity_, they do rely on anonymous user identities to deliver messages it can be, for example, an identity key or a random number. Using a persistent user identity, even anonymous, creates a risk that user's connection graph becomes known to the observers and/or service providers, and it can lead to de-anonymizing some users. If the same user profile is used to connect to two different people via any messenger other than SimpleX, these two people can confirm if they are connected to the same person - they would use the same user identifier in the messages. With SimpleX there is no meta-data in common between your conversations with different contacts - the quality that no other messaging platform has.
## News and updates
Recent updates:
[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).
[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).
[All updates](./blog)
## :zap: Quick installation of a terminal app
```sh
chmod +x <binary>
mv <binary> ~/.local/bin/dog-food
curl -o- https://raw.githubusercontent.com/simplex-chat/simplex-chat/stable/install.sh | bash
```
(or any other preferred location on PATH).
Once the chat client is installed, simply run `simplex-chat` from your terminal.
On MacOS you also need to [allow Gatekeeper to run it](https://support.apple.com/en-us/HT202491).
![simplex-chat](./images/connection.gif)
#### Windows
Read more about [installing and using the terminal app](./docs/CLI.md).
```sh
move <binary> %APPDATA%/local/bin/dog-food.exe
```
## SimpleX Platform design
### Build from source
SimpleX is a client-server network with a unique network topology that uses redundant, disposable message relay nodes to asynchronously pass messages via unidirectional (simplex) message queues, providing recipient and sender anonymity.
#### Using Docker
Unlike P2P networks, all messages are passed through one or several server nodes, that do not even need to have persistence. In fact, the current [SMP server implementation](https://github.com/simplex-chat/simplexmq#smp-server) uses in-memory message storage, persisting only the queue records. SimpleX provides better metadata protection than P2P designs, as no global participant identifiers are used to deliver messages, and avoids [the problems of P2P networks](./docs/SIMPLEX.md#comparison-with-p2p-messaging-protocols).
On Linux, you can build the chat executable using [docker build with custom output](https://docs.docker.com/engine/reference/commandline/build/#custom-build-outputs):
Unlike federated networks, the server nodes **do not have records of the users**, **do not communicate with each other** and **do not store messages** after they are delivered to the recipients. There is no way to discover the full list of servers participating in SimpleX network. This design avoids the problem of metadata visibility that all federated networks have and better protects from the network-wide attacks.
```shell
$ git clone git@github.com:simplex-chat/simplex-chat.git
$ cd simplex-chat
$ DOCKER_BUILDKIT=1 docker build --output ~/.local/bin .
```
Only the client devices have information about users, their contacts and groups.
> **Please note:** If you encounter ``version `GLIBC_2.28' not found`` error, rebuild it with `haskell:8.8.4-stretch` base image (change it in your local [Dockerfile](Dockerfile)).
See [SimpleX whitepaper](https://github.com/simplex-chat/simplexmq/blob/stable/protocol/overview-tjr.md) for more information on platform objectives and technical design.
#### Using Haskell stack
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).
Install [Haskell stack](https://docs.haskellstack.org/en/stable/README/):
## Privacy: technical details and limitations
```shell
curl -sSL https://get.haskellstack.org/ | sh
```
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.
and build the project:
What is already implemented:
```shell
$ git clone git@github.com:simplex-chat/simplex-chat.git
$ cd simplex-chat
$ stack install
```
1. Instead of user profile identifiers used by all other platforms, even the most private ones, SimpleX uses pairwise per-queue identifiers (2 addresses for each unidirectional message queue, with an optional 3rd address for push notifications on iOS, 2 queues in each connection between the users). It makes observing the network graph on the application level more difficult, as for `n` users there can be up to `n * (n-1)` message queues.
2. End-to-end encryption in each message queue using [NaCl cryptobox](https://nacl.cr.yp.to/box.html). This is added to allow redundancy in the future (passing each message via several servers), to avoid having the same ciphertext in different queues (that would only be visible to the attacker if TLS is compromised). The encryption keys used for this encryption are not rotated, instead we are planning to rotate the queues. Curve25519 keys are used for key negotiation.
3. [Double ratchet](https://signal.org/docs/specifications/doubleratchet/) end-to-end encryption in each conversation between two users (or group members). This is the same algorithm that is used in Signal and many other messaging apps; it provides OTR messaging with forward secrecy (each message is encrypted by its own ephemeral key), break-in recovery (the keys are frequently re-negotiated as part of the message exchange). Two pairs of Curve448 keys are used for the initial key agreement, initiating party passes these keys via the connection link, accepting side - in the header of the confirmation message.
4. Additional layer of encryption using NaCL cryptobox for the messages delivered from the server to the recipient. This layer avoids having any ciphertext in common between sent and received traffic of the server inside TLS (and there are no identifiers in common as well).
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.
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.
## Usage
We plan to add soon:
### Running the chat client
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.
To start the chat client, run `dog-food` (as in [eating your own dog food](https://en.wikipedia.org/wiki/Eating_your_own_dog_food)) from the terminal.
## For developers
By default, app data directory is created in the home directory (`~/.simplex`, or `%APPDATA%/simplex` on Windows), and SQLite database file `smp-chat.db` is initialized in it.
You can:
The default SMP server is `smp1.simplex.im#pLdiGvm0jD1CMblnov6Edd/391OrYsShw+RgdfR0ChA=` (base-64 encoded string after server host is the transport key digest) - it is pre-configured in the app.
- use SimpleX Chat library to integrate chat functionality into your mobile apps.
- create chat bots and services in Haskell - see [simple](./apps/simplex-bot/) and more [advanced chat bot example](./apps/simplex-bot-advanced/).
- create chat bots and services in any language running SimpleX Chat terminal CLI as a local WebSocket server. See [TypeScript SimpleX Chat client](./packages/simplex-chat-client/) and [JavaScript chat bot example](./packages/simplex-chat-client/typescript/examples/squaring-bot.js).
- run [simplex-chat terminal CLI](./docs/CLI.md) to execute individual chat commands, e.g. to send messages as part of shell script execution.
To specify a different file path for the chat database use `-d` command line option:
```shell
$ dog-food -d my-chat.db
```
If you deployed your own SMP server(s) you can configure client via `-s` option:
```shell
$ dog-food -s smp.example.com:5223#KXNE1m2E1m0lm92WGKet9CL6+lO742Vy5G6nsrkvgs8=
```
The base-64 encoded string in server address is the digest of RSA transport handshake key that the server will generate on the first run and output its digest.
You can still talk to people using default or any other server - it only affects the location of the message queue when you initiate the connection (and the reply queue can be on another server, as set by the other party's client).
Run `dog-food --help` to see all available options.
### How to use SimpleX chat
This diagram shows how to connect and message a contact:
<div align="center">
<img align="center" src="images/how-to-use-simplex.svg">
</div>
Once you have started the chat, use `/add <name1>` to create a new connection and generate an invitation (`<name1>` is any name you want to use for that contact). The add command will output an invitation. Send this invitation to your contact via any other channel.
The invitation has the format `smp::<server>::<queue_id>::<rsa_public_key_for_this_queue_only>`. The invitation can only be used once and even if this is intercepted, the attacker would not be able to use it to send you the messages via this queue once your contact confirms that the connection is established.
The contact who received the invitation should use `/connect <name2> <invitation>` to accept the connection (`<name2>` is any name that the accepting contact wants to use for you).
Once the contact has used the `/connect` command, a connection is established and both parties are notified.
They would then use `@<name> <message>` commands to send messages. One may also press Space or just start typing a message to send a message to the contact that was the last.
If your contact is disconnected, restart the chat client - it may happen if you lose internet connection.
Use `/help` in chat to see the list of available commands.
### Access chat history
SimpleX chat stores all your contacts and conversations in a local database file, making it private and portable by design, fully owned and controlled by you.
You can search your chat history via SQLite database file:
```
sqlite3 ~/.simplex/smp-chat.db
```
Now you can query `messages` table, for example:
```sql
select * from messages
where conn_alias = cast('alice' as blob)
and body like '%cats%'
order by internal_id desc;
```
> **Please note:** SQLite foreign key constraints are disabled by default, and must be **[enabled separately for each database connection](https://sqlite.org/foreignkeys.html#fk_enable)**. The latter can be achieved by running `PRAGMA foreign_keys = ON;` command on an open database connection. By running data altering queries without enabling foreign keys prior to that, you may risk putting your database in an inconsistent state.
If you are considering developing with SimpleX platform please get in touch for any advice and support.
## Roadmap
1. Switch to application level chat protocol. This will allow to separate physical server connection management from logical chat contacts, and to support all common chat functions.
2. SMP queue redundancy and rotation in SMP agent protocol.
3. Symmetric groups support in SMP agent protocol, as a foundation for chat groups.
4. Delivery confirmation in SMP agent protocol.
5. Multi-agent/device data synchronisation - to use chat on multiple devices.
6. Synchronous streams support in SMP and SMP agent protocols, to support file transfer.
7. Terminal chat UI and mobile apps.
8. Scripts for simple SMP server deployment to hosting providers: Linode, Digital Ocean and Heroku.
9. Public broadcast channels.
10. Optional public contact/group addresses using DNS to establish connections, but not using it to send and receive messages - in this way you will keep all your contacts and groups even if you lose the control of the domain.
- ✅ Easy to deploy SimpleX server with in-memory message storage, without any dependencies.
- ✅ Terminal (console) client with groups and files support.
- ✅ One-click SimpleX server deployment on Linode.
- ✅ End-to-end encryption using double-ratchet protocol with additional encryption layer.
- ✅ Mobile apps v1 for Android and iOS.
- ✅ Private instant notifications for Android using background service.
- ✅ Haskell chat bot templates.
- ✅ v2.0 - supporting images and files in mobile apps.
- ✅ Manual chat history deletion.
- ✅ End-to-end encrypted WebRTC audio and video calls via the mobile apps.
- ✅ Privacy preserving instant notifications for iOS using Apple Push Notification service.
- ✅ Chat database export and import.
- ✅ Chat groups in mobile apps.
- ✅ Connecting to messaging servers via Tor.
- ✅ Dual server addresses to access messaging servers as v3 hidden services.
- ✅ Chat server and TypeScript client SDK to develop chat interfaces, integrations and chat bots (ready for announcement).
- ✅ Incognito mode to share a new random name with each contact.
- ✅ Chat database encryption.
- ✅ Automatic chat history deletion.
- ✅ Links to join groups and improve groups stability.
- ✅ Voice messages (with recipient opt-out per contact).
- ✅ Basic authentication for SMP servers (to authorize creating new queues).
- ✅ View deleted messages, full message deletion by sender (with recipient opt-in per contact).
- ✅ Block screenshots and view in recent apps.
- ✅ Advanced server configuration.
- ✅ Disappearing messages (with recipient opt-in per-contact).
- ✅ "Live" messages.
- ✅ Contact verification via a separate out-of-band channel.
- ✅ Multiple user profiles in the same chat database.
- ✅ Optionally avoid re-using the same TCP session for multiple connections.
- ✅ Preserve message drafts.
- ✅ File server to optimize for efficient and private sending of large files.
- ✅ Improved audio & video calls.
- ✅ 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.
- 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.
- 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.
## Disclaimer
## Disclaimers
System and protocol security have not been audited yet. As such, it is not advised to use SimpleX chat for high security communications.
[SimpleX protocols and security model](https://github.com/simplex-chat/simplexmq/blob/master/protocol/overview-tjr.md) was reviewed, and had many breaking changes and improvements in v1.0.0.
The 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 still a relatively early stage platform (the mobile apps were released in March 2022), so you may discover some bugs and missing features. We would really appreciate if you let us know anything that needs to be fixed or improved.
The default servers configured in the app are provided on the best effort basis. We are currently not guaranteeing any SLAs, although historically our servers had over 99.9% uptime each.
We have never provided or have been requested access to our servers or any information from our servers by any third parties. If we are ever requested to provide such access or information, we will be following due legal process.
We do not log IP addresses of the users and we do not perform any traffic correlation on our servers. If transport level security is critical you must use Tor or some other similar network to access messaging servers. We will be improving the client applications to reduce the opportunities for traffic correlation.
Please read more in [Terms & privacy policy](./PRIVACY.md).
## Security contact
To report a security vulnerability, please send us email to chat@simplex.chat. We will coordinate the fix and disclosure. Please do NOT report security vulnerabilities via GitHub issues.
Please treat any findings of possible traffic correlation attacks allowing to correlate two different conversations to the same user, other than covered in [the threat model](https://github.com/simplex-chat/simplexmq/blob/stable/protocol/overview-tjr.md#threat-model), as security vulnerabilities, and follow this disclosure process.
## License
[AGPL v3](./LICENSE)
[<img src="https://github.com/simplex-chat/.github/blob/master/profile/images/apple_store.svg" alt="iOS app" height="42">](https://apps.apple.com/us/app/simplex-chat/id1605771084)
&nbsp;
[![Android app](https://github.com/simplex-chat/.github/blob/master/profile/images/google_play.svg)](https://play.google.com/store/apps/details?id=chat.simplex.app)
&nbsp;
[<img src="https://github.com/simplex-chat/.github/blob/master/profile/images/f_droid.svg" alt="F-Droid" height="41">](https://app.simplex.chat)
&nbsp;
[<img src="https://github.com/simplex-chat/.github/blob/master/profile/images/testflight.png" alt="iOS TestFlight" height="41">](https://testflight.apple.com/join/DWuT2LQu)
&nbsp;
[<img src="https://github.com/simplex-chat/.github/blob/master/profile/images/apk_icon.png" alt="APK" height="41">](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex.apk)

20
apps/android/.gitignore vendored Normal file
View File

@@ -0,0 +1,20 @@
*.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/

1
apps/android/.idea/.name generated Normal file
View File

@@ -0,0 +1 @@
SimpleX

151
apps/android/.idea/codeStyles/Project.xml generated Normal file
View File

@@ -0,0 +1,151 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<ComposeCustomCodeStyleSettings>
<option name="USE_CUSTOM_FORMATTING_FOR_MODIFIERS" value="false" />
</ComposeCustomCodeStyleSettings>
<JetCodeStyleSettings>
<option name="SPACE_BEFORE_EXTEND_COLON" value="false" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="3" />
<option name="WRAP_EXPRESSION_BODY_FUNCTIONS" value="0" />
<option name="WRAP_ELVIS_EXPRESSIONS" value="0" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="4" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
<option name="RIGHT_MARGIN" value="140" />
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
<option name="KEEP_BLANK_LINES_IN_CODE" value="0" />
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
<option name="CALL_PARAMETERS_WRAP" value="0" />
<option name="METHOD_PARAMETERS_WRAP" value="0" />
<option name="EXTENDS_LIST_WRAP" value="0" />
<option name="METHOD_CALL_CHAIN_WRAP" value="0" />
<option name="ASSIGNMENT_WRAP" value="0" />
<option name="METHOD_ANNOTATION_WRAP" value="0" />
<option name="CLASS_ANNOTATION_WRAP" value="0" />
<option name="FIELD_ANNOTATION_WRAP" value="0" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="4" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

View File

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

6
apps/android/.idea/compiler.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
</component>
</project>

19
apps/android/.idea/gradle.xml generated Normal file
View File

@@ -0,0 +1,19 @@
<?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

@@ -0,0 +1,20 @@
<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>

6
apps/android/.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
</component>
</project>

25
apps/android/README.md Normal file
View File

@@ -0,0 +1,25 @@
# Android App Development
This readme is currently a stub and as such is in development.
Ultimately, this readme will act as a guide to contributing to the develop of the SimpleX android app.
## Gotchas
#### SHA Signature for verification for app links/deep links
In order for the SimpleX app to be automatically adopted for opening links from https://simplex.chat the SHA certificate fingerprint for the App installed on the phone must be in the hosted [assetlinks.json](https://simplex.chat/.well-known/assetlinks.json) file on simplex.chat.
The accepted fingerprints are in the `sha256_cert_fingerprints` list.
To find your SHA certificate fingerprint perform the following steps.
1. Build and install your development version of the app as usual
2. From the terminal in Android studio run `adb shell pm get-app-links chat.simplex.app`
3. Copy the signature listed in `signatures` in the result
4. Add your signature to [assetlinks.json](https://github.com/simplex-chat/website/blob/master/.well-known/assetlinks.json) in the [website repo](https://github.com/simplex-chat/website) and make a PR. On approval, wait a few minutes for the changes to propagate to the public website and then you should be able to verify SimpleX.
More information is available [here](https://developer.android.com/training/app-links/verify-site-associations#manual-verification). If there is no response when running the `pm get-app-links` command, the intents in `AndroidManifest.xml` are likely misspecified. A verification attempt can be triggered using `adb shell pm verify-app-links --re-verify chat.simplex.app`.
Note that this is not an issue for the app store build of the app as this is signed with our app store credentials and thus there is a stable signature over users. Developers do not have general access to these credentials for development and testing.

1
apps/android/app/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,223 @@
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 111
versionName "4.6.1-beta.2"
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", "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" }
}
}

21
apps/android/app/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,22 @@
package chat.simplex.app
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("chat.simplex.app", appContext.packageName)
}
}

View File

@@ -0,0 +1,151 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="chat.simplex.app">
<uses-feature android:name="android.hardware.camera" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.VIDEO_CAPTURE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<application
android:name="SimplexApp"
android:allowBackup="true"
android:fullBackupOnly="true"
android:backupAgent="BackupAgent"
android:icon="@mipmap/icon"
android:label="${app_name}"
android:extractNativeLibs="${extract_native_libs}"
android:supportsRtl="true"
android:theme="@style/Theme.SimpleX">
<!-- android:localeConfig="@xml/locales_config"-->
<!-- Main activity -->
<activity
android:name=".MainActivity"
android:launchMode="singleTask"
android:exported="true"
android:label="${app_name}"
android:windowSoftInputMode="adjustResize"
android:theme="@style/Theme.SimpleX">
<intent-filter>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- open simplex:/ connection URI -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="simplex" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="simplex.chat" />
<data android:pathPrefix="/invitation" />
<data android:pathPrefix="/contact" />
</intent-filter>
<!-- Receive files from other apps -->
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
<activity-alias
android:name=".MainActivity_default"
android:exported="true"
android:icon="@mipmap/icon"
android:enabled="true"
android:targetActivity=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
<activity-alias
android:name=".MainActivity_dark_blue"
android:exported="true"
android:icon="@mipmap/icon_dark_blue"
android:enabled="false"
android:targetActivity=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
<activity android:name=".views.call.IncomingCallActivity"
android:showOnLockScreen="true"
android:exported="false"
android:launchMode="singleTask"/>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${provider_authorities}"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<!-- NtfManager action processing (buttons in notifications) -->
<receiver
android:name=".model.NtfManager$NtfActionReceiver"
android:enabled="true"
android:exported="false" />
<!-- SimplexService foreground service -->
<service
android:name=".SimplexService"
android:enabled="true"
android:exported="false"
android:stopWithTask="false"></service>
<!-- SimplexService restart on reboot -->
<receiver
android:name=".SimplexService$StartReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<!-- SimplexService restart on destruction -->
<receiver
android:name=".SimplexService$AutoRestartReceiver"
android:enabled="true"
android:exported="false" />
</application>
</manifest>

View File

@@ -0,0 +1,3 @@
# WebView for WebRTC calls in SimpleX Chat
Do NOT edit call.js here, it is compiled abd copied here from call.ts in packages/simplex-chat-webrtc

View File

@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<link href="./style.css" rel="stylesheet" />
<script src="./lz-string.min.js"></script>
</head>
<body>
<video
id="remote-video-stream"
autoplay
playsinline
poster="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAQAAAAnOwc2AAAAEUlEQVR42mNk+M+AARiHsiAAcCIKAYwFoQ8AAAAASUVORK5CYII="
></video>
<video
id="local-video-stream"
muted
autoplay
playsinline
poster="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAQAAAAnOwc2AAAAEUlEQVR42mNk+M+AARiHsiAAcCIKAYwFoQ8AAAAASUVORK5CYII="
></video>
</body>
<footer>
<script src="./call.js"></script>
</footer>
</html>

View File

@@ -0,0 +1,672 @@
"use strict";
// Inspired by
// https://github.com/webrtc/samples/blob/gh-pages/src/content/insertable-streams/endtoend-encryption
var CallMediaType;
(function (CallMediaType) {
CallMediaType["Audio"] = "audio";
CallMediaType["Video"] = "video";
})(CallMediaType || (CallMediaType = {}));
var VideoCamera;
(function (VideoCamera) {
VideoCamera["User"] = "user";
VideoCamera["Environment"] = "environment";
})(VideoCamera || (VideoCamera = {}));
// for debugging
// var sendMessageToNative = ({resp}: WVApiMessage) => console.log(JSON.stringify({command: resp}))
var sendMessageToNative = (msg) => console.log(JSON.stringify(msg));
// Global object with cryptrographic/encoding functions
const callCrypto = callCryptoFunction();
var TransformOperation;
(function (TransformOperation) {
TransformOperation["Encrypt"] = "encrypt";
TransformOperation["Decrypt"] = "decrypt";
})(TransformOperation || (TransformOperation = {}));
let activeCall;
let answerTimeout = 30000;
const processCommand = (function () {
const defaultIceServers = [
{ urls: ["stun:stun.simplex.im:443"] },
{ urls: ["turn:turn.simplex.im:443?transport=udp"], username: "private", credential: "yleob6AVkiNI87hpR94Z" },
{ urls: ["turn:turn.simplex.im:443?transport=tcp"], username: "private", credential: "yleob6AVkiNI87hpR94Z" },
];
function getCallConfig(encodedInsertableStreams, iceServers, relay) {
return {
peerConnectionConfig: {
iceServers: iceServers !== null && iceServers !== void 0 ? iceServers : defaultIceServers,
iceCandidatePoolSize: 10,
encodedInsertableStreams,
iceTransportPolicy: relay ? "relay" : "all",
},
iceCandidates: {
delay: 3000,
extrasInterval: 2000,
extrasTimeout: 8000,
},
};
}
function getIceCandidates(conn, config) {
return new Promise((resolve, _) => {
let candidates = [];
let resolved = false;
let extrasInterval;
let extrasTimeout;
const delay = setTimeout(() => {
if (!resolved) {
resolveIceCandidates();
extrasInterval = setInterval(() => {
sendIceCandidates();
}, config.iceCandidates.extrasInterval);
extrasTimeout = setTimeout(() => {
clearInterval(extrasInterval);
sendIceCandidates();
}, config.iceCandidates.extrasTimeout);
}
}, config.iceCandidates.delay);
conn.onicecandidate = ({ candidate: c }) => c && candidates.push(c);
conn.onicegatheringstatechange = () => {
if (conn.iceGatheringState == "complete") {
if (resolved) {
if (extrasInterval)
clearInterval(extrasInterval);
if (extrasTimeout)
clearTimeout(extrasTimeout);
sendIceCandidates();
}
else {
resolveIceCandidates();
}
}
};
function resolveIceCandidates() {
if (delay)
clearTimeout(delay);
resolved = true;
const iceCandidates = serialize(candidates);
candidates = [];
resolve(iceCandidates);
}
function sendIceCandidates() {
if (candidates.length === 0)
return;
const iceCandidates = serialize(candidates);
candidates = [];
sendMessageToNative({ resp: { type: "ice", iceCandidates } });
}
});
}
async function initializeCall(config, mediaType, aesKey, useWorker) {
const pc = new RTCPeerConnection(config.peerConnectionConfig);
const remoteStream = new MediaStream();
const localCamera = VideoCamera.User;
const localStream = await getLocalMediaStream(mediaType, localCamera);
const iceCandidates = getIceCandidates(pc, config);
const call = { connection: pc, iceCandidates, localMedia: mediaType, localCamera, localStream, remoteStream, aesKey, useWorker };
await setupMediaStreams(call);
let connectionTimeout = setTimeout(connectionHandler, answerTimeout);
pc.addEventListener("connectionstatechange", connectionStateChange);
return call;
async function connectionStateChange() {
// "failed" means the second party did not answer in time (15 sec timeout in Chrome WebView)
// See https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/p2p/base/p2p_constants.cc;l=70)
if (pc.connectionState !== "failed")
connectionHandler();
}
async function connectionHandler() {
sendMessageToNative({
resp: {
type: "connection",
state: {
connectionState: pc.connectionState,
iceConnectionState: pc.iceConnectionState,
iceGatheringState: pc.iceGatheringState,
signalingState: pc.signalingState,
},
},
});
if (pc.connectionState == "disconnected" || pc.connectionState == "failed") {
clearConnectionTimeout();
pc.removeEventListener("connectionstatechange", connectionStateChange);
if (activeCall) {
setTimeout(() => sendMessageToNative({ resp: { type: "ended" } }), 0);
}
endCall();
}
else if (pc.connectionState == "connected") {
clearConnectionTimeout();
const stats = (await pc.getStats());
for (const stat of stats.values()) {
const { type, state } = stat;
if (type === "candidate-pair" && state === "succeeded") {
const iceCandidatePair = stat;
const resp = {
type: "connected",
connectionInfo: {
iceCandidatePair,
localCandidate: stats.get(iceCandidatePair.localCandidateId),
remoteCandidate: stats.get(iceCandidatePair.remoteCandidateId),
},
};
setTimeout(() => sendMessageToNative({ resp }), 500);
break;
}
}
}
}
function clearConnectionTimeout() {
if (connectionTimeout) {
clearTimeout(connectionTimeout);
connectionTimeout = undefined;
}
}
}
function serialize(x) {
return LZString.compressToBase64(JSON.stringify(x));
}
function parse(s) {
return JSON.parse(LZString.decompressFromBase64(s));
}
async function processCommand(body) {
const { corrId, command } = body;
const pc = activeCall === null || activeCall === void 0 ? void 0 : activeCall.connection;
let resp;
try {
switch (command.type) {
case "capabilities":
console.log("starting outgoing call - capabilities");
if (activeCall)
endCall();
// This request for local media stream is made to prompt for camera/mic permissions on call start
if (command.media)
await getLocalMediaStream(command.media, VideoCamera.User);
const encryption = supportsInsertableStreams(command.useWorker);
resp = { type: "capabilities", capabilities: { encryption } };
break;
case "start": {
console.log("starting incoming call - create webrtc session");
if (activeCall)
endCall();
const { media, useWorker, iceServers, relay } = command;
const encryption = supportsInsertableStreams(useWorker);
const aesKey = encryption ? command.aesKey : undefined;
activeCall = await initializeCall(getCallConfig(encryption && !!aesKey, iceServers, relay), media, aesKey, useWorker);
const pc = activeCall.connection;
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
// for debugging, returning the command for callee to use
// resp = {
// type: "offer",
// offer: serialize(offer),
// iceCandidates: await activeCall.iceCandidates,
// capabilities: {encryption},
// media,
// iceServers,
// relay,
// aesKey,
// useWorker,
// }
resp = {
type: "offer",
offer: serialize(offer),
iceCandidates: await activeCall.iceCandidates,
capabilities: { encryption },
};
break;
}
case "offer":
if (activeCall) {
resp = { type: "error", message: "accept: call already started" };
}
else if (!supportsInsertableStreams(command.useWorker) && command.aesKey) {
resp = { type: "error", message: "accept: encryption is not supported" };
}
else {
const offer = parse(command.offer);
const remoteIceCandidates = parse(command.iceCandidates);
const { media, aesKey, useWorker, iceServers, relay } = command;
activeCall = await initializeCall(getCallConfig(!!aesKey, iceServers, relay), media, aesKey, useWorker);
const pc = activeCall.connection;
await pc.setRemoteDescription(new RTCSessionDescription(offer));
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
addIceCandidates(pc, remoteIceCandidates);
// same as command for caller to use
resp = {
type: "answer",
answer: serialize(answer),
iceCandidates: await activeCall.iceCandidates,
};
}
break;
case "answer":
if (!pc) {
resp = { type: "error", message: "answer: call not started" };
}
else if (!pc.localDescription) {
resp = { type: "error", message: "answer: local description is not set" };
}
else if (pc.currentRemoteDescription) {
resp = { type: "error", message: "answer: remote description already set" };
}
else {
const answer = parse(command.answer);
const remoteIceCandidates = parse(command.iceCandidates);
await pc.setRemoteDescription(new RTCSessionDescription(answer));
addIceCandidates(pc, remoteIceCandidates);
resp = { type: "ok" };
}
break;
case "ice":
if (pc) {
const remoteIceCandidates = parse(command.iceCandidates);
addIceCandidates(pc, remoteIceCandidates);
resp = { type: "ok" };
}
else {
resp = { type: "error", message: "ice: call not started" };
}
break;
case "media":
if (!activeCall) {
resp = { type: "error", message: "media: call not started" };
}
else if (activeCall.localMedia == CallMediaType.Audio && command.media == CallMediaType.Video) {
resp = { type: "error", message: "media: no video" };
}
else {
enableMedia(activeCall.localStream, command.media, command.enable);
resp = { type: "ok" };
}
break;
case "camera":
if (!activeCall || !pc) {
resp = { type: "error", message: "camera: call not started" };
}
else {
await replaceMedia(activeCall, command.camera);
resp = { type: "ok" };
}
break;
case "end":
endCall();
resp = { type: "ok" };
break;
default:
resp = { type: "error", message: "unknown command" };
break;
}
}
catch (e) {
resp = { type: "error", message: `${command.type}: ${e.message}` };
}
const apiResp = { corrId, resp, command };
sendMessageToNative(apiResp);
return apiResp;
}
function endCall() {
var _a;
try {
(_a = activeCall === null || activeCall === void 0 ? void 0 : activeCall.connection) === null || _a === void 0 ? void 0 : _a.close();
}
catch (e) {
console.log(e);
}
activeCall = undefined;
resetVideoElements();
}
function addIceCandidates(conn, iceCandidates) {
for (const c of iceCandidates) {
conn.addIceCandidate(new RTCIceCandidate(c));
}
}
async function setupMediaStreams(call) {
const videos = getVideoElements();
if (!videos)
throw Error("no video elements");
await setupEncryptionWorker(call);
setupLocalStream(call);
setupRemoteStream(call);
setupCodecPreferences(call);
// setupVideoElement(videos.local)
// setupVideoElement(videos.remote)
videos.local.srcObject = call.localStream;
videos.remote.srcObject = call.remoteStream;
}
async function setupEncryptionWorker(call) {
if (call.aesKey) {
if (!call.key)
call.key = await callCrypto.decodeAesKey(call.aesKey);
if (call.useWorker && !call.worker) {
const workerCode = `const callCrypto = (${callCryptoFunction.toString()})(); (${workerFunction.toString()})()`;
call.worker = new Worker(URL.createObjectURL(new Blob([workerCode], { type: "text/javascript" })));
call.worker.onerror = ({ error, filename, lineno, message }) => console.log(JSON.stringify({ error, filename, lineno, message }));
call.worker.onmessage = ({ data }) => console.log(JSON.stringify({ message: data }));
}
}
}
function setupLocalStream(call) {
const videos = getVideoElements();
if (!videos)
throw Error("no video elements");
const pc = call.connection;
let { localStream } = call;
for (const track of localStream.getTracks()) {
pc.addTrack(track, localStream);
}
if (call.aesKey && call.key) {
console.log("set up encryption for sending");
for (const sender of pc.getSenders()) {
setupPeerTransform(TransformOperation.Encrypt, sender, call.worker, call.aesKey, call.key);
}
}
}
function setupRemoteStream(call) {
// Pull tracks from remote stream as they arrive add them to remoteStream video
const pc = call.connection;
pc.ontrack = (event) => {
try {
if (call.aesKey && call.key) {
console.log("set up decryption for receiving");
setupPeerTransform(TransformOperation.Decrypt, event.receiver, call.worker, call.aesKey, call.key);
}
for (const stream of event.streams) {
for (const track of stream.getTracks()) {
call.remoteStream.addTrack(track);
}
}
console.log(`ontrack success`);
}
catch (e) {
console.log(`ontrack error: ${e.message}`);
}
};
}
function setupCodecPreferences(call) {
// We assume VP8 encoding in the decode/encode stages to get the initial
// bytes to pass as plaintext so we enforce that here.
// VP8 is supported by all supports of webrtc.
// Use of VP8 by default may also reduce depacketisation issues.
// We do not encrypt the first couple of bytes of the payload so that the
// video elements can work by determining video keyframes and the opus mode
// being used. This appears to be necessary for any video feed at all.
// For VP8 this is the content described in
// https://tools.ietf.org/html/rfc6386#section-9.1
// which is 10 bytes for key frames and 3 bytes for delta frames.
// For opus (where encodedFrame.type is not set) this is the TOC byte from
// https://tools.ietf.org/html/rfc6716#section-3.1
var _a;
const capabilities = RTCRtpSender.getCapabilities("video");
if (capabilities) {
const { codecs } = capabilities;
const selectedCodecIndex = codecs.findIndex((c) => c.mimeType === "video/VP8");
const selectedCodec = codecs[selectedCodecIndex];
codecs.splice(selectedCodecIndex, 1);
codecs.unshift(selectedCodec);
for (const t of call.connection.getTransceivers()) {
if (((_a = t.sender.track) === null || _a === void 0 ? void 0 : _a.kind) === "video") {
t.setCodecPreferences(codecs);
}
}
}
}
async function replaceMedia(call, camera) {
const videos = getVideoElements();
if (!videos)
throw Error("no video elements");
const pc = call.connection;
for (const t of call.localStream.getTracks())
t.stop();
call.localCamera = camera;
const localStream = await getLocalMediaStream(call.localMedia, camera);
replaceTracks(pc, localStream.getVideoTracks());
replaceTracks(pc, localStream.getAudioTracks());
call.localStream = localStream;
videos.local.srcObject = localStream;
}
function replaceTracks(pc, tracks) {
if (!tracks.length)
return;
const sender = pc.getSenders().find((s) => { var _a; return ((_a = s.track) === null || _a === void 0 ? void 0 : _a.kind) === tracks[0].kind; });
if (sender)
for (const t of tracks)
sender.replaceTrack(t);
}
function setupPeerTransform(operation, peer, worker, aesKey, key) {
if (worker && "RTCRtpScriptTransform" in window) {
console.log(`${operation} with worker & RTCRtpScriptTransform`);
peer.transform = new RTCRtpScriptTransform(worker, { operation, aesKey });
}
else if ("createEncodedStreams" in peer) {
const { readable, writable } = peer.createEncodedStreams();
if (worker) {
console.log(`${operation} with worker`);
worker.postMessage({ operation, readable, writable, aesKey }, [readable, writable]);
}
else {
console.log(`${operation} without worker`);
const transform = callCrypto.transformFrame[operation](key);
readable.pipeThrough(new TransformStream({ transform })).pipeTo(writable);
}
}
else {
console.log(`no ${operation}`);
}
}
function getLocalMediaStream(mediaType, facingMode) {
const constraints = callMediaConstraints(mediaType, facingMode);
return navigator.mediaDevices.getUserMedia(constraints);
}
function callMediaConstraints(mediaType, facingMode) {
switch (mediaType) {
case CallMediaType.Audio:
return { audio: true, video: false };
case CallMediaType.Video:
return {
audio: true,
video: {
frameRate: 24,
width: {
min: 480,
ideal: 720,
max: 1280,
},
aspectRatio: 1.33,
facingMode,
},
};
}
}
function supportsInsertableStreams(useWorker) {
return (("createEncodedStreams" in RTCRtpSender.prototype && "createEncodedStreams" in RTCRtpReceiver.prototype) ||
(!!useWorker && "RTCRtpScriptTransform" in window));
}
function resetVideoElements() {
const videos = getVideoElements();
if (!videos)
return;
videos.local.srcObject = null;
videos.remote.srcObject = null;
}
function getVideoElements() {
const local = document.getElementById("local-video-stream");
const remote = document.getElementById("remote-video-stream");
if (!(local && remote && local instanceof HTMLMediaElement && remote instanceof HTMLMediaElement))
return;
return { local, remote };
}
// function setupVideoElement(video: HTMLElement) {
// // TODO use display: none
// video.style.opacity = "0"
// video.onplaying = () => {
// video.style.opacity = "1"
// }
// }
function enableMedia(s, media, enable) {
const tracks = media == CallMediaType.Video ? s.getVideoTracks() : s.getAudioTracks();
for (const t of tracks)
t.enabled = enable;
}
return processCommand;
})();
// Cryptography function - it is loaded both in the main window and in worker context (if the worker is used)
function callCryptoFunction() {
const initialPlainTextRequired = {
key: 10,
delta: 3,
empty: 1,
};
const IV_LENGTH = 12;
function encryptFrame(key) {
return async (frame, controller) => {
const data = new Uint8Array(frame.data);
const n = initialPlainTextRequired[frame.type] || 1;
const iv = randomIV();
const initial = data.subarray(0, n);
const plaintext = data.subarray(n, data.byteLength);
try {
const ciphertext = plaintext.length
? new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv: iv.buffer }, key, plaintext))
: new Uint8Array(0);
frame.data = concatN(initial, ciphertext, iv).buffer;
controller.enqueue(frame);
}
catch (e) {
console.log(`encryption error ${e}`);
throw e;
}
};
}
function decryptFrame(key) {
return async (frame, controller) => {
const data = new Uint8Array(frame.data);
const n = initialPlainTextRequired[frame.type] || 1;
const initial = data.subarray(0, n);
const ciphertext = data.subarray(n, data.byteLength - IV_LENGTH);
const iv = data.subarray(data.byteLength - IV_LENGTH, data.byteLength);
try {
const plaintext = ciphertext.length
? new Uint8Array(await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, ciphertext))
: new Uint8Array(0);
frame.data = concatN(initial, plaintext).buffer;
controller.enqueue(frame);
}
catch (e) {
console.log(`decryption error ${e}`);
throw e;
}
};
}
function decodeAesKey(aesKey) {
const keyData = callCrypto.decodeBase64url(callCrypto.encodeAscii(aesKey));
return crypto.subtle.importKey("raw", keyData, { name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"]);
}
function concatN(...bs) {
const a = new Uint8Array(bs.reduce((size, b) => size + b.byteLength, 0));
bs.reduce((offset, b) => {
a.set(b, offset);
return offset + b.byteLength;
}, 0);
return a;
}
function randomIV() {
return crypto.getRandomValues(new Uint8Array(IV_LENGTH));
}
const base64urlChars = new Uint8Array("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".split("").map((c) => c.charCodeAt(0)));
const base64urlLookup = new Array(256);
base64urlChars.forEach((c, i) => (base64urlLookup[c] = i));
const char_equal = "=".charCodeAt(0);
function encodeAscii(s) {
const a = new Uint8Array(s.length);
let i = s.length;
while (i--)
a[i] = s.charCodeAt(i);
return a;
}
function decodeAscii(a) {
let s = "";
for (let i = 0; i < a.length; i++)
s += String.fromCharCode(a[i]);
return s;
}
function encodeBase64url(a) {
const len = a.length;
const b64len = Math.ceil(len / 3) * 4;
const b64 = new Uint8Array(b64len);
let j = 0;
for (let i = 0; i < len; i += 3) {
b64[j++] = base64urlChars[a[i] >> 2];
b64[j++] = base64urlChars[((a[i] & 3) << 4) | (a[i + 1] >> 4)];
b64[j++] = base64urlChars[((a[i + 1] & 15) << 2) | (a[i + 2] >> 6)];
b64[j++] = base64urlChars[a[i + 2] & 63];
}
if (len % 3)
b64[b64len - 1] = char_equal;
if (len % 3 === 1)
b64[b64len - 2] = char_equal;
return b64;
}
function decodeBase64url(b64) {
let len = b64.length;
if (len % 4)
return;
let bLen = (len * 3) / 4;
if (b64[len - 1] === char_equal) {
len--;
bLen--;
if (b64[len - 1] === char_equal) {
len--;
bLen--;
}
}
const bytes = new Uint8Array(bLen);
let i = 0;
let pos = 0;
while (i < len) {
const enc1 = base64urlLookup[b64[i++]];
const enc2 = i < len ? base64urlLookup[b64[i++]] : 0;
const enc3 = i < len ? base64urlLookup[b64[i++]] : 0;
const enc4 = i < len ? base64urlLookup[b64[i++]] : 0;
if (enc1 === undefined || enc2 === undefined || enc3 === undefined || enc4 === undefined)
return;
bytes[pos++] = (enc1 << 2) | (enc2 >> 4);
bytes[pos++] = ((enc2 & 15) << 4) | (enc3 >> 2);
bytes[pos++] = ((enc3 & 3) << 6) | (enc4 & 63);
}
return bytes;
}
return {
transformFrame: { encrypt: encryptFrame, decrypt: decryptFrame },
decodeAesKey,
encodeAscii,
decodeAscii,
encodeBase64url,
decodeBase64url,
};
}
// If the worker is used for decryption, this function code (as string) is used to load the worker via Blob
// We have to use worker optionally, as it crashes in Android web view, regardless of how it is loaded
function workerFunction() {
// encryption with createEncodedStreams support
self.addEventListener("message", async ({ data }) => {
await setupTransform(data);
});
// encryption using RTCRtpScriptTransform.
if ("RTCTransformEvent" in self) {
self.addEventListener("rtctransform", async ({ transformer }) => {
try {
const { operation, aesKey } = transformer.options;
const { readable, writable } = transformer;
await setupTransform({ operation, aesKey, readable, writable });
self.postMessage({ result: "setupTransform success" });
}
catch (e) {
self.postMessage({ message: `setupTransform error: ${e.message}` });
}
});
}
async function setupTransform({ operation, aesKey, readable, writable }) {
const key = await callCrypto.decodeAesKey(aesKey);
const transform = callCrypto.transformFrame[operation](key);
readable.pipeThrough(new TransformStream({ transform })).pipeTo(writable);
}
}
//# sourceMappingURL=call.js.map

View File

@@ -0,0 +1 @@
var LZString=function(){function o(o,r){if(!t[o]){t[o]={};for(var n=0;n<o.length;n++)t[o][o.charAt(n)]=n}return t[o][r]}var r=String.fromCharCode,n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$",t={},i={compressToBase64:function(o){if(null==o)return"";var r=i._compress(o,6,function(o){return n.charAt(o)});switch(r.length%4){default:case 0:return r;case 1:return r+"===";case 2:return r+"==";case 3:return r+"="}},decompressFromBase64:function(r){return null==r?"":""==r?null:i._decompress(r.length,32,function(e){return o(n,r.charAt(e))})},compressToUTF16:function(o){return null==o?"":i._compress(o,15,function(o){return r(o+32)})+" "},decompressFromUTF16:function(o){return null==o?"":""==o?null:i._decompress(o.length,16384,function(r){return o.charCodeAt(r)-32})},compressToUint8Array:function(o){for(var r=i.compress(o),n=new Uint8Array(2*r.length),e=0,t=r.length;t>e;e++){var s=r.charCodeAt(e);n[2*e]=s>>>8,n[2*e+1]=s%256}return n},decompressFromUint8Array:function(o){if(null===o||void 0===o)return i.decompress(o);for(var n=new Array(o.length/2),e=0,t=n.length;t>e;e++)n[e]=256*o[2*e]+o[2*e+1];var s=[];return n.forEach(function(o){s.push(r(o))}),i.decompress(s.join(""))},compressToEncodedURIComponent:function(o){return null==o?"":i._compress(o,6,function(o){return e.charAt(o)})},decompressFromEncodedURIComponent:function(r){return null==r?"":""==r?null:(r=r.replace(/ /g,"+"),i._decompress(r.length,32,function(n){return o(e,r.charAt(n))}))},compress:function(o){return i._compress(o,16,function(o){return r(o)})},_compress:function(o,r,n){if(null==o)return"";var e,t,i,s={},p={},u="",c="",a="",l=2,f=3,h=2,d=[],m=0,v=0;for(i=0;i<o.length;i+=1)if(u=o.charAt(i),Object.prototype.hasOwnProperty.call(s,u)||(s[u]=f++,p[u]=!0),c=a+u,Object.prototype.hasOwnProperty.call(s,c))a=c;else{if(Object.prototype.hasOwnProperty.call(p,a)){if(a.charCodeAt(0)<256){for(e=0;h>e;e++)m<<=1,v==r-1?(v=0,d.push(n(m)),m=0):v++;for(t=a.charCodeAt(0),e=0;8>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;h>e;e++)m=m<<1|t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=a.charCodeAt(0),e=0;16>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}l--,0==l&&(l=Math.pow(2,h),h++),delete p[a]}else for(t=s[a],e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;l--,0==l&&(l=Math.pow(2,h),h++),s[c]=f++,a=String(u)}if(""!==a){if(Object.prototype.hasOwnProperty.call(p,a)){if(a.charCodeAt(0)<256){for(e=0;h>e;e++)m<<=1,v==r-1?(v=0,d.push(n(m)),m=0):v++;for(t=a.charCodeAt(0),e=0;8>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;h>e;e++)m=m<<1|t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=a.charCodeAt(0),e=0;16>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}l--,0==l&&(l=Math.pow(2,h),h++),delete p[a]}else for(t=s[a],e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;l--,0==l&&(l=Math.pow(2,h),h++)}for(t=2,e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;for(;;){if(m<<=1,v==r-1){d.push(n(m));break}v++}return d.join("")},decompress:function(o){return null==o?"":""==o?null:i._decompress(o.length,32768,function(r){return o.charCodeAt(r)})},_decompress:function(o,n,e){var t,i,s,p,u,c,a,l,f=[],h=4,d=4,m=3,v="",w=[],A={val:e(0),position:n,index:1};for(i=0;3>i;i+=1)f[i]=i;for(p=0,c=Math.pow(2,2),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;switch(t=p){case 0:for(p=0,c=Math.pow(2,8),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;l=r(p);break;case 1:for(p=0,c=Math.pow(2,16),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;l=r(p);break;case 2:return""}for(f[3]=l,s=l,w.push(l);;){if(A.index>o)return"";for(p=0,c=Math.pow(2,m),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;switch(l=p){case 0:for(p=0,c=Math.pow(2,8),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;f[d++]=r(p),l=d-1,h--;break;case 1:for(p=0,c=Math.pow(2,16),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;f[d++]=r(p),l=d-1,h--;break;case 2:return w.join("")}if(0==h&&(h=Math.pow(2,m),m++),f[l])v=f[l];else{if(l!==d)return null;v=s+s.charAt(0)}w.push(v),f[d++]=s+v.charAt(0),h--,s=v,0==h&&(h=Math.pow(2,m),m++)}}};return i}();"function"==typeof define&&define.amd?define(function(){return LZString}):"undefined"!=typeof module&&null!=module&&(module.exports=LZString);

View File

@@ -0,0 +1,41 @@
html,
body {
padding: 0;
margin: 0;
background-color: black;
}
#remote-video-stream {
position: absolute;
width: 100%;
height: 100%;
object-fit: cover;
}
#local-video-stream {
position: absolute;
width: 30%;
max-width: 30%;
object-fit: cover;
margin: 16px;
border-radius: 16px;
top: 0;
right: 0;
}
*::-webkit-media-controls {
display: none !important;
-webkit-appearance: none !important;
}
*::-webkit-media-controls-panel {
display: none !important;
-webkit-appearance: none !important;
}
*::-webkit-media-controls-play-button {
display: none !important;
-webkit-appearance: none !important;
}
*::-webkit-media-controls-start-playback-button {
display: none !important;
-webkit-appearance: none !important;
}

View File

@@ -0,0 +1,67 @@
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.10.2)
# Declares and names the project.
project("app")
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
app-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
simplex-api.c)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
find_library( # Sets the name of the path variable.
c-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
c
NAMES libc.so
REQUIRED)
add_library( simplex SHARED IMPORTED )
set_target_properties( simplex PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libsimplex.so)
add_library( support SHARED IMPORTED )
set_target_properties( support PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libsupport.so)
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
app-lib
simplex support
# Links the target library to the log library
# included in the NDK.
${log-lib})

View File

@@ -0,0 +1,111 @@
#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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -0,0 +1,18 @@
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

@@ -0,0 +1,529 @@
package chat.simplex.app
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.MaterialTheme
import androidx.compose.material.Surface
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.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.*
import chat.simplex.app.model.ChatModel
import chat.simplex.app.model.NtfManager
import chat.simplex.app.model.NtfManager.Companion.getUserIdFromIntent
import chat.simplex.app.ui.theme.SimpleButton
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.SplashView
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.newchat.*
import chat.simplex.app.views.onboarding.*
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 = { m.controller.showLANotice(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
if (enteredBackgroundVal == null || elapsedRealtime() - enteredBackgroundVal >= 30_000) {
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(
generalGetString(R.string.auth_unlock),
generalGetString(R.string.auth_log_in_using_credential),
this@MainActivity,
completed = { laResult ->
when (laResult) {
LAResult.Success ->
userAuthorized.value = true
is LAResult.Error, LAResult.Failed ->
laFailed.value = true
LAResult.Unavailable -> {
userAuthorized.value = true
m.performLA.value = false
m.controller.appPrefs.performLA.set(false)
laUnavailableTurningOffAlert()
}
}
}
)
}
}
}
}
private fun setPerformLA(on: Boolean) {
vm.chatModel.controller.appPrefs.laNoticeShown.set(true)
if (on) {
enableLA()
} else {
disableLA()
}
}
private fun enableLA() {
val m = vm.chatModel
authenticate(
generalGetString(R.string.auth_enable_simplex_lock),
generalGetString(R.string.auth_confirm_credential),
this@MainActivity,
completed = { laResult ->
val prefPerformLA = m.controller.appPrefs.performLA
when (laResult) {
LAResult.Success -> {
m.performLA.value = true
prefPerformLA.set(true)
laTurnedOnAlert()
}
is LAResult.Error, LAResult.Failed -> {
m.performLA.value = false
prefPerformLA.set(false)
}
LAResult.Unavailable -> {
m.performLA.value = false
prefPerformLA.set(false)
laUnavailableInstructionAlert()
}
}
}
)
}
private fun disableLA() {
val m = vm.chatModel
authenticate(
generalGetString(R.string.auth_disable_simplex_lock),
generalGetString(R.string.auth_confirm_credential),
this@MainActivity,
completed = { laResult ->
val prefPerformLA = m.controller.appPrefs.performLA
when (laResult) {
LAResult.Success -> {
m.performLA.value = false
prefPerformLA.set(false)
}
is LAResult.Error, LAResult.Failed -> {
m.performLA.value = true
prefPerformLA.set(true)
}
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) -> 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()
}
}
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

@@ -0,0 +1,241 @@
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

@@ -0,0 +1,329 @@
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)
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,315 @@
package chat.simplex.app.model
import android.app.*
import android.content.*
import android.graphics.BitmapFactory
import android.hardware.display.DisplayManager
import android.media.AudioAttributes
import android.net.Uri
import android.util.Log
import android.view.Display
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import chat.simplex.app.*
import chat.simplex.app.views.call.*
import chat.simplex.app.views.chatlist.acceptContactRequest
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.usersettings.NotificationPreviewMode
import kotlinx.datetime.Clock
class NtfManager(val context: Context, private val appPreferences: AppPreferences) {
companion object {
const val MessageChannel: String = "chat.simplex.app.MESSAGE_NOTIFICATION"
const val MessageGroup: String = "chat.simplex.app.MESSAGE_NOTIFICATION"
const val OpenChatAction: String = "chat.simplex.app.OPEN_CHAT"
const val ShowChatsAction: String = "chat.simplex.app.SHOW_CHATS"
// DO NOT change notification channel settings / names
const val CallChannel: String = "chat.simplex.app.CALL_NOTIFICATION_1"
const val AcceptCallAction: String = "chat.simplex.app.ACCEPT_CALL"
const val RejectCallAction: String = "chat.simplex.app.REJECT_CALL"
const val CallNotificationId: Int = -1
private const val UserIdKey: String = "userId"
private const val ChatIdKey: String = "chatId"
fun getUserIdFromIntent(intent: Intent?): Long? {
val userId = intent?.getLongExtra(UserIdKey, -1L)
return if (userId == -1L || userId == null) null else userId
}
}
private val manager: NotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
private var prevNtfTime = mutableMapOf<String, Long>()
private val msgNtfTimeoutMs = 30000L
init {
if (manager.areNotificationsEnabled()) createNtfChannelsMaybeShowAlert()
}
enum class NotificationAction {
ACCEPT_CONTACT_REQUEST
}
private fun callNotificationChannel(channelId: String, channelName: String): NotificationChannel {
val callChannel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH)
val attrs = AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
.build()
val soundUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.packageName + "/" + R.raw.ring_once)
Log.d(TAG,"callNotificationChannel sound: $soundUri")
callChannel.setSound(soundUri, attrs)
callChannel.enableVibration(true)
// the numbers below are explained here: https://developer.android.com/reference/android/os/Vibrator
// (wait, vibration duration, wait till off, wait till on again = ringtone mp3 duration - vibration duration - ~50ms lost somewhere)
callChannel.vibrationPattern = longArrayOf(250, 250, 0, 2600)
return callChannel
}
fun cancelNotificationsForChat(chatId: String) {
prevNtfTime.remove(chatId)
manager.cancel(chatId.hashCode())
val msgNtfs = manager.activeNotifications.filter {
ntf -> ntf.notification.channelId == MessageChannel
}
if (msgNtfs.count() == 1) {
// Have a group notification with no children so cancel it
manager.cancel(0)
}
}
fun notifyContactRequestReceived(user: User, cInfo: ChatInfo.ContactRequest) {
displayNotification(
user = user,
chatId = cInfo.id,
displayName = cInfo.displayName,
msgText = generalGetString(R.string.notification_new_contact_request),
image = cInfo.image,
listOf(NotificationAction.ACCEPT_CONTACT_REQUEST)
)
}
fun notifyContactConnected(user: User, contact: Contact) {
displayNotification(
user = user,
chatId = contact.id,
displayName = contact.displayName,
msgText = generalGetString(R.string.notification_contact_connected)
)
}
fun notifyMessageReceived(user: User, cInfo: ChatInfo, cItem: ChatItem) {
if (!cInfo.ntfsEnabled) return
displayNotification(user = user, chatId = cInfo.id, displayName = cInfo.displayName, msgText = hideSecrets(cItem))
}
fun displayNotification(user: User, chatId: String, displayName: String, msgText: String, image: String? = null, actions: List<NotificationAction> = emptyList()) {
if (!user.showNotifications) return
Log.d(TAG, "notifyMessageReceived $chatId")
val now = Clock.System.now().toEpochMilliseconds()
val recentNotification = (now - prevNtfTime.getOrDefault(chatId, 0) < msgNtfTimeoutMs)
prevNtfTime[chatId] = now
val previewMode = appPreferences.notificationPreviewMode.get()
val title = if (previewMode == NotificationPreviewMode.HIDDEN.name) generalGetString(R.string.notification_preview_somebody) else displayName
val content = if (previewMode != NotificationPreviewMode.MESSAGE.name) generalGetString(R.string.notification_preview_new_message) else msgText
val largeIcon = when {
actions.isEmpty() -> null
image == null || previewMode == NotificationPreviewMode.HIDDEN.name -> BitmapFactory.decodeResource(context.resources, R.drawable.icon)
else -> base64ToBitmap(image)
}
val builder = NotificationCompat.Builder(context, MessageChannel)
.setContentTitle(title)
.setContentText(content)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setGroup(MessageGroup)
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
.setSmallIcon(R.drawable.ntf_icon)
.setLargeIcon(largeIcon)
.setColor(0x88FFFF)
.setAutoCancel(true)
.setVibrate(if (actions.isEmpty()) null else longArrayOf(0, 250, 250, 250))
.setContentIntent(chatPendingIntent(OpenChatAction, user.userId, chatId))
.setSilent(if (actions.isEmpty()) recentNotification else false)
for (action in actions) {
val flags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
val actionIntent = Intent(SimplexApp.context, NtfActionReceiver::class.java)
actionIntent.action = action.name
actionIntent.putExtra(UserIdKey, user.userId)
actionIntent.putExtra(ChatIdKey, chatId)
val actionPendingIntent: PendingIntent = PendingIntent.getBroadcast(SimplexApp.context, 0, actionIntent, flags)
val actionButton = when (action) {
NotificationAction.ACCEPT_CONTACT_REQUEST -> generalGetString(R.string.accept)
}
builder.addAction(0, actionButton, actionPendingIntent)
}
val summary = NotificationCompat.Builder(context, MessageChannel)
.setSmallIcon(R.drawable.ntf_icon)
.setColor(0x88FFFF)
.setGroup(MessageGroup)
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
.setGroupSummary(true)
.setContentIntent(chatPendingIntent(ShowChatsAction, null))
.build()
with(NotificationManagerCompat.from(context)) {
// using cInfo.id only shows one notification per chat and updates it when the message arrives
notify(chatId.hashCode(), builder.build())
notify(0, summary)
}
}
fun notifyCallInvitation(invitation: RcvCallInvitation) {
val keyguardManager = getKeyguardManager(context)
Log.d(TAG,
"notifyCallInvitation pre-requests: " +
"keyguard locked ${keyguardManager.isKeyguardLocked}, " +
"callOnLockScreen ${appPreferences.callOnLockScreen.get()}, " +
"onForeground ${SimplexApp.context.isAppOnForeground}"
)
if (SimplexApp.context.isAppOnForeground) return
val contactId = invitation.contact.id
Log.d(TAG, "notifyCallInvitation $contactId")
val image = invitation.contact.image
val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
val screenOff = displayManager.displays.all { it.state != Display.STATE_ON }
var ntfBuilder =
if ((keyguardManager.isKeyguardLocked || screenOff) && appPreferences.callOnLockScreen.get() != CallOnLockScreen.DISABLE) {
val fullScreenIntent = Intent(context, IncomingCallActivity::class.java)
val fullScreenPendingIntent = PendingIntent.getActivity(context, 0, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
NotificationCompat.Builder(context, CallChannel)
.setFullScreenIntent(fullScreenPendingIntent, true)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
} else {
val soundUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.packageName + "/" + R.raw.ring_once)
val fullScreenPendingIntent = PendingIntent.getActivity(context, 0, Intent(), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
NotificationCompat.Builder(context, CallChannel)
.setContentIntent(chatPendingIntent(OpenChatAction, invitation.user.userId, invitation.contact.id))
.addAction(R.drawable.ntf_icon, generalGetString(R.string.accept), chatPendingIntent(AcceptCallAction, invitation.user.userId, contactId))
.addAction(R.drawable.ntf_icon, generalGetString(R.string.reject), chatPendingIntent(RejectCallAction, invitation.user.userId, contactId, true))
.setFullScreenIntent(fullScreenPendingIntent, true)
.setSound(soundUri)
}
val text = generalGetString(
if (invitation.callType.media == CallMediaType.Video) {
if (invitation.sharedKey == null) R.string.video_call_no_encryption else R.string.encrypted_video_call
} else {
if (invitation.sharedKey == null) R.string.audio_call_no_encryption else R.string.encrypted_audio_call
}
)
val previewMode = appPreferences.notificationPreviewMode.get()
val title = if (previewMode == NotificationPreviewMode.HIDDEN.name)
generalGetString(R.string.notification_preview_somebody)
else
invitation.contact.displayName
val largeIcon = if (image == null || previewMode == NotificationPreviewMode.HIDDEN.name)
BitmapFactory.decodeResource(context.resources, R.drawable.icon)
else
base64ToBitmap(image)
ntfBuilder = ntfBuilder
.setContentTitle(title)
.setContentText(text)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(NotificationCompat.CATEGORY_CALL)
.setSmallIcon(R.drawable.ntf_icon)
.setLargeIcon(largeIcon)
.setColor(0x88FFFF)
.setAutoCancel(true)
val notification = ntfBuilder.build()
// This makes notification sound and vibration repeat endlessly
notification.flags = notification.flags or NotificationCompat.FLAG_INSISTENT
with(NotificationManagerCompat.from(context)) {
notify(CallNotificationId, notification)
}
}
fun cancelCallNotification() {
manager.cancel(CallNotificationId)
}
fun hasNotificationsForChat(chatId: String): Boolean = manager.activeNotifications.any { it.id == chatId.hashCode() }
private fun hideSecrets(cItem: ChatItem) : String {
val md = cItem.formattedText
return if (md != null) {
var res = ""
for (ft in md) {
res += if (ft.format is Format.Secret) "..." else ft.text
}
res
} else {
cItem.text
}
}
private fun chatPendingIntent(intentAction: String, userId: Long?, chatId: String? = null, broadcast: Boolean = false): PendingIntent {
Log.d(TAG, "chatPendingIntent for $intentAction")
val uniqueInt = (System.currentTimeMillis() and 0xfffffff).toInt()
var intent = Intent(context, if (!broadcast) MainActivity::class.java else NtfActionReceiver::class.java)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
.setAction(intentAction)
.putExtra(UserIdKey, userId)
if (chatId != null) intent = intent.putExtra(ChatIdKey, chatId)
return if (!broadcast) {
TaskStackBuilder.create(context).run {
addNextIntentWithParentStack(intent)
getPendingIntent(uniqueInt, PendingIntent.FLAG_IMMUTABLE)
}
} else {
PendingIntent.getBroadcast(SimplexApp.context, uniqueInt, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
}
}
/**
* This function creates notifications channels. On Android 13+ calling it for the first time will trigger system alert,
* The alert asks a user to allow or disallow to show notifications for the app. That's why it should be called only when the user
* already saw such alert or when you want to trigger showing the alert.
* On the first app launch the channels will be created after user profile is created. Subsequent calls will create new channels and delete
* old ones if needed
* */
fun createNtfChannelsMaybeShowAlert() {
manager.createNotificationChannel(NotificationChannel(MessageChannel, generalGetString(R.string.ntf_channel_messages), NotificationManager.IMPORTANCE_HIGH))
manager.createNotificationChannel(callNotificationChannel(CallChannel, generalGetString(R.string.ntf_channel_calls)))
// Remove old channels since they can't be edited
manager.deleteNotificationChannel("chat.simplex.app.CALL_NOTIFICATION")
manager.deleteNotificationChannel("chat.simplex.app.LOCK_SCREEN_CALL_NOTIFICATION")
}
/**
* Processes every action specified by [NotificationCompat.Builder.addAction] that comes with [NotificationAction]
* and [ChatInfo.id] as [ChatIdKey] in extra
* */
class NtfActionReceiver: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val userId = getUserIdFromIntent(intent)
val chatId = intent?.getStringExtra(ChatIdKey) ?: return
val m = SimplexApp.context.chatModel
when (intent.action) {
NotificationAction.ACCEPT_CONTACT_REQUEST.name -> {
val isCurrentUser = m.currentUser.value?.userId == userId
val cInfo: ChatInfo.ContactRequest? = if (isCurrentUser) {
(m.getChat(chatId)?.chatInfo as? ChatInfo.ContactRequest) ?: return
} else {
null
}
val apiId = chatId.replace("<@", "").toLongOrNull() ?: return
acceptContactRequest(apiId, cInfo, isCurrentUser, m)
m.controller.ntfManager.cancelNotificationsForChat(chatId)
}
RejectCallAction -> {
val invitation = m.callInvitations[chatId]
if (invitation != null) {
m.callManager.endCall(invitation = invitation)
}
}
else -> {
Log.e(TAG, "Unknown action. Make sure you provide action from NotificationAction enum")
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
package chat.simplex.app.ui.theme
import androidx.compose.ui.graphics.Color
val Purple200 = Color(0xFFBB86FC)
val Purple500 = Color(0xFF6200EE)
val Purple700 = Color(0xFF3700B3)
val Teal200 = Color(0xFF03DAC5)
val Gray = Color(0x22222222)
val Indigo = Color(0xFF9966FF)
val SimplexBlue = Color(0, 136, 255, 255) // If this value changes also need to update #0088ff in string resource files
val SimplexGreen = Color(77, 218, 103, 255)
val SecretColor = Color(0x40808080)
val LightGray = Color(241, 242, 246, 255)
val DarkGray = Color(43, 44, 46, 255)
val HighOrLowlight = Color(139, 135, 134, 255)
val MessagePreviewDark = Color(179, 175, 174, 255)
val MessagePreviewLight = Color(49, 45, 44, 255)
val ToolbarLight = Color(220, 220, 220, 12)
val ToolbarDark = Color(80, 80, 80, 12)
val SettingsBackgroundLight = Color(220, 216, 215, 90)
val SettingsSecondaryLight = Color(200, 196, 195, 90)
val GroupDark = Color(80, 80, 80, 60)
val IncomingCallLight = Color(239, 237, 236, 255)
val IncomingCallDark = Color(34, 30, 29, 255)
val WarningOrange = Color(255, 127, 0, 255)
val WarningYellow = Color(255, 192, 0, 255)
val FileLight = Color(183, 190, 199, 255)
val FileDark = Color(101, 101, 106, 255)

View File

@@ -0,0 +1,11 @@
package chat.simplex.app.ui.theme
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Shapes
import androidx.compose.ui.unit.dp
val Shapes = Shapes(
small = RoundedCornerShape(4.dp),
medium = RoundedCornerShape(4.dp),
large = RoundedCornerShape(0.dp)
)

View File

@@ -0,0 +1,77 @@
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 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(0xFFCF6679),
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,
// 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

@@ -0,0 +1,64 @@
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

@@ -0,0 +1,56 @@
package chat.simplex.app.ui.theme
import androidx.compose.material.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.*
import androidx.compose.ui.unit.sp
import chat.simplex.app.R
// https://github.com/rsms/inter
val Inter = FontFamily(
Font(R.font.inter_regular),
Font(R.font.inter_italic, style = FontStyle.Italic),
Font(R.font.inter_bold, weight = FontWeight.Bold),
Font(R.font.inter_semi_bold, weight = FontWeight.SemiBold),
Font(R.font.inter_medium, weight = FontWeight.Medium),
Font(R.font.inter_light, weight = FontWeight.Light),
)
// Set of Material typography styles to start with
val Typography = Typography(
h1 = TextStyle(
fontFamily = Inter,
fontWeight = FontWeight.Bold,
fontSize = 32.sp,
),
h2 = TextStyle(
fontFamily = Inter,
fontWeight = FontWeight.Normal,
fontSize = 24.sp
),
h3 = TextStyle(
fontFamily = Inter,
fontWeight = FontWeight.Normal,
fontSize = 18.5.sp
),
body1 = TextStyle(
fontFamily = Inter,
fontWeight = FontWeight.Normal,
fontSize = 16.sp
),
body2 = TextStyle(
fontFamily = Inter,
fontWeight = FontWeight.Normal,
fontSize = 14.sp
),
button = TextStyle(
fontFamily = Inter,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
),
caption = TextStyle(
fontFamily = Inter,
fontWeight = FontWeight.Normal,
fontSize = 18.sp
)
)

View File

@@ -0,0 +1,25 @@
package chat.simplex.app.views
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@Composable
fun SplashView() {
Surface(
Modifier
.background(MaterialTheme.colors.background)
.fillMaxSize()
) {
// Image(
// painter = painterResource(R.drawable.logo),
// contentDescription = "Simplex Icon",
// modifier = Modifier
// .height(230.dp)
// .align(Alignment.Center)
// )
}
}

View File

@@ -0,0 +1,158 @@
package chat.simplex.app.views
import android.content.res.Configuration
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.*
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.chat.*
import chat.simplex.app.views.helpers.*
import com.google.accompanist.insets.ProvideWindowInsets
import com.google.accompanist.insets.navigationBarsWithImePadding
@Composable
fun TerminalView(chatModel: ChatModel, close: () -> Unit) {
val composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = false)) }
BackHandler(onBack = {
close()
})
TerminalLayout(
remember { chatModel.terminalItems },
composeState,
sendCommand = { sendCommand(chatModel, composeState) },
close
)
}
private fun sendCommand(chatModel: ChatModel, composeState: MutableState<ComposeState>) {
val developerTools = chatModel.controller.appPrefs.developerTools.get()
val prefPerformLA = chatModel.controller.appPrefs.performLA.get()
val s = composeState.value.message
if (s.startsWith("/sql") && (!prefPerformLA || !developerTools)) {
val resp = CR.ChatCmdError(null, ChatError.ChatErrorChat(ChatErrorType.СommandError("Failed reading: empty")))
chatModel.addTerminalItem(TerminalItem.cmd(CC.Console(s)))
chatModel.addTerminalItem(TerminalItem.resp(resp))
composeState.value = ComposeState(useLinkPreviews = false)
} else {
withApi {
// show "in progress"
chatModel.controller.sendCmd(CC.Console(s))
composeState.value = ComposeState(useLinkPreviews = false)
// hide "in progress"
}
}
}
@Composable
fun TerminalLayout(
terminalItems: List<TerminalItem>,
composeState: MutableState<ComposeState>,
sendCommand: () -> Unit,
close: () -> Unit
) {
val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground)
val textStyle = remember { mutableStateOf(smallFont) }
fun onMessageChange(s: String) {
composeState.value = composeState.value.copy(message = s)
}
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
Scaffold(
topBar = { CloseSheetBar(close) },
bottomBar = {
Box(Modifier.padding(horizontal = 8.dp)) {
SendMsgView(
composeState = composeState,
showVoiceRecordIcon = false,
recState = remember { mutableStateOf(RecordingState.NotStarted) },
isDirectChat = false,
liveMessageAlertShown = SharedPreference(get = { false }, set = {}),
needToAllowVoiceToContact = false,
allowedVoiceByPrefs = false,
userIsObserver = false,
userCanSend = true,
allowVoiceToContact = {},
sendMessage = sendCommand,
sendLiveMessage = null,
updateLiveMessage = null,
onMessageChange = ::onMessageChange,
textStyle = textStyle
)
}
},
modifier = Modifier.navigationBarsWithImePadding()
) { contentPadding ->
Surface(
modifier = Modifier
.padding(contentPadding)
.fillMaxWidth()
.background(MaterialTheme.colors.background)
) {
TerminalLog(terminalItems)
}
}
}
}
private var lazyListState = 0 to 0
@Composable
fun TerminalLog(terminalItems: List<TerminalItem>) {
val listState = rememberLazyListState(lazyListState.first, lazyListState.second)
DisposableEffect(Unit) {
onDispose { lazyListState = listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset }
}
val reversedTerminalItems by remember { derivedStateOf { terminalItems.reversed().toList() } }
val context = LocalContext.current
LazyColumn(state = listState, reverseLayout = true) {
items(reversedTerminalItems) { item ->
Text(
"${item.date.toString().subSequence(11, 19)} ${item.label}",
style = TextStyle(fontFamily = FontFamily.Monospace, fontSize = 18.sp, color = MaterialTheme.colors.primary),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier
.fillMaxWidth()
.clickable {
ModalManager.shared.showModal(endButtons = { ShareButton { shareText(context, item.details) } }) {
SelectionContainer(modifier = Modifier.verticalScroll(rememberScrollState())) {
Text(item.details, modifier = Modifier.padding(horizontal = DEFAULT_PADDING).padding(bottom = DEFAULT_PADDING))
}
}
}.padding(horizontal = 8.dp, vertical = 4.dp)
)
}
}
}
@Preview(showBackground = true)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
fun PreviewTerminalLayout() {
SimpleXTheme {
TerminalLayout(
terminalItems = TerminalItem.sampleData,
composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = false)) },
sendCommand = {},
close = {}
)
}
}

View File

@@ -0,0 +1,151 @@
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.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
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.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.*
import chat.simplex.app.R
import chat.simplex.app.model.ChatModel
import chat.simplex.app.model.Profile
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.AppBarTitle
import chat.simplex.app.views.helpers.withApi
import chat.simplex.app.views.onboarding.OnboardingStage
import chat.simplex.app.views.onboarding.ReadableText
import com.google.accompanist.insets.navigationBarsWithImePadding
import kotlinx.coroutines.delay
fun isValidDisplayName(name: String) : Boolean {
return (name.firstOrNull { it.isWhitespace() }) == null
}
@Composable
fun CreateProfilePanel(chatModel: ChatModel, close: () -> Unit) {
val displayName = remember { mutableStateOf("") }
val fullName = remember { mutableStateOf("") }
val focusRequester = remember { FocusRequester() }
Surface(Modifier.background(MaterialTheme.colors.onBackground)) {
Column(
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())
) {
AppBarTitle(stringResource(R.string.create_profile), false)
ReadableText(R.string.your_profile_is_stored_on_your_device)
ReadableText(R.string.profile_is_only_shared_with_your_contacts)
Spacer(Modifier.height(10.dp))
Text(
stringResource(R.string.display_name),
style = MaterialTheme.typography.h6,
modifier = Modifier.padding(bottom = 3.dp)
)
ProfileNameField(displayName, focusRequester)
val errorText = if (!isValidDisplayName(displayName.value)) stringResource(R.string.display_name_cannot_contain_whitespace) else ""
Text(
errorText,
fontSize = 15.sp,
color = MaterialTheme.colors.error
)
Spacer(Modifier.height(3.dp))
Text(
stringResource(R.string.full_name_optional__prompt),
style = MaterialTheme.typography.h6,
modifier = Modifier.padding(bottom = 5.dp)
)
ProfileNameField(fullName)
Spacer(Modifier.fillMaxHeight().weight(1f))
Row {
if (chatModel.users.isEmpty()) {
SimpleButton(
text = stringResource(R.string.about_simplex),
icon = Icons.Outlined.ArrowBackIosNew
) { chatModel.onboardingStage.value = OnboardingStage.Step1_SimpleXInfo }
}
Spacer(Modifier.fillMaxWidth().weight(1f))
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)
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>, focusRequester: FocusRequester? = null) {
val modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colors.secondary)
.height(40.dp)
.clip(RoundedCornerShape(5.dp))
.padding(8.dp)
.navigationBarsWithImePadding()
BasicTextField(
value = name.value,
onValueChange = { name.value = it },
modifier = if (focusRequester == null) modifier else modifier.focusRequester(focusRequester),
textStyle = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground),
keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.None,
autoCorrect = false
),
singleLine = true,
cursorBrush = SolidColor(HighOrLowlight)
)
}

View File

@@ -0,0 +1,109 @@
package chat.simplex.app.views.call
import android.util.Log
import chat.simplex.app.TAG
import chat.simplex.app.model.ChatModel
import chat.simplex.app.views.helpers.ModalManager
import chat.simplex.app.views.helpers.withApi
import kotlinx.datetime.Clock
import kotlin.time.Duration.Companion.minutes
class CallManager(val chatModel: ChatModel) {
fun reportNewIncomingCall(invitation: RcvCallInvitation) {
Log.d(TAG, "CallManager.reportNewIncomingCall")
with (chatModel) {
callInvitations[invitation.contact.id] = invitation
if (invitation.user.showNotifications) {
if (Clock.System.now() - invitation.callTs <= 3.minutes) {
activeCallInvitation.value = invitation
controller.ntfManager.notifyCallInvitation(invitation)
} else {
val contact = invitation.contact
controller.ntfManager.displayNotification(user = invitation.user, chatId = contact.id, displayName = contact.displayName, msgText = invitation.callTypeText)
}
}
}
}
fun acceptIncomingCall(invitation: RcvCallInvitation) {
ModalManager.shared.closeModals()
val call = chatModel.activeCall.value
if (call == null) {
justAcceptIncomingCall(invitation = invitation)
} else {
withApi {
chatModel.switchingCall.value = true
try {
endCall(call = call)
justAcceptIncomingCall(invitation = invitation)
} finally {
withApi { chatModel.switchingCall.value = false }
}
}
}
}
private fun justAcceptIncomingCall(invitation: RcvCallInvitation) {
with (chatModel) {
activeCall.value = Call(
contact = invitation.contact,
callState = CallState.InvitationAccepted,
localMedia = invitation.callType.media,
sharedKey = invitation.sharedKey
)
showCallView.value = true
val useRelay = controller.appPrefs.webrtcPolicyRelay.get()
val iceServers = getIceServers()
Log.d(TAG, "answerIncomingCall iceServers: $iceServers")
callCommand.value = WCallCommand.Start(
media = invitation.callType.media,
aesKey = invitation.sharedKey,
iceServers = iceServers,
relay = useRelay
)
callInvitations.remove(invitation.contact.id)
if (invitation.contact.id == activeCallInvitation.value?.contact?.id) {
activeCallInvitation.value = null
controller.ntfManager.cancelCallNotification()
}
}
}
suspend fun endCall(call: Call) {
with (chatModel) {
if (call.callState == CallState.Ended) {
Log.d(TAG, "CallManager.endCall: call ended")
activeCall.value = null
showCallView.value = false
} else {
Log.d(TAG, "CallManager.endCall: ending call...")
callCommand.value = WCallCommand.End
showCallView.value = false
controller.apiEndCall(call.contact)
activeCall.value = null
}
}
}
fun endCall(invitation: RcvCallInvitation) {
with (chatModel) {
callInvitations.remove(invitation.contact.id)
if (invitation.contact.id == activeCallInvitation.value?.contact?.id) {
activeCallInvitation.value = null
controller.ntfManager.cancelCallNotification()
}
withApi {
if (!controller.apiRejectCall(invitation.contact)) {
Log.e(TAG, "apiRejectCall error")
}
}
}
}
fun reportCallRemoteEnded(invitation: RcvCallInvitation) {
if (chatModel.activeCallInvitation.value?.contact?.id == invitation.contact.id) {
chatModel.activeCallInvitation.value = null
chatModel.controller.ntfManager.cancelCallNotification()
}
}
}

View File

@@ -0,0 +1,575 @@
package chat.simplex.app.views.call
import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.content.*
import android.content.pm.ActivityInfo
import android.media.*
import android.os.Build
import android.os.PowerManager
import android.os.PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK
import android.util.Log
import android.view.ViewGroup
import android.webkit.*
import androidx.activity.compose.BackHandler
import androidx.annotation.StringRes
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.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.webkit.WebViewAssetLoader
import androidx.webkit.WebViewClientCompat
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.ProfileImage
import chat.simplex.app.views.helpers.withApi
import chat.simplex.app.views.usersettings.NotificationsMode
import com.google.accompanist.permissions.rememberMultiplePermissionsState
import kotlinx.coroutines.*
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
@SuppressLint("SourceLockedOrientationActivity")
@Composable
fun ActiveCallView(chatModel: ChatModel) {
BackHandler(onBack = {
val call = chatModel.activeCall.value
if (call != null) withApi { chatModel.callManager.endCall(call) }
})
val audioViaBluetooth = rememberSaveable { mutableStateOf(false) }
val ntfModeService = remember { chatModel.controller.appPrefs.notificationsMode.get() == NotificationsMode.SERVICE.name }
LaunchedEffect(Unit) {
// Start service when call happening since it's not already started.
// It's needed to prevent Android from shutting down a microphone after a minute or so when screen is off
if (!ntfModeService) SimplexService.start(SimplexApp.context)
}
DisposableEffect(Unit) {
val am = SimplexApp.context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
var btDeviceCount = 0
val audioCallback = object: AudioDeviceCallback() {
override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>) {
Log.d(TAG, "Added audio devices: ${addedDevices.map { it.type }}")
super.onAudioDevicesAdded(addedDevices)
val addedCount = addedDevices.count { it.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO }
btDeviceCount += addedCount
audioViaBluetooth.value = btDeviceCount > 0
if (addedCount > 0 && chatModel.activeCall.value?.callState == CallState.Connected) {
// Setting params in Connected state makes sure that Bluetooth will NOT be broken on Android < 12
setCallSound(chatModel.activeCall.value?.soundSpeaker ?: return, audioViaBluetooth)
}
}
override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>) {
Log.d(TAG, "Removed audio devices: ${removedDevices.map { it.type }}")
super.onAudioDevicesRemoved(removedDevices)
val removedCount = removedDevices.count { it.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO }
btDeviceCount -= removedCount
audioViaBluetooth.value = btDeviceCount > 0
if (btDeviceCount == 0 && chatModel.activeCall.value?.callState == CallState.Connected) {
// Setting params in Connected state makes sure that Bluetooth will NOT be broken on Android < 12
setCallSound(chatModel.activeCall.value?.soundSpeaker ?: return, audioViaBluetooth)
}
}
}
am.registerAudioDeviceCallback(audioCallback, null)
val pm = (SimplexApp.context.getSystemService(Context.POWER_SERVICE) as PowerManager)
val proximityLock = if (pm.isWakeLockLevelSupported(PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
pm.newWakeLock(PROXIMITY_SCREEN_OFF_WAKE_LOCK, SimplexApp.context.packageName + ":proximityLock")
} else {
null
}
proximityLock?.acquire()
onDispose {
// Stop it when call ended
if (!ntfModeService) SimplexService.safeStopService(SimplexApp.context)
dropAudioManagerOverrides()
am.unregisterAudioDeviceCallback(audioCallback)
proximityLock?.release()
}
}
val scope = rememberCoroutineScope()
Box(Modifier.fillMaxSize()) {
WebRTCView(chatModel.callCommand) { apiMsg ->
Log.d(TAG, "received from WebRTCView: $apiMsg")
val call = chatModel.activeCall.value
if (call != null) {
Log.d(TAG, "has active call $call")
when (val r = apiMsg.resp) {
is WCallResponse.Capabilities -> withApi {
val callType = CallType(call.localMedia, r.capabilities)
chatModel.controller.apiSendCallInvitation(call.contact, callType)
chatModel.activeCall.value = call.copy(callState = CallState.InvitationSent, localCapabilities = r.capabilities)
}
is WCallResponse.Offer -> withApi {
chatModel.controller.apiSendCallOffer(call.contact, r.offer, r.iceCandidates, call.localMedia, r.capabilities)
chatModel.activeCall.value = call.copy(callState = CallState.OfferSent, localCapabilities = r.capabilities)
}
is WCallResponse.Answer -> withApi {
chatModel.controller.apiSendCallAnswer(call.contact, r.answer, r.iceCandidates)
chatModel.activeCall.value = call.copy(callState = CallState.Negotiated)
}
is WCallResponse.Ice -> withApi {
chatModel.controller.apiSendCallExtraInfo(call.contact, r.iceCandidates)
}
is WCallResponse.Connection ->
try {
val callStatus = json.decodeFromString<WebRTCCallStatus>("\"${r.state.connectionState}\"")
if (callStatus == WebRTCCallStatus.Connected) {
chatModel.activeCall.value = call.copy(callState = CallState.Connected)
setCallSound(call.soundSpeaker, audioViaBluetooth)
}
withApi { chatModel.controller.apiCallStatus(call.contact, callStatus) }
} catch (e: Error) {
Log.d(TAG,"call status ${r.state.connectionState} not used")
}
is WCallResponse.Connected -> {
chatModel.activeCall.value = call.copy(callState = CallState.Connected, connectionInfo = r.connectionInfo)
scope.launch {
setCallSound(call.soundSpeaker, audioViaBluetooth)
}
}
is WCallResponse.Ended -> {
chatModel.activeCall.value = call.copy(callState = CallState.Ended)
withApi { chatModel.callManager.endCall(call) }
chatModel.showCallView.value = false
}
is WCallResponse.Ok -> when (val cmd = apiMsg.command) {
is WCallCommand.Answer ->
chatModel.activeCall.value = call.copy(callState = CallState.Negotiated)
is WCallCommand.Media -> {
when (cmd.media) {
CallMediaType.Video -> chatModel.activeCall.value = call.copy(videoEnabled = cmd.enable)
CallMediaType.Audio -> chatModel.activeCall.value = call.copy(audioEnabled = cmd.enable)
}
}
is WCallCommand.Camera -> {
chatModel.activeCall.value = call.copy(localCamera = cmd.camera)
if (!call.audioEnabled) {
chatModel.callCommand.value = WCallCommand.Media(CallMediaType.Audio, enable = false)
}
}
is WCallCommand.End ->
chatModel.showCallView.value = false
else -> {}
}
is WCallResponse.Error -> {
Log.e(TAG, "ActiveCallView: command error ${r.message}")
}
}
}
}
val call = chatModel.activeCall.value
if (call != null) ActiveCallOverlay(call, chatModel, audioViaBluetooth)
}
val context = LocalContext.current
DisposableEffect(Unit) {
val activity = context as? Activity ?: return@DisposableEffect onDispose {}
val prevVolumeControlStream = activity.volumeControlStream
activity.volumeControlStream = AudioManager.STREAM_VOICE_CALL
// Lock orientation to portrait in order to have good experience with calls
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
onDispose {
activity.volumeControlStream = prevVolumeControlStream
// Unlock orientation
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
}
}
}
@Composable
private fun ActiveCallOverlay(call: Call, chatModel: ChatModel, audioViaBluetooth: MutableState<Boolean>) {
ActiveCallOverlayLayout(
call = call,
speakerCanBeEnabled = !audioViaBluetooth.value,
dismiss = { withApi { chatModel.callManager.endCall(call) } },
toggleAudio = { chatModel.callCommand.value = WCallCommand.Media(CallMediaType.Audio, enable = !call.audioEnabled) },
toggleVideo = { chatModel.callCommand.value = WCallCommand.Media(CallMediaType.Video, enable = !call.videoEnabled) },
toggleSound = {
var call = chatModel.activeCall.value
if (call != null) {
call = call.copy(soundSpeaker = !call.soundSpeaker)
chatModel.activeCall.value = call
setCallSound(call.soundSpeaker, audioViaBluetooth)
}
},
flipCamera = { chatModel.callCommand.value = WCallCommand.Camera(call.localCamera.flipped) }
)
}
private fun setCallSound(speaker: Boolean, audioViaBluetooth: MutableState<Boolean>) {
val am = SimplexApp.context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
Log.d(TAG, "setCallSound: set audio mode, speaker enabled: $speaker")
am.mode = AudioManager.MODE_IN_COMMUNICATION
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val btDevice = am.availableCommunicationDevices.lastOrNull { it.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO }
val preferredSecondaryDevice = if (speaker) AudioDeviceInfo.TYPE_BUILTIN_SPEAKER else AudioDeviceInfo.TYPE_BUILTIN_EARPIECE
if (btDevice != null) {
am.setCommunicationDevice(btDevice)
} else if (am.communicationDevice?.type != preferredSecondaryDevice) {
am.availableCommunicationDevices.firstOrNull { it.type == preferredSecondaryDevice }?.let {
am.setCommunicationDevice(it)
}
}
} else {
if (audioViaBluetooth.value) {
am.isSpeakerphoneOn = false
am.startBluetoothSco()
} else {
am.stopBluetoothSco()
am.isSpeakerphoneOn = speaker
}
am.isBluetoothScoOn = am.isBluetoothScoAvailableOffCall && audioViaBluetooth.value
}
}
private fun dropAudioManagerOverrides() {
val am = SimplexApp.context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
am.mode = AudioManager.MODE_NORMAL
// Clear selected communication device to default value after we changed it in call
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
am.clearCommunicationDevice()
} else {
am.isSpeakerphoneOn = false
am.stopBluetoothSco()
}
}
@Composable
private fun ActiveCallOverlayLayout(
call: Call,
speakerCanBeEnabled: Boolean,
dismiss: () -> Unit,
toggleAudio: () -> Unit,
toggleVideo: () -> Unit,
toggleSound: () -> Unit,
flipCamera: () -> Unit
) {
Column(Modifier.padding(16.dp)) {
when (call.peerMedia ?: call.localMedia) {
CallMediaType.Video -> {
CallInfoView(call, alignment = Alignment.Start)
Spacer(Modifier.fillMaxHeight().weight(1f))
Row(Modifier.fillMaxWidth().padding(horizontal = 6.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) {
ToggleAudioButton(call, toggleAudio)
Spacer(Modifier.size(40.dp))
IconButton(onClick = dismiss) {
Icon(Icons.Filled.CallEnd, stringResource(R.string.icon_descr_hang_up), tint = Color.Red, modifier = Modifier.size(64.dp))
}
if (call.videoEnabled) {
ControlButton(call, Icons.Filled.FlipCameraAndroid, R.string.icon_descr_flip_camera, flipCamera)
ControlButton(call, Icons.Filled.Videocam, R.string.icon_descr_video_off, toggleVideo)
} else {
Spacer(Modifier.size(48.dp))
ControlButton(call, Icons.Outlined.VideocamOff, R.string.icon_descr_video_on, toggleVideo)
}
}
}
CallMediaType.Audio -> {
Spacer(Modifier.fillMaxHeight().weight(1f))
Column(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
ProfileImage(size = 192.dp, image = call.contact.profile.image)
CallInfoView(call, alignment = Alignment.CenterHorizontally)
}
Spacer(Modifier.fillMaxHeight().weight(1f))
Box(Modifier.fillMaxWidth().padding(bottom = DEFAULT_BOTTOM_PADDING), contentAlignment = Alignment.CenterStart) {
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
IconButton(onClick = dismiss) {
Icon(Icons.Filled.CallEnd, stringResource(R.string.icon_descr_hang_up), tint = Color.Red, modifier = Modifier.size(64.dp))
}
}
Box(Modifier.padding(start = 32.dp)) {
ToggleAudioButton(call, toggleAudio)
}
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.CenterEnd) {
Box(Modifier.padding(end = 32.dp)) {
ToggleSoundButton(call, speakerCanBeEnabled, toggleSound)
}
}
}
}
}
}
}
@Composable
private fun ControlButton(call: Call, icon: ImageVector, @StringRes iconText: Int, action: () -> Unit, enabled: Boolean = true) {
if (call.hasMedia) {
IconButton(onClick = action, enabled = enabled) {
Icon(icon, stringResource(iconText), tint = if (enabled) Color(0xFFFFFFD8) else HighOrLowlight, modifier = Modifier.size(40.dp))
}
} else {
Spacer(Modifier.size(40.dp))
}
}
@Composable
private fun ToggleAudioButton(call: Call, toggleAudio: () -> Unit) {
if (call.audioEnabled) {
ControlButton(call, Icons.Outlined.Mic, R.string.icon_descr_audio_off, toggleAudio)
} else {
ControlButton(call, Icons.Outlined.MicOff, R.string.icon_descr_audio_on, toggleAudio)
}
}
@Composable
private fun ToggleSoundButton(call: Call, enabled: Boolean, toggleSound: () -> Unit) {
if (call.soundSpeaker) {
ControlButton(call, Icons.Outlined.VolumeUp, R.string.icon_descr_speaker_off, toggleSound, enabled)
} else {
ControlButton(call, Icons.Outlined.VolumeDown, R.string.icon_descr_speaker_on, toggleSound, enabled)
}
}
@Composable
fun CallInfoView(call: Call, alignment: Alignment.Horizontal) {
@Composable fun InfoText(text: String, style: TextStyle = MaterialTheme.typography.body2) =
Text(text, color = Color(0xFFFFFFD8), style = style)
Column(horizontalAlignment = alignment) {
InfoText(call.contact.chatViewName, style = MaterialTheme.typography.h2)
InfoText(call.callState.text)
val connInfo = call.connectionInfo
// val connInfoText = if (connInfo == null) "" else " (${connInfo.text}, ${connInfo.protocolText})"
val connInfoText = if (connInfo == null) "" else " (${connInfo.text})"
InfoText(call.encryptionStatus + connInfoText)
}
}
//@Composable
//fun CallViewDebug(close: () -> Unit) {
// val callCommand = remember { mutableStateOf<WCallCommand?>(null)}
// val commandText = remember { mutableStateOf("{\"command\": {\"type\": \"start\", \"media\": \"video\", \"aesKey\": \"FwW+t6UbnwHoapYOfN4mUBUuqR7UtvYWxW16iBqM29U=\"}}") }
// val clipboard = ContextCompat.getSystemService(LocalContext.current, ClipboardManager::class.java)
//
// BackHandler(onBack = close)
// Column(
// horizontalAlignment = Alignment.CenterHorizontally,
// verticalArrangement = Arrangement.spacedBy(12.dp),
// modifier = Modifier
// .background(MaterialTheme.colors.background)
// .fillMaxSize()
// ) {
// WebRTCView(callCommand) { apiMsg ->
// // for debugging
// // commandText.value = apiMsg
// commandText.value = json.encodeToString(apiMsg)
// }
//
// TextEditor(Modifier.height(180.dp), text = commandText)
//
// Row(
// Modifier
// .fillMaxWidth()
// .padding(bottom = 6.dp),
// horizontalArrangement = Arrangement.SpaceBetween
// ) {
// Button(onClick = {
// val clip: ClipData = ClipData.newPlainText("js command", commandText.value)
// clipboard?.setPrimaryClip(clip)
// }) { Text("Copy") }
// Button(onClick = {
// try {
// val apiCall: WVAPICall = json.decodeFromString(commandText.value)
// commandText.value = ""
// println("sending: ${commandText.value}")
// callCommand.value = apiCall.command
// } catch(e: Error) {
// println("error parsing command: ${commandText.value}")
// println(e)
// }
// }) { Text("Send") }
// Button(onClick = {
// commandText.value = ""
// }) { Text("Clear") }
// }
// }
//}
@Composable
fun WebRTCView(callCommand: MutableState<WCallCommand?>, onResponse: (WVAPIMessage) -> Unit) {
val scope = rememberCoroutineScope()
val webView = remember { mutableStateOf<WebView?>(null) }
val permissionsState = rememberMultiplePermissionsState(
permissions = listOf(
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO,
Manifest.permission.MODIFY_AUDIO_SETTINGS,
Manifest.permission.INTERNET
)
)
fun processCommand(wv: WebView, cmd: WCallCommand) {
val apiCall = WVAPICall(command = cmd)
wv.evaluateJavascript("processCommand(${json.encodeToString(apiCall)})", null)
}
val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME || event == Lifecycle.Event.ON_START) {
permissionsState.launchMultiplePermissionRequest()
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
val wv = webView.value
if (wv != null) processCommand(wv, WCallCommand.End)
lifecycleOwner.lifecycle.removeObserver(observer)
webView.value?.destroy()
webView.value = null
}
}
LaunchedEffect(callCommand.value, webView.value) {
val cmd = callCommand.value
val wv = webView.value
if (cmd != null && wv != null) {
Log.d(TAG, "WebRTCView LaunchedEffect executing $cmd")
processCommand(wv, cmd)
callCommand.value = null
}
}
val assetLoader = WebViewAssetLoader.Builder()
.addPathHandler("/assets/www/", WebViewAssetLoader.AssetsPathHandler(LocalContext.current))
.build()
if (permissionsState.allPermissionsGranted) {
Box(Modifier.fillMaxSize()) {
AndroidView(
factory = { AndroidViewContext ->
WebView(AndroidViewContext).apply {
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
)
this.webChromeClient = object: WebChromeClient() {
override fun onPermissionRequest(request: PermissionRequest) {
if (request.origin.toString().startsWith("file:/")) {
request.grant(request.resources)
} else {
Log.d(TAG, "Permission request from webview denied.")
request.deny()
}
}
}
this.webViewClient = LocalContentWebViewClient(assetLoader)
this.clearHistory()
this.clearCache(true)
this.addJavascriptInterface(WebRTCInterface(onResponse), "WebRTCInterface")
val webViewSettings = this.settings
webViewSettings.allowFileAccess = true
webViewSettings.allowContentAccess = true
webViewSettings.javaScriptEnabled = true
webViewSettings.mediaPlaybackRequiresUserGesture = false
webViewSettings.cacheMode = WebSettings.LOAD_NO_CACHE
this.loadUrl("file:android_asset/www/call.html")
}
}
) { wv ->
Log.d(TAG, "WebRTCView: webview ready")
// for debugging
// wv.evaluateJavascript("sendMessageToNative = ({resp}) => WebRTCInterface.postMessage(JSON.stringify({command: resp}))", null)
scope.launch {
delay(2000L)
wv.evaluateJavascript("sendMessageToNative = (msg) => WebRTCInterface.postMessage(JSON.stringify(msg))", null)
webView.value = wv
}
}
}
}
}
// for debugging
// class WebRTCInterface(private val onResponse: (String) -> Unit) {
class WebRTCInterface(private val onResponse: (WVAPIMessage) -> Unit) {
@JavascriptInterface
fun postMessage(message: String) {
Log.d(TAG, "WebRTCInterface.postMessage")
try {
// for debugging
// onResponse(message)
onResponse(json.decodeFromString(message))
} catch (e: Error) {
Log.e(TAG, "failed parsing WebView message: $message")
}
}
}
private class LocalContentWebViewClient(private val assetLoader: WebViewAssetLoader) : WebViewClientCompat() {
override fun shouldInterceptRequest(
view: WebView,
request: WebResourceRequest
): WebResourceResponse? {
return assetLoader.shouldInterceptRequest(request.url)
}
}
@Preview
@Composable
fun PreviewActiveCallOverlayVideo() {
SimpleXTheme {
ActiveCallOverlayLayout(
call = Call(
contact = Contact.sampleData,
callState = CallState.Negotiated,
localMedia = CallMediaType.Video,
peerMedia = CallMediaType.Video,
connectionInfo = ConnectionInfo(
RTCIceCandidate(RTCIceCandidateType.Host, "tcp", null),
RTCIceCandidate(RTCIceCandidateType.Host, "tcp", null)
)
),
speakerCanBeEnabled = true,
dismiss = {},
toggleAudio = {},
toggleVideo = {},
toggleSound = {},
flipCamera = {}
)
}
}
@Preview
@Composable
fun PreviewActiveCallOverlayAudio() {
SimpleXTheme {
ActiveCallOverlayLayout(
call = Call(
contact = Contact.sampleData,
callState = CallState.Negotiated,
localMedia = CallMediaType.Audio,
peerMedia = CallMediaType.Audio,
connectionInfo = ConnectionInfo(
RTCIceCandidate(RTCIceCandidateType.Host, "udp", null),
RTCIceCandidate(RTCIceCandidateType.Host, "udp", null)
)
),
speakerCanBeEnabled = true,
dismiss = {},
toggleAudio = {},
toggleVideo = {},
toggleSound = {},
flipCamera = {}
)
}
}

View File

@@ -0,0 +1,238 @@
package chat.simplex.app.views.call
import android.app.Activity
import android.app.KeyguardManager
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.WindowManager
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.background
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.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
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.*
import chat.simplex.app.R
import chat.simplex.app.model.*
import chat.simplex.app.model.NtfManager.Companion.OpenChatAction
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.ProfileImage
import chat.simplex.app.views.onboarding.SimpleXLogo
import kotlinx.datetime.Clock
class IncomingCallActivity: ComponentActivity() {
private val vm by viewModels<SimplexViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent { IncomingCallActivityView(vm.chatModel) }
unlockForIncomingCall()
}
override fun onDestroy() {
super.onDestroy()
lockAfterIncomingCall()
}
private fun unlockForIncomingCall() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setShowWhenLocked(true)
setTurnScreenOn(true)
} else {
window.addFlags(activityFlags)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
getKeyguardManager(this).requestDismissKeyguard(this, null)
}
}
private fun lockAfterIncomingCall() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setShowWhenLocked(false)
setTurnScreenOn(false)
} else {
window.clearFlags(activityFlags)
}
}
companion object {
const val activityFlags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
}
}
fun getKeyguardManager(context: Context): KeyguardManager =
context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
@Composable
fun IncomingCallActivityView(m: ChatModel) {
val switchingCall = m.switchingCall.value
val invitation = m.activeCallInvitation.value
val call = m.activeCall.value
val showCallView = m.showCallView.value
val activity = LocalContext.current as Activity
LaunchedEffect(invitation, call, switchingCall, showCallView) {
if (!switchingCall && invitation == null && (!showCallView || call == null)) {
Log.d(TAG, "IncomingCallActivityView: finishing activity")
activity.finish()
}
}
SimpleXTheme {
Surface(
Modifier
.background(MaterialTheme.colors.background)
.fillMaxSize()) {
if (showCallView) {
Box {
ActiveCallView(m)
if (invitation != null) IncomingCallAlertView(invitation, m)
}
} else if (invitation != null) {
IncomingCallLockScreenAlert(invitation, m)
}
}
}
}
@Composable
fun IncomingCallLockScreenAlert(invitation: RcvCallInvitation, chatModel: ChatModel) {
val cm = chatModel.callManager
val callOnLockScreen by remember { mutableStateOf(chatModel.controller.appPrefs.callOnLockScreen.get()) }
val context = LocalContext.current
DisposableEffect(Unit) {
onDispose {
// Cancel notification whatever happens next since otherwise sound from notification and from inside the app can co-exist
chatModel.controller.ntfManager.cancelCallNotification()
}
}
IncomingCallLockScreenAlertLayout(
invitation,
callOnLockScreen,
chatModel,
rejectCall = { cm.endCall(invitation = invitation) },
ignoreCall = {
chatModel.activeCallInvitation.value = null
chatModel.controller.ntfManager.cancelCallNotification()
},
acceptCall = { cm.acceptIncomingCall(invitation = invitation) },
openApp = {
val intent = Intent(context, MainActivity::class.java)
.setAction(OpenChatAction)
.putExtra("userId", invitation.user.userId)
.putExtra("chatId", invitation.contact.id)
context.startActivity(intent)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
getKeyguardManager(context).requestDismissKeyguard((context as Activity), null)
}
(context as Activity).finish()
}
)
}
@Composable
fun IncomingCallLockScreenAlertLayout(
invitation: RcvCallInvitation,
callOnLockScreen: CallOnLockScreen?,
chatModel: ChatModel,
rejectCall: () -> Unit,
ignoreCall: () -> Unit,
acceptCall: () -> Unit,
openApp: () -> Unit
) {
Column(
Modifier
.padding(30.dp)
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
IncomingCallInfo(invitation, chatModel)
Spacer(Modifier.fillMaxHeight().weight(1f))
if (callOnLockScreen == CallOnLockScreen.ACCEPT) {
ProfileImage(size = 192.dp, image = invitation.contact.profile.image)
Text(invitation.contact.chatViewName, style = MaterialTheme.typography.h2)
Spacer(Modifier.fillMaxHeight().weight(1f))
Row {
LockScreenCallButton(stringResource(R.string.reject), Icons.Filled.CallEnd, Color.Red, rejectCall)
Spacer(Modifier.size(48.dp))
LockScreenCallButton(stringResource(R.string.ignore), Icons.Filled.Close, MaterialTheme.colors.primary, ignoreCall)
Spacer(Modifier.size(48.dp))
LockScreenCallButton(stringResource(R.string.accept), Icons.Filled.Check, SimplexGreen, acceptCall)
}
} else if (callOnLockScreen == CallOnLockScreen.SHOW) {
SimpleXLogo()
Text(stringResource(R.string.open_simplex_chat_to_accept_call), textAlign = TextAlign.Center, lineHeight = 22.sp)
Text(stringResource(R.string.allow_accepting_calls_from_lock_screen), textAlign = TextAlign.Center, style = MaterialTheme.typography.body2, lineHeight = 22.sp)
Spacer(Modifier.fillMaxHeight().weight(1f))
SimpleButton(text = stringResource(R.string.open_verb), icon = Icons.Filled.Check, click = openApp)
}
}
}
@Composable
private fun LockScreenCallButton(text: String, icon: ImageVector, color: Color, action: () -> Unit) {
Surface(
shape = RoundedCornerShape(10.dp),
color = Color.Transparent
) {
Column(
Modifier
.defaultMinSize(minWidth = 50.dp)
.padding(4.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
IconButton(action) {
Icon(icon, text, tint = color, modifier = Modifier.scale(1.75f))
}
Spacer(Modifier.height(16.dp))
Text(text, style = MaterialTheme.typography.body2, color = HighOrLowlight)
}
}
}
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true
)
@Composable
fun PreviewIncomingCallLockScreenAlert() {
SimpleXTheme(true) {
Surface(
Modifier
.background(MaterialTheme.colors.background)
.fillMaxSize()) {
IncomingCallLockScreenAlertLayout(
invitation = RcvCallInvitation(
user = User.sampleData,
contact = Contact.sampleData,
callType = CallType(media = CallMediaType.Audio, capabilities = CallCapabilities(encryption = false)),
sharedKey = null,
callTs = Clock.System.now()
),
callOnLockScreen = null,
chatModel = SimplexApp.context.chatModel,
rejectCall = {},
ignoreCall = {},
acceptCall = {},
openApp = {},
)
}
}
}

View File

@@ -0,0 +1,123 @@
package chat.simplex.app.views.call
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.filled.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
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.SimplexApp
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.ProfileImage
import chat.simplex.app.views.usersettings.ProfilePreview
import kotlinx.datetime.Clock
@Composable
fun IncomingCallAlertView(invitation: RcvCallInvitation, chatModel: ChatModel) {
val cm = chatModel.callManager
val cxt = LocalContext.current
val scope = rememberCoroutineScope()
LaunchedEffect(true) { SoundPlayer.shared.start(cxt, scope, sound = !chatModel.showCallView.value) }
DisposableEffect(true) { onDispose { SoundPlayer.shared.stop() } }
IncomingCallAlertLayout(
invitation,
chatModel,
rejectCall = { cm.endCall(invitation = invitation) },
ignoreCall = {
chatModel.activeCallInvitation.value = null
chatModel.controller.ntfManager.cancelCallNotification()
},
acceptCall = { cm.acceptIncomingCall(invitation = invitation) }
)
}
@Composable
fun IncomingCallAlertLayout(
invitation: RcvCallInvitation,
chatModel: ChatModel,
rejectCall: () -> Unit,
ignoreCall: () -> Unit,
acceptCall: () -> Unit
) {
val color = if (isInDarkTheme()) IncomingCallDark else IncomingCallLight
Column(Modifier.fillMaxWidth().background(color).padding(top = 16.dp, bottom = 16.dp, start = 16.dp, end = 8.dp)) {
IncomingCallInfo(invitation, chatModel)
Spacer(Modifier.height(8.dp))
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween) {
Row(Modifier.fillMaxWidth().weight(1f), verticalAlignment = Alignment.CenterVertically) {
ProfilePreview(profileOf = invitation.contact, size = 64.dp, color = Color.White)
}
Row(verticalAlignment = Alignment.CenterVertically) {
CallButton(stringResource(R.string.reject), Icons.Filled.CallEnd, Color.Red, rejectCall)
CallButton(stringResource(R.string.ignore), Icons.Filled.Close, MaterialTheme.colors.primary, ignoreCall)
CallButton(stringResource(R.string.accept), Icons.Filled.Check, SimplexGreen, acceptCall)
}
}
}
}
@Composable
fun IncomingCallInfo(invitation: RcvCallInvitation, chatModel: ChatModel) {
@Composable fun CallIcon(icon: ImageVector, descr: String) = Icon(icon, descr, tint = SimplexGreen)
Row(verticalAlignment = Alignment.CenterVertically) {
if (chatModel.users.size > 1) {
ProfileImage(size = 32.dp, image = invitation.user.profile.image, color = MaterialTheme.colors.secondary)
Spacer(Modifier.width(4.dp))
}
if (invitation.callType.media == CallMediaType.Video) CallIcon(Icons.Filled.Videocam, stringResource(R.string.icon_descr_video_call))
else CallIcon(Icons.Filled.Phone, stringResource(R.string.icon_descr_audio_call))
Spacer(Modifier.width(4.dp))
Text(invitation.callTypeText)
}
}
@Composable
private fun CallButton(text: String, icon: ImageVector, color: Color, action: () -> Unit) {
Surface(
shape = RoundedCornerShape(10.dp),
color = Color.Transparent
) {
Column(
Modifier
.clickable(onClick = action)
.defaultMinSize(minWidth = 50.dp)
.padding(4.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(icon, text, tint = color, modifier = Modifier.scale(1.2f))
Text(text, style = MaterialTheme.typography.body2, color = HighOrLowlight)
}
}
}
@Preview
@Composable
fun PreviewIncomingCallAlertLayout() {
SimpleXTheme {
IncomingCallAlertLayout(
invitation = RcvCallInvitation(
user = User.sampleData,
contact = Contact.sampleData,
callType = CallType(media = CallMediaType.Audio, capabilities = CallCapabilities(encryption = false)),
sharedKey = null,
callTs = Clock.System.now()
),
chatModel = SimplexApp.context.chatModel,
rejectCall = {},
ignoreCall = {},
acceptCall = {}
)
}
}

View File

@@ -0,0 +1,51 @@
package chat.simplex.app.views.call
import android.content.Context
import android.media.*
import android.net.Uri
import android.os.VibrationEffect
import android.os.Vibrator
import androidx.core.content.ContextCompat
import chat.simplex.app.R
import chat.simplex.app.SimplexApp
import chat.simplex.app.views.helpers.withScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
class SoundPlayer {
private var player: MediaPlayer? = null
var playing = false
fun start(cxt: Context, scope: CoroutineScope, sound: Boolean) {
player?.reset()
player = MediaPlayer().apply {
setAudioAttributes(
AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
.build()
)
setDataSource(SimplexApp.context, Uri.parse("android.resource://" + SimplexApp.context.packageName + "/" + R.raw.ring_once))
prepare()
}
val vibrator = ContextCompat.getSystemService(cxt, Vibrator::class.java)
val effect = VibrationEffect.createOneShot(250, VibrationEffect.DEFAULT_AMPLITUDE)
playing = true
withScope(scope) {
while (playing) {
if (sound) player?.start()
vibrator?.vibrate(effect)
delay(3500)
}
}
}
fun stop() {
playing = false
player?.stop()
}
companion object {
val shared = SoundPlayer()
}
}

View File

@@ -0,0 +1,221 @@
package chat.simplex.app.views.call
import android.util.Log
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.toUpperCase
import chat.simplex.app.*
import chat.simplex.app.model.Contact
import chat.simplex.app.model.User
import chat.simplex.app.views.helpers.generalGetString
import kotlinx.datetime.Instant
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import java.net.URI
import java.util.*
import kotlin.collections.ArrayList
data class Call(
val contact: Contact,
val callState: CallState,
val localMedia: CallMediaType,
val localCapabilities: CallCapabilities? = null,
val peerMedia: CallMediaType? = null,
val sharedKey: String? = null,
val audioEnabled: Boolean = true,
val videoEnabled: Boolean = localMedia == CallMediaType.Video,
val soundSpeaker: Boolean = localMedia == CallMediaType.Video,
var localCamera: VideoCamera = VideoCamera.User,
val connectionInfo: ConnectionInfo? = null
) {
val encrypted: Boolean get() = localEncrypted && sharedKey != null
val localEncrypted: Boolean get() = localCapabilities?.encryption ?: false
val encryptionStatus: String @Composable get() = when(callState) {
CallState.WaitCapabilities -> ""
CallState.InvitationSent -> stringResource(if (localEncrypted) R.string.status_e2e_encrypted else R.string.status_no_e2e_encryption)
CallState.InvitationAccepted -> stringResource(if (sharedKey == null) R.string.status_contact_has_no_e2e_encryption else R.string.status_contact_has_e2e_encryption)
else -> stringResource(if (!localEncrypted) R.string.status_no_e2e_encryption else if (sharedKey == null) R.string.status_contact_has_no_e2e_encryption else R.string.status_e2e_encrypted)
}
val hasMedia: Boolean get() = callState == CallState.OfferSent || callState == CallState.Negotiated || callState == CallState.Connected
}
enum class CallState {
WaitCapabilities,
InvitationSent,
InvitationAccepted,
OfferSent,
OfferReceived,
AnswerReceived,
Negotiated,
Connected,
Ended;
val text: String @Composable get() = when(this) {
WaitCapabilities -> stringResource(R.string.callstate_starting)
InvitationSent -> stringResource(R.string.callstate_waiting_for_answer)
InvitationAccepted -> stringResource(R.string.callstate_starting)
OfferSent -> stringResource(R.string.callstate_waiting_for_confirmation)
OfferReceived -> stringResource(R.string.callstate_received_answer)
AnswerReceived -> stringResource(R.string.callstate_received_confirmation)
Negotiated -> stringResource(R.string.callstate_connecting)
Connected -> stringResource(R.string.callstate_connected)
Ended -> stringResource(R.string.callstate_ended)
}
}
@Serializable data class WVAPICall(val corrId: Int? = null, val command: WCallCommand)
@Serializable data class WVAPIMessage(val corrId: Int? = null, val resp: WCallResponse, val command: WCallCommand? = null)
@Serializable
sealed class WCallCommand {
@Serializable @SerialName("capabilities") object Capabilities: WCallCommand()
@Serializable @SerialName("start") data class Start(val media: CallMediaType, val aesKey: String? = null, val iceServers: List<RTCIceServer>? = null, val relay: Boolean? = null): WCallCommand()
@Serializable @SerialName("offer") data class Offer(val offer: String, val iceCandidates: String, val media: CallMediaType, val aesKey: String? = null, val iceServers: List<RTCIceServer>? = null, val relay: Boolean? = null): WCallCommand()
@Serializable @SerialName("answer") data class Answer (val answer: String, val iceCandidates: String): WCallCommand()
@Serializable @SerialName("ice") data class Ice(val iceCandidates: String): WCallCommand()
@Serializable @SerialName("media") data class Media(val media: CallMediaType, val enable: Boolean): WCallCommand()
@Serializable @SerialName("camera") data class Camera(val camera: VideoCamera): WCallCommand()
@Serializable @SerialName("end") object End: WCallCommand()
}
@Serializable
sealed class WCallResponse {
@Serializable @SerialName("capabilities") data class Capabilities(val capabilities: CallCapabilities): WCallResponse()
@Serializable @SerialName("offer") data class Offer(val offer: String, val iceCandidates: String, val capabilities: CallCapabilities): WCallResponse()
@Serializable @SerialName("answer") data class Answer(val answer: String, val iceCandidates: String): WCallResponse()
@Serializable @SerialName("ice") data class Ice(val iceCandidates: String): WCallResponse()
@Serializable @SerialName("connection") data class Connection(val state: ConnectionState): WCallResponse()
@Serializable @SerialName("connected") data class Connected(val connectionInfo: ConnectionInfo): WCallResponse()
@Serializable @SerialName("ended") object Ended: WCallResponse()
@Serializable @SerialName("ok") object Ok: WCallResponse()
@Serializable @SerialName("error") data class Error(val message: String): WCallResponse()
}
@Serializable data class WebRTCCallOffer(val callType: CallType, val rtcSession: WebRTCSession)
@Serializable data class WebRTCSession(val rtcSession: String, val rtcIceCandidates: String)
@Serializable data class WebRTCExtraInfo(val rtcIceCandidates: String)
@Serializable data class CallType(val media: CallMediaType, val capabilities: CallCapabilities)
@Serializable data class RcvCallInvitation(val user: User, val contact: Contact, val callType: CallType, val sharedKey: String? = null, val callTs: Instant) {
val callTypeText: String get() = generalGetString(when(callType.media) {
CallMediaType.Video -> if (sharedKey == null) R.string.video_call_no_encryption else R.string.encrypted_video_call
CallMediaType.Audio -> if (sharedKey == null) R.string.audio_call_no_encryption else R.string.encrypted_audio_call
})
val callTitle: String get() = generalGetString(when(callType.media) {
CallMediaType.Video -> R.string.incoming_video_call
CallMediaType.Audio -> R.string.incoming_audio_call
})
}
@Serializable data class CallCapabilities(val encryption: Boolean)
@Serializable data class ConnectionInfo(private val localCandidate: RTCIceCandidate?, private val remoteCandidate: RTCIceCandidate?) {
val text: String @Composable get() {
val local = localCandidate?.candidateType
val remote = remoteCandidate?.candidateType
return when {
local == RTCIceCandidateType.Host && remote == RTCIceCandidateType.Host ->
stringResource(R.string.call_connection_peer_to_peer)
local == RTCIceCandidateType.Relay && remote == RTCIceCandidateType.Relay ->
stringResource(R.string.call_connection_via_relay)
else ->
"${local?.value ?: "unknown"} / ${remote?.value ?: "unknown"}"
}
}
val protocolText: String get() {
val local = localCandidate?.protocol?.uppercase(Locale.ROOT) ?: "unknown"
val localRelay = localCandidate?.relayProtocol?.uppercase(Locale.ROOT) ?: "unknown"
val remote = remoteCandidate?.protocol?.uppercase(Locale.ROOT) ?: "unknown"
val localText = if (localRelay == local || localCandidate?.relayProtocol == null) local else "$local ($localRelay)"
return if (local == remote) localText else "$localText / $remote"
}
}
// https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidate
@Serializable data class RTCIceCandidate(val candidateType: RTCIceCandidateType?, val protocol: String?, val relayProtocol: String?)
// https://developer.mozilla.org/en-US/docs/Web/API/RTCIceServer
@Serializable data class RTCIceServer(val urls: List<String>, val username: String? = null, val credential: String? = null)
// https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidate/type
@Serializable
enum class RTCIceCandidateType(val value: String) {
@SerialName("host") Host("host"),
@SerialName("srflx") ServerReflexive("srflx"),
@SerialName("prflx") PeerReflexive("prflx"),
@SerialName("relay") Relay("relay")
}
@Serializable
enum class WebRTCCallStatus(val value: String) {
@SerialName("connected") Connected("connected"),
@SerialName("connecting") Connecting("connecting"),
@SerialName("disconnected") Disconnected("disconnected"),
@SerialName("failed") Failed("failed")
}
@Serializable
enum class CallMediaType {
@SerialName("video") Video,
@SerialName("audio") Audio
}
@Serializable
enum class VideoCamera {
@SerialName("user") User,
@SerialName("environment") Environment;
val flipped: VideoCamera get() = if (this == User) Environment else User
}
@Serializable
data class ConnectionState(
val connectionState: String,
val iceConnectionState: String,
val iceGatheringState: String,
val signalingState: String
)
// the servers are expected in this format:
// stun:stun.simplex.im:443?transport=tcp
// turn:private:yleob6AVkiNI87hpR94Z@turn.simplex.im:443?transport=tcp
fun parseRTCIceServer(str: String): RTCIceServer? {
var s = replaceScheme(str, "stun:")
s = replaceScheme(s, "turn:")
s = replaceScheme(s, "turns:")
val u = runCatching { URI(s) }.getOrNull()
if (u != null) {
val scheme = u.scheme
val host = u.host
val port = u.port
if (u.path == "" && (scheme == "stun" || scheme == "turn" || scheme == "turns")) {
val userInfo = u.userInfo?.split(":")
val query = if (u.query == null || u.query == "") "" else "?${u.query}"
return RTCIceServer(
urls = listOf("$scheme:$host:$port$query"),
username = userInfo?.getOrNull(0),
credential = userInfo?.getOrNull(1)
)
}
}
return null
}
private fun replaceScheme(s: String, scheme: String): String = if (s.startsWith(scheme)) s.replace(scheme, "$scheme//") else s
fun parseRTCIceServers(servers: List<String>): List<RTCIceServer>? {
val iceServers: ArrayList<RTCIceServer> = ArrayList()
for (s in servers) {
val server = parseRTCIceServer(s)
if (server != null) {
iceServers.add(server)
} else {
return null
}
}
return if (iceServers.isEmpty()) null else iceServers
}
fun getIceServers(): List<RTCIceServer>? {
val value = SimplexApp.context.chatController.appPrefs.webrtcIceServers.get() ?: return null
val servers: List<String> = value.split("\n")
return parseRTCIceServers(servers)
}

View File

@@ -0,0 +1,471 @@
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 = {},
)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,60 @@
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.InsertDriveFile
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.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.chat.item.SentColorLight
@Composable
fun ComposeFileView(fileName: String, cancelFile: () -> Unit, cancelEnabled: Boolean) {
Row(
Modifier
.height(60.dp)
.fillMaxWidth()
.padding(top = 8.dp)
.background(SentColorLight),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
Icons.Filled.InsertDriveFile,
stringResource(R.string.icon_descr_file),
Modifier
.padding(start = 4.dp, end = 2.dp)
.size(36.dp),
tint = if (isInDarkTheme()) FileDark else FileLight
)
Text(fileName)
Spacer(Modifier.weight(1f))
if (cancelEnabled) {
IconButton(onClick = cancelFile, 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 PreviewComposeFileView() {
SimpleXTheme {
ComposeFileView(
"test.txt",
cancelFile = {},
cancelEnabled = true
)
}
}

View File

@@ -0,0 +1,53 @@
package chat.simplex.app.views.chat
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Close
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
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_HALF
import chat.simplex.app.views.chat.item.SentColorLight
import chat.simplex.app.views.helpers.base64ToBitmap
@Composable
fun ComposeImageView(images: List<String>, cancelImages: () -> Unit, cancelEnabled: Boolean) {
Row(
Modifier
.padding(top = 8.dp)
.background(SentColorLight),
verticalAlignment = Alignment.CenterVertically,
) {
LazyRow(
Modifier.weight(1f).padding(start = DEFAULT_PADDING_HALF, end = if (cancelEnabled) 0.dp else DEFAULT_PADDING_HALF),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(DEFAULT_PADDING_HALF),
) {
items(images.size) { index ->
val imageBitmap = base64ToBitmap(images[index]).asImageBitmap()
Image(
imageBitmap,
"preview image",
modifier = Modifier.widthIn(max = 80.dp).height(60.dp)
)
}
}
if (cancelEnabled) {
IconButton(onClick = cancelImages) {
Icon(
Icons.Outlined.Close,
contentDescription = stringResource(R.string.icon_descr_cancel_image_preview),
tint = MaterialTheme.colors.primary,
)
}
}
}
}

View File

@@ -0,0 +1,873 @@
@file:UseSerializers(UriSerializer::class)
package chat.simplex.app.views.chat
import ComposeFileView
import ComposeVoiceView
import android.Manifest
import android.app.Activity
import android.content.*
import android.content.pm.PackageManager
import android.graphics.*
import android.graphics.drawable.AnimatedImageDrawable
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContract
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AttachFile
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.outlined.Reply
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import chat.simplex.app.*
import chat.simplex.app.R
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.views.chat.item.*
import chat.simplex.app.views.helpers.*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.serialization.*
import java.io.File
import java.nio.file.Files
@Serializable
sealed class ComposePreview {
@Serializable object NoPreview: ComposePreview()
@Serializable class CLinkPreview(val linkPreview: LinkPreview?): ComposePreview()
@Serializable class ImagePreview(val images: List<String>, val content: List<UploadContent>): ComposePreview()
@Serializable class VideoPreview(val images: List<String>, val content: List<UploadContent>): ComposePreview()
@Serializable data class VoicePreview(val voice: String, val durationMs: Int, val finished: Boolean): ComposePreview()
@Serializable class FilePreview(val fileName: String, val uri: Uri): ComposePreview()
}
@Serializable
sealed class ComposeContextItem {
@Serializable object NoContextItem: ComposeContextItem()
@Serializable class QuotedItem(val chatItem: ChatItem): ComposeContextItem()
@Serializable class EditingItem(val chatItem: ChatItem): ComposeContextItem()
}
@Serializable
data class LiveMessage(
val chatItem: ChatItem,
val typedMsg: String,
val sentMsg: String,
val sent: Boolean
)
@Serializable
data class ComposeState(
val message: String = "",
val liveMessage: LiveMessage? = null,
val preview: ComposePreview = ComposePreview.NoPreview,
val contextItem: ComposeContextItem = ComposeContextItem.NoContextItem,
val inProgress: Boolean = false,
val useLinkPreviews: Boolean
) {
constructor(editingItem: ChatItem, liveMessage: LiveMessage? = null, useLinkPreviews: Boolean): this(
editingItem.content.text,
liveMessage,
chatItemPreview(editingItem),
ComposeContextItem.EditingItem(editingItem),
useLinkPreviews = useLinkPreviews
)
val editing: Boolean
get() =
when (contextItem) {
is ComposeContextItem.EditingItem -> true
else -> false
}
val sendEnabled: () -> Boolean
get() = {
val hasContent = when (preview) {
is ComposePreview.ImagePreview -> true
is ComposePreview.VideoPreview -> true
is ComposePreview.VoicePreview -> true
is ComposePreview.FilePreview -> true
else -> message.isNotEmpty() || liveMessage != null
}
hasContent && !inProgress
}
val endLiveDisabled: Boolean
get() = liveMessage != null && message.isEmpty() && preview is ComposePreview.NoPreview && contextItem is ComposeContextItem.NoContextItem
val linkPreviewAllowed: Boolean
get() =
when (preview) {
is ComposePreview.ImagePreview -> false
is ComposePreview.VideoPreview -> false
is ComposePreview.VoicePreview -> false
is ComposePreview.FilePreview -> false
else -> useLinkPreviews
}
val linkPreview: LinkPreview?
get() =
when (preview) {
is ComposePreview.CLinkPreview -> preview.linkPreview
else -> null
}
val attachmentDisabled: Boolean
get() {
if (editing || liveMessage != null) return true
return when (preview) {
ComposePreview.NoPreview -> false
is ComposePreview.CLinkPreview -> false
else -> true
}
}
val empty: Boolean
get() = message.isEmpty() && preview is ComposePreview.NoPreview
companion object {
fun saver(): Saver<MutableState<ComposeState>, *> = Saver(
save = { json.encodeToString(serializer(), it.value) },
restore = {
mutableStateOf(json.decodeFromString(it))
}
)
}
}
sealed class RecordingState {
object NotStarted: RecordingState()
class Started(val filePath: String, val progressMs: Int = 0): RecordingState()
class Finished(val filePath: String, val durationMs: Int): RecordingState()
val filePathNullable: String?
get() = (this as? Started)?.filePath
}
fun chatItemPreview(chatItem: ChatItem): ComposePreview {
val fileName = chatItem.file?.fileName ?: ""
return when (val mc = chatItem.content.msgContent) {
is MsgContent.MCText -> ComposePreview.NoPreview
is MsgContent.MCLink -> ComposePreview.CLinkPreview(linkPreview = mc.preview)
// TODO: include correct type
is MsgContent.MCImage -> ComposePreview.ImagePreview(images = listOf(mc.image), listOf(UploadContent.SimpleImage(getAppFileUri(fileName))))
is MsgContent.MCVideo -> ComposePreview.VideoPreview(images = listOf(mc.image), listOf(UploadContent.SimpleImage(getAppFileUri(fileName))))
is MsgContent.MCVoice -> ComposePreview.VoicePreview(voice = fileName, mc.duration / 1000, true)
is MsgContent.MCFile -> ComposePreview.FilePreview(fileName, getAppFileUri(fileName))
is MsgContent.MCUnknown, null -> ComposePreview.NoPreview
}
}
@Composable
fun ComposeView(
chatModel: ChatModel,
chat: Chat,
composeState: MutableState<ComposeState>,
attachmentOption: MutableState<AttachmentOption?>,
showChooseAttachment: () -> Unit
) {
val context = LocalContext.current
val linkUrl = rememberSaveable { mutableStateOf<String?>(null) }
val prevLinkUrl = rememberSaveable { mutableStateOf<String?>(null) }
val pendingLinkUrl = rememberSaveable { mutableStateOf<String?>(null) }
val cancelledLinks = rememberSaveable { mutableSetOf<String>() }
val useLinkPreviews = chatModel.controller.appPrefs.privacyLinkPreviews.get()
val xftpSendEnabled = chatModel.controller.appPrefs.xftpSendEnabled.get()
val maxFileSize = getMaxFileSize(fileProtocol = if (xftpSendEnabled) FileProtocol.XFTP else FileProtocol.SMP)
val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground)
val textStyle = remember { mutableStateOf(smallFont) }
val cameraLauncher = rememberCameraLauncher { uri: Uri? ->
if (uri != null) {
val bitmap: Bitmap? = getBitmapFromUri(uri)
if (bitmap != null) {
val imagePreview = resizeImageToStrSize(bitmap, maxDataSize = 14000)
composeState.value = composeState.value.copy(preview = ComposePreview.ImagePreview(listOf(imagePreview), listOf(UploadContent.SimpleImage(uri))))
}
}
}
val cameraPermissionLauncher = rememberPermissionLauncher { isGranted: Boolean ->
if (isGranted) {
cameraLauncher.launchWithFallback()
} else {
Toast.makeText(context, generalGetString(R.string.toast_permission_denied), Toast.LENGTH_SHORT).show()
}
}
val processPickedImage = { uris: List<Uri>, text: String? ->
val content = ArrayList<UploadContent>()
val imagesPreview = ArrayList<String>()
uris.forEach { uri ->
val drawable = getDrawableFromUri(uri)
var bitmap: Bitmap? = if (drawable != null) getBitmapFromUri(uri) else null
val isAnimNewApi = Build.VERSION.SDK_INT >= 28 && drawable is AnimatedImageDrawable
val isAnimOldApi = Build.VERSION.SDK_INT < 28 &&
(getFileName(SimplexApp.context, uri)?.endsWith(".gif") == true || getFileName(SimplexApp.context, uri)?.endsWith(".webp") == true)
if (isAnimNewApi || isAnimOldApi) {
// It's a gif or webp
val fileSize = getFileSize(context, uri)
if (fileSize != null && fileSize <= maxFileSize) {
content.add(UploadContent.AnimatedImage(uri))
} else {
bitmap = null
AlertManager.shared.showAlertMsg(
generalGetString(R.string.large_file),
String.format(generalGetString(R.string.maximum_supported_file_size), formatBytes(maxFileSize))
)
}
} else {
content.add(UploadContent.SimpleImage(uri))
}
if (bitmap != null) {
imagesPreview.add(resizeImageToStrSize(bitmap, maxDataSize = 14000))
}
}
if (imagesPreview.isNotEmpty()) {
composeState.value = composeState.value.copy(message = text ?: composeState.value.message, preview = ComposePreview.ImagePreview(imagesPreview, content))
}
}
val processPickedVideo = { uris: List<Uri>, text: String? ->
val content = ArrayList<UploadContent>()
val imagesPreview = ArrayList<String>()
uris.forEach { uri ->
val (bitmap: Bitmap?, durationMs: Long?) = getBitmapFromVideo(uri)
content.add(UploadContent.Video(uri, durationMs?.div(1000)?.toInt() ?: 0))
if (bitmap != null) {
imagesPreview.add(resizeImageToStrSize(bitmap, maxDataSize = 14000))
}
}
if (imagesPreview.isNotEmpty()) {
composeState.value = composeState.value.copy(message = text ?: composeState.value.message, preview = ComposePreview.VideoPreview(imagesPreview, content))
}
}
val processPickedFile = { uri: Uri?, text: String? ->
if (uri != null) {
val fileSize = getFileSize(context, uri)
if (fileSize != null && fileSize <= maxFileSize) {
val fileName = getFileName(SimplexApp.context, uri)
if (fileName != null) {
composeState.value = composeState.value.copy(message = text ?: composeState.value.message, preview = ComposePreview.FilePreview(fileName, uri))
}
} else {
AlertManager.shared.showAlertMsg(
generalGetString(R.string.large_file),
String.format(generalGetString(R.string.maximum_supported_file_size), formatBytes(maxFileSize))
)
}
}
}
val galleryImageLauncher = rememberLauncherForActivityResult(contract = PickMultipleImagesFromGallery()) { processPickedImage(it, null) }
val galleryImageLauncherFallback = rememberGetMultipleContentsLauncher { processPickedImage(it, null) }
val galleryVideoLauncher = rememberLauncherForActivityResult(contract = PickMultipleVideosFromGallery()) { processPickedVideo(it, null) }
val galleryVideoLauncherFallback = rememberGetMultipleContentsLauncher { processPickedVideo(it, null) }
val filesLauncher = rememberGetContentLauncher { processPickedFile(it, null) }
val recState: MutableState<RecordingState> = remember { mutableStateOf(RecordingState.NotStarted) }
LaunchedEffect(attachmentOption.value) {
when (attachmentOption.value) {
AttachmentOption.TakePhoto -> {
when (PackageManager.PERMISSION_GRANTED) {
ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) -> {
cameraLauncher.launchWithFallback()
}
else -> {
cameraPermissionLauncher.launch(Manifest.permission.CAMERA)
}
}
attachmentOption.value = null
}
AttachmentOption.PickImage -> {
try {
galleryImageLauncher.launch(0)
} catch (e: ActivityNotFoundException) {
galleryImageLauncherFallback.launch("image/*")
}
attachmentOption.value = null
}
AttachmentOption.PickVideo -> {
try {
galleryVideoLauncher.launch(0)
} catch (e: ActivityNotFoundException) {
galleryVideoLauncherFallback.launch("video/*")
}
attachmentOption.value = null
}
AttachmentOption.PickFile -> {
filesLauncher.launch("*/*")
attachmentOption.value = null
}
else -> {}
}
}
fun isSimplexLink(link: String): Boolean =
link.startsWith("https://simplex.chat", true) || link.startsWith("http://simplex.chat", true)
fun parseMessage(msg: String): String? {
val parsedMsg = runBlocking { chatModel.controller.apiParseMarkdown(msg) }
val link = parsedMsg?.firstOrNull { ft -> ft.format is Format.Uri && !cancelledLinks.contains(ft.text) && !isSimplexLink(ft.text) }
return link?.text
}
fun loadLinkPreview(url: String, wait: Long? = null) {
if (pendingLinkUrl.value == url) {
composeState.value = composeState.value.copy(preview = ComposePreview.CLinkPreview(null))
withApi {
if (wait != null) delay(wait)
val lp = getLinkPreview(url)
if (lp != null && pendingLinkUrl.value == url) {
composeState.value = composeState.value.copy(preview = ComposePreview.CLinkPreview(lp))
pendingLinkUrl.value = null
} else if (pendingLinkUrl.value == url) {
composeState.value = composeState.value.copy(preview = ComposePreview.NoPreview)
pendingLinkUrl.value = null
}
}
}
}
fun showLinkPreview(s: String) {
prevLinkUrl.value = linkUrl.value
linkUrl.value = parseMessage(s)
val url = linkUrl.value
if (url != null) {
if (url != composeState.value.linkPreview?.uri && url != pendingLinkUrl.value) {
pendingLinkUrl.value = url
loadLinkPreview(url, wait = if (prevLinkUrl.value == url) null else 1500L)
}
} else {
composeState.value = composeState.value.copy(preview = ComposePreview.NoPreview)
}
}
fun resetLinkPreview() {
linkUrl.value = null
prevLinkUrl.value = null
pendingLinkUrl.value = null
cancelledLinks.clear()
}
fun clearState(live: Boolean = false) {
if (live) {
composeState.value = composeState.value.copy(inProgress = false)
} else {
composeState.value = ComposeState(useLinkPreviews = useLinkPreviews)
resetLinkPreview()
}
recState.value = RecordingState.NotStarted
textStyle.value = smallFont
chatModel.removeLiveDummy()
}
fun deleteUnusedFiles() {
chatModel.filesToDelete.forEach { it.delete() }
chatModel.filesToDelete.clear()
}
suspend fun send(cInfo: ChatInfo, mc: MsgContent, quoted: Long?, file: String? = null, live: Boolean = false): ChatItem? {
val aChatItem = chatModel.controller.apiSendMessage(
type = cInfo.chatType,
id = cInfo.apiId,
file = file,
quotedItemId = quoted,
mc = mc,
live = live
)
if (aChatItem != null) chatModel.addChatItem(cInfo, aChatItem.chatItem)
return aChatItem?.chatItem
}
suspend fun sendMessageAsync(text: String?, live: Boolean): ChatItem? {
val cInfo = chat.chatInfo
val cs = composeState.value
var sent: ChatItem?
val msgText = text ?: cs.message
fun sending() {
composeState.value = composeState.value.copy(inProgress = true)
}
fun checkLinkPreview(): MsgContent {
return when (val composePreview = cs.preview) {
is ComposePreview.CLinkPreview -> {
val url = parseMessage(msgText)
val lp = composePreview.linkPreview
if (lp != null && url == lp.uri) {
MsgContent.MCLink(msgText, preview = lp)
} else {
MsgContent.MCText(msgText)
}
}
else -> MsgContent.MCText(msgText)
}
}
fun updateMsgContent(msgContent: MsgContent): MsgContent {
return when (msgContent) {
is MsgContent.MCText -> checkLinkPreview()
is MsgContent.MCLink -> checkLinkPreview()
is MsgContent.MCImage -> MsgContent.MCImage(msgText, image = msgContent.image)
is MsgContent.MCVideo -> MsgContent.MCVideo(msgText, image = msgContent.image, duration = msgContent.duration)
is MsgContent.MCVoice -> MsgContent.MCVoice(msgText, duration = msgContent.duration)
is MsgContent.MCFile -> MsgContent.MCFile(msgText)
is MsgContent.MCUnknown -> MsgContent.MCUnknown(type = msgContent.type, text = msgText, json = msgContent.json)
}
}
suspend fun updateMessage(ei: ChatItem, cInfo: ChatInfo, live: Boolean): ChatItem? {
val oldMsgContent = ei.content.msgContent
if (oldMsgContent != null) {
val updatedItem = chatModel.controller.apiUpdateChatItem(
type = cInfo.chatType,
id = cInfo.apiId,
itemId = ei.meta.itemId,
mc = updateMsgContent(oldMsgContent),
live = live
)
if (updatedItem != null) chatModel.upsertChatItem(cInfo, updatedItem.chatItem)
return updatedItem?.chatItem
}
return null
}
val liveMessage = cs.liveMessage
if (!live) {
if (liveMessage != null) composeState.value = cs.copy(liveMessage = null)
sending()
}
if (cs.contextItem is ComposeContextItem.EditingItem) {
val ei = cs.contextItem.chatItem
sent = updateMessage(ei, cInfo, live)
} else if (liveMessage != null && liveMessage.sent) {
sent = updateMessage(liveMessage.chatItem, cInfo, live)
} else {
val msgs: ArrayList<MsgContent> = ArrayList()
val files: ArrayList<String> = ArrayList()
when (val preview = cs.preview) {
ComposePreview.NoPreview -> msgs.add(MsgContent.MCText(msgText))
is ComposePreview.CLinkPreview -> msgs.add(checkLinkPreview())
is ComposePreview.ImagePreview -> {
preview.content.forEachIndexed { index, it ->
val file = when (it) {
is UploadContent.SimpleImage -> saveImage(context, it.uri)
is UploadContent.AnimatedImage -> saveAnimImage(context, it.uri)
else -> return@forEachIndexed
}
if (file != null) {
files.add(file)
msgs.add(MsgContent.MCImage(if (preview.content.lastIndex == index) msgText else "", preview.images[index]))
}
}
}
is ComposePreview.VideoPreview -> {
preview.content.forEachIndexed { index, it ->
val file = when (it) {
is UploadContent.Video -> saveFileFromUri(context, it.uri)
else -> return@forEachIndexed
}
if (file != null) {
files.add(file)
msgs.add(MsgContent.MCVideo(if (preview.content.lastIndex == index) msgText else "", preview.images[index], it.duration))
}
}
}
is ComposePreview.VoicePreview -> {
val tmpFile = File(preview.voice)
AudioPlayer.stop(tmpFile.absolutePath)
val actualFile = File(getAppFilePath(SimplexApp.context, tmpFile.name.replaceAfter(RecorderNative.extension, "")))
withContext(Dispatchers.IO) {
Files.move(tmpFile.toPath(), actualFile.toPath())
}
files.add(actualFile.name)
deleteUnusedFiles()
msgs.add(MsgContent.MCVoice(if (msgs.isEmpty()) msgText else "", preview.durationMs / 1000))
}
is ComposePreview.FilePreview -> {
val file = saveFileFromUri(context, preview.uri)
if (file != null) {
files.add((file))
msgs.add(MsgContent.MCFile(if (msgs.isEmpty()) msgText else ""))
}
}
}
val quotedItemId: Long? = when (cs.contextItem) {
is ComposeContextItem.QuotedItem -> cs.contextItem.chatItem.id
else -> null
}
sent = null
msgs.forEachIndexed { index, content ->
if (index > 0) delay(100)
sent = send(cInfo, content, if (index == 0) quotedItemId else null, files.getOrNull(index),
if (content !is MsgContent.MCVoice && index == msgs.lastIndex) live else false
)
}
if (sent == null &&
(cs.preview is ComposePreview.ImagePreview ||
cs.preview is ComposePreview.VideoPreview ||
cs.preview is ComposePreview.FilePreview ||
cs.preview is ComposePreview.VoicePreview)
) {
sent = send(cInfo, MsgContent.MCText(msgText), quotedItemId, null, live)
}
}
clearState(live)
return sent
}
fun sendMessage() {
withBGApi {
sendMessageAsync(null, false)
}
}
fun onMessageChange(s: String) {
composeState.value = composeState.value.copy(message = s)
if (isShortEmoji(s)) {
textStyle.value = if (s.codePoints().count() < 4) largeEmojiFont else mediumEmojiFont
} else {
textStyle.value = smallFont
if (composeState.value.linkPreviewAllowed) {
if (s.isNotEmpty()) showLinkPreview(s)
else resetLinkPreview()
}
}
}
fun onAudioAdded(filePath: String, durationMs: Int, finished: Boolean) {
val file = File(filePath)
chatModel.filesToDelete.add(file)
composeState.value = composeState.value.copy(preview = ComposePreview.VoicePreview(filePath, durationMs, finished))
}
fun allowVoiceToContact() {
val contact = (chat.chatInfo as ChatInfo.Direct?)?.contact ?: return
withApi {
chatModel.controller.allowFeatureToContact(contact, ChatFeature.Voice)
}
}
fun cancelLinkPreview() {
val uri = composeState.value.linkPreview?.uri
if (uri != null) {
cancelledLinks.add(uri)
}
pendingLinkUrl.value = null
composeState.value = composeState.value.copy(preview = ComposePreview.NoPreview)
}
fun cancelImages() {
composeState.value = composeState.value.copy(preview = ComposePreview.NoPreview)
}
fun cancelVoice() {
val filePath = recState.value.filePathNullable
recState.value = RecordingState.NotStarted
composeState.value = composeState.value.copy(preview = ComposePreview.NoPreview)
withBGApi {
RecorderNative.stopRecording?.invoke()
AudioPlayer.stop(filePath)
filePath?.let { File(it).delete() }
}
}
fun cancelFile() {
composeState.value = composeState.value.copy(preview = ComposePreview.NoPreview)
}
fun truncateToWords(s: String): String {
var acc = ""
val word = StringBuilder()
for (c in s) {
if (c.isLetter() || c.isDigit()) {
word.append(c)
} else {
acc = acc + word.toString() + c
word.clear()
}
}
return acc
}
suspend fun sendLiveMessage() {
val cs = composeState.value
val typedMsg = cs.message
if ((cs.sendEnabled() || cs.contextItem is ComposeContextItem.QuotedItem) && (cs.liveMessage == null || !cs.liveMessage?.sent)) {
val ci = sendMessageAsync(typedMsg, live = true)
if (ci != null) {
composeState.value = composeState.value.copy(liveMessage = LiveMessage(ci, typedMsg = typedMsg, sentMsg = typedMsg, sent = true))
}
} else if (cs.liveMessage == null) {
val cItem = chatModel.addLiveDummy(chat.chatInfo)
composeState.value = composeState.value.copy(liveMessage = LiveMessage(cItem, typedMsg = typedMsg, sentMsg = typedMsg, sent = false))
}
}
fun liveMessageToSend(lm: LiveMessage, t: String): String? {
val s = if (t != lm.typedMsg) truncateToWords(t) else t
return if (s != lm.sentMsg) s else null
}
suspend fun updateLiveMessage() {
val typedMsg = composeState.value.message
val liveMessage = composeState.value.liveMessage
if (liveMessage != null) {
val sentMsg = liveMessageToSend(liveMessage, typedMsg)
if (sentMsg != null) {
val ci = sendMessageAsync(sentMsg, live = true)
if (ci != null) {
composeState.value = composeState.value.copy(liveMessage = LiveMessage(ci, typedMsg = typedMsg, sentMsg = sentMsg, sent = true))
}
} else if (liveMessage.typedMsg != typedMsg) {
composeState.value = composeState.value.copy(liveMessage = liveMessage.copy(typedMsg = typedMsg))
}
}
}
@Composable
fun previewView() {
when (val preview = composeState.value.preview) {
ComposePreview.NoPreview -> {}
is ComposePreview.CLinkPreview -> ComposeLinkView(preview.linkPreview, ::cancelLinkPreview)
is ComposePreview.ImagePreview -> ComposeImageView(
preview.images,
::cancelImages,
cancelEnabled = !composeState.value.editing
)
is ComposePreview.VideoPreview -> ComposeImageView(
preview.images,
::cancelImages,
cancelEnabled = !composeState.value.editing
)
is ComposePreview.VoicePreview -> ComposeVoiceView(
preview.voice,
preview.durationMs,
preview.finished,
cancelEnabled = !composeState.value.editing,
::cancelVoice
)
is ComposePreview.FilePreview -> ComposeFileView(
preview.fileName,
::cancelFile,
cancelEnabled = !composeState.value.editing
)
}
}
@Composable
fun contextItemView() {
when (val contextItem = composeState.value.contextItem) {
ComposeContextItem.NoContextItem -> {}
is ComposeContextItem.QuotedItem -> ContextItemView(contextItem.chatItem, Icons.Outlined.Reply) {
composeState.value = composeState.value.copy(contextItem = ComposeContextItem.NoContextItem)
}
is ComposeContextItem.EditingItem -> ContextItemView(contextItem.chatItem, Icons.Filled.Edit) {
clearState()
}
}
}
LaunchedEffect(chatModel.sharedContent.value) {
// Important. If it's null, don't do anything, chat is not closed yet but will be after a moment
if (chatModel.chatId.value == null) return@LaunchedEffect
when (val shared = chatModel.sharedContent.value) {
is SharedContent.Text -> onMessageChange(shared.text)
is SharedContent.Images -> processPickedImage(shared.uris, shared.text)
is SharedContent.File -> processPickedFile(shared.uri, shared.text)
null -> {}
}
chatModel.sharedContent.value = null
}
val userCanSend = rememberUpdatedState(chat.userCanSend)
val userIsObserver = rememberUpdatedState(chat.userIsObserver)
Column {
contextItemView()
when {
composeState.value.editing && composeState.value.preview is ComposePreview.VoicePreview -> {}
composeState.value.editing && composeState.value.preview is ComposePreview.FilePreview -> {}
else -> previewView()
}
Row(
modifier = Modifier.padding(end = 8.dp),
verticalAlignment = Alignment.Bottom,
) {
IconButton(showChooseAttachment, enabled = !composeState.value.attachmentDisabled && rememberUpdatedState(chat.userCanSend).value) {
Icon(
Icons.Filled.AttachFile,
contentDescription = stringResource(R.string.attach),
tint = if (!composeState.value.attachmentDisabled && userCanSend.value) MaterialTheme.colors.primary else HighOrLowlight,
modifier = Modifier
.size(28.dp)
.clip(CircleShape)
)
}
val allowedVoiceByPrefs = remember(chat.chatInfo) { chat.chatInfo.featureEnabled(ChatFeature.Voice) }
LaunchedEffect(allowedVoiceByPrefs) {
if (!allowedVoiceByPrefs && composeState.value.preview is ComposePreview.VoicePreview) {
// Voice was disabled right when this user records it, just cancel it
cancelVoice()
}
}
val needToAllowVoiceToContact = remember(chat.chatInfo) {
chat.chatInfo is ChatInfo.Direct && with(chat.chatInfo.contact.mergedPreferences.voice) {
((userPreference as? ContactUserPref.User)?.preference?.allow == FeatureAllowed.NO || (userPreference as? ContactUserPref.Contact)?.preference?.allow == FeatureAllowed.NO) &&
contactPreference.allow == FeatureAllowed.YES
}
}
LaunchedEffect(Unit) {
snapshotFlow { recState.value }
.distinctUntilChanged()
.collect {
when(it) {
is RecordingState.Started -> onAudioAdded(it.filePath, it.progressMs, false)
is RecordingState.Finished -> onAudioAdded(it.filePath, it.durationMs, true)
is RecordingState.NotStarted -> {}
}
}
}
fun clearCurrentDraft() {
if (chatModel.draftChatId.value == chat.id) {
chatModel.draft.value = null
chatModel.draftChatId.value = null
}
}
LaunchedEffect(rememberUpdatedState(chat.userCanSend).value) {
if (!chat.userCanSend) {
clearCurrentDraft()
clearState()
}
}
val activity = LocalContext.current as Activity
DisposableEffect(Unit) {
val orientation = activity.resources.configuration.orientation
onDispose {
if (orientation == activity.resources.configuration.orientation) {
val cs = composeState.value
if (cs.liveMessage != null && (cs.message.isNotEmpty() || cs.liveMessage.sent)) {
sendMessage()
resetLinkPreview()
clearCurrentDraft()
deleteUnusedFiles()
} else if (!composeState.value.empty) {
if (cs.preview is ComposePreview.VoicePreview && !cs.preview.finished) {
composeState.value = cs.copy(preview = cs.preview.copy(finished = true))
}
chatModel.draft.value = composeState.value
chatModel.draftChatId.value = chat.id
} else {
clearCurrentDraft()
deleteUnusedFiles()
}
chatModel.removeLiveDummy()
}
}
}
SendMsgView(
composeState,
showVoiceRecordIcon = true,
recState,
chat.chatInfo is ChatInfo.Direct,
liveMessageAlertShown = chatModel.controller.appPrefs.liveMessageAlertShown,
needToAllowVoiceToContact,
allowedVoiceByPrefs,
allowVoiceToContact = ::allowVoiceToContact,
userIsObserver = userIsObserver.value,
userCanSend = userCanSend.value,
sendMessage = {
sendMessage()
resetLinkPreview()
},
sendLiveMessage = ::sendLiveMessage,
updateLiveMessage = ::updateLiveMessage,
cancelLiveMessage = {
composeState.value = composeState.value.copy(liveMessage = null)
chatModel.removeLiveDummy()
},
onMessageChange = ::onMessageChange,
textStyle = textStyle
)
}
}
}
class PickFromGallery: ActivityResultContract<Int, Uri?>() {
override fun createIntent(context: Context, input: Int) =
Intent(Intent.ACTION_PICK, MediaStore.Images.Media.INTERNAL_CONTENT_URI).apply {
type = "image/*"
}
override fun parseResult(resultCode: Int, intent: Intent?): Uri? = intent?.data
}
class PickMultipleImagesFromGallery: ActivityResultContract<Int, List<Uri>>() {
override fun createIntent(context: Context, input: Int) =
Intent(Intent.ACTION_PICK, MediaStore.Images.Media.INTERNAL_CONTENT_URI).apply {
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
type = "image/*"
}
override fun parseResult(resultCode: Int, intent: Intent?): List<Uri> =
if (intent?.data != null)
listOf(intent.data!!)
else if (intent?.clipData != null)
with(intent.clipData!!) {
val uris = ArrayList<Uri>()
for (i in 0 until kotlin.math.min(itemCount, 10)) {
val uri = getItemAt(i).uri
if (uri != null) uris.add(uri)
}
if (itemCount > 10) {
AlertManager.shared.showAlertMsg(R.string.images_limit_title, R.string.images_limit_desc)
}
uris
}
else
emptyList()
}
class PickMultipleVideosFromGallery: ActivityResultContract<Int, List<Uri>>() {
override fun createIntent(context: Context, input: Int) =
Intent(Intent.ACTION_PICK, MediaStore.Video.Media.INTERNAL_CONTENT_URI).apply {
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
type = "video/*"
}
override fun parseResult(resultCode: Int, intent: Intent?): List<Uri> =
if (intent?.data != null)
listOf(intent.data!!)
else if (intent?.clipData != null)
with(intent.clipData!!) {
val uris = ArrayList<Uri>()
for (i in 0 until kotlin.math.min(itemCount, 10)) {
val uri = getItemAt(i).uri
if (uri != null) uris.add(uri)
}
if (itemCount > 10) {
AlertManager.shared.showAlertMsg(R.string.videos_limit_title, R.string.videos_limit_desc)
}
uris
}
else
emptyList()
}

View File

@@ -0,0 +1,133 @@
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

@@ -0,0 +1,232 @@
package chat.simplex.app.views.chat
import InfoRow
import SectionDivider
import SectionItemView
import SectionSpacer
import SectionTextFooter
import SectionView
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
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.res.stringResource
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.PreferenceToggle
@Composable
fun ContactPreferencesView(
m: ChatModel,
user: User,
contactId: Long,
close: () -> Unit,
) {
val contact = remember { derivedStateOf { (m.getContactChat(contactId)?.chatInfo as? ChatInfo.Direct)?.contact } }
val ct = contact.value ?: return
var featuresAllowed by rememberSaveable(ct, stateSaver = serializableSaver()) { mutableStateOf(contactUserPrefsToFeaturesAllowed(ct.mergedPreferences)) }
var currentFeaturesAllowed by rememberSaveable(ct, stateSaver = serializableSaver()) { mutableStateOf(featuresAllowed) }
fun savePrefs(afterSave: () -> Unit = {}) {
withApi {
val prefs = contactFeaturesAllowedToPrefs(featuresAllowed)
val toContact = m.controller.apiSetContactPrefs(ct.contactId, prefs)
if (toContact != null) {
m.updateContact(toContact)
currentFeaturesAllowed = featuresAllowed
}
afterSave()
}
}
ModalView(
close = {
if (featuresAllowed == currentFeaturesAllowed) close()
else showUnsavedChangesAlert({ savePrefs(close) }, close)
},
background = if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
) {
ContactPreferencesLayout(
featuresAllowed,
currentFeaturesAllowed,
user,
ct,
applyPrefs = { prefs ->
featuresAllowed = prefs
},
reset = {
featuresAllowed = currentFeaturesAllowed
},
savePrefs = ::savePrefs,
)
}
}
@Composable
private fun ContactPreferencesLayout(
featuresAllowed: ContactFeaturesAllowed,
currentFeaturesAllowed: ContactFeaturesAllowed,
user: User,
contact: Contact,
applyPrefs: (ContactFeaturesAllowed) -> Unit,
reset: () -> Unit,
savePrefs: () -> Unit,
) {
Column(
Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState())
.padding(bottom = DEFAULT_PADDING),
horizontalAlignment = Alignment.Start,
) {
AppBarTitle(stringResource(R.string.contact_preferences))
val timedMessages: MutableState<Boolean> = remember(featuresAllowed) { mutableStateOf(featuresAllowed.timedMessagesAllowed) }
val onTTLUpdated = { ttl: Int? ->
applyPrefs(featuresAllowed.copy(timedMessagesTTL = ttl ?: 86400))
}
TimedMessagesFeatureSection(featuresAllowed, contact.mergedPreferences.timedMessages, timedMessages, onTTLUpdated) { allowed, ttl ->
applyPrefs(featuresAllowed.copy(timedMessagesAllowed = allowed, timedMessagesTTL = ttl ?: currentFeaturesAllowed.timedMessagesTTL))
}
SectionSpacer()
val allowFullDeletion: MutableState<ContactFeatureAllowed> = remember(featuresAllowed) { mutableStateOf(featuresAllowed.fullDelete) }
FeatureSection(ChatFeature.FullDelete, user.fullPreferences.fullDelete.allow, contact.mergedPreferences.fullDelete, allowFullDeletion) {
applyPrefs(featuresAllowed.copy(fullDelete = it))
}
SectionSpacer()
val allowVoice: MutableState<ContactFeatureAllowed> = remember(featuresAllowed) { mutableStateOf(featuresAllowed.voice) }
FeatureSection(ChatFeature.Voice, user.fullPreferences.voice.allow, contact.mergedPreferences.voice, allowVoice) {
applyPrefs(featuresAllowed.copy(voice = it))
}
SectionSpacer()
ResetSaveButtons(
reset = reset,
save = savePrefs,
disabled = featuresAllowed == currentFeaturesAllowed
)
}
}
@Composable
private fun FeatureSection(
feature: ChatFeature,
userDefault: FeatureAllowed,
pref: ContactUserPreference,
allowFeature: State<ContactFeatureAllowed>,
onSelected: (ContactFeatureAllowed) -> Unit
) {
val enabled = FeatureEnabled.enabled(
feature.asymmetric,
user = SimpleChatPreference(allow = allowFeature.value.allowed),
contact = pref.contactPreference
)
SectionView(
feature.text.uppercase(),
icon = feature.iconFilled,
iconTint = if (enabled.forUser) SimplexGreen else if (enabled.forContact) WarningYellow else Color.Red,
leadingIcon = true,
) {
SectionItemView {
ExposedDropDownSettingRow(
generalGetString(R.string.chat_preferences_you_allow),
ContactFeatureAllowed.values(userDefault).map { it to it.text },
allowFeature,
icon = null,
onSelected = onSelected
)
}
SectionDivider()
InfoRow(
generalGetString(R.string.chat_preferences_contact_allows),
pref.contactPreference.allow.text
)
}
SectionTextFooter(feature.enabledDescription(enabled))
}
@Composable
private fun TimedMessagesFeatureSection(
featuresAllowed: ContactFeaturesAllowed,
pref: ContactUserPreferenceTimed,
allowFeature: State<Boolean>,
onTTLUpdated: (Int?) -> Unit,
onSelected: (Boolean, Int?) -> Unit
) {
val enabled = FeatureEnabled.enabled(
ChatFeature.TimedMessages.asymmetric,
user = TimedMessagesPreference(allow = if (allowFeature.value) FeatureAllowed.YES else FeatureAllowed.NO),
contact = pref.contactPreference
)
SectionView(
ChatFeature.TimedMessages.text.uppercase(),
icon = ChatFeature.TimedMessages.iconFilled,
iconTint = if (enabled.forUser) SimplexGreen else if (enabled.forContact) WarningYellow else Color.Red,
leadingIcon = true,
) {
SectionItemView {
PreferenceToggle(
generalGetString(R.string.chat_preferences_you_allow),
checked = allowFeature.value,
) { allow ->
onSelected(allow, if (allow) featuresAllowed.timedMessagesTTL ?: 86400 else null)
}
}
SectionDivider()
InfoRow(
generalGetString(R.string.chat_preferences_contact_allows),
pref.contactPreference.allow.text
)
SectionDivider()
if (featuresAllowed.timedMessagesAllowed) {
val ttl = rememberSaveable(featuresAllowed.timedMessagesTTL) { mutableStateOf(featuresAllowed.timedMessagesTTL) }
TimedMessagesTTLPicker(ttl, onTTLUpdated)
} else if (pref.contactPreference.allow == FeatureAllowed.YES || pref.contactPreference.allow == FeatureAllowed.ALWAYS) {
InfoRow(generalGetString(R.string.delete_after), TimedMessagesPreference.ttlText(pref.contactPreference.ttl))
}
}
SectionTextFooter(ChatFeature.TimedMessages.enabledDescription(enabled))
}
@Composable
private fun ResetSaveButtons(reset: () -> Unit, save: () -> Unit, disabled: Boolean) {
SectionView {
SectionItemView(reset, disabled = disabled) {
Text(stringResource(R.string.reset_verb), color = if (disabled) HighOrLowlight else MaterialTheme.colors.primary)
}
SectionDivider()
SectionItemView(save, disabled = disabled) {
Text(stringResource(R.string.save_and_notify_contact), color = if (disabled) HighOrLowlight else MaterialTheme.colors.primary)
}
}
}
@Composable
fun TimedMessagesTTLPicker(selection: MutableState<Int?>, onSelected: (Int?) -> Unit) {
val ttlValues = TimedMessagesPreference.ttlValues
val values = ttlValues + if (ttlValues.contains(selection.value)) listOf() else listOf(selection.value)
SectionItemView {
ExposedDropDownSettingRow(
generalGetString(R.string.delete_after),
values.map { it to TimedMessagesPreference.ttlText(it) },
selection,
onSelected = onSelected
)
}
}
private fun showUnsavedChangesAlert(save: () -> Unit, revert: () -> Unit) {
AlertManager.shared.showAlertDialogStacked(
title = generalGetString(R.string.save_preferences_question),
confirmText = generalGetString(R.string.save_and_notify_contact),
dismissText = generalGetString(R.string.exit_without_saving),
onConfirm = save,
onDismiss = revert,
)
}

View File

@@ -0,0 +1,80 @@
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

@@ -0,0 +1,53 @@
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

@@ -0,0 +1,673 @@
package chat.simplex.app.views.chat
import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.os.Build
import android.text.InputType
import android.util.Log
import android.view.ViewGroup
import android.view.WindowManager
import android.view.inputmethod.*
import android.widget.EditText
import android.widget.TextView
import androidx.compose.animation.core.*
import androidx.compose.foundation.*
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.*
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.*
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.*
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.*
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.*
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.view.inputmethod.EditorInfoCompat
import androidx.core.view.inputmethod.InputConnectionCompat
import androidx.core.widget.*
import chat.simplex.app.R
import chat.simplex.app.SimplexApp
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.ItemAction
import chat.simplex.app.views.helpers.*
import com.google.accompanist.permissions.rememberMultiplePermissionsState
import kotlinx.coroutines.*
import java.lang.reflect.Field
@Composable
fun SendMsgView(
composeState: MutableState<ComposeState>,
showVoiceRecordIcon: Boolean,
recState: MutableState<RecordingState>,
isDirectChat: Boolean,
liveMessageAlertShown: SharedPreference<Boolean>,
needToAllowVoiceToContact: Boolean,
allowedVoiceByPrefs: Boolean,
userIsObserver: Boolean,
userCanSend: Boolean,
allowVoiceToContact: () -> Unit,
sendMessage: () -> Unit,
sendLiveMessage: (suspend () -> Unit)? = null,
updateLiveMessage: (suspend () -> Unit)? = null,
cancelLiveMessage: (() -> Unit)? = null,
onMessageChange: (String) -> Unit,
textStyle: MutableState<TextStyle>
) {
Box(Modifier.padding(vertical = 8.dp)) {
val cs = composeState.value
val showProgress = cs.inProgress && (cs.preview is ComposePreview.ImagePreview || cs.preview is ComposePreview.VideoPreview || cs.preview is ComposePreview.FilePreview)
val showVoiceButton = cs.message.isEmpty() && showVoiceRecordIcon && !composeState.value.editing &&
cs.liveMessage == null && (cs.preview is ComposePreview.NoPreview || recState.value is RecordingState.Started)
val showDeleteTextButton = rememberSaveable { mutableStateOf(false) }
NativeKeyboard(composeState, textStyle, showDeleteTextButton, userIsObserver, onMessageChange)
// Disable clicks on text field
if (cs.preview is ComposePreview.VoicePreview || !userCanSend) {
Box(Modifier
.matchParentSize()
.clickable(enabled = !userCanSend, indication = null, interactionSource = remember { MutableInteractionSource() }, onClick = {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.observer_cant_send_message_title),
text = generalGetString(R.string.observer_cant_send_message_desc)
)
})
)
}
if (showDeleteTextButton.value) {
DeleteTextButton(composeState)
}
Box(Modifier.align(Alignment.BottomEnd)) {
val sendButtonSize = remember { Animatable(36f) }
val sendButtonAlpha = remember { Animatable(1f) }
val permissionsState = rememberMultiplePermissionsState(listOf(Manifest.permission.RECORD_AUDIO))
val scope = rememberCoroutineScope()
LaunchedEffect(Unit) {
// Making LiveMessage alive when screen orientation was changed
if (cs.liveMessage != null && sendLiveMessage != null && updateLiveMessage != null) {
startLiveMessage(scope, sendLiveMessage, updateLiveMessage, sendButtonSize, sendButtonAlpha, composeState, liveMessageAlertShown)
}
}
when {
showProgress -> ProgressIndicator()
showVoiceButton -> {
Row(verticalAlignment = Alignment.CenterVertically) {
val stopRecOnNextClick = remember { mutableStateOf(false) }
when {
needToAllowVoiceToContact || !allowedVoiceByPrefs || !userCanSend -> {
DisallowedVoiceButton(userCanSend) {
if (needToAllowVoiceToContact) {
showNeedToAllowVoiceAlert(allowVoiceToContact)
} else if (!allowedVoiceByPrefs) {
showDisabledVoiceAlert(isDirectChat)
}
}
}
!permissionsState.allPermissionsGranted ->
VoiceButtonWithoutPermission { permissionsState.launchMultiplePermissionRequest() }
else ->
RecordVoiceView(recState, stopRecOnNextClick)
}
if (sendLiveMessage != null
&& updateLiveMessage != null
&& (cs.preview !is ComposePreview.VoicePreview || !stopRecOnNextClick.value)
&& cs.contextItem is ComposeContextItem.NoContextItem) {
Spacer(Modifier.width(10.dp))
StartLiveMessageButton(userCanSend) {
if (composeState.value.preview is ComposePreview.NoPreview) {
startLiveMessage(scope, sendLiveMessage, updateLiveMessage, sendButtonSize, sendButtonAlpha, composeState, liveMessageAlertShown)
}
}
}
}
}
cs.liveMessage?.sent == false && cs.message.isEmpty() -> {
CancelLiveMessageButton {
cancelLiveMessage?.invoke()
}
}
else -> {
val cs = composeState.value
val icon = if (cs.editing || cs.liveMessage != null) Icons.Filled.Check else Icons.Outlined.ArrowUpward
val disabled = !cs.sendEnabled() ||
(!allowedVoiceByPrefs && cs.preview is ComposePreview.VoicePreview) ||
cs.endLiveDisabled
if (cs.liveMessage == null &&
cs.preview !is ComposePreview.VoicePreview && !cs.editing &&
cs.contextItem is ComposeContextItem.NoContextItem &&
sendLiveMessage != null && updateLiveMessage != null
) {
var showDropdown by rememberSaveable { mutableStateOf(false) }
SendMsgButton(icon, sendButtonSize, sendButtonAlpha, !disabled, sendMessage) { showDropdown = true }
DropdownMenu(
expanded = showDropdown,
onDismissRequest = { showDropdown = false },
Modifier.width(220.dp),
) {
ItemAction(
generalGetString(R.string.send_live_message),
Icons.Filled.Bolt,
onClick = {
startLiveMessage(scope, sendLiveMessage, updateLiveMessage, sendButtonSize, sendButtonAlpha, composeState, liveMessageAlertShown)
showDropdown = false
}
)
}
} else {
SendMsgButton(icon, sendButtonSize, sendButtonAlpha, !disabled, sendMessage)
}
}
}
}
}
}
@Composable
private fun NativeKeyboard(
composeState: MutableState<ComposeState>,
textStyle: MutableState<TextStyle>,
showDeleteTextButton: MutableState<Boolean>,
userIsObserver: Boolean,
onMessageChange: (String) -> Unit
) {
val cs = composeState.value
val textColor = MaterialTheme.colors.onBackground
val tintColor = MaterialTheme.colors.secondary
val padding = PaddingValues(12.dp, 7.dp, 45.dp, 0.dp)
val paddingStart = with(LocalDensity.current) { 12.dp.roundToPx() }
val paddingTop = with(LocalDensity.current) { 7.dp.roundToPx() }
val paddingEnd = with(LocalDensity.current) { 45.dp.roundToPx() }
val paddingBottom = with(LocalDensity.current) { 7.dp.roundToPx() }
var showKeyboard by remember { mutableStateOf(false) }
LaunchedEffect(cs.contextItem) {
if (cs.contextItem is ComposeContextItem.QuotedItem) {
delay(100)
showKeyboard = true
} else if (cs.contextItem is ComposeContextItem.EditingItem) {
// Keyboard will not show up if we try to show it too fast
delay(300)
showKeyboard = true
}
}
AndroidView(modifier = Modifier, factory = {
val editText = @SuppressLint("AppCompatCustomView") object: EditText(it) {
override fun setOnReceiveContentListener(
mimeTypes: Array<out String>?,
listener: android.view.OnReceiveContentListener?
) {
super.setOnReceiveContentListener(mimeTypes, listener)
}
override fun onCreateInputConnection(editorInfo: EditorInfo): InputConnection {
val connection = super.onCreateInputConnection(editorInfo)
EditorInfoCompat.setContentMimeTypes(editorInfo, arrayOf("image/*"))
val onCommit = InputConnectionCompat.OnCommitContentListener { inputContentInfo, _, _ ->
try {
inputContentInfo.requestPermission()
} catch (e: Exception) {
return@OnCommitContentListener false
}
SimplexApp.context.chatModel.sharedContent.value = SharedContent.Images("", listOf(inputContentInfo.contentUri))
true
}
return InputConnectionCompat.createWrapper(connection, editorInfo, onCommit)
}
}
editText.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
editText.maxLines = 16
editText.inputType = InputType.TYPE_TEXT_FLAG_CAP_SENTENCES or editText.inputType
editText.setTextColor(textColor.toArgb())
editText.textSize = textStyle.value.fontSize.value
val drawable = it.getDrawable(R.drawable.send_msg_view_background)!!
DrawableCompat.setTint(drawable, tintColor.toArgb())
editText.background = drawable
editText.setPadding(paddingStart, paddingTop, paddingEnd, paddingBottom)
editText.setText(cs.message)
if (Build.VERSION.SDK_INT >= 29) {
editText.textCursorDrawable?.let { DrawableCompat.setTint(it, HighOrLowlight.toArgb()) }
} else {
try {
val f: Field = TextView::class.java.getDeclaredField("mCursorDrawableRes")
f.isAccessible = true
f.set(editText, R.drawable.edit_text_cursor)
} catch (e: Exception) {
Log.e(chat.simplex.app.TAG, e.stackTraceToString())
}
}
editText.doOnTextChanged { text, _, _, _ -> onMessageChange(text.toString()) }
editText.doAfterTextChanged { text -> if (composeState.value.preview is ComposePreview.VoicePreview && text.toString() != "") editText.setText("") }
editText
}) {
it.setTextColor(textColor.toArgb())
it.textSize = textStyle.value.fontSize.value
DrawableCompat.setTint(it.background, tintColor.toArgb())
it.isFocusable = composeState.value.preview !is ComposePreview.VoicePreview
it.isFocusableInTouchMode = it.isFocusable
if (cs.message != it.text.toString()) {
it.setText(cs.message)
// Set cursor to the end of the text
it.setSelection(it.text.length)
}
if (showKeyboard) {
it.requestFocus()
val imm: InputMethodManager = SimplexApp.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(it, InputMethodManager.SHOW_IMPLICIT)
showKeyboard = false
}
showDeleteTextButton.value = it.lineCount >= 4
}
if (composeState.value.preview is ComposePreview.VoicePreview) {
ComposeOverlay(R.string.voice_message_send_text, textStyle, padding)
} else if (userIsObserver) {
ComposeOverlay(R.string.you_are_observer, textStyle, padding)
}
}
@Composable
private fun ComposeOverlay(textId: Int, textStyle: MutableState<TextStyle>, padding: PaddingValues) {
Text(
generalGetString(textId),
Modifier.padding(padding),
color = HighOrLowlight,
style = textStyle.value.copy(fontStyle = FontStyle.Italic)
)
}
@Composable
private fun BoxScope.DeleteTextButton(composeState: MutableState<ComposeState>) {
IconButton(
{ composeState.value = composeState.value.copy(message = "") },
Modifier.align(Alignment.TopEnd).size(36.dp)
) {
Icon(Icons.Filled.Close, null, Modifier.padding(7.dp).size(36.dp), tint = HighOrLowlight)
}
}
@Composable
private fun RecordVoiceView(recState: MutableState<RecordingState>, stopRecOnNextClick: MutableState<Boolean>) {
val rec: Recorder = remember { RecorderNative(MAX_VOICE_SIZE_FOR_SENDING) }
DisposableEffect(Unit) { onDispose { rec.stop() } }
val stopRecordingAndAddAudio: () -> Unit = {
recState.value.filePathNullable?.let {
recState.value = RecordingState.Finished(it, rec.stop())
}
}
if (stopRecOnNextClick.value) {
LaunchedEffect(recState.value) {
if (recState.value is RecordingState.NotStarted) {
stopRecOnNextClick.value = false
}
}
// Lock orientation to current orientation because screen rotation will break the recording
LockToCurrentOrientationUntilDispose()
StopRecordButton(stopRecordingAndAddAudio)
} else {
val startRecording: () -> Unit = {
recState.value = RecordingState.Started(
filePath = rec.start { progress: Int?, finished: Boolean ->
val state = recState.value
if (state is RecordingState.Started && progress != null) {
recState.value = if (!finished)
RecordingState.Started(state.filePath, progress)
else
RecordingState.Finished(state.filePath, progress)
}
},
)
}
val interactionSource = interactionSourceWithTapDetection(
onPress = { if (recState.value is RecordingState.NotStarted) startRecording() },
onClick = {
if (stopRecOnNextClick.value) {
stopRecordingAndAddAudio()
} else {
// tapped and didn't hold a finger
stopRecOnNextClick.value = true
}
},
onCancel = stopRecordingAndAddAudio,
onRelease = stopRecordingAndAddAudio
)
RecordVoiceButton(interactionSource)
}
}
@Composable
private fun DisallowedVoiceButton(enabled: Boolean, onClick: () -> Unit) {
IconButton(onClick, Modifier.size(36.dp), enabled = enabled) {
Icon(
Icons.Outlined.KeyboardVoice,
stringResource(R.string.icon_descr_record_voice_message),
tint = HighOrLowlight,
modifier = Modifier
.size(36.dp)
.padding(4.dp)
)
}
}
@Composable
private fun VoiceButtonWithoutPermission(onClick: () -> Unit) {
IconButton(onClick, Modifier.size(36.dp)) {
Icon(
Icons.Filled.KeyboardVoice,
stringResource(R.string.icon_descr_record_voice_message),
tint = MaterialTheme.colors.primary,
modifier = Modifier
.size(34.dp)
.padding(4.dp)
)
}
}
@Composable
private fun LockToCurrentOrientationUntilDispose() {
val context = LocalContext.current
DisposableEffect(Unit) {
val activity = context as Activity
val manager = context.getSystemService(Activity.WINDOW_SERVICE) as WindowManager
val rotation = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) manager.defaultDisplay.rotation else activity.display?.rotation
activity.requestedOrientation = when (rotation) {
android.view.Surface.ROTATION_90 -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
android.view.Surface.ROTATION_180 -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT
android.view.Surface.ROTATION_270 -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
else -> ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}
// Unlock orientation
onDispose { activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED }
}
}
@Composable
private fun StopRecordButton(onClick: () -> Unit) {
IconButton(onClick, Modifier.size(36.dp)) {
Icon(
Icons.Filled.Stop,
stringResource(R.string.icon_descr_record_voice_message),
tint = MaterialTheme.colors.primary,
modifier = Modifier
.size(36.dp)
.padding(4.dp)
)
}
}
@Composable
private fun RecordVoiceButton(interactionSource: MutableInteractionSource) {
IconButton({}, Modifier.size(36.dp), interactionSource = interactionSource) {
Icon(
Icons.Filled.KeyboardVoice,
stringResource(R.string.icon_descr_record_voice_message),
tint = MaterialTheme.colors.primary,
modifier = Modifier
.size(34.dp)
.padding(4.dp)
)
}
}
@Composable
private fun ProgressIndicator() {
CircularProgressIndicator(Modifier.size(36.dp).padding(4.dp), color = HighOrLowlight, strokeWidth = 3.dp)
}
@Composable
private fun CancelLiveMessageButton(
onClick: () -> Unit
) {
IconButton(onClick, Modifier.size(36.dp)) {
Icon(
Icons.Filled.Close,
stringResource(R.string.icon_descr_cancel_live_message),
tint = MaterialTheme.colors.primary,
modifier = Modifier
.size(36.dp)
.padding(4.dp)
)
}
}
@Composable
private fun SendMsgButton(
icon: ImageVector,
sizeDp: Animatable<Float, AnimationVector1D>,
alpha: Animatable<Float, AnimationVector1D>,
enabled: Boolean,
sendMessage: () -> Unit,
onLongClick: (() -> Unit)? = null
) {
val interactionSource = remember { MutableInteractionSource() }
Box(
modifier = Modifier.requiredSize(36.dp)
.combinedClickable(
onClick = sendMessage,
onLongClick = onLongClick,
enabled = enabled,
role = Role.Button,
interactionSource = interactionSource,
indication = rememberRipple(bounded = false, radius = 24.dp)
),
contentAlignment = Alignment.Center
) {
Icon(
icon,
stringResource(R.string.icon_descr_send_message),
tint = Color.White,
modifier = Modifier
.size(sizeDp.value.dp)
.padding(4.dp)
.alpha(alpha.value)
.clip(CircleShape)
.background(if (enabled) MaterialTheme.colors.primary else HighOrLowlight)
.padding(3.dp)
)
}
}
@Composable
private fun StartLiveMessageButton(enabled: Boolean, onClick: () -> Unit) {
val interactionSource = remember { MutableInteractionSource() }
Box(
modifier = Modifier.requiredSize(36.dp)
.clickable(
onClick = onClick,
enabled = enabled,
role = Role.Button,
interactionSource = interactionSource,
indication = rememberRipple(bounded = false, radius = 24.dp)
),
contentAlignment = Alignment.Center
) {
Icon(
Icons.Filled.Bolt,
stringResource(R.string.icon_descr_send_message),
tint = if (enabled) MaterialTheme.colors.primary else HighOrLowlight,
modifier = Modifier
.size(36.dp)
.padding(4.dp)
)
}
}
private fun startLiveMessage(
scope: CoroutineScope,
send: suspend () -> Unit,
update: suspend () -> Unit,
sendButtonSize: Animatable<Float, AnimationVector1D>,
sendButtonAlpha: Animatable<Float, AnimationVector1D>,
composeState: MutableState<ComposeState>,
liveMessageAlertShown: SharedPreference<Boolean>
) {
fun run() {
scope.launch {
while (composeState.value.liveMessage != null) {
sendButtonSize.animateTo(if (sendButtonSize.value == 36f) 32f else 36f, tween(700, 50))
}
sendButtonSize.snapTo(36f)
}
scope.launch {
while (composeState.value.liveMessage != null) {
sendButtonAlpha.animateTo(if (sendButtonAlpha.value == 1f) 0.75f else 1f, tween(700, 50))
}
sendButtonAlpha.snapTo(1f)
}
scope.launch {
delay(3000)
while (composeState.value.liveMessage != null) {
update()
delay(3000)
}
}
}
fun start() = withBGApi {
if (composeState.value.liveMessage == null) {
send()
}
run()
}
if (liveMessageAlertShown.state.value) {
start()
} else {
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.live_message),
text = generalGetString(R.string.send_live_message_desc),
confirmText = generalGetString(R.string.send_verb),
onConfirm = {
liveMessageAlertShown.set(true)
start()
})
}
}
private fun showNeedToAllowVoiceAlert(onConfirm: () -> Unit) {
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.allow_voice_messages_question),
text = generalGetString(R.string.you_need_to_allow_to_send_voice),
confirmText = generalGetString(R.string.allow_verb),
dismissText = generalGetString(R.string.cancel_verb),
onConfirm = onConfirm,
)
}
private fun showDisabledVoiceAlert(isDirectChat: Boolean) {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.voice_messages_prohibited),
text = generalGetString(
if (isDirectChat)
R.string.ask_your_contact_to_enable_voice
else
R.string.only_group_owners_can_enable_voice
)
)
}
@Preview(showBackground = true)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
fun PreviewSendMsgView() {
val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground)
val textStyle = remember { mutableStateOf(smallFont) }
SimpleXTheme {
SendMsgView(
composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) },
showVoiceRecordIcon = false,
recState = remember { mutableStateOf(RecordingState.NotStarted) },
isDirectChat = true,
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
needToAllowVoiceToContact = false,
allowedVoiceByPrefs = true,
userIsObserver = false,
userCanSend = true,
allowVoiceToContact = {},
sendMessage = {},
onMessageChange = { _ -> },
textStyle = textStyle
)
}
}
@Preview(showBackground = true)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
fun PreviewSendMsgViewEditing() {
val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground)
val textStyle = remember { mutableStateOf(smallFont) }
val composeStateEditing = ComposeState(editingItem = ChatItem.getSampleData(), useLinkPreviews = true)
SimpleXTheme {
SendMsgView(
composeState = remember { mutableStateOf(composeStateEditing) },
showVoiceRecordIcon = false,
recState = remember { mutableStateOf(RecordingState.NotStarted) },
isDirectChat = true,
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
needToAllowVoiceToContact = false,
allowedVoiceByPrefs = true,
userIsObserver = false,
userCanSend = true,
allowVoiceToContact = {},
sendMessage = {},
onMessageChange = { _ -> },
textStyle = textStyle
)
}
}
@Preview(showBackground = true)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
fun PreviewSendMsgViewInProgress() {
val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground)
val textStyle = remember { mutableStateOf(smallFont) }
val composeStateInProgress = ComposeState(preview = ComposePreview.FilePreview("test.txt", getAppFileUri("test.txt")), inProgress = true, useLinkPreviews = true)
SimpleXTheme {
SendMsgView(
composeState = remember { mutableStateOf(composeStateInProgress) },
showVoiceRecordIcon = false,
recState = remember { mutableStateOf(RecordingState.NotStarted) },
isDirectChat = true,
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
needToAllowVoiceToContact = false,
allowedVoiceByPrefs = true,
userIsObserver = false,
userCanSend = true,
allowVoiceToContact = {},
sendMessage = {},
onMessageChange = { _ -> },
textStyle = textStyle
)
}
}

View File

@@ -0,0 +1,137 @@
package chat.simplex.app.views.chat
import SectionView
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Share
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.text.font.FontFamily
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.R
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.newchat.QRCode
@Composable
fun VerifyCodeView(
displayName: String,
connectionCode: String?,
connectionVerified: Boolean,
verify: suspend (String?) -> Pair<Boolean, String>?,
close: () -> Unit,
) {
if (connectionCode != null) {
VerifyCodeLayout(
displayName,
connectionCode,
connectionVerified,
verifyCode = { newCode, cb ->
withBGApi {
val res = verify(newCode)
if (res != null) {
val (verified) = res
cb(verified)
if (verified) close()
}
}
}
)
}
}
@Composable
private fun VerifyCodeLayout(
displayName: String,
connectionCode: String,
connectionVerified: Boolean,
verifyCode: (String?, cb: (Boolean) -> Unit) -> Unit,
) {
Column(
Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(horizontal = DEFAULT_PADDING)
) {
AppBarTitle(stringResource(R.string.security_code), false)
val splitCode = splitToParts(connectionCode, 24)
Row(Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING_HALF), horizontalArrangement = Arrangement.Center) {
if (connectionVerified) {
Icon(Icons.Outlined.VerifiedUser, null, Modifier.padding(end = 4.dp).size(22.dp), tint = HighOrLowlight)
Text(String.format(stringResource(R.string.is_verified), displayName))
} else {
Text(String.format(stringResource(R.string.is_not_verified), displayName))
}
}
SectionView {
QRCode(connectionCode, Modifier.aspectRatio(1f))
}
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Spacer(Modifier.weight(2f))
SelectionContainer(Modifier.padding(vertical = DEFAULT_PADDING_HALF, horizontal = DEFAULT_PADDING_HALF)) {
Text(
splitCode,
fontFamily = FontFamily.Monospace,
fontSize = 18.sp,
maxLines = 20
)
}
val context = LocalContext.current
Box(Modifier.weight(1f)) {
IconButton({ shareText(context, connectionCode) }, Modifier.size(20.dp).align(Alignment.CenterStart)) {
Icon(Icons.Filled.Share, null, tint = MaterialTheme.colors.primary)
}
}
Spacer(Modifier.weight(1f))
}
Text(
generalGetString(R.string.to_verify_compare),
Modifier.padding(bottom = DEFAULT_PADDING)
)
Row(
Modifier.padding(bottom = DEFAULT_PADDING).align(Alignment.CenterHorizontally),
horizontalArrangement = Arrangement.spacedBy(10.dp)
) {
if (connectionVerified) {
SimpleButton(generalGetString(R.string.clear_verification), Icons.Outlined.Shield) {
verifyCode(null) {}
}
} else {
SimpleButton(generalGetString(R.string.scan_code), Icons.Outlined.QrCode) {
ModalManager.shared.showModal {
ScanCodeView(verifyCode) { }
}
}
SimpleButton(generalGetString(R.string.mark_code_verified), Icons.Outlined.VerifiedUser) {
verifyCode(connectionCode) { verified ->
if (!verified) {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.incorrect_code)
)
}
}
}
}
}
}
}
private fun splitToParts(s: String, length: Int): String {
if (length >= s.length) return s
return (0..(s.length - 1) / length)
.map { s.drop(it * length).take(length) }
.joinToString(separator = "\n")
}

View File

@@ -0,0 +1,335 @@
package chat.simplex.app.views.chat.group
import SectionCustomFooter
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.filled.CheckCircle
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.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
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 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.ChatInfoToolbarTitle
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.usersettings.SettingsActionItem
@Composable
fun AddGroupMembersView(groupInfo: GroupInfo, creatingGroup: Boolean = false, chatModel: ChatModel, close: () -> Unit) {
val selectedContacts = remember { mutableStateListOf<Long>() }
val selectedRole = remember { mutableStateOf(GroupMemberRole.Member) }
var allowModifyMembers by remember { mutableStateOf(true) }
BackHandler(onBack = close)
AddGroupMembersLayout(
groupInfo = groupInfo,
creatingGroup = creatingGroup,
contactsToAdd = getContactsToAdd(chatModel),
selectedContacts = selectedContacts,
selectedRole = selectedRole,
allowModifyMembers = allowModifyMembers,
openPreferences = {
ModalManager.shared.showCustomModal { close ->
GroupPreferencesView(chatModel, groupInfo.id, close)
}
},
inviteMembers = {
allowModifyMembers = false
withApi {
for (contactId in selectedContacts) {
val member = chatModel.controller.apiAddMember(groupInfo.groupId, contactId, selectedRole.value)
if (member != null) {
chatModel.upsertGroupMember(groupInfo, member)
} else {
break
}
}
close.invoke()
}
},
clearSelection = { selectedContacts.clear() },
addContact = { contactId -> if (contactId !in selectedContacts) selectedContacts.add(contactId) },
removeContact = { contactId -> selectedContacts.removeIf { it == contactId } },
close = close,
)
}
fun getContactsToAdd(chatModel: ChatModel): List<Contact> {
val memberContactIds = chatModel.groupMembers
.filter { it.memberCurrent }
.mapNotNull { it.memberContactId }
return chatModel.chats
.asSequence()
.map { it.chatInfo }
.filterIsInstance<ChatInfo.Direct>()
.map { it.contact }
.filter { it.contactId !in memberContactIds }
.sortedBy { it.displayName.lowercase() }
.toList()
}
@Composable
fun AddGroupMembersLayout(
groupInfo: GroupInfo,
creatingGroup: Boolean,
contactsToAdd: List<Contact>,
selectedContacts: List<Long>,
selectedRole: MutableState<GroupMemberRole>,
allowModifyMembers: Boolean,
openPreferences: () -> Unit,
inviteMembers: () -> Unit,
clearSelection: () -> Unit,
addContact: (Long) -> Unit,
removeContact: (Long) -> Unit,
close: () -> Unit,
) {
Column(
Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.Start,
) {
AppBarTitle(stringResource(R.string.button_add_members))
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
ChatInfoToolbarTitle(
ChatInfo.Group(groupInfo),
imageSize = 60.dp,
iconColor = if (isInDarkTheme()) GroupDark else SettingsSecondaryLight
)
}
SectionSpacer()
if (contactsToAdd.isEmpty()) {
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
Text(
stringResource(R.string.no_contacts_to_add),
Modifier.padding(),
color = HighOrLowlight
)
}
} else {
SectionView {
if (creatingGroup) {
SectionItemView(openPreferences) {
Text(stringResource(R.string.set_group_preferences))
}
SectionDivider()
}
SectionItemView {
RoleSelectionRow(groupInfo, selectedRole, allowModifyMembers)
}
SectionDivider()
if (creatingGroup && selectedContacts.isEmpty()) {
SkipInvitingButton(close)
} else {
InviteMembersButton(inviteMembers, disabled = selectedContacts.isEmpty() || !allowModifyMembers)
}
}
SectionCustomFooter {
InviteSectionFooter(selectedContactsCount = selectedContacts.size, allowModifyMembers, clearSelection)
}
SectionSpacer()
SectionView(stringResource(R.string.select_contacts)) {
ContactList(contacts = contactsToAdd, selectedContacts, groupInfo, allowModifyMembers, addContact, removeContact)
}
SectionSpacer()
}
}
}
@Composable
private fun RoleSelectionRow(groupInfo: GroupInfo, selectedRole: MutableState<GroupMemberRole>, enabled: Boolean) {
Row(
Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
val values = GroupMemberRole.values().filter { it <= groupInfo.membership.memberRole }.map { it to it.text }
ExposedDropDownSettingRow(
generalGetString(R.string.new_member_role),
values,
selectedRole,
icon = null,
enabled = rememberUpdatedState(enabled),
onSelected = { selectedRole.value = it }
)
}
}
@Composable
fun InviteMembersButton(onClick: () -> Unit, disabled: Boolean) {
SettingsActionItem(
Icons.Outlined.Check,
stringResource(R.string.invite_to_group_button),
click = onClick,
textColor = MaterialTheme.colors.primary,
iconColor = MaterialTheme.colors.primary,
disabled = disabled,
)
}
@Composable
fun SkipInvitingButton(onClick: () -> Unit) {
SettingsActionItem(
Icons.Outlined.Check,
stringResource(R.string.skip_inviting_button),
click = onClick,
textColor = MaterialTheme.colors.primary,
iconColor = MaterialTheme.colors.primary,
)
}
@Composable
fun InviteSectionFooter(selectedContactsCount: Int, enabled: Boolean, clearSelection: () -> Unit) {
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
if (selectedContactsCount >= 1) {
Text(
String.format(generalGetString(R.string.num_contacts_selected), selectedContactsCount),
color = HighOrLowlight,
fontSize = 12.sp
)
Box(
Modifier.clickable { if (enabled) clearSelection() }
) {
Text(
stringResource(R.string.clear_contacts_selection_button),
color = if (enabled) MaterialTheme.colors.primary else HighOrLowlight,
fontSize = 12.sp
)
}
} else {
Text(
stringResource(R.string.no_contacts_selected),
color = HighOrLowlight,
fontSize = 12.sp
)
}
}
}
@Composable
fun ContactList(
contacts: List<Contact>,
selectedContacts: List<Long>,
groupInfo: GroupInfo,
enabled: Boolean,
addContact: (Long) -> Unit,
removeContact: (Long) -> Unit
) {
Column {
contacts.forEachIndexed { index, contact ->
ContactCheckRow(
contact, groupInfo, addContact, removeContact,
checked = selectedContacts.contains(contact.apiId),
enabled = enabled,
)
if (index < contacts.lastIndex) {
SectionDivider()
}
}
}
}
@Composable
fun ContactCheckRow(
contact: Contact,
groupInfo: GroupInfo,
addContact: (Long) -> Unit,
removeContact: (Long) -> Unit,
checked: Boolean,
enabled: Boolean,
) {
val prohibitedToInviteIncognito = !groupInfo.membership.memberIncognito && contact.contactConnIncognito
val icon: ImageVector
val iconColor: Color
if (prohibitedToInviteIncognito) {
icon = Icons.Filled.TheaterComedy
iconColor = HighOrLowlight
} else if (checked) {
icon = Icons.Filled.CheckCircle
iconColor = if (enabled) MaterialTheme.colors.primary else HighOrLowlight
} else {
icon = Icons.Outlined.Circle
iconColor = HighOrLowlight
}
SectionItemView(
click = if (enabled) {
{
if (prohibitedToInviteIncognito) {
showProhibitedToInviteIncognitoAlertDialog()
} else if (!checked)
addContact(contact.apiId)
else
removeContact(contact.apiId)
}
} else null
) {
ProfileImage(size = 36.dp, contact.image)
Spacer(Modifier.width(DEFAULT_SPACE_AFTER_ICON))
Text(
contact.chatViewName, maxLines = 1, overflow = TextOverflow.Ellipsis,
color = if (prohibitedToInviteIncognito) HighOrLowlight else Color.Unspecified
)
Spacer(Modifier.fillMaxWidth().weight(1f))
Icon(
icon,
contentDescription = stringResource(R.string.icon_descr_contact_checked),
tint = iconColor
)
}
}
fun showProhibitedToInviteIncognitoAlertDialog() {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.invite_prohibited),
text = generalGetString(R.string.invite_prohibited_description),
confirmText = generalGetString(R.string.ok),
)
}
@Preview
@Composable
fun PreviewAddGroupMembersLayout() {
SimpleXTheme {
AddGroupMembersLayout(
groupInfo = GroupInfo.sampleData,
creatingGroup = false,
contactsToAdd = listOf(Contact.sampleData, Contact.sampleData, Contact.sampleData),
selectedContacts = remember { mutableStateListOf() },
selectedRole = remember { mutableStateOf(GroupMemberRole.Admin) },
allowModifyMembers = true,
openPreferences = {},
inviteMembers = {},
clearSelection = {},
addContact = {},
removeContact = {},
close = {},
)
}
}

View File

@@ -0,0 +1,466 @@
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

@@ -0,0 +1,189 @@
package chat.simplex.app.views.chat.group
import SectionItemView
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
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.LocalContext
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.*
import chat.simplex.app.views.newchat.QRCode
@Composable
fun GroupLinkView(chatModel: ChatModel, groupInfo: GroupInfo, connReqContact: String?, memberRole: GroupMemberRole?, onGroupLinkUpdated: (Pair<String?, GroupMemberRole?>) -> Unit) {
var groupLink by rememberSaveable { mutableStateOf(connReqContact) }
val groupLinkMemberRole = rememberSaveable { mutableStateOf(memberRole) }
var creatingLink by rememberSaveable { mutableStateOf(false) }
val cxt = LocalContext.current
fun createLink() {
creatingLink = true
withApi {
val link = chatModel.controller.apiCreateGroupLink(groupInfo.groupId)
if (link != null) {
groupLink = link.first
groupLinkMemberRole.value = link.second
onGroupLinkUpdated(groupLink to groupLinkMemberRole.value)
}
creatingLink = false
}
}
LaunchedEffect(Unit) {
if (groupLink == null && !creatingLink) {
createLink()
}
}
GroupLinkLayout(
groupLink = groupLink,
groupInfo,
groupLinkMemberRole,
creatingLink,
createLink = ::createLink,
share = { shareText(cxt, groupLink ?: return@GroupLinkLayout) },
updateLink = {
val role = groupLinkMemberRole.value
if (role != null) {
withBGApi {
val link = chatModel.controller.apiGroupLinkMemberRole(groupInfo.groupId, role)
if (link != null) {
groupLink = link.first
groupLinkMemberRole.value = link.second
onGroupLinkUpdated(groupLink to groupLinkMemberRole.value)
}
}
}
},
deleteLink = {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.delete_link_question),
text = generalGetString(R.string.all_group_members_will_remain_connected),
confirmText = generalGetString(R.string.delete_verb),
onConfirm = {
withApi {
val r = chatModel.controller.apiDeleteGroupLink(groupInfo.groupId)
if (r) {
groupLink = null
onGroupLinkUpdated(null to null)
}
}
}
)
}
)
if (creatingLink) {
ProgressIndicator()
}
}
@Composable
fun GroupLinkLayout(
groupLink: String?,
groupInfo: GroupInfo,
groupLinkMemberRole: MutableState<GroupMemberRole?>,
creatingLink: Boolean,
createLink: () -> Unit,
share: () -> Unit,
updateLink: () -> Unit,
deleteLink: () -> Unit
) {
Column(
Modifier
.verticalScroll(rememberScrollState())
.padding(start = DEFAULT_PADDING, bottom = DEFAULT_BOTTOM_PADDING, end = DEFAULT_PADDING),
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.Top
) {
AppBarTitle(stringResource(R.string.group_link), false)
Text(
stringResource(R.string.you_can_share_group_link_anybody_will_be_able_to_connect),
Modifier.padding(bottom = 12.dp),
lineHeight = 22.sp
)
Column(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceEvenly
) {
if (groupLink == null) {
SimpleButton(stringResource(R.string.button_create_group_link), icon = Icons.Outlined.AddLink, disabled = creatingLink, click = createLink)
} else {
SectionItemView(padding = PaddingValues(bottom = DEFAULT_PADDING)) {
RoleSelectionRow(groupInfo, groupLinkMemberRole)
}
var initialLaunch by remember { mutableStateOf(true) }
LaunchedEffect(groupLinkMemberRole.value) {
if (!initialLaunch) {
updateLink()
}
initialLaunch = false
}
QRCode(groupLink, Modifier.aspectRatio(1f))
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = 10.dp)
) {
SimpleButton(
stringResource(R.string.share_link),
icon = Icons.Outlined.Share,
click = share
)
SimpleButton(
stringResource(R.string.delete_link),
icon = Icons.Outlined.Delete,
color = Color.Red,
click = deleteLink
)
}
}
}
}
}
@Composable
private fun RoleSelectionRow(groupInfo: GroupInfo, selectedRole: MutableState<GroupMemberRole?>, enabled: Boolean = true) {
Row(
Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
val values = listOf(GroupMemberRole.Member, GroupMemberRole.Observer).map { it to it.text }
ExposedDropDownSettingRow(
generalGetString(R.string.initial_member_role),
values,
selectedRole,
icon = null,
enabled = rememberUpdatedState(enabled),
onSelected = { selectedRole.value = it }
)
}
}
@Composable
fun ProgressIndicator() {
Box(
Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator(
Modifier
.padding(horizontal = 2.dp)
.size(30.dp),
color = HighOrLowlight,
strokeWidth = 2.5.dp
)
}
}

View File

@@ -0,0 +1,360 @@
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

@@ -0,0 +1,183 @@
package chat.simplex.app.views.chat.group
import InfoRow
import SectionDivider
import SectionItemView
import SectionSpacer
import SectionTextFooter
import SectionView
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
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 chat.simplex.app.R
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.chat.TimedMessagesTTLPicker
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.usersettings.PreferenceToggleWithIcon
@Composable
fun GroupPreferencesView(m: ChatModel, chatId: String, close: () -> Unit,) {
val groupInfo = remember { derivedStateOf { (m.getChat(chatId)?.chatInfo as? ChatInfo.Group)?.groupInfo } }
val gInfo = groupInfo.value ?: return
var preferences by rememberSaveable(gInfo, stateSaver = serializableSaver()) { mutableStateOf(gInfo.fullGroupPreferences) }
var currentPreferences by rememberSaveable(gInfo, stateSaver = serializableSaver()) { mutableStateOf(preferences) }
fun savePrefs(afterSave: () -> Unit = {}) {
withApi {
val gp = gInfo.groupProfile.copy(groupPreferences = preferences.toGroupPreferences())
val gInfo = m.controller.apiUpdateGroup(gInfo.groupId, gp)
if (gInfo != null) {
m.updateGroup(gInfo)
currentPreferences = preferences
}
afterSave()
}
}
ModalView(
close = {
if (preferences == currentPreferences) close()
else showUnsavedChangesAlert({ savePrefs(close) }, close)
},
background = if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
) {
GroupPreferencesLayout(
preferences,
currentPreferences,
gInfo,
applyPrefs = { prefs ->
preferences = prefs
},
reset = {
preferences = currentPreferences
},
savePrefs = ::savePrefs,
)
}
}
@Composable
private fun GroupPreferencesLayout(
preferences: FullGroupPreferences,
currentPreferences: FullGroupPreferences,
groupInfo: GroupInfo,
applyPrefs: (FullGroupPreferences) -> Unit,
reset: () -> Unit,
savePrefs: () -> Unit,
) {
Column(
Modifier.fillMaxWidth().verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.Start,
) {
AppBarTitle(stringResource(R.string.group_preferences))
val timedMessages = remember(preferences) { mutableStateOf(preferences.timedMessages.enable) }
val onTTLUpdated = { ttl: Int? ->
applyPrefs(preferences.copy(timedMessages = preferences.timedMessages.copy(ttl = ttl ?: 86400)))
}
FeatureSection(GroupFeature.TimedMessages, timedMessages, groupInfo, preferences, onTTLUpdated) { enable ->
if (enable == GroupFeatureEnabled.ON) {
applyPrefs(preferences.copy(timedMessages = TimedMessagesGroupPreference(enable = enable, ttl = preferences.timedMessages.ttl ?: 86400)))
} else {
applyPrefs(preferences.copy(timedMessages = TimedMessagesGroupPreference(enable = enable, ttl = currentPreferences.timedMessages.ttl)))
}
}
SectionSpacer()
val allowDirectMessages = remember(preferences) { mutableStateOf(preferences.directMessages.enable) }
FeatureSection(GroupFeature.DirectMessages, allowDirectMessages, groupInfo, preferences, onTTLUpdated) {
applyPrefs(preferences.copy(directMessages = GroupPreference(enable = it)))
}
SectionSpacer()
val allowFullDeletion = remember(preferences) { mutableStateOf(preferences.fullDelete.enable) }
FeatureSection(GroupFeature.FullDelete, allowFullDeletion, groupInfo, preferences, onTTLUpdated) {
applyPrefs(preferences.copy(fullDelete = GroupPreference(enable = it)))
}
SectionSpacer()
val allowVoice = remember(preferences) { mutableStateOf(preferences.voice.enable) }
FeatureSection(GroupFeature.Voice, allowVoice, groupInfo, preferences, onTTLUpdated) {
applyPrefs(preferences.copy(voice = GroupPreference(enable = it)))
}
if (groupInfo.canEdit) {
SectionSpacer()
ResetSaveButtons(
reset = reset,
save = savePrefs,
disabled = preferences == currentPreferences
)
}
}
}
@Composable
private fun FeatureSection(
feature: GroupFeature,
enableFeature: State<GroupFeatureEnabled>,
groupInfo: GroupInfo,
preferences: FullGroupPreferences,
onTTLUpdated: (Int?) -> Unit,
onSelected: (GroupFeatureEnabled) -> Unit
) {
SectionView {
val on = enableFeature.value == GroupFeatureEnabled.ON
val icon = if (on) feature.iconFilled else feature.icon
val iconTint = if (on) SimplexGreen else HighOrLowlight
val timedOn = feature == GroupFeature.TimedMessages && enableFeature.value == GroupFeatureEnabled.ON
if (groupInfo.canEdit) {
SectionItemView {
PreferenceToggleWithIcon(
feature.text,
icon,
iconTint,
enableFeature.value == GroupFeatureEnabled.ON,
) { checked ->
onSelected(if (checked) GroupFeatureEnabled.ON else GroupFeatureEnabled.OFF)
}
}
if (timedOn) {
SectionDivider()
val ttl = rememberSaveable(preferences.timedMessages) { mutableStateOf(preferences.timedMessages.ttl) }
TimedMessagesTTLPicker(ttl, onTTLUpdated)
}
} else {
InfoRow(
feature.text,
enableFeature.value.text,
icon = icon,
iconTint = iconTint,
)
if (timedOn) {
SectionDivider()
InfoRow(generalGetString(R.string.delete_after), TimedMessagesPreference.ttlText(preferences.timedMessages.ttl))
}
}
}
SectionTextFooter(feature.enableDescription(enableFeature.value, groupInfo.canEdit))
}
@Composable
private fun ResetSaveButtons(reset: () -> Unit, save: () -> Unit, disabled: Boolean) {
SectionView {
SectionItemView(reset, disabled = disabled) {
Text(stringResource(R.string.reset_verb), color = if (disabled) HighOrLowlight else MaterialTheme.colors.primary)
}
SectionDivider()
SectionItemView(save, disabled = disabled) {
Text(stringResource(R.string.save_and_notify_group_members), color = if (disabled) HighOrLowlight else MaterialTheme.colors.primary)
}
}
}
private fun showUnsavedChangesAlert(save: () -> Unit, revert: () -> Unit) {
AlertManager.shared.showAlertDialogStacked(
title = generalGetString(R.string.save_preferences_question),
confirmText = generalGetString(R.string.save_and_notify_group_members),
dismissText = generalGetString(R.string.exit_without_saving),
onConfirm = save,
onDismiss = revert,
)
}

View File

@@ -0,0 +1,182 @@
package chat.simplex.app.views.chat.group
import android.content.res.Configuration
import android.graphics.Bitmap
import android.net.Uri
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.focus.FocusRequester
import androidx.compose.ui.graphics.Color
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.*
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.ProfileNameField
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.isValidDisplayName
import chat.simplex.app.views.usersettings.*
import com.google.accompanist.insets.ProvideWindowInsets
import com.google.accompanist.insets.navigationBarsWithImePadding
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@Composable
fun GroupProfileView(groupInfo: GroupInfo, chatModel: ChatModel, close: () -> Unit) {
GroupProfileLayout(
close = close,
groupProfile = groupInfo.groupProfile,
saveProfile = { p ->
withApi {
val gInfo = chatModel.controller.apiUpdateGroup(groupInfo.groupId, p)
if (gInfo != null) {
chatModel.updateGroup(gInfo)
close.invoke()
}
}
}
)
}
@Composable
fun GroupProfileLayout(
close: () -> Unit,
groupProfile: GroupProfile,
saveProfile: (GroupProfile) -> Unit,
) {
val bottomSheetModalState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)
val displayName = remember { mutableStateOf(groupProfile.displayName) }
val fullName = remember { mutableStateOf(groupProfile.fullName) }
val chosenImage = rememberSaveable { mutableStateOf<Uri?>(null) }
val profileImage = rememberSaveable { mutableStateOf(groupProfile.image) }
val scope = rememberCoroutineScope()
val scrollState = rememberScrollState()
val focusRequester = remember { FocusRequester() }
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
ModalBottomSheetLayout(
scrimColor = Color.Black.copy(alpha = 0.12F),
modifier = Modifier.navigationBarsWithImePadding(),
sheetContent = {
GetImageBottomSheet(
chosenImage,
onImageChange = { bitmap -> profileImage.value = resizeImageToStrSize(cropToSquare(bitmap), maxDataSize = 12500) },
hideBottomSheet = {
scope.launch { bottomSheetModalState.hide() }
})
},
sheetState = bottomSheetModalState,
sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp)
) {
ModalView(close = close) {
Column(
Modifier
.verticalScroll(scrollState)
.padding(horizontal = DEFAULT_PADDING),
horizontalAlignment = Alignment.Start
) {
Text(
stringResource(R.string.group_profile_is_stored_on_members_devices),
Modifier.padding(bottom = 24.dp),
color = MaterialTheme.colors.onBackground,
lineHeight = 22.sp
)
Column(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.Start
) {
Box(
Modifier
.fillMaxWidth()
.padding(bottom = 24.dp),
contentAlignment = Alignment.Center
) {
Box(contentAlignment = Alignment.TopEnd) {
Box(contentAlignment = Alignment.Center) {
ProfileImage(192.dp, profileImage.value)
EditImageButton { scope.launch { bottomSheetModalState.show() } }
}
if (profileImage.value != null) {
DeleteImageButton { profileImage.value = null }
}
}
}
Text(
stringResource(R.string.group_display_name_field),
Modifier.padding(bottom = 3.dp)
)
ProfileNameField(displayName, focusRequester)
val errorText = if (!isValidDisplayName(displayName.value)) stringResource(R.string.display_name_cannot_contain_whitespace) else ""
Text(
errorText,
fontSize = 15.sp,
color = MaterialTheme.colors.error
)
Spacer(Modifier.height(3.dp))
Text(
stringResource(R.string.group_full_name_field),
Modifier.padding(bottom = 5.dp)
)
ProfileNameField(fullName)
Spacer(Modifier.height(16.dp))
Row {
TextButton(stringResource(R.string.cancel_verb)) {
close.invoke()
}
Spacer(Modifier.padding(horizontal = 8.dp))
val enabled = displayName.value.isNotEmpty() && isValidDisplayName(displayName.value)
if (enabled) {
Text(
stringResource(R.string.save_group_profile),
modifier = Modifier.clickable {
saveProfile(groupProfile.copy(
displayName = displayName.value,
fullName = fullName.value,
image = profileImage.value
))
},
color = MaterialTheme.colors.primary
)
} else {
Text(
stringResource(R.string.save_group_profile),
color = HighOrLowlight
)
}
}
}
LaunchedEffect(Unit) {
delay(300)
focusRequester.requestFocus()
}
}
}
}
}
}
@Preview(showBackground = true)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
fun PreviewGroupProfileLayout() {
SimpleXTheme {
GroupProfileLayout(
close = {},
groupProfile = GroupProfile.sampleData,
saveProfile = { _ -> }
)
}
}

View File

@@ -0,0 +1,94 @@
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

@@ -0,0 +1,157 @@
package chat.simplex.app.views.chat.item
import androidx.compose.foundation.layout.*
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PhoneInTalk
import androidx.compose.material.icons.outlined.*
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.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.*
@Composable
fun CICallItemView(cInfo: ChatInfo, cItem: ChatItem, status: CICallStatus, duration: Int, acceptCall: (Contact) -> Unit) {
val sent = cItem.chatDir.sent
Column(
Modifier
.padding(horizontal = 4.dp)
.padding(bottom = 8.dp), horizontalAlignment = Alignment.CenterHorizontally) {
@Composable fun ConnectingCallIcon() = Icon(Icons.Outlined.SettingsPhone, stringResource(R.string.icon_descr_call_connecting), tint = SimplexGreen)
when (status) {
CICallStatus.Pending -> if (sent) {
Icon(Icons.Outlined.Call, stringResource(R.string.icon_descr_call_pending_sent))
} else {
AcceptCallButton(cInfo, acceptCall)
}
CICallStatus.Missed -> Icon(Icons.Outlined.Call, stringResource(R.string.icon_descr_call_missed), tint = Color.Red)
CICallStatus.Rejected -> Icon(Icons.Outlined.CallEnd, stringResource(R.string.icon_descr_call_rejected), tint = Color.Red)
CICallStatus.Accepted -> ConnectingCallIcon()
CICallStatus.Negotiated -> ConnectingCallIcon()
CICallStatus.Progress -> Icon(Icons.Filled.PhoneInTalk, stringResource(R.string.icon_descr_call_progress), tint = SimplexGreen)
CICallStatus.Ended -> Row {
Icon(Icons.Outlined.CallEnd, stringResource(R.string.icon_descr_call_ended), tint = HighOrLowlight, modifier = Modifier.padding(end = 4.dp))
Text(durationText(duration), color = HighOrLowlight)
}
CICallStatus.Error -> {}
}
Text(
cItem.timestampText,
color = HighOrLowlight,
fontSize = 14.sp,
modifier = Modifier.padding(start = 3.dp)
)
}
}
@Composable
fun AcceptCallButton(cInfo: ChatInfo, acceptCall: (Contact) -> Unit) {
if (cInfo is ChatInfo.Direct) {
SimpleButton(stringResource(R.string.answer_call), Icons.Outlined.RingVolume) { acceptCall(cInfo.contact) }
} else {
Icon(Icons.Outlined.RingVolume, stringResource(R.string.answer_call), tint = HighOrLowlight)
}
// if case let .direct(contact) = chatInfo {
// Button {
// if let invitation = m.callInvitations.removeValue(forKey: contact.id) {
// m.activeCallInvitation = nil
// m.activeCall = Call(
// contact: contact,
// callState: .invitationReceived,
// localMedia: invitation.peerMedia,
// sharedKey: invitation.sharedKey
// )
// m.showCallView = true
// m.callCommand = .start(media: invitation.peerMedia, aesKey: invitation.sharedKey, useWorker: true)
// } else {
// AlertManager.shared.showAlertMsg(title: "Call already ended!")
// }
// } label: {
// Label("Answer call", systemImage: "phone.arrow.down.left")
// }
// } else {
// Image(systemName: "phone.arrow.down.left").foregroundColor(.secondary)
// }
}
//struct CICallItemView: View {
// @EnvironmentObject var m: ChatModel
// var chatInfo: ChatInfo
// var chatItem: ChatItem
// var status: CICallStatus
// var duration: Int
//
// var body: some View {
// switch status {
// case .pending:
// if sent {
// Image(systemName: "phone.arrow.up.right").foregroundColor(.secondary)
// } else {
// acceptCallButton()
// }
// case .missed: missedCallIcon(sent).foregroundColor(.red)
// case .rejected: Image(systemName: "phone.down").foregroundColor(.secondary)
// case .accepted: connectingCallIcon()
// case .negotiated: connectingCallIcon()
// case .progress: Image(systemName: "phone.and.waveform.fill").foregroundColor(.green)
// case .ended: endedCallIcon(sent)
// case .error: missedCallIcon(sent).foregroundColor(.orange)
// }
//
// chatItem.timestampText
// .font(.caption)
// .foregroundColor(.secondary)
// .padding(.bottom, 8)
// .padding(.horizontal, 12)
// }
// }
//
// private func missedCallIcon(_ sent: Bool) -> some View {
// Image(systemName: sent ? "phone.arrow.up.right" : "phone.arrow.down.left")
// }
//
// private func connectingCallIcon() -> some View {
// Image(systemName: "phone.connection").foregroundColor(.green)
// }
//
// @ViewBuilder private func endedCallIcon(_ sent: Bool) -> some View {
// HStack {
// Image(systemName: "phone.down")
// Text(CICallStatus.durationText(duration)).foregroundColor(.secondary)
// }
// }
//
//
// @ViewBuilder private func acceptCallButton() -> some View {
// if case let .direct(contact) = chatInfo {
// Button {
// if let invitation = m.callInvitations.removeValue(forKey: contact.id) {
// m.activeCallInvitation = nil
// m.activeCall = Call(
// contact: contact,
// callState: .invitationReceived,
// localMedia: invitation.peerMedia,
// sharedKey: invitation.sharedKey
// )
// m.showCallView = true
// m.callCommand = .start(media: invitation.peerMedia, aesKey: invitation.sharedKey, useWorker: true)
// } else {
// AlertManager.shared.showAlertMsg(title: "Call already ended!")
// }
// } label: {
// Label("Answer call", systemImage: "phone.arrow.down.left")
// }
// } else {
// Image(systemName: "phone.arrow.down.left").foregroundColor(.secondary)
// }
// }
//}

View File

@@ -0,0 +1,34 @@
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

@@ -0,0 +1,65 @@
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

@@ -0,0 +1,54 @@
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.text.*
import androidx.compose.ui.text.font.FontWeight
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.HighOrLowlight
import chat.simplex.app.views.helpers.generalGetString
@Composable
fun CIFeaturePreferenceView(
chatItem: ChatItem,
contact: Contact?,
feature: ChatFeature,
allowed: FeatureAllowed,
acceptFeature: (Contact, ChatFeature, Int?) -> Unit
) {
Row(
Modifier.padding(horizontal = 6.dp, vertical = 6.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
Icon(feature.icon, feature.text, Modifier.size(18.dp), tint = HighOrLowlight)
if (contact != null && allowed != FeatureAllowed.NO && contact.allowsFeature(feature) && !contact.userAllowsFeature(feature)) {
val acceptStyle = SpanStyle(color = MaterialTheme.colors.primary, fontSize = 12.sp)
val setParam = feature == ChatFeature.TimedMessages && contact.mergedPreferences.timedMessages.userPreference.pref.ttl == null
val acceptTextId = if (setParam) R.string.accept_feature_set_1_day else R.string.accept_feature
val param = if (setParam) 86400 else null
val annotatedText = buildAnnotatedString {
withStyle(chatEventStyle) { append(chatItem.content.text + " ") }
withAnnotation(tag = "Accept", annotation = "Accept") {
withStyle(acceptStyle) { append(generalGetString(acceptTextId) + " ") }
}
withStyle(chatEventStyle) { append(chatItem.timestampText) }
}
fun accept(offset: Int): Boolean = annotatedText.getStringAnnotations(tag = "Accept", start = offset, end = offset).isNotEmpty()
ClickableText(
annotatedText,
onClick = { if (accept(it)) { acceptFeature(contact, feature, param) } },
shouldConsumeEvent = ::accept
)
} else {
Text(chatItem.content.text + " " + chatItem.timestampText,
fontSize = 12.sp, fontWeight = FontWeight.Light, color = HighOrLowlight)
}
}
}

View File

@@ -0,0 +1,247 @@
package chat.simplex.app.views.chat.item
import android.widget.Toast
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.InsertDriveFile
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.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.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.helpers.*
import kotlinx.datetime.Clock
@Composable
fun CIFileView(
file: CIFile?,
edited: Boolean,
receiveFile: (Long) -> Unit
) {
val context = LocalContext.current
val saveFileLauncher = rememberSaveFileLauncher(cxt = context, ciFile = file)
@Composable
fun fileIcon(
innerIcon: ImageVector? = null,
color: Color = if (isInDarkTheme()) FileDark else FileLight
) {
Box(
contentAlignment = Alignment.Center
) {
Icon(
Icons.Filled.InsertDriveFile,
stringResource(R.string.icon_descr_file),
Modifier.fillMaxSize(),
tint = color
)
if (innerIcon != null) {
Icon(
innerIcon,
stringResource(R.string.icon_descr_file),
Modifier
.size(32.dp)
.padding(top = 12.dp),
tint = Color.White
)
}
}
}
fun fileSizeValid(): Boolean {
if (file != null) {
return file.fileSize <= getMaxFileSize(file.fileProtocol)
}
return false
}
fun fileAction() {
if (file != null) {
when (file.fileStatus) {
is 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)))
)
}
}
is CIFileStatus.RcvAccepted ->
when (file.fileProtocol) {
FileProtocol.XFTP ->
AlertManager.shared.showAlertMsg(
generalGetString(R.string.waiting_for_file),
generalGetString(R.string.file_will_be_received_when_contact_completes_uploading)
)
FileProtocol.SMP ->
AlertManager.shared.showAlertMsg(
generalGetString(R.string.waiting_for_file),
generalGetString(R.string.file_will_be_received_when_contact_is_online)
)
}
is CIFileStatus.RcvComplete -> {
val filePath = getLoadedFilePath(context, file)
if (filePath != null) {
saveFileLauncher.launch(file.fileName)
} else {
Toast.makeText(context, generalGetString(R.string.file_not_found), Toast.LENGTH_SHORT).show()
}
}
else -> {}
}
}
}
@Composable
fun progressIndicator() {
CircularProgressIndicator(
Modifier.size(32.dp),
color = if (isInDarkTheme()) FileDark else FileLight,
strokeWidth = 3.dp
)
}
@Composable
fun progressCircle(progress: Long, total: Long) {
val angle = 360f * (progress.toDouble() / total.toDouble()).toFloat()
val strokeWidth = with(LocalDensity.current) { 3.dp.toPx() }
val strokeColor = if (isInDarkTheme()) FileDark else FileLight
Surface(
Modifier.drawRingModifier(angle, strokeColor, strokeWidth),
color = Color.Transparent,
shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50))
) {
Box(Modifier.size(32.dp))
}
}
@Composable
fun fileIndicator() {
Box(
Modifier
.size(42.dp)
.clip(RoundedCornerShape(4.dp))
.clickable(onClick = { fileAction() }),
contentAlignment = Alignment.Center
) {
if (file != null) {
when (file.fileStatus) {
is CIFileStatus.SndStored ->
when (file.fileProtocol) {
FileProtocol.XFTP -> progressIndicator()
FileProtocol.SMP -> fileIcon()
}
is CIFileStatus.SndTransfer ->
when (file.fileProtocol) {
FileProtocol.XFTP -> progressCircle(file.fileStatus.sndProgress, file.fileStatus.sndTotal)
FileProtocol.SMP -> progressIndicator()
}
is CIFileStatus.SndComplete -> fileIcon(innerIcon = Icons.Filled.Check)
is CIFileStatus.SndCancelled -> fileIcon(innerIcon = Icons.Outlined.Close)
is CIFileStatus.RcvInvitation ->
if (fileSizeValid())
fileIcon(innerIcon = Icons.Outlined.ArrowDownward, color = MaterialTheme.colors.primary)
else
fileIcon(innerIcon = Icons.Outlined.PriorityHigh, color = WarningOrange)
is CIFileStatus.RcvAccepted -> fileIcon(innerIcon = Icons.Outlined.MoreHoriz)
is CIFileStatus.RcvTransfer ->
if (file.fileProtocol == FileProtocol.XFTP && file.fileStatus.rcvProgress < file.fileStatus.rcvTotal) {
progressCircle(file.fileStatus.rcvProgress, file.fileStatus.rcvTotal)
} else {
progressIndicator()
}
is CIFileStatus.RcvComplete -> fileIcon()
is CIFileStatus.RcvCancelled -> fileIcon(innerIcon = Icons.Outlined.Close)
}
} else {
fileIcon()
}
}
}
Row(
Modifier.padding(top = 4.dp, bottom = 6.dp, start = 6.dp, end = 12.dp),
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.spacedBy(2.dp)
) {
fileIndicator()
val metaReserve = if (edited)
" "
else
" "
if (file != null) {
Column(
horizontalAlignment = Alignment.Start
) {
Text(
file.fileName,
maxLines = 1
)
Text(
formatBytes(file.fileSize) + metaReserve,
color = HighOrLowlight,
fontSize = 14.sp,
maxLines = 1
)
}
} else {
Text(metaReserve)
}
}
}
class ChatItemProvider: PreviewParameterProvider<ChatItem> {
private val sentFile = ChatItem(
chatDir = CIDirection.DirectSnd(),
meta = CIMeta.getSample(1, Clock.System.now(), "", CIStatus.SndSent(), itemEdited = true),
content = CIContent.SndMsgContent(msgContent = MsgContent.MCFile("")),
quotedItem = null,
file = CIFile.getSample(fileStatus = CIFileStatus.SndComplete)
)
private val fileChatItemWtFile = ChatItem(
chatDir = CIDirection.DirectRcv(),
meta = CIMeta.getSample(1, Clock.System.now(), "", CIStatus.RcvRead(), ),
content = CIContent.RcvMsgContent(msgContent = MsgContent.MCFile("")),
quotedItem = null,
file = null
)
override val values = listOf(
sentFile,
ChatItem.getFileMsgContentSample(),
ChatItem.getFileMsgContentSample(fileName = "some_long_file_name_here", fileStatus = CIFileStatus.RcvInvitation),
ChatItem.getFileMsgContentSample(fileStatus = CIFileStatus.RcvAccepted),
ChatItem.getFileMsgContentSample(fileStatus = CIFileStatus.RcvTransfer(rcvProgress = 7, rcvTotal = 10)),
ChatItem.getFileMsgContentSample(fileStatus = CIFileStatus.RcvCancelled),
ChatItem.getFileMsgContentSample(fileSize = 1_000_000_000, fileStatus = CIFileStatus.RcvInvitation),
ChatItem.getFileMsgContentSample(text = "Hello there", fileStatus = CIFileStatus.RcvInvitation),
ChatItem.getFileMsgContentSample(text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", fileStatus = CIFileStatus.RcvInvitation),
fileChatItemWtFile
).asSequence()
}
@Preview
@Composable
fun PreviewCIFileFramedItemView(@PreviewParameter(ChatItemProvider::class) chatItem: ChatItem) {
val showMenu = remember { mutableStateOf(false) }
SimpleXTheme {
FramedItemView(ChatInfo.Direct.sampleData, chatItem, linkMode = SimplexLinkMode.DESCRIPTION, showMenu = showMenu, receiveFile = {})
}
}

View File

@@ -0,0 +1,167 @@
package chat.simplex.app.views.chat.item
import android.content.res.Configuration
import android.util.Log
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.SupervisedUserCircle
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.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.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.helpers.*
@Composable
fun CIGroupInvitationView(
ci: ChatItem,
groupInvitation: CIGroupInvitation,
memberRole: GroupMemberRole,
chatIncognito: Boolean = false,
joinGroup: (Long) -> Unit
) {
val sent = ci.chatDir.sent
val action = !sent && groupInvitation.status == CIGroupInvitationStatus.Pending
@Composable
fun groupInfoView() {
val p = groupInvitation.groupProfile
val iconColor =
if (action) if (chatIncognito) Indigo else MaterialTheme.colors.primary
else if (isInDarkTheme()) FileDark else FileLight
Row(
Modifier
.defaultMinSize(minWidth = 220.dp)
.padding(vertical = 4.dp)
.padding(end = 2.dp)
) {
ProfileImage(size = 60.dp, image = groupInvitation.groupProfile.image, icon = Icons.Filled.SupervisedUserCircle, color = iconColor)
Spacer(Modifier.padding(horizontal = 3.dp))
Column(
Modifier.defaultMinSize(minHeight = 60.dp),
verticalArrangement = Arrangement.Center
) {
Text(p.displayName, style = MaterialTheme.typography.caption, fontWeight = FontWeight.Medium, maxLines = 2, overflow = TextOverflow.Ellipsis)
if (p.fullName != "" && p.displayName != p.fullName) {
Text(p.fullName, maxLines = 2, overflow = TextOverflow.Ellipsis)
}
}
}
}
@Composable
fun groupInvitationText() {
when {
sent -> Text(stringResource(R.string.you_sent_group_invitation))
!sent && groupInvitation.status == CIGroupInvitationStatus.Pending -> Text(stringResource(R.string.you_are_invited_to_group))
!sent && groupInvitation.status == CIGroupInvitationStatus.Accepted -> Text(stringResource(R.string.you_joined_this_group))
!sent && groupInvitation.status == CIGroupInvitationStatus.Rejected -> Text(stringResource(R.string.you_rejected_group_invitation))
!sent && groupInvitation.status == CIGroupInvitationStatus.Expired -> Text(stringResource(R.string.group_invitation_expired))
}
}
Surface(
modifier = if (action) Modifier.clickable(onClick = {
joinGroup(groupInvitation.groupId)
}) else Modifier,
shape = RoundedCornerShape(18.dp),
color = if (sent) SentColorLight else ReceivedColorLight,
) {
Box(
Modifier
.width(IntrinsicSize.Min)
.padding(vertical = 3.dp)
.padding(start = 8.dp, end = 12.dp),
contentAlignment = Alignment.BottomEnd
) {
Column(
Modifier
.defaultMinSize(minWidth = 220.dp)
.padding(bottom = 4.dp),
horizontalAlignment = Alignment.Start
) {
groupInfoView()
Column(Modifier.padding(top = 2.dp, start = 5.dp)) {
Divider(Modifier.fillMaxWidth().padding(bottom = 4.dp))
if (action) {
groupInvitationText()
Text(stringResource(
if (chatIncognito) R.string.group_invitation_tap_to_join_incognito else R.string.group_invitation_tap_to_join),
color = if (chatIncognito) Indigo else MaterialTheme.colors.primary)
} else {
Box(Modifier.padding(end = 48.dp)) {
groupInvitationText()
}
}
}
}
Text(
ci.timestampText,
color = HighOrLowlight,
fontSize = 14.sp,
modifier = Modifier.padding(start = 3.dp)
)
}
}
}
@Preview(showBackground = true)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
name = "Dark Mode"
)
@Composable
fun PendingCIGroupInvitationViewPreview() {
SimpleXTheme {
CIGroupInvitationView(
ci = ChatItem.getGroupInvitationSample(),
groupInvitation = CIGroupInvitation.getSample(),
memberRole = GroupMemberRole.Admin,
joinGroup = {}
)
}
}
@Preview(showBackground = true)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
name = "Dark Mode"
)
@Composable
fun CIGroupInvitationViewAcceptedPreview() {
SimpleXTheme {
CIGroupInvitationView(
ci = ChatItem.getGroupInvitationSample(),
groupInvitation = CIGroupInvitation.getSample(status = CIGroupInvitationStatus.Accepted),
memberRole = GroupMemberRole.Admin,
joinGroup = {}
)
}
}
@Preview(showBackground = true)
@Composable
fun CIGroupInvitationViewLongNamePreview() {
SimpleXTheme {
CIGroupInvitationView(
ci = ChatItem.getGroupInvitationSample(),
groupInvitation = CIGroupInvitation.getSample(
groupProfile = GroupProfile("group_with_a_really_really_really_long_name", "Group With A Really Really Really Long Name"),
status = CIGroupInvitationStatus.Accepted
),
memberRole = GroupMemberRole.Admin,
joinGroup = {}
)
}
}

View File

@@ -0,0 +1,216 @@
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.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)
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

@@ -0,0 +1,46 @@
package chat.simplex.app.views.chat.item
import SectionSpacer
import SectionView
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Share
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.ui.theme.DEFAULT_PADDING
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.usersettings.SettingsActionItem
@Composable
fun CIInvalidJSONView(json: String) {
Row(Modifier
.clickable { ModalManager.shared.showModal(true) { InvalidJSONView(json) } }
.padding(horizontal = 10.dp, vertical = 6.dp)
) {
Text(stringResource(R.string.invalid_data), color = Color.Red, fontStyle = FontStyle.Italic)
}
}
@Composable
fun InvalidJSONView(json: String) {
Column {
Spacer(Modifier.height(DEFAULT_PADDING))
SectionView {
val context = LocalContext.current
SettingsActionItem(Icons.Outlined.Share, generalGetString(R.string.share_verb), click = {
shareText(context, json)
})
}
Column(Modifier.padding(DEFAULT_PADDING).fillMaxWidth().verticalScroll(rememberScrollState())) {
Text(json)
}
}
}

View File

@@ -0,0 +1,190 @@
package chat.simplex.app.views.chat.item
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Circle
import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material.icons.outlined.Timer
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.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.model.*
import chat.simplex.app.ui.theme.HighOrLowlight
import kotlinx.datetime.Clock
@Composable
fun CIMetaView(chatItem: ChatItem, timedMessagesTTL: Int?, metaColor: Color = HighOrLowlight) {
Row(Modifier.padding(start = 3.dp), verticalAlignment = Alignment.CenterVertically) {
if (chatItem.isDeletedContent) {
Text(
chatItem.timestampText,
color = metaColor,
fontSize = 14.sp,
modifier = Modifier.padding(start = 3.dp)
)
} else {
CIMetaText(chatItem.meta, timedMessagesTTL, metaColor)
}
}
}
@Composable
// changing this function requires updating reserveSpaceForMeta
private fun CIMetaText(meta: CIMeta, chatTTL: Int?, color: Color) {
if (meta.itemEdited) {
StatusIconText(Icons.Outlined.Edit, color)
Spacer(Modifier.width(3.dp))
}
if (meta.disappearing) {
StatusIconText(Icons.Outlined.Timer, color)
val ttl = meta.itemTimed?.ttl
if (ttl != chatTTL) {
Text(TimedMessagesPreference.shortTtlText(ttl), color = color, fontSize = 13.sp)
}
Spacer(Modifier.width(4.dp))
}
val statusIcon = meta.statusIcon(MaterialTheme.colors.primary, color)
if (statusIcon != null) {
val (icon, statusColor) = statusIcon
StatusIconText(icon, statusColor)
Spacer(Modifier.width(4.dp))
} else if (!meta.disappearing) {
StatusIconText(Icons.Filled.Circle, Color.Transparent)
Spacer(Modifier.width(4.dp))
}
Text(meta.timestampText, color = color, fontSize = 13.sp, maxLines = 1, overflow = TextOverflow.Ellipsis)
}
// the conditions in this function should match CIMetaText
fun reserveSpaceForMeta(meta: CIMeta, chatTTL: Int?): String {
val iconSpace = " "
var res = ""
if (meta.itemEdited) res += iconSpace
if (meta.itemTimed != null) {
res += iconSpace
val ttl = meta.itemTimed.ttl
if (ttl != chatTTL) {
res += TimedMessagesPreference.shortTtlText(ttl)
}
}
if (meta.statusIcon(HighOrLowlight) != null || !meta.disappearing) {
res += iconSpace
}
return res + meta.timestampText
}
@Composable
private fun StatusIconText(icon: ImageVector, color: Color) {
Icon(icon, null, Modifier.height(12.dp), tint = color)
}
@Preview
@Composable
fun PreviewCIMetaView() {
CIMetaView(
chatItem = ChatItem.getSampleData(
1, CIDirection.DirectSnd(), Clock.System.now(), "hello"
),
null
)
}
@Preview
@Composable
fun PreviewCIMetaViewUnread() {
CIMetaView(
chatItem = ChatItem.getSampleData(
1, CIDirection.DirectSnd(), Clock.System.now(), "hello",
status = CIStatus.RcvNew()
),
null
)
}
@Preview
@Composable
fun PreviewCIMetaViewSendFailed() {
CIMetaView(
chatItem = ChatItem.getSampleData(
1, CIDirection.DirectSnd(), Clock.System.now(), "hello",
status = CIStatus.SndError("CMD SYNTAX")
),
null
)
}
@Preview
@Composable
fun PreviewCIMetaViewSendNoAuth() {
CIMetaView(
chatItem = ChatItem.getSampleData(
1, CIDirection.DirectSnd(), Clock.System.now(), "hello", status = CIStatus.SndErrorAuth()
),
null
)
}
@Preview
@Composable
fun PreviewCIMetaViewSendSent() {
CIMetaView(
chatItem = ChatItem.getSampleData(
1, CIDirection.DirectSnd(), Clock.System.now(), "hello", status = CIStatus.SndSent()
),
null
)
}
@Preview
@Composable
fun PreviewCIMetaViewEdited() {
CIMetaView(
chatItem = ChatItem.getSampleData(
1, CIDirection.DirectSnd(), Clock.System.now(), "hello",
itemEdited = true
),
null
)
}
@Preview
@Composable
fun PreviewCIMetaViewEditedUnread() {
CIMetaView(
chatItem = ChatItem.getSampleData(
1, CIDirection.DirectRcv(), Clock.System.now(), "hello",
itemEdited = true,
status=CIStatus.RcvNew()
),
null
)
}
@Preview
@Composable
fun PreviewCIMetaViewEditedSent() {
CIMetaView(
chatItem = ChatItem.getSampleData(
1, CIDirection.DirectSnd(), Clock.System.now(), "hello",
itemEdited = true,
status=CIStatus.SndSent()
),
null
)
}
@Preview
@Composable
fun PreviewCIMetaViewDeletedContent() {
CIMetaView(
chatItem = ChatItem.getDeletedContentSampleData(),
null
)
}

View File

@@ -0,0 +1,339 @@
package chat.simplex.app.views.chat.item
import android.graphics.Bitmap
import android.graphics.Rect
import android.net.Uri
import androidx.annotation.StringRes
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
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.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.*
import androidx.compose.ui.platform.*
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.*
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.FileProvider
import androidx.core.graphics.drawable.toDrawable
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 com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH
import com.google.android.exoplayer2.ui.StyledPlayerView
import java.io.File
@Composable
fun CIVideoView(
image: String,
duration: Int,
file: CIFile?,
imageProvider: () -> ImageGalleryProvider,
showMenu: MutableState<Boolean>,
receiveFile: (Long) -> Unit
) {
Box(
Modifier.layoutId(CHAT_IMAGE_LAYOUT_ID),
contentAlignment = Alignment.TopEnd
) {
val context = LocalContext.current
val filePath = remember(file) { getLoadedFilePath(SimplexApp.context, file) }
val preview = remember(image) { base64ToBitmap(image) }
if (file != null && filePath != null) {
val uri = remember(filePath) { FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.provider", File(filePath)) }
val view = LocalView.current
VideoView(uri, file, preview, duration * 1000L, showMenu, onClick = {
hideKeyboard(view)
ModalManager.shared.showCustomModal(animated = false) { close ->
ImageFullScreenView(imageProvider, close)
}
})
} else {
Box {
ImageView(preview, showMenu, onClick = {
if (file != null) {
when (file.fileStatus) {
CIFileStatus.RcvInvitation ->
receiveFileIfValidSize(file, receiveFile)
CIFileStatus.RcvAccepted ->
when (file.fileProtocol) {
FileProtocol.XFTP ->
AlertManager.shared.showAlertMsg(
generalGetString(R.string.waiting_for_video),
generalGetString(R.string.video_will_be_received_when_contact_completes_uploading)
)
FileProtocol.SMP ->
AlertManager.shared.showAlertMsg(
generalGetString(R.string.waiting_for_video),
generalGetString(R.string.video_will_be_received_when_contact_is_online)
)
}
CIFileStatus.RcvTransfer(rcvProgress = 7, rcvTotal = 10) -> {} // ?
CIFileStatus.RcvComplete -> {} // ?
CIFileStatus.RcvCancelled -> {} // TODO
else -> {}
}
}
})
if (file != null) {
DurationProgress(file, remember { mutableStateOf(false) }, remember { mutableStateOf(duration * 1000L) }, remember { mutableStateOf(0L) }/*, soundEnabled*/)
}
if (file?.fileStatus is CIFileStatus.RcvInvitation) {
PlayButton(error = false, { showMenu.value = true }) { receiveFileIfValidSize(file, receiveFile) }
}
}
}
loadingIndicator(file)
}
}
@Composable
private fun VideoView(uri: Uri, file: CIFile, defaultPreview: Bitmap, defaultDuration: Long, showMenu: MutableState<Boolean>, onClick: () -> Unit) {
val context = LocalContext.current
val player = remember(uri) { VideoPlayer.getOrCreate(uri, false, defaultPreview, defaultDuration, true, context) }
val videoPlaying = remember(uri.path) { player.videoPlaying }
val progress = remember(uri.path) { player.progress }
val duration = remember(uri.path) { player.duration }
val preview by remember { player.preview }
// val soundEnabled by rememberSaveable(uri.path) { player.soundEnabled }
val brokenVideo by rememberSaveable(uri.path) { player.brokenVideo }
val play = {
player.enableSound(true)
player.play(true)
}
val stop = {
player.enableSound(false)
player.stop()
}
val showPreview = remember { derivedStateOf { !videoPlaying.value || progress.value == 0L } }
DisposableEffect(Unit) {
onDispose {
stop()
}
}
Box {
val windowWidth = LocalWindowWidth()
val width = remember(preview) { if (preview.width * 0.97 <= preview.height) videoViewFullWidth(windowWidth) * 0.75f else 1000.dp }
AndroidView(
factory = { ctx ->
StyledPlayerView(ctx).apply {
useController = false
resizeMode = RESIZE_MODE_FIXED_WIDTH
this.player = player.player
}
},
Modifier
.width(width)
.combinedClickable(
onLongClick = { showMenu.value = true },
onClick = { if (player.player.playWhenReady) stop() else onClick() }
)
)
if (showPreview.value) {
ImageView(preview, showMenu, onClick)
PlayButton(brokenVideo, onLongClick = { showMenu.value = true }, play)
}
DurationProgress(file, videoPlaying, duration, progress/*, soundEnabled*/)
}
}
@Composable
private fun BoxScope.PlayButton(error: Boolean = false, onLongClick: () -> Unit, onClick: () -> Unit) {
Surface(
Modifier.align(Alignment.Center),
color = Color.Black.copy(alpha = 0.25f),
shape = RoundedCornerShape(percent = 50)
) {
Box(
Modifier
.defaultMinSize(minWidth = 40.dp, minHeight = 40.dp)
.combinedClickable(onClick = onClick, onLongClick = onLongClick),
contentAlignment = Alignment.Center
) {
Icon(
imageVector = Icons.Filled.PlayArrow,
contentDescription = null,
Modifier.size(25.dp),
tint = if (error) WarningOrange else Color.White
)
}
}
}
@Composable
private fun DurationProgress(file: CIFile, playing: MutableState<Boolean>, duration: MutableState<Long>, progress: MutableState<Long>/*, soundEnabled: MutableState<Boolean>*/) {
if (duration.value > 0L || progress.value > 0) {
Row {
Box(
Modifier
.padding(DEFAULT_PADDING_HALF)
.background(Color.Black.copy(alpha = 0.35f), RoundedCornerShape(percent = 50))
.padding(vertical = 2.dp, horizontal = 4.dp)
) {
val time = if (progress.value > 0) progress.value else duration.value
val timeStr = durationText((time / 1000).toInt())
val width = if (timeStr.length <= 5) 44 else 50
Text(
timeStr,
Modifier.widthIn(min = with(LocalDensity.current) { width.sp.toDp() }).padding(horizontal = 4.dp),
fontSize = 13.sp,
color = Color.White
)
/*if (!soundEnabled.value) {
Icon(Icons.Outlined.VolumeOff, null,
Modifier.padding(start = 5.dp).size(10.dp),
tint = Color.White
)
}*/
}
if (!playing.value) {
Box(
Modifier
.padding(top = DEFAULT_PADDING_HALF)
.background(Color.Black.copy(alpha = 0.35f), RoundedCornerShape(percent = 50))
.padding(vertical = 2.dp, horizontal = 4.dp)
) {
Text(
formatBytes(file.fileSize),
Modifier.padding(horizontal = 4.dp),
fontSize = 13.sp,
color = Color.White
)
}
}
}
}
}
@Composable
private fun ImageView(preview: Bitmap, showMenu: MutableState<Boolean>, onClick: () -> Unit) {
val windowWidth = LocalWindowWidth()
val width = remember(preview) { if (preview.width * 0.97 <= preview.height) videoViewFullWidth(windowWidth) * 0.75f else 1000.dp }
Image(
preview.asImageBitmap(),
contentDescription = stringResource(R.string.video_descr),
modifier = Modifier
.width(width)
.combinedClickable(
onLongClick = { showMenu.value = true },
onClick = onClick
),
contentScale = ContentScale.FillWidth,
)
}
@Composable
private fun LocalWindowWidth(): Dp {
val view = LocalView.current
val density = LocalDensity.current.density
return remember {
val rect = Rect()
view.getWindowVisibleDisplayFrame(rect)
(rect.width() / density).dp
}
}
@Composable
private fun progressIndicator() {
CircularProgressIndicator(
Modifier.size(16.dp),
color = Color.White,
strokeWidth = 2.dp
)
}
@Composable
private fun fileIcon(icon: ImageVector, @StringRes stringId: Int) {
Icon(
icon,
stringResource(stringId),
Modifier.fillMaxSize(),
tint = Color.White
)
}
@Composable
private fun progressCircle(progress: Long, total: Long) {
val angle = 360f * (progress.toDouble() / total.toDouble()).toFloat()
val strokeWidth = with(LocalDensity.current) { 2.dp.toPx() }
val strokeColor = Color.White
Surface(
Modifier.drawRingModifier(angle, strokeColor, strokeWidth),
color = Color.Transparent,
shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50))
) {
Box(Modifier.size(16.dp))
}
}
@Composable
private fun loadingIndicator(file: CIFile?) {
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 ->
when (file.fileProtocol) {
FileProtocol.XFTP -> progressCircle(file.fileStatus.sndProgress, file.fileStatus.sndTotal)
FileProtocol.SMP -> progressIndicator()
}
is CIFileStatus.SndComplete -> fileIcon(Icons.Filled.Check, R.string.icon_descr_video_snd_complete)
is CIFileStatus.SndCancelled -> fileIcon(Icons.Outlined.Close, R.string.icon_descr_file)
is CIFileStatus.RcvInvitation -> fileIcon(Icons.Outlined.ArrowDownward, R.string.icon_descr_video_asked_to_receive)
is CIFileStatus.RcvAccepted -> fileIcon(Icons.Outlined.MoreHoriz, R.string.icon_descr_waiting_for_video)
is CIFileStatus.RcvTransfer ->
if (file.fileProtocol == FileProtocol.XFTP && file.fileStatus.rcvProgress < file.fileStatus.rcvTotal) {
progressCircle(file.fileStatus.rcvProgress, file.fileStatus.rcvTotal)
} else {
progressIndicator()
}
is CIFileStatus.RcvCancelled -> fileIcon(Icons.Outlined.Close, R.string.icon_descr_file)
else -> {}
}
}
}
}
private fun fileSizeValid(file: CIFile?): Boolean {
if (file != null) {
return file.fileSize <= getMaxFileSize(file.fileProtocol)
}
return false
}
private fun receiveFileIfValidSize(file: CIFile, receiveFile: (Long) -> Unit) {
if (fileSizeValid(file)) {
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)))
)
}
}
private fun videoViewFullWidth(windowWidth: Dp): Dp {
val approximatePadding = 100.dp
return minOf(1000.dp, windowWidth - approximatePadding)
}

View File

@@ -0,0 +1,260 @@
package chat.simplex.app.views.chat.item
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
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.clip
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.*
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.*
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.*
// TODO refactor https://github.com/simplex-chat/simplex-chat/pull/1451#discussion_r1033429901
@Composable
fun CIVoiceView(
providedDurationSec: Int,
file: CIFile?,
edited: Boolean,
sent: Boolean,
hasText: Boolean,
ci: ChatItem,
timedMessagesTTL: Int?,
longClick: () -> Unit,
) {
Row(
Modifier.padding(top = if (hasText) 14.dp else 4.dp, bottom = if (hasText) 14.dp else 6.dp, start = 6.dp, end = 6.dp),
verticalAlignment = Alignment.CenterVertically
) {
if (file != null) {
val context = LocalContext.current
val filePath = remember(file.filePath, file.fileStatus) { getLoadedFilePath(context, file) }
var brokenAudio by rememberSaveable(file.filePath) { mutableStateOf(false) }
val audioPlaying = rememberSaveable(file.filePath) { mutableStateOf(false) }
val progress = rememberSaveable(file.filePath) { mutableStateOf(0) }
val duration = rememberSaveable(file.filePath) { mutableStateOf(providedDurationSec * 1000) }
val play = {
AudioPlayer.play(filePath, audioPlaying, progress, duration, true)
brokenAudio = !audioPlaying.value
}
val pause = {
AudioPlayer.pause(audioPlaying, progress)
}
val text = remember {
derivedStateOf {
val time = when {
audioPlaying.value || progress.value != 0 -> progress.value
else -> duration.value
}
durationText(time / 1000)
}
}
VoiceLayout(file, ci, text, audioPlaying, progress, duration, brokenAudio, sent, hasText, timedMessagesTTL, play, pause, longClick)
} else {
VoiceMsgIndicator(null, false, sent, hasText, null, null, false, {}, {}, longClick)
val metaReserve = if (edited)
" "
else
" "
Text(metaReserve)
}
}
}
@Composable
private fun VoiceLayout(
file: CIFile,
ci: ChatItem,
text: State<String>,
audioPlaying: State<Boolean>,
progress: State<Int>,
duration: State<Int>,
brokenAudio: Boolean,
sent: Boolean,
hasText: Boolean,
timedMessagesTTL: Int?,
play: () -> Unit,
pause: () -> Unit,
longClick: () -> Unit
) {
when {
hasText -> {
Spacer(Modifier.width(6.dp))
VoiceMsgIndicator(file, audioPlaying.value, sent, hasText, progress, duration, brokenAudio, play, pause, longClick)
DurationText(text, PaddingValues(start = 12.dp))
}
sent -> {
Row {
Row(verticalAlignment = Alignment.CenterVertically) {
Spacer(Modifier.height(56.dp))
DurationText(text, PaddingValues(end = 12.dp))
}
Column {
VoiceMsgIndicator(file, audioPlaying.value, sent, hasText, progress, duration, brokenAudio, play, pause, longClick)
Box(Modifier.align(Alignment.CenterHorizontally).padding(top = 6.dp)) {
CIMetaView(ci, timedMessagesTTL)
}
}
}
}
else -> {
Row {
Column {
VoiceMsgIndicator(file, audioPlaying.value, sent, hasText, progress, duration, brokenAudio, play, pause, longClick)
Box(Modifier.align(Alignment.CenterHorizontally).padding(top = 6.dp)) {
CIMetaView(ci, timedMessagesTTL)
}
}
Row(verticalAlignment = Alignment.CenterVertically) {
DurationText(text, PaddingValues(start = 12.dp))
Spacer(Modifier.height(56.dp))
}
}
}
}
}
@Composable
private fun DurationText(text: State<String>, padding: PaddingValues) {
val minWidth = with(LocalDensity.current) { 45.sp.toDp() }
Text(
text.value,
Modifier
.padding(padding)
.widthIn(min = minWidth),
color = HighOrLowlight,
fontSize = 16.sp,
maxLines = 1
)
}
@Composable
private fun PlayPauseButton(
audioPlaying: Boolean,
sent: Boolean,
angle: Float,
strokeWidth: Float,
strokeColor: Color,
enabled: Boolean,
error: Boolean,
play: () -> Unit,
pause: () -> Unit,
longClick: () -> Unit
) {
Surface(
Modifier.drawRingModifier(angle, strokeColor, strokeWidth),
color = if (sent) SentColorLight else ReceivedColorLight,
shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50))
) {
Box(
Modifier
.defaultMinSize(minWidth = 56.dp, minHeight = 56.dp)
.combinedClickable(
onClick = { if (!audioPlaying) play() else pause() },
onLongClick = longClick
),
contentAlignment = Alignment.Center
) {
Icon(
imageVector = if (audioPlaying) Icons.Filled.Pause else Icons.Filled.PlayArrow,
contentDescription = null,
Modifier.size(36.dp),
tint = if (error) WarningOrange else if (!enabled) HighOrLowlight else MaterialTheme.colors.primary
)
}
}
}
@Composable
private fun VoiceMsgIndicator(
file: CIFile?,
audioPlaying: Boolean,
sent: Boolean,
hasText: Boolean,
progress: State<Int>?,
duration: State<Int>?,
error: Boolean,
play: () -> Unit,
pause: () -> Unit,
longClick: () -> Unit
) {
val strokeWidth = with(LocalDensity.current) { 3.dp.toPx() }
val strokeColor = MaterialTheme.colors.primary
if (file != null && file.loaded && progress != null && duration != null) {
val angle = 360f * (progress.value.toDouble() / duration.value).toFloat()
if (hasText) {
IconButton({ if (!audioPlaying) play() else pause() }, Modifier.size(56.dp).drawRingModifier(angle, strokeColor, strokeWidth)) {
Icon(
imageVector = if (audioPlaying) Icons.Filled.Pause else Icons.Filled.PlayArrow,
contentDescription = null,
Modifier.size(36.dp),
tint = MaterialTheme.colors.primary
)
}
} else {
PlayPauseButton(audioPlaying, sent, angle, strokeWidth, strokeColor, true, error, play, pause, longClick = longClick)
}
} else {
if (file?.fileStatus is CIFileStatus.RcvInvitation
|| file?.fileStatus is CIFileStatus.RcvTransfer
|| file?.fileStatus is CIFileStatus.RcvAccepted
) {
Box(
Modifier
.size(56.dp)
.clip(RoundedCornerShape(4.dp)),
contentAlignment = Alignment.Center
) {
ProgressIndicator()
}
} else {
PlayPauseButton(audioPlaying, sent, 0f, strokeWidth, strokeColor, false, false, {}, {}, longClick)
}
}
}
fun Modifier.drawRingModifier(angle: Float, color: Color, strokeWidth: Float) = drawWithCache {
val brush = Brush.linearGradient(
0f to Color.Transparent,
0f to color,
start = Offset(0f, 0f),
end = Offset(strokeWidth, strokeWidth),
tileMode = TileMode.Clamp
)
onDrawWithContent {
drawContent()
drawArc(
brush = brush,
startAngle = -90f,
sweepAngle = angle,
useCenter = false,
topLeft = Offset(strokeWidth / 2, strokeWidth / 2),
size = Size(size.width - strokeWidth, size.height - strokeWidth),
style = Stroke(width = strokeWidth, cap = StrokeCap.Square)
)
}
}
@Composable
private fun ProgressIndicator() {
CircularProgressIndicator(
Modifier.size(32.dp),
color = if (isInDarkTheme()) FileDark else FileLight,
strokeWidth = 4.dp
)
}

View File

@@ -0,0 +1,446 @@
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.HighOrLowlight
import chat.simplex.app.ui.theme.SimpleXTheme
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() {
DropdownMenu(
expanded = showMenu.value,
onDismissRequest = { showMenu.value = false },
Modifier.width(220.dp)
) {
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.cancellable) {
CancelFileItemAction(cItem.file.fileId, showMenu, cancelFile = cancelFile)
}
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() {
DropdownMenu(
expanded = showMenu.value,
onDismissRequest = { showMenu.value = false },
Modifier.width(220.dp)
) {
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)
DropdownMenu(
expanded = showMenu.value,
onDismissRequest = { showMenu.value = false },
Modifier.width(220.dp)
) {
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(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
) {
ItemAction(
stringResource(R.string.cancel_verb),
Icons.Outlined.Close,
onClick = {
showMenu.value = false
cancelFileAlertDialog(fileId, cancelFile = cancelFile)
},
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 = MaterialTheme.colors.onBackground) {
DropdownMenuItem(onClick) {
Row {
Text(
text,
modifier = Modifier
.fillMaxWidth()
.weight(1F)
.padding(end = 15.dp),
color = color
)
Icon(icon, text, tint = color)
}
}
}
fun cancelFileAlertDialog(fileId: Long, cancelFile: (Long) -> Unit) {
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.cancel_file__question),
text = generalGetString(R.string.file_transfer_will_be_cancelled_warning),
confirmText = generalGetString(R.string.confirm_verb),
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.End,
) {
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

@@ -0,0 +1,56 @@
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.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 DeletedItemView(ci: ChatItem, timedMessagesTTL: Int?, showMember: Boolean = false) {
val sent = ci.chatDir.sent
Surface(
shape = RoundedCornerShape(18.dp),
color = if (sent) SentColorLight else ReceivedColorLight,
) {
Row(
Modifier.padding(horizontal = 12.dp, vertical = 6.dp),
verticalAlignment = Alignment.Bottom
) {
Text(
buildAnnotatedString {
appendSender(this, if (showMember) ci.memberDisplayName else null, true)
withStyle(SpanStyle(fontStyle = FontStyle.Italic, color = HighOrLowlight)) { append(ci.content.text) }
},
style = MaterialTheme.typography.body1.copy(lineHeight = 22.sp),
modifier = Modifier.padding(end = 8.dp)
)
CIMetaView(ci, timedMessagesTTL)
}
}
}
@Preview(showBackground = true)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
name = "Dark Mode"
)
@Composable
fun PreviewDeletedItemView() {
SimpleXTheme {
DeletedItemView(
ChatItem.getDeletedContentSampleData(),
null
)
}
}

View File

@@ -0,0 +1,49 @@
package chat.simplex.app.views.chat.item
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.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.model.ChatItem
val largeEmojiFont: TextStyle = TextStyle(fontSize = 48.sp)
val mediumEmojiFont: TextStyle = TextStyle(fontSize = 36.sp)
@Composable
fun EmojiItemView(chatItem: ChatItem, timedMessagesTTL: Int?) {
Column(
Modifier.padding(vertical = 8.dp, horizontal = 12.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
EmojiText(chatItem.content.text)
CIMetaView(chatItem, timedMessagesTTL)
}
}
@Composable
fun EmojiText(text: String) {
val s = text.trim()
Text(s, style = if (s.codePoints().count() < 4) largeEmojiFont else mediumEmojiFont)
}
// https://stackoverflow.com/a/46279500
private const val emojiStr = "^(" +
"(?:[\\u2700-\\u27bf]|" +
"(?:[\\ud83c\\udde6-\\ud83c\\uddff]){2}|" +
"[\\ud800\\udc00-\\uDBFF\\uDFFF]|[\\u2600-\\u26FF])[\\ufe0e\\ufe0f]?(?:[\\u0300-\\u036f\\ufe20-\\ufe23\\u20d0-\\u20f0]|[\\ud83c\\udffb-\\ud83c\\udfff])?" +
"(?:\\u200d(?:[^\\ud800-\\udfff]|" +
"(?:[\\ud83c\\udde6-\\ud83c\\uddff]){2}|" +
"[\\ud800\\udc00-\\uDBFF\\uDFFF]|[\\u2600-\\u26FF])[\\ufe0e\\ufe0f]?(?:[\\u0300-\\u036f\\ufe20-\\ufe23\\u20d0-\\u20f0]|[\\ud83c\\udffb-\\ud83c\\udfff])?)*|" +
"[\\u0023-\\u0039]\\ufe0f?\\u20e3|\\u3299|\\u3297|\\u303d|\\u3030|\\u24c2|[\\ud83c\\udd70-\\ud83c\\udd71]|[\\ud83c\\udd7e-\\ud83c\\udd7f]|\\ud83c\\udd8e|[\\ud83c\\udd91-\\ud83c\\udd9a]|[\\ud83c\\udde6-\\ud83c\\uddff]|[\\ud83c\\ude01-\\ud83c\\ude02]|\\ud83c\\ude1a|\\ud83c\\ude2f|[\\ud83c\\ude32-\\ud83c\\ude3a]|[\\ud83c\\ude50-\\ud83c\\ude51]|\\u203c|\\u2049|[\\u25aa-\\u25ab]|\\u25b6|\\u25c0|[\\u25fb-\\u25fe]|\\u00a9|\\u00ae|\\u2122|\\u2139|\\ud83c\\udc04|[\\u2600-\\u26FF]|\\u2b05|\\u2b06|\\u2b07|\\u2b1b|\\u2b1c|\\u2b50|\\u2b55|\\u231a|\\u231b|\\u2328|\\u23cf|[\\u23e9-\\u23f3]|[\\u23f8-\\u23fa]|\\ud83c\\udccf|\\u2934|\\u2935|[\\u2190-\\u21ff]" +
")+$" // Multiple matches with emojis where one follows another without interruptions from other characters
private val emojiRegex = Regex(emojiStr)
fun isShortEmoji(str: String): Boolean {
val s = str.trim()
return s.codePoints().count() in 1..5 && emojiRegex.matches(str)
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,218 @@
package chat.simplex.app.views.chat.item
import android.graphics.Bitmap
import android.net.Uri
import android.os.Build
import android.view.View
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.*
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
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.graphics.*
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.input.pointer.*
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.*
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.*
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.view.isVisible
import chat.simplex.app.R
import chat.simplex.app.views.chat.ProviderMedia
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 coil.size.Size
import com.google.accompanist.pager.*
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout
import com.google.android.exoplayer2.ui.StyledPlayerView
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
import kotlin.math.absoluteValue
interface ImageGalleryProvider {
val initialIndex: Int
val totalMediaSize: MutableState<Int>
fun getMedia(index: Int): ProviderMedia?
fun currentPageChanged(index: Int)
fun scrollToStart()
fun onDismiss(index: Int)
}
@OptIn(ExperimentalPagerApi::class)
@Composable
fun ImageFullScreenView(imageProvider: () -> ImageGalleryProvider, close: () -> Unit) {
val provider = remember { imageProvider() }
val pagerState = rememberPagerState(provider.initialIndex)
val goBack = { provider.onDismiss(pagerState.currentPage); close() }
BackHandler(onBack = goBack)
// Pager doesn't ask previous page at initialization step who knows why. By not doing this, prev page is not checked and can be blank,
// which makes this blank page visible for a moment. Prevent it by doing the check ourselves
LaunchedEffect(Unit) {
if (provider.getMedia(provider.initialIndex - 1) == null) {
provider.scrollToStart()
pagerState.scrollToPage(0)
}
}
val scope = rememberCoroutineScope()
val playersToRelease = rememberSaveable { mutableSetOf<Uri>() }
DisposableEffectOnGone(
whenGone = { playersToRelease.forEach { VideoPlayer.release(it, true, true) } }
)
HorizontalPager(count = remember { provider.totalMediaSize }.value, state = pagerState) { index ->
Column(
Modifier
.fillMaxSize()
.background(Color.Black)
.clickable(interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = goBack)
) {
var settledCurrentPage by remember { mutableStateOf(pagerState.currentPage) }
LaunchedEffect(pagerState) {
snapshotFlow {
if (!pagerState.isScrollInProgress) pagerState.currentPage else settledCurrentPage
}.collect {
settledCurrentPage = it
}
}
LaunchedEffect(settledCurrentPage) {
// Make this pager with infinity scrolling with only 3 pages at a time when left and right pages constructs in real time
if (settledCurrentPage != provider.initialIndex)
provider.currentPageChanged(index)
}
val media = provider.getMedia(index)
if (media == null) {
// No such image. Let's shrink total pages size or scroll to start of the list of pages to remove blank page automatically
SideEffect {
scope.launch {
when (settledCurrentPage) {
index - 1 -> provider.totalMediaSize.value = settledCurrentPage + 1
index + 1 -> {
provider.scrollToStart()
pagerState.scrollToPage(0)
}
}
}
}
} else {
var scale by remember { mutableStateOf(1f) }
var translationX by remember { mutableStateOf(0f) }
var translationY by remember { mutableStateOf(0f) }
var viewWidth by remember { mutableStateOf(0) }
var allowTranslate by remember { mutableStateOf(true) }
LaunchedEffect(settledCurrentPage) {
scale = 1f
translationX = 0f
translationY = 0f
}
val modifier = Modifier
.onGloballyPositioned {
viewWidth = it.size.width
}
.graphicsLayer(
scaleX = scale,
scaleY = scale,
translationX = translationX,
translationY = translationY,
)
.pointerInput(Unit) {
detectTransformGestures(
{ allowTranslate },
onGesture = { _, pan, gestureZoom, _ ->
scale = (scale * gestureZoom).coerceIn(1f, 20f)
allowTranslate = viewWidth * (scale - 1f) - ((translationX + pan.x * scale).absoluteValue * 2) > 0
if (scale > 1 && allowTranslate) {
translationX += pan.x * scale
translationY += pan.y * scale
} else if (allowTranslate) {
translationX = 0f
translationY = 0f
}
}
)
}
.fillMaxSize()
if (media is ProviderMedia.Image) {
val (uri: Uri, imageBitmap: Bitmap) = media
// I'm making a new instance of imageLoader here because if I use one instance in multiple places
// after end of composition here a GIF from the first instance will be paused automatically which isn't what I want
val imageLoader = ImageLoader.Builder(LocalContext.current)
.components {
if (Build.VERSION.SDK_INT >= 28) {
add(ImageDecoderDecoder.Factory())
} else {
add(GifDecoder.Factory())
}
}
.build()
Image(
rememberAsyncImagePainter(
ImageRequest.Builder(LocalContext.current).data(data = uri).size(Size.ORIGINAL).build(),
placeholder = BitmapPainter(imageBitmap.asImageBitmap()), // show original image while it's still loading by coil
imageLoader = imageLoader
),
contentDescription = stringResource(R.string.image_descr),
contentScale = ContentScale.Fit,
modifier = modifier,
)
} else if (media is ProviderMedia.Video) {
val preview = remember(media.uri.path) { base64ToBitmap(media.preview) }
VideoView(modifier, media.uri, preview, index == settledCurrentPage)
DisposableEffect(Unit) {
onDispose { playersToRelease.add(media.uri) }
}
}
}
}
}
}
@Composable
private fun VideoView(modifier: Modifier, uri: Uri, defaultPreview: Bitmap, currentPage: Boolean) {
val context = LocalContext.current
val player = remember(uri) { VideoPlayer.getOrCreate(uri, true, defaultPreview, 0L, true, context) }
val isCurrentPage = rememberUpdatedState(currentPage)
val play = {
player.play(true)
}
val stop = {
player.stop()
}
LaunchedEffect(Unit) {
player.enableSound(true)
snapshotFlow { isCurrentPage.value }
.distinctUntilChanged()
.collect { if (it) play() else stop() }
}
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
AndroidView(
factory = { ctx ->
StyledPlayerView(ctx).apply {
resizeMode = if (ctx.resources.configuration.screenWidthDp > ctx.resources.configuration.screenHeightDp) {
AspectRatioFrameLayout.RESIZE_MODE_FIXED_HEIGHT
} else {
AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH
}
setShowPreviousButton(false)
setShowNextButton(false)
setShowSubtitleButton(false)
setShowVrButton(false)
controllerAutoShow = false
findViewById<View>(com.google.android.exoplayer2.R.id.exo_controls_background).setBackgroundColor(Color.Black.copy(alpha = 0.3f).toArgb())
findViewById<View>(com.google.android.exoplayer2.R.id.exo_settings).isVisible = false
this.player = player.player
}
},
modifier
)
}
}

View File

@@ -0,0 +1,66 @@
package chat.simplex.app.views.chat.item
import android.content.res.Configuration
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
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.graphics.Color
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontStyle
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.ChatItem
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.helpers.AlertManager
import chat.simplex.app.views.helpers.generalGetString
@Composable
fun IntegrityErrorItemView(ci: ChatItem, timedMessagesTTL: Int?, showMember: Boolean = false) {
Surface(
Modifier.clickable(onClick = {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.alert_title_skipped_messages),
text = generalGetString(R.string.alert_text_skipped_messages_it_can_happen_when)
)
}),
shape = RoundedCornerShape(18.dp),
color = ReceivedColorLight,
) {
Row(
Modifier.padding(horizontal = 12.dp, vertical = 6.dp),
verticalAlignment = Alignment.Bottom
) {
Text(
buildAnnotatedString {
appendSender(this, if (showMember) ci.memberDisplayName else null, true)
withStyle(SpanStyle(fontStyle = FontStyle.Italic, color = Color.Red)) { append(ci.content.text) }
},
style = MaterialTheme.typography.body1.copy(lineHeight = 22.sp),
modifier = Modifier.padding(end = 8.dp)
)
CIMetaView(ci, timedMessagesTTL)
}
}
}
@Preview(showBackground = true)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
name = "Dark Mode"
)
@Composable
fun IntegrityErrorItemViewPreview() {
SimpleXTheme {
IntegrityErrorItemView(
ChatItem.getDeletedContentSampleData(),
null
)
}
}

View File

@@ -0,0 +1,72 @@
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

@@ -0,0 +1,252 @@
package chat.simplex.app.views.chat.item
import android.app.Activity
import android.content.ActivityNotFoundException
import android.util.Log
import androidx.annotation.IntRange
import androidx.compose.foundation.text.*
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.*
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.*
import androidx.core.text.BidiFormatter
import chat.simplex.app.TAG
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.views.helpers.detectGesture
import kotlinx.coroutines.*
val reserveTimestampStyle = SpanStyle(color = Color.Transparent)
val boldFont = SpanStyle(fontWeight = FontWeight.Medium)
fun appendGroupMember(b: AnnotatedString.Builder, chatItem: ChatItem, groupMemberBold: Boolean) {
if (chatItem.chatDir is CIDirection.GroupRcv) {
val name = chatItem.chatDir.groupMember.memberProfile.displayName
if (groupMemberBold) b.withStyle(boldFont) { append(name) }
else b.append(name)
b.append(": ")
}
}
fun appendSender(b: AnnotatedString.Builder, sender: String?, senderBold: Boolean) {
if (sender != null) {
if (senderBold) b.withStyle(boldFont) { append(sender) }
else b.append(sender)
b.append(": ")
}
}
private val noTyping: AnnotatedString = AnnotatedString(" ")
private val typingIndicators: List<AnnotatedString> = listOf(
typing(FontWeight.Black) + typing() + typing(),
typing(FontWeight.Bold) + typing(FontWeight.Black) + typing(),
typing() + typing(FontWeight.Bold) + typing(FontWeight.Black),
typing() + typing() + typing(FontWeight.Bold)
)
private fun typingIndicator(recent: Boolean, @IntRange (from = 0, to = 4) typingIdx: Int): AnnotatedString = buildAnnotatedString {
pushStyle(SpanStyle(color = HighOrLowlight, fontFamily = FontFamily.Monospace, letterSpacing = (-1).sp))
append(if (recent) typingIndicators[typingIdx] else noTyping)
}
private fun typing(w: FontWeight = FontWeight.Light): AnnotatedString =
AnnotatedString(".", SpanStyle(fontWeight = w))
@Composable
fun MarkdownText (
text: CharSequence,
formattedText: List<FormattedText>? = null,
sender: String? = null,
meta: CIMeta? = null,
chatTTL: Int? = null,
style: TextStyle = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onSurface, lineHeight = 22.sp),
maxLines: Int = Int.MAX_VALUE,
overflow: TextOverflow = TextOverflow.Clip,
uriHandler: UriHandler? = null,
senderBold: Boolean = false,
modifier: Modifier = Modifier,
linkMode: SimplexLinkMode,
inlineContent: Map<String, InlineTextContent>? = null,
onLinkLongClick: (link: String) -> Unit = {}
) {
val textLayoutDirection = remember (text) {
if (BidiFormatter.getInstance().isRtl(text.subSequence(0, kotlin.math.min(50, text.length)))) LayoutDirection.Rtl else LayoutDirection.Ltr
}
val reserve = if (textLayoutDirection != LocalLayoutDirection.current && meta != null) {
"\n"
} else if (meta != null) {
reserveSpaceForMeta(meta, chatTTL)
} else {
" "
}
val scope = rememberCoroutineScope()
CompositionLocalProvider(
LocalLayoutDirection provides if (textLayoutDirection != LocalLayoutDirection.current)
if (LocalLayoutDirection.current == LayoutDirection.Ltr) LayoutDirection.Rtl else LayoutDirection.Ltr
else
LocalLayoutDirection.current
) {
var timer: Job? by remember { mutableStateOf(null) }
var typingIdx by rememberSaveable { mutableStateOf(0) }
fun stopTyping() {
timer?.cancel()
timer = null
}
fun switchTyping() {
if (meta != null && meta.isLive && meta.recent) {
timer = timer ?: scope.launch {
while (isActive) {
typingIdx = (typingIdx + 1) % typingIndicators.size
delay(250)
}
}
} else {
stopTyping()
}
}
if (meta?.isLive == true) {
val activity = LocalContext.current as Activity
LaunchedEffect(meta.recent, meta.isLive) {
switchTyping()
}
DisposableEffect(Unit) {
val orientation = activity.resources.configuration.orientation
onDispose {
if (orientation == activity.resources.configuration.orientation) {
stopTyping()
}
}
}
}
if (formattedText == null) {
val annotatedText = buildAnnotatedString {
appendSender(this, sender, senderBold)
if (text is String) append(text)
else if (text is AnnotatedString) append(text)
if (meta?.isLive == true) {
append(typingIndicator(meta.recent, typingIdx))
}
if (meta != null) withStyle(reserveTimestampStyle) { append(reserve) }
}
Text(annotatedText, style = style, modifier = modifier, maxLines = maxLines, overflow = overflow, inlineContent = inlineContent ?: mapOf())
} else {
var hasLinks = false
val annotatedText = buildAnnotatedString {
appendSender(this, sender, senderBold)
for (ft in formattedText) {
if (ft.format == null) append(ft.text)
else {
val link = ft.link(linkMode)
if (link != null) {
hasLinks = true
val ftStyle = if (ft.format is Format.SimplexLink && !ft.format.trustedUri && linkMode == SimplexLinkMode.BROWSER) {
SpanStyle(color = Color.Red, textDecoration = TextDecoration.Underline)
} else {
ft.format.style
}
withAnnotation(tag = "URL", annotation = link) {
withStyle(ftStyle) { append(ft.viewText(linkMode)) }
}
} else {
withStyle(ft.format.style) { append(ft.text) }
}
}
}
if (meta?.isLive == true) {
append(typingIndicator(meta.recent, typingIdx))
}
// With RTL language set globally links looks bad sometimes, better to add a new line to bo sure everything looks good
/*if (metaText != null && hasLinks && LocalLayoutDirection.current == LayoutDirection.Rtl)
withStyle(reserveTimestampStyle) { append("\n" + metaText) }
else */if (meta != null) withStyle(reserveTimestampStyle) { append(reserve) }
}
if (hasLinks && uriHandler != null) {
ClickableText(annotatedText, style = style, modifier = modifier, maxLines = maxLines, overflow = overflow,
onLongClick = { offset ->
annotatedText.getStringAnnotations(tag = "URL", start = offset, end = offset)
.firstOrNull()?.let { annotation -> onLinkLongClick(annotation.item) }
},
onClick = { offset ->
annotatedText.getStringAnnotations(tag = "URL", start = offset, end = offset)
.firstOrNull()?.let { annotation ->
try {
uriHandler.openUri(annotation.item)
} catch (e: ActivityNotFoundException) {
// It can happen, for example, when you click on a text 0.00001 but don't have any app that can catch
// `tel:` scheme in url installed on a device (no phone app or contacts, maybe)
Log.e(TAG, "Open url: ${e.stackTraceToString()}")
}
}
},
shouldConsumeEvent = { offset ->
annotatedText.getStringAnnotations(tag = "URL", start = offset, end = offset).any()
}
)
} else {
Text(annotatedText, style = style, modifier = modifier, maxLines = maxLines, overflow = overflow)
}
}
}
}
@Composable
fun ClickableText(
text: AnnotatedString,
modifier: Modifier = Modifier,
style: TextStyle = TextStyle.Default,
softWrap: Boolean = true,
overflow: TextOverflow = TextOverflow.Clip,
maxLines: Int = Int.MAX_VALUE,
onTextLayout: (TextLayoutResult) -> Unit = {},
onClick: (Int) -> Unit,
onLongClick: (Int) -> Unit = {},
shouldConsumeEvent: (Int) -> Boolean
) {
val layoutResult = remember { mutableStateOf<TextLayoutResult?>(null) }
val pressIndicator = Modifier.pointerInput(onClick, onLongClick) {
detectGesture(onLongPress = { pos ->
layoutResult.value?.let { layoutResult ->
onLongClick(layoutResult.getOffsetForPosition(pos))
}
}, onPress = { pos ->
layoutResult.value?.let { layoutResult ->
val res = tryAwaitRelease()
if (res) {
onClick(layoutResult.getOffsetForPosition(pos))
}
}
}, shouldConsumeEvent = { pos ->
var consume = false
layoutResult.value?.let { layoutResult ->
consume = shouldConsumeEvent(layoutResult.getOffsetForPosition(pos))
}
consume
}
)
}
BasicText(
text = text,
modifier = modifier.then(pressIndicator),
style = style,
softWrap = softWrap,
overflow = overflow,
maxLines = maxLines,
onTextLayout = {
layoutResult.value = it
onTextLayout(it)
}
)
}

View File

@@ -0,0 +1,104 @@
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

@@ -0,0 +1,707 @@
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>) {
DropdownMenuItem({
markChatUnread(chat, chatModel)
showMenu.value = false
}) {
Row {
Text(
stringResource(R.string.mark_unread),
modifier = Modifier
.fillMaxWidth()
.weight(1F)
.padding(end = 15.dp),
color = MaterialTheme.colors.onBackground
)
Icon(
Icons.Outlined.MarkChatUnread,
stringResource(R.string.mark_unread),
tint = MaterialTheme.colors.onBackground
)
}
}
}
@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.End,
) {
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) {
Box(Modifier.padding(horizontal = 16.dp)) {
DropdownMenu(
expanded = showMenu.value,
onDismissRequest = { showMenu.value = false },
Modifier.width(220.dp)
) {
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

@@ -0,0 +1,310 @@
package chat.simplex.app.views.chatlist
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
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.*
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.capitalize
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.unit.*
import chat.simplex.app.*
import chat.simplex.app.R
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.newchat.NewChatSheet
import chat.simplex.app.views.onboarding.WhatsNewView
import chat.simplex.app.views.onboarding.shouldShowWhatsNew
import chat.simplex.app.views.usersettings.SettingsView
import chat.simplex.app.views.usersettings.simplexTeamUri
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
@Composable
fun ChatListView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, stopped: Boolean) {
val newChatSheetState by rememberSaveable(stateSaver = AnimatedViewState.saver()) { mutableStateOf(MutableStateFlow(AnimatedViewState.GONE)) }
val userPickerState by rememberSaveable(stateSaver = AnimatedViewState.saver()) { mutableStateOf(MutableStateFlow(AnimatedViewState.GONE)) }
val showNewChatSheet = {
newChatSheetState.value = AnimatedViewState.VISIBLE
}
val hideNewChatSheet: (animated: Boolean) -> Unit = { animated ->
if (animated) newChatSheetState.value = AnimatedViewState.HIDING
else newChatSheetState.value = AnimatedViewState.GONE
}
LaunchedEffect(Unit) {
if (shouldShowWhatsNew(chatModel)) {
delay(1000L)
ModalManager.shared.showCustomModal { close -> WhatsNewView(close = close) }
}
}
LaunchedEffect(chatModel.clearOverlays.value) {
if (chatModel.clearOverlays.value && newChatSheetState.value.isVisible()) hideNewChatSheet(false)
}
LaunchedEffect(chatModel.appOpenUrl.value) {
val url = chatModel.appOpenUrl.value
if (url != null) {
chatModel.appOpenUrl.value = null
connectIfOpenedViaUri(url, chatModel)
}
}
var searchInList by rememberSaveable { mutableStateOf("") }
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
val switchingUsers = rememberSaveable { mutableStateOf(false) }
Scaffold(
topBar = { ChatListToolbar(chatModel, scaffoldState.drawerState, userPickerState, stopped) { searchInList = it.trim() } },
scaffoldState = scaffoldState,
drawerContent = { SettingsView(chatModel, setPerformLA) },
floatingActionButton = {
if (searchInList.isEmpty()) {
FloatingActionButton(
onClick = {
if (!stopped) {
if (newChatSheetState.value.isVisible()) hideNewChatSheet(true) else showNewChatSheet()
}
},
elevation = FloatingActionButtonDefaults.elevation(
defaultElevation = 0.dp,
pressedElevation = 0.dp,
hoveredElevation = 0.dp,
focusedElevation = 0.dp,
),
backgroundColor = if (!stopped) MaterialTheme.colors.primary else HighOrLowlight,
contentColor = Color.White
) {
Icon(if (!newChatSheetState.collectAsState().value.isVisible()) Icons.Default.Edit else Icons.Default.Close, stringResource(R.string.add_contact_or_create_group))
}
}
}
) {
Box(Modifier.padding(it)) {
Column(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colors.background)
) {
if (chatModel.chats.isNotEmpty()) {
ChatList(chatModel, search = searchInList)
} else if (!switchingUsers.value) {
Box(Modifier.fillMaxSize()) {
if (!stopped && !newChatSheetState.collectAsState().value.isVisible()) {
OnboardingButtons(showNewChatSheet)
}
Text(stringResource(R.string.you_have_no_chats), Modifier.align(Alignment.Center), color = HighOrLowlight)
}
}
}
}
}
if (searchInList.isEmpty()) {
NewChatSheet(chatModel, newChatSheetState, stopped, hideNewChatSheet)
}
UserPicker(chatModel, userPickerState, switchingUsers) {
scope.launch { if (scaffoldState.drawerState.isOpen) scaffoldState.drawerState.close() else scaffoldState.drawerState.open() }
}
if (switchingUsers.value) {
Box(
Modifier.fillMaxSize().clickable(enabled = false, onClick = {}),
contentAlignment = Alignment.Center
) {
ProgressIndicator()
}
}
}
@Composable
private fun OnboardingButtons(openNewChatSheet: () -> Unit) {
Column(Modifier.fillMaxSize().padding(DEFAULT_PADDING), horizontalAlignment = Alignment.End, verticalArrangement = Arrangement.Bottom) {
val uriHandler = LocalUriHandler.current
ConnectButton(generalGetString(R.string.chat_with_developers)) {
uriHandler.openUriCatching(simplexTeamUri)
}
Spacer(Modifier.height(DEFAULT_PADDING))
ConnectButton(generalGetString(R.string.tap_to_start_new_chat), openNewChatSheet)
val color = MaterialTheme.colors.primary
Canvas(modifier = Modifier.width(40.dp).height(10.dp), onDraw = {
val trianglePath = Path().apply {
moveTo(0.dp.toPx(), 0f)
lineTo(16.dp.toPx(), 0.dp.toPx())
lineTo(8.dp.toPx(), 10.dp.toPx())
lineTo(0.dp.toPx(), 0.dp.toPx())
}
drawPath(
color = color,
path = trianglePath
)
})
Spacer(Modifier.height(62.dp))
}
}
@Composable
private fun ConnectButton(text: String, onClick: () -> Unit) {
Button(
onClick,
shape = RoundedCornerShape(21.dp),
colors = ButtonDefaults.textButtonColors(
backgroundColor = MaterialTheme.colors.primary
),
elevation = null,
contentPadding = PaddingValues(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF),
modifier = Modifier.height(42.dp)
) {
Text(text, color = Color.White)
}
}
@Composable
private fun ChatListToolbar(chatModel: ChatModel, drawerState: DrawerState, userPickerState: MutableStateFlow<AnimatedViewState>, stopped: Boolean, onSearchValueChanged: (String) -> Unit) {
var showSearch by rememberSaveable { mutableStateOf(false) }
val hideSearchOnBack = { onSearchValueChanged(""); showSearch = false }
if (showSearch) {
BackHandler(onBack = hideSearchOnBack)
}
val barButtons = arrayListOf<@Composable RowScope.() -> Unit>()
if (chatModel.chats.size >= 8) {
barButtons.add {
IconButton({ showSearch = true }) {
Icon(Icons.Outlined.Search, stringResource(android.R.string.search_go).capitalize(Locale.current), tint = MaterialTheme.colors.primary)
}
}
}
if (stopped) {
barButtons.add {
IconButton(onClick = {
AlertManager.shared.showAlertMsg(
generalGetString(R.string.chat_is_stopped_indication),
generalGetString(R.string.you_can_start_chat_via_setting_or_by_restarting_the_app)
)
}) {
Icon(
Icons.Filled.Report,
generalGetString(R.string.chat_is_stopped_indication),
tint = Color.Red,
)
}
}
}
val scope = rememberCoroutineScope()
DefaultTopAppBar(
navigationButton = {
if (showSearch) {
NavigationButtonBack(hideSearchOnBack)
} else if (chatModel.users.isEmpty()) {
NavigationButtonMenu { scope.launch { if (drawerState.isOpen) drawerState.close() else drawerState.open() } }
} else {
val users by remember { derivedStateOf { chatModel.users.filter { u -> u.user.activeUser || !u.user.hidden } } }
val allRead = users
.filter { u -> !u.user.activeUser && !u.user.hidden }
.all { u -> u.unreadCount == 0 }
UserProfileButton(chatModel.currentUser.value?.profile?.image, allRead) {
if (users.size == 1) {
scope.launch { drawerState.open() }
} else {
userPickerState.value = AnimatedViewState.VISIBLE
}
}
}
},
title = {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
stringResource(R.string.your_chats),
color = MaterialTheme.colors.onBackground,
fontWeight = FontWeight.SemiBold,
)
if (chatModel.incognito.value) {
Icon(
Icons.Filled.TheaterComedy,
stringResource(R.string.incognito),
tint = Indigo,
modifier = Modifier.padding(10.dp).size(26.dp)
)
}
}
},
onTitleClick = null,
showSearch = showSearch,
onSearchValueChanged = onSearchValueChanged,
buttons = barButtons
)
Divider(Modifier.padding(top = AppBarHeight))
}
@Composable
fun UserProfileButton(image: String?, allRead: Boolean, onButtonClicked: () -> Unit) {
IconButton(onClick = onButtonClicked) {
Box {
ProfileImage(
image = image,
size = 37.dp
)
if (!allRead) {
unreadBadge()
}
}
}
}
@Composable
private fun BoxScope.unreadBadge(text: String? = "") {
Text(
text ?: "",
color = MaterialTheme.colors.onPrimary,
fontSize = 6.sp,
modifier = Modifier
.background(MaterialTheme.colors.primary, shape = CircleShape)
.badgeLayout()
.padding(horizontal = 3.dp)
.padding(vertical = 1.dp)
.align(Alignment.TopEnd)
)
}
@Composable
private fun ProgressIndicator() {
CircularProgressIndicator(
Modifier
.padding(horizontal = 2.dp)
.size(30.dp),
color = HighOrLowlight,
strokeWidth = 2.5.dp
)
}
private var lazyListState = 0 to 0
@Composable
private fun ChatList(chatModel: ChatModel, search: String) {
val filter: (Chat) -> Boolean = { chat: Chat ->
chat.chatInfo.chatViewName.lowercase().contains(search.lowercase())
}
val listState = rememberLazyListState(lazyListState.first, lazyListState.second)
DisposableEffect(Unit) {
onDispose { lazyListState = listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset }
}
val chats by remember(search) { derivedStateOf { if (search.isEmpty()) chatModel.chats else chatModel.chats.filter(filter) } }
LazyColumn(
modifier = Modifier.fillMaxWidth(),
listState
) {
items(chats) { chat ->
ChatListNavLinkView(chat, chatModel)
}
}
}

View File

@@ -0,0 +1,312 @@
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.ImagePreview -> Icons.Outlined.Image to null
is ComposePreview.VoicePreview -> Icons.Filled.PlayArrow to durationText(draft.preview.durationMs / 1000)
else -> null
}
val attachment = attachment()
val text = buildAnnotatedString {
appendInlineContent(id = "editIcon")
append(" ")
if (attachment != null) {
appendInlineContent(id = "attachmentIcon")
if (attachment.second != null) {
append(attachment.second as String)
}
append(" ")
}
append(draft.message)
}
val inlineContent: Map<String, InlineTextContent> = mapOf(
"editIcon" to InlineTextContent(
Placeholder(20.sp, 20.sp, PlaceholderVerticalAlign.TextCenter)
) {
Icon(Icons.Outlined.EditNote, null, tint = MaterialTheme.colors.primary)
},
"attachmentIcon" to InlineTextContent(
Placeholder(20.sp, 20.sp, PlaceholderVerticalAlign.TextCenter)
) {
Icon(attachment?.first ?: Icons.Outlined.EditNote, null, tint = HighOrLowlight)
}
)
return text to inlineContent
}
@Composable
fun chatPreviewTitle() {
when (cInfo) {
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

@@ -0,0 +1,55 @@
package chat.simplex.app.views.chatlist
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.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.ProfileImage
@Composable
fun ContactConnectionView(contactConnection: PendingContactConnection) {
Row {
Box(Modifier.size(72.dp), contentAlignment = Alignment.Center) {
ProfileImage(size = 54.dp, null, if (contactConnection.initiated) Icons.Outlined.AddLink else Icons.Outlined.Link)
}
Column(
modifier = Modifier
.padding(horizontal = 8.dp)
.weight(1F)
) {
Text(
contactConnection.displayName,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.h3,
fontWeight = FontWeight.Bold,
color = HighOrLowlight
)
val height = with(LocalDensity.current) { 46.sp.toDp() }
Text(contactConnection.description, Modifier.heightIn(min = height), maxLines = 2, color = if (isInDarkTheme()) MessagePreviewDark else MessagePreviewLight)
}
val ts = getTimestampText(contactConnection.updatedAt)
Column(
Modifier.fillMaxHeight(),
verticalArrangement = Arrangement.Top
) {
Text(
ts,
color = HighOrLowlight,
style = MaterialTheme.typography.body2,
modifier = Modifier.padding(bottom = 5.dp)
)
}
}
}

View File

@@ -0,0 +1,52 @@
package chat.simplex.app.views.chatlist
import androidx.compose.foundation.layout.*
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
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.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.ChatInfoImage
@Composable
fun ContactRequestView(chatModelIncognito: Boolean, contactRequest: ChatInfo.ContactRequest) {
Row {
ChatInfoImage(contactRequest, size = 72.dp)
Column(
modifier = Modifier
.padding(horizontal = 8.dp)
.weight(1F)
) {
Text(
contactRequest.chatViewName,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.h3,
fontWeight = FontWeight.Bold,
color = if (chatModelIncognito) Indigo else MaterialTheme.colors.primary
)
val height = with(LocalDensity.current) { 46.sp.toDp() }
Text(stringResource(R.string.contact_wants_to_connect_with_you), Modifier.heightIn(min = height), maxLines = 2, color = if (isInDarkTheme()) MessagePreviewDark else MessagePreviewLight)
}
val ts = getTimestampText(contactRequest.contactRequest.updatedAt)
Column(
Modifier.fillMaxHeight(),
verticalArrangement = Arrangement.Top
) {
Text(
ts,
color = HighOrLowlight,
style = MaterialTheme.typography.body2,
modifier = Modifier.padding(bottom = 5.dp)
)
}
}
}

View File

@@ -0,0 +1,66 @@
package chat.simplex.app.views.chatlist
import SectionItemView
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.graphics.Color
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.Indigo
import chat.simplex.app.views.helpers.ProfileImage
@Composable
fun ShareListNavLinkView(chat: Chat, chatModel: ChatModel) {
val stopped = chatModel.chatRunning.value == false
when (chat.chatInfo) {
is ChatInfo.Direct ->
ShareListNavLinkLayout(
chatLinkPreview = { SharePreviewView(chat) },
click = { directChatAction(chat.chatInfo, chatModel) },
stopped
)
is ChatInfo.Group ->
ShareListNavLinkLayout(
chatLinkPreview = { SharePreviewView(chat) },
click = { groupChatAction(chat.chatInfo.groupInfo, chatModel) },
stopped
)
is ChatInfo.ContactRequest, is ChatInfo.ContactConnection, is ChatInfo.InvalidJSON -> {}
}
}
@Composable
private fun ShareListNavLinkLayout(
chatLinkPreview: @Composable () -> Unit,
click: () -> Unit,
stopped: Boolean
) {
SectionItemView(minHeight = 50.dp, click = click, disabled = stopped) {
chatLinkPreview()
}
Divider(Modifier.padding(horizontal = 8.dp))
}
@Composable
private fun SharePreviewView(chat: Chat) {
Row(
Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
ProfileImage(size = 46.dp, chat.chatInfo.image)
Text(
chat.chatInfo.chatViewName, maxLines = 1, overflow = TextOverflow.Ellipsis,
color = if (chat.chatInfo.incognito) Indigo else Color.Unspecified
)
}
}
}

View File

@@ -0,0 +1,159 @@
package chat.simplex.app.views.chatlist
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
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.res.stringResource
import androidx.compose.ui.text.capitalize
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.ui.theme.Indigo
import chat.simplex.app.views.helpers.*
import kotlinx.coroutines.flow.MutableStateFlow
@Composable
fun ShareListView(chatModel: ChatModel, stopped: Boolean) {
var searchInList by rememberSaveable { mutableStateOf("") }
val userPickerState by rememberSaveable(stateSaver = AnimatedViewState.saver()) { mutableStateOf(MutableStateFlow(AnimatedViewState.GONE)) }
val switchingUsers = rememberSaveable { mutableStateOf(false) }
Scaffold(
topBar = { Column { ShareListToolbar(chatModel, userPickerState, stopped) { searchInList = it.trim() } } },
) {
Box(Modifier.padding(it)) {
Column(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colors.background)
) {
if (chatModel.chats.isNotEmpty()) {
ShareList(chatModel, search = searchInList)
} else {
EmptyList()
}
}
}
}
UserPicker(chatModel, userPickerState, switchingUsers, showSettings = false, showCancel = true, cancelClicked = {
chatModel.sharedContent.value = null
})
}
@Composable
private fun EmptyList() {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text(stringResource(R.string.you_have_no_chats), color = HighOrLowlight)
}
}
@Composable
private fun ShareListToolbar(chatModel: ChatModel, userPickerState: MutableStateFlow<AnimatedViewState>, stopped: Boolean, onSearchValueChanged: (String) -> Unit) {
var showSearch by rememberSaveable { mutableStateOf(false) }
val hideSearchOnBack = { onSearchValueChanged(""); showSearch = false }
if (showSearch) {
BackHandler(onBack = hideSearchOnBack)
}
val barButtons = arrayListOf<@Composable RowScope.() -> Unit>()
val users by remember { derivedStateOf { chatModel.users.filter { u -> u.user.activeUser || !u.user.hidden } } }
val navButton: @Composable RowScope.() -> Unit = {
when {
showSearch -> NavigationButtonBack(hideSearchOnBack)
users.size > 1 -> {
val allRead = users
.filter { u -> !u.user.activeUser && !u.user.hidden }
.all { u -> u.unreadCount == 0 }
UserProfileButton(chatModel.currentUser.value?.profile?.image, allRead) {
userPickerState.value = AnimatedViewState.VISIBLE
}
}
else -> NavigationButtonBack { chatModel.sharedContent.value = null }
}
}
if (chatModel.chats.size >= 8) {
barButtons.add {
IconButton({ showSearch = true }) {
Icon(Icons.Outlined.Search, stringResource(android.R.string.search_go).capitalize(Locale.current), tint = MaterialTheme.colors.primary)
}
}
}
if (stopped) {
barButtons.add {
IconButton(onClick = {
AlertManager.shared.showAlertMsg(
generalGetString(R.string.chat_is_stopped_indication),
generalGetString(R.string.you_can_start_chat_via_setting_or_by_restarting_the_app)
)
}) {
Icon(
Icons.Filled.Report,
generalGetString(R.string.chat_is_stopped_indication),
tint = Color.Red,
)
}
}
}
DefaultTopAppBar(
navigationButton = navButton,
title = {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
when (chatModel.sharedContent.value) {
is SharedContent.Text -> stringResource(R.string.share_message)
is SharedContent.Images -> stringResource(R.string.share_image)
is SharedContent.File -> stringResource(R.string.share_file)
else -> stringResource(R.string.share_message)
},
color = MaterialTheme.colors.onBackground,
fontWeight = FontWeight.SemiBold,
)
if (chatModel.incognito.value) {
Icon(
Icons.Filled.TheaterComedy,
stringResource(R.string.incognito),
tint = Indigo,
modifier = Modifier.padding(10.dp).size(26.dp)
)
}
}
},
onTitleClick = null,
showSearch = showSearch,
onSearchValueChanged = onSearchValueChanged,
buttons = barButtons
)
Divider()
}
@Composable
private fun ShareList(chatModel: ChatModel, search: String) {
val filter: (Chat) -> Boolean = { chat: Chat ->
chat.chatInfo.chatViewName.lowercase().contains(search.lowercase())
}
val chats by remember(search) {
derivedStateOf {
if (search.isEmpty()) chatModel.chats.filter { it.chatInfo.ready } else chatModel.chats.filter { it.chatInfo.ready }.filter(filter)
}
}
LazyColumn(
modifier = Modifier.fillMaxWidth()
) {
items(chats) { chat ->
ShareListNavLinkView(chat, chatModel)
}
}
}

View File

@@ -0,0 +1,241 @@
package chat.simplex.app.views.chatlist
import SectionItemViewSpaceBetween
import android.util.Log
import androidx.compose.animation.core.*
import androidx.compose.foundation.*
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.*
import androidx.compose.runtime.*
import androidx.compose.ui.*
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.capitalize
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.*
import chat.simplex.app.R
import chat.simplex.app.TAG
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
@Composable
fun UserPicker(
chatModel: ChatModel,
userPickerState: MutableStateFlow<AnimatedViewState>,
switchingUsers: MutableState<Boolean>,
showSettings: Boolean = true,
showCancel: Boolean = false,
cancelClicked: () -> Unit = {},
settingsClicked: () -> Unit = {},
) {
val scope = rememberCoroutineScope()
var newChat by remember { mutableStateOf(userPickerState.value) }
val users by remember {
derivedStateOf {
chatModel.users
.filter { u -> u.user.activeUser || !u.user.hidden }
.sortedByDescending { it.user.activeUser }
}
}
val animatedFloat = remember { Animatable(if (newChat.isVisible()) 0f else 1f) }
LaunchedEffect(Unit) {
launch {
userPickerState.collect {
newChat = it
launch {
animatedFloat.animateTo(if (newChat.isVisible()) 1f else 0f, newChatSheetAnimSpec())
if (newChat.isHiding()) userPickerState.value = AnimatedViewState.GONE
}
}
}
}
LaunchedEffect(Unit) {
snapshotFlow { newChat.isVisible() }
.distinctUntilChanged()
.filter { it }
.collect {
try {
val updatedUsers = chatModel.controller.listUsers().sortedByDescending { it.user.activeUser }
var same = users.size == updatedUsers.size
if (same) {
for (i in 0 until minOf(users.size, updatedUsers.size)) {
val prev = updatedUsers[i].user
val next = users[i].user
if (prev.userId != next.userId || prev.activeUser != next.activeUser || prev.chatViewName != next.chatViewName || prev.image != next.image) {
same = false
break
}
}
}
if (!same) {
chatModel.users.clear()
chatModel.users.addAll(updatedUsers)
}
} catch (e: Exception) {
Log.e(TAG, "Error updating users ${e.stackTraceToString()}")
}
}
}
val xOffset = with(LocalDensity.current) { 10.dp.roundToPx() }
val maxWidth = with(LocalDensity.current) { LocalConfiguration.current.screenWidthDp * density }
Box(Modifier
.fillMaxSize()
.offset { IntOffset(if (newChat.isGone()) -maxWidth.roundToInt() else xOffset, 0) }
.clickable(interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = { userPickerState.value = AnimatedViewState.HIDING })
.padding(bottom = 10.dp, top = 10.dp)
.graphicsLayer {
alpha = animatedFloat.value
translationY = (animatedFloat.value - 1) * xOffset
}
) {
Column(
Modifier
.widthIn(min = 220.dp)
.width(IntrinsicSize.Min)
.height(IntrinsicSize.Min)
.shadow(8.dp, MaterialTheme.shapes.medium, clip = false)
.background(if (isInDarkTheme()) MaterialTheme.colors.background.darker(-0.7f) else MaterialTheme.colors.background, MaterialTheme.shapes.medium)
) {
Column(Modifier.weight(1f).verticalScroll(rememberScrollState())) {
users.forEach { u ->
UserProfilePickerItem(u.user, u.unreadCount, openSettings = {
settingsClicked()
userPickerState.value = AnimatedViewState.GONE
}) {
userPickerState.value = AnimatedViewState.HIDING
if (!u.user.activeUser) {
scope.launch {
val job = launch {
delay(500)
switchingUsers.value = true
}
chatModel.controller.changeActiveUser(u.user.userId, null)
job.cancel()
switchingUsers.value = false
}
}
}
Divider(Modifier.requiredHeight(1.dp))
if (u.user.activeUser) Divider(Modifier.requiredHeight(0.5.dp))
}
}
if (showSettings) {
SettingsPickerItem {
settingsClicked()
userPickerState.value = AnimatedViewState.GONE
}
}
if (showCancel) {
CancelPickerItem {
cancelClicked()
userPickerState.value = AnimatedViewState.GONE
}
}
}
}
}
@Composable
fun UserProfilePickerItem(u: User, unreadCount: Int = 0, onLongClick: () -> Unit = {}, openSettings: () -> Unit = {}, onClick: () -> Unit) {
Row(
Modifier
.fillMaxWidth()
.sizeIn(minHeight = 46.dp)
.combinedClickable(
onClick = if (u.activeUser) openSettings else onClick,
onLongClick = onLongClick,
interactionSource = remember { MutableInteractionSource() },
indication = if (!u.activeUser) LocalIndication.current else null
)
.padding(PaddingValues(start = 8.dp, end = DEFAULT_PADDING)),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
UserProfileRow(u)
if (u.activeUser) {
Icon(Icons.Filled.Done, null, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
} else if (u.hidden) {
Icon(Icons.Outlined.Lock, null, Modifier.size(20.dp), tint = HighOrLowlight)
} else if (unreadCount > 0) {
Row {
Text(
unreadCountStr(unreadCount),
color = Color.White,
fontSize = 11.sp,
modifier = Modifier
.background(if (u.showNtfs) MaterialTheme.colors.primary else HighOrLowlight, shape = CircleShape)
.sizeIn(minWidth = 20.dp, minHeight = 20.dp)
.padding(horizontal = 3.dp)
.padding(vertical = 1.dp),
textAlign = TextAlign.Center,
maxLines = 1
)
Spacer(Modifier.width(2.dp))
}
} else if (!u.showNtfs) {
Icon(Icons.Outlined.NotificationsOff, null, Modifier.size(20.dp), tint = HighOrLowlight)
} else {
Box(Modifier.size(20.dp))
}
}
}
@Composable
fun UserProfileRow(u: User) {
Row(
Modifier
.widthIn(max = LocalConfiguration.current.screenWidthDp.dp * 0.7f)
.padding(vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
ProfileImage(
image = u.image,
size = 54.dp
)
Text(
u.displayName,
modifier = Modifier
.padding(start = 8.dp, end = 8.dp),
fontWeight = if (u.activeUser) FontWeight.Medium else FontWeight.Normal
)
}
}
@Composable
private fun SettingsPickerItem(onClick: () -> Unit) {
SectionItemViewSpaceBetween(onClick, minHeight = 68.dp) {
val text = generalGetString(R.string.settings_section_title_settings).lowercase().capitalize(Locale.current)
Text(
text,
color = MaterialTheme.colors.onBackground,
)
Icon(Icons.Outlined.Settings, text, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
}
}
@Composable
private fun CancelPickerItem(onClick: () -> Unit) {
SectionItemViewSpaceBetween(onClick, minHeight = 68.dp) {
val text = generalGetString(R.string.cancel_verb)
Text(
text,
color = MaterialTheme.colors.onBackground,
)
Icon(Icons.Outlined.Close, text, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
}
}

Some files were not shown because too many files have changed in this diff Show More