Compare commits

..

90 Commits

Author SHA1 Message Date
Evgeny Poberezkin
bcce6a18a1 Delete README.md 2023-04-30 09:17:02 +01:00
lastimeoceanisfall
3bce760e05 Create audio-video-calls.md (#2254) 2023-04-30 09:14:44 +01:00
lastimeoceanisfall
a7c60f0721 Create app-settings.md (#2253) 2023-04-30 09:13:57 +01:00
lastimeoceanisfall
94ad68155f Create WEBRTC.md (#2251) 2023-04-30 09:13:37 +01:00
lastimeoceanisfall
cb2b5ff97b Create TRANSLATIONS.md (#2250) 2023-04-30 09:13:12 +01:00
lastimeoceanisfall
313c40590f Create SQL.md (#2249) 2023-04-30 09:12:53 +01:00
lastimeoceanisfall
d806efc347 Create SIMPLEX.md (#2248) 2023-04-30 09:12:34 +01:00
lastimeoceanisfall
743db49215 Create SERVER.md (#2247) 2023-04-30 09:12:08 +01:00
lastimeoceanisfall
4fdffdb8aa Create README.md (#2246) 2023-04-30 09:11:46 +01:00
lastimeoceanisfall
1699aae906 Create CLI.md (#2242) 2023-04-30 09:10:25 +01:00
lastimeoceanisfall
82eba49b95 Create chat-profiles.md (#2255) 2023-04-30 09:08:48 +01:00
lastimeoceanisfall
218b1a8b7e Create making-connections.md (#2256) 2023-04-30 09:08:22 +01:00
lastimeoceanisfall
6c1bba55b4 Create managing-data.md (#2257) 2023-04-30 09:08:02 +01:00
lastimeoceanisfall
c9fc1547b0 Create privacy-security.md (#2258) 2023-04-30 09:07:44 +01:00
lastimeoceanisfall
8a3a8455d4 Create secret-groups.md (#2260) 2023-04-30 09:07:15 +01:00
lastimeoceanisfall
9c437f6204 Create README.md (#2259) 2023-04-30 09:06:47 +01:00
lastimeoceanisfall
ef698e58d4 Create send-messages.md (#2261) 2023-04-30 09:06:18 +01:00
lastimeoceanisfall
01c0ac3d13 Create CONTRIBUTING.md (#2241) 2023-04-30 09:03:57 +01:00
lastimeoceanisfall
ed7089902c Create README.md (#2272) 2023-04-30 09:02:29 +01:00
lastimeoceanisfall
1ddd4193a0 Create 20201022-simplex-chat.md (#2274) 2023-04-30 09:01:57 +01:00
lastimeoceanisfall
932875fd9d Create 20210512-simplex-chat-terminal-ui.md (#2275) 2023-04-30 09:01:35 +01:00
lastimeoceanisfall
1899c84ee2 Create 20210914-simplex-chat-v0.4-released.md (#2277) 2023-04-30 09:01:07 +01:00
lastimeoceanisfall
91e03a016f Create 20211208-simplex-chat-v0.5-released.md (#2279) 2023-04-30 09:00:48 +01:00
lastimeoceanisfall
a978c12ca3 Create 20220112-simplex-chat-v1-released.md (#2280) 2023-04-30 09:00:30 +01:00
lastimeoceanisfall
da1f2d9ed6 Create 20220214-simplex-chat-ios-public-beta.md (#2282) 2023-04-30 09:00:04 +01:00
lastimeoceanisfall
1ed6179782 Create 20220308-simplex-chat-mobile-apps.md (#2284) 2023-04-30 08:59:44 +01:00
lastimeoceanisfall
e15251e354 Create 20220404-simplex-chat-instant-notifications.md (#2285) 2023-04-30 08:58:55 +01:00
lastimeoceanisfall
c348dd765f Create 20220511-simplex-chat-v2-images-files.md (#2286) 2023-04-30 08:56:02 +01:00
lastimeoceanisfall
78a23e6b02 Create 20220524-simplex-chat-better-privacy.md (#2288) 2023-04-30 08:55:40 +01:00
lastimeoceanisfall
590499684d Create 20220604-simplex-chat-new-privacy-security-settings.md (#2289) 2023-04-30 08:55:16 +01:00
lastimeoceanisfall
59c50f8088 Create 20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md (#2291) 2023-04-30 08:54:59 +01:00
lastimeoceanisfall
ee4c759706 Create 20220723-simplex-chat-v3.1-tor-groups-efficiency.md (#2293) 2023-04-30 08:54:37 +01:00
lastimeoceanisfall
d5bd3a7d68 Create 20220808-simplex-chat-v3.1-chat-groups.md (#2294)
* Create 20220808-simplex-chat-v3.1-chat-groups.md

* Update 20220808-simplex-chat-v3.1-chat-groups.md
2023-04-30 08:54:21 +01:00
lastimeoceanisfall
7159195cb9 Create 20220901-simplex-chat-v3.2-incognito-mode.md (#2295) 2023-04-30 08:54:04 +01:00
lastimeoceanisfall
45d3855d84 Create 20220928-simplex-chat-v4-encrypted-database.md (#2296) 2023-04-30 08:53:48 +01:00
lastimeoceanisfall
650a6f0758 Create 20221108-simplex-chat-v4.2-security-audit-new-website.md (#2297) 2023-04-30 08:53:25 +01:00
lastimeoceanisfall
ae6ba0cfb5 Create 20221206-simplex-chat-v4.3-voice-messages.md (#2298) 2023-04-30 08:53:03 +01:00
lastimeoceanisfall
d9a2317f82 Create 20230103-simplex-chat-v4.4-disappearing-messages.md (#2299) 2023-04-30 08:52:47 +01:00
lastimeoceanisfall
3d8b521c0c Create 20230204-simplex-chat-v4-5-user-chat-profiles.md (#2300) 2023-04-30 08:52:28 +01:00
lastimeoceanisfall
1fd5bbbc4f Create 20230301-simplex-file-transfer-protocol.md (#2301) 2023-04-30 08:52:07 +01:00
lastimeoceanisfall
62299cbf0b Create 20230328-simplex-chat-v4-6-hidden-profiles.md (#2302) 2023-04-30 08:51:47 +01:00
lastimeoceanisfall
3eb969d3c5 Create 20230422-simplex-chat-vision-funding-v5-videos-files-passcode.md (#2303) 2023-04-30 08:51:09 +01:00
lastimeoceanisfall
85dba0f36b Create README.md (#2304) 2023-04-30 08:50:39 +01:00
lastimeoceanisfall
cb6490ed59 Create ANDROID.md (#2240) 2023-04-30 08:50:18 +01:00
Evgeny Poberezkin
315d830357 android: export all theme colors (#2348) 2023-04-29 13:11:44 +01:00
M Sarmad Qadeer
00caeae914 website: add simplex reviews section (#2346)
* website: add simplex reviews section

* website: update review section position & add quality images

* website: add dark mode images

* website: avoid text selection when hover on logos

* website: add layout fix

* website: add spaces in simplex review

* website: improve margin of simplex review

* website: add title to logos in simplex review

* titles, order

* update images

* move images

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-04-29 09:53:27 +01:00
Stanislav Dmitrenko
607f77d432 android: more checks for colors in customized theme (#2343)
* android: more checks for colors in customized theme

* code style

* refactor

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-04-28 09:09:42 +01:00
Stanislav Dmitrenko
c254b33753 android: blue theme and more options (#2331)
* android: blue theme and more options

* more color options and better code

* more color options and some fixes

* removed preferences about non-existent theme

* colors

* Revert "removed preferences about non-existent theme"

This reverts commit cbb38d54a8.

* colors

* update colors

* migrations

* HighOrLowLight -> secondary

* new color

* color

* update colors, move colors to a separate page

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-04-27 20:32:40 +01:00
Stanislav Dmitrenko
59f3848056 android: cancel and destructive buttons in alerts (#2238) 2023-04-27 17:00:03 +01:00
Stanislav Dmitrenko
69aa002c83 android: prevent click & long click at the same time (#2237) 2023-04-27 16:44:12 +01:00
sh
8630d1ab12 templates: update labels (#2335) 2023-04-27 09:18:40 +01:00
Evgeny Poberezkin
591aa9eaa5 core: get all chat items API (#2333)
* core: get all chat items API

* test
2023-04-27 08:12:34 +01:00
sh
f82fa42cba github: issue templates (#2330)
* issue template test

* templates: allow blank issues
2023-04-26 16:37:13 +01:00
Evgeny Poberezkin
aa441c88db Merge branch 'stable' 2023-04-26 08:30:55 +01:00
Evgeny Poberezkin
17ee22da72 readme: update group link 2023-04-26 08:30:35 +01:00
spaced4ndy
a9957fb46d core: delete xftp file when user is not found by file id (#2234) 2023-04-25 15:46:00 +04:00
Stanislav Dmitrenko
f5c87fdd4c android: better auth when opening profiles (#2236)
* android: better auth when opening profiles

* consistent behaviour between auth methods

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-04-25 10:45:46 +01:00
Stanislav Dmitrenko
23467a2248 android: lint fix and removed unused lib (#2235) 2023-04-25 10:31:26 +01:00
M Sarmad Qadeer
9fa93e40cb website: add atom feeds for blogs (#2233)
* website: add atom feeds for blogs

* update front-matter

* website: add atom and rss feeds for blogs

* include full blog content

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-04-24 19:31:59 +01:00
M Sarmad Qadeer
f4b852d2dd website: fix chines in dropdown (#2232) 2023-04-24 18:54:57 +01:00
Stanislav Dmitrenko
f698b7fa9f android: new icons (#2231)
* android: new icons

* different size

* all icons were changed

* sizes

* account icon was returned

* icon

* two icons

* bolt icon

* fix change avatar

* more vert button

* changes in icons

* icon size

* deleted unneeded icon

* icons

* spacer and padding

* no comments

* height of item

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-04-24 18:51:21 +01:00
Stanislav Dmitrenko
b21afa648d android: UserPicker enhancements (#2230)
* android: UserPicker enhancements

* paddings
2023-04-23 10:42:06 +01:00
Evgeny Poberezkin
b9575cc869 website: fix qrcode dependency 2023-04-22 17:19:49 +01:00
Evgeny Poberezkin
3ea91bc4ad Merge branch 'stable' 2023-04-22 17:04:02 +01:00
Evgeny Poberezkin
b3dce5fdb0 blog: v5 announcement (#2225)
* blog: vision, funding, v5 announcement

* keep file name

* update

* update blog

* images, site preview

* fix link

* remove duplication

* corrections
2023-04-22 17:03:41 +01:00
Evgeny Poberezkin
28ad8b8cd5 blog: v5 announcement (#2225)
* blog: vision, funding, v5 announcement

* keep file name

* update

* update blog

* images, site preview

* fix link

* remove duplication

* corrections
2023-04-22 17:01:52 +01:00
Stanislav Dmitrenko
37d4ef770c android: equal paddings between sections and bottom spacer (#2227)
* android: equal paddings between sections and bottom spacer

* one more

* aligning

* paddings

* paddings

* scream color

* switch

* background and scrim colors

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-04-22 16:38:32 +01:00
Stanislav Dmitrenko
ba24e40512 android: settings refactoring and new design (#2226)
* android: settings refactoring and new design

* spacers

* paddings

* paddings

* padding

* weight

* new chat button padding

* removed background color

* profiles

* cancel button function
2023-04-21 20:21:44 +01:00
Evgeny Poberezkin
8097593f5e website: anchor to join simplex 2023-04-21 13:42:44 +01:00
sh
23ccd69b5e docs: add xftp (#2223)
* docs: add xftp

* xftp: clarify statistics section

* xftp: expand configuring the app section
2023-04-21 11:33:55 +01:00
spaced4ndy
5e0d6d77b9 core: check max file size before sending (#2224) 2023-04-21 13:46:56 +04:00
spaced4ndy
a06393f520 core: file status command for XFTP files (#2222) 2023-04-21 13:36:44 +04:00
Evgeny Poberezkin
549ffcefc0 blog: v5 announcement placeholder 2023-04-20 19:57:06 +01:00
spaced4ndy
c8721e8000 5.0: Android 117, iOS 144 2023-04-20 20:26:12 +04:00
Evgeny Poberezkin
03882367da core: 5.0.0.2 2023-04-20 14:19:09 +01:00
spaced4ndy
4d700d113d core, ios: mark files to receive from NSE, receive marked files on chat start (#2218) 2023-04-20 16:52:55 +04:00
Stanislav Dmitrenko
17bdd2a1d2 android: different icons for attachments (#2219)
* android: different icons for attachments

* icon

* change color

* changes

* strings

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-04-20 13:27:27 +01:00
Evgeny Poberezkin
80a68012a2 website: translations (#2217)
* Added translation using Weblate (Polish)

* Added translation using Weblate (Polish)

* Translated using Weblate (Polish)

Currently translated at 33.1% (70 of 211 strings)

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

---------

Co-authored-by: Display Name <ptrumtine@proton.me>
2023-04-20 11:56:53 +01:00
Evgeny Poberezkin
3742906f75 mobile: translations (#2216)
* Translated using Weblate (Spanish)

Currently translated at 100.0% (1125 of 1125 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1043 of 1043 strings)

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

* Translated using Weblate (Czech)

Currently translated at 99.8% (1123 of 1125 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (1043 of 1043 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1123 of 1123 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1042 of 1042 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1123 of 1123 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1042 of 1042 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1125 of 1125 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1125 of 1125 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 98.7% (1029 of 1042 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1125 of 1125 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1042 of 1042 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1125 of 1125 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1043 of 1043 strings)

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

* Translated using Weblate (Czech)

Currently translated at 99.8% (1123 of 1125 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (1043 of 1043 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1123 of 1123 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1042 of 1042 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1123 of 1123 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1042 of 1042 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1125 of 1125 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1125 of 1125 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 98.7% (1029 of 1042 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1125 of 1125 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1042 of 1042 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1125 of 1125 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (1125 of 1125 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1125 of 1125 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (1042 of 1042 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1125 of 1125 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (1125 of 1125 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1125 of 1125 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1125 of 1125 strings)

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

* ios: import/export localizations

---------

Co-authored-by: No name <CertainBot@users.noreply.hosted.weblate.org>
Co-authored-by: zenobit <zen@osowoso.xyz>
Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: Float <float.hu+@gmail.com>
Co-authored-by: sith-on-mars <groguko36@pm.me>
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: B.O.S.S <BxOxSxS@protonmail.com>
2023-04-20 11:55:04 +01:00
Stanislav Dmitrenko
ae90edcdb5 android: moved to BasicTextField in some places (#2215)
* adnroid: moved to BasicTextField in some places

* field height

* field height 2

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-04-20 11:29:50 +01:00
Evgeny Poberezkin
9e76aadb0f Merge branch 'stable' 2023-04-19 18:09:57 +01:00
Evgeny Poberezkin
043544d7ec readme: new users group 2023-04-19 18:09:05 +01:00
Evgeny Poberezkin
2bf7d1dddc android: share video as video (#2214) 2023-04-19 17:39:31 +01:00
Evgeny Poberezkin
e1741118ce android: fix profile string (#2213) 2023-04-19 15:52:33 +01:00
spaced4ndy
58fb3f7f2d android: fix onboarding fonts, paddings (#2212) 2023-04-19 18:48:00 +04:00
spaced4ndy
48e92a7e9b android: don't show button to hide user if authentication is not configured (#2211) 2023-04-19 18:16:26 +04:00
spaced4ndy
5bf16da09d core, mobile: prohibit to change chat item expiration when chat is stopped (#2210) 2023-04-19 15:21:28 +04:00
Evgeny Poberezkin
23ca3dd665 5.0.0-beta.2 2023-04-19 11:59:39 +01:00
Evgeny Poberezkin
2caff25fa2 android: revert to using gallery for images and videos (#2209)
* core: revert to using gallery for images and videos

* remove comment
2023-04-19 11:07:14 +01:00
Evgeny Poberezkin
37f835be8c mobile: remove XFTP toggle (#2208)
* mobile: remove XFTP toggle

* ios: remove unused string

* android: remove unused strings
2023-04-19 09:41:01 +01:00
401 changed files with 9963 additions and 2414 deletions

68
.github/ISSUE_TEMPLATE/bug.yml vendored Normal file
View File

@@ -0,0 +1,68 @@
name: Bug
description: File a bug report/issue
title: "[Bug]: "
labels: ["type:bug", "type:triage"]
body:
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for the bug you encountered.
options:
- label: I have searched the existing issues
required: true
- type: dropdown
attributes:
label: Platform
description: Multiple selections are possible.
multiple: true
options:
- Linux
- Mac
- Windows
- Android
- iOS
validations:
required: true
- type: input
attributes:
label: OS version
description: Specify the OS version
placeholder: ex. Android 12, Ubuntu 20.04
validations:
required: true
- type: input
attributes:
label: App version
description: Specify the SimpleX version
placeholder: ex. 4.3.2
validations:
required: true
- type: textarea
attributes:
label: Current Behavior
description: A concise description of what you're experiencing.
placeholder: Bug happened!
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
description: A concise description of what you expected to happen.
placeholder: No bug should happen!
validations:
required: true
- type: textarea
attributes:
label: Steps To Reproduce
description: Steps to reproduce the behavior.
placeholder: |
1. Go to ...
3. Click on ...
4. See error...
validations:
required: true
- type: textarea
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
render: shell

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1 @@
blank_issues_enabled: true

40
.github/ISSUE_TEMPLATE/feature.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
name: Feature
description: Suggest your feature
title: "[Feature]: "
labels: ["type:enhancement", "type:triage"]
body:
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for the bug you encountered.
options:
- label: I have searched the existing issues
required: true
- type: dropdown
attributes:
label: Platform
description: Multiple selections are possible. If selected input is "all", this considered to be a general feature.
multiple: true
options:
- Linux
- Mac
- Windows
- Android
- iOS
- all
validations:
required: true
- type: input
attributes:
label: App version
description: Specify the SimpleX version
placeholder: ex. 4.3.2
validations:
required: false
- type: textarea
attributes:
label: Feature
description: Describe the feature you would like to see added
placeholder: SimpleX Chat should make me coffee!
validations:
required: true

16
.github/ISSUE_TEMPLATE/question.yml vendored Normal file
View File

@@ -0,0 +1,16 @@
name: Question
description: Ask your question
title: "[Q]: "
labels: ["type:question", "type:triage"]
body:
- type: markdown
attributes:
value: |
Generally, we encourage you to ask questions in our [official group](https://simplex.chat/invitation/#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3Dsimplex:/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D), but you can do it anyway :)
- type: textarea
attributes:
label: Question
description: Please ask your question in plain english.
placeholder: Is SimpleX - chat?
validations:
required: true

View File

@@ -48,7 +48,23 @@
## 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)
**Please note**: The groups below are created for the users to be able to ask questions, make suggestions and ask questions about SimpleX Chat only.
You also can:
- criticize the app, and make comparisons with other messengers.
- share new messengers you think could be interesting for privacy, as long as you don't spam.
- share some privacy related publications, infrequently.
- having preliminary approved with the admin in direct message, share the link to a group you created.
You must:
- be polite to other users
- avoid spam (too frequent messages, even if they are relevant)
- avoid any personal attacks or hostility.
- avoid sharing any content that is not relevant to the above (that includes, but is not limited to, discussing politics or any aspects of society other than privacy, security, technology and communications, sharing any content that may be found offensive by other users, etc.).
Messages not following these rules will be deleted, the right to send messages may be revoked, and the access to the new members to the group may be temporarily restricted, to prevent re-joining under a different name - our imperfect group moderation does not have a better solution at the moment.
You can join an English-speaking users group if you want to ask any questions: [#SimpleX-Group-3](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2Fp-j-D_PrY2UMDchFHEUtbSES0nmzCnvD%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEA3gBfMjB_GDEmKQwjNdqGbnX91yfuZ7nRJgQijsx5Khc%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%2262MvNZ_Ec2mmlS8V0QNtLQ%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:
@@ -181,6 +197,8 @@ You can use SimpleX with your own servers and still communicate with people usin
Recent updates:
[Apr 22, 2023. SimpleX Chat: vision and funding, v5.0 released with videos and files up to 1gb](./blog/20230422-simplex-chat-vision-funding-v5-videos-files-passcode.md).
[Mar 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).
@@ -295,13 +313,15 @@ If you are considering developing with SimpleX platform please get in touch for
- ✅ Improved audio & video calls.
- ✅ Support older Android OS and 32-bit CPUs.
- ✅ Hidden chat profiles.
- 🏗 Sending and receiving large files via [XFTP protocol](./blog/20230301-simplex-file-transfer-protocol.md).
- 🏗 Video messages.
- Sending and receiving large files via [XFTP protocol](./blog/20230301-simplex-file-transfer-protocol.md).
- Video messages.
- ✅ App access passcode.
- 🏗 Improved Android app UI design.
- 🏗 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).
- 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).

View File

@@ -11,8 +11,8 @@ android {
applicationId "chat.simplex.app"
minSdk 26
targetSdk 32
versionCode 115
versionName "5.0-beta.1"
versionCode 117
versionName "5.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
@@ -119,7 +119,8 @@ dependencies {
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 'com.charleskorn.kaml:kaml:0.43.0'
//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"

View File

@@ -1,6 +1,5 @@
package chat.simplex.app
import SectionItemView
import android.app.Application
import android.content.Intent
import android.net.Uri
@@ -14,14 +13,13 @@ import androidx.compose.animation.core.*
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Lock
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.fragment.app.FragmentActivity
@@ -29,8 +27,7 @@ import androidx.lifecycle.*
import chat.simplex.app.MainActivity.Companion.enteredBackground
import chat.simplex.app.model.*
import chat.simplex.app.model.NtfManager.Companion.getUserIdFromIntent
import chat.simplex.app.ui.theme.SimpleButton
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.SplashView
import chat.simplex.app.views.call.ActiveCallView
import chat.simplex.app.views.call.IncomingCallAlertView
@@ -86,20 +83,14 @@ class MainActivity: FragmentActivity() {
}
setContent {
SimpleXTheme {
Surface(
Modifier
.background(MaterialTheme.colors.background)
.fillMaxSize()
) {
MainPage(
m,
userAuthorized,
laFailed,
::runAuthenticate,
::setPerformLA,
showLANotice = { showLANotice(m.controller.appPrefs.laNoticeShown, this) }
)
}
MainPage(
m,
userAuthorized,
laFailed,
::runAuthenticate,
::setPerformLA,
showLANotice = { showLANotice(m.controller.appPrefs.laNoticeShown, this) }
)
}
}
SimplexApp.context.schedulePeriodicServiceRestartWorker()
@@ -423,12 +414,12 @@ fun MainPage(
@Composable
fun authView() {
Box(
Modifier.fillMaxSize(),
Modifier.fillMaxSize().background(MaterialTheme.colors.background),
contentAlignment = Alignment.Center
) {
SimpleButton(
stringResource(R.string.auth_unlock),
icon = Icons.Outlined.Lock,
icon = painterResource(R.drawable.ic_lock),
click = {
laFailed.value = false
runAuthenticate()
@@ -584,14 +575,23 @@ fun processExternalIntent(intent: Intent?, chatModel: ChatModel) {
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 == "text/plain" -> {
val text = intent.getStringExtra(Intent.EXTRA_TEXT)
if (text != null) {
chatModel.sharedContent.value = SharedContent.Text(text)
}
}
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)
isMediaIntent(intent) -> {
val uri = intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri
if (uri != null) {
chatModel.sharedContent.value = SharedContent.Media(intent.getStringExtra(Intent.EXTRA_TEXT) ?: "", listOf(uri))
} // All other mime types
}
else -> {
val uri = intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri
if (uri != null) {
chatModel.sharedContent.value = SharedContent.File(intent.getStringExtra(Intent.EXTRA_TEXT) ?: "", uri)
}
}
}
}
@@ -599,16 +599,23 @@ fun processExternalIntent(intent: Intent?, chatModel: ChatModel) {
// Close active chat and show a list of chats
chatModel.chatId.value = null
chatModel.clearOverlays.value = true
Log.e(TAG, "ACTION_SEND_MULTIPLE ${intent.type}")
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
isMediaIntent(intent) -> {
val uris = intent.getParcelableArrayListExtra<Parcelable>(Intent.EXTRA_STREAM) as? List<Uri>
if (uris != null) {
chatModel.sharedContent.value = SharedContent.Media(intent.getStringExtra(Intent.EXTRA_TEXT) ?: "", uris)
} // All other mime types
}
else -> {}
}
}
}
}
fun isMediaIntent(intent: Intent): Boolean =
intent.type?.startsWith("image/") == true || intent.type?.startsWith("video/") == true
fun connectIfOpenedViaUri(uri: Uri, chatModel: ChatModel) {
Log.d(TAG, "connectIfOpenedViaUri: opened via link")
if (chatModel.currentUser.value == null) {
@@ -620,7 +627,7 @@ fun connectIfOpenedViaUri(uri: Uri, chatModel: ChatModel) {
ConnectionLinkType.INVITATION -> generalGetString(R.string.connect_via_invitation_link)
ConnectionLinkType.GROUP -> generalGetString(R.string.connect_via_group_link)
}
AlertManager.shared.showAlertMsg(
AlertManager.shared.showAlertDialog(
title = title,
text = if (linkType == ConnectionLinkType.GROUP)
generalGetString(R.string.you_will_join_group)

View File

@@ -6,6 +6,7 @@ import android.util.Log
import androidx.lifecycle.*
import androidx.work.*
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.DefaultTheme
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.onboarding.OnboardingStage
import chat.simplex.app.views.usersettings.NotificationsMode
@@ -95,6 +96,7 @@ class SimplexApp: Application(), LifecycleEventObserver {
initChatController()
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
context.getDir("temp", MODE_PRIVATE).deleteRecursively()
runMigrations()
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
@@ -184,6 +186,23 @@ class SimplexApp: Application(), LifecycleEventObserver {
MessagesFetcherWorker.scheduleWork()
}
private fun runMigrations() {
val lastMigration = chatModel.controller.appPrefs.lastMigratedVersionCode
if (lastMigration.get() < BuildConfig.VERSION_CODE) {
while (true) {
if (lastMigration.get() < 117) {
if (chatModel.controller.appPrefs.currentTheme.get() == DefaultTheme.DARK.name) {
chatModel.controller.appPrefs.currentTheme.set(DefaultTheme.SIMPLEX.name)
}
lastMigration.set(117)
} else {
lastMigration.set(BuildConfig.VERSION_CODE)
break
}
}
}
}
companion object {
lateinit var context: SimplexApp private set

View File

@@ -2,11 +2,10 @@ package chat.simplex.app.model
import android.net.Uri
import androidx.compose.material.MaterialTheme
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.*
import androidx.compose.ui.text.style.TextDecoration
@@ -1517,12 +1516,12 @@ data class CIMeta (
val isRcvNew: Boolean get() = itemStatus is CIStatus.RcvNew
fun statusIcon(primaryColor: Color, metaColor: Color = HighOrLowlight): Pair<ImageVector, Color>? =
fun statusIcon(primaryColor: Color, metaColor: Color = CurrentColors.value.colors.secondary): Pair<Int, Color>? =
when (itemStatus) {
is CIStatus.SndSent -> Icons.Filled.Check to metaColor
is CIStatus.SndErrorAuth -> Icons.Filled.Close to Color.Red
is CIStatus.SndError -> Icons.Filled.WarningAmber to WarningYellow
is CIStatus.RcvNew -> Icons.Filled.Circle to primaryColor
is CIStatus.SndSent -> R.drawable.ic_check_filled to metaColor
is CIStatus.SndErrorAuth -> R.drawable.ic_close to Color.Red
is CIStatus.SndError -> R.drawable.ic_warning_filled to WarningYellow
is CIStatus.RcvNew -> R.drawable.ic_circle_filled to primaryColor
else -> null
}

View File

@@ -9,14 +9,12 @@ import android.provider.Settings
import android.util.Log
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 chat.simplex.app.views.helpers.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
@@ -24,14 +22,17 @@ import chat.simplex.app.*
import chat.simplex.app.R
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.call.*
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.newchat.ConnectViaLinkTab
import chat.simplex.app.views.onboarding.OnboardingStage
import chat.simplex.app.views.usersettings.*
import com.charleskorn.kaml.Yaml
import com.charleskorn.kaml.YamlConfiguration
import kotlinx.coroutines.*
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.serialization.*
import kotlinx.serialization.builtins.MapSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.json.*
import java.util.Date
@@ -59,6 +60,7 @@ enum class SimplexLinkMode {
class AppPreferences(val context: Context) {
private val sharedPreferences: SharedPreferences = context.getSharedPreferences(SHARED_PREFS_ID, Context.MODE_PRIVATE)
private val sharedPreferencesThemes: SharedPreferences = context.getSharedPreferences(SHARED_PREFS_THEMES_ID, Context.MODE_PRIVATE)
// deprecated, remove in 2024
private val runServiceInBackground = mkBoolPreference(SHARED_PREFS_RUN_SERVICE_IN_BACKGROUND, true)
@@ -149,11 +151,15 @@ class AppPreferences(val context: Context) {
val confirmDBUpgrades = mkBoolPreference(SHARED_PREFS_CONFIRM_DB_UPGRADES, false)
val currentTheme = mkStrPreference(SHARED_PREFS_CURRENT_THEME, DefaultTheme.SYSTEM.name)
val primaryColor = mkIntPreference(SHARED_PREFS_PRIMARY_COLOR, LightColorPalette.primary.toArgb())
val systemDarkTheme = mkStrPreference(SHARED_PREFS_SYSTEM_DARK_THEME, DefaultTheme.SIMPLEX.name)
val themeOverrides = mkMapPreference(SHARED_PREFS_THEMES, mapOf(), encode = {
json.encodeToString(MapSerializer(String.serializer(), ThemeOverrides.serializer()), it)
}, decode = {
json.decodeFromString(MapSerializer(String.serializer(), ThemeOverrides.serializer()), it)
}, sharedPreferencesThemes)
val whatsNewVersion = mkStrPreference(SHARED_PREFS_WHATS_NEW_VERSION, null)
val xftpSendEnabled = mkBoolPreference(SHARED_PREFS_XFTP_SEND_ENABLED, false)
val lastMigratedVersionCode = mkIntPreference(SHARED_PREFS_LAST_MIGRATED_VERSION_CODE, 0)
private fun mkIntPreference(prefName: String, default: Int) =
SharedPreference(
@@ -208,8 +214,15 @@ class AppPreferences(val context: Context) {
}
)
private fun <K, V> mkMapPreference(prefName: String, default: Map<K, V>, encode: (Map<K, V>) -> String, decode: (String) -> Map<K, V>, prefs: SharedPreferences = sharedPreferences): SharedPreference<Map<K,V>> =
SharedPreference(
get = fun() = decode(prefs.getString(prefName, encode(default))!!),
set = fun(value) = prefs.edit().putString(prefName, encode(value)).apply()
)
companion object {
internal const val SHARED_PREFS_ID = "chat.simplex.app.SIMPLEX_APP_PREFS"
internal const val SHARED_PREFS_THEMES_ID = "chat.simplex.app.THEMES"
private const val SHARED_PREFS_AUTO_RESTART_WORKER_VERSION = "AutoRestartWorkerVersion"
private const val SHARED_PREFS_RUN_SERVICE_IN_BACKGROUND = "RunServiceInBackground"
private const val SHARED_PREFS_NOTIFICATIONS_MODE = "NotificationsMode"
@@ -262,9 +275,10 @@ class AppPreferences(val context: Context) {
private const val SHARED_PREFS_ENCRYPTION_STARTED_AT = "EncryptionStartedAt"
private const val SHARED_PREFS_CONFIRM_DB_UPGRADES = "ConfirmDBUpgrades"
private const val SHARED_PREFS_CURRENT_THEME = "CurrentTheme"
private const val SHARED_PREFS_PRIMARY_COLOR = "PrimaryColor"
private const val SHARED_PREFS_SYSTEM_DARK_THEME = "SystemDarkTheme"
private const val SHARED_PREFS_THEMES = "Themes"
private const val SHARED_PREFS_WHATS_NEW_VERSION = "WhatsNewVersion"
private const val SHARED_PREFS_XFTP_SEND_ENABLED = "XFTPSendEnabled"
private const val SHARED_PREFS_LAST_MIGRATED_VERSION_CODE = "LastMigratedVersionCode"
}
}
@@ -1636,7 +1650,7 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
title = {
Row {
Icon(
Icons.Outlined.Bolt,
painterResource(R.drawable.ic_bolt),
contentDescription =
if (mode == NotificationsMode.SERVICE) stringResource(R.string.icon_descr_instant_notifications) else stringResource(R.string.periodic_notifications),
)
@@ -1673,7 +1687,7 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
title = {
Row {
Icon(
Icons.Outlined.Bolt,
painterResource(R.drawable.ic_bolt),
contentDescription =
if (mode == NotificationsMode.SERVICE) stringResource(R.string.icon_descr_instant_notifications) else stringResource(R.string.periodic_notifications),
)
@@ -1704,7 +1718,7 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
title = {
Row {
Icon(
Icons.Outlined.Bolt,
painterResource(R.drawable.ic_bolt),
contentDescription =
if (mode == NotificationsMode.SERVICE) stringResource(R.string.icon_descr_instant_notifications) else stringResource(R.string.periodic_notifications),
)
@@ -2586,7 +2600,7 @@ data class FeatureEnabled(
}
val iconColor: Color
get() = if (forUser) SimplexGreen else if (forContact) WarningYellow else HighOrLowlight
get() = if (forUser) SimplexGreen else if (forContact) WarningYellow else CurrentColors.value.colors.secondary
companion object {
fun enabled(asymmetric: Boolean, user: ChatPreference, contact: ChatPreference): FeatureEnabled =
@@ -2631,7 +2645,8 @@ sealed class ContactUserPrefTimed {
interface Feature {
// val icon: ImageVector
val text: String
val iconFilled: ImageVector
@Composable
fun iconFilled(): Painter
val hasParam: Boolean
}
@@ -2660,21 +2675,21 @@ enum class ChatFeature: Feature {
Calls -> generalGetString(R.string.audio_video_calls)
}
val icon: ImageVector
get() = when(this) {
TimedMessages -> Icons.Outlined.Timer
FullDelete -> Icons.Outlined.DeleteForever
Voice -> Icons.Outlined.KeyboardVoice
Calls -> Icons.Outlined.Phone
val icon: Painter
@Composable get() = when(this) {
TimedMessages -> painterResource(R.drawable.ic_timer)
FullDelete -> painterResource(R.drawable.ic_delete_forever)
Voice -> painterResource(R.drawable.ic_keyboard_voice)
Calls -> painterResource(R.drawable.ic_call)
}
override val iconFilled: ImageVector
get() = when(this) {
TimedMessages -> Icons.Filled.Timer
FullDelete -> Icons.Filled.DeleteForever
Voice -> Icons.Filled.KeyboardVoice
Calls -> Icons.Filled.Phone
}
@Composable
override fun iconFilled(): Painter = when(this) {
TimedMessages -> painterResource(R.drawable.ic_timer_filled)
FullDelete -> painterResource(R.drawable.ic_delete_forever_filled)
Voice -> painterResource(R.drawable.ic_keyboard_voice_filled)
Calls -> painterResource(R.drawable.ic_call_filled)
}
fun allowDescription(allowed: FeatureAllowed): String =
when (this) {
@@ -2749,21 +2764,21 @@ enum class GroupFeature: Feature {
Voice -> generalGetString(R.string.voice_messages)
}
val icon: ImageVector
get() = when(this) {
TimedMessages -> Icons.Outlined.Timer
DirectMessages -> Icons.Outlined.SwapHorizontalCircle
FullDelete -> Icons.Outlined.DeleteForever
Voice -> Icons.Outlined.KeyboardVoice
val icon: Painter
@Composable get() = when(this) {
TimedMessages -> painterResource(R.drawable.ic_timer)
DirectMessages -> painterResource(R.drawable.ic_swap_horizontal_circle)
FullDelete -> painterResource(R.drawable.ic_delete_forever)
Voice -> painterResource(R.drawable.ic_keyboard_voice)
}
override val iconFilled: ImageVector
get() = when(this) {
TimedMessages -> Icons.Filled.Timer
DirectMessages -> Icons.Filled.SwapHorizontalCircle
FullDelete -> Icons.Filled.DeleteForever
Voice -> Icons.Filled.KeyboardVoice
}
@Composable
override fun iconFilled(): Painter = when(this) {
TimedMessages -> painterResource(R.drawable.ic_timer_filled)
DirectMessages -> painterResource(R.drawable.ic_swap_horizontal_circle_filled)
FullDelete -> painterResource(R.drawable.ic_delete_forever_filled)
Voice -> painterResource(R.drawable.ic_keyboard_voice_filled)
}
fun enableDescription(enabled: GroupFeatureEnabled, canEdit: Boolean): String =
if (canEdit) {
@@ -2969,7 +2984,7 @@ enum class GroupFeatureEnabled {
}
val iconColor: Color
get() = if (this == ON) SimplexGreen else HighOrLowlight
get() = if (this == ON) SimplexGreen else CurrentColors.value.colors.secondary
}
@@ -2980,6 +2995,11 @@ val json = Json {
explicitNulls = false
}
val yaml = Yaml(configuration = YamlConfiguration(
strictMode = false,
encodeDefaults = false,
))
@Serializable
class APIResponse(val resp: CR, val corr: String? = null) {
companion object {

View File

@@ -18,7 +18,6 @@ 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)

View File

@@ -2,19 +2,176 @@ package chat.simplex.app.ui.theme
import android.app.UiModeManager
import android.content.Context
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.*
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.SimplexApp
import chat.simplex.app.views.helpers.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
enum class DefaultTheme {
SYSTEM, DARK, LIGHT
SYSTEM, LIGHT, DARK, SIMPLEX;
// Call it only with base theme, not SYSTEM
fun hasChangedAnyColor(colors: Colors, appColors: AppColors): Boolean {
val palette = when (this) {
SYSTEM -> return false
LIGHT -> LightColorPalette
DARK -> DarkColorPalette
SIMPLEX -> SimplexColorPalette
}
val appPalette = when (this) {
SYSTEM -> return false
LIGHT -> LightColorPaletteApp
DARK -> DarkColorPaletteApp
SIMPLEX -> SimplexColorPaletteApp
}
return colors.primary != palette.primary ||
colors.primaryVariant != palette.primaryVariant ||
colors.secondary != palette.secondary ||
colors.secondaryVariant != palette.secondaryVariant ||
colors.background != palette.background ||
colors.surface != palette.surface ||
appColors != appPalette
}
}
val DEFAULT_PADDING = 16.dp
data class AppColors(
val title: Color,
val sentMessage: Color,
val receivedMessage: Color
)
enum class ThemeColor {
PRIMARY, PRIMARY_VARIANT, SECONDARY, SECONDARY_VARIANT, BACKGROUND, SURFACE, TITLE, SENT_MESSAGE, RECEIVED_MESSAGE;
fun fromColors(colors: Colors, appColors: AppColors): Color {
return when (this) {
PRIMARY -> colors.primary
PRIMARY_VARIANT -> colors.primaryVariant
SECONDARY -> colors.secondary
SECONDARY_VARIANT -> colors.secondaryVariant
BACKGROUND -> colors.background
SURFACE -> colors.surface
TITLE -> appColors.title
SENT_MESSAGE -> appColors.sentMessage
RECEIVED_MESSAGE -> appColors.receivedMessage
}
}
val text: String
get() = when (this) {
PRIMARY -> generalGetString(R.string.color_primary)
PRIMARY_VARIANT -> generalGetString(R.string.color_primary_variant)
SECONDARY -> generalGetString(R.string.color_secondary)
SECONDARY_VARIANT -> generalGetString(R.string.color_secondary_variant)
BACKGROUND -> generalGetString(R.string.color_background)
SURFACE -> generalGetString(R.string.color_surface)
TITLE -> generalGetString(R.string.color_title)
SENT_MESSAGE -> generalGetString(R.string.color_sent_message)
RECEIVED_MESSAGE -> generalGetString(R.string.color_received_message)
}
}
@Serializable
data class ThemeColors(
@SerialName("accent")
val primary: String? = null,
@SerialName("accentVariant")
val primaryVariant: String? = null,
val secondary: String? = null,
val secondaryVariant: String? = null,
val background: String? = null,
@SerialName("menus")
val surface: String? = null,
val title: String? = null,
val sentMessage: String? = null,
val receivedMessage: String? = null,
) {
fun toColors(base: DefaultTheme): Colors {
val baseColors = when (base) {
DefaultTheme.LIGHT -> LightColorPalette
DefaultTheme.DARK -> DarkColorPalette
DefaultTheme.SIMPLEX -> SimplexColorPalette
// shouldn't be here
DefaultTheme.SYSTEM -> LightColorPalette
}
return baseColors.copy(
primary = primary?.colorFromReadableHex() ?: baseColors.primary,
primaryVariant = primaryVariant?.colorFromReadableHex() ?: baseColors.primaryVariant,
secondary = secondary?.colorFromReadableHex() ?: baseColors.secondary,
secondaryVariant = secondaryVariant?.colorFromReadableHex() ?: baseColors.secondaryVariant,
background = background?.colorFromReadableHex() ?: baseColors.background,
surface = surface?.colorFromReadableHex() ?: baseColors.surface,
)
}
fun toAppColors(base: DefaultTheme): AppColors {
val baseColors = when (base) {
DefaultTheme.LIGHT -> LightColorPaletteApp
DefaultTheme.DARK -> DarkColorPaletteApp
DefaultTheme.SIMPLEX -> SimplexColorPaletteApp
// shouldn't be here
DefaultTheme.SYSTEM -> LightColorPaletteApp
}
return baseColors.copy(
title = title?.colorFromReadableHex() ?: baseColors.title,
sentMessage = sentMessage?.colorFromReadableHex() ?: baseColors.sentMessage,
receivedMessage = receivedMessage?.colorFromReadableHex() ?: baseColors.receivedMessage,
)
}
}
private fun String.colorFromReadableHex(): Color =
Color(this.replace("#", "").toLongOrNull(16) ?: Color.White.toArgb().toLong())
@Serializable
data class ThemeOverrides (
val base: DefaultTheme,
val colors: ThemeColors
) {
fun withUpdatedColor(name: ThemeColor, color: String): ThemeOverrides {
return copy(colors = when (name) {
ThemeColor.PRIMARY -> colors.copy(primary = color)
ThemeColor.PRIMARY_VARIANT -> colors.copy(primaryVariant = color)
ThemeColor.SECONDARY -> colors.copy(secondary = color)
ThemeColor.SECONDARY_VARIANT -> colors.copy(secondaryVariant = color)
ThemeColor.BACKGROUND -> colors.copy(background = color)
ThemeColor.SURFACE -> colors.copy(surface = color)
ThemeColor.TITLE -> colors.copy(title = color)
ThemeColor.SENT_MESSAGE -> colors.copy(sentMessage = color)
ThemeColor.RECEIVED_MESSAGE -> colors.copy(receivedMessage = color)
})
}
}
@Serializable
data class ThemeData (val colors: ThemeColors)
fun Modifier.themedBackground(baseTheme: DefaultTheme = CurrentColors.value.base, shape: Shape = RectangleShape): Modifier {
return if (baseTheme == DefaultTheme.SIMPLEX) {
this.background(brush = Brush.linearGradient(
listOf(
CurrentColors.value.colors.background.darker(0.4f),
CurrentColors.value.colors.background.lighter(0.4f)
),
Offset(0f, Float.POSITIVE_INFINITY),
Offset(Float.POSITIVE_INFINITY, 0f)
), shape = shape)
} else {
this.background(color = CurrentColors.value.colors.background, shape = shape)
}
}
val DEFAULT_PADDING = 20.dp
val DEFAULT_SPACE_AFTER_ICON = 4.dp
val DEFAULT_PADDING_HALF = DEFAULT_PADDING / 2
val DEFAULT_BOTTOM_PADDING = 48.dp
@@ -22,10 +179,11 @@ val DEFAULT_BOTTOM_BUTTON_PADDING = 20.dp
val DarkColorPalette = darkColors(
primary = SimplexBlue, // If this value changes also need to update #0088ff in string resource files
primaryVariant = SimplexGreen,
secondary = DarkGray,
primaryVariant = SimplexBlue,
secondary = HighOrLowlight,
secondaryVariant = DarkGray,
// background = Color.Black,
// surface = Color.Black,
surface = Color(0xFF222222),
// background = Color(0xFF121212),
// surface = Color(0xFF121212),
error = Color.Red,
@@ -33,27 +191,59 @@ val DarkColorPalette = darkColors(
onSurface = Color(0xFFFFFBFA),
// onError: Color = Color.Black,
)
val DarkColorPaletteApp = AppColors(
title = SimplexBlue,
sentMessage = Color(0x1E45B8FF),
receivedMessage = Color(0x20B1B0B5)
)
val LightColorPalette = lightColors(
primary = SimplexBlue, // If this value changes also need to update #0088ff in string resource files
primaryVariant = SimplexGreen,
secondary = LightGray,
primaryVariant = SimplexBlue,
secondary = HighOrLowlight,
secondaryVariant = LightGray,
error = Color.Red,
// background = Color.White,
// surface = Color.White
surface = Color.White,
// onPrimary = Color.White,
// onSecondary = Color.Black,
// onBackground = Color.Black,
// onSurface = Color.Black,
)
val LightColorPaletteApp = AppColors(
title = SimplexBlue,
sentMessage = Color(0x1E45B8FF),
receivedMessage = Color(0x20B1B0B5)
)
val CurrentColors: MutableStateFlow<Pair<Colors, DefaultTheme>> = MutableStateFlow(ThemeManager.currentColors(isInNightMode()))
val SimplexColorPalette = darkColors(
primary = Color(0xFF70F0F9), // If this value changes also need to update #0088ff in string resource files
primaryVariant = Color(0xFF1298A5),
secondary = HighOrLowlight,
secondaryVariant = Color(0xFF2C464D),
background = Color(0xFF111528),
// surface = Color.Black,
// background = Color(0xFF121212),
surface = Color(0xFF121C37),
error = Color.Red,
// onBackground = Color(0xFFFFFBFA),
// onSurface = Color(0xFFFFFBFA),
// onError: Color = Color.Black,
)
val SimplexColorPaletteApp = AppColors(
title = Color(0xFF267BE5),
sentMessage = Color(0x1E45B8FF),
receivedMessage = Color(0x20B1B0B5)
)
val CurrentColors: MutableStateFlow<ThemeManager.ActiveTheme> = 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
fun isInDarkTheme(): Boolean = !CurrentColors.collectAsState().value.colors.isLight
@Composable
fun SimpleXTheme(darkTheme: Boolean? = null, content: @Composable () -> Unit) {
@@ -64,14 +254,14 @@ fun SimpleXTheme(darkTheme: Boolean? = null, content: @Composable () -> Unit) {
}
val systemDark = isSystemInDarkTheme()
LaunchedEffect(systemDark) {
if (CurrentColors.value.second == DefaultTheme.SYSTEM && CurrentColors.value.first.isLight == systemDark) {
if (SimplexApp.context.chatModel.controller.appPrefs.currentTheme.get() == DefaultTheme.SYSTEM.name && CurrentColors.value.colors.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,
colors = theme.colors,
typography = Typography,
shapes = Shapes,
content = content

View File

@@ -7,22 +7,56 @@ import chat.simplex.app.R
import chat.simplex.app.SimplexApp
import chat.simplex.app.model.AppPreferences
import chat.simplex.app.views.helpers.generalGetString
import okhttp3.internal.toHexString
object ThemeManager {
private val appPrefs: AppPreferences by lazy {
AppPreferences(SimplexApp.context)
SimplexApp.context.chatModel.controller.appPrefs
}
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)
data class ActiveTheme(val name: String, val base: DefaultTheme, val colors: Colors, val appColors: AppColors)
private fun systemDarkThemeColors(): Pair<Colors, DefaultTheme> = when (appPrefs.systemDarkTheme.get()) {
DefaultTheme.DARK.name -> DarkColorPalette to DefaultTheme.DARK
DefaultTheme.SIMPLEX.name -> SimplexColorPalette to DefaultTheme.SIMPLEX
else -> SimplexColorPalette to DefaultTheme.SIMPLEX
}
fun currentColors(darkForSystemTheme: Boolean): ActiveTheme {
val themeName = appPrefs.currentTheme.get()!!
val themeOverrides = appPrefs.themeOverrides.get()
val nonSystemThemeName = if (themeName != DefaultTheme.SYSTEM.name) {
themeName
} else {
if (darkForSystemTheme) appPrefs.systemDarkTheme.get()!! else DefaultTheme.LIGHT.name
}
return res.copy(first = res.first.copy(primary = Color(appPrefs.primaryColor.get())))
val theme = themeOverrides[nonSystemThemeName]
val baseTheme = when (nonSystemThemeName) {
DefaultTheme.LIGHT.name -> Triple(DefaultTheme.LIGHT, LightColorPalette, LightColorPaletteApp)
DefaultTheme.DARK.name -> Triple(DefaultTheme.DARK, DarkColorPalette, DarkColorPaletteApp)
DefaultTheme.SIMPLEX.name -> Triple(DefaultTheme.SIMPLEX, SimplexColorPalette, SimplexColorPaletteApp)
else -> Triple(DefaultTheme.LIGHT, LightColorPalette, LightColorPaletteApp)
}
if (theme == null) {
return ActiveTheme(themeName, baseTheme.first, baseTheme.second, baseTheme.third)
}
return ActiveTheme(themeName, baseTheme.first, theme.colors.toColors(theme.base), theme.colors.toAppColors(theme.base))
}
fun currentThemeData(darkForSystemTheme: Boolean): ThemeData {
val t = currentColors(darkForSystemTheme)
return ThemeData(colors = ThemeColors(
primary = t.colors.primary.toReadableHex(),
primaryVariant = t.colors.primaryVariant.toReadableHex(),
secondary = t.colors.secondary.toReadableHex(),
secondaryVariant = t.colors.secondaryVariant.toReadableHex(),
background = t.colors.background.toReadableHex(),
surface = t.colors.surface.toReadableHex(),
title = t.appColors.title.toReadableHex(),
sentMessage = t.appColors.sentMessage.toReadableHex(),
receivedMessage = t.appColors.receivedMessage.toReadableHex()
))
}
// colors, default theme enum, localized name of theme
@@ -30,7 +64,7 @@ object ThemeManager {
val allThemes = ArrayList<Triple<Colors, DefaultTheme, String>>()
allThemes.add(
Triple(
if (darkForSystemTheme) DarkColorPalette else LightColorPalette,
if (darkForSystemTheme) systemDarkThemeColors().first else LightColorPalette,
DefaultTheme.SYSTEM,
generalGetString(R.string.theme_system)
)
@@ -49,16 +83,72 @@ object ThemeManager {
generalGetString(R.string.theme_dark)
)
)
allThemes.add(
Triple(
SimplexColorPalette,
DefaultTheme.SIMPLEX,
generalGetString(R.string.theme_simplex)
)
)
return allThemes
}
fun applyTheme(name: String, darkForSystemTheme: Boolean) {
appPrefs.currentTheme.set(name)
fun applyTheme(theme: String, darkForSystemTheme: Boolean) {
appPrefs.currentTheme.set(theme)
CurrentColors.value = currentColors(darkForSystemTheme)
}
fun saveAndApplyPrimaryColor(color: Color) {
appPrefs.primaryColor.set(color.toArgb())
CurrentColors.value = currentColors(!CurrentColors.value.first.isLight)
fun changeDarkTheme(theme: String, darkForSystemTheme: Boolean) {
appPrefs.systemDarkTheme.set(theme)
CurrentColors.value = currentColors(darkForSystemTheme)
}
fun saveAndApplyThemeColor(name: ThemeColor, color: Color? = null, darkForSystemTheme: Boolean) {
val themeName = appPrefs.currentTheme.get()!!
val nonSystemThemeName = if (themeName != DefaultTheme.SYSTEM.name) {
themeName
} else {
if (darkForSystemTheme) appPrefs.systemDarkTheme.get()!! else DefaultTheme.LIGHT.name
}
var colorToSet = color
if (colorToSet == null) {
// Setting default color from a base theme
colorToSet = when(nonSystemThemeName) {
DefaultTheme.LIGHT.name -> name.fromColors(LightColorPalette, LightColorPaletteApp)
DefaultTheme.DARK.name -> name.fromColors(DarkColorPalette, DarkColorPaletteApp)
DefaultTheme.SIMPLEX.name -> name.fromColors(SimplexColorPalette, SimplexColorPaletteApp)
// Will not be here
else -> return
}
}
val overrides = appPrefs.themeOverrides.get().toMutableMap()
val prevValue = overrides[nonSystemThemeName] ?: ThemeOverrides(base = CurrentColors.value.base, colors = ThemeColors())
overrides[nonSystemThemeName] = prevValue.withUpdatedColor(name, colorToSet.toReadableHex())
appPrefs.themeOverrides.set(overrides)
CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight)
}
fun saveAndApplyThemeData(name: String, theme: ThemeData, darkForSystemTheme: Boolean) {
val overrides = appPrefs.themeOverrides.get().toMutableMap()
val prevValue = overrides[name] ?: ThemeOverrides(base = CurrentColors.value.base, colors = ThemeColors())
overrides[name] = prevValue.copy(colors = theme.colors)
appPrefs.themeOverrides.set(overrides)
CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight)
}
fun resetAllThemeColors(darkForSystemTheme: Boolean) {
val themeName = appPrefs.currentTheme.get()!!
val nonSystemThemeName = if (themeName != DefaultTheme.SYSTEM.name) {
themeName
} else {
if (darkForSystemTheme) appPrefs.systemDarkTheme.get()!! else DefaultTheme.LIGHT.name
}
val overrides = appPrefs.themeOverrides.get().toMutableMap()
val prevValue = overrides[nonSystemThemeName] ?: return
overrides[nonSystemThemeName] = prevValue.copy(colors = ThemeColors())
appPrefs.themeOverrides.set(overrides)
CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight)
}
}
private fun Color.toReadableHex(): String = "#" + toArgb().toHexString()

View File

@@ -1,6 +1,5 @@
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
@@ -11,8 +10,8 @@ import androidx.compose.ui.Modifier
fun SplashView() {
Surface(
Modifier
.background(MaterialTheme.colors.background)
.fillMaxSize()
.fillMaxSize(),
color = MaterialTheme.colors.background
) {
// Image(
// painter = painterResource(R.drawable.logo),

View File

@@ -100,7 +100,7 @@ fun TerminalLayout(
modifier = Modifier
.padding(contentPadding)
.fillMaxWidth()
.background(MaterialTheme.colors.background)
.themedBackground()
) {
TerminalLog(terminalItems)
}

View File

@@ -3,18 +3,18 @@ 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.MaterialTheme.colors
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowBackIosNew
import androidx.compose.material.icons.outlined.ArrowForwardIos
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
@@ -33,7 +33,6 @@ import chat.simplex.app.views.onboarding.ReadableText
import com.google.accompanist.insets.navigationBarsWithImePadding
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
fun isValidDisplayName(name: String) : Boolean {
return (name.firstOrNull { it.isWhitespace() }) == null && !name.startsWith("@") && !name.startsWith("#")
@@ -57,7 +56,7 @@ fun CreateProfilePanel(chatModel: ChatModel, close: () -> Unit) {
}
})*/
Column(Modifier.padding(horizontal = DEFAULT_PADDING * 1f)) {
AppBarTitleCentered(stringResource(R.string.create_profile))
AppBarTitle(stringResource(R.string.create_profile))
ReadableText(R.string.your_profile_is_stored_on_your_device, TextAlign.Center, padding = PaddingValues())
ReadableText(R.string.profile_is_only_shared_with_your_contacts, TextAlign.Center)
Spacer(Modifier.height(DEFAULT_PADDING * 1.5f))
@@ -88,9 +87,9 @@ fun CreateProfilePanel(chatModel: ChatModel, close: () -> Unit) {
if (chatModel.users.isEmpty()) {
SimpleButtonDecorated(
text = stringResource(R.string.about_simplex),
icon = Icons.Outlined.ArrowBackIosNew,
icon = painterResource(R.drawable.ic_arrow_back_ios_new),
textDecoration = TextDecoration.None,
fontWeight = FontWeight.Bold
fontWeight = FontWeight.Medium
) { chatModel.onboardingStage.value = OnboardingStage.Step1_SimpleXInfo }
}
Spacer(Modifier.fillMaxWidth().weight(1f))
@@ -102,12 +101,12 @@ fun CreateProfilePanel(chatModel: ChatModel, close: () -> Unit) {
createColor = MaterialTheme.colors.primary
} else {
createModifier = Modifier.padding(8.dp)
createColor = HighOrLowlight
createColor = MaterialTheme.colors.secondary
}
Surface(shape = RoundedCornerShape(20.dp)) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = createModifier) {
Text(stringResource(R.string.create_profile_button), style = MaterialTheme.typography.caption, color = createColor, fontWeight = FontWeight.Medium)
Icon(Icons.Outlined.ArrowForwardIos, stringResource(R.string.create_profile_button), tint = createColor)
Icon(painterResource(R.drawable.ic_arrow_forward_ios), stringResource(R.string.create_profile_button), tint = createColor)
}
}
}
@@ -148,42 +147,38 @@ fun ProfileNameField(name: MutableState<String>, placeholder: String = "", isVal
derivedStateOf {
if (valid) {
if (focused) {
HighOrLowlight.copy(alpha = 0.6f)
CurrentColors.value.colors.secondary.copy(alpha = 0.6f)
} else {
HighOrLowlight.copy(alpha = 0.3f)
CurrentColors.value.colors.secondary.copy(alpha = 0.3f)
}
} else Color.Red
}
}
val modifier = Modifier
.fillMaxWidth()
.height(55.dp)
.border(border = BorderStroke(1.dp, strokeColor), shape = RoundedCornerShape(50))
.padding(horizontal = 8.dp)
.padding(horizontal = DEFAULT_PADDING)
.navigationBarsWithImePadding()
.onFocusChanged { focused = it.isFocused }
TextField(
value = name.value,
onValueChange = { name.value = it },
modifier = if (focusRequester == null) modifier else modifier.focusRequester(focusRequester),
textStyle = TextStyle(fontSize = 18.sp, color = colors.onBackground),
keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.None,
autoCorrect = false
),
singleLine = true,
isError = !valid,
placeholder = { Text(placeholder, fontSize = 18.sp, color = HighOrLowlight.copy(alpha = 0.3f)) },
shape = RoundedCornerShape(50),
colors = TextFieldDefaults.textFieldColors(
backgroundColor = Color.Unspecified,
textColor = MaterialTheme.colors.onBackground,
focusedIndicatorColor = Color.Unspecified,
unfocusedIndicatorColor = Color.Unspecified,
cursorColor = HighOrLowlight,
errorIndicatorColor = Color.Unspecified
)
Box(
Modifier
.fillMaxWidth()
.height(52.dp)
.border(border = BorderStroke(1.dp, strokeColor), shape = RoundedCornerShape(50)),
contentAlignment = Alignment.Center
) {
BasicTextField(
value = name.value,
onValueChange = { name.value = it },
modifier = if (focusRequester == null) modifier else modifier.focusRequester(focusRequester),
textStyle = TextStyle(fontSize = 18.sp, color = colors.onBackground),
keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.None,
autoCorrect = false
),
singleLine = true,
cursorBrush = SolidColor(MaterialTheme.colors.secondary)
)
}
LaunchedEffect(Unit) {
snapshotFlow { name.value }
.distinctUntilChanged()

View File

@@ -16,17 +16,15 @@ 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.graphics.painter.Painter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
@@ -263,7 +261,7 @@ private fun ActiveCallOverlayLayout(
toggleSound: () -> Unit,
flipCamera: () -> Unit
) {
Column(Modifier.padding(16.dp)) {
Column(Modifier.padding(DEFAULT_PADDING)) {
when (call.peerMedia ?: call.localMedia) {
CallMediaType.Video -> {
CallInfoView(call, alignment = Alignment.Start)
@@ -272,14 +270,14 @@ private fun ActiveCallOverlayLayout(
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))
Icon(painterResource(R.drawable.ic_call_end_filled), 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)
ControlButton(call, painterResource(R.drawable.ic_flip_camera_android_filled), R.string.icon_descr_flip_camera, flipCamera)
ControlButton(call, painterResource(R.drawable.ic_videocam_filled), 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)
ControlButton(call, painterResource(R.drawable.ic_videocam_off), R.string.icon_descr_video_on, toggleVideo)
}
}
}
@@ -297,7 +295,7 @@ private fun ActiveCallOverlayLayout(
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))
Icon(painterResource(R.drawable.ic_call_end_filled), stringResource(R.string.icon_descr_hang_up), tint = Color.Red, modifier = Modifier.size(64.dp))
}
}
Box(Modifier.padding(start = 32.dp)) {
@@ -315,10 +313,10 @@ private fun ActiveCallOverlayLayout(
}
@Composable
private fun ControlButton(call: Call, icon: ImageVector, @StringRes iconText: Int, action: () -> Unit, enabled: Boolean = true) {
private fun ControlButton(call: Call, icon: Painter, @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))
Icon(icon, stringResource(iconText), tint = if (enabled) Color(0xFFFFFFD8) else MaterialTheme.colors.secondary, modifier = Modifier.size(40.dp))
}
} else {
Spacer(Modifier.size(40.dp))
@@ -328,18 +326,18 @@ private fun ControlButton(call: Call, icon: ImageVector, @StringRes iconText: In
@Composable
private fun ToggleAudioButton(call: Call, toggleAudio: () -> Unit) {
if (call.audioEnabled) {
ControlButton(call, Icons.Outlined.Mic, R.string.icon_descr_audio_off, toggleAudio)
ControlButton(call, painterResource(R.drawable.ic_mic), R.string.icon_descr_audio_off, toggleAudio)
} else {
ControlButton(call, Icons.Outlined.MicOff, R.string.icon_descr_audio_on, toggleAudio)
ControlButton(call, painterResource(R.drawable.ic_mic_off), 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)
ControlButton(call, painterResource(R.drawable.ic_volume_up), R.string.icon_descr_speaker_off, toggleSound, enabled)
} else {
ControlButton(call, Icons.Outlined.VolumeDown, R.string.icon_descr_speaker_on, toggleSound, enabled)
ControlButton(call, painterResource(R.drawable.ic_volume_down), R.string.icon_descr_speaker_on, toggleSound, enabled)
}
}
@@ -369,7 +367,7 @@ fun CallInfoView(call: Call, alignment: Alignment.Horizontal) {
// horizontalAlignment = Alignment.CenterHorizontally,
// verticalArrangement = Arrangement.spacedBy(12.dp),
// modifier = Modifier
// .background(MaterialTheme.colors.background)
// .themedBackground()
// .fillMaxSize()
// ) {
// WebRTCView(callCommand) { apiMsg ->

View File

@@ -17,14 +17,12 @@ 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.graphics.painter.Painter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@@ -99,7 +97,7 @@ fun IncomingCallActivityView(m: ChatModel) {
SimpleXTheme {
Surface(
Modifier
.background(MaterialTheme.colors.background)
.themedBackground()
.fillMaxSize()) {
if (showCallView) {
Box {
@@ -171,18 +169,18 @@ fun IncomingCallLockScreenAlertLayout(
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)
LockScreenCallButton(stringResource(R.string.reject), painterResource(R.drawable.ic_call_end_filled), Color.Red, rejectCall)
Spacer(Modifier.size(48.dp))
LockScreenCallButton(stringResource(R.string.ignore), Icons.Filled.Close, MaterialTheme.colors.primary, ignoreCall)
LockScreenCallButton(stringResource(R.string.ignore), painterResource(R.drawable.ic_close), MaterialTheme.colors.primary, ignoreCall)
Spacer(Modifier.size(48.dp))
LockScreenCallButton(stringResource(R.string.accept), Icons.Filled.Check, SimplexGreen, acceptCall)
LockScreenCallButton(stringResource(R.string.accept), painterResource(R.drawable.ic_check_filled), 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)
SimpleButton(text = stringResource(R.string.open_verb), icon = painterResource(R.drawable.ic_check_filled), click = openApp)
}
}
}
@@ -199,7 +197,7 @@ private fun SimpleXLogo() {
}
@Composable
private fun LockScreenCallButton(text: String, icon: ImageVector, color: Color, action: () -> Unit) {
private fun LockScreenCallButton(text: String, icon: Painter, color: Color, action: () -> Unit) {
Surface(
shape = RoundedCornerShape(10.dp),
color = Color.Transparent
@@ -213,8 +211,8 @@ private fun LockScreenCallButton(text: String, icon: ImageVector, color: Color,
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)
Spacer(Modifier.height(DEFAULT_PADDING))
Text(text, style = MaterialTheme.typography.body2, color = MaterialTheme.colors.secondary)
}
}
}
@@ -228,7 +226,7 @@ fun PreviewIncomingCallLockScreenAlert() {
SimpleXTheme(true) {
Surface(
Modifier
.background(MaterialTheme.colors.background)
.themedBackground()
.fillMaxSize()) {
IncomingCallLockScreenAlertLayout(
invitation = RcvCallInvitation(

View File

@@ -4,15 +4,14 @@ 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.graphics.painter.Painter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@@ -52,7 +51,7 @@ fun IncomingCallAlertLayout(
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)) {
Column(Modifier.fillMaxWidth().background(color).padding(top = DEFAULT_PADDING, bottom = DEFAULT_PADDING, start = DEFAULT_PADDING, end = 8.dp)) {
IncomingCallInfo(invitation, chatModel)
Spacer(Modifier.height(8.dp))
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween) {
@@ -60,9 +59,9 @@ fun IncomingCallAlertLayout(
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)
CallButton(stringResource(R.string.reject), painterResource(R.drawable.ic_call_end_filled), Color.Red, rejectCall)
CallButton(stringResource(R.string.ignore), painterResource(R.drawable.ic_close), MaterialTheme.colors.primary, ignoreCall)
CallButton(stringResource(R.string.accept), painterResource(R.drawable.ic_check_filled), SimplexGreen, acceptCall)
}
}
}
@@ -70,21 +69,21 @@ fun IncomingCallAlertLayout(
@Composable
fun IncomingCallInfo(invitation: RcvCallInvitation, chatModel: ChatModel) {
@Composable fun CallIcon(icon: ImageVector, descr: String) = Icon(icon, descr, tint = SimplexGreen)
@Composable fun CallIcon(icon: Painter, 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)
ProfileImage(size = 32.dp, image = invitation.user.profile.image, color = MaterialTheme.colors.secondaryVariant)
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))
if (invitation.callType.media == CallMediaType.Video) CallIcon(painterResource(R.drawable.ic_videocam_filled), stringResource(R.string.icon_descr_video_call))
else CallIcon(painterResource(R.drawable.ic_call_filled), 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) {
private fun CallButton(text: String, icon: Painter, color: Color, action: () -> Unit) {
Surface(
shape = RoundedCornerShape(10.dp),
color = Color.Transparent
@@ -97,7 +96,7 @@ private fun CallButton(text: String, icon: ImageVector, color: Color, action: ()
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(icon, text, tint = color, modifier = Modifier.scale(1.2f))
Text(text, style = MaterialTheme.typography.body2, color = HighOrLowlight)
Text(text, style = MaterialTheme.typography.body2, color = MaterialTheme.colors.secondary)
}
}
}

View File

@@ -2,7 +2,8 @@ package chat.simplex.app.views.chat
import InfoRow
import InfoRowEllipsis
import SectionDivider
import SectionBottomSpacer
import SectionDividerSpaced
import SectionItemView
import SectionSpacer
import SectionView
@@ -12,9 +13,6 @@ 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
@@ -22,6 +20,7 @@ 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.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle
@@ -112,7 +111,7 @@ fun ChatInfoView(
}
fun deleteContactDialog(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> Unit)? = null) {
AlertManager.shared.showAlertMsg(
AlertManager.shared.showAlertDialog(
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),
@@ -126,12 +125,13 @@ fun deleteContactDialog(chatInfo: ChatInfo, chatModel: ChatModel, close: (() ->
close?.invoke()
}
}
}
},
destructive = true,
)
}
fun clearChatDialog(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> Unit)? = null) {
AlertManager.shared.showAlertMsg(
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.clear_chat_question),
text = generalGetString(R.string.clear_chat_warning),
confirmText = generalGetString(R.string.clear_verb),
@@ -144,7 +144,8 @@ fun clearChatDialog(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> Unit
close?.invoke()
}
}
}
},
destructive = true,
)
}
@@ -168,8 +169,7 @@ fun ChatInfoLayout(
Column(
Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.Start
.verticalScroll(rememberScrollState())
) {
Row(
Modifier.fillMaxWidth(),
@@ -179,28 +179,25 @@ fun ChatInfoLayout(
}
LocalAliasEditor(localAlias, updateValue = onLocalAliasChanged)
SectionSpacer()
if (customUserProfile != null) {
SectionSpacer()
SectionView(generalGetString(R.string.incognito).uppercase()) {
InfoRow(generalGetString(R.string.incognito_random_profile), customUserProfile.chatViewName)
}
SectionDividerSpaced()
}
SectionSpacer()
SectionView {
if (connectionCode != null) {
VerifyCodeButton(contact.verified, verifyClicked)
SectionDivider()
}
ContactPreferencesButton(openPreferences)
}
SectionSpacer()
SectionDividerSpaced()
SectionView(title = stringResource(R.string.conn_stats_section_title_servers)) {
SwitchAddressButton(switchContactAddress)
SectionDivider()
if (connStats != null) {
SectionItemView({
AlertManager.shared.showAlertMsg(
@@ -211,32 +208,28 @@ fun ChatInfoLayout(
}
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()
SectionDividerSpaced()
SectionView {
ClearChatButton(clearChat)
SectionDivider()
DeleteContactButton(deleteContact)
}
SectionSpacer()
if (developerTools) {
SectionDividerSpaced()
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()
}
SectionBottomSpacer()
}
}
@@ -249,7 +242,7 @@ fun ChatInfoHeader(cInfo: ChatInfo, contact: Contact) {
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)
Icon(painterResource(R.drawable.ic_verified_user), null, Modifier.padding(end = 6.dp, top = 4.dp).size(24.dp), tint = MaterialTheme.colors.secondary)
}
Text(
contact.profile.displayName, style = MaterialTheme.typography.h1.copy(fontWeight = FontWeight.Normal),
@@ -290,13 +283,13 @@ fun LocalAliasEditor(
Text(
generalGetString(R.string.text_field_set_contact_placeholder),
textAlign = if (center) TextAlign.Center else TextAlign.Start,
color = HighOrLowlight
color = MaterialTheme.colors.secondary
)
},
leadingIcon = if (leadingIcon) {
{ Icon(Icons.Default.Edit, null, Modifier.padding(start = 7.dp)) }
{ Icon(painterResource(R.drawable.ic_edit_filled), null, Modifier.padding(start = 7.dp)) }
} else null,
color = HighOrLowlight,
color = MaterialTheme.colors.secondary,
focus = focus,
textStyle = TextStyle.Default.copy(textAlign = if (value.isEmpty() || !center) TextAlign.Start else TextAlign.Center),
keyboardActions = KeyboardActions(onDone = { updateValue(value) })
@@ -331,7 +324,7 @@ private fun NetworkStatusRow(networkStatus: NetworkStatus) {
) {
Text(stringResource(R.string.network_status))
Icon(
Icons.Outlined.Info,
painterResource(R.drawable.ic_info),
stringResource(R.string.network_status),
tint = MaterialTheme.colors.primary
)
@@ -343,7 +336,7 @@ private fun NetworkStatusRow(networkStatus: NetworkStatus) {
) {
Text(
networkStatus.statusString,
color = HighOrLowlight
color = MaterialTheme.colors.secondary
)
ServerImage(networkStatus)
}
@@ -355,12 +348,12 @@ 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)
Icon(painterResource(R.drawable.ic_circle_filled), 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)
Icon(painterResource(R.drawable.ic_pending_filled), stringResource(R.string.icon_descr_server_status_disconnected), tint = MaterialTheme.colors.secondary)
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)
Icon(painterResource(R.drawable.ic_error_filled), stringResource(R.string.icon_descr_server_status_error), tint = MaterialTheme.colors.secondary)
else -> Icon(painterResource(R.drawable.ic_circle), stringResource(R.string.icon_descr_server_status_pending), tint = MaterialTheme.colors.secondary)
}
}
}
@@ -385,17 +378,17 @@ fun SwitchAddressButton(onClick: () -> Unit) {
@Composable
fun VerifyCodeButton(contactVerified: Boolean, onClick: () -> Unit) {
SettingsActionItem(
if (contactVerified) Icons.Outlined.VerifiedUser else Icons.Outlined.Shield,
if (contactVerified) painterResource(R.drawable.ic_verified_user) else painterResource(R.drawable.ic_shield),
stringResource(if (contactVerified) R.string.view_security_code else R.string.verify_security_code),
click = onClick,
iconColor = HighOrLowlight,
iconColor = MaterialTheme.colors.secondary,
)
}
@Composable
private fun ContactPreferencesButton(onClick: () -> Unit) {
SettingsActionItem(
Icons.Outlined.ToggleOn,
painterResource(R.drawable.ic_toggle_on),
stringResource(R.string.contact_preferences),
click = onClick
)
@@ -404,7 +397,7 @@ private fun ContactPreferencesButton(onClick: () -> Unit) {
@Composable
fun ClearChatButton(onClick: () -> Unit) {
SettingsActionItem(
Icons.Outlined.Restore,
painterResource(R.drawable.ic_settings_backup_restore),
stringResource(R.string.clear_chat_button),
click = onClick,
textColor = WarningOrange,
@@ -415,7 +408,7 @@ fun ClearChatButton(onClick: () -> Unit) {
@Composable
private fun DeleteContactButton(onClick: () -> Unit) {
SettingsActionItem(
Icons.Outlined.Delete,
painterResource(R.drawable.ic_delete),
stringResource(R.string.button_delete_contact),
click = onClick,
textColor = Color.Red,
@@ -430,13 +423,14 @@ private fun setContactAlias(contactApiId: Long, localAlias: String, chatModel: C
}
private fun showSwitchContactAddressAlert(m: ChatModel, contactId: Long) {
AlertManager.shared.showAlertMsg(
AlertManager.shared.showAlertDialog(
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)
}
},
destructive = true,
)
}

View File

@@ -12,9 +12,6 @@ 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.mapSaver
import androidx.compose.runtime.saveable.rememberSaveable
@@ -23,6 +20,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.*
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.capitalize
import androidx.compose.ui.text.font.FontWeight
@@ -328,7 +326,7 @@ fun ChatLayout(
Box(
Modifier
.fillMaxWidth()
.background(MaterialTheme.colors.background)
.themedBackground()
) {
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
ModalBottomSheetLayout(
@@ -392,7 +390,7 @@ fun ChatInfoToolbar(
val barButtons = arrayListOf<@Composable RowScope.() -> Unit>()
val menuItems = arrayListOf<@Composable () -> Unit>()
menuItems.add {
ItemAction(stringResource(android.R.string.search_go).capitalize(Locale.current), Icons.Outlined.Search, onClick = {
ItemAction(stringResource(android.R.string.search_go).capitalize(Locale.current), painterResource(R.drawable.ic_search), onClick = {
showMenu.value = false
showSearch = true
})
@@ -404,11 +402,11 @@ fun ChatInfoToolbar(
showMenu.value = false
startCall(CallMediaType.Audio)
}) {
Icon(Icons.Outlined.Phone, stringResource(R.string.icon_descr_more_button), tint = MaterialTheme.colors.primary)
Icon(painterResource(R.drawable.ic_call_500), stringResource(R.string.icon_descr_more_button), tint = MaterialTheme.colors.primary)
}
}
menuItems.add {
ItemAction(stringResource(R.string.icon_descr_video_call).capitalize(Locale.current), Icons.Outlined.Videocam, onClick = {
ItemAction(stringResource(R.string.icon_descr_video_call).capitalize(Locale.current), painterResource(R.drawable.ic_videocam), onClick = {
showMenu.value = false
startCall(CallMediaType.Video)
})
@@ -419,7 +417,7 @@ fun ChatInfoToolbar(
showMenu.value = false
addMembers(chat.chatInfo.groupInfo)
}) {
Icon(Icons.Outlined.PersonAdd, stringResource(R.string.icon_descr_add_members), tint = MaterialTheme.colors.primary)
Icon(painterResource(R.drawable.ic_person_add_500), stringResource(R.string.icon_descr_add_members), tint = MaterialTheme.colors.primary)
}
}
}
@@ -427,7 +425,7 @@ fun ChatInfoToolbar(
menuItems.add {
ItemAction(
if (ntfsEnabled.value) stringResource(R.string.mute_chat) else stringResource(R.string.unmute_chat),
if (ntfsEnabled.value) Icons.Outlined.NotificationsOff else Icons.Outlined.Notifications,
if (ntfsEnabled.value) painterResource(R.drawable.ic_notifications_off) else painterResource(R.drawable.ic_notifications),
onClick = {
showMenu.value = false
// Just to make a delay before changing state of ntfsEnabled, otherwise it will redraw menu item with new value before closing the menu
@@ -441,7 +439,7 @@ fun ChatInfoToolbar(
barButtons.add {
IconButton({ showMenu.value = true }) {
Icon(Icons.Default.MoreVert, stringResource(R.string.icon_descr_more_button), tint = MaterialTheme.colors.primary)
Icon(MoreVertFilled, stringResource(R.string.icon_descr_more_button), tint = MaterialTheme.colors.primary)
}
}
@@ -464,7 +462,7 @@ fun ChatInfoToolbar(
}
@Composable
fun ChatInfoToolbarTitle(cInfo: ChatInfo, imageSize: Dp = 40.dp, iconColor: Color = MaterialTheme.colors.secondary) {
fun ChatInfoToolbarTitle(cInfo: ChatInfo, imageSize: Dp = 40.dp, iconColor: Color = MaterialTheme.colors.secondaryVariant) {
Row(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
@@ -498,7 +496,7 @@ fun ChatInfoToolbarTitle(cInfo: ChatInfo, imageSize: Dp = 40.dp, iconColor: Colo
@Composable
private fun ContactVerifiedShield() {
Icon(Icons.Outlined.VerifiedUser, null, Modifier.size(18.dp).padding(end = 3.dp, top = 1.dp), tint = HighOrLowlight)
Icon(painterResource(R.drawable.ic_verified_user), null, Modifier.size(18.dp).padding(end = 3.dp, top = 1.dp), tint = MaterialTheme.colors.secondary)
}
data class CIListState(val scrolled: Boolean, val itemCount: Int, val keyboardState: KeyboardState)
@@ -787,17 +785,17 @@ fun BoxWithConstraintsScope.FloatingButtons(
val showDropDown = remember { mutableStateOf(false) }
TopEndFloatingButton(
Modifier.padding(end = 16.dp, top = 24.dp).align(Alignment.TopEnd),
Modifier.padding(end = DEFAULT_PADDING, top = 24.dp).align(Alignment.TopEnd),
topUnreadCount,
showButtonWithCounter,
onClick = { scope.launch { listState.animateScrollBy(height) } },
onLongClick = { showDropDown.value = true }
)
DefaultDropdownMenu(showDropDown, offset = DpOffset(maxWidth - 16.dp, 24.dp + fabSize)) {
DefaultDropdownMenu(showDropDown, offset = DpOffset(maxWidth - DEFAULT_PADDING, 24.dp + fabSize)) {
ItemAction(
generalGetString(R.string.mark_read),
Icons.Outlined.Check,
painterResource(R.drawable.ic_check),
onClick = {
markRead(
CC.ItemRange(minUnreadItemId, chatItems[chatItems.size - listState.layoutInfo.visibleItemsInfo.lastIndex - 1].id - 1),
@@ -901,7 +899,7 @@ private fun bottomEndFloatingButton(
modifier = Modifier.size(48.dp)
) {
Icon(
imageVector = Icons.Default.KeyboardArrowDown,
painter = painterResource(R.drawable.ic_keyboard_arrow_down),
contentDescription = null,
tint = MaterialTheme.colors.primary
)

View File

@@ -1,31 +1,30 @@
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.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
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) {
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage
Row(
Modifier
.height(60.dp)
.fillMaxWidth()
.padding(top = 8.dp)
.background(SentColorLight),
.background(sentColor),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
Icons.Filled.InsertDriveFile,
painterResource(R.drawable.ic_draft_filled),
stringResource(R.string.icon_descr_file),
Modifier
.padding(start = 4.dp, end = 2.dp)
@@ -37,7 +36,7 @@ fun ComposeFileView(fileName: String, cancelFile: () -> Unit, cancelEnabled: Boo
if (cancelEnabled) {
IconButton(onClick = cancelFile, modifier = Modifier.padding(0.dp)) {
Icon(
Icons.Outlined.Close,
painterResource(R.drawable.ic_close),
contentDescription = stringResource(R.string.icon_descr_cancel_file_preview),
tint = MaterialTheme.colors.primary,
modifier = Modifier.padding(10.dp)

View File

@@ -6,31 +6,26 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Videocam
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.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.res.painterResource
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.durationText
import chat.simplex.app.ui.theme.DEFAULT_PADDING_HALF
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.views.chat.item.SentColorLight
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.UploadContent
import chat.simplex.app.views.helpers.base64ToBitmap
@Composable
fun ComposeImageView(media: ComposePreview.MediaPreview, cancelImages: () -> Unit, cancelEnabled: Boolean) {
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage
Row(
Modifier
.padding(top = 8.dp)
.background(SentColorLight),
.background(sentColor),
verticalAlignment = Alignment.CenterVertically,
) {
LazyRow(
@@ -49,7 +44,7 @@ fun ComposeImageView(media: ComposePreview.MediaPreview, cancelImages: () -> Uni
modifier = Modifier.widthIn(max = 80.dp).height(60.dp)
)
Icon(
Icons.Default.Videocam,
painterResource(R.drawable.ic_videocam_filled),
"preview video",
Modifier
.size(20.dp),
@@ -69,7 +64,7 @@ fun ComposeImageView(media: ComposePreview.MediaPreview, cancelImages: () -> Uni
if (cancelEnabled) {
IconButton(onClick = cancelImages) {
Icon(
Icons.Outlined.Close,
painterResource(R.drawable.ic_close),
contentDescription = stringResource(R.string.icon_descr_cancel_image_preview),
tint = MaterialTheme.colors.primary,
)

View File

@@ -14,14 +14,11 @@ import android.os.Build
import android.provider.MediaStore
import android.webkit.MimeTypeMap
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
@@ -29,13 +26,13 @@ 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.painterResource
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.*
@@ -179,8 +176,7 @@ fun ComposeView(
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 maxFileSize = getMaxFileSize(FileProtocol.XFTP)
val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground)
val textStyle = remember { mutableStateOf(smallFont) }
val cameraLauncher = rememberCameraLauncher { uri: Uri? ->
@@ -261,13 +257,17 @@ fun ComposeView(
}
}
}
val mediaLauncherWithFiles = rememberGetMultipleContentsLauncher { processPickedMedia(it, null) }
val galleryImageLauncher = rememberLauncherForActivityResult(contract = PickMultipleImagesFromGallery()) { processPickedMedia(it, null) }
val galleryImageLauncherFallback = rememberGetMultipleContentsLauncher { processPickedMedia(it, null) }
val galleryVideoLauncher = rememberLauncherForActivityResult(contract = PickMultipleVideosFromGallery()) { processPickedMedia(it, null) }
val galleryVideoLauncherFallback = rememberGetMultipleContentsLauncher { processPickedMedia(it, null) }
val filesLauncher = rememberGetContentLauncher { processPickedFile(it, null) }
val recState: MutableState<RecordingState> = remember { mutableStateOf(RecordingState.NotStarted) }
LaunchedEffect(attachmentOption.value) {
when (attachmentOption.value) {
AttachmentOption.TakePhoto -> {
AttachmentOption.CameraPhoto -> {
when (PackageManager.PERMISSION_GRANTED) {
ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) -> {
cameraLauncher.launchWithFallback()
@@ -278,11 +278,23 @@ fun ComposeView(
}
attachmentOption.value = null
}
AttachmentOption.PickMedia -> {
mediaLauncherWithFiles.launch(if (xftpSendEnabled) "image/*;video/*" else "image/*")
AttachmentOption.GalleryImage -> {
try {
galleryImageLauncher.launch(0)
} catch (e: ActivityNotFoundException) {
galleryImageLauncherFallback.launch("image/*")
}
attachmentOption.value = null
}
AttachmentOption.PickFile -> {
AttachmentOption.GalleryVideo -> {
try {
galleryVideoLauncher.launch(0)
} catch (e: ActivityNotFoundException) {
galleryVideoLauncherFallback.launch("video/*")
}
attachmentOption.value = null
}
AttachmentOption.File -> {
filesLauncher.launch("*/*")
attachmentOption.value = null
}
@@ -636,10 +648,10 @@ fun ComposeView(
fun contextItemView() {
when (val contextItem = composeState.value.contextItem) {
ComposeContextItem.NoContextItem -> {}
is ComposeContextItem.QuotedItem -> ContextItemView(contextItem.chatItem, Icons.Outlined.Reply) {
is ComposeContextItem.QuotedItem -> ContextItemView(contextItem.chatItem, painterResource(R.drawable.ic_reply)) {
composeState.value = composeState.value.copy(contextItem = ComposeContextItem.NoContextItem)
}
is ComposeContextItem.EditingItem -> ContextItemView(contextItem.chatItem, Icons.Filled.Edit) {
is ComposeContextItem.EditingItem -> ContextItemView(contextItem.chatItem, painterResource(R.drawable.ic_edit_filled)) {
clearState()
}
}
@@ -651,7 +663,7 @@ fun ComposeView(
when (val shared = chatModel.sharedContent.value) {
is SharedContent.Text -> onMessageChange(shared.text)
is SharedContent.Images -> processPickedMedia(shared.uris, shared.text)
is SharedContent.Media -> processPickedMedia(shared.uris, shared.text)
is SharedContent.File -> processPickedFile(shared.uri, shared.text)
null -> {}
}
@@ -674,9 +686,9 @@ fun ComposeView(
) {
IconButton(showChooseAttachment, enabled = !composeState.value.attachmentDisabled && rememberUpdatedState(chat.userCanSend).value) {
Icon(
Icons.Filled.AttachFile,
painterResource(R.drawable.ic_attach_file_filled_500),
contentDescription = stringResource(R.string.attach),
tint = if (!composeState.value.attachmentDisabled && userCanSend.value) MaterialTheme.colors.primary else HighOrLowlight,
tint = if (!composeState.value.attachmentDisabled && userCanSend.value) MaterialTheme.colors.primary else MaterialTheme.colors.secondary,
modifier = Modifier
.size(28.dp)
.clip(CircleShape)

View File

@@ -2,13 +2,11 @@ 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.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@@ -16,7 +14,6 @@ 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
@@ -59,12 +56,13 @@ fun ComposeVoiceView(
.height(3.dp)
.background(MaterialTheme.colors.primary)
)
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage
Row(
Modifier
.height(60.dp)
.fillMaxWidth()
.padding(top = 8.dp)
.background(SentColorLight),
.background(sentColor),
verticalAlignment = Alignment.CenterVertically
) {
IconButton(
@@ -77,12 +75,12 @@ fun ComposeVoiceView(
},
enabled = finishedRecording) {
Icon(
if (audioPlaying.value) Icons.Filled.Pause else Icons.Filled.PlayArrow,
if (audioPlaying.value) painterResource(R.drawable.ic_pause_filled) else painterResource(R.drawable.ic_play_arrow_filled),
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
tint = if (finishedRecording) MaterialTheme.colors.primary else MaterialTheme.colors.secondary
)
}
val numberInText = remember(recordedDurationMs, progress.value) {
@@ -97,7 +95,7 @@ fun ComposeVoiceView(
Text(
durationText(numberInText.value),
fontSize = 18.sp,
color = HighOrLowlight,
color = MaterialTheme.colors.secondary,
)
Spacer(Modifier.weight(1f))
if (cancelEnabled) {
@@ -109,7 +107,7 @@ fun ComposeVoiceView(
modifier = Modifier.padding(0.dp)
) {
Icon(
Icons.Outlined.Close,
painterResource(R.drawable.ic_close),
contentDescription = stringResource(R.string.icon_descr_cancel_file_preview),
tint = MaterialTheme.colors.primary,
modifier = Modifier.padding(10.dp)

View File

@@ -1,9 +1,9 @@
package chat.simplex.app.views.chat
import InfoRow
import SectionDivider
import SectionBottomSpacer
import SectionDividerSpaced
import SectionItemView
import SectionSpacer
import SectionTextFooter
import SectionView
import androidx.compose.foundation.*
@@ -12,7 +12,6 @@ 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
@@ -50,7 +49,6 @@ fun ContactPreferencesView(
if (featuresAllowed == currentFeaturesAllowed) close()
else showUnsavedChangesAlert({ savePrefs(close) }, close)
},
background = if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
) {
ContactPreferencesLayout(
featuresAllowed,
@@ -81,9 +79,7 @@ private fun ContactPreferencesLayout(
Column(
Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState())
.padding(bottom = DEFAULT_PADDING),
horizontalAlignment = Alignment.Start,
.verticalScroll(rememberScrollState()),
) {
AppBarTitle(stringResource(R.string.contact_preferences))
val timedMessages: MutableState<Boolean> = remember(featuresAllowed) { mutableStateOf(featuresAllowed.timedMessagesAllowed) }
@@ -93,27 +89,28 @@ private fun ContactPreferencesLayout(
TimedMessagesFeatureSection(featuresAllowed, contact.mergedPreferences.timedMessages, timedMessages, onTTLUpdated) { allowed, ttl ->
applyPrefs(featuresAllowed.copy(timedMessagesAllowed = allowed, timedMessagesTTL = ttl ?: currentFeaturesAllowed.timedMessagesTTL))
}
SectionSpacer()
SectionDividerSpaced(true, maxBottomPadding = false)
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()
SectionDividerSpaced(true, maxBottomPadding = false)
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()
SectionDividerSpaced(true, maxBottomPadding = false)
val allowCalls: MutableState<ContactFeatureAllowed> = remember(featuresAllowed) { mutableStateOf(featuresAllowed.calls) }
FeatureSection(ChatFeature.Calls, user.fullPreferences.calls.allow, contact.mergedPreferences.calls, allowCalls) {
applyPrefs(featuresAllowed.copy(calls = it))
}
SectionSpacer()
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
ResetSaveButtons(
reset = reset,
save = savePrefs,
disabled = featuresAllowed == currentFeaturesAllowed
)
SectionBottomSpacer()
}
}
@@ -133,21 +130,18 @@ private fun FeatureSection(
SectionView(
feature.text.uppercase(),
icon = feature.iconFilled,
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,
enabled = remember { mutableStateOf(feature != ChatFeature.Calls) },
onSelected = onSelected
)
}
SectionDivider()
ExposedDropDownSettingRow(
generalGetString(R.string.chat_preferences_you_allow),
ContactFeatureAllowed.values(userDefault).map { it to it.text },
allowFeature,
icon = null,
enabled = remember { mutableStateOf(feature != ChatFeature.Calls) },
onSelected = onSelected
)
InfoRow(
generalGetString(R.string.chat_preferences_contact_allows),
pref.contactPreference.allow.text
@@ -172,24 +166,20 @@ private fun TimedMessagesFeatureSection(
SectionView(
ChatFeature.TimedMessages.text.uppercase(),
icon = ChatFeature.TimedMessages.iconFilled,
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)
}
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)
@@ -204,11 +194,10 @@ private fun TimedMessagesFeatureSection(
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)
Text(stringResource(R.string.reset_verb), color = if (disabled) MaterialTheme.colors.secondary 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)
Text(stringResource(R.string.save_and_notify_contact), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary)
}
}
}
@@ -217,14 +206,12 @@ private fun ResetSaveButtons(reset: () -> Unit, save: () -> Unit, disabled: Bool
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
)
}
ExposedDropDownSettingRow(
generalGetString(R.string.delete_after),
values.map { it to TimedMessagesPreference.ttlText(it) },
selection,
onSelected = onSelected
)
}
private fun showUnsavedChangesAlert(save: () -> Unit, revert: () -> Unit) {

View File

@@ -3,34 +3,34 @@ 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.ui.graphics.painter.Painter
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.painterResource
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.ui.theme.*
import chat.simplex.app.views.chat.item.*
import kotlinx.datetime.Clock
@Composable
fun ContextItemView(
contextItem: ChatItem,
contextIcon: ImageVector,
contextIcon: Painter,
cancelContextItem: () -> Unit
) {
val sent = contextItem.chatDir.sent
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage
val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage
Row(
Modifier
.padding(top = 8.dp)
.background(if (sent) SentColorLight else ReceivedColorLight),
.background(if (sent) sentColor else receivedColor),
verticalAlignment = Alignment.CenterVertically
) {
Row(
@@ -47,7 +47,7 @@ fun ContextItemView(
.height(20.dp)
.width(20.dp),
contentDescription = stringResource(R.string.icon_descr_context),
tint = HighOrLowlight,
tint = MaterialTheme.colors.secondary,
)
MarkdownText(
contextItem.text, contextItem.formattedText,
@@ -58,7 +58,7 @@ fun ContextItemView(
}
IconButton(onClick = cancelContextItem) {
Icon(
Icons.Outlined.Close,
painterResource(R.drawable.ic_close),
contentDescription = stringResource(R.string.cancel_verb),
tint = MaterialTheme.colors.primary,
modifier = Modifier.padding(10.dp)
@@ -73,8 +73,7 @@ fun PreviewContextItemView() {
SimpleXTheme {
ContextItemView(
contextItem = ChatItem.getSampleData(1, CIDirection.DirectRcv(), Clock.System.now(), "hello"),
contextIcon = Icons.Filled.Edit,
cancelContextItem = {}
)
contextIcon = painterResource(R.drawable.ic_edit_filled)
) {}
}
}

View File

@@ -20,9 +20,6 @@ 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
@@ -30,8 +27,9 @@ 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.graphics.painter.Painter
import androidx.compose.ui.platform.*
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.TextStyle
@@ -46,7 +44,7 @@ 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.CurrentColors
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.chat.item.ItemAction
import chat.simplex.app.views.helpers.*
@@ -146,7 +144,7 @@ fun SendMsgView(
}
else -> {
val cs = composeState.value
val icon = if (cs.editing || cs.liveMessage != null) Icons.Filled.Check else Icons.Outlined.ArrowUpward
val icon = if (cs.editing || cs.liveMessage != null) painterResource(R.drawable.ic_check_filled) else painterResource(R.drawable.ic_arrow_upward)
val disabled = !cs.sendEnabled() ||
(!allowedVoiceByPrefs && cs.preview is ComposePreview.VoicePreview) ||
cs.endLiveDisabled
@@ -163,7 +161,7 @@ fun SendMsgView(
) {
ItemAction(
generalGetString(R.string.send_live_message),
Icons.Filled.Bolt,
BoltFilled,
onClick = {
startLiveMessage(scope, sendLiveMessage, updateLiveMessage, sendButtonSize, sendButtonAlpha, composeState, liveMessageAlertShown)
showDropdown.value = false
@@ -189,7 +187,7 @@ private fun NativeKeyboard(
) {
val cs = composeState.value
val textColor = MaterialTheme.colors.onBackground
val tintColor = MaterialTheme.colors.secondary
val tintColor = MaterialTheme.colors.secondaryVariant
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() }
@@ -225,7 +223,7 @@ private fun NativeKeyboard(
} catch (e: Exception) {
return@OnCommitContentListener false
}
SimplexApp.context.chatModel.sharedContent.value = SharedContent.Images("", listOf(inputContentInfo.contentUri))
SimplexApp.context.chatModel.sharedContent.value = SharedContent.Media("", listOf(inputContentInfo.contentUri))
true
}
return InputConnectionCompat.createWrapper(connection, editorInfo, onCommit)
@@ -242,7 +240,7 @@ private fun NativeKeyboard(
editText.setPadding(paddingStart, paddingTop, paddingEnd, paddingBottom)
editText.setText(cs.message)
if (Build.VERSION.SDK_INT >= 29) {
editText.textCursorDrawable?.let { DrawableCompat.setTint(it, HighOrLowlight.toArgb()) }
editText.textCursorDrawable?.let { DrawableCompat.setTint(it, CurrentColors.value.colors.secondary.toArgb()) }
} else {
try {
val f: Field = TextView::class.java.getDeclaredField("mCursorDrawableRes")
@@ -286,7 +284,7 @@ private fun ComposeOverlay(textId: Int, textStyle: MutableState<TextStyle>, padd
Text(
generalGetString(textId),
Modifier.padding(padding),
color = HighOrLowlight,
color = MaterialTheme.colors.secondary,
style = textStyle.value.copy(fontStyle = FontStyle.Italic)
)
}
@@ -297,7 +295,7 @@ private fun BoxScope.DeleteTextButton(composeState: MutableState<ComposeState>)
{ 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)
Icon(painterResource(R.drawable.ic_close), null, Modifier.padding(7.dp).size(36.dp), tint = MaterialTheme.colors.secondary)
}
}
@@ -354,9 +352,9 @@ private fun RecordVoiceView(recState: MutableState<RecordingState>, stopRecOnNex
private fun DisallowedVoiceButton(enabled: Boolean, onClick: () -> Unit) {
IconButton(onClick, Modifier.size(36.dp), enabled = enabled) {
Icon(
Icons.Outlined.KeyboardVoice,
painterResource(R.drawable.ic_keyboard_voice),
stringResource(R.string.icon_descr_record_voice_message),
tint = HighOrLowlight,
tint = MaterialTheme.colors.secondary,
modifier = Modifier
.size(36.dp)
.padding(4.dp)
@@ -368,7 +366,7 @@ private fun DisallowedVoiceButton(enabled: Boolean, onClick: () -> Unit) {
private fun VoiceButtonWithoutPermission(onClick: () -> Unit) {
IconButton(onClick, Modifier.size(36.dp)) {
Icon(
Icons.Filled.KeyboardVoice,
painterResource(R.drawable.ic_keyboard_voice_filled),
stringResource(R.string.icon_descr_record_voice_message),
tint = MaterialTheme.colors.primary,
modifier = Modifier
@@ -400,7 +398,7 @@ private fun LockToCurrentOrientationUntilDispose() {
private fun StopRecordButton(onClick: () -> Unit) {
IconButton(onClick, Modifier.size(36.dp)) {
Icon(
Icons.Filled.Stop,
painterResource(R.drawable.ic_stop_filled),
stringResource(R.string.icon_descr_record_voice_message),
tint = MaterialTheme.colors.primary,
modifier = Modifier
@@ -414,7 +412,7 @@ private fun StopRecordButton(onClick: () -> Unit) {
private fun RecordVoiceButton(interactionSource: MutableInteractionSource) {
IconButton({}, Modifier.size(36.dp), interactionSource = interactionSource) {
Icon(
Icons.Filled.KeyboardVoice,
painterResource(R.drawable.ic_keyboard_voice_filled),
stringResource(R.string.icon_descr_record_voice_message),
tint = MaterialTheme.colors.primary,
modifier = Modifier
@@ -426,7 +424,7 @@ private fun RecordVoiceButton(interactionSource: MutableInteractionSource) {
@Composable
private fun ProgressIndicator() {
CircularProgressIndicator(Modifier.size(36.dp).padding(4.dp), color = HighOrLowlight, strokeWidth = 3.dp)
CircularProgressIndicator(Modifier.size(36.dp).padding(4.dp), color = MaterialTheme.colors.secondary, strokeWidth = 3.dp)
}
@Composable
@@ -435,7 +433,7 @@ private fun CancelLiveMessageButton(
) {
IconButton(onClick, Modifier.size(36.dp)) {
Icon(
Icons.Filled.Close,
painterResource(R.drawable.ic_close),
stringResource(R.string.icon_descr_cancel_live_message),
tint = MaterialTheme.colors.primary,
modifier = Modifier
@@ -447,7 +445,7 @@ private fun CancelLiveMessageButton(
@Composable
private fun SendMsgButton(
icon: ImageVector,
icon: Painter,
sizeDp: Animatable<Float, AnimationVector1D>,
alpha: Animatable<Float, AnimationVector1D>,
enabled: Boolean,
@@ -476,7 +474,7 @@ private fun SendMsgButton(
.padding(4.dp)
.alpha(alpha.value)
.clip(CircleShape)
.background(if (enabled) MaterialTheme.colors.primary else HighOrLowlight)
.background(if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary)
.padding(3.dp)
)
}
@@ -497,9 +495,9 @@ private fun StartLiveMessageButton(enabled: Boolean, onClick: () -> Unit) {
contentAlignment = Alignment.Center
) {
Icon(
Icons.Filled.Bolt,
BoltFilled,
stringResource(R.string.icon_descr_send_message),
tint = if (enabled) MaterialTheme.colors.primary else HighOrLowlight,
tint = if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary,
modifier = Modifier
.size(36.dp)
.padding(4.dp)

View File

@@ -1,18 +1,17 @@
package chat.simplex.app.views.chat
import SectionBottomSpacer
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.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp
@@ -66,7 +65,7 @@ private fun VerifyCodeLayout(
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)
Icon(painterResource(R.drawable.ic_verified_user), null, Modifier.padding(end = 4.dp).size(22.dp), tint = MaterialTheme.colors.secondary)
Text(String.format(stringResource(R.string.is_verified), displayName))
} else {
Text(String.format(stringResource(R.string.is_not_verified), displayName))
@@ -90,7 +89,7 @@ private fun VerifyCodeLayout(
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)
Icon(painterResource(R.drawable.ic_share_filled), null, tint = MaterialTheme.colors.primary)
}
}
Spacer(Modifier.weight(1f))
@@ -106,16 +105,16 @@ private fun VerifyCodeLayout(
horizontalArrangement = Arrangement.spacedBy(10.dp)
) {
if (connectionVerified) {
SimpleButton(generalGetString(R.string.clear_verification), Icons.Outlined.Shield) {
SimpleButton(generalGetString(R.string.clear_verification), painterResource(R.drawable.ic_shield)) {
verifyCode(null) {}
}
} else {
SimpleButton(generalGetString(R.string.scan_code), Icons.Outlined.QrCode) {
SimpleButton(generalGetString(R.string.scan_code), painterResource(R.drawable.ic_qr_code)) {
ModalManager.shared.showModal {
ScanCodeView(verifyCode) { }
}
}
SimpleButton(generalGetString(R.string.mark_code_verified), Icons.Outlined.VerifiedUser) {
SimpleButton(generalGetString(R.string.mark_code_verified), painterResource(R.drawable.ic_verified_user)) {
verifyCode(connectionCode) { verified ->
if (!verified) {
AlertManager.shared.showAlertMsg(
@@ -126,6 +125,7 @@ private fun VerifyCodeLayout(
}
}
}
SectionBottomSpacer()
}
}

View File

@@ -1,7 +1,8 @@
package chat.simplex.app.views.chat.group
import SectionBottomSpacer
import SectionCustomFooter
import SectionDivider
import SectionDividerSpaced
import SectionItemView
import SectionSpacer
import SectionView
@@ -9,15 +10,12 @@ 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.graphics.painter.Painter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
@@ -105,7 +103,6 @@ fun AddGroupMembersLayout(
Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.Start,
) {
AppBarTitle(stringResource(R.string.button_add_members))
InfoAboutIncognito(
@@ -136,7 +133,7 @@ fun AddGroupMembersLayout(
Text(
stringResource(R.string.no_contacts_to_add),
Modifier.padding(),
color = HighOrLowlight
color = MaterialTheme.colors.secondary
)
}
} else {
@@ -145,12 +142,8 @@ fun AddGroupMembersLayout(
SectionItemView(openPreferences) {
Text(stringResource(R.string.set_group_preferences))
}
SectionDivider()
}
SectionItemView {
RoleSelectionRow(groupInfo, selectedRole, allowModifyMembers)
}
SectionDivider()
RoleSelectionRow(groupInfo, selectedRole, allowModifyMembers)
if (creatingGroup && selectedContacts.isEmpty()) {
SkipInvitingButton(close)
} else {
@@ -160,13 +153,13 @@ fun AddGroupMembersLayout(
SectionCustomFooter {
InviteSectionFooter(selectedContactsCount = selectedContacts.size, allowModifyMembers, clearSelection)
}
SectionSpacer()
SectionDividerSpaced(maxTopPadding = true)
SectionView(stringResource(R.string.select_contacts)) {
ContactList(contacts = contactsToAdd, selectedContacts, groupInfo, allowModifyMembers, addContact, removeContact)
}
SectionSpacer()
}
SectionBottomSpacer()
}
}
@@ -183,16 +176,15 @@ private fun RoleSelectionRow(groupInfo: GroupInfo, selectedRole: MutableState<Gr
values,
selectedRole,
icon = null,
enabled = rememberUpdatedState(enabled),
onSelected = { selectedRole.value = it }
)
enabled = rememberUpdatedState(enabled)
) { selectedRole.value = it }
}
}
@Composable
fun InviteMembersButton(onClick: () -> Unit, disabled: Boolean) {
SettingsActionItem(
Icons.Outlined.Check,
painterResource(R.drawable.ic_check),
stringResource(R.string.invite_to_group_button),
click = onClick,
textColor = MaterialTheme.colors.primary,
@@ -204,7 +196,7 @@ fun InviteMembersButton(onClick: () -> Unit, disabled: Boolean) {
@Composable
fun SkipInvitingButton(onClick: () -> Unit) {
SettingsActionItem(
Icons.Outlined.Check,
painterResource(R.drawable.ic_check),
stringResource(R.string.skip_inviting_button),
click = onClick,
textColor = MaterialTheme.colors.primary,
@@ -222,7 +214,7 @@ fun InviteSectionFooter(selectedContactsCount: Int, enabled: Boolean, clearSelec
if (selectedContactsCount >= 1) {
Text(
String.format(generalGetString(R.string.num_contacts_selected), selectedContactsCount),
color = HighOrLowlight,
color = MaterialTheme.colors.secondary,
fontSize = 12.sp
)
Box(
@@ -230,14 +222,14 @@ fun InviteSectionFooter(selectedContactsCount: Int, enabled: Boolean, clearSelec
) {
Text(
stringResource(R.string.clear_contacts_selection_button),
color = if (enabled) MaterialTheme.colors.primary else HighOrLowlight,
color = if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary,
fontSize = 12.sp
)
}
} else {
Text(
stringResource(R.string.no_contacts_selected),
color = HighOrLowlight,
color = MaterialTheme.colors.secondary,
fontSize = 12.sp
)
}
@@ -260,9 +252,6 @@ fun ContactList(
checked = selectedContacts.contains(contact.apiId),
enabled = enabled,
)
if (index < contacts.lastIndex) {
SectionDivider()
}
}
}
}
@@ -277,17 +266,17 @@ fun ContactCheckRow(
enabled: Boolean,
) {
val prohibitedToInviteIncognito = !groupInfo.membership.memberIncognito && contact.contactConnIncognito
val icon: ImageVector
val icon: Painter
val iconColor: Color
if (prohibitedToInviteIncognito) {
icon = Icons.Filled.TheaterComedy
iconColor = HighOrLowlight
icon = painterResource(R.drawable.ic_theater_comedy_filled)
iconColor = MaterialTheme.colors.secondary
} else if (checked) {
icon = Icons.Filled.CheckCircle
iconColor = if (enabled) MaterialTheme.colors.primary else HighOrLowlight
icon = painterResource(R.drawable.ic_check_circle_filled)
iconColor = if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary
} else {
icon = Icons.Outlined.Circle
iconColor = HighOrLowlight
icon = painterResource(R.drawable.ic_circle)
iconColor = MaterialTheme.colors.secondary
}
SectionItemView(
click = if (enabled) {
@@ -305,7 +294,7 @@ fun ContactCheckRow(
Spacer(Modifier.width(DEFAULT_SPACE_AFTER_ICON))
Text(
contact.chatViewName, maxLines = 1, overflow = TextOverflow.Ellipsis,
color = if (prohibitedToInviteIncognito) HighOrLowlight else Color.Unspecified
color = if (prohibitedToInviteIncognito) MaterialTheme.colors.secondary else Color.Unspecified
)
Spacer(Modifier.fillMaxWidth().weight(1f))
Icon(

View File

@@ -1,7 +1,8 @@
package chat.simplex.app.views.chat.group
import InfoRow
import SectionDivider
import SectionBottomSpacer
import SectionDividerSpaced
import SectionItemView
import SectionSpacer
import SectionTextFooter
@@ -11,12 +12,11 @@ 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.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
@@ -108,7 +108,7 @@ fun deleteGroupDialog(chatInfo: ChatInfo, groupInfo: GroupInfo, chatModel: ChatM
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(
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.delete_group_question),
text = generalGetString(alertTextKey),
confirmText = generalGetString(R.string.delete_verb),
@@ -122,12 +122,13 @@ fun deleteGroupDialog(chatInfo: ChatInfo, groupInfo: GroupInfo, chatModel: ChatM
close?.invoke()
}
}
}
},
destructive = true,
)
}
fun leaveGroupDialog(groupInfo: GroupInfo, chatModel: ChatModel, close: (() -> Unit)? = null) {
AlertManager.shared.showAlertMsg(
AlertManager.shared.showAlertDialog(
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),
@@ -136,7 +137,8 @@ fun leaveGroupDialog(groupInfo: GroupInfo, chatModel: ChatModel, close: (() -> U
chatModel.controller.leaveGroup(groupInfo.groupId)
close?.invoke()
}
}
},
destructive = true,
)
}
@@ -160,8 +162,7 @@ fun GroupChatInfoLayout(
Column(
Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.Start
.verticalScroll(rememberScrollState())
) {
Row(
Modifier.fillMaxWidth(),
@@ -173,63 +174,50 @@ fun GroupChatInfoLayout(
SectionView {
if (groupInfo.canEdit) {
SectionItemView(editGroupProfile) { EditGroupProfileButton() }
SectionDivider()
SectionItemView(addOrEditWelcomeMessage) { AddOrEditWelcomeMessage(groupInfo.groupProfile.description) }
SectionDivider()
EditGroupProfileButton(editGroupProfile)
AddOrEditWelcomeMessage(groupInfo.groupProfile.description, addOrEditWelcomeMessage)
}
GroupPreferencesButton(openPreferences)
}
SectionTextFooter(stringResource(R.string.only_group_owners_can_change_prefs))
SectionSpacer()
SectionDividerSpaced(maxTopPadding = true)
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()
}
if (groupLink == null) {
CreateGroupLinkButton(manageGroupLink)
} else {
GroupLinkButton(manageGroupLink)
}
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()
val tint = if (chat.chatInfo.incognito) MaterialTheme.colors.secondary else MaterialTheme.colors.primary
AddMembersButton(tint, onAddMembersClick)
}
SectionItemView(minHeight = 50.dp) {
SectionItemView(minHeight = 54.dp) {
MemberRow(groupInfo.membership, user = true)
}
if (members.isNotEmpty()) {
SectionDivider()
}
MembersList(members, showMemberInfo)
}
SectionSpacer()
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
SectionView {
ClearChatButton(clearChat)
if (groupInfo.canDelete) {
SectionDivider()
SectionItemView(deleteGroup) { DeleteGroupButton() }
DeleteGroupButton(deleteGroup)
}
if (groupInfo.membership.memberCurrent) {
SectionDivider()
SectionItemView(leaveGroup) { LeaveGroupButton() }
LeaveGroupButton(leaveGroup)
}
}
SectionSpacer()
if (developerTools) {
SectionDividerSpaced()
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()
}
SectionBottomSpacer()
}
}
@@ -260,38 +248,31 @@ private fun GroupChatInfoHeader(cInfo: ChatInfo) {
@Composable
private fun GroupPreferencesButton(onClick: () -> Unit) {
SettingsActionItem(
Icons.Outlined.ToggleOn,
painterResource(R.drawable.ic_toggle_on),
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)
}
private fun AddMembersButton(tint: Color = MaterialTheme.colors.primary, onClick: () -> Unit) {
SettingsActionItem(
painterResource(R.drawable.ic_add),
stringResource(R.string.button_add_members),
onClick,
iconColor = tint,
textColor = tint
)
}
@Composable
private fun MembersList(members: List<GroupMember>, showMemberInfo: (GroupMember) -> Unit) {
Column {
members.forEachIndexed { index, member ->
SectionItemView({ showMemberInfo(member) }, minHeight = 50.dp) {
Divider()
SectionItemView({ showMemberInfo(member) }, minHeight = 54.dp) {
MemberRow(member)
}
if (index < members.lastIndex) {
SectionDivider()
}
}
}
}
@@ -309,6 +290,7 @@ private fun MemberRow(member: GroupMember, user: Boolean = false) {
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
ProfileImage(size = 46.dp, member.image)
Spacer(Modifier.width(DEFAULT_PADDING_HALF))
Column {
Row(verticalAlignment = Alignment.CenterVertically) {
if (member.verified) {
@@ -323,7 +305,7 @@ private fun MemberRow(member: GroupMember, user: Boolean = false) {
val statusDescr = if (user) String.format(generalGetString(R.string.group_info_member_you), s) else s
Text(
statusDescr,
color = HighOrLowlight,
color = MaterialTheme.colors.secondary,
fontSize = 12.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis
@@ -332,119 +314,81 @@ private fun MemberRow(member: GroupMember, user: Boolean = false) {
}
val role = member.memberRole
if (role == GroupMemberRole.Owner || role == GroupMemberRole.Admin) {
Text(role.text, color = HighOrLowlight)
Text(role.text, color = MaterialTheme.colors.secondary)
}
}
}
@Composable
private fun MemberVerifiedShield() {
Icon(Icons.Outlined.VerifiedUser, null, Modifier.padding(end = 3.dp).size(16.dp), tint = HighOrLowlight)
Icon(painterResource(R.drawable.ic_verified_user), null, Modifier.padding(end = 3.dp).size(16.dp), tint = MaterialTheme.colors.secondary)
}
@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))
}
private fun GroupLinkButton(onClick: () -> Unit) {
SettingsActionItem(
painterResource(R.drawable.ic_link),
stringResource(R.string.group_link),
onClick,
iconColor = MaterialTheme.colors.secondary
)
}
@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))
}
private fun CreateGroupLinkButton(onClick: () -> Unit) {
SettingsActionItem(
painterResource(R.drawable.ic_add_link),
stringResource(R.string.create_group_link),
onClick,
iconColor = MaterialTheme.colors.secondary
)
}
@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))
}
fun EditGroupProfileButton(onClick: () -> Unit) {
SettingsActionItem(
painterResource(R.drawable.ic_edit),
stringResource(R.string.button_edit_group_profile),
onClick,
iconColor = MaterialTheme.colors.secondary
)
}
@Composable
private fun AddOrEditWelcomeMessage(welcomeMessage: String?) {
private fun AddOrEditWelcomeMessage(welcomeMessage: String?, onClick: () -> Unit) {
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)
}
SettingsActionItem(
painterResource(R.drawable.ic_maps_ugc),
text,
onClick,
iconColor = MaterialTheme.colors.secondary
)
}
@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)
}
private fun LeaveGroupButton(onClick: () -> Unit) {
SettingsActionItem(
painterResource(R.drawable.ic_logout),
stringResource(R.string.button_leave_group),
onClick,
iconColor = Color.Red,
textColor = 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)
}
private fun DeleteGroupButton(onClick: () -> Unit) {
SettingsActionItem(
painterResource(R.drawable.ic_delete),
stringResource(R.string.button_delete_group),
onClick,
iconColor = Color.Red,
textColor = Color.Red
)
}
@Preview

View File

@@ -1,19 +1,17 @@
package chat.simplex.app.views.chat.group
import SectionItemView
import SectionBottomSpacer
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.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.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -67,7 +65,7 @@ fun GroupLinkView(chatModel: ChatModel, groupInfo: GroupInfo, connReqContact: St
}
},
deleteLink = {
AlertManager.shared.showAlertMsg(
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.delete_link_question),
text = generalGetString(R.string.all_group_members_will_remain_connected),
confirmText = generalGetString(R.string.delete_verb),
@@ -79,7 +77,8 @@ fun GroupLinkView(chatModel: ChatModel, groupInfo: GroupInfo, connReqContact: St
onGroupLinkUpdated(null to null)
}
}
}
},
destructive = true,
)
}
)
@@ -101,15 +100,12 @@ fun GroupLinkLayout(
) {
Column(
Modifier
.verticalScroll(rememberScrollState())
.padding(start = DEFAULT_PADDING, bottom = DEFAULT_BOTTOM_PADDING, end = DEFAULT_PADDING),
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.Top
.verticalScroll(rememberScrollState()),
) {
AppBarTitle(stringResource(R.string.group_link), false)
AppBarTitle(stringResource(R.string.group_link))
Text(
stringResource(R.string.you_can_share_group_link_anybody_will_be_able_to_connect),
Modifier.padding(bottom = 12.dp),
Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = 12.dp),
lineHeight = 22.sp
)
Column(
@@ -118,11 +114,9 @@ fun GroupLinkLayout(
verticalArrangement = Arrangement.SpaceEvenly
) {
if (groupLink == null) {
SimpleButton(stringResource(R.string.button_create_group_link), icon = Icons.Outlined.AddLink, disabled = creatingLink, click = createLink)
SimpleButton(stringResource(R.string.button_create_group_link), icon = painterResource(R.drawable.ic_add_link), disabled = creatingLink, click = createLink)
} else {
SectionItemView(padding = PaddingValues(bottom = DEFAULT_PADDING)) {
RoleSelectionRow(groupInfo, groupLinkMemberRole)
}
RoleSelectionRow(groupInfo, groupLinkMemberRole)
var initialLaunch by remember { mutableStateOf(true) }
LaunchedEffect(groupLinkMemberRole.value) {
if (!initialLaunch) {
@@ -130,26 +124,27 @@ fun GroupLinkLayout(
}
initialLaunch = false
}
QRCode(groupLink, Modifier.aspectRatio(1f))
QRCode(groupLink, Modifier.aspectRatio(1f).padding(horizontal = DEFAULT_PADDING))
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = 10.dp)
modifier = Modifier.padding(horizontal = DEFAULT_PADDING, vertical = 10.dp)
) {
SimpleButton(
stringResource(R.string.share_link),
icon = Icons.Outlined.Share,
icon = painterResource(R.drawable.ic_share),
click = share
)
SimpleButton(
stringResource(R.string.delete_link),
icon = Icons.Outlined.Delete,
icon = painterResource(R.drawable.ic_delete),
color = Color.Red,
click = deleteLink
)
}
}
}
SectionBottomSpacer()
}
}
@@ -166,9 +161,8 @@ private fun RoleSelectionRow(groupInfo: GroupInfo, selectedRole: MutableState<Gr
values,
selectedRole,
icon = null,
enabled = rememberUpdatedState(enabled),
onSelected = { selectedRole.value = it }
)
enabled = rememberUpdatedState(enabled)
) { selectedRole.value = it }
}
}
@@ -182,7 +176,7 @@ fun ProgressIndicator() {
Modifier
.padding(horizontal = 2.dp)
.size(30.dp),
color = HighOrLowlight,
color = MaterialTheme.colors.secondary,
strokeWidth = 2.5.dp
)
}

View File

@@ -1,20 +1,19 @@
package chat.simplex.app.views.chat.group
import InfoRow
import SectionDivider
import SectionItemView
import SectionBottomSpacer
import SectionDividerSpaced
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.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
@@ -117,7 +116,7 @@ fun GroupMemberInfoView(
}
fun removeMemberDialog(groupInfo: GroupInfo, member: GroupMember, chatModel: ChatModel, close: (() -> Unit)? = null) {
AlertManager.shared.showAlertMsg(
AlertManager.shared.showAlertDialog(
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),
@@ -129,7 +128,8 @@ fun removeMemberDialog(groupInfo: GroupInfo, member: GroupMember, chatModel: Cha
}
close?.invoke()
}
}
},
destructive = true,
)
}
@@ -152,7 +152,6 @@ fun GroupMemberInfoLayout(
Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.Start
) {
Row(
Modifier.fillMaxWidth(),
@@ -169,9 +168,6 @@ fun GroupMemberInfoLayout(
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)
@@ -183,36 +179,30 @@ fun GroupMemberInfoLayout(
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)
}
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) {
SectionDividerSpaced()
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()) {
@@ -220,24 +210,23 @@ fun GroupMemberInfoLayout(
}
}
}
SectionSpacer()
}
if (member.canBeRemoved(groupInfo)) {
SectionDividerSpaced(maxBottomPadding = false)
SectionView {
RemoveMemberButton(removeMember)
}
SectionSpacer()
}
if (developerTools) {
SectionDividerSpaced()
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()
}
SectionBottomSpacer()
}
}
@@ -250,7 +239,7 @@ fun GroupMemberInfoHeader(member: GroupMember) {
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)
Icon(painterResource(R.drawable.ic_verified_user), null, Modifier.padding(end = 6.dp, top = 4.dp).size(24.dp), tint = MaterialTheme.colors.secondary)
}
Text(
member.displayName, style = MaterialTheme.typography.h1.copy(fontWeight = FontWeight.Normal),
@@ -273,7 +262,7 @@ fun GroupMemberInfoHeader(member: GroupMember) {
@Composable
fun RemoveMemberButton(onClick: () -> Unit) {
SettingsActionItem(
Icons.Outlined.Delete,
painterResource(R.drawable.ic_delete),
stringResource(R.string.button_remove_member),
click = onClick,
textColor = Color.Red,
@@ -284,7 +273,7 @@ fun RemoveMemberButton(onClick: () -> Unit) {
@Composable
fun OpenChatButton(onClick: () -> Unit) {
SettingsActionItem(
Icons.Outlined.Message,
painterResource(R.drawable.ic_chat),
stringResource(R.string.button_send_direct_message),
click = onClick,
textColor = MaterialTheme.colors.primary,

View File

@@ -1,9 +1,9 @@
package chat.simplex.app.views.chat.group
import InfoRow
import SectionDivider
import SectionBottomSpacer
import SectionDividerSpaced
import SectionItemView
import SectionSpacer
import SectionTextFooter
import SectionView
import androidx.compose.foundation.*
@@ -12,7 +12,6 @@ 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
@@ -45,7 +44,6 @@ fun GroupPreferencesView(m: ChatModel, chatId: String, close: () -> Unit,) {
if (preferences == currentPreferences) close()
else showUnsavedChangesAlert({ savePrefs(close) }, close)
},
background = if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
) {
GroupPreferencesLayout(
preferences,
@@ -73,7 +71,6 @@ private fun GroupPreferencesLayout(
) {
Column(
Modifier.fillMaxWidth().verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.Start,
) {
AppBarTitle(stringResource(R.string.group_preferences))
val timedMessages = remember(preferences) { mutableStateOf(preferences.timedMessages.enable) }
@@ -87,29 +84,30 @@ private fun GroupPreferencesLayout(
applyPrefs(preferences.copy(timedMessages = TimedMessagesGroupPreference(enable = enable, ttl = currentPreferences.timedMessages.ttl)))
}
}
SectionSpacer()
SectionDividerSpaced(true, maxBottomPadding = false)
val allowDirectMessages = remember(preferences) { mutableStateOf(preferences.directMessages.enable) }
FeatureSection(GroupFeature.DirectMessages, allowDirectMessages, groupInfo, preferences, onTTLUpdated) {
applyPrefs(preferences.copy(directMessages = GroupPreference(enable = it)))
}
SectionSpacer()
SectionDividerSpaced(true, maxBottomPadding = false)
val allowFullDeletion = remember(preferences) { mutableStateOf(preferences.fullDelete.enable) }
FeatureSection(GroupFeature.FullDelete, allowFullDeletion, groupInfo, preferences, onTTLUpdated) {
applyPrefs(preferences.copy(fullDelete = GroupPreference(enable = it)))
}
SectionSpacer()
SectionDividerSpaced(true, maxBottomPadding = false)
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()
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
ResetSaveButtons(
reset = reset,
save = savePrefs,
disabled = preferences == currentPreferences
)
}
SectionBottomSpacer()
}
}
@@ -124,22 +122,19 @@ private fun FeatureSection(
) {
SectionView {
val on = enableFeature.value == GroupFeatureEnabled.ON
val icon = if (on) feature.iconFilled else feature.icon
val iconTint = if (on) SimplexGreen else HighOrLowlight
val icon = if (on) feature.iconFilled() else feature.icon
val iconTint = if (on) SimplexGreen else MaterialTheme.colors.secondary
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)
}
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)
}
@@ -151,7 +146,6 @@ private fun FeatureSection(
iconTint = iconTint,
)
if (timedOn) {
SectionDivider()
InfoRow(generalGetString(R.string.delete_after), TimedMessagesPreference.ttlText(preferences.timedMessages.ttl))
}
}
@@ -163,11 +157,10 @@ private fun FeatureSection(
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)
Text(stringResource(R.string.reset_verb), color = if (disabled) MaterialTheme.colors.secondary 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)
Text(stringResource(R.string.save_and_notify_group_members), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary)
}
}
}

View File

@@ -1,5 +1,6 @@
package chat.simplex.app.views.chat.group
import SectionBottomSpacer
import android.content.res.Configuration
import android.net.Uri
import androidx.compose.foundation.*
@@ -64,7 +65,7 @@ fun GroupProfileLayout(
val dataUnchanged =
displayName.value == groupProfile.displayName &&
fullName.value == groupProfile.fullName &&
chosenImage.value == null
groupProfile.image == profileImage.value
val closeWithAlert = {
if (dataUnchanged || !(displayName.value.isNotEmpty() && isValidDisplayName(displayName.value))) {
close()
@@ -113,7 +114,7 @@ fun GroupProfileLayout(
) {
Box(contentAlignment = Alignment.TopEnd) {
Box(contentAlignment = Alignment.Center) {
ProfileImage(108.dp, profileImage.value, color = HighOrLowlight.copy(alpha = 0.1f))
ProfileImage(108.dp, profileImage.value, color = MaterialTheme.colors.secondary.copy(alpha = 0.1f))
EditImageButton { scope.launch { bottomSheetModalState.show() } }
}
if (profileImage.value != null) {
@@ -162,12 +163,12 @@ fun GroupProfileLayout(
} else {
Text(
stringResource(R.string.save_group_profile),
color = HighOrLowlight
color = MaterialTheme.colors.secondary
)
}
}
Spacer(Modifier.height(DEFAULT_BOTTOM_BUTTON_PADDING))
SectionBottomSpacer()
LaunchedEffect(Unit) {
delay(300)

View File

@@ -1,5 +1,6 @@
package chat.simplex.app.views.chat.group
import SectionBottomSpacer
import SectionItemView
import SectionSpacer
import SectionView
@@ -44,7 +45,6 @@ fun GroupWelcomeView(m: ChatModel, groupInfo: GroupInfo, close: () -> Unit) {
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,
@@ -71,6 +71,7 @@ private fun GroupWelcomeLayout(
save = save,
disabled = welcomeText.value == groupInfo.groupProfile.description || (welcomeText.value == "" && groupInfo.groupProfile.description == null)
)
SectionBottomSpacer()
}
}
@@ -78,7 +79,7 @@ private fun GroupWelcomeLayout(
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)
Text(stringResource(R.string.save_and_update_group_profile), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary)
}
}
}

View File

@@ -1,15 +1,12 @@
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.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.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -24,28 +21,28 @@ fun CICallItemView(cInfo: ChatInfo, cItem: ChatItem, status: CICallStatus, durat
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)
@Composable fun ConnectingCallIcon() = Icon(painterResource(R.drawable.ic_settings_phone), 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))
Icon(painterResource(R.drawable.ic_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.Missed -> Icon(painterResource(R.drawable.ic_call), stringResource(R.string.icon_descr_call_missed), tint = Color.Red)
CICallStatus.Rejected -> Icon(painterResource(R.drawable.ic_call_end), 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.Progress -> Icon(painterResource(R.drawable.ic_phone_in_talk_filled), 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)
Icon(painterResource(R.drawable.ic_call_end), stringResource(R.string.icon_descr_call_ended), tint = MaterialTheme.colors.secondary, modifier = Modifier.padding(end = 4.dp))
Text(durationText(duration), color = MaterialTheme.colors.secondary)
}
CICallStatus.Error -> {}
}
Text(
cItem.timestampText,
color = HighOrLowlight,
color = MaterialTheme.colors.secondary,
fontSize = 14.sp,
modifier = Modifier.padding(start = 3.dp)
)
@@ -55,9 +52,9 @@ fun CICallItemView(cInfo: ChatInfo, cItem: ChatItem, status: CICallStatus, durat
@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) }
SimpleButton(stringResource(R.string.answer_call), painterResource(R.drawable.ic_ring_volume)) { acceptCall(cInfo.contact) }
} else {
Icon(Icons.Outlined.RingVolume, stringResource(R.string.answer_call), tint = HighOrLowlight)
Icon(painterResource(R.drawable.ic_ring_volume), stringResource(R.string.answer_call), tint = MaterialTheme.colors.secondary)
}
// if case let .direct(contact) = chatInfo {
// Button {

View File

@@ -6,7 +6,7 @@ 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.graphics.painter.Painter
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.model.*
@@ -16,14 +16,14 @@ fun CIChatFeatureView(
chatItem: ChatItem,
feature: Feature,
iconColor: Color,
icon: ImageVector? = null
icon: Painter? = 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)
Icon(icon ?: feature.iconFilled(), feature.text, Modifier.size(18.dp), tint = iconColor)
Text(
chatEventText(chatItem),
Modifier,

View File

@@ -13,8 +13,7 @@ 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
import chat.simplex.app.ui.theme.*
@Composable
fun CIEventView(ci: ChatItem) {
@@ -22,28 +21,25 @@ fun CIEventView(ci: ChatItem) {
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))
}
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)
val chatEventStyle = SpanStyle(fontSize = 12.sp, fontWeight = FontWeight.Light, color = CurrentColors.value.colors.secondary)
fun chatEventText(ci: ChatItem): AnnotatedString =
buildAnnotatedString {

View File

@@ -11,7 +11,6 @@ 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
@@ -27,7 +26,7 @@ fun CIFeaturePreferenceView(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
Icon(feature.icon, feature.text, Modifier.size(18.dp), tint = HighOrLowlight)
Icon(feature.icon, feature.text, Modifier.size(18.dp), tint = MaterialTheme.colors.secondary)
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
@@ -48,7 +47,7 @@ fun CIFeaturePreferenceView(
)
} else {
Text(chatItem.content.text + " " + chatItem.timestampText,
fontSize = 12.sp, fontWeight = FontWeight.Light, color = HighOrLowlight)
fontSize = 12.sp, fontWeight = FontWeight.Light, color = MaterialTheme.colors.secondary)
}
}
}

View File

@@ -6,18 +6,15 @@ 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.graphics.painter.Painter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.*
import androidx.compose.ui.unit.dp
@@ -39,14 +36,14 @@ fun CIFileView(
@Composable
fun fileIcon(
innerIcon: ImageVector? = null,
innerIcon: Painter? = null,
color: Color = if (isInDarkTheme()) FileDark else FileLight
) {
Box(
contentAlignment = Alignment.Center
) {
Icon(
Icons.Filled.InsertDriveFile,
painterResource(R.drawable.ic_draft_filled),
stringResource(R.string.icon_descr_file),
Modifier.fillMaxSize(),
tint = color
@@ -154,15 +151,15 @@ fun CIFileView(
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.SndError -> fileIcon(innerIcon = Icons.Outlined.Close)
is CIFileStatus.SndComplete -> fileIcon(innerIcon = painterResource(R.drawable.ic_check_filled))
is CIFileStatus.SndCancelled -> fileIcon(innerIcon = painterResource(R.drawable.ic_close))
is CIFileStatus.SndError -> fileIcon(innerIcon = painterResource(R.drawable.ic_close))
is CIFileStatus.RcvInvitation ->
if (fileSizeValid())
fileIcon(innerIcon = Icons.Outlined.ArrowDownward, color = MaterialTheme.colors.primary)
fileIcon(innerIcon = painterResource(R.drawable.ic_arrow_downward), color = MaterialTheme.colors.primary)
else
fileIcon(innerIcon = Icons.Outlined.PriorityHigh, color = WarningOrange)
is CIFileStatus.RcvAccepted -> fileIcon(innerIcon = Icons.Outlined.MoreHoriz)
fileIcon(innerIcon = painterResource(R.drawable.ic_priority_high), color = WarningOrange)
is CIFileStatus.RcvAccepted -> fileIcon(innerIcon = painterResource(R.drawable.ic_more_horiz))
is CIFileStatus.RcvTransfer ->
if (file.fileProtocol == FileProtocol.XFTP && file.fileStatus.rcvProgress < file.fileStatus.rcvTotal) {
progressCircle(file.fileStatus.rcvProgress, file.fileStatus.rcvTotal)
@@ -170,8 +167,8 @@ fun CIFileView(
progressIndicator()
}
is CIFileStatus.RcvComplete -> fileIcon()
is CIFileStatus.RcvCancelled -> fileIcon(innerIcon = Icons.Outlined.Close)
is CIFileStatus.RcvError -> fileIcon(innerIcon = Icons.Outlined.Close)
is CIFileStatus.RcvCancelled -> fileIcon(innerIcon = painterResource(R.drawable.ic_close))
is CIFileStatus.RcvError -> fileIcon(innerIcon = painterResource(R.drawable.ic_close))
}
} else {
fileIcon()
@@ -190,16 +187,14 @@ fun CIFileView(
else
" "
if (file != null) {
Column(
horizontalAlignment = Alignment.Start
) {
Column {
Text(
file.fileName,
maxLines = 1
)
Text(
formatBytes(file.fileSize) + metaReserve,
color = HighOrLowlight,
color = MaterialTheme.colors.secondary,
fontSize = 14.sp,
maxLines = 1
)

View File

@@ -1,13 +1,10 @@
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
@@ -46,7 +43,7 @@ fun CIGroupInvitationView(
.padding(vertical = 4.dp)
.padding(end = 2.dp)
) {
ProfileImage(size = 60.dp, image = groupInvitation.groupProfile.image, icon = Icons.Filled.SupervisedUserCircle, color = iconColor)
ProfileImage(size = 60.dp, image = groupInvitation.groupProfile.image, icon = R.drawable.ic_supervised_user_circle_filled, color = iconColor)
Spacer(Modifier.padding(horizontal = 3.dp))
Column(
Modifier.defaultMinSize(minHeight = 60.dp),
@@ -71,12 +68,14 @@ fun CIGroupInvitationView(
}
}
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage
val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage
Surface(
modifier = if (action) Modifier.clickable(onClick = {
joinGroup(groupInvitation.groupId)
}) else Modifier,
shape = RoundedCornerShape(18.dp),
color = if (sent) SentColorLight else ReceivedColorLight,
color = if (sent) sentColor else receivedColor,
) {
Box(
Modifier
@@ -89,7 +88,6 @@ fun CIGroupInvitationView(
Modifier
.defaultMinSize(minWidth = 220.dp)
.padding(bottom = 4.dp),
horizontalAlignment = Alignment.Start
) {
groupInfoView()
Column(Modifier.padding(top = 2.dp, start = 5.dp)) {
@@ -108,7 +106,7 @@ fun CIGroupInvitationView(
}
Text(
ci.timestampText,
color = HighOrLowlight,
color = MaterialTheme.colors.secondary,
fontSize = 14.sp,
modifier = Modifier.padding(start = 3.dp)
)

View File

@@ -8,9 +8,6 @@ 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
@@ -18,10 +15,10 @@ 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.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -55,7 +52,7 @@ fun CIImageView(
}
@Composable
fun fileIcon(icon: ImageVector, @StringRes stringId: Int) {
fun fileIcon(icon: Painter, @StringRes stringId: Int) {
Icon(
icon,
stringResource(stringId),
@@ -80,14 +77,14 @@ fun CIImageView(
FileProtocol.SMP -> {}
}
is CIFileStatus.SndTransfer -> progressIndicator()
is CIFileStatus.SndComplete -> fileIcon(Icons.Filled.Check, R.string.icon_descr_image_snd_complete)
is CIFileStatus.SndCancelled -> fileIcon(Icons.Outlined.Close, R.string.icon_descr_file)
is CIFileStatus.SndError -> fileIcon(Icons.Outlined.Close, R.string.icon_descr_file)
is CIFileStatus.RcvInvitation -> fileIcon(Icons.Outlined.ArrowDownward, R.string.icon_descr_asked_to_receive)
is CIFileStatus.RcvAccepted -> fileIcon(Icons.Outlined.MoreHoriz, R.string.icon_descr_waiting_for_image)
is CIFileStatus.SndComplete -> fileIcon(painterResource(R.drawable.ic_check_filled), R.string.icon_descr_image_snd_complete)
is CIFileStatus.SndCancelled -> fileIcon(painterResource(R.drawable.ic_close), R.string.icon_descr_file)
is CIFileStatus.SndError -> fileIcon(painterResource(R.drawable.ic_close), R.string.icon_descr_file)
is CIFileStatus.RcvInvitation -> fileIcon(painterResource(R.drawable.ic_arrow_downward), R.string.icon_descr_asked_to_receive)
is CIFileStatus.RcvAccepted -> fileIcon(painterResource(R.drawable.ic_more_horiz), R.string.icon_descr_waiting_for_image)
is CIFileStatus.RcvTransfer -> progressIndicator()
is CIFileStatus.RcvCancelled -> fileIcon(Icons.Outlined.Close, R.string.icon_descr_file)
is CIFileStatus.RcvError -> fileIcon(Icons.Outlined.Close, R.string.icon_descr_file)
is CIFileStatus.RcvCancelled -> fileIcon(painterResource(R.drawable.ic_close), R.string.icon_descr_file)
is CIFileStatus.RcvError -> fileIcon(painterResource(R.drawable.ic_close), R.string.icon_descr_file)
else -> {}
}
}

View File

@@ -5,12 +5,11 @@ 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.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.unit.dp
@@ -35,7 +34,7 @@ fun InvalidJSONView(json: String) {
Spacer(Modifier.height(DEFAULT_PADDING))
SectionView {
val context = LocalContext.current
SettingsActionItem(Icons.Outlined.Share, generalGetString(R.string.share_verb), click = {
SettingsActionItem(painterResource(R.drawable.ic_share), generalGetString(R.string.share_verb), click = {
shareText(context, json)
})
}

View File

@@ -2,25 +2,23 @@ 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.ui.graphics.painter.Painter
import androidx.compose.ui.res.painterResource
import chat.simplex.app.R
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 chat.simplex.app.ui.theme.CurrentColors
import kotlinx.datetime.Clock
@Composable
fun CIMetaView(chatItem: ChatItem, timedMessagesTTL: Int?, metaColor: Color = HighOrLowlight) {
fun CIMetaView(chatItem: ChatItem, timedMessagesTTL: Int?, metaColor: Color = MaterialTheme.colors.secondary) {
Row(Modifier.padding(start = 3.dp), verticalAlignment = Alignment.CenterVertically) {
if (chatItem.isDeletedContent) {
Text(
@@ -39,11 +37,11 @@ fun CIMetaView(chatItem: ChatItem, timedMessagesTTL: Int?, metaColor: Color = Hi
// changing this function requires updating reserveSpaceForMeta
private fun CIMetaText(meta: CIMeta, chatTTL: Int?, color: Color) {
if (meta.itemEdited) {
StatusIconText(Icons.Outlined.Edit, color)
StatusIconText(painterResource(R.drawable.ic_edit), color)
Spacer(Modifier.width(3.dp))
}
if (meta.disappearing) {
StatusIconText(Icons.Outlined.Timer, color)
StatusIconText(painterResource(R.drawable.ic_timer), color)
val ttl = meta.itemTimed?.ttl
if (ttl != chatTTL) {
Text(TimedMessagesPreference.shortTtlText(ttl), color = color, fontSize = 13.sp)
@@ -53,10 +51,10 @@ private fun CIMetaText(meta: CIMeta, chatTTL: Int?, color: Color) {
val statusIcon = meta.statusIcon(MaterialTheme.colors.primary, color)
if (statusIcon != null) {
val (icon, statusColor) = statusIcon
StatusIconText(icon, statusColor)
StatusIconText(painterResource(icon), statusColor)
Spacer(Modifier.width(4.dp))
} else if (!meta.disappearing) {
StatusIconText(Icons.Filled.Circle, Color.Transparent)
StatusIconText(painterResource(R.drawable.ic_circle_filled), Color.Transparent)
Spacer(Modifier.width(4.dp))
}
Text(meta.timestampText, color = color, fontSize = 13.sp, maxLines = 1, overflow = TextOverflow.Ellipsis)
@@ -74,14 +72,14 @@ fun reserveSpaceForMeta(meta: CIMeta, chatTTL: Int?): String {
res += TimedMessagesPreference.shortTtlText(ttl)
}
}
if (meta.statusIcon(HighOrLowlight) != null || !meta.disappearing) {
if (meta.statusIcon(CurrentColors.value.colors.secondary) != null || !meta.disappearing) {
res += iconSpace
}
return res + meta.timestampText
}
@Composable
private fun StatusIconText(icon: ImageVector, color: Color) {
private fun StatusIconText(icon: Painter, color: Color) {
Icon(icon, null, Modifier.height(12.dp), tint = color)
}

View File

@@ -9,17 +9,15 @@ 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.graphics.painter.Painter
import androidx.compose.ui.layout.*
import androidx.compose.ui.platform.*
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.*
import androidx.compose.ui.viewinterop.AndroidView
@@ -163,9 +161,8 @@ private fun BoxScope.PlayButton(error: Boolean = false, onLongClick: () -> Unit,
contentAlignment = Alignment.Center
) {
Icon(
imageVector = Icons.Filled.PlayArrow,
painterResource(R.drawable.ic_play_arrow_filled),
contentDescription = null,
Modifier.size(25.dp),
tint = if (error) WarningOrange else Color.White
)
}
@@ -192,7 +189,7 @@ private fun DurationProgress(file: CIFile, playing: MutableState<Boolean>, durat
color = Color.White
)
/*if (!soundEnabled.value) {
Icon(Icons.Outlined.VolumeOff, null,
Icon(painterResource(R.drawable.ic_volume_off_filled), null,
Modifier.padding(start = 5.dp).size(10.dp),
tint = Color.White
)
@@ -255,7 +252,7 @@ private fun progressIndicator() {
}
@Composable
private fun fileIcon(icon: ImageVector, @StringRes stringId: Int) {
private fun fileIcon(icon: Painter, @StringRes stringId: Int) {
Icon(
icon,
stringResource(stringId),
@@ -298,19 +295,19 @@ private fun loadingIndicator(file: CIFile?) {
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.SndError -> 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.SndComplete -> fileIcon(painterResource(R.drawable.ic_check_filled), R.string.icon_descr_video_snd_complete)
is CIFileStatus.SndCancelled -> fileIcon(painterResource(R.drawable.ic_close), R.string.icon_descr_file)
is CIFileStatus.SndError -> fileIcon(painterResource(R.drawable.ic_close), R.string.icon_descr_file)
is CIFileStatus.RcvInvitation -> fileIcon(painterResource(R.drawable.ic_arrow_downward), R.string.icon_descr_video_asked_to_receive)
is CIFileStatus.RcvAccepted -> fileIcon(painterResource(R.drawable.ic_more_horiz), 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)
is CIFileStatus.RcvError -> fileIcon(Icons.Outlined.Close, R.string.icon_descr_file)
is CIFileStatus.RcvCancelled -> fileIcon(painterResource(R.drawable.ic_close), R.string.icon_descr_file)
is CIFileStatus.RcvError -> fileIcon(painterResource(R.drawable.ic_close), R.string.icon_descr_file)
else -> {}
}
}

View File

@@ -5,8 +5,6 @@ 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
@@ -19,7 +17,9 @@ 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.res.painterResource
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.helpers.*
@@ -137,7 +137,7 @@ private fun DurationText(text: State<String>, padding: PaddingValues) {
Modifier
.padding(padding)
.widthIn(min = minWidth),
color = HighOrLowlight,
color = MaterialTheme.colors.secondary,
fontSize = 16.sp,
maxLines = 1
)
@@ -156,9 +156,11 @@ private fun PlayPauseButton(
pause: () -> Unit,
longClick: () -> Unit
) {
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage
val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage
Surface(
Modifier.drawRingModifier(angle, strokeColor, strokeWidth),
color = if (sent) SentColorLight else ReceivedColorLight,
color = if (sent) sentColor else receivedColor,
shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50))
) {
Box(
@@ -171,10 +173,10 @@ private fun PlayPauseButton(
contentAlignment = Alignment.Center
) {
Icon(
imageVector = if (audioPlaying) Icons.Filled.Pause else Icons.Filled.PlayArrow,
if (audioPlaying) painterResource(R.drawable.ic_pause_filled) else painterResource(R.drawable.ic_play_arrow_filled),
contentDescription = null,
Modifier.size(36.dp),
tint = if (error) WarningOrange else if (!enabled) HighOrLowlight else MaterialTheme.colors.primary
tint = if (error) WarningOrange else if (!enabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary
)
}
}
@@ -200,7 +202,7 @@ private fun VoiceMsgIndicator(
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,
if (audioPlaying) painterResource(R.drawable.ic_pause_filled) else painterResource(R.drawable.ic_play_arrow_filled),
contentDescription = null,
Modifier.size(36.dp),
tint = MaterialTheme.colors.primary

View File

@@ -6,16 +6,15 @@ 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.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.*
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@@ -106,7 +105,7 @@ fun ChatItemView(
fun MsgContentItemDropdownMenu() {
DefaultDropdownMenu(showMenu) {
if (cItem.meta.itemDeleted == null && !live) {
ItemAction(stringResource(R.string.reply_verb), Icons.Outlined.Reply, onClick = {
ItemAction(stringResource(R.string.reply_verb), painterResource(R.drawable.ic_reply), onClick = {
if (composeState.value.editing) {
composeState.value = ComposeState(contextItem = ComposeContextItem.QuotedItem(cItem), useLinkPreviews = useLinkPreviews)
} else {
@@ -115,7 +114,7 @@ fun ChatItemView(
showMenu.value = false
})
}
ItemAction(stringResource(R.string.share_verb), Icons.Outlined.Share, onClick = {
ItemAction(stringResource(R.string.share_verb), painterResource(R.drawable.ic_share), onClick = {
val filePath = getLoadedFilePath(SimplexApp.context, cItem.file)
when {
filePath != null -> shareFile(context, cItem.text, filePath)
@@ -123,7 +122,7 @@ fun ChatItemView(
}
showMenu.value = false
})
ItemAction(stringResource(R.string.copy_verb), Icons.Outlined.ContentCopy, onClick = {
ItemAction(stringResource(R.string.copy_verb), painterResource(R.drawable.ic_content_copy), onClick = {
copyText(context, cItem.content.text)
showMenu.value = false
})
@@ -131,7 +130,7 @@ fun ChatItemView(
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 = {
ItemAction(stringResource(R.string.save_verb), painterResource(R.drawable.ic_download), onClick = {
when (cItem.content.msgContent) {
is MsgContent.MCImage -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R || writePermissionState.hasPermission) {
@@ -148,7 +147,7 @@ fun ChatItemView(
}
}
if (cItem.meta.editable && cItem.content.msgContent !is MsgContent.MCVoice && !live) {
ItemAction(stringResource(R.string.edit_verb), Icons.Filled.Edit, onClick = {
ItemAction(stringResource(R.string.edit_verb), painterResource(R.drawable.ic_edit_filled), onClick = {
composeState.value = ComposeState(editingItem = cItem, useLinkPreviews = useLinkPreviews)
showMenu.value = false
})
@@ -156,7 +155,7 @@ fun ChatItemView(
if (cItem.meta.itemDeleted != null && revealed.value) {
ItemAction(
stringResource(R.string.hide_verb),
Icons.Outlined.VisibilityOff,
painterResource(R.drawable.ic_visibility_off),
onClick = {
revealed.value = false
showMenu.value = false
@@ -182,7 +181,7 @@ fun ChatItemView(
if (!cItem.isDeletedContent) {
ItemAction(
stringResource(R.string.reveal_verb),
Icons.Outlined.Visibility,
painterResource(R.drawable.ic_visibility),
onClick = {
revealed.value = true
showMenu.value = false
@@ -248,7 +247,7 @@ fun ChatItemView(
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.SndChatPreference -> CIChatFeatureView(cItem, c.feature, MaterialTheme.colors.secondary, 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)
@@ -270,7 +269,7 @@ fun CancelFileItemAction(
) {
ItemAction(
stringResource(cancelAction.uiActionId),
Icons.Outlined.Close,
painterResource(R.drawable.ic_close),
onClick = {
showMenu.value = false
cancelFileAlertDialog(fileId, cancelFile = cancelFile, cancelAction = cancelAction)
@@ -288,7 +287,7 @@ fun DeleteItemAction(
) {
ItemAction(
stringResource(R.string.delete_verb),
Icons.Outlined.Delete,
painterResource(R.drawable.ic_delete),
onClick = {
showMenu.value = false
deleteMessageAlertDialog(cItem, questionText, deleteMessage = deleteMessage)
@@ -306,7 +305,7 @@ fun ModerateItemAction(
) {
ItemAction(
stringResource(R.string.moderate_verb),
Icons.Outlined.Flag,
painterResource(R.drawable.ic_flag),
onClick = {
showMenu.value = false
moderateMessageAlertDialog(cItem, questionText, deleteMessage = deleteMessage)
@@ -315,6 +314,26 @@ fun ModerateItemAction(
)
}
@Composable
fun ItemAction(text: String, icon: Painter, onClick: () -> Unit, color: Color = Color.Unspecified) {
val finalColor = if (color == Color.Unspecified) {
if (isInDarkTheme()) MenuTextColorDark else Color.Black
} else color
DropdownMenuItem(onClick, contentPadding = PaddingValues(horizontal = DEFAULT_PADDING * 1.5f)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text,
modifier = Modifier
.fillMaxWidth()
.weight(1F)
.padding(end = 15.dp),
color = finalColor
)
Icon(icon, text, tint = finalColor)
}
}
}
@Composable
fun ItemAction(text: String, icon: ImageVector, onClick: () -> Unit, color: Color = Color.Unspecified) {
val finalColor = if (color == Color.Unspecified) {
@@ -361,13 +380,13 @@ fun deleteMessageAlertDialog(chatItem: ChatItem, questionText: String, deleteMes
TextButton(onClick = {
deleteMessage(chatItem.id, CIDeleteMode.cidmInternal)
AlertManager.shared.hideAlert()
}) { Text(stringResource(R.string.for_me_only)) }
}) { Text(stringResource(R.string.for_me_only), color = MaterialTheme.colors.error) }
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)) }
}) { Text(stringResource(R.string.for_everybody), color = MaterialTheme.colors.error) }
}
}
}

View File

@@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.*
@@ -13,15 +14,16 @@ 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
import chat.simplex.app.ui.theme.*
@Composable
fun DeletedItemView(ci: ChatItem, timedMessagesTTL: Int?, showMember: Boolean = false) {
val sent = ci.chatDir.sent
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage
val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage
Surface(
shape = RoundedCornerShape(18.dp),
color = if (sent) SentColorLight else ReceivedColorLight,
color = if (sent) sentColor else receivedColor,
) {
Row(
Modifier.padding(horizontal = 12.dp, vertical = 6.dp),
@@ -30,7 +32,7 @@ fun DeletedItemView(ci: ChatItem, timedMessagesTTL: Int?, showMember: Boolean =
Text(
buildAnnotatedString {
appendSender(this, if (showMember) ci.memberDisplayName else null, true)
withStyle(SpanStyle(fontStyle = FontStyle.Italic, color = HighOrLowlight)) { append(ci.content.text) }
withStyle(SpanStyle(fontStyle = FontStyle.Italic, color = MaterialTheme.colors.secondary)) { append(ci.content.text) }
},
style = MaterialTheme.typography.body1.copy(lineHeight = 22.sp),
modifier = Modifier.padding(end = 8.dp)

View File

@@ -4,10 +4,6 @@ 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.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.Flag
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -15,9 +11,10 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.*
import androidx.compose.ui.platform.UriHandler
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontStyle
@@ -32,11 +29,6 @@ import chat.simplex.app.views.helpers.*
import kotlinx.datetime.Clock
import kotlin.math.min
val SentColorLight = Color(0x1E45B8FF)
val ReceivedColorLight = Color(0x20B1B0B5)
val SentQuoteColorLight = Color(0x2545B8FF)
val ReceivedQuoteColorLight = Color(0x25B1B0B5)
@Composable
fun FramedItemView(
chatInfo: ChatInfo,
@@ -57,6 +49,9 @@ fun FramedItemView(
return if (chatInfo is ChatInfo.Group) chatInfo.groupInfo.membership else null
}
@Composable
fun Color.toQuote(): Color = if (isInDarkTheme()) lighter(0.12f) else darker(0.12f)
@Composable
fun ciQuotedMsgView(qi: CIQuote) {
Box(
@@ -72,10 +67,12 @@ fun FramedItemView(
}
@Composable
fun FramedItemHeader(caption: String, italic: Boolean, icon: ImageVector? = null) {
fun FramedItemHeader(caption: String, italic: Boolean, icon: Painter? = null) {
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage
val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage
Row(
Modifier
.background(if (sent) SentQuoteColorLight else ReceivedQuoteColorLight)
.background(if (sent) sentColor.toQuote() else receivedColor.toQuote())
.fillMaxWidth()
.padding(start = 8.dp, top = 6.dp, end = 12.dp, bottom = if (ci.quotedItem == null) 6.dp else 0.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
@@ -90,7 +87,7 @@ fun FramedItemView(
}
Text(
buildAnnotatedString {
withStyle(SpanStyle(fontSize = 12.sp, fontStyle = if (italic) FontStyle.Italic else FontStyle.Normal, color = HighOrLowlight)) {
withStyle(SpanStyle(fontSize = 12.sp, fontStyle = if (italic) FontStyle.Italic else FontStyle.Normal, color = MaterialTheme.colors.secondary)) {
append(caption)
}
},
@@ -103,9 +100,11 @@ fun FramedItemView(
@Composable
fun ciQuoteView(qi: CIQuote) {
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage
val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage
Row(
Modifier
.background(if (sent) SentQuoteColorLight else ReceivedQuoteColorLight)
.background(if (sent) sentColor.toQuote() else receivedColor.toQuote())
.fillMaxWidth()
.combinedClickable(
onLongClick = { showMenu.value = true },
@@ -142,7 +141,7 @@ fun FramedItemView(
ciQuotedMsgView(qi)
}
Icon(
if (qi.content is MsgContent.MCFile) Icons.Filled.InsertDriveFile else Icons.Filled.Mic,
if (qi.content is MsgContent.MCFile) painterResource(R.drawable.ic_draft_filled) else painterResource(R.drawable.ic_mic_filled),
if (qi.content is MsgContent.MCFile) stringResource(R.string.icon_descr_file) else stringResource(R.string.voice_message),
Modifier
.padding(top = 6.dp, end = 4.dp)
@@ -166,24 +165,26 @@ fun FramedItemView(
val transparentBackground = (ci.content.msgContent is MsgContent.MCImage || ci.content.msgContent is MsgContent.MCVideo) &&
!ci.meta.isLive && ci.content.text.isEmpty() && ci.quotedItem == null
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage
val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage
Box(Modifier
.clip(RoundedCornerShape(18.dp))
.background(
when {
transparentBackground -> Color.Transparent
sent -> SentColorLight
else -> ReceivedColorLight
sent -> sentColor
else -> receivedColor
}
)) {
var metaColor = HighOrLowlight
var metaColor = MaterialTheme.colors.secondary
Box(contentAlignment = Alignment.BottomEnd) {
Column(Modifier.width(IntrinsicSize.Max)) {
PriorityLayout(Modifier, CHAT_IMAGE_LAYOUT_ID) {
if (ci.meta.itemDeleted != null) {
if (ci.meta.itemDeleted is CIDeleted.Moderated) {
FramedItemHeader(String.format(stringResource(R.string.moderated_item_description), ci.meta.itemDeleted.byGroupMember.chatViewName), true, Icons.Outlined.Flag)
FramedItemHeader(String.format(stringResource(R.string.moderated_item_description), ci.meta.itemDeleted.byGroupMember.chatViewName), true, painterResource(R.drawable.ic_flag))
} else {
FramedItemHeader(stringResource(R.string.marked_deleted_description), true, Icons.Outlined.Delete)
FramedItemHeader(stringResource(R.string.marked_deleted_description), true, painterResource(R.drawable.ic_delete))
}
} else if (ci.meta.isLive) {
FramedItemHeader(stringResource(R.string.live), false)

View File

@@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -18,6 +19,7 @@ import androidx.compose.ui.unit.sp
import chat.simplex.app.R
import chat.simplex.app.model.ChatItem
import chat.simplex.app.model.MsgErrorType
import chat.simplex.app.ui.theme.CurrentColors
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.helpers.AlertManager
import chat.simplex.app.views.helpers.generalGetString
@@ -50,10 +52,11 @@ fun IntegrityErrorItemView(msgError: MsgErrorType, ci: ChatItem, timedMessagesTT
@Composable
fun CIMsgError(ci: ChatItem, timedMessagesTTL: Int?, showMember: Boolean = false, onClick: () -> Unit) {
val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage
Surface(
Modifier.clickable(onClick = onClick),
shape = RoundedCornerShape(18.dp),
color = ReceivedColorLight,
color = receivedColor,
) {
Row(
Modifier.padding(horizontal = 12.dp, vertical = 6.dp),

View File

@@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.*
@@ -16,15 +17,16 @@ 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.ui.theme.*
import chat.simplex.app.views.helpers.generalGetString
@Composable
fun MarkedDeletedItemView(ci: ChatItem, timedMessagesTTL: Int?, showMember: Boolean = false) {
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage
val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage
Surface(
shape = RoundedCornerShape(18.dp),
color = if (ci.chatDir.sent) SentColorLight else ReceivedColorLight,
color = if (ci.chatDir.sent) sentColor else receivedColor,
) {
Row(
Modifier.padding(horizontal = 12.dp, vertical = 6.dp),
@@ -47,7 +49,7 @@ 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) }
withStyle(SpanStyle(fontSize = 12.sp, fontStyle = FontStyle.Italic, color = MaterialTheme.colors.secondary)) { append(text) }
},
style = MaterialTheme.typography.body1.copy(lineHeight = 22.sp),
modifier = Modifier.padding(end = 8.dp),

View File

@@ -22,7 +22,7 @@ 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.ui.theme.CurrentColors
import chat.simplex.app.views.helpers.detectGesture
import kotlinx.coroutines.*
@@ -57,7 +57,7 @@ private val typingIndicators: List<AnnotatedString> = listOf(
private fun typingIndicator(recent: Boolean, @IntRange (from = 0, to = 4) typingIdx: Int): AnnotatedString = buildAnnotatedString {
pushStyle(SpanStyle(color = HighOrLowlight, fontFamily = FontFamily.Monospace, letterSpacing = (-1).sp))
pushStyle(SpanStyle(color = CurrentColors.value.colors.secondary, fontFamily = FontFamily.Monospace, letterSpacing = (-1).sp))
append(if (recent) typingIndicators[typingIdx] else noTyping)
}
@@ -228,7 +228,7 @@ fun ClickableText(
}
}
}, shouldConsumeEvent = { pos ->
var consume = false
var consume = false
layoutResult.value?.let { layoutResult ->
consume = shouldConsumeEvent(layoutResult.getOffsetForPosition(pos))
}

View File

@@ -4,8 +4,7 @@ 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.ui.res.painterResource
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -28,7 +27,6 @@ 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
@@ -44,7 +42,6 @@ fun ChatHelpView(addContact: (() -> Unit)? = null) {
Column(
Modifier.padding(top = 24.dp),
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
Text(
@@ -58,7 +55,7 @@ fun ChatHelpView(addContact: (() -> Unit)? = null) {
) {
Text(stringResource(R.string.chat_help_tap_button))
Icon(
Icons.Outlined.PersonAdd,
painterResource(R.drawable.ic_person_add),
stringResource(R.string.add_contact),
modifier = if (addContact != null) Modifier.clickable(onClick = addContact) else Modifier,
)
@@ -70,7 +67,6 @@ fun ChatHelpView(addContact: (() -> Unit)? = null) {
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)
@@ -81,7 +77,6 @@ fun ChatHelpView(addContact: (() -> Unit)? = null) {
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)

View File

@@ -4,14 +4,12 @@ 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.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
@@ -183,7 +181,7 @@ fun GroupMenuItems(chat: Chat, groupInfo: GroupInfo, chatModel: ChatModel, showM
fun MarkReadChatAction(chat: Chat, chatModel: ChatModel, showMenu: MutableState<Boolean>) {
ItemAction(
stringResource(R.string.mark_read),
Icons.Outlined.Check,
painterResource(R.drawable.ic_check),
onClick = {
markChatRead(chat, chatModel)
chatModel.controller.ntfManager.cancelNotificationsForChat(chat.id)
@@ -196,7 +194,7 @@ fun MarkReadChatAction(chat: Chat, chatModel: ChatModel, showMenu: MutableState<
fun MarkUnreadChatAction(chat: Chat, chatModel: ChatModel, showMenu: MutableState<Boolean>) {
ItemAction(
stringResource(R.string.mark_unread),
Icons.Outlined.MarkChatUnread,
painterResource(R.drawable.ic_mark_chat_unread),
onClick = {
markChatUnread(chat, chatModel)
showMenu.value = false
@@ -208,7 +206,7 @@ fun MarkUnreadChatAction(chat: Chat, chatModel: ChatModel, showMenu: MutableStat
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,
if (ntfsEnabled) painterResource(R.drawable.ic_notifications_off) else painterResource(R.drawable.ic_notifications),
onClick = {
changeNtfsStatePerChat(!ntfsEnabled, mutableStateOf(ntfsEnabled), chat, chatModel)
showMenu.value = false
@@ -220,7 +218,7 @@ fun ToggleNotificationsChatAction(chat: Chat, chatModel: ChatModel, ntfsEnabled:
fun ClearChatAction(chat: Chat, chatModel: ChatModel, showMenu: MutableState<Boolean>) {
ItemAction(
stringResource(R.string.clear_chat_menu_action),
Icons.Outlined.Restore,
painterResource(R.drawable.ic_settings_backup_restore),
onClick = {
clearChatDialog(chat.chatInfo, chatModel)
showMenu.value = false
@@ -233,7 +231,7 @@ fun ClearChatAction(chat: Chat, chatModel: ChatModel, showMenu: MutableState<Boo
fun DeleteContactAction(chat: Chat, chatModel: ChatModel, showMenu: MutableState<Boolean>) {
ItemAction(
stringResource(R.string.delete_contact_menu_action),
Icons.Outlined.Delete,
painterResource(R.drawable.ic_delete),
onClick = {
deleteContactDialog(chat.chatInfo, chatModel)
showMenu.value = false
@@ -246,7 +244,7 @@ fun DeleteContactAction(chat: Chat, chatModel: ChatModel, showMenu: MutableState
fun DeleteGroupAction(chat: Chat, groupInfo: GroupInfo, chatModel: ChatModel, showMenu: MutableState<Boolean>) {
ItemAction(
stringResource(R.string.delete_group_menu_action),
Icons.Outlined.Delete,
painterResource(R.drawable.ic_delete),
onClick = {
deleteGroupDialog(chat.chatInfo, groupInfo, chatModel)
showMenu.value = false
@@ -260,7 +258,7 @@ fun JoinGroupAction(chat: Chat, groupInfo: GroupInfo, chatModel: ChatModel, show
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,
if (chat.chatInfo.incognito) painterResource(R.drawable.ic_theater_comedy_filled) else painterResource(R.drawable.ic_login),
color = if (chat.chatInfo.incognito) Indigo else MaterialTheme.colors.onBackground,
onClick = {
joinGroup()
@@ -273,7 +271,7 @@ fun JoinGroupAction(chat: Chat, groupInfo: GroupInfo, chatModel: ChatModel, show
fun LeaveGroupAction(groupInfo: GroupInfo, chatModel: ChatModel, showMenu: MutableState<Boolean>) {
ItemAction(
stringResource(R.string.leave_group_button),
Icons.Outlined.Logout,
painterResource(R.drawable.ic_logout),
onClick = {
leaveGroupDialog(groupInfo, chatModel)
showMenu.value = false
@@ -286,7 +284,7 @@ fun LeaveGroupAction(groupInfo: GroupInfo, chatModel: ChatModel, showMenu: Mutab
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,
if (chatModel.incognito.value) painterResource(R.drawable.ic_theater_comedy_filled) else painterResource(R.drawable.ic_check),
color = if (chatModel.incognito.value) Indigo else MaterialTheme.colors.onBackground,
onClick = {
acceptContactRequest(chatInfo.apiId, chatInfo, true, chatModel)
@@ -295,7 +293,7 @@ fun ContactRequestMenuItems(chatInfo: ChatInfo.ContactRequest, chatModel: ChatMo
)
ItemAction(
stringResource(R.string.reject_contact_button),
Icons.Outlined.Close,
painterResource(R.drawable.ic_close),
onClick = {
rejectContactRequest(chatInfo, chatModel)
showMenu.value = false
@@ -308,7 +306,7 @@ fun ContactRequestMenuItems(chatInfo: ChatInfo.ContactRequest, chatModel: ChatMo
fun ContactConnectionMenuItems(chatInfo: ChatInfo.ContactConnection, chatModel: ChatModel, showMenu: MutableState<Boolean>) {
ItemAction(
stringResource(R.string.set_contact_name),
Icons.Outlined.Edit,
painterResource(R.drawable.ic_edit),
onClick = {
ModalManager.shared.showModalCloseable(true) { close ->
ContactConnectionInfoView(chatModel, chatInfo.contactConnection.connReqInv, chatInfo.contactConnection, true, close)
@@ -318,7 +316,7 @@ fun ContactConnectionMenuItems(chatInfo: ChatInfo.ContactConnection, chatModel:
)
ItemAction(
stringResource(R.string.delete_verb),
Icons.Outlined.Delete,
painterResource(R.drawable.ic_delete),
onClick = {
deleteContactConnectionAlert(chatInfo.contactConnection, chatModel) {}
showMenu.value = false
@@ -330,7 +328,7 @@ fun ContactConnectionMenuItems(chatInfo: ChatInfo.ContactConnection, chatModel:
@Composable
private fun InvalidDataView() {
Row {
ProfileImage(72.dp, null, Icons.Filled.AccountCircle, HighOrLowlight)
ProfileImage(72.dp, null, R.drawable.ic_account_circle_filled, MaterialTheme.colors.secondary)
Column(
modifier = Modifier
.padding(horizontal = 8.dp)
@@ -468,7 +466,8 @@ fun deleteContactConnectionAlert(connection: PendingContactConnection, chatModel
onSuccess()
}
}
}
},
destructive = true,
)
}
@@ -486,6 +485,7 @@ fun pendingContactAlertDialog(chatInfo: ChatInfo, chatModel: ChatModel) {
}
}
},
destructive = true,
dismissText = generalGetString(R.string.cancel_verb),
)
}

View File

@@ -7,15 +7,13 @@ 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.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.capitalize
import androidx.compose.ui.text.font.FontWeight
@@ -67,10 +65,10 @@ fun ChatListView(chatModel: ChatModel, setPerformLA: (Boolean, FragmentActivity)
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
val switchingUsers = rememberSaveable { mutableStateOf(false) }
Scaffold(
topBar = { ChatListToolbar(chatModel, scaffoldState.drawerState, userPickerState, stopped) { searchInList = it.trim() } },
Scaffold(topBar = { ChatListToolbar(chatModel, scaffoldState.drawerState, userPickerState, stopped) { searchInList = it.trim() } },
scaffoldState = scaffoldState,
drawerContent = { SettingsView(chatModel, setPerformLA) },
drawerScrimColor = MaterialTheme.colors.onSurface.copy(alpha = if (isInDarkTheme()) 0.16f else 0.32f),
floatingActionButton = {
if (searchInList.isEmpty()) {
FloatingActionButton(
@@ -79,16 +77,17 @@ fun ChatListView(chatModel: ChatModel, setPerformLA: (Boolean, FragmentActivity)
if (newChatSheetState.value.isVisible()) hideNewChatSheet(true) else showNewChatSheet()
}
},
Modifier.padding(end = DEFAULT_PADDING - 16.dp, bottom = DEFAULT_PADDING - 16.dp),
elevation = FloatingActionButtonDefaults.elevation(
defaultElevation = 0.dp,
pressedElevation = 0.dp,
hoveredElevation = 0.dp,
focusedElevation = 0.dp,
),
backgroundColor = if (!stopped) MaterialTheme.colors.primary else HighOrLowlight,
backgroundColor = if (!stopped) MaterialTheme.colors.primary else MaterialTheme.colors.secondary,
contentColor = Color.White
) {
Icon(if (!newChatSheetState.collectAsState().value.isVisible()) Icons.Default.Edit else Icons.Default.Close, stringResource(R.string.add_contact_or_create_group))
Icon(if (!newChatSheetState.collectAsState().value.isVisible()) painterResource(R.drawable.ic_edit_filled) else painterResource(R.drawable.ic_close), stringResource(R.string.add_contact_or_create_group))
}
}
}
@@ -97,7 +96,6 @@ fun ChatListView(chatModel: ChatModel, setPerformLA: (Boolean, FragmentActivity)
Column(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colors.background)
) {
if (chatModel.chats.isNotEmpty()) {
ChatList(chatModel, search = searchInList)
@@ -106,7 +104,7 @@ fun ChatListView(chatModel: ChatModel, setPerformLA: (Boolean, FragmentActivity)
if (!stopped && !newChatSheetState.collectAsState().value.isVisible()) {
OnboardingButtons(showNewChatSheet)
}
Text(stringResource(R.string.you_have_no_chats), Modifier.align(Alignment.Center), color = HighOrLowlight)
Text(stringResource(R.string.you_have_no_chats), Modifier.align(Alignment.Center), color = MaterialTheme.colors.secondary)
}
}
}
@@ -137,7 +135,7 @@ private fun OnboardingButtons(openNewChatSheet: () -> Unit) {
}
Spacer(Modifier.height(DEFAULT_PADDING))
ConnectButton(generalGetString(R.string.tap_to_start_new_chat), openNewChatSheet)
val color = MaterialTheme.colors.primary
val color = MaterialTheme.colors.primaryVariant
Canvas(modifier = Modifier.width(40.dp).height(10.dp), onDraw = {
val trianglePath = Path().apply {
moveTo(0.dp.toPx(), 0f)
@@ -160,7 +158,7 @@ private fun ConnectButton(text: String, onClick: () -> Unit) {
onClick,
shape = RoundedCornerShape(21.dp),
colors = ButtonDefaults.textButtonColors(
backgroundColor = MaterialTheme.colors.primary
backgroundColor = MaterialTheme.colors.primaryVariant
),
elevation = null,
contentPadding = PaddingValues(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF),
@@ -181,7 +179,7 @@ private fun ChatListToolbar(chatModel: ChatModel, drawerState: DrawerState, user
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)
Icon(painterResource(R.drawable.ic_search_500), stringResource(android.R.string.search_go).capitalize(Locale.current), tint = MaterialTheme.colors.primary)
}
}
}
@@ -194,7 +192,7 @@ private fun ChatListToolbar(chatModel: ChatModel, drawerState: DrawerState, user
)
}) {
Icon(
Icons.Filled.Report,
painterResource(R.drawable.ic_report_filled),
generalGetString(R.string.chat_is_stopped_indication),
tint = Color.Red,
)
@@ -231,7 +229,7 @@ private fun ChatListToolbar(chatModel: ChatModel, drawerState: DrawerState, user
)
if (chatModel.incognito.value) {
Icon(
Icons.Filled.TheaterComedy,
painterResource(R.drawable.ic_theater_comedy_filled),
stringResource(R.string.incognito),
tint = Indigo,
modifier = Modifier.padding(10.dp).size(26.dp)
@@ -283,7 +281,7 @@ private fun ProgressIndicator() {
Modifier
.padding(horizontal = 2.dp)
.size(30.dp),
color = HighOrLowlight,
color = MaterialTheme.colors.secondary,
strokeWidth = 2.5.dp
)
}

View File

@@ -7,15 +7,12 @@ 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.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontWeight
@@ -46,10 +43,10 @@ fun ChatPreviewView(
@Composable
fun groupInactiveIcon() {
Icon(
Icons.Filled.Cancel,
painterResource(R.drawable.ic_cancel_filled),
stringResource(R.string.icon_descr_group_inactive),
Modifier.size(18.dp).background(MaterialTheme.colors.background, CircleShape),
tint = HighOrLowlight
tint = MaterialTheme.colors.secondary
)
}
@@ -79,15 +76,15 @@ fun ChatPreviewView(
@Composable
fun VerifiedIcon() {
Icon(Icons.Outlined.VerifiedUser, null, Modifier.size(19.dp).padding(end = 3.dp, top = 1.dp), tint = HighOrLowlight)
Icon(painterResource(R.drawable.ic_verified_user), null, Modifier.size(19.dp).padding(end = 3.dp, top = 1.dp), tint = MaterialTheme.colors.secondary)
}
fun messageDraft(draft: ComposeState): Pair<AnnotatedString, Map<String, InlineTextContent>> {
fun attachment(): Pair<ImageVector, String?>? =
fun attachment(): Pair<Int, String?>? =
when (draft.preview) {
is ComposePreview.FilePreview -> Icons.Filled.InsertDriveFile to draft.preview.fileName
is ComposePreview.MediaPreview -> Icons.Outlined.Image to null
is ComposePreview.VoicePreview -> Icons.Filled.PlayArrow to durationText(draft.preview.durationMs / 1000)
is ComposePreview.FilePreview -> R.drawable.ic_draft_filled to draft.preview.fileName
is ComposePreview.MediaPreview -> R.drawable.ic_image to null
is ComposePreview.VoicePreview -> R.drawable.ic_play_arrow_filled to durationText(draft.preview.durationMs / 1000)
else -> null
}
@@ -108,12 +105,12 @@ fun ChatPreviewView(
"editIcon" to InlineTextContent(
Placeholder(20.sp, 20.sp, PlaceholderVerticalAlign.TextCenter)
) {
Icon(Icons.Outlined.EditNote, null, tint = MaterialTheme.colors.primary)
Icon(painterResource(R.drawable.ic_edit_note), null, tint = MaterialTheme.colors.primary)
},
"attachmentIcon" to InlineTextContent(
Placeholder(20.sp, 20.sp, PlaceholderVerticalAlign.TextCenter)
) {
Icon(attachment?.first ?: Icons.Outlined.EditNote, null, tint = HighOrLowlight)
Icon(if (attachment?.first != null) painterResource(attachment.first) else painterResource(R.drawable.ic_edit_note), null, tint = MaterialTheme.colors.secondary)
}
)
return text to inlineContent
@@ -127,12 +124,12 @@ fun ChatPreviewView(
if (cInfo.contact.verified) {
VerifiedIcon()
}
chatPreviewTitleText(if (cInfo.ready) Color.Unspecified else HighOrLowlight)
chatPreviewTitleText(if (cInfo.ready) Color.Unspecified else MaterialTheme.colors.secondary)
}
is ChatInfo.Group ->
when (cInfo.groupInfo.membership.memberStatus) {
GroupMemberStatus.MemInvited -> chatPreviewTitleText(if (chat.chatInfo.incognito) Indigo else MaterialTheme.colors.primary)
GroupMemberStatus.MemAccepted -> chatPreviewTitleText(HighOrLowlight)
GroupMemberStatus.MemAccepted -> chatPreviewTitleText(MaterialTheme.colors.secondary)
else -> chatPreviewTitleText()
}
else -> chatPreviewTitleText()
@@ -173,12 +170,12 @@ fun ChatPreviewView(
when (cInfo) {
is ChatInfo.Direct ->
if (!cInfo.ready) {
Text(stringResource(R.string.contact_connection_pending), color = HighOrLowlight)
Text(stringResource(R.string.contact_connection_pending), color = MaterialTheme.colors.secondary)
}
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)
GroupMemberStatus.MemAccepted -> Text(stringResource(R.string.group_connection_pending), color = MaterialTheme.colors.secondary)
else -> {}
}
else -> {}
@@ -211,7 +208,7 @@ fun ChatPreviewView(
) {
Text(
ts,
color = HighOrLowlight,
color = MaterialTheme.colors.secondary,
style = MaterialTheme.typography.body2,
modifier = Modifier.padding(bottom = 5.dp)
)
@@ -224,10 +221,10 @@ fun ChatPreviewView(
) {
Text(
if (n > 0) unreadCountStr(n) else "",
color = MaterialTheme.colors.onPrimary,
color = Color.White,
fontSize = 11.sp,
modifier = Modifier
.background(if (stopped || showNtfsIcon) HighOrLowlight else MaterialTheme.colors.primary, shape = CircleShape)
.background(if (stopped || showNtfsIcon) MaterialTheme.colors.secondary else MaterialTheme.colors.primaryVariant, shape = CircleShape)
.badgeLayout()
.padding(horizontal = 3.dp)
.padding(vertical = 1.dp)
@@ -239,9 +236,9 @@ fun ChatPreviewView(
contentAlignment = Alignment.Center
) {
Icon(
Icons.Filled.NotificationsOff,
painterResource(R.drawable.ic_notifications_off_filled),
contentDescription = generalGetString(R.string.notifications),
tint = HighOrLowlight,
tint = MaterialTheme.colors.secondary,
modifier = Modifier
.padding(horizontal = 3.dp)
.padding(vertical = 1.dp)
@@ -281,9 +278,9 @@ fun ChatStatusImage(s: NetworkStatus?) {
val descr = s?.statusString
if (s is NetworkStatus.Error) {
Icon(
Icons.Outlined.ErrorOutline,
painterResource(R.drawable.ic_error),
contentDescription = descr,
tint = HighOrLowlight,
tint = MaterialTheme.colors.secondary,
modifier = Modifier
.size(19.dp)
)
@@ -292,7 +289,7 @@ fun ChatStatusImage(s: NetworkStatus?) {
Modifier
.padding(horizontal = 2.dp)
.size(15.dp),
color = HighOrLowlight,
color = MaterialTheme.colors.secondary,
strokeWidth = 1.5.dp
)
}

View File

@@ -3,16 +3,16 @@ 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.res.painterResource
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.ProfileImage
@@ -21,7 +21,7 @@ import chat.simplex.app.views.helpers.ProfileImage
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)
ProfileImage(size = 54.dp, null, if (contactConnection.initiated) R.drawable.ic_add_link else R.drawable.ic_link)
}
Column(
modifier = Modifier
@@ -34,7 +34,7 @@ fun ContactConnectionView(contactConnection: PendingContactConnection) {
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.h3,
fontWeight = FontWeight.Bold,
color = HighOrLowlight
color = MaterialTheme.colors.secondary
)
val height = with(LocalDensity.current) { 46.sp.toDp() }
Text(contactConnection.description, Modifier.heightIn(min = height), maxLines = 2, color = if (isInDarkTheme()) MessagePreviewDark else MessagePreviewLight)
@@ -42,11 +42,10 @@ fun ContactConnectionView(contactConnection: PendingContactConnection) {
val ts = getTimestampText(contactConnection.updatedAt)
Column(
Modifier.fillMaxHeight(),
verticalArrangement = Arrangement.Top
) {
Text(
ts,
color = HighOrLowlight,
color = MaterialTheme.colors.secondary,
style = MaterialTheme.typography.body2,
modifier = Modifier.padding(bottom = 5.dp)
)

View File

@@ -39,11 +39,10 @@ fun ContactRequestView(chatModelIncognito: Boolean, contactRequest: ChatInfo.Con
val ts = getTimestampText(contactRequest.contactRequest.updatedAt)
Column(
Modifier.fillMaxHeight(),
verticalArrangement = Arrangement.Top
) {
Text(
ts,
color = HighOrLowlight,
color = MaterialTheme.colors.secondary,
style = MaterialTheme.typography.body2,
modifier = Modifier.padding(bottom = 5.dp)
)

View File

@@ -6,14 +6,12 @@ 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.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.capitalize
import androidx.compose.ui.text.font.FontWeight
@@ -21,8 +19,7 @@ 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.ui.theme.*
import chat.simplex.app.views.helpers.*
import kotlinx.coroutines.flow.MutableStateFlow
@@ -38,7 +35,7 @@ fun ShareListView(chatModel: ChatModel, stopped: Boolean) {
Column(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colors.background)
.themedBackground()
) {
if (chatModel.chats.isNotEmpty()) {
ShareList(chatModel, search = searchInList)
@@ -56,7 +53,7 @@ fun ShareListView(chatModel: ChatModel, stopped: Boolean) {
@Composable
private fun EmptyList() {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text(stringResource(R.string.you_have_no_chats), color = HighOrLowlight)
Text(stringResource(R.string.you_have_no_chats), color = MaterialTheme.colors.secondary)
}
}
@@ -86,7 +83,7 @@ private fun ShareListToolbar(chatModel: ChatModel, userPickerState: MutableState
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)
Icon(painterResource(R.drawable.ic_search_500), stringResource(android.R.string.search_go).capitalize(Locale.current), tint = MaterialTheme.colors.primary)
}
}
}
@@ -99,7 +96,7 @@ private fun ShareListToolbar(chatModel: ChatModel, userPickerState: MutableState
)
}) {
Icon(
Icons.Filled.Report,
painterResource(R.drawable.ic_report_filled),
generalGetString(R.string.chat_is_stopped_indication),
tint = Color.Red,
)
@@ -114,7 +111,7 @@ private fun ShareListToolbar(chatModel: ChatModel, userPickerState: MutableState
Text(
when (chatModel.sharedContent.value) {
is SharedContent.Text -> stringResource(R.string.share_message)
is SharedContent.Images -> stringResource(R.string.share_image)
is SharedContent.Media -> stringResource(R.string.share_image)
is SharedContent.File -> stringResource(R.string.share_file)
else -> stringResource(R.string.share_message)
},
@@ -123,7 +120,7 @@ private fun ShareListToolbar(chatModel: ChatModel, userPickerState: MutableState
)
if (chatModel.incognito.value) {
Icon(
Icons.Filled.TheaterComedy,
painterResource(R.drawable.ic_theater_comedy_filled),
stringResource(R.string.incognito),
tint = Indigo,
modifier = Modifier.padding(10.dp).size(26.dp)

View File

@@ -1,7 +1,6 @@
package chat.simplex.app.views.chatlist
import SectionItemView
import SectionItemViewSpaceBetween
import android.util.Log
import androidx.compose.animation.core.*
import androidx.compose.foundation.*
@@ -9,9 +8,6 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.*
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.*
@@ -19,6 +15,7 @@ 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.res.painterResource
import androidx.compose.ui.text.capitalize
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.intl.Locale
@@ -110,12 +107,12 @@ fun UserPicker(
.width(IntrinsicSize.Min)
.height(IntrinsicSize.Min)
.shadow(8.dp, RoundedCornerShape(corner = CornerSize(25.dp)), clip = true)
.background(if (isInDarkTheme()) Color(0xff222222) else MaterialTheme.colors.background, RoundedCornerShape(corner = CornerSize(25.dp)))
.background(MaterialTheme.colors.surface, RoundedCornerShape(corner = CornerSize(25.dp)))
.clip(RoundedCornerShape(corner = CornerSize(25.dp)))
) {
Column(Modifier.weight(1f).verticalScroll(rememberScrollState())) {
users.forEach { u ->
UserProfilePickerItem(u.user, u.unreadCount, PaddingValues(start = DEFAULT_PADDING, end = DEFAULT_PADDING * 2), openSettings = {
UserProfilePickerItem(u.user, u.unreadCount, PaddingValues(start = DEFAULT_PADDING_HALF, end = DEFAULT_PADDING), openSettings = {
settingsClicked()
userPickerState.value = AnimatedViewState.GONE
}) {
@@ -153,7 +150,7 @@ fun UserPicker(
}
@Composable
fun UserProfilePickerItem(u: User, unreadCount: Int = 0, padding: PaddingValues = PaddingValues(start = 8.dp, end = DEFAULT_PADDING), onLongClick: () -> Unit = {}, openSettings: () -> Unit = {}, onClick: () -> Unit) {
fun UserProfilePickerItem(u: User, unreadCount: Int = 0, padding: PaddingValues = PaddingValues(start = DEFAULT_PADDING_HALF, end = DEFAULT_PADDING), onLongClick: () -> Unit = {}, openSettings: () -> Unit = {}, onClick: () -> Unit) {
Row(
Modifier
.fillMaxWidth()
@@ -170,27 +167,25 @@ fun UserProfilePickerItem(u: User, unreadCount: Int = 0, padding: PaddingValues
) {
UserProfileRow(u)
if (u.activeUser) {
Icon(Icons.Filled.Done, null, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
Icon(painterResource(R.drawable.ic_done_filled), null, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
} else if (u.hidden) {
Icon(Icons.Outlined.Lock, null, Modifier.size(20.dp), tint = HighOrLowlight)
Icon(painterResource(R.drawable.ic_lock), null, Modifier.size(20.dp), tint = MaterialTheme.colors.secondary)
} else if (unreadCount > 0) {
Row {
Box(
contentAlignment = Alignment.Center
) {
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
.background(MaterialTheme.colors.primaryVariant, shape = CircleShape)
.padding(2.dp)
.badgeLayout()
)
Spacer(Modifier.width(2.dp))
}
} else if (!u.showNtfs) {
Icon(Icons.Outlined.NotificationsOff, null, Modifier.size(20.dp), tint = HighOrLowlight)
Icon(painterResource(R.drawable.ic_notifications_off), null, Modifier.size(20.dp), tint = MaterialTheme.colors.secondary)
} else {
Box(Modifier.size(20.dp))
}
@@ -212,7 +207,7 @@ fun UserProfileRow(u: User) {
Text(
u.displayName,
modifier = Modifier
.padding(start = 8.dp, end = 8.dp),
.padding(start = 10.dp, end = 8.dp),
color = if (isInDarkTheme()) MenuTextColorDark else Color.Black,
fontWeight = if (u.activeUser) FontWeight.Medium else FontWeight.Normal
)
@@ -221,10 +216,10 @@ fun UserProfileRow(u: User) {
@Composable
private fun SettingsPickerItem(onClick: () -> Unit) {
SectionItemView(onClick, padding = PaddingValues(start = DEFAULT_PADDING * 2.2f, end = DEFAULT_PADDING * 2), minHeight = 68.dp) {
SectionItemView(onClick, padding = PaddingValues(start = DEFAULT_PADDING + 7.dp, end = DEFAULT_PADDING), minHeight = 68.dp) {
val text = generalGetString(R.string.settings_section_title_settings).lowercase().capitalize(Locale.current)
Icon(Icons.Outlined.Settings, text, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
Spacer(Modifier.width(DEFAULT_PADDING * 1.5f))
Icon(painterResource(R.drawable.ic_settings), text, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
Spacer(Modifier.width(DEFAULT_PADDING + 6.dp))
Text(
text,
color = if (isInDarkTheme()) MenuTextColorDark else Color.Black,
@@ -234,10 +229,10 @@ private fun SettingsPickerItem(onClick: () -> Unit) {
@Composable
private fun CancelPickerItem(onClick: () -> Unit) {
SectionItemViewSpaceBetween(onClick, padding = PaddingValues(start = DEFAULT_PADDING * 2.2f, end = DEFAULT_PADDING * 2), minHeight = 68.dp) {
SectionItemView(onClick, padding = PaddingValues(start = DEFAULT_PADDING + 7.dp, end = DEFAULT_PADDING), minHeight = 68.dp) {
val text = generalGetString(R.string.cancel_verb)
Icon(Icons.Outlined.Close, text, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
Spacer(Modifier.width(DEFAULT_PADDING * 1.5f))
Icon(painterResource(R.drawable.ic_close), text, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
Spacer(Modifier.width(DEFAULT_PADDING + 6.dp))
Text(
text,
color = if (isInDarkTheme()) MenuTextColorDark else Color.Black,

View File

@@ -1,6 +1,6 @@
package chat.simplex.app.views.database
import SectionDivider
import SectionBottomSpacer
import SectionTextFooter
import SectionView
import android.content.Context
@@ -13,16 +13,14 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.TAG
import chat.simplex.app.model.ChatModel
@@ -57,20 +55,18 @@ fun ChatArchiveLayout(
) {
Column(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.Start,
) {
AppBarTitle(title)
SectionView(stringResource(R.string.chat_archive_section)) {
SettingsActionItem(
Icons.Outlined.IosShare,
painterResource(R.drawable.ic_ios_share),
stringResource(R.string.save_archive),
saveArchive,
textColor = MaterialTheme.colors.primary,
iconColor = MaterialTheme.colors.primary,
)
SectionDivider()
SettingsActionItem(
Icons.Outlined.Delete,
painterResource(R.drawable.ic_delete),
stringResource(R.string.delete_archive),
deleteArchiveAlert,
textColor = Color.Red,
@@ -81,6 +77,7 @@ fun ChatArchiveLayout(
SectionTextFooter(
String.format(generalGetString(R.string.archive_created_on_ts), archiveTs)
)
SectionBottomSpacer()
}
}
@@ -119,7 +116,8 @@ private fun deleteArchiveAlert(m: ChatModel, archivePath: String) {
} else {
Log.e(TAG, "deleteArchiveAlert delete() error")
}
}
},
destructive = true,
)
}

View File

@@ -1,6 +1,6 @@
package chat.simplex.app.views.database
import SectionDivider
import SectionBottomSpacer
import SectionItemView
import SectionItemViewSpaceBetween
import SectionTextFooter
@@ -12,15 +12,13 @@ import androidx.compose.foundation.shape.ZeroCornerSize
import androidx.compose.foundation.text.*
import androidx.compose.material.*
import androidx.compose.material.TextFieldDefaults.indicatorLine
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.SolidColor
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontWeight
@@ -114,7 +112,7 @@ fun DatabaseEncryptionView(m: ChatModel) {
Modifier
.padding(horizontal = 2.dp)
.size(30.dp),
color = HighOrLowlight,
color = MaterialTheme.colors.secondary,
strokeWidth = 2.5.dp
)
}
@@ -137,7 +135,6 @@ fun DatabaseEncryptionLayout(
) {
Column(
Modifier.fillMaxWidth().verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.Start,
) {
AppBarTitle(stringResource(R.string.database_passphrase))
SectionView(null) {
@@ -162,8 +159,6 @@ fun DatabaseEncryptionLayout(
}
if (!initialRandomDBPassphrase.value && chatDbEncrypted == true) {
SectionDivider()
PassphraseField(
currentKey,
generalGetString(R.string.current_passphrase),
@@ -173,8 +168,6 @@ fun DatabaseEncryptionLayout(
)
}
SectionDivider()
PassphraseField(
newKey,
generalGetString(R.string.new_passphrase),
@@ -206,8 +199,6 @@ fun DatabaseEncryptionLayout(
!validKey(newKey.value) ||
progressIndicator.value
SectionDivider()
PassphraseField(
confirmNewKey,
generalGetString(R.string.confirm_new_passphrase),
@@ -219,10 +210,8 @@ fun DatabaseEncryptionLayout(
}),
)
SectionDivider()
SectionItemViewSpaceBetween(onClickUpdate, disabled = disabled, minHeight = TextFieldDefaults.MinHeight) {
Text(generalGetString(R.string.update_database_passphrase), color = if (disabled) HighOrLowlight else MaterialTheme.colors.primary)
Text(generalGetString(R.string.update_database_passphrase), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary)
}
}
@@ -245,6 +234,7 @@ fun DatabaseEncryptionLayout(
SectionTextFooter(generalGetString(R.string.impossible_to_recover_passphrase))
}
}
SectionBottomSpacer()
}
}
@@ -254,7 +244,7 @@ fun encryptDatabaseSavedAlert(onConfirm: () -> Unit) {
text = generalGetString(R.string.database_will_be_encrypted_and_passphrase_stored) + "\n" + storeSecurelySaved(),
confirmText = generalGetString(R.string.encrypt_database),
onConfirm = onConfirm,
destructive = false,
destructive = true,
)
}
@@ -300,9 +290,9 @@ fun SavePassphraseSetting(
SectionItemView(minHeight = minHeight) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
if (storedKey) Icons.Filled.VpnKey else Icons.Filled.VpnKeyOff,
if (storedKey) painterResource(R.drawable.ic_vpn_key_filled) else painterResource(R.drawable.ic_vpn_key_off_filled),
stringResource(R.string.save_passphrase_in_keychain),
tint = if (storedKey) SimplexGreen else HighOrLowlight
tint = if (storedKey) SimplexGreen else MaterialTheme.colors.secondary
)
Spacer(Modifier.padding(horizontal = 4.dp))
Text(
@@ -311,13 +301,9 @@ fun SavePassphraseSetting(
color = Color.Unspecified
)
Spacer(Modifier.fillMaxWidth().weight(1f))
Switch(
DefaultSwitch(
checked = useKeychain,
onCheckedChange = onCheckedChange,
colors = SwitchDefaults.colors(
checkedThumbColor = MaterialTheme.colors.primary,
uncheckedThumbColor = HighOrLowlight
),
enabled = !initialRandomDBPassphrase && !progressIndicator
)
}
@@ -371,10 +357,10 @@ fun PassphraseField(
var valid by remember { mutableStateOf(validKey(key.value)) }
var showKey by remember { mutableStateOf(false) }
val icon = if (valid) {
if (showKey) Icons.Filled.VisibilityOff else Icons.Filled.Visibility
} else Icons.Outlined.Error
if (showKey) painterResource(R.drawable.ic_visibility_off_filled) else painterResource(R.drawable.ic_visibility_filled)
} else painterResource(R.drawable.ic_error)
val iconColor = if (valid) {
if (showStrength && key.value.isNotEmpty()) PassphraseStrength.check(key.value).color else HighOrLowlight
if (showStrength && key.value.isNotEmpty()) PassphraseStrength.check(key.value).color else MaterialTheme.colors.secondary
} else Color.Red
val keyboard = LocalSoftwareKeyboardController.current
val keyboardOptions = KeyboardOptions(
@@ -431,7 +417,7 @@ fun PassphraseField(
TextFieldDefaults.TextFieldDecorationBox(
value = state.value.text,
innerTextField = innerTextField,
placeholder = { Text(placeholder, color = HighOrLowlight) },
placeholder = { Text(placeholder, color = MaterialTheme.colors.secondary) },
singleLine = true,
enabled = enabled,
isError = !valid,

View File

@@ -1,5 +1,6 @@
package chat.simplex.app.views.database
import SectionBottomSpacer
import SectionSpacer
import SectionView
import android.content.Context
@@ -61,7 +62,7 @@ fun DatabaseErrorView(
fun DatabaseErrorDetails(@StringRes title: Int, content: @Composable ColumnScope.() -> Unit) {
Text(
generalGetString(title),
Modifier.padding(start = 16.dp, top = 16.dp, bottom = 16.dp),
Modifier.padding(start = DEFAULT_PADDING, top = DEFAULT_PADDING, bottom = DEFAULT_PADDING),
style = MaterialTheme.typography.h1
)
SectionView(null, padding = PaddingValues(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF), content)
@@ -79,7 +80,6 @@ fun DatabaseErrorView(
Column(
Modifier.fillMaxSize().verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.Center,
) {
val buttonEnabled = validKey(dbKey.value) && !progressIndicator.value
@@ -158,7 +158,7 @@ fun DatabaseErrorView(
if (restoreDbFromBackup.value) {
SectionSpacer()
Text(generalGetString(R.string.database_backup_can_be_restored))
Spacer(Modifier.size(16.dp))
Spacer(Modifier.size(DEFAULT_PADDING))
RestoreDbButton {
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.restore_database_alert_title),
@@ -169,6 +169,7 @@ fun DatabaseErrorView(
)
}
}
SectionBottomSpacer()
}
if (progressIndicator.value) {
Box(
@@ -179,7 +180,7 @@ fun DatabaseErrorView(
Modifier
.padding(horizontal = 2.dp)
.size(30.dp),
color = HighOrLowlight,
color = MaterialTheme.colors.secondary,
strokeWidth = 2.5.dp
)
}

View File

@@ -1,9 +1,9 @@
package chat.simplex.app.views.database
import SectionDivider
import SectionBottomSpacer
import SectionDividerSpaced
import SectionTextFooter
import SectionItemView
import SectionSpacer
import SectionView
import android.content.Context
import android.content.res.Configuration
@@ -17,14 +17,12 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontWeight
@@ -116,7 +114,7 @@ fun DatabaseView(
Modifier
.padding(horizontal = 2.dp)
.size(30.dp),
color = HighOrLowlight,
color = MaterialTheme.colors.secondary,
strokeWidth = 2.5.dp
)
}
@@ -154,13 +152,12 @@ fun DatabaseLayout(
val operationsDisabled = !stopped || progressIndicator
Column(
Modifier.fillMaxWidth().verticalScroll(rememberScrollState()).padding(bottom = DEFAULT_BOTTOM_PADDING),
horizontalAlignment = Alignment.Start,
Modifier.fillMaxWidth().verticalScroll(rememberScrollState()),
) {
AppBarTitle(stringResource(R.string.your_chat_database))
SectionView(stringResource(R.string.messages_section_title).uppercase()) {
SectionItemView { TtlOptions(chatItemTTL, enabled = rememberUpdatedState(!progressIndicator && !chatDbChanged), onChatItemTTLSelected) }
TtlOptions(chatItemTTL, enabled = rememberUpdatedState(!stopped && !progressIndicator), onChatItemTTLSelected)
}
SectionTextFooter(
remember(currentUser?.displayName) {
@@ -173,27 +170,28 @@ fun DatabaseLayout(
}
}
)
SectionSpacer()
SectionDividerSpaced(maxTopPadding = true)
SectionView(stringResource(R.string.run_chat_section)) {
RunChatSetting(runChat, stopped, chatDbDeleted, startChat, stopChatAlert)
}
SectionSpacer()
SectionDividerSpaced()
SectionView(stringResource(R.string.chat_database_section)) {
val unencrypted = chatDbEncrypted == false
SettingsActionItem(
if (unencrypted) Icons.Outlined.LockOpen else if (useKeyChain) Icons.Filled.VpnKey else Icons.Outlined.Lock,
if (unencrypted) painterResource(R.drawable.ic_lock_open) else if (useKeyChain) painterResource(R.drawable.ic_vpn_key_filled)
else painterResource(R
.drawable.ic_lock),
stringResource(R.string.database_passphrase),
click = showSettingsModal() { DatabaseEncryptionView(it) },
iconColor = if (unencrypted) WarningOrange else HighOrLowlight,
iconColor = if (unencrypted) WarningOrange else MaterialTheme.colors.secondary,
disabled = operationsDisabled
)
SectionDivider()
AppDataBackupPreference(privacyFullBackup, initialRandomDBPassphrase)
SectionDivider()
SectionDividerSpaced(maxBottomPadding = false)
SettingsActionItem(
Icons.Outlined.IosShare,
painterResource(R.drawable.ic_ios_share),
stringResource(R.string.export_database),
click = {
if (initialRandomDBPassphrase.get()) {
@@ -206,31 +204,28 @@ fun DatabaseLayout(
iconColor = MaterialTheme.colors.primary,
disabled = operationsDisabled
)
SectionDivider()
SettingsActionItem(
Icons.Outlined.FileDownload,
painterResource(R.drawable.ic_download),
stringResource(R.string.import_database),
{ importArchiveLauncher.launch("application/zip") },
textColor = Color.Red,
iconColor = Color.Red,
disabled = operationsDisabled
)
SectionDivider()
val chatArchiveNameVal = chatArchiveName.value
val chatArchiveTimeVal = chatArchiveTime.value
val chatLastStartVal = chatLastStart.value
if (chatArchiveNameVal != null && chatArchiveTimeVal != null && chatLastStartVal != null) {
val title = chatArchiveTitle(chatArchiveTimeVal, chatLastStartVal)
SettingsActionItem(
Icons.Outlined.Inventory2,
painterResource(R.drawable.ic_inventory_2),
title,
click = showSettingsModal { ChatArchiveView(it, title, chatArchiveNameVal, chatArchiveTimeVal) },
disabled = operationsDisabled
)
SectionDivider()
}
SettingsActionItem(
Icons.Outlined.DeleteForever,
painterResource(R.drawable.ic_delete_forever),
stringResource(R.string.delete_database),
deleteChatAlert,
textColor = Color.Red,
@@ -245,7 +240,7 @@ fun DatabaseLayout(
stringResource(R.string.stop_chat_to_enable_database_actions)
}
)
SectionSpacer()
SectionDividerSpaced(maxTopPadding = true)
SectionView(stringResource(R.string.files_and_media_section).uppercase()) {
val deleteFilesDisabled = operationsDisabled || appFilesCountAndSize.value.first == 0
@@ -255,7 +250,7 @@ fun DatabaseLayout(
) {
Text(
stringResource(if (users.size > 1) R.string.delete_files_and_media_for_all_users else R.string.delete_files_and_media_all),
color = if (deleteFilesDisabled) HighOrLowlight else Color.Red
color = if (deleteFilesDisabled) MaterialTheme.colors.secondary else Color.Red
)
}
}
@@ -267,35 +262,23 @@ fun DatabaseLayout(
String.format(stringResource(R.string.total_files_count_and_size), count, formatBytes(size))
}
)
SectionBottomSpacer()
}
}
@Composable
private fun AppDataBackupPreference(privacyFullBackup: SharedPreference<Boolean>, initialRandomDBPassphrase: SharedPreference<Boolean>) {
SectionItemView {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Outlined.Backup, stringResource(R.string.full_backup), tint = HighOrLowlight)
Spacer(Modifier.padding(horizontal = 4.dp))
val prefState = remember { mutableStateOf(privacyFullBackup.get()) }
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Text(stringResource(R.string.full_backup), Modifier.padding(end = 24.dp))
Spacer(Modifier.fillMaxWidth().weight(1f))
Switch(
checked = prefState.value,
onCheckedChange = {
if (initialRandomDBPassphrase.get()) {
exportProhibitedAlert()
} else {
privacyFullBackup.set(it)
prefState.value = it
}
},
colors = SwitchDefaults.colors(
checkedThumbColor = MaterialTheme.colors.primary,
uncheckedThumbColor = HighOrLowlight
)
)
}
SettingsPreferenceItem(
painterResource(R.drawable.ic_backup),
iconColor = MaterialTheme.colors.secondary,
pref = privacyFullBackup,
text = stringResource(R.string.full_backup)
) {
if (initialRandomDBPassphrase.get()) {
exportProhibitedAlert()
privacyFullBackup.set(false)
} else {
privacyFullBackup.set(it)
}
}
}
@@ -311,7 +294,8 @@ private fun setChatItemTTLAlert(
text = generalGetString(R.string.enable_automatic_deletion_message),
confirmText = generalGetString(R.string.delete_messages),
onConfirm = { setCiTTL(m, selectedChatItemTTL, progressIndicator, appFilesCountAndSize, context) },
onDismiss = { selectedChatItemTTL.value = m.chatItemTTL.value }
onDismiss = { selectedChatItemTTL.value = m.chatItemTTL.value },
destructive = true,
)
}
@@ -350,36 +334,23 @@ fun RunChatSetting(
startChat: () -> Unit,
stopChatAlert: () -> Unit
) {
SectionItemView() {
Row(verticalAlignment = Alignment.CenterVertically) {
val chatRunningText = if (stopped) stringResource(R.string.chat_is_stopped) else stringResource(R.string.chat_is_running)
Icon(
if (stopped) Icons.Filled.Report else Icons.Filled.PlayArrow,
chatRunningText,
tint = if (stopped) Color.Red else MaterialTheme.colors.primary
)
Spacer(Modifier.padding(horizontal = 4.dp))
Text(
chatRunningText,
Modifier.padding(end = 24.dp)
)
Spacer(Modifier.fillMaxWidth().weight(1f))
Switch(
enabled = !chatDbDeleted,
checked = runChat,
onCheckedChange = { runChatSwitch ->
if (runChatSwitch) {
startChat()
} else {
stopChatAlert()
}
},
colors = SwitchDefaults.colors(
checkedThumbColor = MaterialTheme.colors.primary,
uncheckedThumbColor = HighOrLowlight
),
)
}
val chatRunningText = if (stopped) stringResource(R.string.chat_is_stopped) else stringResource(R.string.chat_is_running)
SettingsActionItemWithContent(
icon = if (stopped) painterResource(R.drawable.ic_report_filled) else painterResource(R.drawable.ic_play_arrow_filled),
text = chatRunningText,
iconColor = if (stopped) Color.Red else MaterialTheme.colors.primary,
) {
DefaultSwitch(
enabled = !chatDbDeleted,
checked = runChat,
onCheckedChange = { runChatSwitch ->
if (runChatSwitch) {
startChat()
} else {
stopChatAlert()
}
},
)
}
}
@@ -573,7 +544,8 @@ private fun importArchiveAlert(
title = generalGetString(R.string.import_database_question),
text = generalGetString(R.string.your_current_chat_database_will_be_deleted_and_replaced_with_the_imported_one),
confirmText = generalGetString(R.string.import_database_confirmation),
onConfirm = { importArchive(m, context, importedArchiveUri, appFilesCountAndSize, progressIndicator) }
onConfirm = { importArchive(m, context, importedArchiveUri, appFilesCountAndSize, progressIndicator) },
destructive = true,
)
}
@@ -638,7 +610,8 @@ private fun deleteChatAlert(m: ChatModel, progressIndicator: MutableState<Boolea
title = generalGetString(R.string.delete_chat_profile_question),
text = generalGetString(R.string.delete_chat_profile_action_cannot_be_undone_warning),
confirmText = generalGetString(R.string.delete_verb),
onConfirm = { deleteChat(m, progressIndicator) }
onConfirm = { deleteChat(m, progressIndicator) },
destructive = true,
)
}

View File

@@ -37,7 +37,6 @@ class AlertManager {
) {
showAlert {
AlertDialog(
backgroundColor = if (isInDarkTheme()) Color(0xff222222) else MaterialTheme.colors.background,
onDismissRequest = this::hideAlert,
title = alertTitle(title),
text = alertText(text),
@@ -56,7 +55,7 @@ class AlertManager {
Dialog(onDismissRequest = this::hideAlert) {
Column(
Modifier
.background(if (isInDarkTheme()) Color(0xff222222) else MaterialTheme.colors.background, RoundedCornerShape(corner = CornerSize(25.dp)))
.background(MaterialTheme.colors.surface, RoundedCornerShape(corner = CornerSize(25.dp)))
.padding(bottom = DEFAULT_PADDING)
) {
Text(
@@ -67,7 +66,7 @@ class AlertManager {
)
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.high) {
if (text != null) {
Text(text, Modifier.fillMaxWidth().padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING * 1.5f), fontSize = 16.sp, textAlign = TextAlign.Center, color = HighOrLowlight)
Text(text, Modifier.fillMaxWidth().padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING * 1.5f), fontSize = 16.sp, textAlign = TextAlign.Center, color = MaterialTheme.colors.secondary)
}
buttons()
}
@@ -106,7 +105,6 @@ class AlertManager {
}) { Text(confirmText, color = if (destructive) MaterialTheme.colors.error else Color.Unspecified) }
}
},
backgroundColor = if (isInDarkTheme()) Color(0xff222222) else MaterialTheme.colors.background,
shape = RoundedCornerShape(corner = CornerSize(25.dp))
)
}
@@ -129,7 +127,7 @@ class AlertManager {
text = alertText(text),
buttons = {
Column(
Modifier.fillMaxWidth().padding(horizontal = 8.dp).padding(top = 16.dp, bottom = 2.dp),
Modifier.fillMaxWidth().padding(horizontal = DEFAULT_PADDING_HALF).padding(top = DEFAULT_PADDING, bottom = 2.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
TextButton(onClick = {
@@ -142,7 +140,6 @@ class AlertManager {
}) { Text(confirmText, color = if (destructive) Color.Red else Color.Unspecified, textAlign = TextAlign.End) }
}
},
backgroundColor = if (isInDarkTheme()) Color(0xff222222) else MaterialTheme.colors.background,
shape = RoundedCornerShape(corner = CornerSize(25.dp))
)
}
@@ -150,7 +147,7 @@ class AlertManager {
fun showAlertMsg(
title: String, text: String? = null,
confirmText: String = generalGetString(R.string.ok), onConfirm: (() -> Unit)? = null
confirmText: String = generalGetString(R.string.ok)
) {
showAlert {
AlertDialog(
@@ -163,23 +160,19 @@ class AlertManager {
horizontalArrangement = Arrangement.Center
) {
TextButton(onClick = {
onConfirm?.invoke()
hideAlert()
}) { Text(confirmText, color = Color.Unspecified) }
}
},
backgroundColor = if (isInDarkTheme()) Color(0xff222222) else MaterialTheme.colors.background,
shape = RoundedCornerShape(corner = CornerSize(25.dp))
)
}
}
fun showAlertMsg(
title: Int,
text: Int? = null,
confirmText: Int = R.string.ok,
onConfirm: (() -> Unit)? = null
) = showAlertMsg(generalGetString(title), if (text != null) generalGetString(text) else null, generalGetString(confirmText), onConfirm)
) = showAlertMsg(generalGetString(title), if (text != null) generalGetString(text) else null, generalGetString(confirmText))
@Composable
fun showInView() {
@@ -212,7 +205,7 @@ private fun alertText(text: String?): (@Composable () -> Unit)? {
Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
fontSize = 16.sp,
color = HighOrLowlight
color = MaterialTheme.colors.secondary
)
})
}

View File

@@ -5,15 +5,12 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.*
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
@@ -23,18 +20,18 @@ import chat.simplex.app.model.ChatInfo
import chat.simplex.app.ui.theme.SimpleXTheme
@Composable
fun ChatInfoImage(chatInfo: ChatInfo, size: Dp, iconColor: Color = MaterialTheme.colors.secondary) {
fun ChatInfoImage(chatInfo: ChatInfo, size: Dp, iconColor: Color = MaterialTheme.colors.secondaryVariant) {
val icon =
if (chatInfo is ChatInfo.Group) Icons.Filled.SupervisedUserCircle
else Icons.Filled.AccountCircle
if (chatInfo is ChatInfo.Group) R.drawable.ic_supervised_user_circle_filled
else R.drawable.ic_account_circle_filled
ProfileImage(size, chatInfo.image, icon, iconColor)
}
@Composable
fun IncognitoImage(size: Dp, iconColor: Color = MaterialTheme.colors.secondary) {
fun IncognitoImage(size: Dp, iconColor: Color = MaterialTheme.colors.secondaryVariant) {
Box(Modifier.size(size)) {
Icon(
Icons.Filled.TheaterComedy, stringResource(R.string.incognito),
painterResource(R.drawable.ic_theater_comedy_filled), stringResource(R.string.incognito),
modifier = Modifier.size(size).padding(size / 12),
iconColor
)
@@ -45,17 +42,31 @@ fun IncognitoImage(size: Dp, iconColor: Color = MaterialTheme.colors.secondary)
fun ProfileImage(
size: Dp,
image: String? = null,
icon: ImageVector = Icons.Filled.AccountCircle,
color: Color = MaterialTheme.colors.secondary
icon: Int = R.drawable.ic_account_circle_filled,
color: Color = MaterialTheme.colors.secondaryVariant
) {
Box(Modifier.size(size)) {
if (image == null) {
Icon(
icon,
contentDescription = stringResource(R.string.icon_descr_profile_image_placeholder),
tint = color,
modifier = Modifier.fillMaxSize()
)
val iconToReplace = when (icon) {
R.drawable.ic_account_circle_filled -> AccountCircleFilled
R.drawable.ic_supervised_user_circle_filled -> SupervisedUserCircleFilled
else -> null
}
if (iconToReplace != null) {
Icon(
iconToReplace,
contentDescription = stringResource(R.string.icon_descr_profile_image_placeholder),
tint = color,
modifier = Modifier.fillMaxSize()
)
} else {
Icon(
painterResource(icon),
contentDescription = stringResource(R.string.icon_descr_profile_image_placeholder),
tint = color,
modifier = Modifier.fillMaxSize()
)
}
} else {
val imageBitmap = base64ToBitmap(image).asImageBitmap()
Image(
@@ -68,6 +79,7 @@ fun ProfileImage(
}
}
@Preview
@Composable
fun PreviewChatInfoImage() {

View File

@@ -1,21 +1,24 @@
package chat.simplex.app.views.helpers
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.*
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.views.newchat.ActionButton
sealed class AttachmentOption {
object TakePhoto: AttachmentOption()
object PickMedia: AttachmentOption()
object PickFile: AttachmentOption()
object CameraPhoto: AttachmentOption()
object GalleryImage: AttachmentOption()
object GalleryVideo: AttachmentOption()
object File: AttachmentOption()
}
@Composable
@@ -37,16 +40,20 @@ fun ChooseAttachmentView(
.padding(horizontal = 8.dp, vertical = 30.dp),
horizontalArrangement = Arrangement.SpaceEvenly
) {
ActionButton(null, stringResource(R.string.use_camera_button), icon = Icons.Outlined.PhotoCamera) {
attachmentOption.value = AttachmentOption.TakePhoto
ActionButton(Modifier.fillMaxWidth(0.25f), null, stringResource(R.string.use_camera_button), icon = painterResource(R.drawable.ic_camera_enhance)) {
attachmentOption.value = AttachmentOption.CameraPhoto
hide()
}
ActionButton(null, stringResource(R.string.from_gallery_button), icon = Icons.Outlined.Collections) {
attachmentOption.value = AttachmentOption.PickMedia
ActionButton(Modifier.fillMaxWidth(0.33f), null, stringResource(R.string.gallery_image_button), icon = painterResource(R.drawable.ic_add_photo)) {
attachmentOption.value = AttachmentOption.GalleryImage
hide()
}
ActionButton(null, stringResource(R.string.choose_file), icon = Icons.Outlined.InsertDriveFile) {
attachmentOption.value = AttachmentOption.PickFile
ActionButton(Modifier.fillMaxWidth(0.50f), null, stringResource(R.string.gallery_video_button), icon = painterResource(R.drawable.ic_smart_display)) {
attachmentOption.value = AttachmentOption.GalleryVideo
hide()
}
ActionButton(Modifier.fillMaxWidth(1f), null, stringResource(R.string.choose_file), icon = painterResource(R.drawable.ic_note_add)) {
attachmentOption.value = AttachmentOption.File
hide()
}
}

View File

@@ -3,14 +3,16 @@ package chat.simplex.app.views.helpers
import android.content.res.Configuration
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
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.ui.theme.*
@Composable
@@ -42,30 +44,21 @@ fun CloseSheetBar(close: (() -> Unit)?, endButtons: @Composable RowScope.() -> U
@Composable
fun AppBarTitle(title: String, withPadding: Boolean = true) {
val padding = if (withPadding)
PaddingValues(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING)
else
PaddingValues(bottom = DEFAULT_PADDING)
val theme = CurrentColors.collectAsState()
val titleColor = CurrentColors.collectAsState().value.appColors.title
val brush = if (theme.value.base == DefaultTheme.SIMPLEX)
Brush.linearGradient(listOf(titleColor.darker(0.2f), titleColor.lighter(0.35f)), Offset(0f, Float.POSITIVE_INFINITY), Offset(Float.POSITIVE_INFINITY, 0f))
else // color is not updated when changing themes if I pass null here
Brush.linearGradient(listOf(titleColor, titleColor), Offset(0f, Float.POSITIVE_INFINITY), Offset(Float.POSITIVE_INFINITY, 0f))
Text(
title,
Modifier
.fillMaxWidth()
.padding(padding),
.padding(bottom = DEFAULT_PADDING * 1.5f, start = if (withPadding) DEFAULT_PADDING else 0.dp, end = if (withPadding) DEFAULT_PADDING else 0.dp,),
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.h1
)
}
@Composable
fun ColumnScope.AppBarTitleCentered(title: String, withPadding: Boolean = true) {
Text(
title,
Modifier
.padding(bottom = if (withPadding) DEFAULT_PADDING * 1.5f else 0.dp)
.align(Alignment.CenterHorizontally),
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.h1,
color = MaterialTheme.colors.primary
style = MaterialTheme.typography.h1.copy(brush = brush),
color = MaterialTheme.colors.primaryVariant,
textAlign = TextAlign.Center
)
}

View File

@@ -0,0 +1,143 @@
package chat.simplex.app.views.helpers
import androidx.compose.material.icons.materialIcon
import androidx.compose.material.icons.materialPath
import androidx.compose.ui.graphics.vector.*
val AccountCircleFilled: ImageVector
get() {
if (_accountCircleFilled != null) {
return _accountCircleFilled!!
}
_accountCircleFilled = materialIcon(name = "Filled.AccountCircle") {
materialPath {
moveTo(12.0f, 2.0f)
curveTo(6.48f, 2.0f, 2.0f, 6.48f, 2.0f, 12.0f)
reflectiveCurveToRelative(4.48f, 10.0f, 10.0f, 10.0f)
reflectiveCurveToRelative(10.0f, -4.48f, 10.0f, -10.0f)
reflectiveCurveTo(17.52f, 2.0f, 12.0f, 2.0f)
close()
moveTo(12.0f, 5.0f)
curveToRelative(1.66f, 0.0f, 3.0f, 1.34f, 3.0f, 3.0f)
reflectiveCurveToRelative(-1.34f, 3.0f, -3.0f, 3.0f)
reflectiveCurveToRelative(-3.0f, -1.34f, -3.0f, -3.0f)
reflectiveCurveToRelative(1.34f, -3.0f, 3.0f, -3.0f)
close()
moveTo(12.0f, 19.2f)
curveToRelative(-2.5f, 0.0f, -4.71f, -1.28f, -6.0f, -3.22f)
curveToRelative(0.03f, -1.99f, 4.0f, -3.08f, 6.0f, -3.08f)
curveToRelative(1.99f, 0.0f, 5.97f, 1.09f, 6.0f, 3.08f)
curveToRelative(-1.29f, 1.94f, -3.5f, 3.22f, -6.0f, 3.22f)
close()
}
}
return _accountCircleFilled!!
}
private var _accountCircleFilled: ImageVector? = null
val SupervisedUserCircleFilled: ImageVector
get() {
if (_supervisedUserCircleFilled != null) {
return _supervisedUserCircleFilled!!
}
_supervisedUserCircleFilled = materialIcon(name = "Filled.SupervisedUserCircle") {
materialPath {
moveTo(11.99f, 2.0f)
curveToRelative(-5.52f, 0.0f, -10.0f, 4.48f, -10.0f, 10.0f)
reflectiveCurveToRelative(4.48f, 10.0f, 10.0f, 10.0f)
reflectiveCurveToRelative(10.0f, -4.48f, 10.0f, -10.0f)
reflectiveCurveToRelative(-4.48f, -10.0f, -10.0f, -10.0f)
close()
moveTo(15.6f, 8.34f)
curveToRelative(1.07f, 0.0f, 1.93f, 0.86f, 1.93f, 1.93f)
curveToRelative(0.0f, 1.07f, -0.86f, 1.93f, -1.93f, 1.93f)
curveToRelative(-1.07f, 0.0f, -1.93f, -0.86f, -1.93f, -1.93f)
curveToRelative(-0.01f, -1.07f, 0.86f, -1.93f, 1.93f, -1.93f)
close()
moveTo(9.6f, 6.76f)
curveToRelative(1.3f, 0.0f, 2.36f, 1.06f, 2.36f, 2.36f)
curveToRelative(0.0f, 1.3f, -1.06f, 2.36f, -2.36f, 2.36f)
reflectiveCurveToRelative(-2.36f, -1.06f, -2.36f, -2.36f)
curveToRelative(0.0f, -1.31f, 1.05f, -2.36f, 2.36f, -2.36f)
close()
moveTo(9.6f, 15.89f)
verticalLineToRelative(3.75f)
curveToRelative(-2.4f, -0.75f, -4.3f, -2.6f, -5.14f, -4.96f)
curveToRelative(1.05f, -1.12f, 3.67f, -1.69f, 5.14f, -1.69f)
curveToRelative(0.53f, 0.0f, 1.2f, 0.08f, 1.9f, 0.22f)
curveToRelative(-1.64f, 0.87f, -1.9f, 2.02f, -1.9f, 2.68f)
close()
moveTo(11.99f, 20.0f)
curveToRelative(-0.27f, 0.0f, -0.53f, -0.01f, -0.79f, -0.04f)
verticalLineToRelative(-4.07f)
curveToRelative(0.0f, -1.42f, 2.94f, -2.13f, 4.4f, -2.13f)
curveToRelative(1.07f, 0.0f, 2.92f, 0.39f, 3.84f, 1.15f)
curveToRelative(-1.17f, 2.97f, -4.06f, 5.09f, -7.45f, 5.09f)
close()
}
}
return _supervisedUserCircleFilled!!
}
private var _supervisedUserCircleFilled: ImageVector? = null
val BoltFilled: ImageVector
get() {
if (_boltFilled != null) {
return _boltFilled!!
}
_boltFilled = materialIcon(name = "Filled.Bolt") {
materialPath {
moveTo(11.0f, 21.0f)
horizontalLineToRelative(-1.0f)
lineToRelative(1.0f, -7.0f)
horizontalLineTo(7.5f)
curveToRelative(-0.58f, 0.0f, -0.57f, -0.32f, -0.38f, -0.66f)
curveToRelative(0.19f, -0.34f, 0.05f, -0.08f, 0.07f, -0.12f)
curveTo(8.48f, 10.94f, 10.42f, 7.54f, 13.0f, 3.0f)
horizontalLineToRelative(1.0f)
lineToRelative(-1.0f, 7.0f)
horizontalLineToRelative(3.5f)
curveToRelative(0.49f, 0.0f, 0.56f, 0.33f, 0.47f, 0.51f)
lineToRelative(-0.07f, 0.15f)
curveTo(12.96f, 17.55f, 11.0f, 21.0f, 11.0f, 21.0f)
close()
}
}
return _boltFilled!!
}
private var _boltFilled: ImageVector? = null
val MoreVertFilled: ImageVector
get() {
if (_moreVertFilled != null) {
return _moreVertFilled!!
}
_moreVertFilled = materialIcon(name = "Filled.MoreVert") {
materialPath {
moveTo(12.0f, 8.0f)
curveToRelative(1.1f, 0.0f, 2.0f, -0.9f, 2.0f, -2.0f)
reflectiveCurveToRelative(-0.9f, -2.0f, -2.0f, -2.0f)
reflectiveCurveToRelative(-2.0f, 0.9f, -2.0f, 2.0f)
reflectiveCurveToRelative(0.9f, 2.0f, 2.0f, 2.0f)
close()
moveTo(12.0f, 10.0f)
curveToRelative(-1.1f, 0.0f, -2.0f, 0.9f, -2.0f, 2.0f)
reflectiveCurveToRelative(0.9f, 2.0f, 2.0f, 2.0f)
reflectiveCurveToRelative(2.0f, -0.9f, 2.0f, -2.0f)
reflectiveCurveToRelative(-0.9f, -2.0f, -2.0f, -2.0f)
close()
moveTo(12.0f, 16.0f)
curveToRelative(-1.1f, 0.0f, -2.0f, 0.9f, -2.0f, 2.0f)
reflectiveCurveToRelative(0.9f, 2.0f, 2.0f, 2.0f)
reflectiveCurveToRelative(2.0f, -0.9f, 2.0f, -2.0f)
reflectiveCurveToRelative(-0.9f, -2.0f, -2.0f, -2.0f)
close()
}
}
return _moreVertFilled!!
}
private var _moreVertFilled: ImageVector? = null

View File

@@ -8,10 +8,6 @@ import androidx.compose.foundation.shape.ZeroCornerSize
import androidx.compose.foundation.text.*
import androidx.compose.material.*
import androidx.compose.material.TextFieldDefaults.indicatorLine
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material.icons.outlined.Error
import androidx.compose.runtime.*
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
@@ -19,12 +15,13 @@ import androidx.compose.ui.focus.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.*
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.R
import chat.simplex.app.views.database.PassphraseStrength
import chat.simplex.app.views.database.validKey
import kotlinx.coroutines.delay
@@ -136,10 +133,10 @@ fun DefaultConfigurableTextField(
var valid by remember { mutableStateOf(validKey(state.value.text)) }
var showKey by remember { mutableStateOf(false) }
val icon = if (valid) {
if (showKey) Icons.Filled.VisibilityOff else Icons.Filled.Visibility
} else Icons.Outlined.Error
if (showKey) painterResource(R.drawable.ic_visibility_off_filled) else painterResource(R.drawable.ic_visibility_filled)
} else painterResource(R.drawable.ic_error)
val iconColor = if (valid) {
if (showPasswordStrength && state.value.text.isNotEmpty()) PassphraseStrength.check(state.value.text).color else HighOrLowlight
if (showPasswordStrength && state.value.text.isNotEmpty()) PassphraseStrength.check(state.value.text).color else MaterialTheme.colors.secondary
} else Color.Red
val keyboard = LocalSoftwareKeyboardController.current
val keyboardOptions = KeyboardOptions(
@@ -191,7 +188,7 @@ fun DefaultConfigurableTextField(
TextFieldDefaults.TextFieldDecorationBox(
value = state.value.text,
innerTextField = innerTextField,
placeholder = { Text(placeholder, color = HighOrLowlight) },
placeholder = { Text(placeholder, color = MaterialTheme.colors.secondary) },
singleLine = true,
enabled = enabled,
isError = !valid,

View File

@@ -1,5 +1,6 @@
package chat.simplex.app.views.helpers
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -19,7 +20,6 @@ fun DefaultDropdownMenu(
dropdownMenuItems: (@Composable () -> Unit)?
) {
MaterialTheme(
colors = MaterialTheme.colors.copy(surface = if (isInDarkTheme()) Color(0xFF080808) else MaterialTheme.colors.background),
shapes = MaterialTheme.shapes.copy(medium = RoundedCornerShape(corner = CornerSize(25.dp)))
) {
DropdownMenu(
@@ -27,6 +27,7 @@ fun DefaultDropdownMenu(
onDismissRequest = { showMenu.value = false },
Modifier
.widthIn(min = 250.dp)
.background(MaterialTheme.colors.surface)
.padding(vertical = 4.dp),
offset = offset,
) {
@@ -42,11 +43,13 @@ fun ExposedDropdownMenuBoxScope.DefaultExposedDropdownMenu(
dropdownMenuItems: (@Composable () -> Unit)?
) {
MaterialTheme(
colors = MaterialTheme.colors.copy(surface = if (isInDarkTheme()) Color(0xFF080808) else MaterialTheme.colors.background),
shapes = MaterialTheme.shapes.copy(medium = RoundedCornerShape(corner = CornerSize(25.dp)))
) {
ExposedDropdownMenu(
modifier = Modifier.widthIn(min = 200.dp).then(modifier),
modifier = Modifier
.widthIn(min = 200.dp)
.background(MaterialTheme.colors.surface)
.then(modifier),
expanded = expanded.value,
onDismissRequest = {
expanded.value = false

View File

@@ -0,0 +1,39 @@
package chat.simplex.app.views.helpers
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.*
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
@Composable
fun DefaultSwitch(
checked: Boolean,
onCheckedChange: ((Boolean) -> Unit)?,
modifier: Modifier = Modifier,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
colors: SwitchColors = SwitchDefaults.colors(
checkedThumbColor = MaterialTheme.colors.primary,
uncheckedThumbColor = MaterialTheme.colors.secondary,
checkedTrackAlpha = 0.0f,
uncheckedTrackAlpha = 0.0f,
)
) {
val color = if (checked) MaterialTheme.colors.primary.copy(alpha = 0.3f) else MaterialTheme.colors.secondary.copy(alpha = 0.3f)
val size = with(LocalDensity.current) { Size(46.dp.toPx(), 28.dp.toPx()) }
val offset = with(LocalDensity.current) { Offset(1.dp.toPx(), 10.dp.toPx()) }
val radius = with(LocalDensity.current) { 28.dp.toPx() }
Switch(
checked = checked,
onCheckedChange = onCheckedChange,
modifier.drawBehind { drawRoundRect(color, size = size, topLeft = offset, cornerRadius = CornerRadius(radius, radius)) },
colors = colors,
enabled = enabled,
interactionSource = interactionSource,
)
}

View File

@@ -4,12 +4,11 @@ import chat.simplex.app.R
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.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.*
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import chat.simplex.app.ui.theme.*
@@ -48,7 +47,7 @@ fun DefaultTopAppBar(
fun NavigationButtonBack(onButtonClicked: (() -> Unit)?) {
IconButton(onButtonClicked ?: {}, enabled = onButtonClicked != null) {
Icon(
Icons.Outlined.ArrowBackIos, stringResource(R.string.back), tint = if (onButtonClicked != null) MaterialTheme.colors.primary else HighOrLowlight
painterResource(R.drawable.ic_arrow_back_ios_new), stringResource(R.string.back), tint = if (onButtonClicked != null) MaterialTheme.colors.primary else MaterialTheme.colors.secondary
)
}
}
@@ -57,7 +56,7 @@ fun NavigationButtonBack(onButtonClicked: (() -> Unit)?) {
fun ShareButton(onButtonClicked: () -> Unit) {
IconButton(onButtonClicked) {
Icon(
Icons.Outlined.Share, stringResource(R.string.share_verb), tint = MaterialTheme.colors.primary
painterResource(R.drawable.ic_share), stringResource(R.string.share_verb), tint = MaterialTheme.colors.primary
)
}
}
@@ -66,7 +65,7 @@ fun ShareButton(onButtonClicked: () -> Unit) {
fun NavigationButtonMenu(onButtonClicked: () -> Unit) {
IconButton(onClick = onButtonClicked) {
Icon(
Icons.Outlined.Menu,
painterResource(R.drawable.ic_menu),
stringResource(R.string.icon_descr_settings),
tint = MaterialTheme.colors.primary,
)

View File

@@ -11,7 +11,7 @@ import kotlinx.serialization.encoding.Encoder
sealed class SharedContent {
data class Text(val text: String): SharedContent()
data class Images(val text: String, val uris: List<Uri>): SharedContent()
data class Media(val text: String, val uris: List<Uri>): SharedContent()
data class File(val text: String, val uri: Uri): SharedContent()
}

View File

@@ -2,20 +2,19 @@ package chat.simplex.app.views.helpers
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ExpandLess
import androidx.compose.material.icons.outlined.ExpandMore
import androidx.compose.ui.res.painterResource
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.graphics.painter.Painter
import androidx.compose.ui.platform.LocalDensity
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.ui.theme.*
import chat.simplex.app.views.usersettings.SettingsActionItemWithContent
@Composable
fun <T> ExposedDropDownSettingRow(
@@ -23,27 +22,13 @@ fun <T> ExposedDropDownSettingRow(
values: List<Pair<T, String>>,
selection: State<T>,
label: String? = null,
icon: ImageVector? = null,
iconTint: Color = HighOrLowlight,
icon: Painter? = null,
iconTint: Color = MaterialTheme.colors.secondary,
enabled: State<Boolean> = mutableStateOf(true),
onSelected: (T) -> Unit
) {
Row(
Modifier.fillMaxWidth().padding(vertical = 10.dp),
verticalAlignment = Alignment.CenterVertically,
) {
SettingsActionItemWithContent(icon, title, iconColor = iconTint, disabled = !enabled.value) {
val expanded = remember { mutableStateOf(false) }
if (icon != null) {
Icon(
icon,
"",
Modifier.padding(end = 8.dp),
tint = iconTint
)
}
Text(title, Modifier.weight(1f), color = if (enabled.value) Color.Unspecified else HighOrLowlight)
ExposedDropdownMenuBox(
expanded = expanded.value,
onExpandedChange = {
@@ -55,19 +40,19 @@ fun <T> ExposedDropDownSettingRow(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.End
) {
val maxWidth = with(LocalDensity.current){ 180.sp.toDp() }
val maxWidth = with(LocalDensity.current) { 180.sp.toDp() }
Text(
values.first { it.first == selection.value }.second + (if (label != null) " $label" else ""),
Modifier.widthIn(max = maxWidth),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = HighOrLowlight
color = MaterialTheme.colors.secondary
)
Spacer(Modifier.size(12.dp))
Icon(
if (!expanded.value) Icons.Outlined.ExpandMore else Icons.Outlined.ExpandLess,
if (!expanded.value) painterResource(R.drawable.ic_expand_more) else painterResource(R.drawable.ic_expand_less),
generalGetString(R.string.icon_descr_more_button),
tint = HighOrLowlight
tint = MaterialTheme.colors.secondary
)
}
DefaultExposedDropdownMenu(

View File

@@ -80,10 +80,16 @@ suspend fun PointerInputScope.detectGesture(
pressScope.release()
}
} catch (_: PointerEventTimeoutCancellationException) {
onLongPress?.invoke(down.position)
if (shouldConsume)
consumeUntilUp()
pressScope.release()
if (onLongPress != null) {
onLongPress(down.position)
if (shouldConsume)
consumeUntilUp()
pressScope.cancel()
} else {
if (shouldConsume)
consumeUntilUp()
pressScope.release()
}
}
}
}

View File

@@ -17,15 +17,13 @@ import androidx.activity.result.contract.ActivityResultContract
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.CallSuper
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Collections
import androidx.compose.material.icons.outlined.PhotoCamera
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
@@ -248,7 +246,7 @@ fun GetImageBottomSheet(
.padding(horizontal = 8.dp, vertical = 30.dp),
horizontalArrangement = Arrangement.SpaceEvenly
) {
ActionButton(null, stringResource(R.string.use_camera_button), icon = Icons.Outlined.PhotoCamera) {
ActionButton(null, stringResource(R.string.use_camera_button), icon = painterResource(R.drawable.ic_photo_camera)) {
when (PackageManager.PERMISSION_GRANTED) {
ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) -> {
cameraLauncher.launchWithFallback()
@@ -259,7 +257,7 @@ fun GetImageBottomSheet(
}
}
}
ActionButton(null, stringResource(R.string.from_gallery_button), icon = Icons.Outlined.Collections) {
ActionButton(null, stringResource(R.string.from_gallery_button), icon = painterResource(R.drawable.ic_image)) {
try {
galleryLauncher.launch(0)
} catch (e: ActivityNotFoundException) {

View File

@@ -6,13 +6,13 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Close
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
@@ -20,9 +20,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.R
import chat.simplex.app.model.LinkPreview
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.chat.item.SentColorLight
import chat.simplex.app.ui.theme.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jsoup.Jsoup
@@ -80,8 +78,9 @@ suspend fun getLinkPreview(url: String): LinkPreview? {
@Composable
fun ComposeLinkView(linkPreview: LinkPreview?, cancelPreview: () -> Unit) {
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage
Row(
Modifier.fillMaxWidth().padding(top = 8.dp).background(SentColorLight),
Modifier.fillMaxWidth().padding(top = 8.dp).background(sentColor),
verticalAlignment = Alignment.CenterVertically
) {
if (linkPreview == null) {
@@ -91,7 +90,7 @@ fun ComposeLinkView(linkPreview: LinkPreview?, cancelPreview: () -> Unit) {
) {
CircularProgressIndicator(
Modifier.size(16.dp),
color = HighOrLowlight,
color = MaterialTheme.colors.secondary,
strokeWidth = 2.dp
)
}
@@ -112,7 +111,7 @@ fun ComposeLinkView(linkPreview: LinkPreview?, cancelPreview: () -> Unit) {
}
IconButton(onClick = cancelPreview, modifier = Modifier.padding(0.dp)) {
Icon(
Icons.Outlined.Close,
painterResource(R.drawable.ic_close),
contentDescription = stringResource(R.string.icon_descr_cancel_link_preview),
tint = MaterialTheme.colors.primary,
modifier = Modifier.padding(10.dp)
@@ -135,7 +134,7 @@ fun ChatItemLinkView(linkPreview: LinkPreview) {
if (linkPreview.description != "") {
Text(linkPreview.description, maxLines = 12, overflow = TextOverflow.Ellipsis, fontSize = 14.sp, lineHeight = 20.sp)
}
Text(linkPreview.uri, maxLines = 1, overflow = TextOverflow.Ellipsis, fontSize = 12.sp, color = HighOrLowlight)
Text(linkPreview.uri, maxLines = 1, overflow = TextOverflow.Ellipsis, fontSize = 12.sp, color = MaterialTheme.colors.secondary)
}
}
}

View File

@@ -1,6 +1,7 @@
package chat.simplex.app.views.helpers
import android.os.Build.VERSION.SDK_INT
import androidx.activity.compose.BackHandler
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricManager.Authenticators.*
import androidx.biometric.BiometricPrompt
@@ -53,6 +54,10 @@ fun authenticate(
LAMode.PASSCODE -> {
val password = ksAppPassword.get() ?: return completed(LAResult.Unavailable(generalGetString(R.string.la_no_app_password)))
ModalManager.shared.showCustomModal(animated = false) { close ->
BackHandler {
close()
completed(LAResult.Error(generalGetString(R.string.authentication_cancelled)))
}
Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
LocalAuthView(SimplexApp.context.chatModel, LocalAuthRequest(promptTitle, promptSubtitle, password) {
close()

View File

@@ -11,8 +11,8 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import chat.simplex.app.TAG
import chat.simplex.app.ui.theme.SettingsBackgroundLight
import chat.simplex.app.ui.theme.isInDarkTheme
import chat.simplex.app.ui.theme.themedBackground
import java.util.concurrent.atomic.AtomicBoolean
@Composable
@@ -25,7 +25,7 @@ fun ModalView(
) {
BackHandler(onBack = close)
Surface(Modifier.fillMaxSize()) {
Column(Modifier.background(background)) {
Column(if (background != MaterialTheme.colors.background) Modifier.background(background) else Modifier.themedBackground()) {
CloseSheetBar(close, endButtons)
Box(modifier) { content() }
}
@@ -40,13 +40,13 @@ class ModalManager {
fun showModal(settings: Boolean = false, endButtons: @Composable RowScope.() -> Unit = {}, content: @Composable () -> Unit) {
showCustomModal { close ->
ModalView(close, if (!settings || isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight, endButtons = endButtons, content = content)
ModalView(close, endButtons = endButtons, content = content)
}
}
fun showModalCloseable(settings: Boolean = false, content: @Composable (close: () -> Unit) -> Unit) {
showCustomModal { close ->
ModalView(close, if (!settings || isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight, content = { content(close) })
ModalView(close, content = { content(close) })
}
}

View File

@@ -8,8 +8,6 @@ import androidx.compose.foundation.text.*
import androidx.compose.material.*
import androidx.compose.material.TextFieldDefaults.indicatorLine
import androidx.compose.material.TextFieldDefaults.textFieldWithLabelPadding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.ExperimentalComposeUiApi
@@ -19,6 +17,7 @@ import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.*
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
@@ -99,7 +98,7 @@ fun SearchTextField(modifier: Modifier, placeholder: String, alwaysVisible: Bool
searchText = TextFieldValue("");
onValueChange("")
}) {
Icon(Icons.Default.Close, stringResource(R.string.icon_descr_close_button), tint = MaterialTheme.colors.primary,)
Icon(painterResource(R.drawable.ic_close), stringResource(R.string.icon_descr_close_button), tint = MaterialTheme.colors.primary,)
}
}} else null,
singleLine = true,

View File

@@ -3,42 +3,41 @@ import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Check
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.graphics.painter.Painter
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.*
import chat.simplex.app.R
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.ValueTitleDesc
import chat.simplex.app.views.helpers.ValueTitle
import chat.simplex.app.views.usersettings.SettingsActionItemWithContent
@Composable
fun SectionView(title: String? = null, padding: PaddingValues = PaddingValues(), content: (@Composable ColumnScope.() -> Unit)) {
Column {
if (title != null) {
Text(
title, color = HighOrLowlight, style = MaterialTheme.typography.body2,
title, color = MaterialTheme.colors.secondary, style = MaterialTheme.typography.body2,
modifier = Modifier.padding(start = DEFAULT_PADDING, bottom = 5.dp), fontSize = 12.sp
)
}
Surface(color = if (isInDarkTheme()) GroupDark else MaterialTheme.colors.background) {
Column(Modifier.padding(padding).fillMaxWidth()) { content() }
}
Column(Modifier.padding(padding).fillMaxWidth()) { content() }
}
}
@Composable
fun SectionView(
title: String,
icon: ImageVector,
iconTint: Color = HighOrLowlight,
icon: Painter,
iconTint: Color = MaterialTheme.colors.secondary,
leadingIcon: Boolean = false,
padding: PaddingValues = PaddingValues(),
content: (@Composable ColumnScope.() -> Unit)
@@ -46,13 +45,11 @@ fun SectionView(
Column {
val iconSize = with(LocalDensity.current) { 21.sp.toDp() }
Row(Modifier.padding(start = DEFAULT_PADDING, bottom = 5.dp), verticalAlignment = Alignment.CenterVertically) {
if (leadingIcon) Icon(icon, null, Modifier.padding(end = 4.dp).size(iconSize), tint = iconTint)
Text(title, color = HighOrLowlight, style = MaterialTheme.typography.body2, fontSize = 12.sp)
if (!leadingIcon) Icon(icon, null, Modifier.padding(start = 4.dp).size(iconSize), tint = iconTint)
}
Surface(color = if (isInDarkTheme()) GroupDark else MaterialTheme.colors.background) {
Column(Modifier.padding(padding).fillMaxWidth()) { content() }
if (leadingIcon) Icon(icon, null, Modifier.padding(end = DEFAULT_PADDING_HALF).size(iconSize), tint = iconTint)
Text(title, color = MaterialTheme.colors.secondary, style = MaterialTheme.typography.body2, fontSize = 12.sp)
if (!leadingIcon) Icon(icon, null, Modifier.padding(start = DEFAULT_PADDING_HALF).size(iconSize), tint = iconTint)
}
Column(Modifier.padding(padding).fillMaxWidth()) { content() }
}
}
@@ -70,7 +67,7 @@ fun <T> SectionViewSelectable(
SectionItemViewSpaceBetween({ onSelected(item.value) }) {
Text(item.title)
if (currentValue.value == item.value) {
Icon(Icons.Outlined.Check, item.title, tint = MaterialTheme.colors.primary)
Icon(painterResource(R.drawable.ic_check), item.title, tint = MaterialTheme.colors.primary)
}
}
Spacer(Modifier.padding(horizontal = 4.dp))
@@ -85,7 +82,30 @@ fun SectionItemView(
click: (() -> Unit)? = null,
minHeight: Dp = 46.dp,
disabled: Boolean = false,
padding: PaddingValues = PaddingValues(horizontal = DEFAULT_PADDING),
extraPadding: Boolean = false,
padding: PaddingValues = if (extraPadding)
PaddingValues(start = DEFAULT_PADDING * 1.7f, end = DEFAULT_PADDING)
else
PaddingValues(horizontal = DEFAULT_PADDING),
content: (@Composable RowScope.() -> Unit)
) {
val modifier = Modifier
.fillMaxWidth()
.sizeIn(minHeight = minHeight)
Row(
if (click == null || disabled) modifier.padding(padding) else modifier.clickable(onClick = click).padding(padding),
verticalAlignment = Alignment.CenterVertically
) {
content()
}
}
@Composable
fun SectionItemViewWithIcon(
click: (() -> Unit)? = null,
minHeight: Dp = 46.dp,
disabled: Boolean = false,
padding: PaddingValues = PaddingValues(start = DEFAULT_PADDING * 1.7f, end = DEFAULT_PADDING),
content: (@Composable RowScope.() -> Unit)
) {
val modifier = Modifier
@@ -126,24 +146,12 @@ fun <T> SectionItemWithValue(
currentValue: State<T>,
values: List<ValueTitle<T>>,
label: String? = null,
icon: ImageVector? = null,
iconTint: Color = HighOrLowlight,
icon: Painter? = null,
iconTint: Color = MaterialTheme.colors.secondary,
enabled: State<Boolean> = mutableStateOf(true),
onSelected: () -> Unit
) {
SectionItemView(click = if (enabled.value) onSelected else null) {
if (icon != null) {
Icon(
icon,
title,
Modifier.padding(end = 8.dp),
tint = iconTint
)
}
Text(title, color = if (enabled.value) Color.Unspecified else HighOrLowlight)
Spacer(Modifier.fillMaxWidth().weight(1f))
SettingsActionItemWithContent(icon = icon, text = title, iconColor = iconTint, click = if (enabled.value) onSelected else null, disabled = !enabled.value) {
Row(
Modifier.padding(start = 10.dp),
verticalAlignment = Alignment.CenterVertically,
@@ -153,7 +161,7 @@ fun <T> SectionItemWithValue(
values.first { it.value == currentValue.value }.title + (if (label != null) " $label" else ""),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = HighOrLowlight
color = MaterialTheme.colors.secondary
)
}
}
@@ -169,7 +177,7 @@ fun SectionTextFooter(text: AnnotatedString) {
Text(
text,
Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, top = DEFAULT_PADDING_HALF).fillMaxWidth(0.9F),
color = HighOrLowlight,
color = MaterialTheme.colors.secondary,
lineHeight = 18.sp,
fontSize = 14.sp
)
@@ -189,20 +197,41 @@ fun SectionDivider() {
Divider(Modifier.padding(horizontal = 8.dp))
}
@Composable
fun SectionDividerSpaced(maxTopPadding: Boolean = false, maxBottomPadding: Boolean = true) {
Divider(
Modifier.padding(
start = DEFAULT_PADDING_HALF,
top = if (maxTopPadding) 40.dp else 30.dp,
end = DEFAULT_PADDING_HALF,
bottom = if (maxBottomPadding) 40.dp else 30.dp)
)
}
@Composable
fun SectionSpacer() {
Spacer(Modifier.height(30.dp))
}
@Composable
fun InfoRow(title: String, value: String, icon: ImageVector? = null, iconTint: Color? = null) {
fun SectionBottomSpacer() {
Spacer(Modifier.height(DEFAULT_BOTTOM_PADDING))
}
@Composable
fun TextIconSpaced(extraPadding: Boolean = false) {
Spacer(Modifier.padding(horizontal = if (extraPadding) 17.dp else DEFAULT_PADDING_HALF))
}
@Composable
fun InfoRow(title: String, value: String, icon: Painter? = null, iconTint: Color? = null) {
SectionItemViewSpaceBetween {
Row {
val iconSize = with(LocalDensity.current) { 21.sp.toDp() }
if (icon != null) Icon(icon, title, Modifier.padding(end = 8.dp).size(iconSize), tint = iconTint ?: HighOrLowlight)
if (icon != null) Icon(icon, title, Modifier.padding(end = 8.dp).size(iconSize), tint = iconTint ?: MaterialTheme.colors.secondary)
Text(title)
}
Text(value, color = HighOrLowlight)
Text(value, color = MaterialTheme.colors.secondary)
}
}
@@ -218,7 +247,7 @@ fun InfoRowEllipsis(title: String, value: String, onClick: () -> Unit) {
.widthIn(max = (configuration.screenWidthDp / 2).dp),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = HighOrLowlight
color = MaterialTheme.colors.secondary
)
}
}

View File

@@ -1,24 +1,25 @@
package chat.simplex.app.ui.theme
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Share
import androidx.compose.ui.res.painterResource
import androidx.compose.runtime.Composable
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.graphics.painter.Painter
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
@Composable
fun SimpleButton(text: String, icon: ImageVector,
fun SimpleButton(text: String, icon: Painter,
color: Color = MaterialTheme.colors.primary,
click: () -> Unit) {
SimpleButtonFrame(click) {
@@ -31,7 +32,7 @@ fun SimpleButton(text: String, icon: ImageVector,
}
@Composable
fun SimpleButtonDecorated(text: String, icon: ImageVector,
fun SimpleButtonDecorated(text: String, icon: Painter,
color: Color = MaterialTheme.colors.primary,
textDecoration: TextDecoration = TextDecoration.Underline,
fontWeight: FontWeight = FontWeight.Normal,
@@ -47,24 +48,24 @@ fun SimpleButtonDecorated(text: String, icon: ImageVector,
@Composable
fun SimpleButton(
text: String, icon: ImageVector,
text: String, icon: Painter,
color: Color = MaterialTheme.colors.primary,
disabled: Boolean,
click: () -> Unit
) {
SimpleButtonFrame(click, disabled = disabled) {
Icon(
icon, text, tint = if (disabled) HighOrLowlight else color,
icon, text, tint = if (disabled) MaterialTheme.colors.secondary else color,
modifier = Modifier.padding(end = 8.dp)
)
Text(text, style = MaterialTheme.typography.caption, color = if (disabled) HighOrLowlight else color)
Text(text, style = MaterialTheme.typography.caption, color = if (disabled) MaterialTheme.colors.secondary else color)
}
}
@Composable
fun SimpleButtonIconEnded(
text: String,
icon: ImageVector,
icon: Painter,
color: Color = MaterialTheme.colors.primary,
click: () -> Unit
) {
@@ -79,7 +80,7 @@ fun SimpleButtonIconEnded(
@Composable
fun SimpleButtonFrame(click: () -> Unit, modifier: Modifier = Modifier, disabled: Boolean = false, content: @Composable () -> Unit) {
Surface(shape = RoundedCornerShape(20.dp)) {
Box(Modifier.clip(RoundedCornerShape(20.dp))) {
val modifier = if (disabled) modifier else modifier.clickable { click() }
Row(
verticalAlignment = Alignment.CenterVertically,
@@ -92,6 +93,6 @@ fun SimpleButtonFrame(click: () -> Unit, modifier: Modifier = Modifier, disabled
@Composable
fun PreviewCloseSheetBar() {
SimpleXTheme {
SimpleButton(text = "Share", icon = Icons.Outlined.Share, click = {})
SimpleButton(text = "Share", icon = painterResource(R.drawable.ic_share), click = {})
}
}

View File

@@ -18,7 +18,6 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.unit.*
import chat.simplex.app.ui.theme.DEFAULT_PADDING
import chat.simplex.app.ui.theme.HighOrLowlight
@Composable
fun TextEditor(
@@ -41,11 +40,11 @@ fun TextEditor(
autoCorrect = false
),
modifier = modifier,
cursorBrush = SolidColor(HighOrLowlight),
cursorBrush = SolidColor(MaterialTheme.colors.secondary),
decorationBox = { innerTextField ->
Surface(
shape = if (border) RoundedCornerShape(10.dp) else RectangleShape,
border = if (border) BorderStroke(1.dp, MaterialTheme.colors.secondary) else null
border = if (border) BorderStroke(1.dp, MaterialTheme.colors.secondaryVariant) else null
) {
Row(
Modifier.background(background),

View File

@@ -37,6 +37,9 @@ import androidx.core.text.HtmlCompat
import chat.simplex.app.*
import chat.simplex.app.R
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.ThemeData
import chat.simplex.app.ui.theme.ThemeOverrides
import com.charleskorn.kaml.decodeFromStream
import kotlinx.coroutines.*
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
@@ -388,6 +391,22 @@ fun getDrawableFromUri(uri: Uri, withAlertOnException: Boolean = true): Drawable
}
}
fun getThemeFromUri(uri: Uri, withAlertOnException: Boolean = true): ThemeData? {
SimplexApp.context.contentResolver.openInputStream(uri).use {
runCatching {
return yaml.decodeFromStream<ThemeData>(it!!)
}.onFailure {
if (withAlertOnException) {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.import_theme_error),
text = generalGetString(R.string.import_theme_error_desc),
)
}
}
}
return null
}
fun saveImage(context: Context, uri: Uri): String? {
val bitmap = getBitmapFromUri(uri) ?: return null
return saveImage(context, bitmap)
@@ -557,7 +576,7 @@ fun getBitmapFromVideo(uri: Uri, timestamp: Long? = null, random: Boolean = true
val image = when {
timestamp != null -> mmr.getFrameAtTime(timestamp * 1000, MediaMetadataRetriever.OPTION_CLOSEST)
random -> mmr.frameAtTime
else -> mmr.getFrameAtIndex(0)
else -> mmr.getFrameAtTime(0)
}
mmr.release()
return VideoPlayer.PreviewAndDuration(image, durationMs, timestamp ?: 0)
@@ -566,6 +585,9 @@ fun getBitmapFromVideo(uri: Uri, timestamp: Long? = null, random: Boolean = true
fun Color.darker(factor: Float = 0.1f): Color =
Color(max(red * (1 - factor), 0f), max(green * (1 - factor), 0f), max(blue * (1 - factor), 0f), alpha)
fun Color.lighter(factor: Float = 0.1f): Color =
Color(min(red * (1 + factor), 1f), min(green * (1 + factor), 1f), min(blue * (1 + factor), 1f), alpha)
fun ByteArray.toBase64String() = Base64.encodeToString(this, Base64.DEFAULT)
fun String.toByteArrayFromBase64() = Base64.decode(this, Base64.DEFAULT)

View File

@@ -86,7 +86,7 @@ class VideoPlayer private constructor(
.build()
.apply {
// Repeat the same track endlessly
repeatMode = 1
repeatMode = Player.REPEAT_MODE_ONE
currentVolume = volume
if (!soundEnabled) {
volume = 0f

View File

@@ -4,13 +4,11 @@ import android.content.res.Configuration
import androidx.compose.foundation.layout.*
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Done
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.ui.theme.DEFAULT_PADDING
@@ -41,9 +39,9 @@ fun PasscodeView(
}
PasscodeEntry(passcode, true)
Row {
SimpleButton(generalGetString(R.string.cancel_verb), icon = Icons.Default.Close, click = cancel)
SimpleButton(generalGetString(R.string.cancel_verb), icon = painterResource(R.drawable.ic_close), click = cancel)
Spacer(Modifier.size(20.dp))
SimpleButton(submitLabel, icon = Icons.Default.Done, disabled = submitEnabled?.invoke(passcode.value) == false || passcode.value.length < 4, click = submit)
SimpleButton(submitLabel, icon = painterResource(R.drawable.ic_done_filled), disabled = submitEnabled?.invoke(passcode.value) == false || passcode.value.length < 4, click = submit)
}
}
}
@@ -84,8 +82,8 @@ fun PasscodeView(
Modifier.padding(start = 30.dp).height(s * 3),
verticalArrangement = Arrangement.SpaceEvenly
) {
SimpleButton(generalGetString(R.string.cancel_verb), icon = Icons.Default.Close, click = cancel)
SimpleButton(submitLabel, icon = Icons.Default.Done, disabled = submitEnabled?.invoke(passcode.value) == false || passcode.value.length < 4, click = submit)
SimpleButton(generalGetString(R.string.cancel_verb), icon = painterResource(R.drawable.ic_close), click = cancel)
SimpleButton(submitLabel, icon = painterResource(R.drawable.ic_done_filled), disabled = submitEnabled?.invoke(passcode.value) == false || passcode.value.length < 4, click = submit)
}
}
}

View File

@@ -5,21 +5,18 @@ 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.Close
import androidx.compose.material.icons.outlined.Backspace
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.painterResource
import chat.simplex.app.R
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.LocalDensity
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.*
import chat.simplex.app.model.ChatModel
import chat.simplex.app.ui.theme.HighOrLowlight
@Composable
fun PasscodeEntry(
@@ -59,13 +56,13 @@ private fun BoxWithConstraintsScope.VerticalPasswordGrid(password: MutableState<
DigitsRow(s, 7, 8, 9, password)
Divider()
Row(Modifier.requiredHeight(s)) {
PasswordEdit(s, Icons.Default.Close) {
PasswordEdit(s, painterResource(R.drawable.ic_close)) {
password.value = ""
}
VerticalDivider()
PasswordDigit(s, 0, password)
VerticalDivider()
PasswordEdit(s, Icons.Outlined.Backspace) {
PasswordEdit(s, painterResource(R.drawable.ic_backspace)) {
password.value = password.value.dropLast(1)
}
}
@@ -79,7 +76,7 @@ private fun BoxWithConstraintsScope.HorizontalPasswordGrid(password: MutableStat
Row(Modifier.height(IntrinsicSize.Min)) {
DigitsRow(s, 1, 2, 3, password);
VerticalDivider()
PasswordEdit(s, Icons.Default.Close) {
PasswordEdit(s, painterResource(R.drawable.ic_close)) {
password.value = ""
}
}
@@ -93,7 +90,7 @@ private fun BoxWithConstraintsScope.HorizontalPasswordGrid(password: MutableStat
Row(Modifier.height(IntrinsicSize.Min)) {
DigitsRow(s, 7, 8, 9, password)
VerticalDivider()
PasswordEdit(s, Icons.Outlined.Backspace) {
PasswordEdit(s, painterResource(R.drawable.ic_backspace)) {
password.value = password.value.dropLast(1)
}
}
@@ -131,15 +128,15 @@ private fun PasswordDigit(size: Dp, d: Int, password: MutableState<String>) {
fontSize = 30.sp,
letterSpacing = (-0.5).sp
),
color = HighOrLowlight
color = MaterialTheme.colors.secondary
)
}
}
@Composable
private fun PasswordEdit(size: Dp, image: ImageVector, action: () -> Unit) {
private fun PasswordEdit(size: Dp, image: Painter, action: () -> Unit) {
PasswordButton(size, action) {
Icon(image, null, tint = HighOrLowlight)
Icon(image, null, tint = MaterialTheme.colors.secondary)
}
}

View File

@@ -1,5 +1,6 @@
package chat.simplex.app.views.localauth
import androidx.activity.compose.BackHandler
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import chat.simplex.app.R
@@ -18,6 +19,10 @@ fun SetAppPasscodeView(
@Composable
fun SetPasswordView(title: String, submitLabel: String, submitEnabled: (((String) -> Boolean))? = null, submit: () -> Unit) {
BackHandler {
close()
cancel()
}
PasscodeView(passcode, title = title, submitLabel = submitLabel, submitEnabled = submitEnabled, submit = submit) {
close()
cancel()

View File

@@ -1,14 +1,12 @@
package chat.simplex.app.views.newchat
import SectionBottomSpacer
import android.content.res.Configuration
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.TheaterComedy
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material.icons.outlined.Share
import androidx.compose.ui.res.painterResource
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -19,7 +17,6 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.R
import chat.simplex.app.model.ChatModel
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.*
@@ -39,8 +36,7 @@ fun AddContactLayout(connReq: String, connIncognito: Boolean, share: () -> Unit)
val screenHeight = maxHeight
Column(
Modifier
.verticalScroll(rememberScrollState())
.padding(bottom = 16.dp),
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.SpaceBetween,
) {
AppBarTitle(stringResource(R.string.add_contact), false)
@@ -67,7 +63,7 @@ fun AddContactLayout(connReq: String, connIncognito: Boolean, share: () -> Unit)
.size(36.dp)
.padding(4.dp)
.align(Alignment.CenterHorizontally),
color = HighOrLowlight,
color = MaterialTheme.colors.secondary,
strokeWidth = 3.dp
)
}
@@ -75,14 +71,15 @@ fun AddContactLayout(connReq: String, connIncognito: Boolean, share: () -> Unit)
annotatedStringResource(R.string.if_you_cannot_meet_in_person_show_QR_in_video_call_or_via_another_channel),
lineHeight = 22.sp,
modifier = Modifier
.padding(top = 16.dp, bottom = if (screenHeight > 600.dp) 16.dp else 0.dp)
.padding(top = DEFAULT_PADDING, bottom = if (screenHeight > 600.dp) DEFAULT_PADDING else 0.dp)
)
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
SimpleButton(stringResource(R.string.share_invitation_link), icon = Icons.Outlined.Share, click = share)
SimpleButton(stringResource(R.string.share_invitation_link), icon = painterResource(R.drawable.ic_share), click = share)
}
SectionBottomSpacer()
}
}
}
@@ -98,7 +95,7 @@ fun InfoAboutIncognito(chatModelIncognito: Boolean, supportedIncognito: Boolean
horizontalArrangement = if (centered) Arrangement.Center else Arrangement.Start
) {
Icon(
if (supportedIncognito) Icons.Filled.TheaterComedy else Icons.Outlined.Info,
if (supportedIncognito) painterResource(R.drawable.ic_theater_comedy_filled) else painterResource(R.drawable.ic_info),
stringResource(R.string.incognito),
tint = if (supportedIncognito) Indigo else WarningOrange,
modifier = Modifier.padding(end = 10.dp).size(20.dp)
@@ -114,9 +111,9 @@ fun InfoAboutIncognito(chatModelIncognito: Boolean, supportedIncognito: Boolean
horizontalArrangement = if (centered) Arrangement.Center else Arrangement.Start
) {
Icon(
Icons.Outlined.Info,
painterResource(R.drawable.ic_info),
stringResource(R.string.incognito),
tint = HighOrLowlight,
tint = MaterialTheme.colors.secondary,
modifier = Modifier.padding(end = 10.dp).size(20.dp)
)
Text(offText, textAlign = if (centered) TextAlign.Center else TextAlign.Left, style = MaterialTheme.typography.body2)

View File

@@ -5,14 +5,13 @@ import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowForwardIos
import androidx.compose.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.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
@@ -89,7 +88,7 @@ fun AddGroupLayout(createGroup: (GroupProfile) -> Unit, close: () -> Unit) {
.verticalScroll(rememberScrollState())
.padding(horizontal = DEFAULT_PADDING)
) {
AppBarTitleCentered(stringResource(R.string.create_secret_group_title))
AppBarTitle(stringResource(R.string.create_secret_group_title))
ReadableText(R.string.group_is_decentralized, TextAlign.Center)
Box(
Modifier
@@ -142,7 +141,7 @@ fun AddGroupLayout(createGroup: (GroupProfile) -> Unit, close: () -> Unit) {
}
.padding(8.dp))
} else {
CreateGroupButton(HighOrLowlight, Modifier.padding(8.dp))
CreateGroupButton(MaterialTheme.colors.secondary, Modifier.padding(8.dp))
}
LaunchedEffect(Unit) {
delay(300)
@@ -163,7 +162,7 @@ fun CreateGroupButton(color: Color, modifier: Modifier) {
Surface(shape = RoundedCornerShape(20.dp)) {
Row(modifier, verticalAlignment = Alignment.CenterVertically) {
Text(stringResource(R.string.create_profile_button), style = MaterialTheme.typography.caption, color = color, fontWeight = FontWeight.Bold)
Icon(Icons.Outlined.ArrowForwardIos, stringResource(R.string.create_profile_button), tint = color)
Icon(painterResource(R.drawable.ic_arrow_forward_ios), stringResource(R.string.create_profile_button), tint = color)
}
}
}

View File

@@ -2,15 +2,14 @@ package chat.simplex.app.views.newchat
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.sp
import chat.simplex.app.R
import chat.simplex.app.model.ChatModel
import chat.simplex.app.ui.theme.HighOrLowlight
enum class ConnectViaLinkTab {
SCAN, PASTE
@@ -45,7 +44,7 @@ fun ConnectViaLinkView(m: ChatModel, close: () -> Unit) {
}
TabRow(
selectedTabIndex = selection.value.ordinal,
backgroundColor = MaterialTheme.colors.background,
backgroundColor = Color.Transparent,
contentColor = MaterialTheme.colors.primary,
) {
tabTitles.forEachIndexed { index, it ->
@@ -58,12 +57,12 @@ fun ConnectViaLinkView(m: ChatModel, close: () -> Unit) {
text = { Text(it, fontSize = 13.sp) },
icon = {
Icon(
if (ConnectViaLinkTab.SCAN.ordinal == index) Icons.Outlined.QrCode else Icons.Outlined.Article,
if (ConnectViaLinkTab.SCAN.ordinal == index) painterResource(R.drawable.ic_qr_code) else painterResource(R.drawable.ic_article),
it
)
},
selectedContentColor = MaterialTheme.colors.primary,
unselectedContentColor = HighOrLowlight,
unselectedContentColor = MaterialTheme.colors.secondary,
)
}
}

View File

@@ -1,17 +1,16 @@
package chat.simplex.app.views.newchat
import SectionDivider
import SectionBottomSpacer
import SectionView
import android.content.res.Configuration
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import chat.simplex.app.R
@@ -101,17 +100,17 @@ private fun ContactConnectionInfoLayout(
SectionView {
if (!connReq.isNullOrEmpty() && contactConnection.initiated) {
ShowQrButton(contactConnection.incognito, showQr)
SectionDivider()
}
DeleteButton(deleteConnection)
}
SectionBottomSpacer()
}
}
@Composable
fun ShowQrButton(incognito: Boolean, onClick: () -> Unit) {
SettingsActionItem(
Icons.Outlined.QrCode,
painterResource(R.drawable.ic_qr_code),
stringResource(R.string.show_QR_code),
click = onClick,
textColor = if (incognito) Indigo else MaterialTheme.colors.primary,
@@ -122,7 +121,7 @@ fun ShowQrButton(incognito: Boolean, onClick: () -> Unit) {
@Composable
fun DeleteButton(onClick: () -> Unit) {
SettingsActionItem(
Icons.Outlined.Delete,
painterResource(R.drawable.ic_delete),
stringResource(R.string.delete_verb),
click = onClick,
textColor = Color.Red,

View File

@@ -2,17 +2,16 @@ package chat.simplex.app.views.newchat
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.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.sp
import chat.simplex.app.R
import chat.simplex.app.model.ChatModel
import chat.simplex.app.ui.theme.DEFAULT_PADDING
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.views.helpers.ModalManager
import chat.simplex.app.views.helpers.withApi
import chat.simplex.app.views.usersettings.UserAddressView
@@ -68,7 +67,7 @@ fun CreateLinkView(m: ChatModel, initialSelection: CreateLinkTab) {
}
TabRow(
selectedTabIndex = selection.value.ordinal,
backgroundColor = MaterialTheme.colors.background,
backgroundColor = Color.Transparent,
contentColor = MaterialTheme.colors.primary,
) {
tabTitles.forEachIndexed { index, it ->
@@ -80,12 +79,12 @@ fun CreateLinkView(m: ChatModel, initialSelection: CreateLinkTab) {
text = { Text(it, fontSize = 13.sp) },
icon = {
Icon(
if (CreateLinkTab.ONE_TIME.ordinal == index) Icons.Outlined.RepeatOne else Icons.Outlined.AllInclusive,
if (CreateLinkTab.ONE_TIME.ordinal == index) painterResource(R.drawable.ic_repeat_one) else painterResource(R.drawable.ic_all_inclusive),
it
)
},
selectedContentColor = MaterialTheme.colors.primary,
unselectedContentColor = HighOrLowlight,
unselectedContentColor = MaterialTheme.colors.secondary,
)
}
}

View File

@@ -9,17 +9,14 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
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.drawBehind
import androidx.compose.ui.graphics.*
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.*
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
@@ -59,7 +56,7 @@ fun NewChatSheet(chatModel: ChatModel, newChatSheetState: StateFlow<AnimatedView
}
private val titles = listOf(R.string.share_one_time_link, R.string.connect_via_link_or_qr, R.string.create_group)
private val icons = listOf(Icons.Outlined.AddLink, Icons.Outlined.QrCode, Icons.Outlined.Group)
private val icons = listOf(R.drawable.ic_add_link, R.drawable.ic_qr_code, R.drawable.ic_group)
@Composable
private fun NewChatSheetLayout(
@@ -132,7 +129,7 @@ private fun NewChatSheetLayout(
fontWeight = FontWeight.Medium,
)
Icon(
icons[index],
painterResource(icons[index]),
stringResource(titles[index]),
Modifier.size(42.dp),
tint = if (isInDarkTheme()) MaterialTheme.colors.primary else MaterialTheme.colors.primary
@@ -146,22 +143,22 @@ private fun NewChatSheetLayout(
}
FloatingActionButton(
onClick = { if (!stopped) closeNewChatSheet(true) },
Modifier.padding(end = 16.dp, bottom = 16.dp),
Modifier.padding(end = DEFAULT_PADDING, bottom = DEFAULT_PADDING),
elevation = FloatingActionButtonDefaults.elevation(
defaultElevation = 0.dp,
pressedElevation = 0.dp,
hoveredElevation = 0.dp,
focusedElevation = 0.dp,
),
backgroundColor = if (!stopped) MaterialTheme.colors.primary else HighOrLowlight,
backgroundColor = if (!stopped) MaterialTheme.colors.primary else MaterialTheme.colors.secondary,
contentColor = Color.White
) {
Icon(
Icons.Default.Edit, stringResource(R.string.add_contact_or_create_group),
painterResource(R.drawable.ic_edit_filled), stringResource(R.string.add_contact_or_create_group),
Modifier.graphicsLayer { alpha = 1 - animatedFloat.value }
)
Icon(
Icons.Default.Close, stringResource(R.string.add_contact_or_create_group),
painterResource(R.drawable.ic_close), stringResource(R.string.add_contact_or_create_group),
Modifier.graphicsLayer { alpha = animatedFloat.value }
)
}
@@ -172,7 +169,7 @@ private fun NewChatSheetLayout(
fun ActionButton(
text: String?,
comment: String?,
icon: ImageVector,
icon: Painter,
disabled: Boolean = false,
click: () -> Unit = {}
) {
@@ -183,7 +180,53 @@ fun ActionButton(
.padding(8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
val tint = if (disabled) HighOrLowlight else MaterialTheme.colors.primary
val tint = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary
Icon(
icon, text,
tint = tint,
modifier = Modifier
.size(40.dp)
.padding(bottom = 8.dp)
)
if (text != null) {
Text(
text,
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
color = tint,
modifier = Modifier.padding(bottom = 4.dp)
)
}
if (comment != null) {
Text(
comment,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.body2
)
}
}
}
}
@Composable
fun ActionButton(
modifier: Modifier,
text: String?,
comment: String?,
icon: Painter,
tint: Color = MaterialTheme.colors.primary,
disabled: Boolean = false,
click: () -> Unit = {}
) {
Surface(modifier, shape = RoundedCornerShape(18.dp)) {
Column(
Modifier
.fillMaxWidth()
.clickable(onClick = click)
.padding(8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
val tint = if (disabled) MaterialTheme.colors.secondary else tint
Icon(
icon, text,
tint = tint,

View File

@@ -1,5 +1,6 @@
package chat.simplex.app.views.newchat
import SectionBottomSpacer
import android.content.ClipboardManager
import android.content.res.Configuration
import android.net.Uri
@@ -8,11 +9,10 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@@ -45,7 +45,7 @@ fun PasteToConnectView(chatModel: ChatModel, close: () -> Unit) {
}
}
if (linkType == ConnectionLinkType.GROUP) {
AlertManager.shared.showAlertMsg(
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.connect_via_group_link),
text = generalGetString(R.string.you_will_join_group),
confirmText = generalGetString(R.string.connect_via_link_verb),
@@ -84,7 +84,7 @@ fun PasteToConnectLayout(
generalGetString(R.string.profile_will_be_sent_to_contact_sending_link)
)
Box(Modifier.padding(top = 16.dp, bottom = 6.dp)) {
Box(Modifier.padding(top = DEFAULT_PADDING, bottom = 6.dp)) {
TextEditor(Modifier.height(180.dp), text = connectionLink)
}
@@ -93,21 +93,22 @@ fun PasteToConnectLayout(
horizontalArrangement = Arrangement.Start,
) {
if (connectionLink.value == "") {
SimpleButton(text = stringResource(R.string.paste_button), icon = Icons.Outlined.ContentPaste) {
SimpleButton(text = stringResource(R.string.paste_button), icon = painterResource(R.drawable.ic_content_paste)) {
pasteFromClipboard()
}
} else {
SimpleButton(text = stringResource(R.string.clear_verb), icon = Icons.Outlined.Clear) {
SimpleButton(text = stringResource(R.string.clear_verb), icon = painterResource(R.drawable.ic_close)) {
connectionLink.value = ""
}
}
Spacer(Modifier.weight(1f).fillMaxWidth())
SimpleButton(text = stringResource(R.string.connect_button), icon = Icons.Outlined.Link) {
SimpleButton(text = stringResource(R.string.connect_button), icon = painterResource(R.drawable.ic_link)) {
connectViaLink(connectionLink.value)
}
}
Text(annotatedStringResource(R.string.you_can_also_connect_by_clicking_the_link))
SectionBottomSpacer()
}
}

View File

@@ -1,5 +1,6 @@
package chat.simplex.app.views.newchat
import SectionBottomSpacer
import android.Manifest
import android.content.res.Configuration
import android.net.Uri
@@ -47,7 +48,7 @@ fun ScanToConnectView(chatModel: ChatModel, close: () -> Unit) {
}
}
if (linkType == ConnectionLinkType.GROUP) {
AlertManager.shared.showAlertMsg(
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.connect_via_group_link),
text = generalGetString(R.string.you_will_join_group),
confirmText = generalGetString(R.string.connect_via_link_verb),
@@ -141,6 +142,7 @@ fun ConnectContactLayout(chatModelIncognito: Boolean, qrCodeScanner: @Composable
annotatedStringResource(R.string.if_you_cannot_meet_in_person_scan_QR_in_video_call_or_ask_for_invitation_link),
lineHeight = 22.sp
)
SectionBottomSpacer()
}
}

View File

@@ -26,7 +26,6 @@ fun HowItWorks(user: User?, onboardingStage: MutableState<OnboardingStage?>? = n
Column(Modifier
.fillMaxWidth()
.padding(horizontal = DEFAULT_PADDING),
horizontalAlignment = Alignment.Start
) {
AppBarTitle(stringResource(R.string.how_simplex_works), false)
ReadableText(R.string.many_people_asked_how_can_it_deliver)
@@ -47,7 +46,7 @@ fun HowItWorks(user: User?, onboardingStage: MutableState<OnboardingStage?>? = n
Spacer(Modifier.fillMaxHeight().weight(1f))
if (onboardingStage != null) {
Box(Modifier.fillMaxWidth().padding(bottom = 16.dp), contentAlignment = Alignment.Center) {
Box(Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING), contentAlignment = Alignment.Center) {
OnboardingActionButton(user, onboardingStage, onclick = { ModalManager.shared.closeModal() })
}
Spacer(Modifier.fillMaxHeight().weight(1f))

View File

@@ -10,10 +10,8 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.R
@@ -26,10 +24,13 @@ import chat.simplex.app.views.usersettings.changeNotificationsMode
@Composable
fun SetNotificationsMode(m: ChatModel) {
Column(
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(vertical = 14.dp)
) {
//CloseSheetBar(null)
AppBarTitleCentered(stringResource(R.string.onboarding_notifications_mode_title))
AppBarTitle(stringResource(R.string.onboarding_notifications_mode_title))
val currentMode = rememberSaveable { mutableStateOf(NotificationsMode.default) }
Column(Modifier.padding(horizontal = DEFAULT_PADDING * 1f)) {
Text(stringResource(R.string.onboarding_notifications_mode_subtitle), Modifier.fillMaxWidth(), textAlign = TextAlign.Center)
@@ -39,7 +40,7 @@ fun SetNotificationsMode(m: ChatModel) {
NotificationButton(currentMode, NotificationsMode.SERVICE, R.string.onboarding_notifications_mode_service, R.string.onboarding_notifications_mode_service_desc)
}
Spacer(Modifier.fillMaxHeight().weight(1f))
Box(Modifier.fillMaxWidth().padding(bottom = 16.dp), contentAlignment = Alignment.Center) {
Box(Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING), contentAlignment = Alignment.Center) {
OnboardingActionButton(R.string.use_chat, OnboardingStage.OnboardingComplete, m.onboardingStage, false) {
changeNotificationsMode(currentMode.value, m)
}
@@ -52,16 +53,16 @@ fun SetNotificationsMode(m: ChatModel) {
private fun NotificationButton(currentMode: MutableState<NotificationsMode>, mode: NotificationsMode, @StringRes title: Int, @StringRes description: Int) {
TextButton(
onClick = { currentMode.value = mode },
border = BorderStroke(1.dp, color = if (currentMode.value == mode) MaterialTheme.colors.primary else HighOrLowlight.copy(alpha = 0.5f)),
border = BorderStroke(1.dp, color = if (currentMode.value == mode) MaterialTheme.colors.primary else MaterialTheme.colors.secondary.copy(alpha = 0.5f)),
shape = RoundedCornerShape(35.dp),
) {
Column(Modifier.padding(14.dp)) {
Column(Modifier.padding(horizontal = 14.dp).padding(top = 4.dp, bottom = 8.dp)) {
Text(
stringResource(title),
style = MaterialTheme.typography.h2,
fontWeight = FontWeight.Medium,
color = if (currentMode.value == mode) MaterialTheme.colors.primary else HighOrLowlight,
modifier = Modifier.padding(bottom = 14.dp).align(Alignment.CenterHorizontally),
color = if (currentMode.value == mode) MaterialTheme.colors.primary else MaterialTheme.colors.secondary,
modifier = Modifier.padding(bottom = 8.dp).align(Alignment.CenterHorizontally),
textAlign = TextAlign.Center
)
Text(annotatedStringResource(description),

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