Compare commits

...

48 Commits
v1 ... v1.37.0

Author SHA1 Message Date
Mattermost Build
aebff0f2af Bump app build number to 334 (#4964) (#4965)
(cherry picked from commit 83a157421c)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2020-11-12 22:01:49 -03:00
Mattermost Build
6384f38ea3 MM-29965 Fix at_mention autocomplete selection bug (#4937) (#4963)
* Adding E2E tests for at mention autocomplete

* Removing mentionComplete from at_mention autocomplete

- The component is already being hidden when there is no valid matchTerm or sections
- Setting mentionComplete to true after an autocomplete was selected prevented the autocomplete from rendering when @ is tapped a second time

* Updating E2E description

* Update detox/e2e/test/autocomplete/at_mention.e2e.js

Co-authored-by: Joseph Baylon <joseph.baylon@mattermost.com>

* complete at-mention selection when there are no more sections

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
Co-authored-by: Joseph Baylon <joseph.baylon@mattermost.com>
(cherry picked from commit e319a79e42)

Co-authored-by: Andre Vasconcelos <andre.onogoro@gmail.com>
2020-11-12 21:12:28 -03:00
Mattermost Build
a9497c3e1d Fix the Post List gap visible on iOS 14.2 (#4960) (#4962)
(cherry picked from commit c386e69cd8)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2020-11-12 20:20:59 -03:00
Weblate (bot)
8caa48f1f7 Translations update from Weblate (#4956)
* Translated using Weblate (Spanish)

Currently translated at 100.0% (656 of 656 strings)

Co-authored-by: Elias  Nahum <elias@mattermost.com>
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_release-1-37/es/
Translation: mattermost-languages-shipped/mattermost-mobile_release-1.37

* Translated using Weblate (Turkish)

Currently translated at 100.0% (656 of 656 strings)

Co-authored-by: Kaya Zeren <kayazeren@gmail.com>
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_release-1-37/tr/
Translation: mattermost-languages-shipped/mattermost-mobile_release-1.37

* Translated using Weblate (Dutch)

Currently translated at 100.0% (656 of 656 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (656 of 656 strings)

Co-authored-by: Tom De Moor <tom@controlaltdieliet.be>
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_release-1-37/nl/
Translation: mattermost-languages-shipped/mattermost-mobile_release-1.37

* Translated using Weblate (Japanese)

Currently translated at 100.0% (656 of 656 strings)

Co-authored-by: kaakaa <stooner.hoe@gmail.com>
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_release-1-37/ja/
Translation: mattermost-languages-shipped/mattermost-mobile_release-1.37

Co-authored-by: Elias  Nahum <elias@mattermost.com>
Co-authored-by: Kaya Zeren <kayazeren@gmail.com>
Co-authored-by: Tom De Moor <tom@controlaltdieliet.be>
Co-authored-by: kaakaa <stooner.hoe@gmail.com>
2020-11-10 09:10:30 +01:00
Mattermost Build
0af61964e8 Bump app build number to 333 (#4952) (#4953)
(cherry picked from commit c70381baf2)

Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com>
2020-11-05 12:47:29 -07:00
Mattermost Build
ebe57e9950 Execute completion handler on main queue (#4947) (#4948)
(cherry picked from commit 8d4b602ab8)

Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com>
2020-11-04 08:53:31 -03:00
Mattermost Build
a0eb5192ce MM-30101 Match channel specific global notification preference (#4935) (#4946)
* MM-30101 Match channel specific global notification preference

* Avoid crash if globalNotifyProps is undefined

(cherry picked from commit e57bcfdaba)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2020-11-03 16:17:25 -03:00
Weblate (bot)
46a4c3ebef Translations update from Weblate (#4942)
* Translated using Weblate (Dutch)

Currently translated at 100.0% (656 of 656 strings)

Co-authored-by: Tom De Moor <tom@controlaltdieliet.be>
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_release-1-37/nl/
Translation: mattermost-languages-shipped/mattermost-mobile_release-1.37

* Translated using Weblate (Turkish)

Currently translated at 100.0% (656 of 656 strings)

Co-authored-by: Kaya Zeren <kayazeren@gmail.com>
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_release-1-37/tr/
Translation: mattermost-languages-shipped/mattermost-mobile_release-1.37

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (656 of 656 strings)

Co-authored-by: aeomin <lin@aeomin.net>
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_release-1-37/zh_Hans/
Translation: mattermost-languages-shipped/mattermost-mobile_release-1.37

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_release-1-37/
Translation: mattermost-languages-shipped/mattermost-mobile_release-1.37

* Translated using Weblate (Spanish)

Currently translated at 100.0% (655 of 655 strings)

Translation: mattermost-languages-shipped/mattermost-mobile_release-1.37
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_release-1-37/es/

Co-authored-by: Tom De Moor <tom@controlaltdieliet.be>
Co-authored-by: Kaya Zeren <kayazeren@gmail.com>
Co-authored-by: aeomin <lin@aeomin.net>
Co-authored-by: Elias  Nahum <elias@mattermost.com>
2020-11-02 21:31:20 +01:00
Mattermost Build
a239773ffb MM-30185 Fix bad permalink reference for saved posts screen (#4938) (#4940)
(cherry picked from commit 91ff9b7480)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2020-11-02 13:56:29 -03:00
Mattermost Build
94d5501d05 Bump app build number to 332 (#4933) (#4934)
(cherry picked from commit 4e53683a3a)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2020-10-29 09:28:08 -03:00
Mattermost Build
dd30a159d1 MM-29869 Replace radio button Material with Compass icons (#4928) (#4932)
(cherry picked from commit 9940aacad1)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2020-10-29 08:15:33 -03:00
Mattermost Build
f014c8557f MM-29948 Display error msg when pasting files and uploads are disabled (#4927) (#4930)
* MM-29948 Display error msg when pasting files and uploads are disabled

* Remove unnecessary ref and Animated.View

Co-authored-by: Miguel Alatzar <this.migbot@gmail.com>
(cherry picked from commit f73db6e51c)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2020-10-28 13:23:40 -03:00
Mattermost Build
c08d97f67c MM-29252 Tapping on invalid permalink shows an error (#4926) (#4929)
(cherry picked from commit 2c0b67067f)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2020-10-28 12:05:06 -03:00
Mattermost Build
40a0e40225 MM-29620 Add props to post input to prevent autofill (#4916) (#4921)
* MM-29620 Add props to post input to prevent autofill

* Set autoCompleteType to off instead of none

(cherry picked from commit cb4d875b13)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2020-10-23 15:22:33 -07:00
Mattermost Build
d19e56a457 MM-28968 Fix redux-persist serializer (#4918) (#4920)
(cherry picked from commit 79d2d7b31d)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2020-10-23 15:21:33 -07:00
Elias Nahum
0569479398 MM-28105 do not crash when in-app notification shows (#4915)
* MM-28105 do not crash when in-app notification shows

* Use index.tsx to register the notification screen

* Fix notification e2e and some import cleanup

* Update iOS notification json

* e2e: Show the add reaction screen before receiving a notification

* Update snapshots

* Patch react-native-notifications to prevent iOS crash when opening

* Apply TM4J id

Co-authored-by: Joseph Baylon <joseph.baylon@mattermost.com>
2020-10-22 14:37:14 -03:00
Elisabeth Kulzer
8dac3ce9b9 Translations update from Weblate (#4899) (#4914)
* Translated using Weblate (Korean)

Currently translated at 79.6% (522 of 655 strings)

Co-authored-by: retheviper <youngbina@hotmail.com>
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_release-1-36/ko/
Translation: mattermost-languages-shipped/mattermost-mobile_release-1.36

* Translated using Weblate (Korean)

Currently translated at 79.6% (522 of 655 strings)

Co-authored-by: Ji-Hyeon Gim <potatogim@potatogim.net>
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_release-1-36/ko/
Translation: mattermost-languages-shipped/mattermost-mobile_release-1.36

Co-authored-by: retheviper <youngbina@hotmail.com>
Co-authored-by: Ji-Hyeon Gim <potatogim@potatogim.net>

Co-authored-by: Weblate (bot) <hosted@weblate.org>
Co-authored-by: retheviper <youngbina@hotmail.com>
Co-authored-by: Ji-Hyeon Gim <potatogim@potatogim.net>
2020-10-21 16:04:29 +02:00
Mattermost Build
e5ca1c17db Upgrade rudderstack attempt 2 (#4912) (#4913)
(cherry picked from commit ccf286f324)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2020-10-21 08:26:59 -03:00
Mattermost Build
b5431a293f Update rudderStack (#4909) (#4910)
(cherry picked from commit 30f7dce631)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2020-10-20 10:27:52 -03:00
Mattermost Build
ba1ba47265 Bump Version to 1.37.0 and Build to 331 (#4907) (#4908)
* Bump app build number to 331

* Bump app version number to 1.37.0

* Update fastlane to 2.164.0

(cherry picked from commit 80c655b8a9)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2020-10-20 09:38:41 -03:00
Elias Nahum
5979cf6b43 MM-28688 Close sidebar when current channel is selected (#4897) (#4906)
* MM-28688 Close sidebar when current channel is selected

* Update snapshot

* Apply suggestions from code review

Co-authored-by: Joseph Baylon <joseph.baylon@mattermost.com>

* Fix iOS select_channel e2e and feedback review

Co-authored-by: Joseph Baylon <joseph.baylon@mattermost.com>

Co-authored-by: Joseph Baylon <joseph.baylon@mattermost.com>
2020-10-20 08:58:35 -03:00
Mattermost Build
3316c4a5ec MM-28336 Align hamburger menu badges (#4894) (#4905)
* MM-28336 Align hamburger menu badges

* Fix Fastlane and CI artifacts for PR's

* Fastlane to post to QA build channel

* Fix plist.erb template

* Fix variable type

(cherry picked from commit aeb61cb162)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2020-10-19 22:08:49 -03:00
Mattermost Build
0b21e31f01 Refactor Android Share extension (js) (#4893) (#4904)
* Refactor Android Share extension (js)

* Feedback review

(cherry picked from commit 043b3a0e8e)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2020-10-19 21:48:21 -03:00
Elias Nahum
abab4855d2 MM-28539 Fix wrong participant count in GM info (#4887) (#4903)
* Fix e2e flaky tests

* MM-28539 Fix wrong participant count in GM info

* Apply suggestions from code review

Co-authored-by: Joseph Baylon <joseph.baylon@mattermost.com>

* Apply suggestions from code review

Co-authored-by: Joseph Baylon <joseph.baylon@mattermost.com>
Co-authored-by: Saturnino Abril <saturnino.abril@gmail.com>

* Update detox/e2e/test/autocomplete/edit_post.e2e.js

Co-authored-by: Joseph Baylon <joseph.baylon@mattermost.com>

Co-authored-by: Joseph Baylon <joseph.baylon@mattermost.com>
Co-authored-by: Saturnino Abril <saturnino.abril@gmail.com>

Co-authored-by: Joseph Baylon <joseph.baylon@mattermost.com>
Co-authored-by: Saturnino Abril <saturnino.abril@gmail.com>
2020-10-19 21:39:41 -03:00
Mattermost Build
f23190cf29 Build Improvements (#4884) (#4902)
* Use AppGroupId from Info.plists instead of hardcoded constant

* Update script, ci & Makefile

* Update Cocoapods to 1.9.3

* Split android builds using ABI filters

* Update Fastlane deps & build scripts

* Update CI to use latests scripts

* Display app version & build number in select server screen

* Make generate scripts compatible with node < 12

* Build scripts

* add build script to package.json

* Update to use bundler 2.1.4 and CI with Xcode 12

* Fix script name for build:ios-unsigned

* Fix RN iOS scripts

* Update CI pods-dependencies step

* Add pipefail to android executor

* Update Fastlane

* Fix type in postinstall script

* update android executor and set TERM

* Fix S3 bucket name variable

* Apply suggestions from code review

Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com>

* Fix master unit tests

* use requireActual in jest setup

* Jest setup to use react instead of React

Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com>
(cherry picked from commit 30d4aa2a3e)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2020-10-19 21:21:37 -03:00
Mattermost Build
768e2f1b84 [MM-22366] Make user autocomplete also match on spaces (#4880) (#4901)
* [MM-22366] Make user autocomplete also match on spaces

* Move template string to fullName variable

* Add Detox E2E tests for user autocomplete

* Add TM4J IDs

Co-authored-by: Joseph Baylon <joseph.baylon@mattermost.com>

* Add test for keyword not associated with user

Co-authored-by: Joseph Baylon <joseph.baylon@mattermost.com>
(cherry picked from commit cff81f168e)

Co-authored-by: Ed Trist <edtrist@users.noreply.github.com>
2020-10-19 21:09:17 -03:00
Miguel Alatzar
a0758fd0cd [MM-22959] Use Compass icons (#4847) (#4900)
* Use Compass icons

* Update ChannelInfo and AdvancedSettings

* Fix search modifiers

* Fix Autocomplete item

* Remove VectorIcon component

* Unlink react-native-vector-icons

* Revert ProgressiveImage changes

* Update Mark as Unread icon

* Apply review suggestion

* Replace extension icons

* Update video control button style

* Replace (un)flag with (un)save

* Replace (un)flag with (un)save - take 2

* Use bookmark-outline icon

Co-authored-by: Miguel Alatzar <miguel@Miguels-MacBook-Pro.local>

Co-authored-by: Miguel Alatzar <miguel@Miguels-MacBook-Pro.local>
2020-10-19 16:54:40 -07:00
Weblate (bot)
d5a408b967 Translations update from Weblate (#4886)
* Translated using Weblate (Korean)

Currently translated at 70.8% (464 of 655 strings)

Co-authored-by: Heefe <vampelf@gmail.com>
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_release-1-36/ko/
Translation: mattermost-languages-shipped/mattermost-mobile_release-1.36

* Translated using Weblate (Spanish)

Currently translated at 100.0% (655 of 655 strings)

Co-authored-by: Elias  Nahum <elias@mattermost.com>
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_release-1-36/es/
Translation: mattermost-languages-shipped/mattermost-mobile_release-1.36

* Translated using Weblate (Ukrainian)

Currently translated at 89.4% (586 of 655 strings)

Co-authored-by: Tatyana Rabievska <radyuk.tatyana@gmail.com>
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_release-1-36/uk/
Translation: mattermost-languages-shipped/mattermost-mobile_release-1.36

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (655 of 655 strings)

Co-authored-by: aeomin <lin@aeomin.net>
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_release-1-36/zh_Hans/
Translation: mattermost-languages-shipped/mattermost-mobile_release-1.36

* Translated using Weblate (German)

Currently translated at 96.1% (630 of 655 strings)

Co-authored-by: Elisabeth Kulzer <elikul@elikul.de>
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_release-1-36/de/
Translation: mattermost-languages-shipped/mattermost-mobile_release-1.36

Co-authored-by: Heefe <vampelf@gmail.com>
Co-authored-by: Elias  Nahum <elias@mattermost.com>
Co-authored-by: Tatyana Rabievska <radyuk.tatyana@gmail.com>
Co-authored-by: aeomin <lin@aeomin.net>
Co-authored-by: Elisabeth Kulzer <elikul@elikul.de>
2020-10-13 07:16:48 -03:00
Weblate (bot)
95599b2c8a Translations update from Weblate (#4882)
* Translated using Weblate (Japanese)

Currently translated at 100.0% (655 of 655 strings)

Translated using Weblate (Japanese)

Currently translated at 99.8% (649 of 650 strings)

Co-authored-by: kaakaa <stooner.hoe@gmail.com>
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_release-1-36/ja/
Translation: mattermost-languages-shipped/mattermost-mobile_release-1.36

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (650 of 650 strings)

Co-authored-by: Chikei <chikei@gmail.com>
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_release-1-36/zh_Hant/
Translation: mattermost-languages-shipped/mattermost-mobile_release-1.36

* Translated using Weblate (Dutch)

Currently translated at 100.0% (655 of 655 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (651 of 651 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (650 of 650 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (650 of 650 strings)

Co-authored-by: Tom De Moor <tom@controlaltdieliet.be>
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_release-1-36/nl/
Translation: mattermost-languages-shipped/mattermost-mobile_release-1.36

* Translated using Weblate (Russian)

Currently translated at 100.0% (650 of 650 strings)

Co-authored-by: Alexey Napalkov <flynbit@gmail.com>
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_release-1-36/ru/
Translation: mattermost-languages-shipped/mattermost-mobile_release-1.36

* Translated using Weblate (Romanian)

Currently translated at 100.0% (650 of 650 strings)

Co-authored-by: Viorel-Cosmin Miron <cosmin@uhlhost.net>
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_release-1-36/ro/
Translation: mattermost-languages-shipped/mattermost-mobile_release-1.36

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (655 of 655 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (650 of 650 strings)

Co-authored-by: aeomin <lin@aeomin.net>
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_release-1-36/zh_Hans/
Translation: mattermost-languages-shipped/mattermost-mobile_release-1.36

* Translated using Weblate (Turkish)

Currently translated at 100.0% (651 of 651 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (650 of 650 strings)

Co-authored-by: Kaya Zeren <kayazeren@gmail.com>
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_release-1-36/tr/
Translation: mattermost-languages-shipped/mattermost-mobile_release-1.36

* Translated using Weblate (Dutch)

Currently translated at 100.0% (651 of 651 strings)

Co-authored-by: ctlaltdieliet_TESTACCOUNTFORWEBINARS <github3@controlaltdieliet.be>
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_release-1-36/nl/
Translation: mattermost-languages-shipped/mattermost-mobile_release-1.36

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (655 of 655 strings)

Co-authored-by: rodrigocorsi <rodrigocorsi@gmail.com>
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_release-1-36/pt_BR/
Translation: mattermost-languages-shipped/mattermost-mobile_release-1.36

* Translated using Weblate (Korean)

Currently translated at 69.7% (457 of 655 strings)

Co-authored-by: Heefe <vampelf@gmail.com>
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_release-1-36/ko/
Translation: mattermost-languages-shipped/mattermost-mobile_release-1.36

* Translated using Weblate (Korean)

Currently translated at 70.3% (461 of 655 strings)

Translation: mattermost-languages-shipped/mattermost-mobile_release-1.36
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_release-1-36/ko/

Co-authored-by: kaakaa <stooner.hoe@gmail.com>
Co-authored-by: Chikei <chikei@gmail.com>
Co-authored-by: Tom De Moor <tom@controlaltdieliet.be>
Co-authored-by: Alexey Napalkov <flynbit@gmail.com>
Co-authored-by: Viorel-Cosmin Miron <cosmin@uhlhost.net>
Co-authored-by: aeomin <lin@aeomin.net>
Co-authored-by: Kaya Zeren <kayazeren@gmail.com>
Co-authored-by: ctlaltdieliet_TESTACCOUNTFORWEBINARS <github3@controlaltdieliet.be>
Co-authored-by: rodrigocorsi <rodrigocorsi@gmail.com>
Co-authored-by: Heefe <vampelf@gmail.com>
2020-10-07 10:31:54 +02:00
Mattermost Build
349726641c Bump app build number to 330 (#4876) (#4877)
(cherry picked from commit 4782afc28e)

Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com>
2020-10-05 13:51:15 -07:00
Mattermost Build
4602fb916f Emit PASTE_FILES event only from last subscribed input (#4872) (#4875)
(cherry picked from commit ae43ba1187)

Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com>
2020-10-05 13:18:04 -07:00
Mattermost Build
c9ce2338f8 [MM-29275]: Remove first char of slash cmd autocomplete suggestion text, only if it is a top-level command (#4868) (#4873)
* Remove first char of suggestion text, only if it is a slash

* Check if suggestion is for a top-level command

(cherry picked from commit a6779f4293)

Co-authored-by: Michael Kochell <6913320+mickmister@users.noreply.github.com>
2020-10-05 12:19:42 -07:00
Mattermost Build
a3dae17ded Bump app build number to 329 (#4864) (#4865)
(cherry picked from commit 2c7be6f0aa)

Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com>
2020-10-02 11:04:58 -07:00
Mattermost Build
c815325034 [MM-29216] Don't allow sharing of files if file uploads are disabled (#4857) (#4862)
* Don't allow sharing of files if file uploads are disabled

* Remove snapshot

* Fix iOS message

(cherry picked from commit b8ce726b0d)

Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com>
2020-10-02 10:12:41 -07:00
Mattermost Build
33cfb52bab [MM-29225] Linking fix (#4860) (#4861)
* Catch openURL over checking canOpenURL

* Update en.json

(cherry picked from commit a5cb92876c)

Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com>
2020-10-02 10:12:29 -07:00
Mattermost Build
eeb26eac35 Update NOTICE.txt (#4836) (#4859)
(cherry picked from commit 07d47d90ce)

Co-authored-by: Amy Blais <amy_blais@hotmail.com>
2020-10-01 14:53:37 -07:00
Mattermost Build
e711b36789 [MM-29132] Update upload item styling (#4853) (#4856)
* Update upload item styling

* Use font size 9

(cherry picked from commit 85382e695a)

Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com>
2020-10-01 11:36:56 -07:00
Mattermost Build
e9791b7bee Bump app build number to 328 (#4848) (#4849)
(cherry picked from commit 4265db3ecd)

Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com>
2020-09-25 15:01:33 -07:00
Mattermost Build
d8ff0e52bb MM-22968 Restyle mobile autocomplete (#4531) (#4846)
* WIP: slash suggestion autocomplete

* WIP: patched styles a bit

* WIP: Adding styles

* Adding active state to autocomplete items

* Fixing style for channel mention item

* Fixing bugs + styling issues for Android

* Updating snapshot

* Fixing autocomplete to render on top of post draft

- Misc style fixes

* Renaming props, patching slash suggestion icon

* Fixing tests and lint errors

* Resolving post-merge issue with slash commands

* Fixing android positioning for autocomplete

* Fixing autocomplete not scrolling in edit_channel_info

* WIP: Fixing things according to UX Review

* UX Fixes to autocomplete

* Updating snapshots

* Updating snapshots, replacing slash-command icons

* Fixing android scrolling and positioning issues

* Fixing issues with date_suggestion not rendering

* Making use of the "ShowFullName" config in at_mention_item

* Removing top border on first autocomplete section

* Allowing autocomplete to be smaller than its maxWidth

* Fixing slash_suggestion padding

* removing "componentWillReceiveProps" from date_suggestion

* Changing edit_channel_info autocomplete offset

* Replacing toUpperCase() with textTransform: uppercase

* Fixing odd border issues + prop validation warning

* Restore section header background & add paddingBottom

* Patching up padding on channel mentions

- Reverting previous incorrect padding adjustments

* Removing inline 'completeSuggestion' function

* Removing brackets from style prop

(cherry picked from commit 59045e3bb0)

Co-authored-by: Andre Vasconcelos <andre.onogoro@gmail.com>
2020-09-25 11:39:38 -07:00
Mattermost Build
5778019074 [MM-28706] revert ordering change for in: autosuggest search (#4818) (#4844)
Summary:
Revert ordering change for in: auto-suggest search after user feedback. The ordering now is Public Channels, Private Channels, and finally DMs and GMs.
This PR only fixes ordering. Further search improvements are planned in https://mattermost.atlassian.net/browse/MM-26805

Ticket Link:
https://mattermost.atlassian.net/browse/MM-28706
The Jira ticket above reverts the changes in https://mattermost.atlassian.net/browse/MM-27540 (Parent: https://mattermost.atlassian.net/browse/MM-15477) which was implemented in #4704

(cherry picked from commit 99f018faac)

Co-authored-by: Ashish Bhate <bhate.ashish@gmail.com>
2020-09-25 13:58:04 +05:30
Mattermost Build
d67b7a2deb [MM-23909] better error message if url is empty (#4821) (#4842)
(cherry picked from commit b6fbcf2e7c)

Co-authored-by: Ashish Bhate <bhate.ashish@gmail.com>
2020-09-25 13:52:47 +05:30
Mattermost Build
dbefec6f17 [MM-28100] fix reachability check and ensure channel membership before getting posts (#4810) (#4841)
(cherry picked from commit 7647367bcf)

Co-authored-by: Ashish Bhate <bhate.ashish@gmail.com>
2020-09-25 13:46:17 +05:30
Mattermost Build
12fe0465c2 [MM-27615] Update RequiredServer (#4830) (#4840)
* Update RequiredServer

* Take 2

* Update patch version

* Take 3

* Update version

* Update version

(cherry picked from commit 72313291a1)

Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com>
2020-09-24 11:13:53 -07:00
Mattermost Build
aa22d11f79 Exclude NotificationPreference for DMs (#4831) (#4839)
(cherry picked from commit 1d94fb6e16)

Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com>
2020-09-24 11:07:52 -07:00
Mattermost Build
8cfa485f51 [MM-28973] Prevent destructuring of undefined (#4832) (#4838)
* Prevent destructuring of undefined

* Do the same for thread drafts

(cherry picked from commit 8a5c58b39a)

Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com>
2020-09-24 11:00:58 -07:00
Miguel Alatzar
ae93498d50 Bump app build number to 327 (#4829) 2020-09-22 10:56:59 -07:00
Miguel Alatzar
60385eeae8 Bump app version number to 1.36.0 (#4828) 2020-09-22 08:13:52 -07:00
Mattermost Build
22af5690ac Fix getPostThread import (#4819) (#4822)
(cherry picked from commit de1f38b839)

Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com>
2020-09-21 11:05:30 -07:00
491 changed files with 12515 additions and 8560 deletions

View File

@@ -13,7 +13,7 @@ executors:
NODE_ENV: production
BABEL_ENV: production
docker:
- image: circleci/android:api-27-node
- image: circleci/android:api-29-node
working_directory: ~/mattermost-mobile
resource_class: <<parameters.resource_class>>
@@ -23,7 +23,7 @@ executors:
NODE_ENV: production
BABEL_ENV: production
macos:
xcode: "11.0.0"
xcode: "12.0.0"
working_directory: ~/mattermost-mobile
shell: /bin/bash --login -o pipefail
@@ -44,7 +44,6 @@ commands:
for:
type: string
steps:
- ruby-setup
- restore_cache:
name: Restore Fastlane cache
key: v1-gems-<< parameters.for >>-{{ checksum "fastlane/Gemfile.lock" }}-{{ arch }}
@@ -82,7 +81,7 @@ commands:
key: v1-assets-{{ checksum "assets/base/config.json" }}-{{ arch }}
- run:
name: Generate assets
command: make dist/assets
command: node ./scripts/generate-assets.js
- save_cache:
name: Save assets cache
key: v1-assets-{{ checksum "assets/base/config.json" }}-{{ arch }}
@@ -104,8 +103,8 @@ commands:
paths:
- node_modules
- run:
name: "Run post install scripts"
command: make post-install
name: "Patch dependencies"
command: npx patch-package
pods-dependencies:
description: "Get cocoapods dependencies"
@@ -113,10 +112,12 @@ commands:
- restore_cache:
name: Restore cocoapods specs and pods
key: v1-cocoapods-{{ checksum "ios/Podfile.lock" }}-{{ arch }}
- run:
name: iOS gems
command: npm run ios-gems
- run:
name: Getting cocoapods dependencies
working_directory: ios
command: pod install
command: npm run pod-install
- save_cache:
name: Save cocoapods specs and pods cache
key: v1-cocoapods-{{ checksum "ios/Podfile.lock" }}-{{ arch }}
@@ -143,11 +144,14 @@ commands:
echo MATTERMOST_RELEASE_STORE_FILE=${STORE_FILE} | tee -a android/gradle.properties > /dev/null
echo ${STORE_ALIAS} | tee -a android/gradle.properties > /dev/null
echo ${STORE_PASSWORD} | tee -a android/gradle.properties > /dev/null
- run:
name: Jetify android libraries
command: ./node_modules/.bin/jetify
- run:
working_directory: fastlane
name: Run fastlane to build android
no_output_timeout: 30m
command: bundle exec fastlane android build
command: export TERM=xterm && bundle exec fastlane android build
build-ios:
description: "Build the iOS app"
@@ -165,7 +169,7 @@ commands:
no_output_timeout: 30m
command: |
HOMEBREW_NO_AUTO_UPDATE=1 brew install watchman
bundle exec fastlane ios build
export TERM=xterm && bundle exec fastlane ios build
deploy-to-store:
description: "Deploy build to store"
@@ -197,15 +201,14 @@ commands:
parameters:
filename:
type: string
steps:
- store_artifacts:
path: ~/mattermost-mobile/<<parameters.filename>>
ruby-setup:
steps:
- run:
name: Set Ruby Version
command: echo "ruby-2.6.3" > ~/.ruby-version
name: Copying artifacts
command: |
mkdir /tmp/artifacts;
cp ~/mattermost-mobile/<<parameters.filename>> /tmp/artifacts;
- store_artifacts:
path: /tmp/artifacts
jobs:
test:
@@ -225,19 +228,7 @@ jobs:
command: npm test
- run:
name: Check i18n
command: |
mkdir -p tmp
cp assets/base/i18n/en.json tmp/en.json
mkdir -p tmp/fake-webapp-dir/i18n/
echo '{}' > tmp/fake-webapp-dir/i18n/en.json
npm run mmjstool -- i18n extract-mobile --webapp-dir tmp/fake-webapp-dir --mobile-dir .
diff tmp/en.json assets/base/i18n/en.json
# Address weblate behavior which does not remove whole translation item when translation string is set to empty
npm run mmjstool -- i18n clean-empty --webapp-dir tmp/fake-webapp-dir --mobile-dir . --check
rm -rf tmp
command: ./scripts/precommit/i18n.sh
check-deps:
parameters:
@@ -297,7 +288,7 @@ jobs:
- build-android
- persist
- save:
filename: "Mattermost_Beta.apk"
filename: "*.apk"
build-android-release:
executor: android
@@ -305,7 +296,7 @@ jobs:
- build-android
- persist
- save:
filename: "Mattermost.apk"
filename: "*.apk"
build-android-pr:
executor: android
@@ -314,7 +305,7 @@ jobs:
steps:
- build-android
- save:
filename: "Mattermost_Beta.apk"
filename: "*.apk"
build-android-unsigned:
executor: android
@@ -326,6 +317,9 @@ jobs:
- fastlane-dependencies:
for: android
- gradle-dependencies
- run:
name: Jetify Android libraries
command: ./node_modules/.bin/jetify
- run:
working_directory: fastlane
name: Run fastlane to build unsigned android
@@ -333,7 +327,7 @@ jobs:
command: bundle exec fastlane android unsigned
- persist
- save:
filename: "Mattermost-unsigned.apk"
filename: "*.apk"
build-ios-beta:
executor: ios
@@ -341,7 +335,7 @@ jobs:
- build-ios
- persist
- save:
filename: "Mattermost_Beta.ipa"
filename: "*.ipa"
build-ios-release:
executor: ios
@@ -349,7 +343,7 @@ jobs:
- build-ios
- persist
- save:
filename: "Mattermost.ipa"
filename: "*.ipa"
build-ios-pr:
executor: ios
@@ -358,14 +352,13 @@ jobs:
steps:
- build-ios
- save:
filename: "Mattermost_Beta.ipa"
filename: "*.ipa"
build-ios-unsigned:
executor: ios
steps:
- checkout:
path: ~/mattermost-mobile
- ruby-setup
- npm-dependencies
- pods-dependencies
- assets
@@ -381,16 +374,15 @@ jobs:
- persist_to_workspace:
root: ~/
paths:
- mattermost-mobile/Mattermost-unsigned.ipa
- mattermost-mobile/*.ipa
- save:
filename: "Mattermost-unsigned.ipa"
filename: "*.ipa"
build-ios-simulator:
executor: ios
steps:
- checkout:
path: ~/mattermost-mobile
- ruby-setup
- npm-dependencies
- pods-dependencies
- assets
@@ -415,47 +407,42 @@ jobs:
name: android
resource_class: medium
steps:
- ruby-setup
- deploy-to-store:
task: "Deploy to Google Play"
target: android
file: Mattermost.apk
file: "*.apk"
deploy-android-beta:
executor:
name: android
resource_class: medium
steps:
- ruby-setup
- deploy-to-store:
task: "Deploy to Google Play"
target: android
file: Mattermost_Beta.apk
file: "*.apk"
deploy-ios-release:
executor: ios
steps:
- ruby-setup
- deploy-to-store:
task: "Deploy to TestFlight"
target: ios
file: Mattermost.ipa
file: "*.ipa"
deploy-ios-beta:
executor: ios
steps:
- ruby-setup
- deploy-to-store:
task: "Deploy to TestFlight"
target: ios
file: Mattermost_Beta.ipa
file: "*.ipa"
github-release:
executor:
name: android
resource_class: medium
steps:
- ruby-setup
- attach_workspace:
at: ~/
- run:

232
Makefile
View File

@@ -1,232 +0,0 @@
.PHONY: pre-run pre-build clean
.PHONY: check-style
.PHONY: start stop
.PHONY: run run-ios run-android
.PHONY: build build-ios build-android unsigned-ios unsigned-android ios-sim-x86_64
.PHONY: build-pr can-build-pr prepare-pr
.PHONY: test help
OS := $(shell sh -c 'uname -s 2>/dev/null')
BASE_ASSETS = $(shell find assets/base -type d) $(shell find assets/base -type f -name '*')
OVERRIDE_ASSETS = $(shell find assets/override -type d 2> /dev/null) $(shell find assets/override -type f -name '*' 2> /dev/null)
MM_UTILITIES_DIR = ../mattermost-utilities
node_modules: package.json
@if ! [ $(shell which npm 2> /dev/null) ]; then \
echo "npm is not installed https://npmjs.com"; \
exit 1; \
fi
@echo Getting Javascript dependencies
@npm install
npm-ci: package.json
@if ! [ $(shell which npm 2> /dev/null) ]; then \
echo "npm is not installed https://npmjs.com"; \
exit 1; \
fi
@echo Getting Javascript dependencies
@npm ci
.podinstall:
ifeq ($(OS), Darwin)
@echo "Required version of Cocoapods is not installed"
@echo Installing gems;
@bundle install
@echo Getting Cocoapods dependencies;
@cd ios && bundle exec pod install;
endif
@touch $@
dist/assets: $(BASE_ASSETS) $(OVERRIDE_ASSETS)
@mkdir -p dist
@if [ -e dist/assets ] ; then \
rm -rf dist/assets; \
fi
@echo "Generating app assets"
@node scripts/make-dist-assets.js
pre-run: | node_modules .podinstall dist/assets ## Installs dependencies and assets
pre-build: | npm-ci .podinstall dist/assets ## Install dependencies and assets before building
check-style: node_modules ## Runs eslint
@echo Checking for style guide compliance
@npm run check
clean: ## Cleans dependencies, previous builds and temp files
@echo Cleaning started
@rm -f .podinstall
@rm -rf ios/Pods
@rm -rf node_modules
@rm -rf dist
@rm -rf ios/build
@rm -rf android/app/build
@echo Cleanup finished
post-install:
@./node_modules/.bin/patch-package
@./node_modules/.bin/jetify
@rm -f node_modules/intl/.babelrc
@# Hack to get react-intl and its dependencies to work with react-native
@# Based off of https://github.com/este/este/blob/master/gulp/native-fix.js
@sed -i'' -e 's|"./locale-data/index.js": false|"./locale-data/index.js": "./locale-data/index.js"|g' node_modules/react-intl/package.json
@sed -i'' -e 's|"./lib/locales": false|"./lib/locales": "./lib/locales"|g' node_modules/intl-messageformat/package.json
@sed -i'' -e 's|"./lib/locales": false|"./lib/locales": "./lib/locales"|g' node_modules/intl-relativeformat/package.json
@sed -i'' -e 's|"./locale-data/complete.js": false|"./locale-data/complete.js": "./locale-data/complete.js"|g' node_modules/intl/package.json
start: | pre-run ## Starts the React Native packager server
$(call start_packager)
stop: ## Stops the React Native packager server
$(call stop_packager)
check-device-ios:
@if ! [ $(shell which xcodebuild) ]; then \
echo "xcode is not installed"; \
exit 1; \
fi
@if ! [ $(shell which watchman) ]; then \
echo "watchman is not installed"; \
exit 1; \
fi
check-device-android:
@if ! [ $(ANDROID_HOME) ]; then \
echo "ANDROID_HOME is not set"; \
exit 1; \
fi
@if ! [ $(shell which adb 2> /dev/null) ]; then \
echo "adb is not installed"; \
exit 1; \
fi
@echo "Connect your Android device or open the emulator"
@adb wait-for-device
@if ! [ $(shell which watchman 2> /dev/null) ]; then \
echo "watchman is not installed"; \
exit 1; \
fi
prepare-android-build:
@rm -rf ./node_modules/react-native/local-cli/templates/HelloWorld
@rm -rf ./node_modules/react-native-linear-gradient/Examples/
@rm -rf ./node_modules/react-native-orientation/demo/
run: run-ios ## alias for run-ios
run-ios: | check-device-ios pre-run ## Runs the app on an iOS simulator
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
echo Starting React Native packager server; \
npm start & echo Running iOS app in development; \
if [ ! -z "${SIMULATOR}" ]; then \
react-native run-ios --simulator="${SIMULATOR}"; \
else \
react-native run-ios; \
fi; \
wait; \
else \
echo Running iOS app in development; \
if [ ! -z "${SIMULATOR}" ]; then \
react-native run-ios --simulator="${SIMULATOR}"; \
else \
react-native run-ios; \
fi; \
fi
run-android: | check-device-android pre-run prepare-android-build ## Runs the app on an Android emulator or dev device
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
echo Starting React Native packager server; \
npm start & echo Running Android app in development; \
if [ ! -z ${VARIANT} ]; then \
react-native run-android --no-packager --variant=${VARIANT}; \
else \
react-native run-android --no-packager; \
fi; \
wait; \
else \
echo Running Android app in development; \
if [ ! -z ${VARIANT} ]; then \
react-native run-android --no-packager --variant=${VARIANT}; \
else \
react-native run-android --no-packager; \
fi; \
fi
build: | stop pre-build check-style ## Builds the app for Android & iOS
$(call start_packager)
@echo "Building App"
@cd fastlane && BABEL_ENV=production NODE_ENV=production bundle exec fastlane build
$(call stop_packager)
build-ios: | stop pre-build check-style ## Builds the iOS app
$(call start_packager)
@echo "Building iOS app"
@cd fastlane && BABEL_ENV=production NODE_ENV=production bundle exec fastlane ios build
$(call stop_packager)
build-android: | stop pre-build check-style prepare-android-build ## Build the Android app
$(call start_packager)
@echo "Building Android app"
@cd fastlane && BABEL_ENV=production NODE_ENV=production bundle exec fastlane android build
$(call stop_packager)
unsigned-ios: stop pre-build check-style ## Build an unsigned version of the iOS app
$(call start_packager)
@cd fastlane && NODE_ENV=production bundle exec fastlane ios unsigned
$(call stop_packager)
ios-sim-x86_64: stop pre-build check-style ## Build an unsigned x86_64 version of the iOS app for iPhone simulator
$(call start_packager)
@echo "Building unsigned x86_64 iOS app for iPhone simulator"
@cd fastlane && NODE_ENV=production bundle exec fastlane ios simulator
$(call stop_packager)
unsigned-android: stop pre-build check-style prepare-android-build ## Build an unsigned version of the Android app
@cd fastlane && NODE_ENV=production bundle exec fastlane android unsigned
test: | pre-run check-style ## Runs tests
@npm test
build-pr: | can-build-pr stop pre-build check-style ## Build a PR from the mattermost-mobile repo
$(call start_packager)
@echo "Building App from PR ${PR_ID}"
@cd fastlane && BABEL_ENV=production NODE_ENV=production bundle exec fastlane build_pr pr:PR-${PR_ID}
$(call stop_packager)
can-build-pr:
@if [ -z ${PR_ID} ]; then \
echo a PR number needs to be specified; \
exit 1; \
fi
## Help documentation https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
define start_packager
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
echo Starting React Native packager server; \
npm start & echo; \
else \
echo React Native packager server already running; \
fi
endef
define stop_packager
@echo Stopping React Native packager server
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 1 ]; then \
ps -ef | grep -i "cli.js start" | grep -iv grep | awk '{print $$2}' | xargs kill -9; \
echo React Native packager server stopped; \
else \
echo No React Native packager server running; \
fi
endef

View File

@@ -2577,41 +2577,6 @@ SOFTWARE.
---
## react-native-v8
This product contains 'react-native-v8' by Kudo Chien.
Opt-in V8 runtime for React Native Android
* HOMEPAGE:
* https://github.com/Kudo/react-native-v8
* LICENSE: MIT
MIT License
Copyright (c) 2019 Kudo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
## react-native-vector-icons
This product contains 'react-native-vector-icons' by Joel Arvidsson.

View File

@@ -101,7 +101,7 @@ if (System.getenv("SENTRY_ENABLED") == "true") {
* Upload all the APKs to the Play Store and people will download
* the correct one based on the CPU architecture of their device.
*/
def enableSeparateBuildPerCPUArchitecture = false
def enableSeparateBuildPerCPUArchitecture = project.hasProperty('separateApk') ? project.property('separateApk').toBoolean() : false
/**
* Run Proguard to shrink the Java bytecode in release builds.
@@ -132,12 +132,9 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
missingDimensionStrategy "RNNotifications.reactNativeVersion", "reactNative60"
versionCode 325
versionName "1.35.0"
versionCode 334
versionName "1.37.0"
multiDexEnabled = true
ndk {
abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64'
}
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
@@ -155,7 +152,7 @@ android {
abi {
reset()
enable enableSeparateBuildPerCPUArchitecture
universalApk false // If true, also generate a universal APK
universalApk enableSeparateBuildPerCPUArchitecture // If true, also generate a universal APK
include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}
}
@@ -184,7 +181,7 @@ android {
def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride =
versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
versionCodes.get(abi) * 1000000 + defaultConfig.versionCode
}
}
}

Binary file not shown.

View File

@@ -82,7 +82,12 @@ public class RNPasteableActionCallback implements ActionMode.Callback {
return null;
}
String text = item.getText().toString();
CharSequence chars = item.getText();
if (chars == null) {
return null;
}
String text = chars.toString();
if (text.length() > 0) {
return null;
}

View File

@@ -17,9 +17,4 @@ public class ShareActivity extends ReactActivity {
MainApplication app = (MainApplication) this.getApplication();
app.sharedExtensionIsOpened = true;
}
@Override
public void onBackPressed() {
finish();
}
}

View File

@@ -186,6 +186,7 @@ export function goToScreen(name, title, passProps = {}, options = {}) {
backButton: {
color: theme.sidebarHeaderTextColor,
title: '',
testID: 'screen.back.button',
},
background: {
color: theme.sidebarHeaderBg,

View File

@@ -188,6 +188,7 @@ describe('@actions/navigation', () => {
backButton: {
color: theme.sidebarHeaderTextColor,
title: '',
testID: 'screen.back.button',
},
background: {
color: theme.sidebarHeaderBg,
@@ -494,4 +495,4 @@ describe('@actions/navigation', () => {
expect(popToRoot).toHaveBeenCalledWith(topComponentId);
expect(EventEmitter.emit).toHaveBeenCalledWith(NavigationTypes.NAVIGATION_DISMISS_AND_POP_TO_ROOT);
});
});
});

View File

@@ -9,6 +9,7 @@ import {ChannelTypes, RoleTypes, GroupTypes} from '@mm-redux/action_types';
import {
fetchMyChannelsAndMembers,
getChannelByNameAndTeamName,
joinChannel,
leaveChannel as serviceLeaveChannel,
} from '@mm-redux/actions/channels';
import {savePreferences} from '@mm-redux/actions/preferences';
@@ -22,6 +23,7 @@ import {
getCurrentChannelId,
getRedirectChannelNameForTeam,
getChannelsNameMapInTeam,
getMyChannelMemberships,
isManuallyUnread,
} from '@mm-redux/selectors/entities/channels';
import {getCurrentUserId} from '@mm-redux/selectors/entities/users';
@@ -44,15 +46,21 @@ export function loadChannelsByTeamName(teamName, errorHandler) {
return async (dispatch, getState) => {
const state = getState();
const {currentTeamId} = state.entities.teams;
const team = getTeamByName(state, teamName);
if (!team && errorHandler) {
errorHandler();
if (teamName) {
const team = getTeamByName(state, teamName);
if (!team && errorHandler) {
errorHandler();
return {error: true};
}
if (team && team.id !== currentTeamId) {
await dispatch(fetchMyChannelsAndMembers(team.id));
}
}
if (team && team.id !== currentTeamId) {
await dispatch(fetchMyChannelsAndMembers(team.id));
}
return {data: true};
};
}
@@ -210,13 +218,15 @@ export function handleSelectChannel(channelId) {
export function handleSelectChannelByName(channelName, teamName, errorHandler) {
return async (dispatch, getState) => {
const state = getState();
let state = getState();
const {teams: currentTeams, currentTeamId} = state.entities.teams;
const currentTeam = currentTeams[currentTeamId];
const currentTeamName = currentTeam?.name;
const response = await dispatch(getChannelByNameAndTeamName(teamName || currentTeamName, channelName));
const {error, data: channel} = response;
const currentChannelId = getCurrentChannelId(state);
state = getState();
const reachable = getChannelReachable(state, channelName, teamName);
if (!reachable && errorHandler) {
@@ -234,6 +244,17 @@ export function handleSelectChannelByName(channelName, teamName, errorHandler) {
}
if (channel && currentChannelId !== channel.id) {
if (channel.type === General.OPEN_CHANNEL) {
const myMemberships = getMyChannelMemberships(state);
if (!myMemberships[channel.id]) {
const currentUserId = getCurrentUserId(state);
console.log('joining channel', channel?.display_name, channel.id); //eslint-disable-line
const result = await dispatch(joinChannel(currentUserId, teamName, channel.id));
if (result.error || !result.data || !result.data.channel) {
return {error};
}
}
}
dispatch(handleSelectChannel(channel.id));
}
@@ -728,7 +749,7 @@ export function loadChannelsForTeam(teamId, skipDispatch = false) {
};
}
function loadSidebar(data) {
export function loadSidebar(data) {
return async (dispatch, getState) => {
const state = getState();
const {channels, channelMembers} = data;
@@ -737,6 +758,8 @@ function loadSidebar(data) {
if (sidebarActions.length) {
dispatch(batchActions(sidebarActions, 'BATCH_LOAD_SIDEBAR'));
}
return {data: true};
};
}

View File

@@ -0,0 +1,51 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {intlShape} from 'react-intl';
import {Keyboard} from 'react-native';
import {showModalOverCurrentContext} from '@actions/navigation';
import {loadChannelsByTeamName} from '@actions/views/channel';
import {selectFocusedPostId} from '@mm-redux/actions/posts';
import type {DispatchFunc} from '@mm-redux/types/actions';
import {permalinkBadTeam} from '@utils/general';
import {changeOpacity} from '@utils/theme';
export let showingPermalink = false;
export function showPermalink(intl: typeof intlShape, teamName: string, postId: string, openAsPermalink = true) {
return async (dispatch: DispatchFunc) => {
const loadTeam = await dispatch(loadChannelsByTeamName(teamName, permalinkBadTeam.bind(null, intl)));
if (!loadTeam.error) {
Keyboard.dismiss();
dispatch(selectFocusedPostId(postId));
if (!showingPermalink) {
const screen = 'Permalink';
const passProps = {
isPermalink: openAsPermalink,
onClose: () => {
dispatch(closePermalink());
},
};
const options = {
layout: {
componentBackgroundColor: changeOpacity('#000', 0.2),
},
};
showingPermalink = true;
showModalOverCurrentContext(screen, passProps, options);
}
}
};
}
export function closePermalink() {
return async (dispatch: DispatchFunc) => {
showingPermalink = false;
return dispatch(selectFocusedPostId(''));
};
}

View File

@@ -51,10 +51,9 @@ exports[`AnnouncementBanner should match snapshot 1`] = `
value="Banner Text"
/>
</Text>
<Icon
allowFontScaling={false}
<CompassIcon
color="#fff"
name="info"
name="info-outline"
size={16}
/>
</ForwardRef>
@@ -112,10 +111,9 @@ exports[`AnnouncementBanner should match snapshot 2`] = `
value="Banner Text"
/>
</Text>
<Icon
allowFontScaling={false}
<CompassIcon
color="#fff"
name="info"
name="info-outline"
size={16}
/>
</ForwardRef>

View File

@@ -11,11 +11,11 @@ import {
TouchableOpacity,
} from 'react-native';
import {intlShape} from 'react-intl';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import RemoveMarkdown from 'app/components/remove_markdown';
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
import {goToScreen} from 'app/actions/navigation';
import CompassIcon from '@components/compass_icon';
import RemoveMarkdown from '@components/remove_markdown';
import {paddingHorizontal as padding} from '@components/safe_area_view/iphone_x_spacing';
import {goToScreen} from '@actions/navigation';
const {View: AnimatedView} = Animated;
@@ -122,9 +122,9 @@ export default class AnnouncementBanner extends PureComponent {
>
<RemoveMarkdown value={bannerText}/>
</Text>
<MaterialIcons
<CompassIcon
color={bannerTextColor}
name='info'
name='info-outline'
size={16}
/>
</TouchableOpacity>

View File

@@ -1,44 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import Svg, {
G,
Path,
} from 'react-native-svg';
export default class AppIcon extends PureComponent {
static propTypes = {
width: PropTypes.number.isRequired,
height: PropTypes.number.isRequired,
color: PropTypes.string.isRequired,
};
render() {
return (
<Svg
height={this.props.height}
width={this.props.width}
viewBox='0 0 500 500'
>
<G id='XMLID_1_'>
<G id='XMLID_3_'>
<Path
id='XMLID_4_'
class='st0'
d='M396.9,47.7l2.6,53.1c43,47.5,60,114.8,38.6,178.1c-32,94.4-137.4,144.1-235.4,110.9 S51.1,253.1,83,158.7C104.5,95.2,159.2,52,222.5,40.5l34.2-40.4C150-2.8,49.3,63.4,13.3,169.9C-31,300.6,39.1,442.5,169.9,486.7 s272.6-25.8,316.9-156.6C522.7,223.9,483.1,110.3,396.9,47.7z'
fill={this.props.color}
/>
</G>
<Path
id='XMLID_2_'
class='st0'
d='M335.6,204.3l-1.8-74.2l-1.5-42.7l-1-37c0,0,0.2-17.8-0.4-22c-0.1-0.9-0.4-1.6-0.7-2.2 c0-0.1-0.1-0.2-0.1-0.3c0-0.1-0.1-0.2-0.1-0.2c-0.7-1.2-1.8-2.1-3.1-2.6c-1.4-0.5-2.9-0.4-4.2,0.2c0,0-0.1,0-0.1,0 c-0.2,0.1-0.3,0.1-0.4,0.2c-0.6,0.3-1.2,0.7-1.8,1.3c-3,3-13.7,17.2-13.7,17.2l-23.2,28.8l-27.1,33l-46.5,57.8 c0,0-21.3,26.6-16.6,59.4s29.1,48.7,48,55.1c18.9,6.4,48,8.5,71.6-14.7C336.4,238.4,335.6,204.3,335.6,204.3z'
fill={this.props.color}
/>
</G>
</Svg>
);
}
}

View File

@@ -0,0 +1,38 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {StyleSheet, View} from 'react-native';
import DeviceInfo from 'react-native-device-info';
import FormattedText from '@components/formatted_text';
const style = StyleSheet.create({
info: {
alignItems: 'center',
justifyContent: 'flex-end',
},
version: {
fontSize: 12,
},
});
const AppVersion = () => {
return (
<View pointerEvents='none'>
<View style={style.info}>
<FormattedText
id='mobile.about.appVersion'
defaultMessage='App Version: {version} (Build {number})'
style={style.version}
values={{
version: DeviceInfo.getVersion(),
number: DeviceInfo.getBuildNumber(),
}}
/>
</View>
</View>
);
};
export default AppVersion;

View File

@@ -6,13 +6,13 @@ import PropTypes from 'prop-types';
import {StyleSheet, Text} from 'react-native';
import Clipboard from '@react-native-community/clipboard';
import {intlShape} from 'react-intl';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
import {displayUsername} from '@mm-redux/utils/user_utils';
import {showModal} from '@actions/navigation';
import {displayUsername} from '@mm-redux/utils/user_utils';
import CompassIcon from '@components/compass_icon';
import CustomPropTypes from '@constants/custom_prop_types';
import mattermostManaged from 'app/mattermost_managed';
import BottomSheet from '@utils/bottom_sheet';
import mattermostManaged from 'app/mattermost_managed';
export default class AtMention extends React.PureComponent {
static propTypes = {
@@ -57,7 +57,7 @@ export default class AtMention extends React.PureComponent {
};
if (!this.closeButton) {
this.closeButton = await MaterialIcon.getImageSource('close', 20, theme.sidebarHeaderTextColor);
this.closeButton = await CompassIcon.getImageSource('close', 24, theme.sidebarHeaderTextColor);
}
const options = {

View File

@@ -13,10 +13,9 @@ exports[`AttachmentButton should match snapshot 1`] = `
}
type="opacity"
>
<Icon
allowFontScaling={false}
<CompassIcon
color="rgba(61,60,64,0.9)"
name="md-add"
name="plus"
size={30}
style={
Object {

View File

@@ -14,12 +14,12 @@ import RNFetchBlob from 'rn-fetch-blob';
import DeviceInfo from 'react-native-device-info';
import AndroidOpenSettings from 'react-native-android-open-settings';
import Icon from 'react-native-vector-icons/Ionicons';
import DocumentPicker from 'react-native-document-picker';
import ImagePicker from 'react-native-image-picker';
import Permissions from 'react-native-permissions';
import {showModalOverCurrentContext} from '@actions/navigation';
import CompassIcon from '@components/compass_icon';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {NavigationTypes} from '@constants';
import emmProvider from '@init/emm_provider';
@@ -424,7 +424,7 @@ export default class AttachmentButton extends PureComponent {
id: t('mobile.file_upload.camera_photo'),
defaultMessage: 'Take Photo',
},
icon: 'camera',
icon: 'camera-outline',
});
}
@@ -435,7 +435,7 @@ export default class AttachmentButton extends PureComponent {
id: t('mobile.file_upload.camera_video'),
defaultMessage: 'Take Video',
},
icon: 'video-camera',
icon: 'video-outline',
});
}
@@ -446,7 +446,7 @@ export default class AttachmentButton extends PureComponent {
id: t('mobile.file_upload.library'),
defaultMessage: 'Photo Library',
},
icon: 'photo',
icon: 'file-image-outline',
});
}
@@ -457,7 +457,7 @@ export default class AttachmentButton extends PureComponent {
id: t('mobile.file_upload.video'),
defaultMessage: 'Video Library',
},
icon: 'file-video-o',
icon: 'file-video-outline',
});
}
@@ -468,7 +468,7 @@ export default class AttachmentButton extends PureComponent {
id: t('mobile.file_upload.browse'),
defaultMessage: 'Browse Files',
},
icon: 'file',
icon: 'file-outline',
});
}
@@ -503,11 +503,11 @@ export default class AttachmentButton extends PureComponent {
style={style.buttonContainer}
type={'opacity'}
>
<Icon
<CompassIcon
size={30}
style={style.attachIcon}
color={changeOpacity(theme.centerChannelColor, 0.9)}
name='md-add'
name='plus'
/>
</TouchableWithFeedback>
);

View File

@@ -9,7 +9,6 @@ import {RequestStatus} from '@mm-redux/constants';
import {AT_MENTION_REGEX, AT_MENTION_SEARCH_REGEX} from 'app/constants/autocomplete';
import AtMentionItem from 'app/components/autocomplete/at_mention_item';
import AutocompleteDivider from 'app/components/autocomplete/autocomplete_divider';
import AutocompleteSectionHeader from 'app/components/autocomplete/autocomplete_section_header';
import SpecialMentionItem from 'app/components/autocomplete/special_mention_item';
import GroupMentionItem from 'app/components/autocomplete/at_mention_group/at_mention_group';
@@ -62,22 +61,9 @@ export default class AtMention extends PureComponent {
// Not invoked, render nothing.
if (matchTerm === null) {
this.props.onResultCountChange(0);
this.setState({
mentionComplete: false,
sections: [],
});
return;
}
if (this.state.mentionComplete) {
// Mention has been completed. Hide autocomplete.
this.setState({
sections: [],
});
this.props.onResultCountChange(0);
return;
}
@@ -108,6 +94,12 @@ export default class AtMention extends PureComponent {
}
}
componentDidUpdate(prevProps, prevState) {
if (prevState.sections.length !== this.state.sections.length && this.state.sections.length === 0) {
this.props.onResultCountChange(0);
}
}
buildSections = (props) => {
const {isSearch, inChannel, outChannel, teamMembers, matchTerm, groups} = props;
const sections = [];
@@ -204,16 +196,20 @@ export default class AtMention extends PureComponent {
}
onChangeText(completedDraft);
this.setState({mentionComplete: true});
this.setState({
sections: [],
});
};
renderSectionHeader = ({section}) => {
const isFirstSection = section.id === this.state.sections[0].id;
return (
<AutocompleteSectionHeader
id={section.id}
defaultMessage={section.defaultMessage}
theme={this.props.theme}
isLandscape={this.props.isLandscape}
isFirstSection={isFirstSection}
/>
);
};
@@ -221,6 +217,7 @@ export default class AtMention extends PureComponent {
renderItem = ({item}) => {
return (
<AtMentionItem
testID={`autocomplete.at_mention.item.${item}`}
onPress={this.completeMention}
userId={item}
/>
@@ -253,8 +250,8 @@ export default class AtMention extends PureComponent {
render() {
const {maxListHeight, theme, nestedScrollEnabled} = this.props;
const {mentionComplete, sections} = this.state;
if (sections.length === 0 || mentionComplete) {
const {sections} = this.state;
if (sections.length === 0) {
// If we are not in an active state or the mention has been completed return null so nothing is rendered
// other components are not blocked.
return null;
@@ -270,7 +267,6 @@ export default class AtMention extends PureComponent {
sections={sections}
renderItem={this.renderItem}
renderSectionHeader={this.renderSectionHeader}
ItemSeparatorComponent={AutocompleteDivider}
initialNumToRender={10}
nestedScrollEnabled={nestedScrollEnabled}
/>
@@ -282,6 +278,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
return {
listView: {
backgroundColor: theme.centerChannelBg,
borderRadius: 4,
},
};
});

View File

@@ -7,10 +7,10 @@ import {
Text,
View,
} from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
import CompassIcon from '@components/compass_icon';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
export default class GroupMentionItem extends PureComponent {
static propTypes = {
@@ -39,8 +39,8 @@ export default class GroupMentionItem extends PureComponent {
type={'opacity'}
>
<View style={style.rowPicture}>
<Icon
name='users'
<CompassIcon
name='account-group-outline'
style={style.rowIcon}
/>
</View>

View File

@@ -12,7 +12,7 @@ import ProfilePicture from 'app/components/profile_picture';
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
import {BotTag, GuestTag} from 'app/components/tag';
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
import FormattedText from 'app/components/formatted_text';
export default class AtMentionItem extends PureComponent {
@@ -28,6 +28,8 @@ export default class AtMentionItem extends PureComponent {
theme: PropTypes.object.isRequired,
isLandscape: PropTypes.bool.isRequired,
isCurrentUser: PropTypes.bool.isRequired,
showFullName: PropTypes.string,
testID: PropTypes.string,
};
static defaultProps = {
@@ -40,11 +42,24 @@ export default class AtMentionItem extends PureComponent {
onPress(username);
};
renderNameBlock = () => {
let name = '';
const {showFullName, firstName, lastName, nickname} = this.props;
const hasNickname = nickname.length > 0;
if (showFullName === 'true') {
name += `${firstName} ${lastName} `;
}
if (hasNickname) {
name += `(${nickname})`;
}
return name.trim();
}
render() {
const {
firstName,
lastName,
nickname,
userId,
username,
theme,
@@ -52,49 +67,59 @@ export default class AtMentionItem extends PureComponent {
isLandscape,
isGuest,
isCurrentUser,
testID,
} = this.props;
const style = getStyleFromTheme(theme);
const hasFullName = firstName.length > 0 && lastName.length > 0;
const hasNickname = nickname.length > 0;
const name = this.renderNameBlock();
return (
<TouchableWithFeedback
testID={testID}
key={userId}
onPress={this.completeMention}
style={[style.row, padding(isLandscape)]}
type={'opacity'}
style={padding(isLandscape)}
underlayColor={changeOpacity(theme.buttonBg, 0.08)}
type={'native'}
>
<View style={style.rowPicture}>
<ProfilePicture
userId={userId}
<View style={style.row}>
<View style={style.rowPicture}>
<ProfilePicture
userId={userId}
theme={theme}
size={24}
status={null}
showStatus={false}
/>
</View>
<BotTag
show={isBot}
theme={theme}
size={20}
status={null}
/>
<GuestTag
show={isGuest}
theme={theme}
/>
{Boolean(name.length) &&
<Text
style={style.rowFullname}
numberOfLines={1}
>
{name}
{isCurrentUser &&
<FormattedText
id='suggestion.mention.you'
defaultMessage='(you)'
/>}
</Text>
}
<Text
style={style.rowUsername}
numberOfLines={1}
>
{` @${username}`}
</Text>
</View>
<Text style={style.rowUsername}>{`@${username}`}</Text>
<BotTag
show={isBot}
theme={theme}
/>
<GuestTag
show={isGuest}
theme={theme}
/>
{hasFullName && <Text style={style.rowUsername}>{' - '}</Text>}
<Text
style={style.rowFullname}
numberOfLines={1}
>
{hasFullName && `${firstName} ${lastName}`}
{hasNickname && ` (${nickname}) `}
{isCurrentUser &&
<FormattedText
id='suggestion.mention.you'
defaultMessage='(you)'
/>}
</Text>
</TouchableWithFeedback>
);
}
@@ -103,24 +128,29 @@ export default class AtMentionItem extends PureComponent {
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
return {
row: {
height: 40,
paddingVertical: 8,
paddingTop: 4,
paddingHorizontal: 16,
flexDirection: 'row',
alignItems: 'center',
backgroundColor: theme.centerChannelBg,
},
rowPicture: {
marginHorizontal: 8,
width: 20,
marginRight: 10,
marginLeft: 2,
width: 24,
alignItems: 'center',
justifyContent: 'center',
},
rowUsername: {
fontSize: 13,
color: theme.centerChannelColor,
},
rowFullname: {
fontSize: 15,
color: theme.centerChannelColor,
opacity: 0.6,
paddingLeft: 4,
},
rowUsername: {
color: theme.centerChannelColor,
fontSize: 15,
opacity: 0.56,
flex: 1,
},
};

View File

@@ -6,6 +6,7 @@ import {connect} from 'react-redux';
import {getCurrentUserId, getUser} from '@mm-redux/selectors/entities/users';
import {getTheme} from '@mm-redux/selectors/entities/preferences';
import {getConfig} from '@mm-redux/selectors/entities/general';
import AtMentionItem from './at_mention_item';
@@ -14,12 +15,13 @@ import {isGuest} from 'app/utils/users';
function mapStateToProps(state, ownProps) {
const user = getUser(state, ownProps.userId);
const config = getConfig(state);
return {
firstName: user.first_name,
lastName: user.last_name,
nickname: user.nickname,
username: user.username,
showFullName: config.ShowFullName,
isBot: Boolean(user.is_bot),
isGuest: isGuest(user),
theme: getTheme(state),

View File

@@ -36,8 +36,9 @@ export default class Autocomplete extends PureComponent {
valueEvent: PropTypes.string,
cursorPositionEvent: PropTypes.string,
nestedScrollEnabled: PropTypes.bool,
expandDown: PropTypes.bool,
onVisible: PropTypes.func,
offsetY: PropTypes.number,
onKeyboardOffsetChanged: PropTypes.func,
style: ViewPropTypes.style,
};
@@ -47,6 +48,8 @@ export default class Autocomplete extends PureComponent {
enableDateSuggestion: false,
nestedScrollEnabled: false,
onVisible: emptyFunction,
onKeyboardOffsetChanged: emptyFunction,
offsetY: 80,
};
static getDerivedStateFromProps(props, state) {
@@ -149,10 +152,12 @@ export default class Autocomplete extends PureComponent {
keyboardDidShow = (e) => {
const {height} = e.endCoordinates;
this.setState({keyboardOffset: height});
this.props.onKeyboardOffsetChanged(height);
};
keyboardDidHide = () => {
this.setState({keyboardOffset: 0});
this.props.onKeyboardOffsetChanged(0);
};
maxListHeight() {
@@ -166,42 +171,37 @@ export default class Autocomplete extends PureComponent {
offset = 90;
}
maxHeight = this.props.deviceHeight - offset - this.state.keyboardOffset;
maxHeight = (this.props.deviceHeight / 2) - offset;
}
return maxHeight;
}
render() {
const {theme, isSearch, expandDown} = this.props;
const {atMentionCount, channelMentionCount, emojiCount, commandCount, dateCount, cursorPosition, value} = this.state;
const {theme, isSearch, offsetY} = this.props;
const style = getStyleFromTheme(theme);
const maxListHeight = this.maxListHeight();
const wrapperStyles = [];
const containerStyles = [];
const containerStyles = [style.borders];
if (Platform.OS === 'ios') {
wrapperStyles.push(style.shadow);
}
if (isSearch) {
wrapperStyles.push(style.base, style.searchContainer);
containerStyles.push(style.content);
wrapperStyles.push(style.base, style.searchContainer, {height: maxListHeight});
} else {
const containerStyle = expandDown ? style.containerExpandDown : style.container;
const containerStyle = {bottom: offsetY};
containerStyles.push(style.base, containerStyle);
}
// We always need to render something, but we only draw the borders when we have results to show
const {atMentionCount, channelMentionCount, emojiCount, commandCount, dateCount, cursorPosition, value} = this.state;
if (atMentionCount + channelMentionCount + emojiCount + commandCount + dateCount > 0) {
if (this.props.isSearch) {
wrapperStyles.push(style.bordersSearch);
} else {
containerStyles.push(style.borders);
}
// Hide when there are no active autocompletes
if (atMentionCount + channelMentionCount + emojiCount + commandCount + dateCount === 0) {
wrapperStyles.push(style.hidden);
containerStyles.push(style.hidden);
}
if (this.props.style) {
containerStyles.push(this.props.style);
}
const maxListHeight = this.maxListHeight();
return (
<View style={wrapperStyles}>
<View
@@ -261,39 +261,37 @@ export default class Autocomplete extends PureComponent {
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
return {
base: {
left: 0,
overflow: 'hidden',
left: 8,
position: 'absolute',
right: 0,
right: 8,
},
borders: {
borderWidth: 1,
borderColor: changeOpacity(theme.centerChannelColor, 0.2),
borderBottomWidth: 0,
overflow: 'hidden',
borderRadius: 4,
},
bordersSearch: {
borderWidth: 1,
borderColor: changeOpacity(theme.centerChannelColor, 0.2),
},
container: {
bottom: 0,
},
containerExpandDown: {
top: 0,
},
content: {
flex: 1,
hidden: {
display: 'none',
},
searchContainer: {
flex: 1,
...Platform.select({
android: {
top: 46,
top: 42,
},
ios: {
top: 44,
top: 55,
},
}),
},
shadow: {
shadowColor: '#000',
shadowOpacity: 0.12,
shadowRadius: 8,
shadowOffset: {
width: 0,
height: 8,
},
},
};
});

View File

@@ -1,32 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {View} from 'react-native';
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
export default class AutocompleteDivider extends PureComponent {
static propTypes = {
theme: PropTypes.object.isRequired,
};
render() {
const {theme} = this.props;
const style = getStyleFromTheme(theme);
return (
<View style={style.divider}/>
);
}
}
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
return {
divider: {
height: 1,
backgroundColor: changeOpacity(theme.centerChannelColor, 0.2),
},
};
});

View File

@@ -1,16 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {connect} from 'react-redux';
import {getTheme} from '@mm-redux/selectors/entities/preferences';
import AutocompleteDivider from './autocomplete_divider';
function mapStateToProps(state) {
return {
theme: getTheme(state),
};
}
export default connect(mapStateToProps)(AutocompleteDivider);

View File

@@ -16,6 +16,7 @@ export default class AutocompleteSectionHeader extends PureComponent {
loading: PropTypes.bool,
theme: PropTypes.object.isRequired,
isLandscape: PropTypes.bool.isRequired,
isFirstSection: PropTypes.bool,
};
static defaultProps = {
@@ -23,12 +24,17 @@ export default class AutocompleteSectionHeader extends PureComponent {
};
render() {
const {defaultMessage, id, loading, theme, isLandscape} = this.props;
const {defaultMessage, id, loading, theme, isLandscape, isFirstSection} = this.props;
const style = getStyleFromTheme(theme);
const sectionStyles = [style.section, padding(isLandscape)];
if (!isFirstSection) {
sectionStyles.push(style.borderTop);
}
return (
<View style={style.sectionWrapper}>
<View style={[style.section, padding(isLandscape)]}>
<View style={sectionStyles}>
<FormattedText
id={id}
defaultMessage={defaultMessage}
@@ -48,18 +54,24 @@ export default class AutocompleteSectionHeader extends PureComponent {
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
return {
section: {
justifyContent: 'center',
paddingHorizontal: 8,
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
borderTop: {
borderTopWidth: 1,
borderTopColor: changeOpacity(theme.centerChannelColor, 0.2),
},
section: {
justifyContent: 'center',
position: 'relative',
top: -1,
flexDirection: 'row',
},
sectionText: {
fontSize: 12,
color: changeOpacity(theme.centerChannelColor, 0.7),
paddingVertical: 7,
fontWeight: '600',
textTransform: 'uppercase',
color: changeOpacity(theme.centerChannelColor, 0.56),
paddingTop: 16,
paddingBottom: 8,
paddingHorizontal: 16,
flex: 1,
},
sectionWrapper: {

View File

@@ -91,12 +91,13 @@ export default class ChannelMention extends PureComponent {
myMembers !== this.props.myMembers)) {
const sections = [];
if (isSearch) {
if (directAndGroupMessages.length) {
if (publicChannels.length) {
sections.push({
id: t('suggestion.search.direct'),
defaultMessage: 'Direct Messages',
data: directAndGroupMessages,
key: 'directAndGroupMessages',
id: t('suggestion.search.public'),
defaultMessage: 'Public Channels',
data: publicChannels.filter((cId) => myMembers[cId]),
key: 'publicChannels',
hideLoadingIndicator: true,
});
}
@@ -110,13 +111,12 @@ export default class ChannelMention extends PureComponent {
});
}
if (publicChannels.length) {
if (directAndGroupMessages.length) {
sections.push({
id: t('suggestion.search.public'),
defaultMessage: 'Public Channels',
data: publicChannels.filter((cId) => myMembers[cId]),
key: 'publicChannels',
hideLoadingIndicator: true,
id: t('suggestion.search.direct'),
defaultMessage: 'Direct Messages',
data: directAndGroupMessages,
key: 'directAndGroupMessages',
});
}
} else {
@@ -184,6 +184,7 @@ export default class ChannelMention extends PureComponent {
};
renderSectionHeader = ({section}) => {
const isFirstSection = section.id === this.state.sections[0].id;
return (
<AutocompleteSectionHeader
id={section.id}
@@ -191,6 +192,7 @@ export default class ChannelMention extends PureComponent {
loading={!section.hideLoadingIndicator && this.props.requestStatus === RequestStatus.STARTED}
theme={this.props.theme}
isLandscape={this.props.isLandscape}
isFirstSection={isFirstSection}
/>
);
};
@@ -235,6 +237,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
return {
listView: {
backgroundColor: theme.centerChannelBg,
borderRadius: 4,
},
};
});

View File

@@ -5,14 +5,15 @@ import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
Text,
View,
} from 'react-native';
import {General} from '@mm-redux/constants';
import AutocompleteDivider from 'app/components/autocomplete/autocomplete_divider';
import {BotTag, GuestTag} from 'app/components/tag';
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
import CompassIcon from '@components/compass_icon';
import {BotTag, GuestTag} from '@components/tag';
import {paddingHorizontal as padding} from '@components/safe_area_view/iphone_x_spacing';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
export default class ChannelMentionItem extends PureComponent {
static propTypes = {
@@ -49,8 +50,12 @@ export default class ChannelMentionItem extends PureComponent {
} = this.props;
const style = getStyleFromTheme(theme);
let iconName = 'globe';
let component;
if (type === General.PRIVATE_CHANNEL) {
iconName = 'lock';
}
if (type === General.DM_CHANNEL || type === General.GM_CHANNEL) {
if (!displayName) {
return null;
@@ -79,11 +84,18 @@ export default class ChannelMentionItem extends PureComponent {
<TouchableWithFeedback
key={channelId}
onPress={this.completeMention}
style={[style.row, padding(isLandscape)]}
type={'opacity'}
style={padding(isLandscape)}
underlayColor={changeOpacity(theme.buttonBg, 0.08)}
type={'native'}
>
<Text style={style.rowDisplayName}>{displayName}</Text>
<Text style={style.rowName}>{` (~${name})`}</Text>
<View style={style.row}>
<CompassIcon
name={iconName}
style={style.icon}
/>
<Text style={style.rowDisplayName}>{displayName}</Text>
<Text style={style.rowName}>{` ~${name}`}</Text>
</View>
</TouchableWithFeedback>
);
}
@@ -91,7 +103,6 @@ export default class ChannelMentionItem extends PureComponent {
return (
<React.Fragment>
{component}
<AutocompleteDivider/>
</React.Fragment>
);
}
@@ -99,19 +110,26 @@ export default class ChannelMentionItem extends PureComponent {
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
return {
icon: {
fontSize: 18,
marginRight: 11,
color: theme.centerChannelColor,
opacity: 0.56,
},
row: {
padding: 8,
paddingHorizontal: 16,
height: 40,
flexDirection: 'row',
alignItems: 'center',
backgroundColor: theme.centerChannelBg,
},
rowDisplayName: {
fontSize: 13,
fontSize: 15,
color: theme.centerChannelColor,
},
rowName: {
fontSize: 15,
color: theme.centerChannelColor,
opacity: 0.6,
opacity: 0.56,
},
};
});

View File

@@ -2,7 +2,7 @@
// See LICENSE.txt for license information.
import React, {PureComponent} from 'react';
import {Dimensions, Platform, StyleSheet} from 'react-native';
import {Dimensions, Platform, View} from 'react-native';
import PropTypes from 'prop-types';
import {CalendarList, LocaleConfig} from 'react-native-calendars';
import {intlShape} from 'react-intl';
@@ -10,7 +10,7 @@ import {intlShape} from 'react-intl';
import {memoizeResult} from '@mm-redux/utils/helpers';
import {DATE_MENTION_SEARCH_REGEX, ALL_SEARCH_FLAGS_REGEX} from 'app/constants/autocomplete';
import {changeOpacity} from 'app/utils/theme';
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
export default class DateSuggestion extends PureComponent {
static propTypes = {
@@ -37,6 +37,7 @@ export default class DateSuggestion extends PureComponent {
this.state = {
mentionComplete: false,
active: false,
sections: [],
};
}
@@ -45,18 +46,37 @@ export default class DateSuggestion extends PureComponent {
this.setCalendarLocale();
}
onLayout = (e) => {
this.setState({calendarWidth: e.nativeEvent.layout.width});
};
componentDidUpdate(prevProps) {
const {locale, matchTerm} = this.props;
const {locale, matchTerm, enableDateSuggestion} = this.props;
const {mentionComplete} = this.state;
if ((matchTerm !== prevProps.matchTerm && matchTerm === null) || this.state.mentionComplete) {
this.resetComponent();
}
if (matchTerm === null || mentionComplete || !enableDateSuggestion) {
this.setCalendarActive(false);
return;
}
if (matchTerm !== null) {
this.props.onResultCountChange(1);
this.setCalendarActive(true);
}
if (locale !== prevProps.locale) {
this.setCalendarLocale();
}
}
setCalendarActive = (active) => {
this.setState({active});
}
completeMention = (day) => {
const mention = day.dateString;
const {cursorPosition, onChangeText, value} = this.props;
@@ -118,10 +138,11 @@ export default class DateSuggestion extends PureComponent {
};
render() {
const {mentionComplete} = this.state;
const {matchTerm, enableDateSuggestion, theme} = this.props;
const {active, calendarWidth} = this.state;
const {theme} = this.props;
const styles = getStyleFromTheme(theme);
if (matchTerm === null || mentionComplete || !enableDateSuggestion) {
if (!active) {
// If we are not in an active state or the mention has been completed return null so nothing is rendered
// other components are not blocked.
return null;
@@ -131,22 +152,29 @@ export default class DateSuggestion extends PureComponent {
const calendarStyle = calendarTheme(theme);
return (
<CalendarList
<View
onLayout={this.onLayout}
style={styles.calList}
current={currentDate}
maxDate={currentDate}
pastScrollRange={24}
futureScrollRange={0}
scrollingEnabled={true}
pagingEnabled={true}
hideArrows={false}
horizontal={true}
showScrollIndicator={true}
onDayPress={this.completeMention}
showWeekNumbers={false}
theme={calendarStyle}
keyboardShouldPersistTaps='always'
/>
>
{Boolean(calendarWidth) &&
<CalendarList
current={currentDate}
maxDate={currentDate}
pastScrollRange={24}
futureScrollRange={0}
scrollingEnabled={true}
calendarWidth={calendarWidth}
pagingEnabled={true}
hideArrows={false}
horizontal={true}
showScrollIndicator={true}
onDayPress={this.completeMention}
showWeekNumbers={false}
theme={calendarStyle}
keyboardShouldPersistTaps='always'
/>
}
</View>
);
}
}
@@ -197,9 +225,13 @@ const calendarTheme = memoizeResult((theme) => ({
},
}));
const styles = StyleSheet.create({
calList: {
height: 1700,
paddingTop: 5,
},
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
return {
calList: {
paddingTop: 5,
width: '100%',
borderRadius: 4,
backgroundColor: theme.centerChannelBg,
},
};
});

View File

@@ -4,7 +4,6 @@ exports[`components/autocomplete/emoji_suggestion should match snapshot 1`] = `n
exports[`components/autocomplete/emoji_suggestion should match snapshot 2`] = `
<FlatList
ItemSeparatorComponent={[Function]}
data={
Array [
"+1",
@@ -3044,6 +3043,8 @@ exports[`components/autocomplete/emoji_suggestion should match snapshot 2`] = `
Array [
Object {
"backgroundColor": "#ffffff",
"borderRadius": 4,
"paddingTop": 16,
},
Object {
"maxHeight": undefined,

View File

@@ -11,12 +11,11 @@ import {
} from 'react-native';
import Fuse from 'fuse.js';
import AutocompleteDivider from '@components/autocomplete/autocomplete_divider';
import Emoji from '@components/emoji';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {BuiltInEmojis} from '@utils/emojis';
import {getEmojiByName, compareEmojis} from '@utils/emoji_utils';
import {makeStyleSheetFromTheme} from '@utils/theme';
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
const EMOJI_REGEX = /(^|\s|^\+|^-)(:([^:\s]*))$/i;
const EMOJI_REGEX_WITHOUT_PREFIX = /\B(:([^:\s]*))$/i;
@@ -150,21 +149,23 @@ export default class EmojiSuggestion extends PureComponent {
renderItem = ({item}) => {
const style = getStyleFromTheme(this.props.theme);
const completeSuggestion = () => this.completeSuggestion(item);
return (
<TouchableWithFeedback
onPress={() => this.completeSuggestion(item)}
style={style.row}
type={'opacity'}
onPress={completeSuggestion}
underlayColor={changeOpacity(this.props.theme.buttonBg, 0.08)}
type={'native'}
>
<View style={style.emoji}>
<Emoji
emojiName={item}
textStyle={style.emojiText}
size={20}
/>
<View style={style.row}>
<View style={style.emoji}>
<Emoji
emojiName={item}
textStyle={style.emojiText}
size={24}
/>
</View>
<Text style={style.emojiName}>{`:${item}:`}</Text>
</View>
<Text style={style.emojiName}>{`:${item}:`}</Text>
</TouchableWithFeedback>
);
};
@@ -199,12 +200,14 @@ export default class EmojiSuggestion extends PureComponent {
return values;
}, []);
const data = results.sort(sorter);
this.props.onResultCountChange(data.length);
this.setState({
active: data.length > 0,
dataSource: data,
});
}, 100);
} else {
this.props.onResultCountChange(emojis.length);
this.setState({
active: emojis.length > 0,
dataSource: emojis.sort(sorter),
@@ -229,7 +232,6 @@ export default class EmojiSuggestion extends PureComponent {
data={this.state.dataSource}
keyExtractor={this.keyExtractor}
renderItem={this.renderItem}
ItemSeparatorComponent={AutocompleteDivider}
pageSize={10}
initialListSize={10}
nestedScrollEnabled={nestedScrollEnabled}
@@ -244,7 +246,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
marginRight: 5,
},
emojiName: {
fontSize: 13,
fontSize: 15,
color: theme.centerChannelColor,
},
emojiText: {
@@ -252,14 +254,17 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
fontWeight: 'bold',
},
listView: {
paddingTop: 16,
backgroundColor: theme.centerChannelBg,
borderRadius: 4,
},
row: {
height: 40,
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 8,
backgroundColor: theme.centerChannelBg,
overflow: 'hidden',
paddingBottom: 8,
paddingHorizontal: 16,
height: 40,
},
};
});

View File

@@ -8,7 +8,6 @@ import {
Platform,
} from 'react-native';
import AutocompleteDivider from 'app/components/autocomplete/autocomplete_divider';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
import SlashSuggestionItem from './slash_suggestion_item';
@@ -211,7 +210,6 @@ export default class SlashSuggestion extends PureComponent {
data={this.state.dataSource}
keyExtractor={this.keyExtractor}
renderItem={this.renderItem}
ItemSeparatorComponent={AutocompleteDivider}
pageSize={10}
initialListSize={10}
nestedScrollEnabled={nestedScrollEnabled}
@@ -225,6 +223,8 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
listView: {
flex: 1,
backgroundColor: theme.centerChannelBg,
paddingTop: 8,
borderRadius: 4,
},
};
});

View File

@@ -3,11 +3,12 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {Text} from 'react-native';
import {Image, Text, View} from 'react-native';
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import {paddingHorizontal as padding} from '@components/safe_area_view/iphone_x_spacing';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import slashIcon from '@assets/images/autocomplete/slash_command.png';
export default class SlashSuggestionItem extends PureComponent {
static propTypes = {
@@ -31,19 +32,44 @@ export default class SlashSuggestionItem extends PureComponent {
hint,
theme,
suggestion,
complete,
isLandscape,
} = this.props;
const style = getStyleFromTheme(theme);
let suggestionText = suggestion;
if (suggestionText[0] === '/' && complete.split(' ').length === 1) {
suggestionText = suggestionText.substring(1);
}
return (
<TouchableWithFeedback
onPress={this.completeSuggestion}
style={[style.row, padding(isLandscape)]}
type={'opacity'}
style={padding(isLandscape)}
underlayColor={changeOpacity(theme.buttonBg, 0.08)}
type={'native'}
>
<Text style={style.suggestionName}>{`${suggestion} ${hint}`}</Text>
<Text style={style.suggestionDescription}>{description}</Text>
<View style={style.container}>
<View style={style.icon}>
<Image
style={style.iconColor}
width={10}
height={16}
source={slashIcon}
/>
</View>
<View style={style.suggestionContainer}>
<Text style={style.suggestionName}>{`${suggestionText} ${hint}`}</Text>
<Text
ellipsizeMode='tail'
numberOfLines={1}
style={style.suggestionDescription}
>
{description}
</Text>
</View>
</View>
</TouchableWithFeedback>
);
}
@@ -51,32 +77,38 @@ export default class SlashSuggestionItem extends PureComponent {
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
return {
row: {
paddingVertical: 8,
icon: {
fontSize: 24,
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
width: 35,
height: 35,
marginRight: 12,
borderRadius: 4,
justifyContent: 'center',
paddingHorizontal: 8,
backgroundColor: theme.centerChannelBg,
borderLeftWidth: 1,
borderLeftColor: changeOpacity(theme.centerChannelColor, 0.2),
borderRightWidth: 1,
borderRightColor: changeOpacity(theme.centerChannelColor, 0.2),
alignItems: 'center',
marginTop: 8,
},
rowDisplayName: {
fontSize: 13,
color: theme.centerChannelColor,
iconColor: {
tintColor: theme.centerChannelColor,
},
rowName: {
color: theme.centerChannelColor,
opacity: 0.6,
container: {
flexDirection: 'row',
alignItems: 'center',
paddingBottom: 8,
paddingHorizontal: 16,
overflow: 'hidden',
},
suggestionContainer: {
flex: 1,
},
suggestionDescription: {
fontSize: 11,
color: changeOpacity(theme.centerChannelColor, 0.6),
fontSize: 12,
color: changeOpacity(theme.centerChannelColor, 0.56),
},
suggestionName: {
fontSize: 13,
fontSize: 15,
color: theme.centerChannelColor,
marginBottom: 5,
marginBottom: 4,
},
};
});

View File

@@ -7,11 +7,11 @@ import {
Text,
View,
} from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import FormattedText from 'app/components/formatted_text';
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
import CompassIcon from '@components/compass_icon';
import FormattedText from '@components/formatted_text';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
export default class SpecialMentionItem extends PureComponent {
static propTypes = {
@@ -42,25 +42,27 @@ export default class SpecialMentionItem extends PureComponent {
return (
<TouchableWithFeedback
onPress={this.completeMention}
style={style.row}
type={'opacity'}
underlayColor={changeOpacity(theme.buttonBg, 0.08)}
type={'native'}
>
<View style={style.rowPicture}>
<Icon
name='users'
style={style.rowIcon}
/>
<View style={style.row}>
<View style={style.rowPicture}>
<CompassIcon
name='account-group-outline'
style={style.rowIcon}
/>
</View>
<Text style={style.textWrapper}>
<Text style={style.rowUsername}>{`@${completeHandle}`}</Text>
<Text style={style.rowUsername}>{' - '}</Text>
<FormattedText
id={id}
defaultMessage={defaultMessage}
values={values}
style={style.rowFullname}
/>
</Text>
</View>
<Text style={style.textWrapper}>
<Text style={style.rowUsername}>{`@${completeHandle}`}</Text>
<Text style={style.rowUsername}>{' - '}</Text>
<FormattedText
id={id}
defaultMessage={defaultMessage}
values={values}
style={style.rowFullname}
/>
</Text>
</TouchableWithFeedback>
);
}
@@ -68,10 +70,11 @@ export default class SpecialMentionItem extends PureComponent {
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
return {
row: {
height: 40,
paddingVertical: 8,
paddingHorizontal: 9,
flexDirection: 'row',
alignItems: 'center',
backgroundColor: theme.centerChannelBg,
},
rowPicture: {
marginHorizontal: 8,
@@ -81,10 +84,10 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
},
rowIcon: {
color: changeOpacity(theme.centerChannelColor, 0.7),
fontSize: 14,
fontSize: 18,
},
rowUsername: {
fontSize: 13,
fontSize: 15,
color: theme.centerChannelColor,
},
rowFullname: {

View File

@@ -5,17 +5,17 @@ import React, {PureComponent} from 'react';
import {Text, View} from 'react-native';
import PropTypes from 'prop-types';
import {intlShape} from 'react-intl';
import Icon from 'react-native-vector-icons/FontAwesome';
import {displayUsername} from '@mm-redux/utils/user_utils';
import FormattedText from 'app/components/formatted_text';
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
import {preventDoubleTap} from 'app/utils/tap';
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
import {ViewTypes} from 'app/constants';
import {goToScreen} from 'app/actions/navigation';
import CompassIcon from '@components/compass_icon';
import FormattedText from '@components/formatted_text';
import {paddingHorizontal as padding} from '@components/safe_area_view/iphone_x_spacing';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {preventDoubleTap} from '@utils/tap';
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
import {ViewTypes} from '@constants';
import {goToScreen} from '@actions/navigation';
export default class AutocompleteSelector extends PureComponent {
static propTypes = {
@@ -202,7 +202,7 @@ export default class AutocompleteSelector extends PureComponent {
>
{text}
</Text>
<Icon
<CompassIcon
name='chevron-down'
color={changeOpacity(theme.centerChannelColor, 0.5)}
style={[style.icon, padding(isLandscape)]}

View File

@@ -10,9 +10,8 @@ import {
import {General} from '@mm-redux/constants';
import Icon from 'app/components/vector_icon';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import CompassIcon from '@components/compass_icon';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
export default class ChannelIcon extends React.PureComponent {
static propTypes = {
@@ -82,48 +81,52 @@ export default class ChannelIcon extends React.PureComponent {
let icon;
if (isArchived) {
icon = (
<Icon
name='archive'
<CompassIcon
name='archive-outline'
style={[style.icon, unreadIcon, activeIcon, {fontSize: size}]}
type='mattermost'
testID='channel_icon.archive'
/>
);
} else if (isBot) {
icon = (
<Icon
name='bot'
style={[style.icon, unreadIcon, activeIcon, {fontSize: (size - 1), left: -1.5, top: -1}]}
type='mattermost'
<CompassIcon
name='robot-happy'
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, left: -1.5, top: -1}]}
testID='channel_icon.bot'
/>
);
} else if (hasDraft) {
icon = (
<Icon
name='draft'
<CompassIcon
name='pencil-outline'
style={[style.icon, unreadIcon, activeIcon, {fontSize: size}]}
type='mattermost'
testID='channel_icon.draft'
/>
);
} else if (type === General.OPEN_CHANNEL) {
icon = (
<Icon
name='public'
<CompassIcon
name='globe'
style={[style.icon, unreadIcon, activeIcon, {fontSize: size}]}
type='mattermost'
testID='channel_icon.public'
/>
);
} else if (type === General.PRIVATE_CHANNEL) {
icon = (
<Icon
name='private'
<CompassIcon
name='lock-outline'
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, left: 0.5}]}
type='mattermost'
testID='channel_icon.private'
/>
);
} else if (type === General.GM_CHANNEL) {
const fontSize = (size - 10);
icon = (
<View style={[style.groupBox, unreadGroupBox, activeGroupBox, {width: size + 1, height: size + 1}]}>
<Text style={[style.group, unreadGroup, activeGroup, {fontSize: (size - 4)}]}>
<View style={[style.groupBox, unreadGroupBox, activeGroupBox, {width: size, height: size}]}>
<Text
style={[style.group, unreadGroup, activeGroup, {fontSize}]}
testID='channel_icon.gm_member_count'
>
{membersCount}
</Text>
</View>
@@ -132,37 +135,37 @@ export default class ChannelIcon extends React.PureComponent {
switch (status) {
case General.AWAY:
icon = (
<Icon
name='away-avatar'
<CompassIcon
name='clock'
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, color: theme.awayIndicator}]}
type='mattermost'
testID='channel_icon.away'
/>
);
break;
case General.DND:
icon = (
<Icon
name='dnd-avatar'
<CompassIcon
name='minus-circle'
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, color: theme.dndIndicator}]}
type='mattermost'
testID='channel_icon.dnd'
/>
);
break;
case General.ONLINE:
icon = (
<Icon
name='online-avatar'
<CompassIcon
name='check-circle'
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, color: theme.onlineIndicator}]}
type='mattermost'
testID='channel_icon.online'
/>
);
break;
default:
icon = (
<Icon
name='offline-avatar'
<CompassIcon
name='circle-outline'
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, color: offlineColor}]}
type='mattermost'
testID='channel_icon.offline'
/>
);
break;
@@ -180,7 +183,7 @@ export default class ChannelIcon extends React.PureComponent {
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
container: {
marginRight: 12,
marginRight: 8,
alignItems: 'center',
},
icon: {

View File

@@ -8,20 +8,20 @@ import {
View,
} from 'react-native';
import {injectIntl, intlShape} from 'react-intl';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
import {displayUsername} from '@mm-redux/utils/user_utils';
import {General} from '@mm-redux/constants';
import {showModal} from 'app/actions/navigation';
import ProfilePicture from 'app/components/profile_picture';
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
import {BotTag, GuestTag} from 'app/components/tag';
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
import {preventDoubleTap} from 'app/utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import {t} from 'app/utils/i18n';
import {isGuest} from 'app/utils/users';
import {showModal} from '@actions/navigation';
import CompassIcon from '@components/compass_icon';
import ProfilePicture from '@components/profile_picture';
import {paddingHorizontal as padding} from '@components/safe_area_view/iphone_x_spacing';
import {BotTag, GuestTag} from '@components/tag';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {preventDoubleTap} from '@utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {t} from '@utils/i18n';
import {isGuest} from '@utils/users';
class ChannelIntro extends PureComponent {
static propTypes = {
@@ -47,7 +47,7 @@ class ChannelIntro extends PureComponent {
};
if (!this.closeButton) {
this.closeButton = await MaterialIcon.getImageSource('close', 20, theme.sidebarHeaderTextColor);
this.closeButton = await CompassIcon.getImageSource('close', 24, theme.sidebarHeaderTextColor);
}
const options = {
@@ -91,6 +91,7 @@ class ChannelIntro extends PureComponent {
<ProfilePicture
userId={member.id}
size={64}
iconSize={48}
statusBorderWidth={2}
statusSize={25}
/>

View File

@@ -1,29 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import Svg, {Path} from 'react-native-svg';
export default class CheckMark extends PureComponent {
static propTypes = {
width: PropTypes.number.isRequired,
height: PropTypes.number.isRequired,
color: PropTypes.string.isRequired,
};
render() {
return (
<Svg
width={this.props.width}
height={this.props.height}
viewBox='0 0 12 12'
>
<Path
d='M11.667 1.756l-0.775-0.624c-0.382-0.307-0.604-0.304-0.932 0.101l-5.636 6.955 -2.623-2.179c-0.362-0.304-0.588-0.288-0.886 0.084l-0.598 0.779c-0.304 0.382-0.265 0.599 0.094 0.899l3.738 3.092c0.385 0.323 0.602 0.291 0.899-0.071l6.817-8.104c0.32-0.385 0.3-0.615-0.098-0.932Z'
fill={this.props.color}
/>
</Svg>
);
}
}

View File

@@ -11,13 +11,13 @@ import {
View,
} from 'react-native';
import {intlShape} from 'react-intl';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
import FormattedText from 'app/components/formatted_text';
import {DeviceTypes} from 'app/constants';
import {checkUpgradeType, isUpgradeAvailable} from 'app/utils/client_upgrade';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import {showModal, dismissModal} from 'app/actions/navigation';
import CompassIcon from '@components/compass_icon';
import FormattedText from '@components/formatted_text';
import {DeviceTypes} from '@constants';
import {checkUpgradeType, isUpgradeAvailable} from '@utils/client_upgrade';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {showModal, dismissModal} from '@actions/navigation';
const {View: AnimatedView} = Animated;
@@ -46,7 +46,7 @@ export default class ClientUpgradeListener extends PureComponent {
constructor(props) {
super(props);
MaterialIcon.getImageSource('close', 20, this.props.theme.sidebarHeaderTextColor).then((source) => {
CompassIcon.getImageSource('close', 24, this.props.theme.sidebarHeaderTextColor).then((source) => {
this.closeButton = source;
});
@@ -117,11 +117,7 @@ export default class ClientUpgradeListener extends PureComponent {
const {downloadLink} = this.props;
const {intl} = this.context;
Linking.canOpenURL(downloadLink).then((supported) => {
if (supported) {
return Linking.openURL(downloadLink);
}
Linking.openURL(downloadLink).catch(() => {
Alert.alert(
intl.formatMessage({
id: 'mobile.client_upgrade.download_error.title',
@@ -132,8 +128,6 @@ export default class ClientUpgradeListener extends PureComponent {
defaultMessage: 'An error occurred while trying to open the download link.',
}),
);
return false;
});
this.toggleUpgradeMessage(false);

View File

@@ -0,0 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {createIconSetFromFontello} from 'react-native-vector-icons';
import fontelloConfig from '@assets/compass-icons.json';
export default createIconSetFromFontello(fontelloConfig, 'compass-icons', 'compass-icons.ttf');

View File

@@ -7,10 +7,10 @@ import {
Text,
View,
} from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
import CustomListRow from 'app/components/custom_list/custom_list_row';
import CompassIcon from '@components/compass_icon';
import CustomListRow from '@components/custom_list/custom_list_row';
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
export default class ChannelListRow extends React.PureComponent {
static propTypes = {
@@ -53,8 +53,8 @@ export default class ChannelListRow extends React.PureComponent {
>
<View style={style.container}>
<View style={style.titleContainer}>
<Icon
name={this.props.isArchived ? 'archive' : 'globe'}
<CompassIcon
name={this.props.isArchived ? 'archive-outline' : 'globe'}
style={style.icon}
/>
<Text style={style.displayName}>

View File

@@ -7,10 +7,11 @@ import {
StyleSheet,
View,
} from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import {paddingLeft as padding} from 'app/components/safe_area_view/iphone_x_spacing';
import ConditionalTouchable from 'app/components/conditional_touchable';
import CustomPropTypes from 'app/constants/custom_prop_types';
import CompassIcon from '@components/compass_icon';
import {paddingLeft as padding} from '@components/safe_area_view/iphone_x_spacing';
import ConditionalTouchable from '@components/conditional_touchable';
import CustomPropTypes from '@constants/custom_prop_types';
export default class CustomListRow extends React.PureComponent {
static propTypes = {
@@ -21,6 +22,7 @@ export default class CustomListRow extends React.PureComponent {
children: CustomPropTypes.Children,
item: PropTypes.object,
isLandscape: PropTypes.bool.isRequired,
testID: PropTypes.string,
};
static defaultProps = {
@@ -34,15 +36,16 @@ export default class CustomListRow extends React.PureComponent {
touchable={Boolean(this.props.enabled && this.props.onPress)}
onPress={this.props.onPress}
style={style.touchable}
testID={this.props.testID}
>
<View style={[style.container, padding(this.props.isLandscape)]}>
{this.props.selectable &&
<View style={style.selectorContainer}>
<View style={[style.selector, (this.props.selected && style.selectorFilled), (!this.props.enabled && style.selectorDisabled)]}>
{this.props.selected &&
<Icon
<CompassIcon
name='check'
size={16}
size={24}
color='#fff'
/>
}
@@ -92,7 +95,7 @@ const style = StyleSheet.create({
backgroundColor: '#888',
},
selectorFilled: {
backgroundColor: '#378FD2',
backgroundColor: '#166DE0',
borderWidth: 0,
},
});

View File

@@ -33,6 +33,7 @@ export default class CustomList extends PureComponent {
theme: PropTypes.object.isRequired,
shouldRenderSeparator: PropTypes.bool,
isLandscape: PropTypes.bool.isRequired,
testID: PropTypes.string,
};
static defaultProps = {
@@ -148,6 +149,7 @@ export default class CustomList extends PureComponent {
scrollEventThrottle={60}
style={style.list}
stickySectionHeadersEnabled={true}
testID={this.props.testID}
/>
);
};
@@ -201,6 +203,7 @@ export default class CustomList extends PureComponent {
sections={data}
style={style.list}
stickySectionHeadersEnabled={false}
testID={this.props.testID}
/>
);
};

View File

@@ -27,6 +27,7 @@ exports[`UserListRow should match snapshot 1`] = `
}
>
<Connect(ProfilePicture)
iconSize={24}
size={32}
userId="21345"
/>
@@ -159,6 +160,7 @@ exports[`UserListRow should match snapshot for currentUser with (you) populated
}
>
<Connect(ProfilePicture)
iconSize={24}
size={32}
userId="21345"
/>
@@ -289,6 +291,7 @@ exports[`UserListRow should match snapshot for deactivated user 1`] = `
}
>
<Connect(ProfilePicture)
iconSize={24}
size={32}
userId="21345"
/>
@@ -432,6 +435,7 @@ exports[`UserListRow should match snapshot for guest user 1`] = `
}
>
<Connect(ProfilePicture)
iconSize={24}
size={32}
userId="21345"
/>

View File

@@ -24,6 +24,7 @@ export default class UserListRow extends React.PureComponent {
theme: PropTypes.object.isRequired,
user: PropTypes.object.isRequired,
teammateNameDisplay: PropTypes.string.isRequired,
testID: PropTypes.string,
...CustomListRow.propTypes,
};
@@ -73,11 +74,13 @@ export default class UserListRow extends React.PureComponent {
selectable={selectable}
selected={selected}
isLandscape={isLandscape}
testID={this.props.testID}
>
<View style={style.profileContainer}>
<ProfilePicture
userId={id}
size={32}
iconSize={24}
/>
</View>
<View style={style.textContainer}>

View File

@@ -4,10 +4,11 @@
import React, {PureComponent} from 'react';
import {View} from 'react-native';
import PropTypes from 'prop-types';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import FormattedText from 'app/components/formatted_text';
import AppIcon from 'app/components/app_icon';
import {ViewTypes} from 'app/constants';
import CompassIcon from '@components/compass_icon';
import FormattedText from '@components/formatted_text';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {ViewTypes} from '@constants';
class DeletedPost extends PureComponent {
static propTypes = {
@@ -20,10 +21,10 @@ class DeletedPost extends PureComponent {
return (
<View style={style.main}>
<View style={style.iconContainer}>
<AppIcon
<CompassIcon
name='mattermost'
color={theme.centerChannelColor}
height={ViewTypes.PROFILE_PICTURE_SIZE}
width={ViewTypes.PROFILE_PICTURE_SIZE}
size={ViewTypes.PROFILE_PICTURE_SIZE}
/>
</View>
<View style={style.textContainer}>

View File

@@ -309,18 +309,28 @@ exports[`EditChannelInfo should match snapshot 1`] = `
</View>
</TouchableWithoutFeedback>
</KeyboardAwareScrollView>
<KeyboardTrackingView
<View
style={
Object {
"justifyContent": "flex-end",
}
Array [
Object {
"flex": 1,
"justifyContent": "flex-end",
"position": "absolute",
"width": "100%",
},
Object {
"bottom": 0,
},
]
}
>
<Connect(Autocomplete)
cursorPosition={6}
maxHeight={200}
nestedScrollEnabled={true}
offsetY={8}
onChangeText={[Function]}
onKeyboardOffsetChanged={[Function]}
style={
Object {
"position": undefined,
@@ -328,6 +338,6 @@ exports[`EditChannelInfo should match snapshot 1`] = `
}
value="header"
/>
</KeyboardTrackingView>
</View>
</React.Fragment>
`;

View File

@@ -9,11 +9,10 @@ import {
View,
} from 'react-native';
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scrollview';
import {KeyboardTrackingView} from 'react-native-keyboard-tracking-view';
import {General} from '@mm-redux/constants';
import Autocomplete from 'app/components/autocomplete';
import Autocomplete, {AUTOCOMPLETE_MAX_HEIGHT} from 'app/components/autocomplete';
import ErrorText from 'app/components/error_text';
import FormattedText from 'app/components/formatted_text';
import Loading from 'app/components/loading';
@@ -71,6 +70,7 @@ export default class EditChannelInfo extends PureComponent {
this.state = {
keyboardVisible: false,
keyboardPosition: 0,
};
}
@@ -174,6 +174,10 @@ export default class EditChannelInfo extends PureComponent {
this.setState({keyboardVisible: false});
}
onKeyboardOffsetChanged = (keyboardPosition) => {
this.setState({keyboardPosition});
}
onHeaderFocus = () => {
if (this.state.keyboardVisible) {
this.scrollHeaderToTop();
@@ -201,8 +205,13 @@ export default class EditChannelInfo extends PureComponent {
error,
saving,
} = this.props;
const {keyboardVisible} = this.state;
const {keyboardVisible, keyboardPosition} = this.state;
const bottomStyle = {
bottom: Platform.select({
ios: keyboardPosition,
android: 0,
}),
};
const style = getStyleSheet(theme);
const displayHeaderOnly = channelType === General.DM_CHANNEL ||
@@ -354,16 +363,18 @@ export default class EditChannelInfo extends PureComponent {
</View>
</TouchableWithoutFeedback>
</KeyboardAwareScrollView>
<KeyboardTrackingView style={style.autocompleteContainer}>
<View style={[style.autocompleteContainer, bottomStyle]}>
<Autocomplete
cursorPosition={header.length}
maxHeight={200}
maxHeight={AUTOCOMPLETE_MAX_HEIGHT}
onChangeText={this.onHeaderChangeText}
value={header}
nestedScrollEnabled={true}
onKeyboardOffsetChanged={this.onKeyboardOffsetChanged}
offsetY={8}
style={style.autocomplete}
/>
</KeyboardTrackingView>
</View>
</React.Fragment>
);
}
@@ -375,6 +386,9 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
position: undefined,
},
autocompleteContainer: {
position: 'absolute',
width: '100%',
flex: 1,
justifyContent: 'flex-end',
},
container: {

View File

@@ -2098,7 +2098,7 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
},
],
"defaultMessage": "PEOPLE",
"icon": "smile-o",
"icon": "emoticon-happy-outline",
"id": "mobile.emoji_picker.people",
"key": "people",
},
@@ -3182,7 +3182,7 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
},
],
"defaultMessage": "NATURE",
"icon": "leaf",
"icon": "leaf-outline",
"id": "mobile.emoji_picker.nature",
"key": "nature",
},
@@ -3778,7 +3778,7 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
},
],
"defaultMessage": "FOODS",
"icon": "cutlery",
"icon": "food-apple",
"id": "mobile.emoji_picker.foods",
"key": "foods",
},
@@ -4335,7 +4335,7 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
},
],
"defaultMessage": "ACTIVITY",
"icon": "futbol-o",
"icon": "basketball",
"id": "mobile.emoji_picker.activity",
"key": "activity",
},
@@ -5144,7 +5144,7 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
},
],
"defaultMessage": "PLACES",
"icon": "plane",
"icon": "airplane-variant",
"id": "mobile.emoji_picker.places",
"key": "places",
},
@@ -6323,7 +6323,7 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
},
],
"defaultMessage": "OBJECTS",
"icon": "lightbulb-o",
"icon": "lightbulb-outline",
"id": "mobile.emoji_picker.objects",
"key": "objects",
},
@@ -8155,7 +8155,7 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
},
],
"defaultMessage": "SYMBOLS",
"icon": "heart-o",
"icon": "heart-outline",
"id": "mobile.emoji_picker.symbols",
"key": "symbols",
},
@@ -9874,7 +9874,7 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
},
],
"defaultMessage": "FLAGS",
"icon": "flag-o",
"icon": "flag-outline",
"id": "mobile.emoji_picker.flags",
"key": "flags",
},
@@ -9952,7 +9952,7 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
},
],
"defaultMessage": "CUSTOM",
"icon": "at",
"icon": "emoticon-custom-outline",
"id": "mobile.emoji_picker.custom",
"key": "custom",
},
@@ -10008,9 +10008,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
}
}
>
<Icon
allowFontScaling={false}
name="smile-o"
<CompassIcon
name="emoticon-happy-outline"
size={17}
style={
Array [
@@ -10035,9 +10034,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
}
}
>
<Icon
allowFontScaling={false}
name="leaf"
<CompassIcon
name="leaf-outline"
size={17}
style={
Array [
@@ -10060,9 +10058,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
}
}
>
<Icon
allowFontScaling={false}
name="cutlery"
<CompassIcon
name="food-apple"
size={17}
style={
Array [
@@ -10085,9 +10082,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
}
}
>
<Icon
allowFontScaling={false}
name="futbol-o"
<CompassIcon
name="basketball"
size={17}
style={
Array [
@@ -10110,9 +10106,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
}
}
>
<Icon
allowFontScaling={false}
name="plane"
<CompassIcon
name="airplane-variant"
size={17}
style={
Array [
@@ -10135,9 +10130,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
}
}
>
<Icon
allowFontScaling={false}
name="lightbulb-o"
<CompassIcon
name="lightbulb-outline"
size={17}
style={
Array [
@@ -10160,9 +10154,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
}
}
>
<Icon
allowFontScaling={false}
name="heart-o"
<CompassIcon
name="heart-outline"
size={17}
style={
Array [
@@ -10185,9 +10178,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
}
}
>
<Icon
allowFontScaling={false}
name="flag-o"
<CompassIcon
name="flag-outline"
size={17}
style={
Array [
@@ -10210,9 +10202,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
}
}
>
<Icon
allowFontScaling={false}
name="at"
<CompassIcon
name="emoticon-custom-outline"
size={17}
style={
Array [

View File

@@ -12,6 +12,8 @@ import {shallowWithIntl} from 'test/intl-test-helper';
import {filterEmojiSearchInput} from './emoji_picker_base';
import EmojiPicker from './emoji_picker.ios';
jest.useFakeTimers();
describe('components/emoji_picker/emoji_picker.ios', () => {
const state = {
...initialState,
@@ -59,17 +61,17 @@ describe('components/emoji_picker/emoji_picker.ios', () => {
];
testCases.forEach((testCase) => {
test(`'${testCase.input}' should return '${testCase.output}'`, () => {
test(`'${testCase.input}' should return '${testCase.output}'`, async () => {
expect(filterEmojiSearchInput(testCase.input)).toEqual(testCase.output);
});
});
test('should match snapshot', () => {
test('should match snapshot', async () => {
const wrapper = shallowWithIntl(<EmojiPicker {...baseProps}/>);
expect(wrapper.getElement()).toMatchSnapshot();
});
test('searchEmojis should return the right values on fuse', () => {
test('searchEmojis should return the right values on fuse', async () => {
const input = '1';
const output = ['100', '1234', '1st_place_medal', '+1', '-1', 'u7121'];
@@ -78,7 +80,7 @@ describe('components/emoji_picker/emoji_picker.ios', () => {
expect(result).toEqual(output);
});
test('should set rebuildEmojis to true when deviceWidth changes', () => {
test('should set rebuildEmojis to true when deviceWidth changes', async () => {
const wrapper = shallowWithIntl(<EmojiPicker {...baseProps}/>);
const instance = wrapper.instance();
@@ -90,7 +92,7 @@ describe('components/emoji_picker/emoji_picker.ios', () => {
expect(instance.rebuildEmojis).toBe(true);
});
test('should rebuild emojis emojis when emojis change', () => {
test('should rebuild emojis emojis when emojis change', async () => {
const wrapper = shallowWithIntl(<EmojiPicker {...baseProps}/>);
const instance = wrapper.instance();
const renderableEmojis = jest.spyOn(instance, 'renderableEmojis');
@@ -103,7 +105,7 @@ describe('components/emoji_picker/emoji_picker.ios', () => {
expect(renderableEmojis).toHaveBeenCalledWith(baseProps.emojisBySection, baseProps.deviceWidth);
});
test('should set rebuilt emojis when rebuildEmojis is true and searchBarAnimationComplete is true', () => {
test('should set rebuilt emojis when rebuildEmojis is true and searchBarAnimationComplete is true', async () => {
const wrapper = shallowWithIntl(<EmojiPicker {...baseProps}/>);
const instance = wrapper.instance();
instance.setState = jest.fn();
@@ -118,7 +120,7 @@ describe('components/emoji_picker/emoji_picker.ios', () => {
expect(instance.rebuildEmojis).toBe(false);
});
test('should not set rebuilt emojis when rebuildEmojis is false and searchBarAnimationComplete is true', () => {
test('should not set rebuilt emojis when rebuildEmojis is false and searchBarAnimationComplete is true', async () => {
const wrapper = shallowWithIntl(<EmojiPicker {...baseProps}/>);
const instance = wrapper.instance();
instance.setState = jest.fn();
@@ -131,7 +133,7 @@ describe('components/emoji_picker/emoji_picker.ios', () => {
expect(instance.setState).not.toHaveBeenCalled();
});
test('should not set rebuilt emojis when rebuildEmojis is true and searchBarAnimationComplete is false', () => {
test('should not set rebuilt emojis when rebuildEmojis is true and searchBarAnimationComplete is false', async () => {
const wrapper = shallowWithIntl(<EmojiPicker {...baseProps}/>);
const instance = wrapper.instance();
instance.setState = jest.fn();

View File

@@ -13,21 +13,20 @@ import {
TouchableOpacity,
View,
} from 'react-native';
import FontAwesomeIcon from 'react-native-vector-icons/FontAwesome';
import Octicons from 'react-native-vector-icons/Octicons';
import sectionListGetItemLayout from 'react-native-section-list-get-item-layout';
import Emoji from 'app/components/emoji';
import FormattedText from 'app/components/formatted_text';
import {DeviceTypes} from 'app/constants';
import {emptyFunction} from 'app/utils/general';
import CompassIcon from '@components/compass_icon';
import Emoji from '@components/emoji';
import FormattedText from '@components/formatted_text';
import {paddingHorizontal as padding} from '@components/safe_area_view/iphone_x_spacing';
import {DeviceTypes} from '@constants';
import {emptyFunction} from '@utils/general';
import {
makeStyleSheetFromTheme,
changeOpacity,
} from 'app/utils/theme';
import {compareEmojis} from 'app/utils/emoji_utils';
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
} from '@utils/theme';
import {compareEmojis} from '@utils/emoji_utils';
import EmojiPickerRow from './emoji_picker_row';
const EMOJI_SIZE = 30;
@@ -431,7 +430,7 @@ export default class EmojiPicker extends PureComponent {
onPress={onPress}
style={styles.sectionIconContainer}
>
<FontAwesomeIcon
<CompassIcon
name={section.icon}
size={17}
style={[styles.sectionIcon, (index === this.state.currentSectionIndex && styles.sectionIconHighlight)]}
@@ -475,9 +474,9 @@ export default class EmojiPicker extends PureComponent {
<View style={[styles.flex, styles.flexCenter]}>
<View style={styles.flexCenter}>
<View style={styles.notFoundIcon}>
<Octicons
name='search'
size={60}
<CompassIcon
name='magnify'
size={72}
color={theme.buttonBg}
/>
</View>

View File

@@ -9,10 +9,10 @@ import {
View,
TouchableOpacity,
} from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import Config from '@assets/config.json';
import FormattedText from '@components/formatted_text';
import CompassIcon from '@components/compass_icon';
import GeneralError from './general_error';
@@ -98,7 +98,7 @@ export default class ErrorList extends PureComponent {
onPress={() => this.props.actions.clearErrors()}
>
<View style={style.closeButton}>
<Icon
<CompassIcon
name='close'
size={10}
color='#fff'

View File

@@ -9,7 +9,8 @@ import {
Text,
TouchableOpacity,
} from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import CompassIcon from '@components/compass_icon';
const style = StyleSheet.create({
buttonContainer: {
@@ -54,7 +55,7 @@ function GeneralError(props) {
style={style.buttonContainer}
onPress={dismiss}
>
<Icon
<CompassIcon
name='close'
size={20}
color='#fff'

View File

@@ -18,8 +18,10 @@ exports[`FileAttachment should match snapshot 1`] = `
<View
style={
Object {
"marginHorizontal": 20,
"marginVertical": 10,
"marginBottom": 8.2,
"marginLeft": 8,
"marginRight": 6,
"marginTop": 7.8,
}
}
>
@@ -28,14 +30,16 @@ exports[`FileAttachment should match snapshot 1`] = `
type="opacity"
>
<FileAttachmentIcon
defaultImage={false}
failed={false}
file={
Object {
"mime_type": "image/png",
}
}
iconHeight={48}
iconWidth={36}
iconSize={48}
onCaptureRef={[Function]}
smallImage={false}
theme={
Object {
"awayIndicator": "#ffbc42",
@@ -65,8 +69,6 @@ exports[`FileAttachment should match snapshot 1`] = `
"type": "Mattermost",
}
}
wrapperHeight={48}
wrapperWidth={36}
/>
</TouchableWithFeedbackIOS>
</View>

View File

@@ -246,8 +246,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
borderRadius: 5,
},
iconWrapper: {
marginHorizontal: 20,
marginVertical: 10,
marginTop: 7.8,
marginRight: 6,
marginBottom: 8.2,
marginLeft: 8,
},
circularProgress: {
width: '100%',

View File

@@ -26,8 +26,6 @@ import mattermostBucket from 'app/mattermost_bucket';
import {changeOpacity} from 'app/utils/theme';
import {goToScreen} from 'app/actions/navigation';
import {ATTACHMENT_ICON_HEIGHT, ATTACHMENT_ICON_WIDTH} from 'app/constants/attachment';
const {DOCUMENTS_PATH} = DeviceTypes;
const TEXT_PREVIEW_FORMATS = [
'application/json',
@@ -40,20 +38,9 @@ export default class FileAttachmentDocument extends PureComponent {
static propTypes = {
backgroundColor: PropTypes.string,
canDownloadFiles: PropTypes.bool.isRequired,
iconHeight: PropTypes.number,
iconWidth: PropTypes.number,
file: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
onLongPress: PropTypes.func,
wrapperHeight: PropTypes.number,
wrapperWidth: PropTypes.number,
};
static defaultProps = {
iconHeight: ATTACHMENT_ICON_HEIGHT,
iconWidth: ATTACHMENT_ICON_WIDTH,
wrapperHeight: ATTACHMENT_ICON_HEIGHT,
wrapperWidth: ATTACHMENT_ICON_WIDTH,
};
static contextTypes = {
@@ -322,17 +309,13 @@ export default class FileAttachmentDocument extends PureComponent {
};
renderFileAttachmentIcon = () => {
const {backgroundColor, iconHeight, iconWidth, file, theme, wrapperHeight, wrapperWidth} = this.props;
const {backgroundColor, file, theme} = this.props;
return (
<FileAttachmentIcon
backgroundColor={backgroundColor}
file={file.data}
theme={theme}
iconHeight={iconHeight}
iconWidth={iconWidth}
wrapperHeight={wrapperHeight}
wrapperWidth={wrapperWidth}
/>
);
}

View File

@@ -4,62 +4,70 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
Image,
View,
StyleSheet,
} from 'react-native';
import audioIcon from '@assets/images/icons/audio.png';
import codeIcon from '@assets/images/icons/code.png';
import excelIcon from '@assets/images/icons/excel.png';
import genericIcon from '@assets/images/icons/generic.png';
import imageIcon from '@assets/images/icons/image.png';
import patchIcon from '@assets/images/icons/patch.png';
import pdfIcon from '@assets/images/icons/pdf.png';
import pptIcon from '@assets/images/icons/ppt.png';
import textIcon from '@assets/images/icons/text.png';
import videoIcon from '@assets/images/icons/video.png';
import wordIcon from '@assets/images/icons/word.png';
import {ATTACHMENT_ICON_HEIGHT, ATTACHMENT_ICON_WIDTH} from '@constants/attachment';
import * as Utils from '@mm-redux/utils/file_utils';
import {changeOpacity} from '@utils/theme';
const ICON_PATH_FROM_FILE_TYPE = {
audio: audioIcon,
code: codeIcon,
image: imageIcon,
other: genericIcon,
patch: patchIcon,
pdf: pdfIcon,
presentation: pptIcon,
spreadsheet: excelIcon,
text: textIcon,
video: videoIcon,
word: wordIcon,
import CompassIcon from '@components/compass_icon';
const BLUE_ICON = '#338AFF';
const RED_ICON = '#ED522A';
const GREEN_ICON = '#1CA660';
const GRAY_ICON = '#999999';
const FAILED_ICON_NAME_AND_COLOR = ['jumbo-attachment-image-broken', GRAY_ICON];
const ICON_NAME_AND_COLOR_FROM_FILE_TYPE = {
audio: ['jumbo-attachment-audio', BLUE_ICON],
code: ['jumbo-attachment-code', BLUE_ICON],
image: ['jumbo-attachment-image', BLUE_ICON],
smallImage: ['image-outline', BLUE_ICON],
other: ['jumbo-attachment-generic', BLUE_ICON],
patch: ['jumbo-attachment-patch', BLUE_ICON],
pdf: ['jumbo-attachment-pdf', RED_ICON],
presentation: ['jumbo-attachment-powerpoint', RED_ICON],
spreadsheet: ['jumbo-attachment-excel', GREEN_ICON],
text: ['jumbo-attachment-text', GRAY_ICON],
video: ['jumbo-attachment-video', BLUE_ICON],
word: ['jumbo-attachment-word', BLUE_ICON],
zip: ['jumbo-attachment-zip', BLUE_ICON],
};
export default class FileAttachmentIcon extends PureComponent {
static propTypes = {
backgroundColor: PropTypes.string,
file: PropTypes.object.isRequired,
iconHeight: PropTypes.number,
iconWidth: PropTypes.number,
failed: PropTypes.bool,
defaultImage: PropTypes.bool,
smallImage: PropTypes.bool,
file: PropTypes.object,
onCaptureRef: PropTypes.func,
wrapperHeight: PropTypes.number,
wrapperWidth: PropTypes.number,
iconColor: PropTypes.string,
iconSize: PropTypes.number,
theme: PropTypes.object,
};
static defaultProps = {
iconHeight: ATTACHMENT_ICON_HEIGHT,
iconWidth: ATTACHMENT_ICON_WIDTH,
wrapperHeight: ATTACHMENT_ICON_HEIGHT,
wrapperWidth: ATTACHMENT_ICON_WIDTH,
failed: false,
defaultImage: false,
smallImage: false,
iconSize: 48,
};
getFileIconPath(file) {
getFileIconNameAndColor(file) {
if (this.props.failed) {
return FAILED_ICON_NAME_AND_COLOR;
}
if (this.props.defaultImage) {
if (this.props.smallImage) {
return ICON_NAME_AND_COLOR_FROM_FILE_TYPE.smallImage;
}
return ICON_NAME_AND_COLOR_FROM_FILE_TYPE.image;
}
const fileType = Utils.getFileType(file);
return ICON_PATH_FROM_FILE_TYPE[fileType] || ICON_PATH_FROM_FILE_TYPE.other;
return ICON_NAME_AND_COLOR_FROM_FILE_TYPE[fileType] || ICON_NAME_AND_COLOR_FROM_FILE_TYPE.other;
}
handleCaptureRef = (ref) => {
@@ -71,18 +79,20 @@ export default class FileAttachmentIcon extends PureComponent {
};
render() {
const {backgroundColor, file, iconHeight, iconWidth, wrapperHeight, wrapperWidth, theme} = this.props;
const source = this.getFileIconPath(file);
const bgColor = backgroundColor || theme.centerChannelBg || 'transparent';
const {backgroundColor, file, iconSize, theme, iconColor} = this.props;
const [iconName, defaultIconColor] = this.getFileIconNameAndColor(file);
const color = iconColor || defaultIconColor;
const bgColor = backgroundColor || theme?.centerChannelBg || 'transparent';
return (
<View
ref={this.handleCaptureRef}
style={[styles.fileIconWrapper, {backgroundColor: bgColor, height: wrapperHeight, width: wrapperWidth}]}
style={[styles.fileIconWrapper, {backgroundColor: bgColor}]}
>
<Image
style={{maxHeight: iconHeight, maxWidth: iconWidth, tintColor: changeOpacity(theme.centerChannelColor, 20)}}
source={source}
<CompassIcon
name={iconName}
size={iconSize}
color={color}
/>
</View>
);
@@ -91,6 +101,8 @@ export default class FileAttachmentIcon extends PureComponent {
const styles = StyleSheet.create({
fileIconWrapper: {
flex: 1,
borderRadius: 4,
alignItems: 'center',
justifyContent: 'center',
},

View File

@@ -8,11 +8,12 @@ import {
StyleSheet,
} from 'react-native';
import brokenImageIcon from '@assets/images/icons/brokenimage.png';
import ProgressiveImage from '@components/progressive_image';
import {Client4} from '@mm-redux/client';
import {changeOpacity} from '@utils/theme';
import FileAttachmentIcon from './file_attachment_icon';
const SMALL_IMAGE_MAX_HEIGHT = 48;
const SMALL_IMAGE_MAX_WIDTH = 48;
@@ -36,10 +37,9 @@ export default class FileAttachmentImage extends PureComponent {
theme: PropTypes.object,
resizeMode: PropTypes.string,
resizeMethod: PropTypes.string,
wrapperHeight: PropTypes.number,
wrapperWidth: PropTypes.number,
isSingleImage: PropTypes.bool,
imageDimensions: PropTypes.object,
backgroundColor: PropTypes.string,
};
static defaultProps = {
@@ -72,11 +72,8 @@ export default class FileAttachmentImage extends PureComponent {
imageProps = (file) => {
const imageProps = {};
const {failed} = this.state;
if (failed) {
imageProps.defaultSource = brokenImageIcon;
} else if (file.localPath) {
if (file.localPath) {
imageProps.defaultSource = {uri: file.localPath};
} else if (file.id) {
imageProps.thumbnailUri = Client4.getFileThumbnailUrl(file.id);
@@ -104,7 +101,10 @@ export default class FileAttachmentImage extends PureComponent {
style={[
wrapperStyle,
style.smallImageBorder,
{borderColor: changeOpacity(theme.centerChannelColor, 0.4)},
{
borderColor: changeOpacity(theme.centerChannelColor, 0.4),
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
},
]}
>
{this.boxPlaceholder()}
@@ -124,13 +124,26 @@ export default class FileAttachmentImage extends PureComponent {
};
render() {
const {failed} = this.state;
const {
file,
imageDimensions,
resizeMethod,
resizeMode,
backgroundColor,
theme,
} = this.props;
if (failed) {
return (
<FileAttachmentIcon
failed={failed}
backgroundColor={backgroundColor}
theme={theme}
/>
);
}
if (file.height <= SMALL_IMAGE_MAX_HEIGHT || file.width <= SMALL_IMAGE_MAX_WIDTH) {
return this.renderSmallImage();
}
@@ -169,7 +182,6 @@ const style = StyleSheet.create({
paddingBottom: '100%',
},
smallImageBorder: {
borderWidth: 1,
borderRadius: 5,
},
smallImageOverlay: {

View File

@@ -4,7 +4,6 @@ import React from 'react';
import {shallow} from 'enzyme';
import {Client4} from '@mm-redux/client';
import brokenImageIcon from '@assets/images/icons/brokenimage.png';
import FileAttachmentImage from './file_attachment_image.js';
@@ -27,15 +26,6 @@ describe('FileAttachmentImage', () => {
);
const instance = wrapper.instance();
it('should have brokenImageIcon as defaultSource if state.failed is true', () => {
wrapper.setState({failed: true});
const file = {};
const imageProps = instance.imageProps(file);
expect(imageProps.defaultSource).toStrictEqual(brokenImageIcon);
expect(imageProps.thumbnailUri).toBeUndefined();
expect(imageProps.imageUri).toBeUndefined();
});
it('should have file.localPath as defaultSource if localPath is set', () => {
wrapper.setState({failed: false});
const file = {localPath: '/localPath.png'};

View File

@@ -3,11 +3,11 @@
import {PureComponent} from 'react';
import PropTypes from 'prop-types';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
import {Alert} from 'react-native';
import {intlShape} from 'react-intl';
import {showModal} from 'app/actions/navigation';
import {showModal} from '@actions/navigation';
import CompassIcon from '@components/compass_icon';
export default class InteractiveDialogController extends PureComponent {
static propTypes = {
@@ -22,7 +22,7 @@ export default class InteractiveDialogController extends PureComponent {
constructor(props) {
super(props);
MaterialIcon.getImageSource('close', 20, props.theme.sidebarHeaderTextColor).then((source) => {
CompassIcon.getImageSource('close', 24, props.theme.sidebarHeaderTextColor).then((source) => {
this.closeButton = source;
});
}

View File

@@ -7,9 +7,6 @@ import {shallow} from 'enzyme';
import InteractiveDialogController from './interactive_dialog_controller';
jest.mock('react-intl');
jest.mock('react-native-vector-icons/MaterialIcons', () => ({
getImageSource: jest.fn().mockResolvedValue({}),
}));
describe('InteractiveDialogController', () => {
test('should open interactive dialog as alert or screen depending on with or without element', () => {

View File

@@ -16,6 +16,7 @@ export default class KeyboardLayout extends PureComponent {
static propTypes = {
children: PropTypes.node,
style: CustomPropTypes.Style,
testID: PropTypes.string,
};
constructor(props) {
@@ -60,7 +61,10 @@ export default class KeyboardLayout extends PureComponent {
}
return (
<View style={layoutStyle}>
<View
style={layoutStyle}
testID={this.props.testID}
>
{this.props.children}
</View>
);

View File

@@ -8,8 +8,8 @@ import {
View,
} from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import CustomPropTypes from 'app/constants/custom_prop_types';
import CompassIcon from '@components/compass_icon';
import CustomPropTypes from '@constants/custom_prop_types';
export default class MarkdownBlockQuote extends PureComponent {
static propTypes = {
@@ -22,10 +22,10 @@ export default class MarkdownBlockQuote extends PureComponent {
let icon;
if (!this.props.continue) {
icon = (
<Icon
name='quote-left'
<CompassIcon
name='format-quote-open'
style={this.props.iconStyle}
size={14}
size={20}
/>
);
}

View File

@@ -5,16 +5,16 @@ import PropTypes from 'prop-types';
import React from 'react';
import {intlShape} from 'react-intl';
import {
Alert,
Linking,
Platform,
StyleSheet,
Text,
View,
} from 'react-native';
import FastImage from 'react-native-fast-image';
import Clipboard from '@react-native-community/clipboard';
import brokenImageIcon from '@assets/images/icons/brokenimage.png';
import CompassIcon from '@components/compass_icon';
import ImageViewPort from '@components/image_viewport';
import ProgressiveImage from '@components/progressive_image';
import FormattedText from '@components/formatted_text';
@@ -103,11 +103,19 @@ export default class MarkdownImage extends ImageViewPort {
handleLinkPress = () => {
const url = normalizeProtocol(this.props.linkDestination);
const {intl} = this.context;
Linking.canOpenURL(url).then((supported) => {
if (supported) {
Linking.openURL(url);
}
Linking.openURL(url).catch(() => {
Alert.alert(
intl.formatMessage({
id: 'mobile.link.error.title',
defaultMessage: 'Error',
}),
intl.formatMessage({
id: 'mobile.link.error.text',
defaultMessage: 'Unable to open the link.',
}),
);
});
};
@@ -215,9 +223,9 @@ export default class MarkdownImage extends ImageViewPort {
}
} else if (this.state.failed) {
image = (
<FastImage
source={brokenImageIcon}
style={style.brokenImageIcon}
<CompassIcon
name='jumbo-attachment-image-broken'
size={24}
/>
);
}

View File

@@ -64,22 +64,18 @@ export default class MarkdownLink extends PureComponent {
onPermalinkPress(match.postId, match.teamName);
}
} else {
Linking.canOpenURL(url).then((supported) => {
if (supported) {
Linking.openURL(url);
} else {
const {formatMessage} = this.context.intl;
Alert.alert(
formatMessage({
id: 'mobile.server_link.error.title',
defaultMessage: 'Link Error',
}),
formatMessage({
id: 'mobile.server_link.error.text',
defaultMessage: 'The link could not be found on this server.',
}),
);
}
Linking.openURL(url).catch(() => {
const {formatMessage} = this.context.intl;
Alert.alert(
formatMessage({
id: 'mobile.server_link.error.title',
defaultMessage: 'Link Error',
}),
formatMessage({
id: 'mobile.server_link.error.text',
defaultMessage: 'The link could not be found on this server.',
}),
);
});
}
});

View File

@@ -11,14 +11,14 @@ import {
View,
} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import Icon from 'react-native-vector-icons/FontAwesome';
import {CELL_MAX_WIDTH, CELL_MIN_WIDTH} from 'app/components/markdown/markdown_table_cell/markdown_table_cell';
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
import {preventDoubleTap} from 'app/utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import {goToScreen} from 'app/actions/navigation';
import {DeviceTypes} from 'app/constants';
import CompassIcon from '@components/compass_icon';
import {CELL_MAX_WIDTH, CELL_MIN_WIDTH} from '@components/markdown/markdown_table_cell/markdown_table_cell';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {preventDoubleTap} from '@utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {goToScreen} from '@actions/navigation';
import {DeviceTypes} from '@constants';
const MAX_HEIGHT = 300;
const MAX_PREVIEW_COLUMNS = 5;
@@ -239,14 +239,14 @@ export default class MarkdownTable extends React.PureComponent {
if (expandButtonOffset > 0) {
expandButton = (
<TouchableWithFeedback
type={'opacity'}
type='opacity'
onPress={this.handlePress}
style={[style.expandButton, {left: expandButtonOffset}]}
>
<View style={[style.iconContainer, {width: this.getTableWidth()}]}>
<View style={style.iconButton}>
<Icon
name={'expand'}
<CompassIcon
name='arrow-expand'
style={style.icon}
/>
</View>
@@ -259,7 +259,7 @@ export default class MarkdownTable extends React.PureComponent {
<TouchableWithFeedback
style={style.tablePadding}
onPress={this.handlePress}
type={'opacity'}
type='opacity'
>
<ScrollView
onContentSizeChange={this.handleContentSizeChange}

View File

@@ -2,9 +2,10 @@
// See LICENSE.txt for license information.
import React, {PureComponent} from 'react';
import {Linking, Text, View} from 'react-native';
import {Alert, Linking, Text, View} from 'react-native';
import FastImage from 'react-native-fast-image';
import PropTypes from 'prop-types';
import {intlShape} from 'react-intl';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
@@ -16,10 +17,27 @@ export default class AttachmentAuthor extends PureComponent {
theme: PropTypes.object.isRequired,
};
static contextTypes = {
intl: intlShape.isRequired,
};
openLink = () => {
const {link} = this.props;
if (link && Linking.canOpenURL(link)) {
Linking.openURL(link);
const {intl} = this.context;
if (link) {
Linking.openURL(link).catch(() => {
Alert.alert(
intl.formatMessage({
id: 'mobile.link.error.title',
defaultMessage: 'Error',
}),
intl.formatMessage({
id: 'mobile.link.error.text',
defaultMessage: 'Unable to open the link.',
}),
);
});
}
};

View File

@@ -2,8 +2,9 @@
// See LICENSE.txt for license information.
import React, {PureComponent} from 'react';
import {Linking, Text, View} from 'react-native';
import {Alert, Linking, Text, View} from 'react-native';
import PropTypes from 'prop-types';
import {intlShape} from 'react-intl';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
import Markdown from 'app/components/markdown';
@@ -15,10 +16,27 @@ export default class AttachmentTitle extends PureComponent {
value: PropTypes.string,
};
static contextTypes = {
intl: intlShape.isRequired,
};
openLink = () => {
const {link} = this.props;
if (link && Linking.canOpenURL(link)) {
Linking.openURL(link);
const {intl} = this.context;
if (link) {
Linking.openURL(link).catch(() => {
Alert.alert(
intl.formatMessage({
id: 'mobile.link.error.title',
defaultMessage: 'Error',
}),
intl.formatMessage({
id: 'mobile.link.error.text',
defaultMessage: 'Unable to open the link.',
}),
);
});
}
};

View File

@@ -54,10 +54,9 @@ exports[`AttachmentFooter matches snapshot 1`] = `
}
}
>
<Icon
allowFontScaling={false}
<CompassIcon
color="#FFFFFF"
name="md-checkmark"
name="check"
size={20}
/>
</View>

View File

@@ -12,18 +12,19 @@ import {
View,
} from 'react-native';
import NetInfo from '@react-native-community/netinfo';
import IonIcon from 'react-native-vector-icons/Ionicons';
import {RequestStatus} from '@mm-redux/constants';
import EventEmitter from '@mm-redux/utils/event_emitter';
import FormattedText from 'app/components/formatted_text';
import CompassIcon from '@components/compass_icon';
import FormattedText from '@components/formatted_text';
import {DeviceTypes, ViewTypes} from '@constants';
import {INDICATOR_BAR_HEIGHT} from '@constants/view';
import networkConnectionListener, {checkConnection} from '@utils/network';
import {t} from '@utils/i18n';
import mattermostBucket from 'app/mattermost_bucket';
import PushNotifications from 'app/push_notifications';
import networkConnectionListener, {checkConnection} from 'app/utils/network';
import {t} from 'app/utils/i18n';
const MAX_WEBSOCKET_RETRIES = 3;
const CONNECTION_RETRY_SECONDS = 5;
@@ -371,9 +372,9 @@ export default class NetworkIndicator extends PureComponent {
defaultMessage = 'Connected';
action = (
<View style={styles.actionContainer}>
<IonIcon
<CompassIcon
color='#FFFFFF'
name='md-checkmark'
name='check'
size={20}
/>
</View>

View File

@@ -3,18 +3,15 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {Image, Text, View} from 'react-native';
import IonIcon from 'react-native-vector-icons/Ionicons';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import {Text, View} from 'react-native';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import CompassIcon from '@components/compass_icon';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
export default class NoResults extends PureComponent {
static propTypes = {
description: PropTypes.string,
iconName: PropTypes.string,
iconType: PropTypes.oneOf(['' /* image */, 'ion', 'material-community']),
image: PropTypes.oneOfType([PropTypes.object, PropTypes.number]),
iconName: PropTypes.string.isRequired,
theme: PropTypes.object.isRequired,
title: PropTypes.string.isRequired,
};
@@ -23,45 +20,19 @@ export default class NoResults extends PureComponent {
const {
description,
iconName,
iconType,
image,
theme,
title,
} = this.props;
const style = getStyleFromTheme(theme);
let icon;
if (image) {
icon = (
<Image
source={image}
style={{width: 37, height: 37, tintColor: changeOpacity(theme.centerChannelColor, 0.5)}}
/>
);
} else if (iconName) {
if (iconType === 'ion') {
icon = (
<IonIcon
size={72}
name={iconName}
style={style.icon}
/>
);
} else if (iconType === 'material-community') {
icon = (
<MaterialCommunityIcons
size={72}
name={iconName}
style={style.icon}
/>
);
}
}
return (
<View style={style.container}>
<View style={style.iconContainer}>
{icon}
<CompassIcon
size={72}
name={iconName}
style={style.icon}
/>
</View>
<Text style={style.title}>{title}</Text>
{description &&

View File

@@ -27,7 +27,17 @@ export class PasteableTextInput extends React.PureComponent {
}
}
getLastSubscriptionKey = () => {
const subscriptions = OnPasteEventEmitter._subscriber._subscriptionsForType.onPaste?.filter((sub) => sub); // eslint-disable-line no-underscore-dangle
return subscriptions?.length && subscriptions[subscriptions.length - 1].key;
}
onPaste = (event) => {
const lastSubscriptionKey = this.getLastSubscriptionKey();
if (this.subscription.key !== lastSubscriptionKey) {
return;
}
let data = null;
let error = null;

View File

@@ -12,6 +12,8 @@ import {PasteableTextInput} from './index';
const nativeEventEmitter = new NativeEventEmitter();
describe('PasteableTextInput', () => {
const emit = jest.spyOn(EventEmitter, 'emit');
test('should render pasteable text input', () => {
const onPaste = jest.fn();
const text = 'My Text';
@@ -24,12 +26,11 @@ describe('PasteableTextInput', () => {
test('should call onPaste props if native onPaste trigger', () => {
const event = {someData: 'data'};
const text = 'My Text';
const onPaste = jest.spyOn(EventEmitter, 'emit');
shallow(
<PasteableTextInput>{text}</PasteableTextInput>,
);
nativeEventEmitter.emit('onPaste', event);
expect(onPaste).toHaveBeenCalledWith(PASTE_FILES, null, event);
expect(emit).toHaveBeenCalledWith(PASTE_FILES, null, event);
});
test('should remove onPaste listener when unmount', () => {
@@ -43,4 +44,16 @@ describe('PasteableTextInput', () => {
component.instance().componentWillUnmount();
expect(mockRemove).toHaveBeenCalled();
});
test('should emit PASTE_FILES event only for last subscription', () => {
const component1 = shallow(<PasteableTextInput/>);
const instance1 = component1.instance();
const component2 = shallow(<PasteableTextInput/>);
const instance2 = component2.instance();
instance1.onPaste();
expect(emit).not.toHaveBeenCalled();
instance2.onPaste();
expect(emit).toHaveBeenCalledTimes(1);
});
});

View File

@@ -10,7 +10,6 @@ import {
ViewPropTypes,
} from 'react-native';
import {intlShape} from 'react-intl';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
import {Posts} from '@mm-redux/constants';
import EventEmitter from '@mm-redux/utils/event_emitter';
@@ -18,6 +17,7 @@ import {isPostEphemeral, isPostPendingOrFailed, isSystemMessage} from '@mm-redux
import {showModalOverCurrentContext, showModal} from '@actions/navigation';
import CompassIcon from '@components/compass_icon';
import PostBody from '@components/post_body';
import PostHeader from '@components/post_header';
import PostProfilePicture from '@components/post_profile_picture';
@@ -102,7 +102,7 @@ export default class Post extends PureComponent {
};
if (!this.closeButton) {
this.closeButton = await MaterialIcon.getImageSource('close', 20, theme.sidebarHeaderTextColor);
this.closeButton = await CompassIcon.getImageSource('close', 24, theme.sidebarHeaderTextColor);
}
const options = {

View File

@@ -9,24 +9,25 @@ import {
View,
} from 'react-native';
import {intlShape} from 'react-intl';
import Icon from 'react-native-vector-icons/Ionicons';
import {Posts} from '@mm-redux/constants';
import CombinedSystemMessage from 'app/components/combined_system_message';
import CompassIcon from '@components/compass_icon';
import CombinedSystemMessage from '@components/combined_system_message';
import FormattedText from '@components/formatted_text';
import Markdown from '@components/markdown';
import MarkdownEmoji from '@components/markdown/markdown_emoji';
import ShowMoreButton from '@components/show_more_button';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {emptyFunction} from '@utils/general';
import {getMarkdownTextStyles, getMarkdownBlockStyles} from '@utils/markdown';
import {preventDoubleTap} from '@utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {showModalOverCurrentContext} from '@actions/navigation';
import telemetry from '@telemetry';
import {renderSystemMessage} from './system_message_helpers';
import FormattedText from 'app/components/formatted_text';
import Markdown from 'app/components/markdown';
import MarkdownEmoji from 'app/components/markdown/markdown_emoji';
import ShowMoreButton from 'app/components/show_more_button';
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
import {emptyFunction} from 'app/utils/general';
import {getMarkdownTextStyles, getMarkdownBlockStyles} from 'app/utils/markdown';
import {preventDoubleTap} from 'app/utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import {showModalOverCurrentContext} from 'app/actions/navigation';
import telemetry from 'app/telemetry';
let FileAttachmentList;
let PostAddChannelMember;
@@ -71,7 +72,7 @@ export default class PostBody extends PureComponent {
shouldRenderJumboEmoji: PropTypes.bool.isRequired,
theme: PropTypes.object,
location: PropTypes.string,
mentionKeys: PropTypes.array.isRequired,
mentionKeys: PropTypes.array,
};
static defaultProps = {
@@ -79,6 +80,7 @@ export default class PostBody extends PureComponent {
onFailedPostPress: emptyFunction,
onPress: emptyFunction,
replyBarStyle: [],
mentionKeys: [],
message: '',
postProps: {},
};
@@ -457,8 +459,8 @@ export default class PostBody extends PureComponent {
style={style.retry}
type={'opacity'}
>
<Icon
name='ios-information-circle-outline'
<CompassIcon
name='information-outline'
size={26}
color={theme.errorTextColor}
/>

View File

@@ -16,7 +16,7 @@ const renderUsername = (value = '') => {
};
const renderMessage = (postBodyProps, styles, intl, localeHolder, values, skipMarkdown = false) => {
const {onPress} = postBodyProps;
const {onPress, onPermalinkPress} = postBodyProps;
const {messageStyle, textStyles} = styles;
if (skipMarkdown) {
@@ -32,6 +32,7 @@ const renderMessage = (postBodyProps, styles, intl, localeHolder, values, skipMa
baseTextStyle={messageStyle}
disableAtChannelMentionHighlight={true}
onPostPress={onPress}
onPermalinkPress={onPermalinkPress}
textStyles={textStyles}
value={intl.formatMessage(localeHolder, values)}
/>

View File

@@ -291,6 +291,7 @@ exports[`PostDraft Should render the DraftInput 1`] = `
<View>
<TextInput
allowFontScaling={true}
autoCompleteType="off"
blurOnSubmit={false}
disableFullscreenUI={true}
keyboardAppearance="light"
@@ -316,6 +317,7 @@ exports[`PostDraft Should render the DraftInput 1`] = `
}
}
testID="post_input"
textContentType="none"
underlineColorAndroid="transparent"
/>
<View
@@ -366,8 +368,6 @@ exports[`PostDraft Should render the DraftInput 1`] = `
}
>
<View
forwardedRef={[Function]}
isInteraction={true}
style={
Object {
"flex": 1,
@@ -398,25 +398,6 @@ exports[`PostDraft Should render the DraftInput 1`] = `
}
}
>
<View
accessible={true}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"alignItems": "center",
"justifyContent": "center",
"opacity": 1,
"padding": 10,
}
}
/>
<View
accessible={true}
focusable={true}
@@ -436,23 +417,10 @@ exports[`PostDraft Should render the DraftInput 1`] = `
}
}
>
<Image
source={
Object {
"testUri": "../../../dist/assets/images/icons/slash-forward-box.png",
}
}
style={
Array [
Object {
"height": 24,
"opacity": 1,
"tintColor": "rgba(61,60,64,0.64)",
"width": 24,
},
null,
]
}
<Icon
color="rgba(61,60,64,0.64)"
name="at"
size={24}
/>
</View>
<View
@@ -473,7 +441,13 @@ exports[`PostDraft Should render the DraftInput 1`] = `
"padding": 10,
}
}
/>
>
<Icon
color="rgba(61,60,64,0.64)"
name="slash-forward-box-outline"
size={24}
/>
</View>
<View
accessible={true}
focusable={true}
@@ -492,7 +466,13 @@ exports[`PostDraft Should render the DraftInput 1`] = `
"padding": 10,
}
}
/>
>
<Icon
color="rgba(61,60,64,0.64)"
name="file-document-outline"
size={24}
/>
</View>
<View
accessible={true}
focusable={true}
@@ -511,7 +491,38 @@ exports[`PostDraft Should render the DraftInput 1`] = `
"padding": 10,
}
}
/>
>
<Icon
color="rgba(61,60,64,0.64)"
name="image-outline"
size={24}
/>
</View>
<View
accessible={true}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"alignItems": "center",
"justifyContent": "center",
"opacity": 1,
"padding": 10,
}
}
>
<Icon
color="rgba(61,60,64,0.64)"
name="camera-outline"
size={24}
/>
</View>
</View>
<View
style={
@@ -539,44 +550,11 @@ exports[`PostDraft Should render the DraftInput 1`] = `
]
}
>
<RNSVGSvgView
align="xMidYMid"
bbHeight={16}
bbWidth={19}
focusable={false}
height={16}
meetOrSlice={0}
minX={0}
minY={0}
style={
Array [
Object {
"backgroundColor": "transparent",
"borderWidth": 0,
},
Object {
"flex": 0,
"height": 16,
"width": 19,
},
]
}
vbHeight={14}
vbWidth={16}
width={19}
>
<RNSVGGroup>
<RNSVGPath
d="M1.09222552,5.76354703 L0.405413926,0.94983436 C0.379442059,0.7702001 0.452067095,0.59056584 0.594431403,0.479386798 C0.737276671,0.368693254 0.927737029,0.343932856 1.09318744,0.414815564 C3.7432798,1.54942439 12.1192069,5.13676911 15.0920238,6.40974487 C15.2723839,6.48693905 15.3892573,6.66560231 15.3892573,6.8632 C15.3892573,7.06079769 15.2723839,7.23946095 15.0920238,7.31665513 C12.0961208,8.59982635 3.6105347,12.2337789 1.0316245,13.3378013 C0.878198098,13.4033436 0.701685594,13.3810106 0.569902417,13.2785706 C0.43763828,13.1761305 0.37030381,13.0096047 0.393870874,12.8430789 L1.07875863,7.96479496 L8.93669128,6.8632 L1.09222552,5.76354703 Z"
fill={2164260863}
propList={
Array [
"fill",
]
}
/>
</RNSVGGroup>
</RNSVGSvgView>
<Icon
color="rgba(255,255,255,0.5)"
name="send"
size={24}
/>
</View>
</View>
</View>
@@ -607,6 +585,17 @@ exports[`PostDraft Should render the ReadOnly for canPost 1`] = `
}
}
>
<Icon
color="#3d3c40"
name="glasses"
style={
Object {
"fontSize": 20,
"lineHeight": 22,
"opacity": 0.56,
}
}
/>
<Text
style={
Object {
@@ -645,6 +634,17 @@ exports[`PostDraft Should render the ReadOnly for channelIsReadOnly 1`] = `
}
}
>
<Icon
color="#3d3c40"
name="glasses"
style={
Object {
"fontSize": 20,
"lineHeight": 22,
"opacity": 0.56,
}
}
/>
<Text
style={
Object {

View File

@@ -415,16 +415,6 @@ export default class DraftInput extends PureComponent {
theme={theme}
registerTypingAnimation={registerTypingAnimation}
/>
{Platform.OS === 'android' &&
<Autocomplete
cursorPositionEvent={cursorPositionEvent}
maxHeight={Math.min(this.state.top - AUTOCOMPLETE_MARGIN, AUTOCOMPLETE_MAX_HEIGHT)}
onChangeText={this.handleInputQuickAction}
valueEvent={valueEvent}
rootId={rootId}
channelId={channelId}
/>
}
<View
style={[style.inputWrapper, padding(isLandscape)]}
onLayout={this.handleLayout}
@@ -475,6 +465,16 @@ export default class DraftInput extends PureComponent {
</View>
</ScrollView>
</View>
{Platform.OS === 'android' &&
<Autocomplete
cursorPositionEvent={cursorPositionEvent}
maxHeight={Math.min(this.state.top - AUTOCOMPLETE_MARGIN, AUTOCOMPLETE_MAX_HEIGHT)}
onChangeText={this.handleInputQuickAction}
valueEvent={valueEvent}
rootId={rootId}
channelId={channelId}
/>
}
</>
);
}
@@ -513,4 +513,4 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
borderTopColor: changeOpacity(theme.centerChannelColor, 0.20),
},
};
});
});

View File

@@ -11,6 +11,8 @@ import {renderWithReduxIntl} from 'test/testing_library';
import PostDraft from './post_draft';
jest.mock('app/components/compass_icon', () => 'Icon');
const mockStore = configureMockStore([thunk]);
const state = {
...intitialState,

View File

@@ -2,6 +2,7 @@
exports[`PostInput should match, full snapshot 1`] = `
<ForwardRef(WrappedPasteableTextInput)
autoCompleteType="off"
blurOnSubmit={false}
disableFullscreenUI={true}
keyboardAppearance="light"
@@ -24,6 +25,7 @@ exports[`PostInput should match, full snapshot 1`] = `
"paddingTop": 6,
}
}
textContentType="none"
underlineColorAndroid="transparent"
/>
`;

View File

@@ -290,6 +290,8 @@ export default class PostInput extends PureComponent {
keyboardType={this.state.keyboardType}
onEndEditing={this.handleEndEditing}
disableFullscreenUI={true}
textContentType='none'
autoCompleteType='off'
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
/>
);

View File

@@ -12,8 +12,7 @@ exports[`CameraButton should match snapshot 1`] = `
}
type="opacity"
>
<Icon
allowFontScaling={false}
<CompassIcon
color="rgba(61,60,64,0.64)"
name="camera-outline"
size={24}

View File

@@ -10,10 +10,10 @@ import {
StyleSheet,
} from 'react-native';
import DeviceInfo from 'react-native-device-info';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import ImagePicker from 'react-native-image-picker';
import Permissions from 'react-native-permissions';
import CompassIcon from '@components/compass_icon';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {NavigationTypes} from '@constants';
import {ICON_SIZE, MAX_FILE_COUNT_WARNING} from '@constants/post_draft';
@@ -159,7 +159,7 @@ export default class CameraQuickAction extends PureComponent {
style={style.icon}
type={'opacity'}
>
<MaterialCommunityIcons
<CompassIcon
color={color}
name='camera-outline'
size={ICON_SIZE}

View File

@@ -12,8 +12,7 @@ exports[`FileQuickAction should match snapshot 1`] = `
}
type="opacity"
>
<Icon
allowFontScaling={false}
<CompassIcon
color="rgba(61,60,64,0.64)"
name="file-document-outline"
size={24}

View File

@@ -6,10 +6,10 @@ import {intlShape} from 'react-intl';
import {Alert, NativeModules, Platform, StyleSheet} from 'react-native';
import DeviceInfo from 'react-native-device-info';
import AndroidOpenSettings from 'react-native-android-open-settings';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import DocumentPicker from 'react-native-document-picker';
import Permissions from 'react-native-permissions';
import CompassIcon from '@components/compass_icon';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {NavigationTypes} from '@constants';
import {ICON_SIZE, MAX_FILE_COUNT_WARNING} from '@constants/post_draft';
@@ -151,7 +151,7 @@ export default class FileQuickAction extends PureComponent {
style={style.icon}
type={'opacity'}
>
<MaterialCommunityIcons
<CompassIcon
color={color}
name='file-document-outline'
size={ICON_SIZE}

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