Compare commits

...

154 Commits

Author SHA1 Message Date
Gleidson Daniel Silva 4dfc9c70f3
[FIX] Keyboard not showing all emojis or showing cut emojis (#4919)
* fix the logic based on window width and make the keyboard dynamic

* fix var naming and math

* fix alignment of emojis

* add comment

* wip
2023-03-09 17:17:20 -03:00
Piyush Gupta 513e8ee636
[FIX] Increase border radius on MessageAvatar as small (#4837)
* fixed border radius of 4 on avatar

* fixed lint

* Update tests

---------

Co-authored-by: Diego Mello <diegolmello@gmail.com>
2023-03-08 15:34:39 -03:00
Mister-H aa57237004
[FIX] Order of quoted message done according to desktop version (#4739)
Co-authored-by: Diego Mello <diegolmello@gmail.com>
2023-03-08 11:20:01 -03:00
Diego Mello 58391964cb
[FIX] Message not rendering E2EE data if md exists (#4951) 2023-03-07 10:57:57 -03:00
Diego Mello 412f62eb2a
Chore: Update Detox to 20.1.2 (#4866) 2023-03-07 09:28:51 -03:00
Reinaldo Neto 4526b7f871
[FIX] UGC Rules Text align (#4934) 2023-03-02 16:04:52 -03:00
Diego Mello 27efa89dac
Bump version to 4.37.0 (#4938) 2023-03-02 13:22:40 -03:00
Gleidson Daniel Silva 3fbb7b5720
[IMPROVE] Brings the operation of the video call closer to the web (#4883)
* rename CallAgainActionSheet to StartACallActionSheet

* remove useVideoConf and use videoConfJoin directly

* consider phone on calls

* fix text shrink

* fix mic audio

* change the behavior of call icon on header and RoomInfo

* update types

* update types and variables names

* revert old type

* fix issue on old servers

* rename to a correct naming

* fix translation

* revamp call icon

* add error handling to videoconf capabilities

* lint

* fix role logic

* change const name

* rename comp

* remove commented code

* fix types and apply correct logic

* fix naming

* correct the import

* update icon size

* create timer function for videoConf bellow 5.0

* add subscription to useVideoConf hook

---------

Co-authored-by: Diego Mello <diegolmello@gmail.com>
2023-03-01 15:26:56 -03:00
Reinaldo Neto 4d5ff8aba1
[FIX] Omnichannel's icon svg fallback when the link returns an error (#4916)
* [FIX] Omnichannel's icon svg fallback when the link returns an error

* add activity indicator

* switch between activity indicator to custom icon

* reuse the same component as const
2023-02-27 17:31:28 -03:00
Reinaldo Neto 4336f0db40
[FIX] "Allow Reaction" does not work properly on "Read Only" rooms (#4864)
* [FIX] "Allow Reaction" does not work properly on "Read Only" rooms

* fix the handle of message box

---------

Co-authored-by: Gleidson Daniel Silva <gleidson10daniel@hotmail.com>
2023-02-24 11:37:56 -03:00
Gleidson Daniel Silva 4686d4f6f8
[FIX] Text color and fit content of TaskList (#4906)
* fix regression on plain text

* update stories

* fix where to add the color

* fit content taskList

---------

Co-authored-by: Reinaldo Neto <reinaldonetof@hotmail.com>
2023-02-16 10:07:19 -03:00
Gleidson Daniel Silva 58f28fb488
[FIX] Remove some permissions and keep only basic Bluetooth permission on Android (#4912) 2023-02-15 14:19:44 -03:00
lingohub[bot] 6bb4adaecd
Language update from LingoHub 🤖 on 2023-02-14Z (#4911)
* Language update from LingoHub 🤖

Project Name: Rocket.Chat.ReactNative
Project Link: https://translate.lingohub.com/rocketchat/dashboard/rocket-dot-chat-dot-reactnative
User: Diego Mello

Easy language translations with LingoHub 🚀

* Language update from LingoHub 🤖

Project Name: Rocket.Chat.ReactNative
Project Link: https://translate.lingohub.com/rocketchat/dashboard/rocket-dot-chat-dot-reactnative
User: Diego Mello

Easy language translations with LingoHub 🚀

---------

Co-authored-by: Diego Mello <diego.mello@rocket.chat>
2023-02-14 18:00:41 -03:00
Diego Mello 197616b94c
Chore: Remove deprecated typing stream (#4888) 2023-02-14 15:49:16 -03:00
Diego Mello edd0333be1
Chore: Update React Native to 0.68.6 (#4881) 2023-02-14 12:06:37 -03:00
Diego Mello 8c47187f70
[NEW] Presence Cap (#4900) 2023-02-14 10:47:56 -03:00
Gleidson Daniel Silva c4a2ce20c6
Regression: Adds idempotent prop to delete audio (#4862) 2023-02-06 16:07:23 -03:00
Reinaldo Neto 68f6eb40de
Chore: Hooks app/views/InviteUsersEditView (#4670)
* Chore: Hooks app/views/InviteUsersEditView

* minor tweak

* switch value name
2023-02-02 18:41:32 -03:00
Diego Mello 59c76d3956
Bump version to 4.36.0 (#4884) 2023-02-02 16:51:16 -03:00
Reinaldo Neto 9396b08ead
[FIX] Quote rendering with leading empty space on mobile only (#4778)
* [FIX] Quote rendering with leading empty space on mobile only

* update the message storyshot

* compareServerVersion to connection string

* fix time

* fix lint

* update to servers greater or equal than 6.0

* refactor tests

* minor tweak

---------

Co-authored-by: GleidsonDaniel <gleidson10daniel@hotmail.com>
2023-02-02 00:17:09 -03:00
Reinaldo Neto a927746d7f
[FIX] Show read receipts when it isn't read yet (#4865)
* [FIX] Show read receipts when it isn't read yet

* minor tweak
2023-02-02 00:10:27 -03:00
Reinaldo Neto 21e4818af7
[FIX] Team icons on Share View and searching on RoomsListView (#4833)
* [FIX] Team icons on Share View

* fix the teams when searching
2023-02-02 00:04:17 -03:00
Diego Mello 4de7c83e80
Bump version to 4.35.1 (#4871) 2023-01-30 15:10:09 -03:00
Gleidson Daniel Silva 18cc16beb7
Regression: Add token to Jitsi call on iOS (#4863)
* add token to jitsi builder

* fix room

* fix room and remove flags

* fix video prop
2023-01-30 13:56:39 -03:00
Reinaldo Neto 3cd1a5f0a6
[FIX] Add users shouldn't have Skip option (#4828) 2023-01-25 15:28:03 -03:00
lingohub[bot] 1abff18e75
Language update from LingoHub 🤖 (#4859)
Project Name: Rocket.Chat.ReactNative
Project Link: https://translate.lingohub.com/rocketchat/dashboard/rocket-dot-chat-dot-reactnative
User: Diego Mello

Easy language translations with LingoHub 🚀

Co-authored-by: Diego Mello <diego.mello@rocket.chat>
2023-01-24 16:01:29 -03:00
Diego Mello 26c8931563
Bump version to 4.36.0 (#4854) 2023-01-24 13:25:13 -03:00
Gleidson Daniel Silva 192a6b055a
Regression: Fix RoomItem's loading status (#4835) 2023-01-24 10:03:48 -03:00
Diego Mello 7363d9d742
[FIX] Official CI build (#4850) 2023-01-24 09:50:17 -03:00
Umang Patel 21f64b08e5
Docs: Fix Rocket.Chat server guide URL (#4848) 2023-01-23 13:50:30 -03:00
Gleidson Daniel Silva 38511d2f2d
[IMPROVE] Migrate from react-native-jitsi-meet to lib react-native-jitsimeet-custom (#4823)
Co-authored-by: Diego Mello <diegolmello@gmail.com>
2023-01-20 13:55:53 -03:00
Reinaldo Neto 593d12b129
[FIX] Add User Generated Content link on login (#4827)
* [FIX] Add User Generated Content link on login

* minor tweak
2023-01-20 11:42:18 -03:00
Diego Mello ad3bfa830c
Chore: Rotate CI secrets (#4797) 2023-01-18 19:15:22 -03:00
Diego Mello af70849b5e
[FIX] Point to correct RN fork commit (#4832) 2023-01-18 14:55:54 -03:00
Diego Mello 91831c8f33
[FIX] Unwanted clear keychain and request challenges (#4826) 2023-01-18 13:43:26 -03:00
Diego Mello 447a2b9344
Regression: App not fetching messages properly after #4801 (#4819) 2023-01-16 13:43:27 -03:00
Gleidson Daniel Silva fbb05fa0d3
Regression: Add bluetooth permissions for Jitsi (#4796)
* add blt permissions

* Update AndroidManifest.xml

* add blt permissions

* remove wrong permission
2023-01-16 10:17:52 -03:00
Gleidson Daniel Silva 41b54d6d87
[FIX] Audio names not being handled properly (#4685) 2023-01-14 07:07:25 -03:00
Diego Mello 5387d31a68
[FIX] Messages not loading on some edge cases (#4801) 2023-01-13 16:32:52 -03:00
Diego Mello a1580811ed
[IMPROVE] E2EE improvements (#4763) 2023-01-12 10:32:33 -03:00
Reinaldo Neto 3a1b06b86c
[FIX] Not Login with SSO after upgrade to version 5.4.0 and 5.4.1 (#4783)
* [FIX] Not Login with SSO after upgrade to version 5.4.0 and 5.4.1

* minor tweak
2023-01-10 20:17:14 -03:00
Reinaldo Neto 9525ef25d8
[FIX] Modal freezing app after return from the background (#4795) 2023-01-09 15:32:16 -03:00
Gleidson Daniel Silva d208373b3a
Regression: Prevent screen from sleeping on Jitsi on Android (#4791) 2023-01-09 10:29:58 -03:00
Reinaldo Neto 24ad69346a
[FIX] Opening search field after server list does not hide server list (#4792)
* [FIX] Opening search field after server list does not hide server list

* minor tweak
2023-01-06 12:36:23 -03:00
Reinaldo Neto 8102bef1d0
[FIX] Quote message and reply with image (#4715)
* send msg with attachment

* send quote inside image

* minor tweak

* remove msg from return

* fix the lint and prettier

* fix visual bug for iOS

* fixing the message box input
2023-01-05 15:23:11 -03:00
Reinaldo Neto b6b5e7294f
[FIX] Member list filters for All by default (#4757)
Co-authored-by: Gleidson Daniel Silva <gleidson10daniel@hotmail.com>
2023-01-04 11:36:51 -03:00
Diego Mello 73557038a2
[FIX] Flipper on Android (#4771) 2022-12-21 17:35:45 -03:00
Diego Mello 4174aef4f8
Chore: Upgrade react-native-device-info to 10.3.0 (#4770) 2022-12-21 15:47:42 -03:00
dependabot[bot] 67f41a712c
Chore: Bump loader-utils from 1.4.1 to 1.4.2 (#4687)
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 1.4.1 to 1.4.2.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v1.4.2/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v1.4.1...v1.4.2)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Diego Mello <diegolmello@gmail.com>
2022-12-21 14:39:09 -03:00
dependabot[bot] c13e0af110
Chore: Bump underscore from 1.10.2 to 1.13.6 (#4764)
Bumps [underscore](https://github.com/jashkenas/underscore) from 1.10.2 to 1.13.6.
- [Release notes](https://github.com/jashkenas/underscore/releases)
- [Commits](https://github.com/jashkenas/underscore/compare/1.10.2...1.13.6)

---
updated-dependencies:
- dependency-name: underscore
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Diego Mello <diegolmello@gmail.com>
2022-12-21 14:38:36 -03:00
dependabot[bot] d10a77ba3d
Bump minimist from 1.2.5 to 1.2.7 (#4616)
Bumps [minimist](https://github.com/minimistjs/minimist) from 1.2.5 to 1.2.7.
- [Release notes](https://github.com/minimistjs/minimist/releases)
- [Changelog](https://github.com/minimistjs/minimist/blob/main/CHANGELOG.md)
- [Commits](https://github.com/minimistjs/minimist/compare/v1.2.5...v1.2.7)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Diego Mello <diegolmello@gmail.com>
2022-12-21 14:38:18 -03:00
Reinaldo Neto 68066d487f
[FIX] Try again to resend a file on a thread (#4756) 2022-12-21 14:34:48 -03:00
Reinaldo Neto 0b5dab3ddf
[FIX] Notification preferences menu shows nothing (#4703) 2022-12-21 13:15:30 -03:00
Reinaldo Neto 23f716c280
[FIX] Disappear push notifications when open the app (#4718)
* [FIX] Disappear push notifications when open the app

* minor tweak

* test badgeCount

* refactor remove notification to remove notification and badge
2022-12-21 13:07:17 -03:00
Diego Mello 4b1f1d9074
Chore: Select different provisioning profile for Experimental app (#4752) 2022-12-14 18:58:05 -03:00
Diego Mello 09cdb3e7a4
Bump version to 4.35.0 (#4751) 2022-12-14 16:23:28 -03:00
lingohub[bot] 46c18fb20d
Language update from LingoHub 🤖 on 2022-12-14Z (#4748)
* Language update from LingoHub 🤖

Project Name: Rocket.Chat.ReactNative
Project Link: https://translate.lingohub.com/rocketchat/dashboard/rocket-dot-chat-dot-reactnative
User: Diego Mello

Easy language translations with LingoHub 🚀

* Add sv and fi

* Add missing locales to util

* Reorder languages

Co-authored-by: Diego Mello <diego.mello@rocket.chat>
Co-authored-by: Diego Mello <diegolmello@gmail.com>
2022-12-14 12:59:30 -03:00
Diego Mello 0290d8e0a9
[FIX] Emoji picker not opening in some cases (#4735) 2022-12-08 15:38:16 -03:00
Gleidson Daniel Silva a2722f07b0
[FIX] Can't upload the same file on two rooms at the same time (#4528)
* update style to match other spaces

* fix sending the same file in different rooms and add the limit for sending the same file

* remove when error happens and cant finish the upload

* use the same variable as rocket.chat

* minor tweak upload path and removed _raw

* change from 2 to 4

Co-authored-by: Reinaldo Neto <reinaldonetof@hotmail.com>
Co-authored-by: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com>
2022-12-05 13:31:44 -03:00
Reinaldo Neto 00661174a3
[FIX] Bottom View color when using iPad (#4643)
* [FIX] Bottom View color when using iPAD

* [FIX] Bottom View color when using iPAD

* tweak on path to react-native-ui-lib in package.json
2022-12-05 12:19:57 -03:00
Reinaldo Neto cbd9b27cf7
[FIX] Cannot read property 'fetch' of undefined on ThreadMessagesView (#4557)
* [FIX] Cannot read property 'fetch' of undefined on ThreadMessagesView

* added e2e test

* change from _raw to _id

* test thread messages view with a thread created
2022-12-02 14:06:14 -03:00
Diego Mello 87a3f85feb
Bump version to 4.34.0 (#4724) 2022-12-02 11:09:24 -03:00
Gleidson Daniel Silva a8b3a3b7de
[IMPROVE] Disable Jitsi native module on Android (#4708)
* temp: disable jitsi on android

* update props and subscription

* add open intent

* add request permissions

* disable react-native-jitsi-meet on android and separate implementations

* fix ios

* fix import alias

* revert android manifest indentation

* add catch to method

* return comment

* remove is iOS

* fix queries

* remove unused data

* webview audio

* fix android permissions

* fix audio android

* change how to open jitsi app

* remove loading

* update close logic
2022-12-01 14:20:22 -03:00
Reinaldo Neto 057ca8afac
Regression: The Unordered and Ordered List text color to bodyText (#4717)
* Regression: The Unordered and Ordered List text color to bodyText

* update storyshot
2022-11-30 15:35:17 -03:00
Gleidson Daniel Silva 92ae597b76
[FIX] videoconf skeleton border color and height (#4716) 2022-11-29 17:18:53 -03:00
Reinaldo Neto 7def88a7e2
Regression: Avatar and Username for Allowed Reactions and for Message Removed (#4714) 2022-11-29 12:12:35 -03:00
Reinaldo Neto aa26f7251e
[FIX] Remove nested room navigation (#4702)
* reset from room to room

* jump from room to room it's fine

* threads ipad

* fix ts roomslistview

* remove params

* fix the ipad highlight

* jump to a message from a thread to main room

* the reset within the goRoom

* create channel

* changes in canned response

* fix discussion navigation

* navigation newmessageview to users

* fix go room from room info view

* inappnotification, deeplinking, room.js

* change from room.rooms to room.subscribed

* minor tweak jumptomessage

* fix add existing channel to team and fixing test 02 of teams

* keep the same behavior after add existing channel

* keep the same behavior after add existing channel

* clean cosole

* changes requested about the name

* inapp redux to hooks

* added a comment to addexistingchanneltoteam

* minor tweak jumptomessage

* refactor goRoom to add the param popToRoot, also refactor the navigate in deeplinking too

* refactor other places that exist goRoom

* fix the didUpdate

* added in app notification test

* clean js

* minor tweak test
2022-11-25 10:21:56 -03:00
Gleidson Daniel Silva 21ee25e818
[FIX] Check videoConf type (#4707)
* add videoconf check

* update message type
2022-11-24 14:24:20 -03:00
Gleidson Daniel Silva 362df65bbe
[NEW] Video conf message block (#4619)
* create skeleton loading

* add phone-in icon

* fix avatar style props

* fix ios icon

* add new types

* add Rocket.Chat definitions

* fix block re-render

* create VideoConferenceBlock

* better composition

* fix call originator

* add pt-br translations

* create useSnaps hook

* update colors

* init action sheet

* fix to go back when the call ends

* create CallAgainActionSheet

* update pods

* bump lib react-native-skeleton-placeholder

* update hook location

* remove loading prop

* move files to components

* update border radius

* add verify on message options

* update icons

* apply patch
2022-11-24 08:37:08 -03:00
Diego Mello 61dc320a68
Chore: Update react-native-mmkv-storage to 0.8.0 (#4695) 2022-11-21 16:16:53 -03:00
Diego Mello 62c2d876eb
[IMPROVE] Standardize padding of E2E screens (#4701) 2022-11-21 16:10:38 -03:00
Diego Mello 44f062f9b9
Bump react-native-notifications to 4.3.3 (#4692) 2022-11-16 18:09:46 -03:00
GitStart 438b69b371
[FIX] Playing multiple voice messages at the same time (#4662)
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Debojyoti Singha <20729878+debojyoti452@users.noreply.github.com>
Co-authored-by: Diego Mello <diegolmello@gmail.com>
2022-11-16 10:40:45 -03:00
Diego Mello 988c6931ec
Chore: Bump patches (#4679) 2022-11-16 10:26:37 -03:00
Reinaldo Neto b0ea5e31f8
[FIX] Auto-translate feature wasn't working (#4678) 2022-11-11 16:51:57 -03:00
dependabot[bot] 82c922fa4d
Chore: Bump moment from 2.29.3 to 2.29.4 (#4347)
Bumps [moment](https://github.com/moment/moment) from 2.29.3 to 2.29.4.
- [Release notes](https://github.com/moment/moment/releases)
- [Changelog](https://github.com/moment/moment/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/moment/moment/compare/2.29.3...2.29.4)

---
updated-dependencies:
- dependency-name: moment
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-11 16:20:02 -03:00
dependabot[bot] 9b48ce0264
Chore(deps): Bump simple-plist from 1.1.1 to 1.3.1 (#4263)
Bumps [simple-plist](https://github.com/wollardj/simple-plist) from 1.1.1 to 1.3.1.
- [Release notes](https://github.com/wollardj/simple-plist/releases)
- [Commits](https://github.com/wollardj/simple-plist/compare/v1.1.1...v1.3.1)

---
updated-dependencies:
- dependency-name: simple-plist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-11 15:27:50 -03:00
dependabot[bot] 994892abb2
Chore: Bump loader-utils from 1.4.0 to 1.4.1 (#4660)
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 1.4.0 to 1.4.1.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v1.4.1/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v1.4.0...v1.4.1)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-11 15:16:59 -03:00
Reinaldo Neto 251cc9843b
[IMPROVE] Add delete-own-message permission (#4663) 2022-11-11 14:28:41 -03:00
Diego Mello 316bc80876
[IMPROVE] Add missing borders styles changes (#4675) 2022-11-11 13:04:57 -03:00
Diego Mello 94f0981b49
Regression: Remove users already in local database (#4673) 2022-11-10 16:59:27 -03:00
Diego Mello 05a04f7f29
[IMPROVE] Sync message actions order with web app (#4672) 2022-11-10 16:48:52 -03:00
Reinaldo Neto cd9924361e
[FIX] Non primitive dependency on useSubscriptionRoles (#4618)
* [FIX] Non primitive dependency on useSubscriptionRoles

* removed sort

* Remove JSON.parse and order by array

Co-authored-by: Diego Mello <diegolmello@gmail.com>
2022-11-10 13:36:24 -03:00
Reinaldo Neto 440e745901
[IMPROVE] Border radius and width on inputs (#4659)
* [IMPROVE] Border radius to 4 on FormTextInput

* change border width

* Remove border radius from StatusView input

Co-authored-by: Diego Mello <diegolmello@gmail.com>
2022-11-10 13:35:59 -03:00
Reinaldo Neto 612405cfae
[IMPROVE] Increase border radius on all buttons (#4657)
* border radius complete

* increase to chip

* Apply in missing places

* tweak slash command and update test

Co-authored-by: Diego Mello <diegolmello@gmail.com>
2022-11-10 13:22:02 -03:00
Diego Mello 6b6bb1df08
Chore: Reduce the frequency of dependabot runs to weekly basis (#4656) 2022-11-10 13:15:56 -03:00
Reinaldo Neto 640d83c9df
[FIX] The TS in data_setup and add expect in room and ignoreuser spec (#4658) 2022-11-09 13:41:39 -03:00
Reinaldo Neto a5e538c13f
[NEW] Emphasis Elements (italic, strike and bold) in Message Parser Components (#4621)
* [NEW] Emphasis Elements (italic, strike and bold) in Message Parser Components

* update storyshot

* minor tweak and remove of isLink

* Add theme provider to new md stories

* Cleanup

* Update tests

Co-authored-by: Diego Mello <diegolmello@gmail.com>
2022-11-07 13:42:42 -03:00
Duda Nogueira 0ad5545a4c
[i18n] Add Slovenian (#4474)
Co-authored-by: Diego Mello <diegolmello@gmail.com>
2022-11-04 15:13:29 -03:00
lingohub[bot] faf79bb59e
Language update from LingoHub 🤖 (#4640)
Project Name: Rocket.Chat.ReactNative
Project Link: https://translate.lingohub.com/rocketchat/dashboard/rocket-dot-chat-dot-reactnative
User: Robot LingoHub

Easy language translations with LingoHub 🚀

Co-authored-by: Robot LingoHub <robot@lingohub.com>
Co-authored-by: Diego Mello <diegolmello@gmail.com>
2022-11-04 15:03:55 -03:00
Gleidson Daniel Silva 3a9a98bdfe
[NEW] Reply in direct (#4582)
* add translations

* implements reply in DM

* refactor data e2e

* fix ignoreuser e2e

* create test for reply in dm

* minor tweak

Co-authored-by: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com>
Co-authored-by: Reinaldo Neto <reinaldonetof@hotmail.com>
2022-11-04 14:09:58 -03:00
Diego Mello 4f960e9fb4
[FIX] Secured push notifications not fetching content on Android (#4487) 2022-11-04 13:34:00 -03:00
Diego Mello 80d53d3876
[FIX] Push notification's deep linking not working when opened from a cold state on Android (#4648) 2022-11-04 13:33:03 -03:00
Reinaldo Neto 8940883dac
Regression: Fix styles on FormTextInput (#4625)
* Regression: Fix styles on FormTextInput

* minor tweak padding icons

* added 48 height
2022-11-04 10:49:00 -03:00
Reinaldo Neto 711cac5876
[FIX] Cancelling when adding a server (#4565)
* TC-110 [FIX] Cancelling when adding a server

* Removing the close button when connecting to a server
2022-11-01 14:09:00 -03:00
Reinaldo Neto 816f43b828
[FIX] Messages attributed to wrong user (#4598) 2022-10-31 14:02:35 -03:00
Reinaldo Neto 57620ff40d
[IMPROVE] Spotlight users order (#4527)
* [IMPROVE] Spotlight users order

* minor tweak query

* minor tweak removing query and using regex

* minor tweak

* minor tweak, make ts happy

* fix the ts

* fix lint and type TSearch
2022-10-31 13:43:47 -03:00
Reinaldo Neto 2b53898066
[FIX] Storybook of System Message within Message.stories (#4629) 2022-10-21 17:09:05 -03:00
Diego Mello 1aef030d75
Chore: Deprecate Dimensions (#4597) 2022-10-21 15:29:02 -03:00
Danish Ahmed Mirza 1486204546
[IMPROVE] Redesign emoji picker (#4328)
Co-authored-by: Diego Mello <diegolmello@gmail.com>
2022-10-21 15:27:55 -03:00
Reinaldo Neto ec03f49b2d
[FIX] UiKit/DatePicker is broken (#4607)
* [FIX] UiKit/DatePicker is broken

* minor tweak
2022-10-21 13:52:12 -03:00
Gleidson Daniel Silva 16e11c7304
[IMPROVE] System messages consistency (#4461)
* fix sys messages

* update snapshot

* update snapshot

* add username

* wip

* wip

* change to switch and fix types

* wip

* new translations

* update snapshot

* Update app/containers/message/utils.ts

Co-authored-by: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com>

* fix types and return

* remove unused translation

* fix types

* remove unused params

* minor tweak

* update story

Co-authored-by: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com>
Co-authored-by: Reinaldo Neto <reinaldonetof@hotmail.com>
2022-10-21 13:35:26 -03:00
Reinaldo Neto 761aef2c04
[FIX] Screen Lock's Password screen UI is broken on tablets (#4583)
* [FIX] Screen Lock's Password screen UI is broken on tablets

* minor tweak and added comment

* minor tweak

* minor tweak

* minor tweak
2022-10-18 16:26:49 -03:00
Gleidson Daniel Silva 525f84885a
remove wrong logic on badge prop (#4564)
Co-authored-by: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com>
2022-10-18 15:25:15 -03:00
Luciano Marcos Pierdona Junior 6dd9578413
[FIX] Team creation not adding members (#4588)
Co-authored-by: Gleidson Daniel Silva <gleidson10daniel@hotmail.com>
2022-10-18 08:39:06 -03:00
Diego Mello ef753086a9
Bump version to 4.33.0 (#4615) 2022-10-17 15:54:51 -03:00
Reinaldo Neto 531f3d0147
[IMPROVE] Add ignore user to user profile (#4600)
* button ignore and pased the param

* load room from database and check is is ignored

* move handleIgnore to lib/method/helpers

* Ignore and Unignore, also reactivity

* block and unblock user

* pass fromRid from actionView to InfoView too

* remove console.log

* unsubscribe subscriptionFrom

* block and unblock user from dm

* test to block user and ignore user

* minor tweak

* tweak data

* minor tweak

* add test before tapBack

* refactor names
2022-10-13 18:29:55 -03:00
Reinaldo Neto 300e5fc793
[FIX] Touch/Face ID toggle does not enable automatically the first time (#4576)
* [FIX] Touch/Face ID toggle does not enable automatically the first time

* minor tweak no competition setState
2022-10-07 12:34:51 -03:00
Reinaldo Neto 63e2a3a85c
Regression: Reset E2E Key section missing padding (#4595) 2022-10-07 12:01:04 -03:00
Diego Mello 5828a04aeb
[IMPROVE] Use Android 12 SplashScreen API (#4479) 2022-10-04 10:36:05 -03:00
Diego Mello 5344f2f7c1
[FIX] App always using the last deep link on Android (#4585) 2022-10-04 10:25:31 -03:00
Gleidson Daniel Silva cda998942b
[FIX] Fix quote message layout (#4568)
* fix layout

* fix quote

* update snapshot
2022-10-04 10:05:40 -03:00
Gleidson Daniel Silva 2d68faff3f
[Regression] Fix iPad subtitle font size on Header (#4586) 2022-10-04 09:56:39 -03:00
Gleidson Daniel Silva fe04faac64
Chore: Migrate SettingsView to hooks (#4542)
* migrate settings view to hooks

* temp - add non-null assertion

* fix options

* Update app/views/SettingsView/index.tsx

Co-authored-by: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com>

* Update app/views/SettingsView/index.tsx

Co-authored-by: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com>

* fix options

Co-authored-by: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com>
2022-09-30 11:50:33 -03:00
Danish Ahmed Mirza 2f03ca52c5
[IMPROVE] Add `All` tab in Reactions List (#4409)
Co-authored-by: Diego Mello <diegolmello@gmail.com>
2022-09-29 16:42:04 -03:00
Reinaldo Neto 43ebae9b19
[FIX] Cannot read property 'render' of undefined on markdown (#4549) 2022-09-28 17:06:43 -03:00
Diego Mello f213151db0
Bump version to 4.32.0 (#4575) 2022-09-28 10:18:59 -03:00
Diego Mello 6df0149210 Bump version to 4.32.0 2022-09-28 10:18:08 -03:00
Reinaldo Neto e48788c828
Regression: Inconsistent style on TextInput on iOS and Android (#4569)
* Regression: Inconsistent style on TextInput on iOS and Android

* tweak to height
2022-09-27 15:21:05 -03:00
Reinaldo Neto d2d1d787f2
Regression: Local search is returning wrong object (#4567) 2022-09-27 15:08:44 -03:00
Diego Mello e883795c5f
[FIX] Reply and deep link from push notifications not working on iOS (#4550) 2022-09-23 17:35:03 -03:00
Reinaldo Neto cbf9ae3d37
[FIX] Decrease space between sections on lists (#4543)
Co-authored-by: Diego Mello <diegolmello@gmail.com>
2022-09-23 17:24:37 -03:00
Reinaldo Neto ee1b09d5ec
Regression: Default Browser View title (#4556) 2022-09-23 17:22:21 -03:00
Gleidson Daniel Silva d1e2c3fe6d
[FIX] Fix filenames and servers url when downloading audio files (#4553) 2022-09-23 17:21:11 -03:00
Reinaldo Neto 5771dbd066
[FIX] SearchBox background and unify usage (#4519) 2022-09-22 23:03:02 -03:00
Reinaldo Neto e10de8b209
[FIX] Search in Rooms List crashes Android app (#4546)
* [FIX] Search in Rooms List crashes Android app

* back to previous search

* change the method/searchlocal
2022-09-22 22:39:52 -03:00
Reinaldo Neto 9d69c10bbd
Chore: Hooks app/views/LanguageView (#4537)
* Chore: Hooks app/views/LanguageView

* minor tweak app selector
2022-09-22 18:40:32 -03:00
Reinaldo Neto b3a286329c
Chore: Hooks app/views/UserNotificationPreferenceView (#4502)
* Chore: Hooks app/views/UserNotificationPreferenceView

* added trycatch and userId

Co-authored-by: Gleidson Daniel Silva <gleidson10daniel@hotmail.com>
2022-09-22 18:31:23 -03:00
Reinaldo Neto 214e1cc009
[FIX] Apply thicker border width and remove fixed height on TextInput (#4503)
* [FIX] Apply thicker border width and remove fixed height on TextInput

* changed the last input border from hairline to 2

* update formtextinput storyshot test
2022-09-22 18:16:25 -03:00
Diego Mello dec5c4470c
Regression: Push notifications not working after update to Android 12 (#4532)
* Remove unnecessary method from custom push notification

* Use immutable

* Maybe
2022-09-22 17:39:27 -03:00
Diego Mello 18f359a8ef
Regression: Poor performance in messages list on Android (#4536)
* Create custom RefreshControl as a workaround

* Remove unnecessary FlatList rngh change
2022-09-22 09:49:59 -03:00
Reinaldo Neto 05c1798a58
[FIX] Decrease space between sections on List (#4520)
* [FIX] Decrease space between sections on List

* minor tweak listcontainer

* update test

* Small fixes for header/info

* Update tests

Co-authored-by: Diego Mello <diegolmello@gmail.com>
2022-09-19 15:14:05 -03:00
Gleidson Daniel Silva e146cbae9f
Chore: Create useDebounce hook (#4470)
* create useDebounce hook

* fix types

* apply useDebounce hook

* migrate to use-debounce lib

* remove useless useDebounce
2022-09-16 12:19:07 -03:00
Reinaldo Neto aefb60b5d2
[FIX]Update profile name (#4513) 2022-09-16 12:04:25 -03:00
Diego Mello 92a63e6a39
Chore: Remove unused iOS permissions (#4524) 2022-09-15 13:41:55 -03:00
Diego Mello ac01acac20
Chore: Upgrade Expo to SDK 46 (#4463) 2022-09-13 16:59:31 -03:00
Diego Mello f0a1266e21
[FIX] Support Android SDK 31 (#4522) 2022-09-13 16:02:01 -03:00
Diego Mello cc28acd6ed
Bump version to 4.31.0 (#4523) 2022-09-13 13:19:34 -03:00
Reinaldo Neto bb880f01ad
Chore: Migrate E2E tests from JS to TS (#4475)
* Migrate E2E tests from JS to TS

* fixed type mocha into tsconfig

* migration data.cloud and data.docker

* migrate team folder and fix Detox namescpace

* add type TTextMatcher and start the room folder migration

* room / 02 and 03

* room's folder

* onboarding's folder

* assorted/ 01

* assorted/ 02, 03, 04

* assorted/ 05, 06, 07

* folder assorted

* fix lint

* fix device
2022-09-12 11:51:33 -03:00
Gleidson Daniel Silva 989ced89bc
Chore: Improve code and migrate some props to hooks on ThemeView (#4414)
Co-authored-by: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com>
2022-09-12 10:16:00 -03:00
Gleidson Daniel Silva 07f0618ab4
[FIX] Big usernames during discussion creation bugs UI (#4499)
* fix multiselect itens style

* remove useless brackets

* remove scroll from the top of the list items
2022-09-09 15:23:00 -03:00
Gleidson Daniel Silva 6484edddb6
Regression: Jitsi not leaving call properly (#4511) 2022-09-09 15:15:08 -03:00
Gleidson Daniel Silva cf7b9a09f4
[FIX] When creating a new user, "Custom Fields" are breaking functionality (#4501) 2022-09-09 14:01:55 -03:00
Reinaldo Neto 1bc3f542f2
Regression: Create private team and channel as default (#4493)
* Regression: Create private team and channel as default

* minor tweak
2022-09-09 11:49:06 -03:00
Gleidson Daniel Silva f9f13e7fe3
[FIX] Remove the tmid parameter when it is empty (#4500)
* add custom fields after register

* remove tmid when it is null

* wip

* wip

* wip
2022-09-08 17:58:50 -03:00
Reinaldo Neto c1a4ddc001
[FIX] System messages should be not italic (#4484) 2022-09-06 18:28:28 -03:00
Reinaldo Neto c9ce52958c
Chore: Hooks app/views/ForgotPasswordView (#4485)
* Chore: Hooks app/views/ForgotPasswordView

* validating email and using hook forms

* using mode onCHange

* add return

* fix theme

Co-authored-by: Gleidson Daniel <gleidson10daniel@hotmail.com>
2022-09-06 14:44:31 -03:00
Reinaldo Neto eabb9f157c
Chore: Hooks app/views/SetUsernameView (#4488)
* Chore: Hooks app/views/SetUsernameView

* semantic change of loading state

Co-authored-by: Gleidson Daniel <gleidson10daniel@hotmail.com>
2022-09-06 13:53:21 -03:00
Gleidson Daniel Silva e3a99a93e4
[FIX] Add loading prop on RoomActionsView to avoid double click (#4462) 2022-09-05 09:33:56 -03:00
Gleidson Daniel Silva 8d26230e4f
[FIX] Wrong Team's icon on SelectTeam item's list (#4469) 2022-09-05 09:19:45 -03:00
Reinaldo Neto 991d7156a9
Regression: Jump to message from search view (#4476) 2022-08-30 17:59:10 -03:00
Reinaldo Neto 9cbffff248
[IMPROVE] Redesign create room flow (#4381)
Co-authored-by: Diego Mello <diegolmello@gmail.com>
2022-08-26 16:16:45 -03:00
Gleidson Daniel Silva cbc6892084
[NEW] Unify members section (#4399)
* create useUserPermissions hook

* create CheckRadioButton component

* fix return

* create MembersSection component

* apply MembersSection and header filter

* fix re-render and testID

* fix detox tests

* rename to RadioButton

* move the component closer to the screen

* remove useUserPermissions

* remove theme prop

* migrate to hooks

* fix team permissions

* remove theme prop from UserItem

* remove options prop

* fix Member

* remove commented test

* fixes

* fix for room not joined

* add room members events

* adds empty option

* add members filter and pagination

* clear RoomMembersView

* remove unused styles

* Update app/views/RoomMembersView/index.tsx

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* wip

* Temp workaround for SearchBox background color

* Rename import

* Fix missing params for 5.0

* Fix e2e tests

Co-authored-by: Diego Mello <diegolmello@gmail.com>
2022-08-26 10:21:25 -03:00
Reinaldo Neto ccbc84f9a8
[IMPROVE] Remove show message in main thread preference (#4435)
* [IMPROVE] Remove show message in main thread preference

* default settings

* created the get

* fix compare server version

* fix E2E tests

* settings to logged user

* remove constant and get alsosendtochannel from user

* fix send to channel first message

* fix when the alsoSendThreadToChannel is checked

* added list picker user preference

* tweaks in messagebox tmid and detox

* added pt-br and deleted expectToBeVisible id

* reactive alsoSendThreadTOChannel

* fix the behavior when press or long press threads messages

* remove reply in thread from threads

* clean helpers detox

* tweak onMessageLongPress and onSubmit

* Remove unnecessary calculations inside ListPicker

* Fix long press logic

* Fix onReplyInit logic

* fix data_setup at detox for servers greater than 5.0

Co-authored-by: Diego Mello <diegolmello@gmail.com>
2022-08-25 16:53:19 -03:00
475 changed files with 16064 additions and 11915 deletions

View File

@ -1,9 +1,12 @@
defaults: &defaults defaults: &defaults
working_directory: ~/repo working_directory: ~/repo
orbs:
android: circleci/android@2.1.2
macos: &macos macos: &macos
macos: macos:
xcode: "13.3.0" xcode: "14.2.0"
resource_class: large resource_class: large
bash-env: &bash-env bash-env: &bash-env
@ -51,14 +54,14 @@ save-gems-cache: &save-gems-cache
update-fastlane-ios: &update-fastlane-ios update-fastlane-ios: &update-fastlane-ios
name: Update Fastlane name: Update Fastlane
command: | command: |
echo "ruby-2.6.4" > ~/.ruby-version echo "ruby-2.7.7" > ~/.ruby-version
bundle install bundle install
working_directory: ios working_directory: ios
update-fastlane-android: &update-fastlane-android update-fastlane-android: &update-fastlane-android
name: Update Fastlane name: Update Fastlane
command: | command: |
echo "ruby-2.6.4" > ~/.ruby-version echo "ruby-2.7.7" > ~/.ruby-version
bundle install bundle install
working_directory: android working_directory: android
@ -118,26 +121,26 @@ commands:
if [[ $CIRCLE_JOB == "android-build-official" ]]; then if [[ $CIRCLE_JOB == "android-build-official" ]]; then
echo -e "APPLICATION_ID=chat.rocket.android" >> ./gradle.properties echo -e "APPLICATION_ID=chat.rocket.android" >> ./gradle.properties
echo -e "BugsnagAPIKey=$BUGSNAG_KEY_OFFICIAL" >> ./gradle.properties echo -e "BugsnagAPIKey=$BUGSNAG_KEY_OFFICIAL" >> ./gradle.properties
echo $CHAT_ROCKET_ANDROID_STORE_FILE_BASE64_JKS | base64 --decode > ./app/$KEYSTORE_OFFICIAL echo $KEYSTORE_OFFICIAL_BASE64 | base64 --decode > ./app/$KEYSTORE_OFFICIAL
echo -e "KEYSTORE=$KEYSTORE_OFFICIAL" >> ./gradle.properties echo -e "KEYSTORE=$KEYSTORE_OFFICIAL" >> ./gradle.properties
echo -e "KEYSTORE_PASSWORD=$CHAT_ROCKET_ANDROID_STORE_PASSWORD" >> ./gradle.properties echo -e "KEYSTORE_PASSWORD=$KEYSTORE_OFFICIAL_PASSWORD" >> ./gradle.properties
echo -e "KEY_ALIAS=$CHAT_ROCKET_ANDROID_KEY_ALIAS" >> ./gradle.properties echo -e "KEY_ALIAS=$KEYSTORE_OFFICIAL_ALIAS" >> ./gradle.properties
echo -e "KEY_PASSWORD=$CHAT_ROCKET_ANDROID_KEY_PASSWORD" >> ./gradle.properties echo -e "KEY_PASSWORD=$KEYSTORE_OFFICIAL_PASSWORD" >> ./gradle.properties
else else
echo -e "APPLICATION_ID=chat.rocket.reactnative" >> ./gradle.properties echo -e "APPLICATION_ID=chat.rocket.reactnative" >> ./gradle.properties
echo -e "BugsnagAPIKey=$BUGSNAG_KEY" >> ./gradle.properties echo -e "BugsnagAPIKey=$BUGSNAG_KEY" >> ./gradle.properties
echo $KEYSTORE_BASE64 | base64 --decode > ./app/$KEYSTORE echo $KEYSTORE_EXPERIMENTAL_BASE64 | base64 --decode > ./app/$KEYSTORE_EXPERIMENTAL
echo -e "KEYSTORE=$KEYSTORE" >> ./gradle.properties echo -e "KEYSTORE=$KEYSTORE_EXPERIMENTAL" >> ./gradle.properties
echo -e "KEYSTORE_PASSWORD=$KEYSTORE_PASSWORD" >> ./gradle.properties echo -e "KEYSTORE_PASSWORD=$KEYSTORE_EXPERIMENTAL_PASSWORD" >> ./gradle.properties
echo -e "KEY_ALIAS=$KEY_ALIAS" >> ./gradle.properties echo -e "KEY_ALIAS=$KEYSTORE_EXPERIMENTAL_ALIAS" >> ./gradle.properties
echo -e "KEY_PASSWORD=$KEYSTORE_PASSWORD" >> ./gradle.properties echo -e "KEY_PASSWORD=$KEYSTORE_EXPERIMENTAL_PASSWORD" >> ./gradle.properties
fi fi
working_directory: android working_directory: android
- run: - run:
name: Set Google Services name: Set Google Services
command: | command: |
if [[ $KEYSTORE ]]; then if [[ $GOOGLE_SERVICES_ANDROID ]]; then
echo $GOOGLE_SERVICES_ANDROID | base64 --decode > google-services.json echo $GOOGLE_SERVICES_ANDROID | base64 --decode > google-services.json
fi fi
working_directory: android/app working_directory: android/app
@ -151,7 +154,7 @@ commands:
if [[ $CIRCLE_JOB == "android-build-experimental" || "android-automatic-build-experimental" ]]; then if [[ $CIRCLE_JOB == "android-build-experimental" || "android-automatic-build-experimental" ]]; then
./gradlew bundleExperimentalPlayRelease ./gradlew bundleExperimentalPlayRelease
fi fi
if [[ ! $KEYSTORE ]]; then if [[ ! $GOOGLE_SERVICES_ANDROID ]]; then
./gradlew assembleExperimentalPlayDebug ./gradlew assembleExperimentalPlayDebug
fi fi
working_directory: android working_directory: android
@ -200,8 +203,12 @@ commands:
- run: - run:
name: Set Google Services name: Set Google Services
command: | command: |
if [[ $KEYSTORE ]]; then if [[ $APP_STORE_CONNECT_API_KEY_BASE64 ]]; then
if [[ $CIRCLE_JOB == "ios-build-official" ]]; then
echo $GOOGLE_SERVICES_IOS | base64 --decode > GoogleService-Info.plist echo $GOOGLE_SERVICES_IOS | base64 --decode > GoogleService-Info.plist
else
echo $GOOGLE_SERVICES_IOS_EXPERIMENTAL | base64 --decode > GoogleService-Info.plist
fi
fi fi
working_directory: ios working_directory: ios
- run: - run:
@ -223,12 +230,12 @@ commands:
/usr/libexec/PlistBuddy -c "Set IS_OFFICIAL NO" ./NotificationService/Info.plist /usr/libexec/PlistBuddy -c "Set IS_OFFICIAL NO" ./NotificationService/Info.plist
fi fi
if [[ $APP_STORE_CONNECT_API_BASE64 ]]; then if [[ $APP_STORE_CONNECT_API_KEY_BASE64 ]]; then
echo $APP_STORE_CONNECT_API_BASE64 | base64 --decode > ./fastlane/app_store_connect_api_key.p8 echo $APP_STORE_CONNECT_API_KEY_BASE64 | base64 --decode > ./fastlane/app_store_connect_api_key.p8
if [[ $CIRCLE_JOB == "ios-build-official" ]]; then if [[ $CIRCLE_JOB == "ios-build-official" ]]; then
bundle exec fastlane ios build_official bundle exec fastlane ios build_official
else else
if [[ $KEYSTORE ]]; then if [[ $APP_STORE_CONNECT_API_KEY_BASE64 ]]; then
bundle exec fastlane ios build_experimental bundle exec fastlane ios build_experimental
else else
bundle exec fastlane ios build_fork bundle exec fastlane ios build_fork
@ -318,11 +325,19 @@ commands:
- run: - run:
name: Fastlane Tesflight Upload name: Fastlane Tesflight Upload
command: | command: |
echo $APP_STORE_CONNECT_API_BASE64 | base64 --decode > ./fastlane/app_store_connect_api_key.p8 echo $APP_STORE_CONNECT_API_KEY_BASE64 | base64 --decode > ./fastlane/app_store_connect_api_key.p8
bundle exec fastlane ios beta official:<< parameters.official >> bundle exec fastlane ios beta official:<< parameters.official >>
working_directory: ios working_directory: ios
- save_cache: *save-gems-cache - save_cache: *save-gems-cache
create-e2e-account-file:
description: "Create e2e account file"
steps:
- run:
command: |
echo $E2E_ACCOUNT | base64 --decode > ./e2e_account.ts
working_directory: e2e
version: 2.1 version: 2.1
# EXECUTORS # EXECUTORS
@ -434,6 +449,94 @@ jobs:
- upload-to-google-play-beta: - upload-to-google-play-beta:
official: true official: true
e2e-build-android:
<<: *defaults
executor:
name: android/android-machine
resource-class: xlarge
tag: 2022.12.1
environment:
<<: *android-env
steps:
- checkout
- restore_cache: *restore-npm-cache-linux
- run: *install-npm-modules
- save_cache: *save-npm-cache-linux
- restore_cache: *restore-gradle-cache
- run:
name: Configure Gradle
command: |
echo -e "" > ./gradle.properties
# echo -e "android.enableAapt2=false" >> ./gradle.properties
echo -e "android.useAndroidX=true" >> ./gradle.properties
echo -e "android.enableJetifier=true" >> ./gradle.properties
echo -e "newArchEnabled=false" >> ./gradle.properties
echo -e "FLIPPER_VERSION=0.125.0" >> ./gradle.properties
echo -e "VERSIONCODE=$CIRCLE_BUILD_NUM" >> ./gradle.properties
echo -e "APPLICATION_ID=chat.rocket.reactnative" >> ./gradle.properties
echo -e "BugsnagAPIKey=$BUGSNAG_KEY" >> ./gradle.properties
echo $KEYSTORE_EXPERIMENTAL_BASE64 | base64 --decode > ./app/$KEYSTORE_EXPERIMENTAL
echo -e "KEYSTORE=$KEYSTORE_EXPERIMENTAL" >> ./gradle.properties
echo -e "KEYSTORE_PASSWORD=$KEYSTORE_EXPERIMENTAL_PASSWORD" >> ./gradle.properties
echo -e "KEY_ALIAS=$KEYSTORE_EXPERIMENTAL_ALIAS" >> ./gradle.properties
echo -e "KEY_PASSWORD=$KEYSTORE_EXPERIMENTAL_PASSWORD" >> ./gradle.properties
working_directory: android
- run:
name: Build Android
command: |
echo "RUNNING_E2E_TESTS=true" > ./.env
yarn e2e:android-build
- save_cache: *save-gradle-cache
- store_artifacts:
path: android/app/build/outputs/apk/experimentalPlay/release/app-experimental-play-release.apk
- store_artifacts:
path: android/app/build/outputs/apk/androidTest/experimentalPlay/release/app-experimental-play-release-androidTest.apk
- persist_to_workspace:
root: /home/circleci/repo
paths:
- android/app/build/outputs/apk/
e2e-test-android:
<<: *defaults
executor:
name: android/android-machine
resource-class: xlarge
tag: 2022.12.1
parallelism: 4
steps:
- checkout
- attach_workspace:
at: /home/circleci/repo
- restore_cache: *restore-npm-cache-linux
- run: *install-npm-modules
- save_cache: *save-npm-cache-linux
- run: mkdir ~/junit
- create-e2e-account-file
- android/create-avd:
avd-name: Pixel_API_31_AOSP
install: true
system-image: system-images;android-31;default;x86_64
- run:
name: Setup emulator
command: |
echo "hw.lcd.density = 440" >> ~/.android/avd/Pixel_API_31_AOSP.avd/config.ini
echo "hw.lcd.height = 2280" >> ~/.android/avd/Pixel_API_31_AOSP.avd/config.ini
echo "hw.lcd.width = 1080" >> ~/.android/avd/Pixel_API_31_AOSP.avd/config.ini
- run:
name: Run Detox Tests
command: |
TEST=$(circleci tests glob "e2e/tests/**/*.ts" | circleci tests split --split-by=timings)
yarn e2e:android-test $TEST
- store_artifacts:
path: artifacts
- run:
command: cp junit.xml ~/junit/
when: always
- store_test_results:
path: ~/junit
- store_artifacts:
path: ~/junit
# iOS builds # iOS builds
ios-build-experimental: ios-build-experimental:
executor: mac-env executor: mac-env
@ -457,11 +560,89 @@ jobs:
- upload-to-testflight: - upload-to-testflight:
official: true official: true
e2e-build-ios:
executor: mac-env
steps:
- checkout
- restore_cache: *restore-gems-cache
- restore_cache: *restore-npm-cache-mac
- run: *install-npm-modules
- run: *update-fastlane-ios
- save_cache: *save-npm-cache-mac
- save_cache: *save-gems-cache
- manage-pods
- run:
name: Configure Detox
command: |
brew tap wix/brew
brew install applesimutils
- run:
name: Build
command: |
/usr/libexec/PlistBuddy -c "Set :bugsnag:apiKey $BUGSNAG_KEY" ./ios/RocketChatRN/Info.plist
/usr/libexec/PlistBuddy -c "Set :bugsnag:apiKey $BUGSNAG_KEY" ./ios/ShareRocketChatRN/Info.plist
yarn detox clean-framework-cache && yarn detox build-framework-cache
echo "RUNNING_E2E_TESTS=true" > ./.env
yarn e2e:ios-build
- persist_to_workspace:
root: /Users/distiller/project
paths:
- ios/build/Build/Products/Release-iphonesimulator/Rocket.Chat Experimental.app
e2e-test-ios:
executor: mac-env
parallelism: 5
steps:
- checkout
- attach_workspace:
at: /Users/distiller/project
- restore_cache: *restore-npm-cache-mac
- run: *install-npm-modules
- save_cache: *save-npm-cache-mac
- run: mkdir ~/junit
- run:
name: Configure Detox
command: |
brew tap wix/brew
brew install applesimutils
yarn detox clean-framework-cache && yarn detox build-framework-cache
- create-e2e-account-file
- run:
name: Run tests
command: |
TEST=$(circleci tests glob "e2e/tests/**/*.ts" | circleci tests split --split-by=timings)
yarn e2e:ios-test $TEST
- store_artifacts:
path: artifacts
- run:
command: cp junit.xml ~/junit/
when: always
- store_test_results:
path: ~/junit
- store_artifacts:
path: ~/junit
workflows: workflows:
build-and-test: build-and-test:
jobs: jobs:
- lint-testunit - lint-testunit
# E2E tests
- e2e-hold:
type: approval
- e2e-build-ios:
requires:
- e2e-hold
- e2e-test-ios:
requires:
- e2e-build-ios
- e2e-build-android:
requires:
- e2e-hold
- e2e-test-android:
requires:
- e2e-build-android
# iOS Experimental # iOS Experimental
- ios-hold-build-experimental: - ios-hold-build-experimental:
type: approval type: approval

91
.detoxrc.js Normal file
View File

@ -0,0 +1,91 @@
/** @type {Detox.DetoxConfig} */
module.exports = {
testRunner: {
args: {
$0: 'jest',
config: 'e2e/jest.config.js'
},
retries: process.env.CI ? 3 : 0
},
artifacts: {
plugins: {
screenshot: 'failing',
video: 'failing',
uiHierarchy: 'enabled'
}
},
apps: {
'ios.debug': {
type: 'ios.app',
binaryPath: 'ios/build/Build/Products/Debug-iphonesimulator/Rocket.Chat Experimental.app',
build:
'xcodebuild -workspace ios/RocketChatRN.xcworkspace -scheme RocketChatRN -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build'
},
'ios.release': {
type: 'ios.app',
binaryPath: 'ios/build/Build/Products/Release-iphonesimulator/Rocket.Chat Experimental.app',
build:
'xcodebuild -workspace ios/RocketChatRN.xcworkspace -scheme RocketChatRN -configuration Release -sdk iphonesimulator -derivedDataPath ios/build'
},
'android.debug': {
type: 'android.apk',
binaryPath: 'android/app/build/outputs/apk/experimentalPlay/debug/app-experimental-play-debug.apk',
build:
'cd android ; ./gradlew assembleExperimentalPlayDebug assembleExperimentalPlayDebugAndroidTest -DtestBuildType=debug ; cd -',
reversePorts: [8081]
},
'android.release': {
type: 'android.apk',
binaryPath: 'android/app/build/outputs/apk/experimentalPlay/release/app-experimental-play-release.apk',
build:
'cd android ; ./gradlew assembleExperimentalPlayRelease assembleExperimentalPlayReleaseAndroidTest -DtestBuildType=release ; cd -'
}
},
devices: {
simulator: {
type: 'ios.simulator',
device: {
type: 'iPhone 14'
}
},
attached: {
type: 'android.attached',
device: {
adbName: '.*'
}
},
emulator: {
type: 'android.emulator',
device: {
avdName: 'Pixel_API_31_AOSP'
},
headless: process.env.CI ? true : false
}
},
configurations: {
'ios.sim.debug': {
device: 'simulator',
app: 'ios.debug'
},
'ios.sim.release': {
device: 'simulator',
app: 'ios.release'
},
'android.att.debug': {
device: 'attached',
app: 'android.debug'
},
'android.att.release': {
device: 'attached',
app: 'android.release'
},
'android.emu.debug': {
device: 'emulator',
app: 'android.debug'
},
'android.emu.release': {
device: 'emulator',
app: 'android.release'
}
}
};

2
.env Normal file
View File

@ -0,0 +1,2 @@
# DON'T COMMIT THIS FILE
RUNNING_E2E_TESTS=

View File

@ -2,7 +2,7 @@ module.exports = {
settings: { settings: {
'import/resolver': { 'import/resolver': {
node: { node: {
extensions: ['.ts', '.tsx', '.js', '.ios.js', '.android.js', '.native.js'] extensions: ['.ts', '.tsx', '.js', '.ios.js', '.android.js', '.native.js', '.ios.tsx', '.android.tsx']
} }
} }
}, },
@ -156,22 +156,6 @@ module.exports = {
__DEV__: true __DEV__: true
}, },
overrides: [ overrides: [
{
files: ['e2e/**'],
globals: {
by: true,
detox: true,
device: true,
element: true,
expect: true,
waitFor: true
},
rules: {
'import/no-extraneous-dependencies': 0,
'no-await-in-loop': 0,
'no-restricted-syntax': 0
}
},
{ {
files: ['**/*.ts', '**/*.tsx'], files: ['**/*.ts', '**/*.tsx'],
extends: [ extends: [
@ -253,6 +237,12 @@ module.exports = {
} }
} }
} }
},
{
files: ['e2e/**'],
rules: {
'no-await-in-loop': 0
}
} }
] ]
}; };

View File

@ -3,5 +3,5 @@ updates:
- package-ecosystem: "npm" - package-ecosystem: "npm"
directory: "/" directory: "/"
schedule: schedule:
interval: "daily" interval: "weekly"
open-pull-requests-limit: 25 open-pull-requests-limit: 25

2
.gitignore vendored
View File

@ -66,5 +66,7 @@ artifacts
e2e/docker/rc_test_env/docker-compose.yml e2e/docker/rc_test_env/docker-compose.yml
e2e/docker/data/db e2e/docker/data/db
e2e/e2e_account.js e2e/e2e_account.js
e2e/e2e_account.ts
junit.xml
*.p8 *.p8

View File

@ -1 +1 @@
2.7.4 2.7.7

View File

@ -6,11 +6,13 @@ import RNBootSplash from 'react-native-bootsplash';
import { selectServerRequest } from '../app/actions/server'; import { selectServerRequest } from '../app/actions/server';
import { mockedStore as store } from '../app/reducers/mockedStore'; import { mockedStore as store } from '../app/reducers/mockedStore';
import database from '../app/lib/database'; import database from '../app/lib/database';
import { setUser } from '../app/actions/login';
RNBootSplash.hide(); RNBootSplash.hide();
const baseUrl = 'https://open.rocket.chat'; const baseUrl = 'https://open.rocket.chat';
store.dispatch(selectServerRequest(baseUrl)); store.dispatch(selectServerRequest(baseUrl));
store.dispatch(setUser({ id: 'abc', username: 'rocket.cat', name: 'Rocket Cat' }));
database.setActiveDB(baseUrl); database.setActiveDB(baseUrl);
const StorybookUIRoot = getStorybookUI({}); const StorybookUIRoot = getStorybookUI({});

View File

@ -3,7 +3,13 @@ import { Provider } from 'react-redux';
import { themes } from '../app/lib/constants'; import { themes } from '../app/lib/constants';
import MessageContext from '../app/containers/message/Context'; import MessageContext from '../app/containers/message/Context';
import { selectServerRequest } from '../app/actions/server';
import { mockedStore as store } from '../app/reducers/mockedStore'; import { mockedStore as store } from '../app/reducers/mockedStore';
import { setUser } from '../app/actions/login';
const baseUrl = 'https://open.rocket.chat';
store.dispatch(selectServerRequest(baseUrl));
store.dispatch(setUser({ id: 'abc', username: 'rocket.cat', name: 'Rocket Cat' }));
export const decorators = [ export const decorators = [
Story => ( Story => (
@ -15,7 +21,7 @@ export const decorators = [
username: 'diego.mello', username: 'diego.mello',
token: 'abc' token: 'abc'
}, },
baseUrl: 'https://open.rocket.chat', baseUrl,
onPress: () => {}, onPress: () => {},
onLongPress: () => {}, onLongPress: () => {},
reactionInit: () => {}, reactionInit: () => {},

View File

@ -22,6 +22,7 @@ const getStories = () => {
require("../app/containers/Avatar/Avatar.stories.tsx"), require("../app/containers/Avatar/Avatar.stories.tsx"),
require("../app/containers/BackgroundContainer/index.stories.tsx"), require("../app/containers/BackgroundContainer/index.stories.tsx"),
require("../app/containers/Button/Button.stories.tsx"), require("../app/containers/Button/Button.stories.tsx"),
require("../app/containers/Chip/Chip.stories.tsx"),
require("../app/containers/HeaderButton/HeaderButtons.stories.tsx"), require("../app/containers/HeaderButton/HeaderButtons.stories.tsx"),
require("../app/containers/List/List.stories.tsx"), require("../app/containers/List/List.stories.tsx"),
require("../app/containers/LoginServices/LoginServices.stories.tsx"), require("../app/containers/LoginServices/LoginServices.stories.tsx"),
@ -29,6 +30,7 @@ const getStories = () => {
require("../app/containers/markdown/new/NewMarkdown.stories.tsx"), require("../app/containers/markdown/new/NewMarkdown.stories.tsx"),
require("../app/containers/message/Components/CollapsibleQuote/CollapsibleQuote.stories.tsx"), require("../app/containers/message/Components/CollapsibleQuote/CollapsibleQuote.stories.tsx"),
require("../app/containers/message/Message.stories.tsx"), require("../app/containers/message/Message.stories.tsx"),
require("../app/containers/ReactionsList/ReactionsList.stories.tsx"),
require("../app/containers/RoomHeader/RoomHeader.stories.tsx"), require("../app/containers/RoomHeader/RoomHeader.stories.tsx"),
require("../app/containers/RoomItem/RoomItem.stories.tsx"), require("../app/containers/RoomItem/RoomItem.stories.tsx"),
require("../app/containers/SearchBox/SearchBox.stories.tsx"), require("../app/containers/SearchBox/SearchBox.stories.tsx"),
@ -38,6 +40,7 @@ const getStories = () => {
require("../app/containers/UIKit/UiKitModal.stories.tsx"), require("../app/containers/UIKit/UiKitModal.stories.tsx"),
require("../app/containers/UnreadBadge/UnreadBadge.stories.tsx"), require("../app/containers/UnreadBadge/UnreadBadge.stories.tsx"),
require("../app/views/CannedResponsesListView/CannedResponseItem.stories.tsx"), require("../app/views/CannedResponsesListView/CannedResponseItem.stories.tsx"),
require("../app/views/CreateChannelView/RoomSettings/SwitchItem.stories.tsx"),
require("../app/views/DiscussionsView/Item.stories.tsx"), require("../app/views/DiscussionsView/Item.stories.tsx"),
require("../app/views/RoomView/LoadMore/LoadMore.stories.tsx"), require("../app/views/RoomView/LoadMore/LoadMore.stories.tsx"),
require("../app/views/ThreadMessagesView/Item.stories.tsx"), require("../app/views/ThreadMessagesView/Item.stories.tsx"),

View File

@ -1,4 +1,4 @@
source 'https://rubygems.org' source 'https://rubygems.org'
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
ruby '2.7.4' ruby '2.7.7'
gem 'cocoapods', '~> 1.11', '>= 1.11.2' gem 'cocoapods', '~> 1.11', '>= 1.11.2'

View File

@ -1,13 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots Button Custom Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"purple\\",\\"padding\\":10,\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"yellow\\",\\"fontSize\\":18},[{\\"textAlign\\":\\"left\\"}]],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`; exports[`Storyshots Button Custom Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"purple\\",\\"padding\\":10,\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"yellow\\",\\"fontSize\\":18},[{\\"textAlign\\":\\"left\\"}]],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
exports[`Storyshots Button Disabled Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":true},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":0.3}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#ffffff\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`; exports[`Storyshots Button Disabled Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":true},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":0.3}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#ffffff\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
exports[`Storyshots Button Disabled Loading Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":true},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":0.3}},\\"children\\":[{\\"type\\":\\"ActivityIndicator\\",\\"props\\":{\\"style\\":[{\\"padding\\":16,\\"flex\\":1},null],\\"color\\":\\"#ffffff\\"},\\"children\\":null}]}"`; exports[`Storyshots Button Disabled Loading Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":true},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":0.3}},\\"children\\":[{\\"type\\":\\"ActivityIndicator\\",\\"props\\":{\\"style\\":[{\\"padding\\":16,\\"flex\\":1},null],\\"color\\":\\"#ffffff\\"},\\"children\\":null}]}"`;
exports[`Storyshots Button Loading Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":true},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"ActivityIndicator\\",\\"props\\":{\\"style\\":[{\\"padding\\":16,\\"flex\\":1},null],\\"color\\":\\"#ffffff\\"},\\"children\\":null}]}"`; exports[`Storyshots Button Loading Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":true},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"ActivityIndicator\\",\\"props\\":{\\"style\\":[{\\"padding\\":16,\\"flex\\":1},null],\\"color\\":\\"#ffffff\\"},\\"children\\":null}]}"`;
exports[`Storyshots Button Primary Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#ffffff\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`; exports[`Storyshots Button Primary Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#ffffff\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
exports[`Storyshots Button Secondary Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`; exports[`Storyshots Button Secondary Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;

View File

@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots Chip Chip Text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"style\\":[{\\"paddingHorizontal\\":8,\\"marginRight\\":8,\\"borderRadius\\":4,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"width\\":28,\\"height\\":28,\\"borderRadius\\":4},{\\"marginRight\\":8,\\"marginVertical\\":8}],\\"testID\\":\\"avatar\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"overflow\\":\\"hidden\\"},{\\"width\\":28,\\"height\\":28,\\"borderRadius\\":4}]},\\"children\\":[{\\"type\\":\\"FastImageView\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},\\"source\\":{\\"uri\\":\\"https://open.rocket.chat/avatar/rocket.cat?format=png&size=56\\",\\"headers\\":{\\"User-Agent\\":\\"RC Mobile; ios unknown; vunknown (unknown)\\"},\\"priority\\":\\"high\\"},\\"resizeMode\\":\\"cover\\"},\\"children\\":null}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"marginRight\\":8,\\"maxWidth\\":120}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Rocket.Cat\\"]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16,\\"color\\":\\"#6C727A\\"},null,{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}"`;
exports[`Storyshots Chip Chip With Short Text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"style\\":[{\\"paddingHorizontal\\":8,\\"marginRight\\":8,\\"borderRadius\\":4,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"width\\":28,\\"height\\":28,\\"borderRadius\\":4},{\\"marginRight\\":8,\\"marginVertical\\":8}],\\"testID\\":\\"avatar\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"overflow\\":\\"hidden\\"},{\\"width\\":28,\\"height\\":28,\\"borderRadius\\":4}]},\\"children\\":[{\\"type\\":\\"FastImageView\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},\\"source\\":{\\"uri\\":\\"https://open.rocket.chat/avatar/rocket.cat?format=png&size=56\\",\\"headers\\":{\\"User-Agent\\":\\"RC Mobile; ios unknown; vunknown (unknown)\\"},\\"priority\\":\\"high\\"},\\"resizeMode\\":\\"cover\\"},\\"children\\":null}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"marginRight\\":8,\\"maxWidth\\":120}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Short\\"]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16,\\"color\\":\\"#6C727A\\"},null,{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}"`;
exports[`Storyshots Chip Chip Without Avatar 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"style\\":[{\\"paddingHorizontal\\":8,\\"marginRight\\":8,\\"borderRadius\\":4,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"marginRight\\":8,\\"maxWidth\\":120}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Without Avatar\\"]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16,\\"color\\":\\"#6C727A\\"},null,{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}"`;
exports[`Storyshots Chip Chip Without Avatar And Icon 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityState\\":{\\"disabled\\":true},\\"focusable\\":true,\\"style\\":[{\\"paddingHorizontal\\":8,\\"marginRight\\":8,\\"borderRadius\\":4,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"marginRight\\":8,\\"maxWidth\\":120}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Without Avatar and Icon\\"]}]}]}]}]}"`;
exports[`Storyshots Chip Chip Without Icon 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityState\\":{\\"disabled\\":true},\\"focusable\\":true,\\"style\\":[{\\"paddingHorizontal\\":8,\\"marginRight\\":8,\\"borderRadius\\":4,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"width\\":28,\\"height\\":28,\\"borderRadius\\":4},{\\"marginRight\\":8,\\"marginVertical\\":8}],\\"testID\\":\\"avatar\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"overflow\\":\\"hidden\\"},{\\"width\\":28,\\"height\\":28,\\"borderRadius\\":4}]},\\"children\\":[{\\"type\\":\\"FastImageView\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},\\"source\\":{\\"uri\\":\\"https://open.rocket.chat/avatar/rocket.cat?format=png&size=56\\",\\"headers\\":{\\"User-Agent\\":\\"RC Mobile; ios unknown; vunknown (unknown)\\"},\\"priority\\":\\"high\\"},\\"resizeMode\\":\\"cover\\"},\\"children\\":null}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"marginRight\\":8,\\"maxWidth\\":120}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Without Icon\\"]}]}]}]}]}"`;

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots Login Services Separators 1`] = `"[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"More options\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":0,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#1d74f5\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"More options\\"},\\"children\\":[\\"More options\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"marginVertical\\":24}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":14,\\"marginLeft\\":14,\\"marginRight\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#9ca2a8\\"}]},\\"children\\":[\\"OR\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null}]},{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Less options\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":0,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#1d74f5\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Less options\\"},\\"children\\":[\\"Less options\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"marginVertical\\":24}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":14,\\"marginLeft\\":14,\\"marginRight\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#9ca2a8\\"}]},\\"children\\":[\\"OR\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null}]}]"`; exports[`Storyshots Login Services Separators 1`] = `"[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"More options\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":0,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#1d74f5\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"More options\\"},\\"children\\":[\\"More options\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"marginVertical\\":24}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":14,\\"marginLeft\\":14,\\"marginRight\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#9ca2a8\\"}]},\\"children\\":[\\"OR\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null}]},{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Less options\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":0,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#1d74f5\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Less options\\"},\\"children\\":[\\"Less options\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"marginVertical\\":24}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":14,\\"marginLeft\\":14,\\"marginRight\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#9ca2a8\\"}]},\\"children\\":[\\"OR\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null}]}]"`;
exports[`Storyshots Login Services Service List 1`] = `"[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":2,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"github\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":2,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"gitlab\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":2,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"google\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":2,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"apple\\"]}]}]}]"`; exports[`Storyshots Login Services Service List 1`] = `"[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":4,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"github\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":4,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"gitlab\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":4,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"google\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":4,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"apple\\"]}]}]}]"`;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots SearchBox Basic 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"testID\\":\\"searchbox\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"marginBottom\\":10},{\\"margin\\":16,\\"marginBottom\\":16}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"position\\":\\"relative\\"}},\\"children\\":[{\\"type\\":\\"TextInput\\",\\"props\\":{\\"style\\":[{\\"color\\":\\"#0d0e12\\"},[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"height\\":48,\\"fontSize\\":16,\\"padding\\":14,\\"borderWidth\\":0.5,\\"borderRadius\\":2},null,{\\"paddingRight\\":45},{\\"backgroundColor\\":\\"#ffffff\\",\\"borderColor\\":\\"#cbcbcc\\",\\"color\\":\\"#0d0e12\\"},null,null],{\\"textAlign\\":\\"auto\\"}],\\"placeholderTextColor\\":\\"#9ca2a8\\",\\"keyboardAppearance\\":\\"light\\",\\"autoCorrect\\":false,\\"autoCapitalize\\":\\"none\\",\\"underlineColorAndroid\\":\\"transparent\\",\\"accessibilityLabel\\":\\"Search\\",\\"placeholder\\":\\"Search\\",\\"value\\":\\"\\",\\"blurOnSubmit\\":true,\\"returnKeyType\\":\\"search\\"},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":20,\\"color\\":\\"#2f343d\\"},[{\\"position\\":\\"absolute\\",\\"top\\":14},{\\"right\\":15}],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}"`; exports[`Storyshots SearchBox Basic 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"testID\\":\\"searchbox\\",\\"style\\":{\\"backgroundColor\\":\\"#ffffff\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"marginBottom\\":10},{\\"margin\\":16,\\"marginBottom\\":16}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"position\\":\\"relative\\",\\"justifyContent\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"TextInput\\",\\"props\\":{\\"style\\":[{\\"color\\":\\"#0d0e12\\"},[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"height\\":48,\\"fontSize\\":16,\\"paddingHorizontal\\":16,\\"paddingVertical\\":10,\\"borderWidth\\":1,\\"borderRadius\\":4},null,{\\"paddingRight\\":45},{\\"backgroundColor\\":\\"#ffffff\\",\\"borderColor\\":\\"#cbcbcc\\",\\"color\\":\\"#0d0e12\\"},null,null],{\\"textAlign\\":\\"auto\\"}],\\"placeholderTextColor\\":\\"#9ca2a8\\",\\"keyboardAppearance\\":\\"light\\",\\"autoCorrect\\":false,\\"autoCapitalize\\":\\"none\\",\\"underlineColorAndroid\\":\\"transparent\\",\\"accessibilityLabel\\":\\"Search\\",\\"placeholder\\":\\"Search\\",\\"value\\":\\"\\",\\"blurOnSubmit\\":true,\\"returnKeyType\\":\\"search\\"},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":20,\\"color\\":\\"#2f343d\\"},[{\\"position\\":\\"absolute\\"},{\\"right\\":12}],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}"`;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots SwitchItem Switch 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"minHeight\\":54,\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"space-between\\",\\"flexDirection\\":\\"row\\",\\"maxHeight\\":80,\\"marginBottom\\":12},{\\"backgroundColor\\":\\"#ffffff\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"marginRight\\":8}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Welcome to Rocket.Chat\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"testID\\":\\"create-channel-switch-id-hint\\",\\"style\\":[{\\"fontSize\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#9ca2a8\\"}]},\\"children\\":[\\"Only authorized users can write new messages\\"]}]},{\\"type\\":\\"RCTSwitch\\",\\"props\\":{\\"testID\\":\\"create-channel-switch-id\\",\\"disabled\\":false,\\"onTintColor\\":\\"#2de0a5\\",\\"style\\":{\\"height\\":31,\\"width\\":51},\\"tintColor\\":\\"#f5455c\\",\\"value\\":false,\\"accessibilityRole\\":\\"switch\\"},\\"children\\":null}]}]}"`;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -147,7 +147,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode VERSIONCODE as Integer versionCode VERSIONCODE as Integer
versionName "4.30.0" versionName "4.37.0"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
if (!isFoss) { if (!isFoss) {
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String] manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
@ -250,6 +250,7 @@ android {
release { release {
minifyEnabled enableProguardInReleaseBuilds minifyEnabled enableProguardInReleaseBuilds
setProguardFiles([getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro']) setProguardFiles([getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'])
proguardFile "${rootProject.projectDir}/../node_modules/detox/android/detox/proguard-rules-app.pro"
signingConfig signingConfigs.release signingConfig signingConfigs.release
if (!isFoss) { if (!isFoss) {
firebaseCrashlytics { firebaseCrashlytics {
@ -268,6 +269,11 @@ android {
// pickFirst '**/x86_64/libc++_shared.so' // pickFirst '**/x86_64/libc++_shared.so'
// } // }
// FIXME: Remove when we update RN
packagingOptions {
pickFirst '**/*.so'
}
// applicationVariants are e.g. debug, release // applicationVariants are e.g. debug, release
flavorDimensions "app", "type" flavorDimensions "app", "type"
@ -280,10 +286,6 @@ android {
dimension = "app" dimension = "app"
buildConfigField "boolean", "IS_OFFICIAL", "false" buildConfigField "boolean", "IS_OFFICIAL", "false"
} }
e2e {
dimension = "app"
buildConfigField "boolean", "IS_OFFICIAL", "false"
}
foss { foss {
dimension = "type" dimension = "type"
buildConfigField "boolean", "FDROID_BUILD", "true" buildConfigField "boolean", "FDROID_BUILD", "true"
@ -311,16 +313,6 @@ android {
java.srcDirs = ['src/main/java', 'src/play/java'] java.srcDirs = ['src/main/java', 'src/play/java']
manifest.srcFile 'src/play/AndroidManifest.xml' manifest.srcFile 'src/play/AndroidManifest.xml'
} }
e2ePlayDebug {
java.srcDirs = ['src/main/java', 'src/play/java']
res.srcDirs = ['src/experimental/res']
manifest.srcFile 'src/play/AndroidManifest.xml'
}
e2ePlayRelease {
java.srcDirs = ['src/main/java', 'src/play/java']
res.srcDirs = ['src/experimental/res']
manifest.srcFile 'src/play/AndroidManifest.xml'
}
} }
applicationVariants.all { variant -> applicationVariants.all { variant ->
@ -351,14 +343,12 @@ android {
dependencies { dependencies {
implementation project(':@react-native-community_viewpager') implementation project(':@react-native-community_viewpager')
implementation "androidx.core:core-splashscreen:1.0.0"
playImplementation project(':react-native-notifications') playImplementation project(':react-native-notifications')
playImplementation 'com.google.firebase:firebase-core:16.0.0' playImplementation 'com.google.firebase:firebase-core:16.0.0'
playImplementation project(':@react-native-firebase_app') playImplementation project(':@react-native-firebase_app')
playImplementation project(':@react-native-firebase_analytics') playImplementation project(':@react-native-firebase_analytics')
playImplementation project(':@react-native-firebase_crashlytics') playImplementation project(':@react-native-firebase_crashlytics')
implementation(project(':react-native-jitsi-meet')) { // https://github.com/skrafft/react-native-jitsi-meet#side-note
exclude group: 'com.facebook.react',module:'react-native-svg'
}
implementation fileTree(dir: "libs", include: ["*.jar"]) implementation fileTree(dir: "libs", include: ["*.jar"])
//noinspection GradleDynamicVersion //noinspection GradleDynamicVersion
@ -387,8 +377,9 @@ dependencies {
implementation "com.github.bumptech.glide:glide:4.9.0" implementation "com.github.bumptech.glide:glide:4.9.0"
annotationProcessor "com.github.bumptech.glide:compiler:4.9.0" annotationProcessor "com.github.bumptech.glide:compiler:4.9.0"
implementation "com.tencent:mmkv-static:1.2.10" implementation "com.tencent:mmkv-static:1.2.10"
androidTestImplementation('com.wix:detox:+') { transitive = true } androidTestImplementation('com.wix:detox:+')
androidTestImplementation 'junit:junit:4.12' implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.facebook.soloader:soloader:0.10.4'
} }
if (isNewArchitectureEnabled()) { if (isNewArchitectureEnabled()) {

View File

@ -18,7 +18,7 @@ public class DetoxTest {
@Rule @Rule
// Replace 'MainActivity' with the value of android:name entry in // Replace 'MainActivity' with the value of android:name entry in
// <activity> in AndroidManifest.xml // <activity> in AndroidManifest.xml
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class, false, false); public ActivityTestRule<chat.rocket.reactnative.MainActivity> mActivityRule = new ActivityTestRule<>(chat.rocket.reactnative.MainActivity.class, false, false);
@Test @Test
public void runDetoxTests() { public void runDetoxTests() {

View File

@ -1,25 +0,0 @@
package chat.rocket.reactnative;
import android.content.Context;
import com.facebook.react.ReactInstanceManager;
public class MainDebugApplication extends MainApplication {
@Override
public void onCreate() {
super.onCreate();
initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
}
/**
* Loads Flipper in React Native templates. Call this in the onCreate method with something like
* initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
*
* @param context
* @param reactInstanceManager
*/
private static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
ReactNativeFlipper.initializeFlipper(context, reactInstanceManager);
}
}

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config xmlns:tools="http://schemas.android.com/tools">
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user"
tools:ignore="AcceptsUserCertificates" />
</trust-anchors>
</base-config>
</network-security-config>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@ -5,6 +5,15 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.VIDEO_CAPTURE" />
<uses-permission android:name="android.permission.AUDIO_CAPTURE" />
<!-- permissions related to jitsi call -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<application <application
android:name="chat.rocket.reactnative.MainApplication" android:name="chat.rocket.reactnative.MainApplication"
android:allowBackup="false" android:allowBackup="false"
@ -13,17 +22,9 @@
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
android:requestLegacyExternalStorage="true" android:requestLegacyExternalStorage="true"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:replace="android:allowBackup">
<activity
android:name="com.zoontek.rnbootsplash.RNBootSplashActivity"
android:theme="@style/BootTheme" android:theme="@style/BootTheme"
android:exported="true"> android:hardwareAccelerated="true"
<intent-filter> tools:replace="android:allowBackup">
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity <activity
android:name="chat.rocket.reactnative.MainActivity" android:name="chat.rocket.reactnative.MainActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode" android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
@ -56,6 +57,10 @@
android:host="jitsi.rocket.chat" android:host="jitsi.rocket.chat"
android:scheme="rocketchat" /> android:scheme="rocketchat" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity> </activity>
<activity <activity
android:name="chat.rocket.reactnative.share.ShareActivity" android:name="chat.rocket.reactnative.share.ShareActivity"
@ -63,7 +68,7 @@
android:label="@string/share_extension_name" android:label="@string/share_extension_name"
android:noHistory="true" android:noHistory="true"
android:screenOrientation="portrait" android:screenOrientation="portrait"
android:theme="@style/AppTheme" android:theme="@style/ShareTheme"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
@ -74,5 +79,10 @@
</intent-filter> </intent-filter>
</activity> </activity>
</application> </application>
<queries>
<package android:name="org.jitsi.meet" />
<intent>
<action android:name="android.intent.action.SEND" />
</intent>
</queries>
</manifest> </manifest>

View File

@ -1,24 +1,23 @@
package chat.rocket.reactnative; package chat.rocket.reactnative;
import android.os.Bundle;
import android.content.Intent; import android.content.Intent;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.os.Bundle;
import com.facebook.react.ReactRootView;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactActivity; import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;
import com.zoontek.rnbootsplash.RNBootSplash;
import expo.modules.ReactActivityDelegateWrapper; import expo.modules.ReactActivityDelegateWrapper;
import com.zoontek.rnbootsplash.RNBootSplash;
public class MainActivity extends ReactActivity { public class MainActivity extends ReactActivity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
RNBootSplash.init(this);
// https://github.com/software-mansion/react-native-screens/issues/17#issuecomment-424704067 // https://github.com/software-mansion/react-native-screens/issues/17#issuecomment-424704067
super.onCreate(null); super.onCreate(null);
RNBootSplash.init(R.drawable.launch_screen, MainActivity.this);
} }
@Override @Override

View File

@ -9,13 +9,16 @@ import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage; import com.facebook.react.ReactPackage;
import com.facebook.react.config.ReactFeatureFlags; import com.facebook.react.config.ReactFeatureFlags;
import com.facebook.react.ReactInstanceManager;
import com.facebook.soloader.SoLoader; import com.facebook.soloader.SoLoader;
import com.reactnativecommunity.viewpager.RNCViewPagerPackage; import com.reactnativecommunity.viewpager.RNCViewPagerPackage;
import com.facebook.react.bridge.JSIModulePackage; import com.facebook.react.bridge.JSIModulePackage;
import com.swmansion.reanimated.ReanimatedJSIModulePackage; import com.swmansion.reanimated.ReanimatedJSIModulePackage;
import android.content.Context;
import android.content.res.Configuration; import android.content.res.Configuration;
import expo.modules.ApplicationLifecycleDispatcher; import expo.modules.ApplicationLifecycleDispatcher;
import expo.modules.ReactNativeHostWrapper; import expo.modules.ReactNativeHostWrapper;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -76,6 +79,7 @@ public class MainApplication extends Application implements ReactApplication {
// If you opted-in for the New Architecture, we enable the TurboModule system // If you opted-in for the New Architecture, we enable the TurboModule system
ReactFeatureFlags.useTurboModules = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; ReactFeatureFlags.useTurboModules = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
SoLoader.init(this, /* native exopackage */ false); SoLoader.init(this, /* native exopackage */ false);
initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
ApplicationLifecycleDispatcher.onApplicationCreate(this); ApplicationLifecycleDispatcher.onApplicationCreate(this);
} }
@ -84,4 +88,35 @@ public class MainApplication extends Application implements ReactApplication {
super.onConfigurationChanged(newConfig); super.onConfigurationChanged(newConfig);
ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig); ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig);
} }
/**
* Loads Flipper in React Native templates. Call this in the onCreate method with something like
* initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
*
* @param context
* @param reactInstanceManager
*/
private static void initializeFlipper(
Context context, ReactInstanceManager reactInstanceManager) {
if (BuildConfig.DEBUG) {
try {
/*
We use reflection here to pick up the class that initializes Flipper,
since Flipper library is not available in release mode
*/
Class<?> aClass = Class.forName("chat.rocket.reactnative.ReactNativeFlipper");
aClass
.getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
.invoke(null, context, reactInstanceManager);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
} }

View File

@ -1,8 +1,6 @@
package chat.rocket.reactnative.share; package chat.rocket.reactnative.share;
import com.facebook.react.ReactActivity; import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;
public class ShareActivity extends ReactActivity { public class ShareActivity extends ReactActivity {
@Override @Override

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
<!-- the background color. it can be a system color or a custom one defined in colors.xml -->
<item android:drawable="@color/splashBackground" />
<item>
<!-- the app logo, centered horizontally and vertically -->
<bitmap
android:src="@drawable/splash"
android:gravity="center" />
</item>
</layer-list>

View File

@ -8,25 +8,15 @@
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item> <item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
</style> </style>
<style name="Share.Window" parent="android:Theme"> <style name="ShareTheme" parent="AppTheme">
<item name="android:windowEnterAnimation">@null</item> <item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:windowExitAnimation">@null</item> <item name="android:statusBarColor">@android:color/transparent</item>
</style> </style>
<style name="Theme.Share.Transparent" parent="android:Theme"> <style name="BootTheme" parent="Theme.SplashScreen">
<item name="android:windowIsTranslucent">true</item> <item name="windowSplashScreenBackground">@color/splashBackground</item>
<item name="android:windowBackground">@color/primary_dark</item> <item name="windowSplashScreenAnimatedIcon">@drawable/ic_launcher_foreground</item>
<item name="android:windowContentOverlay">@null</item> <item name="postSplashScreenTheme">@style/AppTheme</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
<item name="android:backgroundDimEnabled">true</item>
<item name="android:windowAnimationStyle">@style/Share.Window</item>
</style>
<style name="BootTheme" parent="AppTheme">
<item name="android:background">@drawable/launch_screen</item>
<item name="colorPrimaryDark">@color/splashBackground</item>
<item name="android:navigationBarColor">@color/splashBackground</item>
</style> </style>
<!-- https://github.com/facebook/react-native/blob/d1ab03235cb4b93304150878d2b9057ab45bba77/ReactAndroid/src/main/res/views/modal/values/themes.xml#L5 --> <!-- https://github.com/facebook/react-native/blob/d1ab03235cb4b93304150878d2b9057ab45bba77/ReactAndroid/src/main/res/views/modal/values/themes.xml#L5 -->

View File

@ -7,4 +7,8 @@
tools:ignore="AcceptsUserCertificates" /> tools:ignore="AcceptsUserCertificates" />
</trust-anchors> </trust-anchors>
</base-config> </base-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">10.0.2.2</domain>
<domain includeSubdomains="true">localhost</domain>
</domain-config>
</network-security-config> </network-security-config>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
<!-- the background color. it can be a system color or a custom one defined in colors.xml -->
<item android:drawable="@color/splashBackground" />
<item>
<!-- the app logo, centered horizontally and vertically -->
<bitmap
android:src="@drawable/splash"
android:gravity="center" />
</item>
</layer-list>

View File

@ -6,7 +6,6 @@
android:name="chat.rocket.reactnative.MainPlayApplication" android:name="chat.rocket.reactnative.MainPlayApplication"
android:label="@string/app_name" android:label="@string/app_name"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:theme="@style/AppTheme"
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
tools:replace="android:name" tools:replace="android:name"
> >

View File

@ -1,5 +1,7 @@
package chat.rocket.reactnative; package chat.rocket.reactnative;
import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_NAME;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationChannel; import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
@ -35,8 +37,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_NAME;
public class CustomPushNotification extends PushNotification { public class CustomPushNotification extends PushNotification {
public static ReactApplicationContext reactApplicationContext; public static ReactApplicationContext reactApplicationContext;
final NotificationManager notificationManager; final NotificationManager notificationManager;
@ -322,7 +322,12 @@ public class CustomPushNotification extends PushNotification {
replyIntent.setAction(KEY_REPLY); replyIntent.setAction(KEY_REPLY);
replyIntent.putExtra("pushNotification", bundle); replyIntent.putExtra("pushNotification", bundle);
PendingIntent replyPendingIntent = PendingIntent.getBroadcast(mContext, notificationId, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent replyPendingIntent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
replyPendingIntent = PendingIntent.getBroadcast(mContext, notificationId, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
} else {
replyPendingIntent = PendingIntent.getBroadcast(mContext, notificationId, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
}
RemoteInput remoteInput = new RemoteInput.Builder(KEY_REPLY) RemoteInput remoteInput = new RemoteInput.Builder(KEY_REPLY)
.setLabel(label) .setLabel(label)
@ -343,12 +348,13 @@ public class CustomPushNotification extends PushNotification {
Intent intent = new Intent(mContext, DismissNotification.class); Intent intent = new Intent(mContext, DismissNotification.class);
intent.putExtra(NOTIFICATION_ID, notificationId); intent.putExtra(NOTIFICATION_ID, notificationId);
PendingIntent dismissPendingIntent = PendingIntent.getBroadcast(mContext, notificationId, intent, 0); PendingIntent dismissPendingIntent = PendingIntent.getBroadcast(mContext, notificationId, intent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
notification.setDeleteIntent(dismissPendingIntent); notification.setDeleteIntent(dismissPendingIntent);
} }
private void notificationLoad(Ejson ejson, Callback callback) { private void notificationLoad(Ejson ejson, Callback callback) {
LoadNotification.load(reactApplicationContext, ejson, callback); LoadNotification loadNotification = new LoadNotification();
loadNotification.load(reactApplicationContext, ejson, callback);
} }
} }

View File

@ -1,20 +1,14 @@
package chat.rocket.reactnative; package chat.rocket.reactnative;
import android.os.Bundle; import android.os.Bundle;
import android.content.Context;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.HttpUrl;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.Interceptor;
import com.google.gson.Gson;
import java.io.IOException;
import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactApplicationContext;
import com.google.gson.Gson;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
class JsonResponse { class JsonResponse {
Data data; Data data;
@ -49,11 +43,11 @@ class JsonResponse {
} }
public class LoadNotification { public class LoadNotification {
private static int RETRY_COUNT = 0; private int RETRY_COUNT = 0;
private static int[] TIMEOUT = new int[]{0, 1, 3, 5, 10}; private int[] TIMEOUT = new int[]{0, 1, 3, 5, 10};
private static String TOKEN_KEY = "reactnativemeteor_usertoken-"; private String TOKEN_KEY = "reactnativemeteor_usertoken-";
public static void load(ReactApplicationContext reactApplicationContext, final Ejson ejson, Callback callback) { public void load(ReactApplicationContext reactApplicationContext, final Ejson ejson, Callback callback) {
final OkHttpClient client = new OkHttpClient(); final OkHttpClient client = new OkHttpClient();
HttpUrl.Builder url = HttpUrl.parse(ejson.serverURL().concat("/api/v1/push.get")).newBuilder(); HttpUrl.Builder url = HttpUrl.parse(ejson.serverURL().concat("/api/v1/push.get")).newBuilder();
@ -73,7 +67,7 @@ public class LoadNotification {
runRequest(client, request, callback); runRequest(client, request, callback);
} }
private static void runRequest(OkHttpClient client, Request request, Callback callback) { private void runRequest(OkHttpClient client, Request request, Callback callback) {
try { try {
Thread.sleep(TIMEOUT[RETRY_COUNT] * 1000); Thread.sleep(TIMEOUT[RETRY_COUNT] * 1000);

View File

@ -1,9 +1,5 @@
import org.apache.tools.ant.taskdefs.condition.Os import org.apache.tools.ant.taskdefs.condition.Os
def safeExtGet(prop, fallback) {
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
def taskRequests = getGradle().getStartParameter().getTaskRequests().toString().toLowerCase() def taskRequests = getGradle().getStartParameter().getTaskRequests().toString().toLowerCase()
@ -13,8 +9,7 @@ buildscript {
buildToolsVersion = "31.0.0" buildToolsVersion = "31.0.0"
minSdkVersion = 23 minSdkVersion = 23
compileSdkVersion = 31 compileSdkVersion = 31
// TODO: Fix "android:exported" issue and target 31 again targetSdkVersion = 31
targetSdkVersion = 30
if (System.properties['os.arch'] == "aarch64") { if (System.properties['os.arch'] == "aarch64") {
// For M1 Users we need to use the NDK 24 which added support for aarch64 // For M1 Users we need to use the NDK 24 which added support for aarch64
ndkVersion = "24.0.8215888" ndkVersion = "24.0.8215888"
@ -27,8 +22,6 @@ buildscript {
kotlinVersion = '1.6.10' kotlinVersion = '1.6.10'
supportLibVersion = "28.0.0" supportLibVersion = "28.0.0"
libre_build = !(isPlay.toBoolean()) libre_build = !(isPlay.toBoolean())
jitsi_url = isPlay ? "https://github.com/RocketChat/jitsi-maven-repository/raw/master/releases" : "https://github.com/RocketChat/jitsi-maven-repository/raw/libre/releases"
jitsi_version = isPlay ? "3.6.0" : "3.6.0-libre"
} }
repositories { repositories {
@ -69,9 +62,6 @@ allprojects {
url "$rootDir/../node_modules/detox/Detox-android" url "$rootDir/../node_modules/detox/Detox-android"
} }
maven {
url jitsi_url
}
mavenCentral { mavenCentral {
content { content {
excludeGroup "com.facebook.react" excludeGroup "com.facebook.react"
@ -81,5 +71,38 @@ allprojects {
google() google()
maven { url 'https://maven.google.com' } maven { url 'https://maven.google.com' }
maven { url 'https://www.jitpack.io' } maven { url 'https://www.jitpack.io' }
// https://stackoverflow.com/a/74333788/5447468
// TODO: remove once we update RN
exclusiveContent {
// We get React Native's Android binaries exclusively through npm,
// from a local Maven repo inside node_modules/react-native/.
// (The use of exclusiveContent prevents looking elsewhere like Maven Central
// and potentially getting a wrong version.)
filter {
includeGroup "com.facebook.react"
}
forRepository {
maven {
// NOTE: if you are in a monorepo, you may have "$rootDir/../../../node_modules/react-native/android"
url "$rootDir/../node_modules/react-native/android"
}
}
}
}
}
subprojects { subproject ->
afterEvaluate {
if (!project.name.equalsIgnoreCase("app") && project.hasProperty("android")) {
android {
compileSdkVersion 31
buildToolsVersion "31.0.0"
defaultConfig {
minSdkVersion 23
targetSdkVersion 31
}
}
}
} }
} }

View File

@ -23,7 +23,7 @@ android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
# Version of flipper SDK to use with React Native # Version of flipper SDK to use with React Native
FLIPPER_VERSION=0.125.0 FLIPPER_VERSION=0.175.0
# Use this property to specify which architecture you want to build. # Use this property to specify which architecture you want to build.
# You can also override it from the CLI using # You can also override it from the CLI using

View File

@ -28,7 +28,9 @@ export const ROOM = createRequestTypes('ROOM', [
'DELETE', 'DELETE',
'REMOVED', 'REMOVED',
'FORWARD', 'FORWARD',
'USER_TYPING' 'USER_TYPING',
'HISTORY_REQUEST',
'HISTORY_FINISHED'
]); ]);
export const INQUIRY = createRequestTypes('INQUIRY', [ export const INQUIRY = createRequestTypes('INQUIRY', [
...defaultTypes, ...defaultTypes,
@ -38,7 +40,14 @@ export const INQUIRY = createRequestTypes('INQUIRY', [
'QUEUE_UPDATE', 'QUEUE_UPDATE',
'QUEUE_REMOVE' 'QUEUE_REMOVE'
]); ]);
export const APP = createRequestTypes('APP', ['START', 'READY', 'INIT', 'INIT_LOCAL_SETTINGS', 'SET_MASTER_DETAIL']); export const APP = createRequestTypes('APP', [
'START',
'READY',
'INIT',
'INIT_LOCAL_SETTINGS',
'SET_MASTER_DETAIL',
'SET_NOTIFICATION_PRESENCE_CAP'
]);
export const MESSAGES = createRequestTypes('MESSAGES', ['REPLY_BROADCAST']); export const MESSAGES = createRequestTypes('MESSAGES', ['REPLY_BROADCAST']);
export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [...defaultTypes]); export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [...defaultTypes]);
export const CREATE_DISCUSSION = createRequestTypes('CREATE_DISCUSSION', [...defaultTypes]); export const CREATE_DISCUSSION = createRequestTypes('CREATE_DISCUSSION', [...defaultTypes]);

View File

@ -12,7 +12,11 @@ interface ISetMasterDetail extends Action {
isMasterDetail: boolean; isMasterDetail: boolean;
} }
export type TActionApp = IAppStart & ISetMasterDetail; interface ISetNotificationPresenceCap extends Action {
show: boolean;
}
export type TActionApp = IAppStart & ISetMasterDetail & ISetNotificationPresenceCap;
interface Params { interface Params {
root: RootEnum; root: RootEnum;
@ -51,3 +55,10 @@ export function setMasterDetail(isMasterDetail: boolean): ISetMasterDetail {
isMasterDetail isMasterDetail
}; };
} }
export function setNotificationPresenceCap(show: boolean): ISetNotificationPresenceCap {
return {
type: APP.SET_NOTIFICATION_PRESENCE_CAP,
show
};
}

View File

@ -13,6 +13,7 @@ interface ILoginRequest extends Action {
credentials: any; credentials: any;
logoutOnError?: boolean; logoutOnError?: boolean;
isFromWebView?: boolean; isFromWebView?: boolean;
registerCustomFields?: any;
} }
interface ILoginSuccess extends Action { interface ILoginSuccess extends Action {
@ -56,13 +57,15 @@ export type TActionsLogin = ILoginRequest &
export function loginRequest( export function loginRequest(
credentials: Partial<ICredentials>, credentials: Partial<ICredentials>,
logoutOnError?: boolean, logoutOnError?: boolean,
isFromWebView?: boolean isFromWebView?: boolean,
registerCustomFields?: any
): ILoginRequest { ): ILoginRequest {
return { return {
type: types.LOGIN.REQUEST, type: types.LOGIN.REQUEST,
credentials, credentials,
logoutOnError, logoutOnError,
isFromWebView isFromWebView,
registerCustomFields
}; };
} }

View File

@ -1,6 +1,6 @@
import { Action } from 'redux'; import { Action } from 'redux';
import { ERoomType } from '../definitions/ERoomType'; import { ERoomType, RoomType } from '../definitions';
import { ROOM } from './actionsTypes'; import { ROOM } from './actionsTypes';
// TYPE RETURN RELATED // TYPE RETURN RELATED
@ -44,7 +44,24 @@ interface IUserTyping extends Action {
status: boolean; status: boolean;
} }
export type TActionsRoom = TSubscribeRoom & TUnsubscribeRoom & ILeaveRoom & IDeleteRoom & IForwardRoom & IUserTyping; export interface IRoomHistoryRequest extends Action {
rid: string;
t: RoomType;
loaderId: string;
}
export interface IRoomHistoryFinished extends Action {
loaderId: string;
}
export type TActionsRoom = TSubscribeRoom &
TUnsubscribeRoom &
ILeaveRoom &
IDeleteRoom &
IForwardRoom &
IUserTyping &
IRoomHistoryRequest &
IRoomHistoryFinished;
export function subscribeRoom(rid: string): TSubscribeRoom { export function subscribeRoom(rid: string): TSubscribeRoom {
return { return {
@ -99,3 +116,19 @@ export function userTyping(rid: string, status = true): IUserTyping {
status status
}; };
} }
export function roomHistoryRequest({ rid, t, loaderId }: { rid: string; t: RoomType; loaderId: string }): IRoomHistoryRequest {
return {
type: ROOM.HISTORY_REQUEST,
rid,
t,
loaderId
};
}
export function roomHistoryFinished({ loaderId }: { loaderId: string }): IRoomHistoryFinished {
return {
type: ROOM.HISTORY_FINISHED,
loaderId
};
}

View File

@ -18,10 +18,10 @@ const HANDLE_HEIGHT = isIOS ? 40 : 56;
const MIN_SNAP_HEIGHT = 16; const MIN_SNAP_HEIGHT = 16;
const CANCEL_HEIGHT = 64; const CANCEL_HEIGHT = 64;
const ANIMATION_DURATION = 250; export const ACTION_SHEET_ANIMATION_DURATION = 250;
const ANIMATION_CONFIG = { const ANIMATION_CONFIG = {
duration: ANIMATION_DURATION, duration: ACTION_SHEET_ANIMATION_DURATION,
// https://easings.net/#easeInOutCubic // https://easings.net/#easeInOutCubic
easing: Easing.bezier(0.645, 0.045, 0.355, 1.0) easing: Easing.bezier(0.645, 0.045, 0.355, 1.0)
}; };
@ -140,7 +140,7 @@ const ActionSheet = React.memo(
style={{ ...styles.container, ...bottomSheet }} style={{ ...styles.container, ...bottomSheet }}
backgroundStyle={{ backgroundColor: colors.focusedBackground }} backgroundStyle={{ backgroundColor: colors.focusedBackground }}
onChange={index => index === -1 && onClose()} onChange={index => index === -1 && onClose()}
// We need this to allow horizontal swipe gestures inside bottom sheet like in reaction picker // We need this to allow horizontal swipe gesture inside the bottom sheet like in reaction picker
enableContentPanningGesture={data?.enableContentPanningGesture ?? true} enableContentPanningGesture={data?.enableContentPanningGesture ?? true}
{...androidTablet} {...androidTablet}
> >

View File

@ -122,7 +122,6 @@ const ActionSheetContentWithInputAndSubmit = ({
}} }}
testID={testID} testID={testID}
secureTextEntry={secureTextEntry} secureTextEntry={secureTextEntry}
inputStyle={{ borderWidth: 2 }}
bottomSheet={isIOS} bottomSheet={isIOS}
/> />
) : null} ) : null}

View File

@ -52,7 +52,11 @@ const BottomSheetContent = React.memo(({ options, hasCancel, hide, children }: I
/> />
); );
} }
return <BottomSheetView style={styles.contentContainer}>{children}</BottomSheetView>; return (
<BottomSheetView testID='action-sheet' style={styles.contentContainer}>
{children}
</BottomSheetView>
);
}); });
export default BottomSheetContent; export default BottomSheetContent;

View File

@ -1 +1,2 @@
export * from './Provider'; export * from './Provider';
export * from './ActionSheet';

View File

@ -52,7 +52,7 @@ export default StyleSheet.create({
paddingHorizontal: 14, paddingHorizontal: 14,
justifyContent: 'center', justifyContent: 'center',
height: ITEM_HEIGHT, height: ITEM_HEIGHT,
borderRadius: 2, borderRadius: 4,
marginBottom: 12 marginBottom: 12
}, },
text: { text: {

View File

@ -43,9 +43,7 @@ const Avatar = React.memo(
let image; let image;
if (emoji) { if (emoji) {
image = ( image = <Emoji getCustomEmoji={getCustomEmoji} isMessageContainsOnlyEmoji literal={emoji} style={avatarStyle} />;
<Emoji baseUrl={server} getCustomEmoji={getCustomEmoji} isMessageContainsOnlyEmoji literal={emoji} style={avatarStyle} />
);
} else { } else {
let uri = avatar; let uri = avatar;
if (!isStatic) { if (!isStatic) {

View File

@ -1,10 +1,11 @@
import React from 'react'; import React from 'react';
import { ViewStyle } from 'react-native';
import { TGetCustomEmoji } from '../../definitions/IEmoji'; import { TGetCustomEmoji } from '../../definitions/IEmoji';
export interface IAvatar { export interface IAvatar {
server?: string; server?: string;
style?: any; style?: ViewStyle;
text?: string; text?: string;
avatar?: string; avatar?: string;
emoji?: string; emoji?: string;

View File

@ -22,7 +22,7 @@ const styles = StyleSheet.create({
paddingHorizontal: 14, paddingHorizontal: 14,
justifyContent: 'center', justifyContent: 'center',
height: 48, height: 48,
borderRadius: 2, borderRadius: 4,
marginBottom: 12 marginBottom: 12
}, },
text: { text: {

View File

@ -0,0 +1,32 @@
import React from 'react';
import { StyleSheet, View } from 'react-native';
import Chip, { IChip } from './index';
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'flex-start',
padding: 16
}
});
export default {
title: 'Chip'
};
const ChipWrapped = ({ avatar, text, onPress, testID, style }: IChip) => (
<View style={styles.container}>
<Chip avatar={avatar} text={text} onPress={onPress} testID={testID} style={style} />
</View>
);
export const ChipText = () => <ChipWrapped avatar='rocket.cat' text={'Rocket.Cat'} onPress={() => {}} />;
export const ChipWithShortText = () => <ChipWrapped avatar='rocket.cat' text={'Short'} onPress={() => {}} />;
export const ChipWithoutAvatar = () => <ChipWrapped text={'Without Avatar'} onPress={() => {}} />;
export const ChipWithoutIcon = () => <ChipWrapped avatar='rocket.cat' text='Without Icon' />;
export const ChipWithoutAvatarAndIcon = () => <ChipWrapped text='Without Avatar and Icon' />;

View File

@ -0,0 +1,58 @@
import React from 'react';
import { fireEvent, render } from '@testing-library/react-native';
import { Provider } from 'react-redux';
import Chip, { IChip } from '.';
import { ISelectedUser } from '../../reducers/selectedUsers';
import { mockedStore as store } from '../../reducers/mockedStore';
const onPressMock = jest.fn((item: any) => item);
const testChip = {
testID: 'test-chip-id',
item: { fname: 'rocket.chat', name: 'rocket.chat' } as ISelectedUser,
onPress: onPressMock
};
const Render = ({ testID, text, avatar, onPress }: IChip) => (
<Provider store={store}>
<Chip testID={testID} text={text} onPress={onPress} avatar={avatar} />
</Provider>
);
describe('Chips', () => {
it('should render the Chip component', () => {
const { findByTestId } = render(
<Render
text={testChip.item.fname}
avatar={testChip.item.name}
testID={testChip.testID}
onPress={() => testChip.onPress(testChip.item)}
/>
);
expect(findByTestId(testChip.testID)).toBeTruthy();
});
it("should not call onPress if it's not passed", async () => {
const { findByTestId } = render(<Render text={testChip.item.fname} avatar={testChip.item.name} testID={testChip.testID} />);
const component = await findByTestId(testChip.testID);
fireEvent.press(component);
expect(onPressMock).not.toHaveBeenCalled();
});
it('should tap Chip and return item', async () => {
const { findByTestId } = render(
<Render
text={testChip.item.fname}
avatar={testChip.item.name}
testID={testChip.testID}
onPress={() => testChip.onPress(testChip.item)}
/>
);
const component = await findByTestId(testChip.testID);
fireEvent.press(component);
expect(onPressMock).toHaveBeenCalled();
expect(onPressMock).toHaveReturnedWith(testChip.item);
});
});

View File

@ -0,0 +1,75 @@
import React from 'react';
import { Pressable, StyleSheet, View, Text, StyleProp, ViewStyle } from 'react-native';
import { useTheme } from '../../theme';
import { CustomIcon } from '../CustomIcon';
import sharedStyles from '../../views/Styles';
import Avatar from '../Avatar';
const styles = StyleSheet.create({
pressable: {
paddingHorizontal: 8,
marginRight: 8,
borderRadius: 4,
justifyContent: 'center',
maxWidth: 192
},
container: {
flexDirection: 'row',
alignItems: 'center'
},
avatar: {
marginRight: 8,
marginVertical: 8
},
textContainer: {
marginRight: 8,
maxWidth: 120
},
name: {
fontSize: 16,
...sharedStyles.textMedium
}
});
export interface IChip {
avatar?: string;
text: string;
onPress?: Function;
testID?: string;
style?: StyleProp<ViewStyle>;
}
const Chip = ({ avatar, text, onPress, testID, style }: IChip) => {
const { colors } = useTheme();
return (
<Pressable
testID={testID}
style={({ pressed }) => [
styles.pressable,
{
backgroundColor: pressed ? colors.bannerBackground : colors.auxiliaryBackground
},
style
]}
disabled={!onPress}
onPress={() => onPress?.()}
android_ripple={{
color: colors.bannerBackground
}}
>
<View style={styles.container}>
{avatar ? <Avatar text={avatar} size={28} style={styles.avatar} /> : null}
<View style={styles.textContainer}>
<Text style={[styles.name, { color: colors.bodyText }]} numberOfLines={1}>
{text}
</Text>
</View>
{onPress ? <CustomIcon name='close' size={16} color={colors.auxiliaryTintColor} /> : null}
</View>
</Pressable>
);
};
export default Chip;

View File

@ -1,7 +1,10 @@
export const mappedIcons = { export const mappedIcons = {
'lamp-bulb': 59812, 'status-disabled': 59837,
'lamp-bulb': 59836,
'phone-in': 59835,
'basketball': 59776, 'basketball': 59776,
'percentage': 59777, 'percentage': 59777,
'glasses': 59812,
'burger': 59813, 'burger': 59813,
'leaf': 59814, 'leaf': 59814,
'airplane': 59815, 'airplane': 59815,

File diff suppressed because one or more lines are too long

View File

@ -1,24 +1,30 @@
import React from 'react'; import React from 'react';
import FastImage from 'react-native-fast-image'; import { StyleProp } from 'react-native';
import FastImage, { ImageStyle } from 'react-native-fast-image';
import { ICustomEmoji } from '../../definitions/IEmoji'; import { useAppSelector } from '../../lib/hooks';
import { ICustomEmoji } from '../../definitions';
interface ICustomEmojiProps {
emoji: ICustomEmoji;
style: StyleProp<ImageStyle>;
}
const CustomEmoji = React.memo( const CustomEmoji = React.memo(
({ baseUrl, emoji, style }: ICustomEmoji) => ( ({ emoji, style }: ICustomEmojiProps) => {
const baseUrl = useAppSelector(state => state.share.server.server || state.server.server);
return (
<FastImage <FastImage
style={style} style={style}
source={{ source={{
uri: `${baseUrl}/emoji-custom/${encodeURIComponent(emoji.content || emoji.name)}.${emoji.extension}`, uri: `${baseUrl}/emoji-custom/${encodeURIComponent(emoji.name)}.${emoji.extension}`,
priority: FastImage.priority.high priority: FastImage.priority.high
}} }}
resizeMode={FastImage.resizeMode.contain} resizeMode={FastImage.resizeMode.contain}
/> />
), );
(prevProps, nextProps) => { },
const prevEmoji = prevProps.emoji.content || prevProps.emoji.name; () => true
const nextEmoji = nextProps.emoji.content || nextProps.emoji.name;
return prevEmoji === nextEmoji;
}
); );
export default CustomEmoji; export default CustomEmoji;

View File

@ -0,0 +1,18 @@
import React from 'react';
import { Text } from 'react-native';
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
import styles from './styles';
import CustomEmoji from './CustomEmoji';
import { IEmoji } from '../../definitions/IEmoji';
interface IEmojiProps {
emoji: IEmoji;
}
export const Emoji = ({ emoji }: IEmojiProps): React.ReactElement => {
if (typeof emoji === 'string') {
return <Text style={styles.categoryEmoji}>{shortnameToUnicode(`:${emoji}:`)}</Text>;
}
return <CustomEmoji style={styles.customCategoryEmoji} emoji={emoji} />;
};

View File

@ -1,75 +1,42 @@
import React from 'react'; import React from 'react';
import { FlatList, Text, TouchableOpacity } from 'react-native'; import { FlatList } from 'react-native-gesture-handler';
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode'; import { IEmoji } from '../../definitions/IEmoji';
import styles from './styles';
import CustomEmoji from './CustomEmoji';
import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps'; import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps';
import { IEmoji, IEmojiCategory } from '../../definitions/IEmoji'; import { PressableEmoji } from './PressableEmoji';
import { EMOJI_BUTTON_SIZE } from './styles';
const EMOJI_SIZE = 50; interface IEmojiCategoryProps {
emojis: IEmoji[];
onEmojiSelected: (emoji: IEmoji) => void;
tabLabel?: string; // needed for react-native-scrollable-tab-view only
parentWidth: number;
}
const renderEmoji = (emoji: IEmoji, size: number, baseUrl: string) => { const EmojiCategory = ({ onEmojiSelected, emojis, parentWidth }: IEmojiCategoryProps): React.ReactElement | null => {
if (emoji && emoji.isCustom) { if (!parentWidth) {
return (
<CustomEmoji
style={[styles.customCategoryEmoji, { height: size - 16, width: size - 16 }]}
emoji={emoji}
baseUrl={baseUrl}
/>
);
}
return (
<Text style={[styles.categoryEmoji, { height: size, width: size, fontSize: size - 14 }]}>
{shortnameToUnicode(`:${emoji}:`)}
</Text>
);
};
class EmojiCategory extends React.Component<IEmojiCategory> {
renderItem(emoji: IEmoji) {
const { baseUrl, onEmojiSelected } = this.props;
return (
<TouchableOpacity
activeOpacity={0.7}
// @ts-ignore
key={emoji && emoji.isCustom ? emoji.content : emoji}
onPress={() => onEmojiSelected(emoji)}
testID={`reaction-picker-${emoji && emoji.isCustom ? emoji.content : emoji}`}
>
{renderEmoji(emoji, EMOJI_SIZE, baseUrl)}
</TouchableOpacity>
);
}
render() {
const { emojis, width } = this.props;
if (!width) {
return null; return null;
} }
const numColumns = Math.trunc(width / EMOJI_SIZE); const numColumns = Math.trunc(parentWidth / EMOJI_BUTTON_SIZE);
const marginHorizontal = (width - numColumns * EMOJI_SIZE) / 2; const marginHorizontal = (parentWidth % EMOJI_BUTTON_SIZE) / 2;
const renderItem = ({ item }: { item: IEmoji }) => <PressableEmoji emoji={item} onPress={onEmojiSelected} />;
return ( return (
<FlatList <FlatList
contentContainerStyle={{ marginHorizontal }} key={`emoji-category-${parentWidth}`}
// rerender FlatList in case of width changes keyExtractor={item => (typeof item === 'string' ? item : item.name)}
key={`emoji-category-${width}`}
// @ts-ignore
keyExtractor={item => (item && item.isCustom && item.content) || item}
data={emojis} data={emojis}
extraData={this.props} renderItem={renderItem}
renderItem={({ item }) => this.renderItem(item)}
numColumns={numColumns} numColumns={numColumns}
initialNumToRender={45} initialNumToRender={45}
removeClippedSubviews removeClippedSubviews
contentContainerStyle={{ marginHorizontal }}
{...scrollPersistTaps} {...scrollPersistTaps}
keyboardDismissMode={'none'} keyboardDismissMode={'none'}
/> />
); );
} };
}
export default EmojiCategory; export default EmojiCategory;

View File

@ -0,0 +1,61 @@
import React, { useState } from 'react';
import { StyleSheet, TextInputProps } from 'react-native';
import { FormTextInput } from '../TextInput/FormTextInput';
import { useTheme } from '../../theme';
import I18n from '../../i18n';
import { isIOS } from '../../lib/methods/helpers';
const styles = StyleSheet.create({
input: {
height: 32,
borderWidth: 0,
paddingVertical: 0,
borderRadius: 4
},
textInputContainer: {
marginBottom: 0
}
});
interface IEmojiSearchBarProps {
onBlur?: TextInputProps['onBlur'];
onChangeText: TextInputProps['onChangeText'];
bottomSheet?: boolean;
}
export const EmojiSearch = ({ onBlur, onChangeText, bottomSheet }: IEmojiSearchBarProps): React.ReactElement => {
const { colors } = useTheme();
const [searchText, setSearchText] = useState<string>('');
const handleTextChange = (text: string) => {
setSearchText(text);
if (onChangeText) {
onChangeText(text);
}
};
return (
<FormTextInput
autoCapitalize='none'
autoCorrect={false}
autoComplete='off'
returnKeyType='search'
textContentType='none'
blurOnSubmit
placeholder={I18n.t('Search_emoji')}
placeholderTextColor={colors.auxiliaryText}
underlineColorAndroid='transparent'
onChangeText={handleTextChange}
inputStyle={[styles.input, { backgroundColor: colors.textInputSecondaryBackground }]}
containerStyle={styles.textInputContainer}
value={searchText}
onClearInput={() => handleTextChange('')}
onBlur={onBlur}
iconRight={'search'}
testID='emoji-searchbar-input'
bottomSheet={bottomSheet && isIOS}
autoFocus={!bottomSheet} // focus on input when not in reaction picker
/>
);
};

View File

@ -0,0 +1,36 @@
import React from 'react';
import { View, Pressable } from 'react-native';
import { useTheme } from '../../theme';
import { CustomIcon } from '../CustomIcon';
import styles from './styles';
import { IFooterProps } from './interfaces';
const BUTTON_HIT_SLOP = { top: 15, right: 15, bottom: 15, left: 15 };
const Footer = ({ onSearchPressed, onBackspacePressed }: IFooterProps): React.ReactElement => {
const { colors } = useTheme();
return (
<View style={[styles.footerContainer, { borderTopColor: colors.borderColor }]}>
<Pressable
onPress={onSearchPressed}
hitSlop={BUTTON_HIT_SLOP}
style={({ pressed }) => [styles.footerButtonsContainer, { opacity: pressed ? 0.7 : 1 }]}
testID='emoji-picker-search'
>
<CustomIcon color={colors.auxiliaryTintColor} size={24} name='search' />
</Pressable>
<Pressable
onPress={onBackspacePressed}
hitSlop={BUTTON_HIT_SLOP}
style={({ pressed }) => [styles.footerButtonsContainer, { opacity: pressed ? 0.7 : 1 }]}
testID='emoji-picker-backspace'
>
<CustomIcon color={colors.auxiliaryTintColor} size={24} name='backspace' />
</Pressable>
</View>
);
};
export default Footer;

View File

@ -0,0 +1,28 @@
import React from 'react';
import { Pressable } from 'react-native';
import styles, { EMOJI_BUTTON_SIZE } from './styles';
import { IEmoji } from '../../definitions/IEmoji';
import { useTheme } from '../../theme';
import { isIOS } from '../../lib/methods/helpers';
import { Emoji } from './Emoji';
export const PressableEmoji = ({ emoji, onPress }: { emoji: IEmoji; onPress: (emoji: IEmoji) => void }): React.ReactElement => {
const { colors } = useTheme();
return (
<Pressable
key={typeof emoji === 'string' ? emoji : emoji.name}
onPress={() => onPress(emoji)}
testID={`emoji-${typeof emoji === 'string' ? emoji : emoji.name}`}
android_ripple={{ color: colors.bannerBackground, borderless: true, radius: EMOJI_BUTTON_SIZE / 2 }}
style={({ pressed }: { pressed: boolean }) => [
styles.emojiButton,
{
backgroundColor: isIOS && pressed ? colors.bannerBackground : 'transparent'
}
]}
>
<Emoji emoji={emoji} />
</Pressable>
);
};

View File

@ -1,56 +1,42 @@
import React from 'react'; import React from 'react';
import { StyleProp, Text, TextStyle, TouchableOpacity, View } from 'react-native'; import { Pressable, View } from 'react-native';
import styles from './styles'; import styles from './styles';
import { themes } from '../../lib/constants'; import { useTheme } from '../../theme';
import { TSupportedThemes } from '../../theme'; import { ITabBarProps } from './interfaces';
import { isIOS } from '../../lib/methods/helpers';
import { CustomIcon } from '../CustomIcon';
interface ITabBarProps { const TabBar = ({ activeTab, tabs, goToPage }: ITabBarProps): React.ReactElement => {
goToPage?: (page: number) => void; const { colors } = useTheme();
activeTab?: number;
tabs?: string[];
tabEmojiStyle: StyleProp<TextStyle>;
theme: TSupportedThemes;
}
export default class TabBar extends React.Component<ITabBarProps> {
shouldComponentUpdate(nextProps: ITabBarProps) {
const { activeTab, theme } = this.props;
if (nextProps.activeTab !== activeTab) {
return true;
}
if (nextProps.theme !== theme) {
return true;
}
return false;
}
render() {
const { tabs, goToPage, tabEmojiStyle, activeTab, theme } = this.props;
return ( return (
<View style={styles.tabsContainer}> <View style={styles.tabsContainer}>
{tabs?.map((tab, i) => ( {tabs?.map((tab, i) => (
<TouchableOpacity <Pressable
activeOpacity={0.7}
key={tab} key={tab}
onPress={() => { onPress={() => goToPage?.(i)}
if (goToPage) { testID={`emoji-picker-tab-${tab}`}
goToPage(i); android_ripple={{ color: colors.bannerBackground }}
style={({ pressed }: { pressed: boolean }) => [
styles.tab,
{
backgroundColor: isIOS && pressed ? colors.bannerBackground : 'transparent'
} }
}} ]}
style={styles.tab}
testID={`reaction-picker-${tab}`}
> >
<Text style={[styles.tabEmoji, tabEmojiStyle]}>{tab}</Text> <CustomIcon name={tab} size={24} color={activeTab === i ? colors.tintColor : colors.auxiliaryTintColor} />
{activeTab === i ? ( <View
<View style={[styles.activeTabLine, { backgroundColor: themes[theme].tintColor }]} /> style={
) : ( activeTab === i
<View style={styles.tabLine} /> ? [styles.activeTabLine, { backgroundColor: colors.tintColor }]
)} : [styles.tabLine, { backgroundColor: colors.borderColor }]
</TouchableOpacity> }
/>
</Pressable>
))} ))}
</View> </View>
); );
} };
}
export default TabBar;

View File

@ -1,155 +1,46 @@
import React, { Component } from 'react'; import React, { useState } from 'react';
import { StyleProp, TextStyle, View } from 'react-native'; import { View } from 'react-native';
import ScrollableTabView from 'react-native-scrollable-tab-view'; import ScrollableTabView from 'react-native-scrollable-tab-view';
import { dequal } from 'dequal';
import { connect } from 'react-redux';
import orderBy from 'lodash/orderBy';
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import { ImageStyle } from 'react-native-fast-image';
import TabBar from './TabBar'; import TabBar from './TabBar';
import EmojiCategory from './EmojiCategory'; import EmojiCategory from './EmojiCategory';
import Footer from './Footer';
import styles from './styles'; import styles from './styles';
import categories from './categories'; import { categories, emojisByCategory } from '../../lib/constants';
import database from '../../lib/database'; import { useTheme } from '../../theme';
import { emojisByCategory } from './emojis'; import { IEmoji, ICustomEmojis } from '../../definitions';
import protectedFunction from '../../lib/methods/helpers/protectedFunction'; import { useAppSelector, useFrequentlyUsedEmoji } from '../../lib/hooks';
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode'; import { addFrequentlyUsed } from '../../lib/methods';
import log from '../../lib/methods/helpers/log'; import { IEmojiPickerProps, EventTypes } from './interfaces';
import { themes } from '../../lib/constants';
import { TSupportedThemes } from '../../theme';
import { IEmoji, TGetCustomEmoji, IApplicationState, ICustomEmojis, TFrequentlyUsedEmojiModel } from '../../definitions';
interface IEmojiPickerProps { const EmojiPicker = ({
isMessageContainsOnlyEmoji?: boolean; onItemClicked,
getCustomEmoji?: TGetCustomEmoji; isEmojiKeyboard = false,
baseUrl: string; searching = false,
customEmojis: ICustomEmojis; searchedEmojis = []
style?: StyleProp<ImageStyle>; }: IEmojiPickerProps): React.ReactElement | null => {
theme: TSupportedThemes; const { colors } = useTheme();
onEmojiSelected: (emoji: string, shortname?: string) => void; const [parentWidth, setParentWidth] = useState(0);
tabEmojiStyle?: StyleProp<TextStyle>;
}
interface IEmojiPickerState { const { frequentlyUsed, loaded } = useFrequentlyUsedEmoji();
frequentlyUsed: (string | { content?: string; extension?: string; isCustom: boolean })[];
customEmojis: any;
show: boolean;
width: number | null;
}
class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> { const allCustomEmojis: ICustomEmojis = useAppSelector(
constructor(props: IEmojiPickerProps) { state => state.customEmojis,
super(props); () => true
const customEmojis = Object.keys(props.customEmojis) );
.filter(item => item === props.customEmojis[item].name) const customEmojis = Object.keys(allCustomEmojis)
.filter(item => item === allCustomEmojis[item].name)
.map(item => ({ .map(item => ({
content: props.customEmojis[item].name, name: allCustomEmojis[item].name,
extension: props.customEmojis[item].extension, extension: allCustomEmojis[item].extension
isCustom: true
})); }));
this.state = {
frequentlyUsed: [],
customEmojis,
show: false,
width: null
};
}
async componentDidMount() { const handleEmojiSelect = (emoji: IEmoji) => {
await this.updateFrequentlyUsed(); onItemClicked(EventTypes.EMOJI_PRESSED, emoji);
this.setState({ show: true }); addFrequentlyUsed(emoji);
}
shouldComponentUpdate(nextProps: IEmojiPickerProps, nextState: IEmojiPickerState) {
const { frequentlyUsed, show, width } = this.state;
const { theme } = this.props;
if (nextProps.theme !== theme) {
return true;
}
if (nextState.show !== show) {
return true;
}
if (nextState.width !== width) {
return true;
}
if (!dequal(nextState.frequentlyUsed, frequentlyUsed)) {
return true;
}
return false;
}
onEmojiSelected = (emoji: IEmoji) => {
try {
const { onEmojiSelected } = this.props;
if (emoji.isCustom) {
this._addFrequentlyUsed({
content: emoji.content,
extension: emoji.extension,
isCustom: true
});
onEmojiSelected(`:${emoji.content}:`);
} else {
const content = emoji;
this._addFrequentlyUsed({ content, isCustom: false });
const shortname = `:${emoji}:`;
onEmojiSelected(shortnameToUnicode(shortname), shortname);
}
} catch (e) {
log(e);
}
}; };
_addFrequentlyUsed = protectedFunction(async (emoji: IEmoji) => { const renderCategory = (category: keyof typeof emojisByCategory, i: number, label: string) => {
const db = database.active;
const freqEmojiCollection = db.get('frequently_used_emojis');
let freqEmojiRecord: TFrequentlyUsedEmojiModel;
try {
freqEmojiRecord = await freqEmojiCollection.find(emoji.content);
} catch (error) {
// Do nothing
}
await db.write(async () => {
if (freqEmojiRecord) {
await freqEmojiRecord.update(f => {
if (f.count) {
f.count += 1;
}
});
} else {
await freqEmojiCollection.create(f => {
f._raw = sanitizedRaw({ id: emoji.content }, freqEmojiCollection.schema);
Object.assign(f, emoji);
f.count = 1;
});
}
});
});
updateFrequentlyUsed = async () => {
const db = database.active;
const frequentlyUsedRecords = await db.get('frequently_used_emojis').query().fetch();
const frequentlyUsedOrdered = orderBy(frequentlyUsedRecords, ['count'], ['desc']);
const frequentlyUsed = frequentlyUsedOrdered.map(item => {
if (item.isCustom) {
return { content: item.content, extension: item.extension, isCustom: item.isCustom };
}
return shortnameToUnicode(`${item.content}`);
});
this.setState({ frequentlyUsed });
};
onLayout = ({
nativeEvent: {
layout: { width }
}
}: any) => this.setState({ width });
renderCategory(category: keyof typeof emojisByCategory, i: number, label: string) {
const { frequentlyUsed, customEmojis, width } = this.state;
const { baseUrl } = this.props;
let emojis = []; let emojis = [];
if (i === 0) { if (i === 0) {
emojis = frequentlyUsed; emojis = frequentlyUsed;
@ -158,49 +49,51 @@ class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
} else { } else {
emojis = emojisByCategory[category]; emojis = emojisByCategory[category];
} }
return ( if (!emojis.length) {
<EmojiCategory
emojis={emojis}
onEmojiSelected={(emoji: IEmoji) => this.onEmojiSelected(emoji)}
style={styles.categoryContainer}
width={width}
baseUrl={baseUrl}
tabLabel={label}
/>
);
}
render() {
const { show, frequentlyUsed } = this.state;
const { tabEmojiStyle, theme } = this.props;
if (!show) {
return null; return null;
} }
return ( return (
<View onLayout={this.onLayout} style={{ flex: 1 }}> <EmojiCategory
parentWidth={parentWidth}
emojis={emojis}
onEmojiSelected={(emoji: IEmoji) => handleEmojiSelect(emoji)}
tabLabel={label}
/>
);
};
if (!loaded) {
return null;
}
return (
<View style={styles.emojiPickerContainer} onLayout={e => setParentWidth(e.nativeEvent.layout.width)}>
{searching ? (
<EmojiCategory
emojis={searchedEmojis}
onEmojiSelected={(emoji: IEmoji) => handleEmojiSelect(emoji)}
parentWidth={parentWidth}
/>
) : (
<ScrollableTabView <ScrollableTabView
renderTabBar={() => <TabBar tabEmojiStyle={tabEmojiStyle} theme={theme} />} renderTabBar={() => <TabBar />}
contentProps={{ contentProps={{
keyboardShouldPersistTaps: 'always', keyboardShouldPersistTaps: 'always',
keyboardDismissMode: 'none' keyboardDismissMode: 'none'
}} }}
style={{ backgroundColor: themes[theme].focusedBackground }} style={{ backgroundColor: colors.messageboxBackground }}
> >
{categories.tabs.map((tab: any, i) => {categories.tabs.map((tab: any, i) => renderCategory(tab.category, i, tab.tabLabel))}
i === 0 && frequentlyUsed.length === 0
? null // when no frequentlyUsed don't show the tab
: this.renderCategory(tab.category, i, tab.tabLabel)
)}
</ScrollableTabView> </ScrollableTabView>
)}
{isEmojiKeyboard && (
<Footer
onSearchPressed={() => onItemClicked(EventTypes.SEARCH_PRESSED)}
onBackspacePressed={() => onItemClicked(EventTypes.BACKSPACE_PRESSED)}
/>
)}
</View> </View>
); );
} };
}
const mapStateToProps = (state: IApplicationState) => ({ export default EmojiPicker;
customEmojis: state.customEmojis,
baseUrl: state.share.server.server || state.server.server
});
export default connect(mapStateToProps)(EmojiPicker);

View File

@ -0,0 +1,26 @@
import { TIconsName } from '../CustomIcon';
import { IEmoji } from '../../definitions';
export enum EventTypes {
EMOJI_PRESSED = 'emojiPressed',
BACKSPACE_PRESSED = 'backspacePressed',
SEARCH_PRESSED = 'searchPressed'
}
export interface IEmojiPickerProps {
onItemClicked: (event: EventTypes, emoji?: IEmoji) => void;
isEmojiKeyboard?: boolean;
searching?: boolean;
searchedEmojis?: IEmoji[];
}
export interface IFooterProps {
onBackspacePressed: () => void;
onSearchPressed: () => void;
}
export interface ITabBarProps {
goToPage?: (page: number) => void;
activeTab?: number;
tabs?: TIconsName[];
}

View File

@ -2,20 +2,23 @@ import { StyleSheet } from 'react-native';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
export const EMOJI_BUTTON_SIZE = 44;
export const EMOJI_SIZE = EMOJI_BUTTON_SIZE - 16;
export default StyleSheet.create({ export default StyleSheet.create({
container: { container: {
flex: 1 flex: 1
}, },
tabsContainer: { tabsContainer: {
height: 45, height: EMOJI_BUTTON_SIZE,
flexDirection: 'row', flexDirection: 'row'
paddingTop: 5
}, },
tab: { tab: {
flex: 1, flex: 1,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
paddingBottom: 10 paddingVertical: 10,
width: EMOJI_BUTTON_SIZE
}, },
tabEmoji: { tabEmoji: {
fontSize: 20, fontSize: 20,
@ -33,7 +36,6 @@ export default StyleSheet.create({
left: 0, left: 0,
right: 0, right: 0,
height: 2, height: 2,
backgroundColor: 'rgba(0,0,0,0.05)',
bottom: 0 bottom: 0
}, },
categoryContainer: { categoryContainer: {
@ -49,10 +51,34 @@ export default StyleSheet.create({
}, },
categoryEmoji: { categoryEmoji: {
...sharedStyles.textAlignCenter, ...sharedStyles.textAlignCenter,
textAlignVertical: 'center',
fontSize: EMOJI_SIZE,
backgroundColor: 'transparent', backgroundColor: 'transparent',
color: '#ffffff' color: '#ffffff'
}, },
customCategoryEmoji: { customCategoryEmoji: {
margin: 8 height: EMOJI_SIZE,
} width: EMOJI_SIZE
},
emojiButton: {
alignItems: 'center',
justifyContent: 'center',
height: EMOJI_BUTTON_SIZE,
width: EMOJI_BUTTON_SIZE
},
footerContainer: {
height: EMOJI_BUTTON_SIZE,
paddingHorizontal: 12,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
borderTopWidth: 1
},
footerButtonsContainer: {
height: EMOJI_BUTTON_SIZE,
width: EMOJI_BUTTON_SIZE,
justifyContent: 'center',
alignItems: 'center'
},
emojiPickerContainer: { flex: 1 }
}); });

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { StyleSheet } from 'react-native'; import { StyleSheet, View } from 'react-native';
import { STATUS_COLORS } from '../../lib/constants';
import UnreadBadge from '../UnreadBadge'; import UnreadBadge from '../UnreadBadge';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@ -15,6 +16,8 @@ const styles = StyleSheet.create({
} }
}); });
export const Badge = ({ ...props }): React.ReactElement => <UnreadBadge {...props} style={styles.badgeContainer} small />; export const BadgeUnread = ({ ...props }): React.ReactElement => <UnreadBadge {...props} style={styles.badgeContainer} small />;
export default Badge; export const BadgeWarn = (): React.ReactElement => (
<View style={[styles.badgeContainer, { width: 10, height: 10, backgroundColor: STATUS_COLORS.disabled }]} />
);

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { View } from 'react-native';
import { Header, HeaderBackground } from '@react-navigation/elements'; import { Header, HeaderBackground } from '@react-navigation/elements';
import { NavigationContainer } from '@react-navigation/native'; import { NavigationContainer } from '@react-navigation/native';
import { SafeAreaProvider } from 'react-native-safe-area-context'; import { SafeAreaProvider } from 'react-native-safe-area-context';
@ -103,9 +104,10 @@ export const Badge = () => (
<HeaderExample <HeaderExample
left={() => ( left={() => (
<HeaderButton.Container left> <HeaderButton.Container left>
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.Badge tunread={[1]} />} /> <HeaderButton.Item iconName='threads' badge={() => <HeaderButton.BadgeUnread tunread={[1]} />} />
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.Badge tunread={[1]} tunreadUser={[1]} />} /> <HeaderButton.Item iconName='threads' badge={() => <HeaderButton.BadgeUnread tunread={[1]} tunreadUser={[1]} />} />
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.Badge tunread={[1]} tunreadGroup={[1]} />} /> <HeaderButton.Item iconName='threads' badge={() => <HeaderButton.BadgeUnread tunread={[1]} tunreadGroup={[1]} />} />
<HeaderButton.Drawer badge={() => <HeaderButton.BadgeWarn />} />
</HeaderButton.Container> </HeaderButton.Container>
)} )}
/> />
@ -114,20 +116,23 @@ export const Badge = () => (
const ThemeStory = ({ theme }: { theme: TSupportedThemes }) => ( const ThemeStory = ({ theme }: { theme: TSupportedThemes }) => (
<ThemeContext.Provider value={{ theme, colors: colors[theme] }}> <ThemeContext.Provider value={{ theme, colors: colors[theme] }}>
<View style={{ flexDirection: 'column' }}>
<HeaderExample <HeaderExample
left={() => ( left={() => (
<HeaderButton.Container left> <HeaderButton.Container left>
<HeaderButton.Drawer badge={() => <HeaderButton.BadgeWarn />} />
<HeaderButton.Item iconName='threads' /> <HeaderButton.Item iconName='threads' />
</HeaderButton.Container> </HeaderButton.Container>
)} )}
right={() => ( right={() => (
<HeaderButton.Container> <HeaderButton.Container>
<HeaderButton.Item title='Threads' /> <HeaderButton.Item title='Threads' />
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.Badge tunread={[1]} />} /> <HeaderButton.Item iconName='threads' badge={() => <HeaderButton.BadgeUnread tunread={[1]} />} />
</HeaderButton.Container> </HeaderButton.Container>
)} )}
colors={colors[theme]} colors={colors[theme]}
/> />
</View>
</ThemeContext.Provider> </ThemeContext.Provider>
); );

View File

@ -1,4 +1,4 @@
export { default as Container } from './HeaderButtonContainer'; export { default as Container } from './HeaderButtonContainer';
export { default as Item } from './HeaderButtonItem'; export { default as Item } from './HeaderButtonItem';
export { default as Badge } from './HeaderButtonItemBadge'; export * from './HeaderButtonItemBadge';
export * from './Common'; export * from './Common';

View File

@ -12,7 +12,6 @@ import { themes } from '../../lib/constants';
import { useTheme } from '../../theme'; import { useTheme } from '../../theme';
import { ROW_HEIGHT } from '../RoomItem'; import { ROW_HEIGHT } from '../RoomItem';
import { goRoom } from '../../lib/methods/helpers/goRoom'; import { goRoom } from '../../lib/methods/helpers/goRoom';
import Navigation from '../../lib/navigation/appNavigation';
import { useOrientation } from '../../dimensions'; import { useOrientation } from '../../dimensions';
import { IApplicationState, ISubscription, SubscriptionType } from '../../definitions'; import { IApplicationState, ISubscription, SubscriptionType } from '../../definitions';
@ -98,12 +97,7 @@ const NotifierComponent = React.memo(({ notification, isMasterDetail }: INotifie
prid prid
}; };
if (isMasterDetail) { goRoom({ item, isMasterDetail, jumpToMessageId: _id, popToRoot: true });
Navigation.navigate('DrawerNavigator');
} else {
Navigation.navigate('RoomsListView');
}
goRoom({ item, isMasterDetail, jumpToMessageId: _id });
hideNotification(); hideNotification();
}; };
@ -124,6 +118,7 @@ const NotifierComponent = React.memo(({ notification, isMasterDetail }: INotifie
onPress={onPress} onPress={onPress}
hitSlop={BUTTON_HIT_SLOP} hitSlop={BUTTON_HIT_SLOP}
background={Touchable.SelectableBackgroundBorderless()} background={Touchable.SelectableBackgroundBorderless()}
testID={`in-app-notification-${text}`}
> >
<> <>
<Avatar text={avatar} size={AVATAR_SIZE} type={type} rid={rid} style={styles.avatar} /> <Avatar text={avatar} size={AVATAR_SIZE} type={type} rid={rid} style={styles.avatar} />

View File

@ -1,19 +1,20 @@
import React, { memo, useEffect } from 'react'; import React, { memo, useEffect } from 'react';
import { Easing, Notifier, NotifierRoot } from 'react-native-notifier'; import { Easing, Notifier, NotifierRoot } from 'react-native-notifier';
import { connect } from 'react-redux';
import { dequal } from 'dequal';
import NotifierComponent, { INotifierComponent } from './NotifierComponent'; import NotifierComponent, { INotifierComponent } from './NotifierComponent';
import EventEmitter from '../../lib/methods/helpers/events'; import EventEmitter from '../../lib/methods/helpers/events';
import Navigation from '../../lib/navigation/appNavigation'; import Navigation from '../../lib/navigation/appNavigation';
import { getActiveRoute } from '../../lib/methods/helpers/navigation'; import { getActiveRoute } from '../../lib/methods/helpers/navigation';
import { IApplicationState } from '../../definitions'; import { useAppSelector } from '../../lib/hooks';
import { IRoom } from '../../reducers/room';
export const INAPP_NOTIFICATION_EMITTER = 'NotificationInApp'; export const INAPP_NOTIFICATION_EMITTER = 'NotificationInApp';
const InAppNotification = memo( const InAppNotification = memo(() => {
({ rooms, appState }: { rooms: IRoom['rooms']; appState: string }) => { const { appState, subscribedRoom } = useAppSelector(state => ({
subscribedRoom: state.room.subscribedRoom,
appState: state.app.ready && state.app.foreground ? 'foreground' : 'background'
}));
const show = (notification: INotifierComponent['notification']) => { const show = (notification: INotifierComponent['notification']) => {
if (appState !== 'foreground') { if (appState !== 'foreground') {
return; return;
@ -23,7 +24,7 @@ const InAppNotification = memo(
const state = Navigation.navigationRef.current?.getRootState(); const state = Navigation.navigationRef.current?.getRootState();
const route = getActiveRoute(state); const route = getActiveRoute(state);
if (payload.rid) { if (payload.rid) {
if (rooms.includes(payload.rid) || route?.name === 'JitsiMeetView') { if (payload.rid === subscribedRoom || route?.name === 'JitsiMeetView') {
return; return;
} }
Notifier.showNotification({ Notifier.showNotification({
@ -41,16 +42,9 @@ const InAppNotification = memo(
return () => { return () => {
EventEmitter.removeListener(INAPP_NOTIFICATION_EMITTER, listener); EventEmitter.removeListener(INAPP_NOTIFICATION_EMITTER, listener);
}; };
}, [rooms]); }, [subscribedRoom, appState]);
return <NotifierRoot />; return <NotifierRoot />;
},
(prevProps, nextProps) => dequal(prevProps.rooms, nextProps.rooms)
);
const mapStateToProps = (state: IApplicationState) => ({
rooms: state.room.rooms,
appState: state.app.ready && state.app.foreground ? 'foreground' : 'background'
}); });
export default connect(mapStateToProps)(InAppNotification); export default InAppNotification;

View File

@ -9,7 +9,7 @@ import { PADDING_HORIZONTAL } from './constants';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
paddingBottom: 12, paddingVertical: 8,
paddingHorizontal: PADDING_HORIZONTAL paddingHorizontal: PADDING_HORIZONTAL
}, },
title: { title: {

View File

@ -5,7 +5,7 @@ import { Header } from '.';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
marginVertical: 16 marginBottom: 16
} }
}); });

View File

@ -2,6 +2,6 @@ import { StyleSheet } from 'react-native';
export const styles = StyleSheet.create({ export const styles = StyleSheet.create({
contentContainerStyleFlatList: { contentContainerStyleFlatList: {
paddingVertical: 32 paddingVertical: 16
} }
}); });

View File

@ -4,7 +4,7 @@ import sharedStyles from '../../views/Styles';
export const BUTTON_HEIGHT = 48; export const BUTTON_HEIGHT = 48;
export const SERVICE_HEIGHT = 58; export const SERVICE_HEIGHT = 58;
export const BORDER_RADIUS = 2; export const BORDER_RADIUS = 4;
export const SERVICES_COLLAPSED_HEIGHT = 174; export const SERVICES_COLLAPSED_HEIGHT = 174;
export default StyleSheet.create({ export default StyleSheet.create({

View File

@ -1,32 +1,29 @@
import React, { useEffect, useState } from 'react'; import React from 'react';
import { FlatList, StyleSheet, Text, View } from 'react-native'; import { FlatList, StyleSheet, Text, View } from 'react-native';
import { TSupportedThemes, useTheme } from '../../theme'; import { TSupportedThemes, useTheme } from '../../theme';
import { themes } from '../../lib/constants'; import { themes } from '../../lib/constants';
import { CustomIcon } from '../CustomIcon'; import { CustomIcon } from '../CustomIcon';
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode'; import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
import { addFrequentlyUsed } from '../../lib/methods';
import { useFrequentlyUsedEmoji } from '../../lib/hooks';
import CustomEmoji from '../EmojiPicker/CustomEmoji'; import CustomEmoji from '../EmojiPicker/CustomEmoji';
import database from '../../lib/database';
import { useDimensions } from '../../dimensions'; import { useDimensions } from '../../dimensions';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import { TAnyMessageModel, TFrequentlyUsedEmojiModel } from '../../definitions'; import { IEmoji, TAnyMessageModel } from '../../definitions';
import Touch from '../Touch'; import Touch from '../Touch';
type TItem = TFrequentlyUsedEmojiModel | string;
export interface IHeader { export interface IHeader {
handleReaction: (emoji: TItem, message: TAnyMessageModel) => void; handleReaction: (emoji: IEmoji | null, message: TAnyMessageModel) => void;
server: string;
message: TAnyMessageModel; message: TAnyMessageModel;
isMasterDetail: boolean; isMasterDetail: boolean;
} }
type TOnReaction = ({ emoji }: { emoji: TItem }) => void; type TOnReaction = ({ emoji }: { emoji?: IEmoji }) => void;
interface THeaderItem { interface THeaderItem {
item: TItem; item: IEmoji;
onReaction: TOnReaction; onReaction: TOnReaction;
server: string;
theme: TSupportedThemes; theme: TSupportedThemes;
} }
@ -64,30 +61,19 @@ const styles = StyleSheet.create({
} }
}); });
const keyExtractor = (item: TItem) => { const HeaderItem = ({ item, onReaction, theme }: THeaderItem) => (
const emojiModel = item as TFrequentlyUsedEmojiModel;
return (emojiModel.id ? emojiModel.content : item) as string;
};
const DEFAULT_EMOJIS = ['clap', '+1', 'heart_eyes', 'grinning', 'thinking_face', 'smiley'];
const HeaderItem = ({ item, onReaction, server, theme }: THeaderItem) => {
const emojiModel = item as TFrequentlyUsedEmojiModel;
const emoji = (emojiModel.id ? emojiModel.content : item) as string;
return (
<Touch <Touch
testID={`message-actions-emoji-${emoji}`} testID={`message-actions-emoji-${item}`}
onPress={() => onReaction({ emoji: `:${emoji}:` })} onPress={() => onReaction({ emoji: item })}
style={[styles.headerItem, { backgroundColor: themes[theme].auxiliaryBackground }]} style={[styles.headerItem, { backgroundColor: themes[theme].auxiliaryBackground }]}
> >
{emojiModel?.isCustom ? ( {typeof item === 'string' ? (
<CustomEmoji style={styles.customEmoji} emoji={emojiModel} baseUrl={server} /> <Text style={styles.headerIcon}>{shortnameToUnicode(`:${item}:`)}</Text>
) : ( ) : (
<Text style={styles.headerIcon}>{shortnameToUnicode(`:${emoji}:`)}</Text> <CustomEmoji style={styles.customEmoji} emoji={item} />
)} )}
</Touch> </Touch>
); );
};
const HeaderFooter = ({ onReaction, theme }: THeaderFooter) => ( const HeaderFooter = ({ onReaction, theme }: THeaderFooter) => (
<Touch <Touch
@ -99,49 +85,37 @@ const HeaderFooter = ({ onReaction, theme }: THeaderFooter) => (
</Touch> </Touch>
); );
const Header = React.memo(({ handleReaction, server, message, isMasterDetail }: IHeader) => { const Header = React.memo(({ handleReaction, message, isMasterDetail }: IHeader) => {
const [items, setItems] = useState<TItem[]>([]);
const { width, height } = useDimensions(); const { width, height } = useDimensions();
const { theme } = useTheme(); const { theme } = useTheme();
const { frequentlyUsed, loaded } = useFrequentlyUsedEmoji(true);
// TODO: create custom hook to re-render based on screen size
const setEmojis = async () => {
try {
const db = database.active;
const freqEmojiCollection = db.get('frequently_used_emojis');
let freqEmojis: TItem[] = await freqEmojiCollection.query().fetch();
const isLandscape = width > height; const isLandscape = width > height;
const size = (isLandscape || isMasterDetail ? width / 2 : width) - CONTAINER_MARGIN * 2; const size = (isLandscape || isMasterDetail ? width / 2 : width) - CONTAINER_MARGIN * 2;
const quantity = size / (ITEM_SIZE + ITEM_MARGIN * 2) - 1; const quantity = Math.trunc(size / (ITEM_SIZE + ITEM_MARGIN * 2) - 1);
freqEmojis = freqEmojis.concat(DEFAULT_EMOJIS).slice(0, quantity); const onReaction: TOnReaction = ({ emoji }) => {
setItems(freqEmojis); handleReaction(emoji || null, message);
} catch { if (emoji) {
// Do nothing addFrequentlyUsed(emoji);
} }
}; };
useEffect(() => { const renderItem = ({ item }: { item: IEmoji }) => <HeaderItem item={item} onReaction={onReaction} theme={theme} />;
setEmojis();
}, []);
const onReaction: TOnReaction = ({ emoji }) => handleReaction(emoji, message);
const renderItem = ({ item }: { item: TItem }) => (
<HeaderItem item={item} onReaction={onReaction} server={server} theme={theme} />
);
const renderFooter = () => <HeaderFooter onReaction={onReaction} theme={theme} />; const renderFooter = () => <HeaderFooter onReaction={onReaction} theme={theme} />;
if (!loaded) {
return null;
}
return ( return (
<View style={[styles.container, { backgroundColor: themes[theme].focusedBackground }]}> <View style={[styles.container, { backgroundColor: themes[theme].focusedBackground }]}>
<FlatList <FlatList
data={items} data={frequentlyUsed.slice(0, quantity)}
renderItem={renderItem} renderItem={renderItem}
ListFooterComponent={renderFooter} ListFooterComponent={renderFooter}
style={{ backgroundColor: themes[theme].focusedBackground }} style={{ backgroundColor: themes[theme].focusedBackground }}
keyExtractor={keyExtractor} keyExtractor={item => (typeof item === 'string' ? item : item.name)}
showsHorizontalScrollIndicator={false} showsHorizontalScrollIndicator={false}
scrollEnabled={false} scrollEnabled={false}
horizontal horizontal

View File

@ -12,12 +12,12 @@ import { getMessageTranslation } from '../message/utils';
import { LISTENER } from '../Toast'; import { LISTENER } from '../Toast';
import EventEmitter from '../../lib/methods/helpers/events'; import EventEmitter from '../../lib/methods/helpers/events';
import { showConfirmationAlert } from '../../lib/methods/helpers/info'; import { showConfirmationAlert } from '../../lib/methods/helpers/info';
import { TActionSheetOptionsItem, useActionSheet } from '../ActionSheet'; import { TActionSheetOptionsItem, useActionSheet, ACTION_SHEET_ANIMATION_DURATION } from '../ActionSheet';
import Header, { HEADER_HEIGHT, IHeader } from './Header'; import Header, { HEADER_HEIGHT, IHeader } from './Header';
import events from '../../lib/methods/helpers/log/events'; import events from '../../lib/methods/helpers/log/events';
import { IApplicationState, ILoggedUser, TAnyMessageModel, TSubscriptionModel } from '../../definitions'; import { IApplicationState, IEmoji, ILoggedUser, TAnyMessageModel, TSubscriptionModel } from '../../definitions';
import { getPermalinkMessage } from '../../lib/methods'; import { getPermalinkMessage } from '../../lib/methods';
import { hasPermission } from '../../lib/methods/helpers'; import { getRoomTitle, getUidDirectMessage, hasPermission } from '../../lib/methods/helpers';
import { Services } from '../../lib/services'; import { Services } from '../../lib/services';
export interface IMessageActionsProps { export interface IMessageActionsProps {
@ -26,7 +26,7 @@ export interface IMessageActionsProps {
user: Pick<ILoggedUser, 'id'>; user: Pick<ILoggedUser, 'id'>;
editInit: (message: TAnyMessageModel) => void; editInit: (message: TAnyMessageModel) => void;
reactionInit: (message: TAnyMessageModel) => void; reactionInit: (message: TAnyMessageModel) => void;
onReactionPress: (shortname: string, messageId: string) => void; onReactionPress: (shortname: IEmoji, messageId: string) => void;
replyInit: (message: TAnyMessageModel, mention: boolean) => void; replyInit: (message: TAnyMessageModel, mention: boolean) => void;
isMasterDetail: boolean; isMasterDetail: boolean;
isReadOnly: boolean; isReadOnly: boolean;
@ -37,11 +37,12 @@ export interface IMessageActionsProps {
Message_AllowPinning?: boolean; Message_AllowPinning?: boolean;
Message_AllowStarring?: boolean; Message_AllowStarring?: boolean;
Message_Read_Receipt_Store_Users?: boolean; Message_Read_Receipt_Store_Users?: boolean;
server: string;
editMessagePermission?: string[]; editMessagePermission?: string[];
deleteMessagePermission?: string[]; deleteMessagePermission?: string[];
forceDeleteMessagePermission?: string[]; forceDeleteMessagePermission?: string[];
deleteOwnMessagePermission?: string[];
pinMessagePermission?: string[]; pinMessagePermission?: string[];
createDirectMessagePermission?: string[];
} }
export interface IMessageActions { export interface IMessageActions {
@ -60,7 +61,6 @@ const MessageActions = React.memo(
onReactionPress, onReactionPress,
replyInit, replyInit,
isReadOnly, isReadOnly,
server,
Message_AllowDeleting, Message_AllowDeleting,
Message_AllowDeleting_BlockDeleteInMinutes, Message_AllowDeleting_BlockDeleteInMinutes,
Message_AllowEditing, Message_AllowEditing,
@ -72,7 +72,9 @@ const MessageActions = React.memo(
editMessagePermission, editMessagePermission,
deleteMessagePermission, deleteMessagePermission,
forceDeleteMessagePermission, forceDeleteMessagePermission,
pinMessagePermission deleteOwnMessagePermission,
pinMessagePermission,
createDirectMessagePermission
}, },
ref ref
) => { ) => {
@ -80,19 +82,27 @@ const MessageActions = React.memo(
hasEditPermission: false, hasEditPermission: false,
hasDeletePermission: false, hasDeletePermission: false,
hasForceDeletePermission: false, hasForceDeletePermission: false,
hasPinPermission: false hasPinPermission: false,
hasDeleteOwnPermission: false
}; };
const { showActionSheet, hideActionSheet } = useActionSheet(); const { showActionSheet, hideActionSheet } = useActionSheet();
const getPermissions = async () => { const getPermissions = async () => {
try { try {
const permission = [editMessagePermission, deleteMessagePermission, forceDeleteMessagePermission, pinMessagePermission]; const permission = [
editMessagePermission,
deleteMessagePermission,
forceDeleteMessagePermission,
pinMessagePermission,
deleteOwnMessagePermission
];
const result = await hasPermission(permission, room.rid); const result = await hasPermission(permission, room.rid);
permissions = { permissions = {
hasEditPermission: result[0], hasEditPermission: result[0],
hasDeletePermission: result[1], hasDeletePermission: result[1],
hasForceDeletePermission: result[2], hasForceDeletePermission: result[2],
hasPinPermission: result[3] hasPinPermission: result[3],
hasDeleteOwnPermission: result[4]
}; };
} catch { } catch {
// Do nothing // Do nothing
@ -134,7 +144,7 @@ const MessageActions = React.memo(
if (tmid === message.id) { if (tmid === message.id) {
return false; return false;
} }
const deleteOwn = isOwn(message); const deleteOwn = isOwn(message) && permissions.hasDeleteOwnPermission;
if (!(permissions.hasDeletePermission || (Message_AllowDeleting && deleteOwn) || permissions.hasForceDeletePermission)) { if (!(permissions.hasDeletePermission || (Message_AllowDeleting && deleteOwn) || permissions.hasForceDeletePermission)) {
return false; return false;
} }
@ -237,6 +247,23 @@ const MessageActions = React.memo(
replyInit(message, false); replyInit(message, false);
}; };
const handleReplyInDM = async (message: TAnyMessageModel) => {
if (message?.u?.username) {
const result = await Services.createDirectMessage(message.u.username);
if (result.success) {
const { room } = result;
const params = {
rid: room.rid,
name: getRoomTitle(room),
t: room.t,
roomUserId: getUidDirectMessage(room),
replyInDM: message
};
Navigation.replace('RoomView', params);
}
}
};
const handleStar = async (message: TAnyMessageModel) => { const handleStar = async (message: TAnyMessageModel) => {
logEvent(message.starred ? events.ROOM_MSG_ACTION_UNSTAR : events.ROOM_MSG_ACTION_STAR); logEvent(message.starred ? events.ROOM_MSG_ACTION_UNSTAR : events.ROOM_MSG_ACTION_STAR);
try { try {
@ -258,15 +285,13 @@ const MessageActions = React.memo(
} }
}; };
const handleReaction: IHeader['handleReaction'] = (shortname, message) => { const handleReaction: IHeader['handleReaction'] = (emoji, message) => {
logEvent(events.ROOM_MSG_ACTION_REACTION); logEvent(events.ROOM_MSG_ACTION_REACTION);
if (shortname) { if (emoji) {
// TODO: evaluate unification with IEmoji onReactionPress(emoji, message.id);
onReactionPress(shortname as any, message.id);
} else { } else {
reactionInit(message); setTimeout(() => reactionInit(message), ACTION_SHEET_ANIMATION_DURATION);
} }
// close actionSheet when click at header
hideActionSheet(); hideActionSheet();
}; };
@ -327,21 +352,11 @@ const MessageActions = React.memo(
}; };
const getOptions = (message: TAnyMessageModel) => { const getOptions = (message: TAnyMessageModel) => {
let options: TActionSheetOptionsItem[] = []; const options: TActionSheetOptionsItem[] = [];
const videoConfBlock = message.t === 'videoconf';
// Reply
if (!isReadOnly) {
options = [
{
title: I18n.t('Reply_in_Thread'),
icon: 'threads',
onPress: () => handleReply(message)
}
];
}
// Quote // Quote
if (!isReadOnly) { if (!isReadOnly && !videoConfBlock) {
options.push({ options.push({
title: I18n.t('Quote'), title: I18n.t('Quote'),
icon: 'quote', icon: 'quote',
@ -349,21 +364,23 @@ const MessageActions = React.memo(
}); });
} }
// Edit // Reply
if (allowEdit(message)) { if (!isReadOnly && !tmid) {
options.push({ options.push({
title: I18n.t('Edit'), title: I18n.t('Reply_in_Thread'),
icon: 'edit', icon: 'threads',
onPress: () => handleEdit(message) onPress: () => handleReply(message)
}); });
} }
// Permalink // Reply in DM
if (room.t !== 'd' && room.t !== 'l' && createDirectMessagePermission && !videoConfBlock) {
options.push({ options.push({
title: I18n.t('Permalink'), title: I18n.t('Reply_in_direct_message'),
icon: 'link', icon: 'arrow-back',
onPress: () => handlePermalink(message) onPress: () => handleReplyInDM(message)
}); });
}
// Create Discussion // Create Discussion
options.push({ options.push({
@ -372,21 +389,21 @@ const MessageActions = React.memo(
onPress: () => handleCreateDiscussion(message) onPress: () => handleCreateDiscussion(message)
}); });
// Mark as unread // Permalink
if (message.u && message.u._id !== user.id) {
options.push({ options.push({
title: I18n.t('Mark_unread'), title: I18n.t('Get_link'),
icon: 'flag', icon: 'link',
onPress: () => handleUnread(message) onPress: () => handlePermalink(message)
}); });
}
// Copy // Copy
if (!videoConfBlock) {
options.push({ options.push({
title: I18n.t('Copy'), title: I18n.t('Copy'),
icon: 'copy', icon: 'copy',
onPress: () => handleCopy(message) onPress: () => handleCopy(message)
}); });
}
// Share // Share
options.push({ options.push({
@ -395,8 +412,26 @@ const MessageActions = React.memo(
onPress: () => handleShare(message) onPress: () => handleShare(message)
}); });
// Edit
if (allowEdit(message) && !videoConfBlock) {
options.push({
title: I18n.t('Edit'),
icon: 'edit',
onPress: () => handleEdit(message)
});
}
// Pin
if (Message_AllowPinning && permissions?.hasPinPermission && !videoConfBlock) {
options.push({
title: I18n.t(message.pinned ? 'Unpin' : 'Pin'),
icon: 'pin',
onPress: () => handlePin(message)
});
}
// Star // Star
if (Message_AllowStarring) { if (Message_AllowStarring && !videoConfBlock) {
options.push({ options.push({
title: I18n.t(message.starred ? 'Unstar' : 'Star'), title: I18n.t(message.starred ? 'Unstar' : 'Star'),
icon: message.starred ? 'star-filled' : 'star', icon: message.starred ? 'star-filled' : 'star',
@ -404,12 +439,12 @@ const MessageActions = React.memo(
}); });
} }
// Pin // Mark as unread
if (Message_AllowPinning && permissions?.hasPinPermission) { if (message.u && message.u._id !== user.id) {
options.push({ options.push({
title: I18n.t(message.pinned ? 'Unpin' : 'Pin'), title: I18n.t('Mark_unread'),
icon: 'pin', icon: 'flag',
onPress: () => handlePin(message) onPress: () => handleUnread(message)
}); });
} }
@ -460,7 +495,7 @@ const MessageActions = React.memo(
headerHeight: HEADER_HEIGHT, headerHeight: HEADER_HEIGHT,
customHeader: customHeader:
!isReadOnly || room.reactWhenReadOnly ? ( !isReadOnly || room.reactWhenReadOnly ? (
<Header server={server} handleReaction={handleReaction} isMasterDetail={isMasterDetail} message={message} /> <Header handleReaction={handleReaction} isMasterDetail={isMasterDetail} message={message} />
) : null ) : null
}); });
}; };
@ -483,8 +518,10 @@ const mapStateToProps = (state: IApplicationState) => ({
isMasterDetail: state.app.isMasterDetail, isMasterDetail: state.app.isMasterDetail,
editMessagePermission: state.permissions['edit-message'], editMessagePermission: state.permissions['edit-message'],
deleteMessagePermission: state.permissions['delete-message'], deleteMessagePermission: state.permissions['delete-message'],
deleteOwnMessagePermission: state.permissions['delete-own-message'],
forceDeleteMessagePermission: state.permissions['force-delete-message'], forceDeleteMessagePermission: state.permissions['force-delete-message'],
pinMessagePermission: state.permissions['pin-message'] pinMessagePermission: state.permissions['pin-message'],
createDirectMessagePermission: state.permissions['create-d']
}); });
export default connect(mapStateToProps, null, null, { forwardRef: true })(MessageActions); export default connect(mapStateToProps, null, null, { forwardRef: true })(MessageActions);

View File

@ -6,22 +6,31 @@ import { Provider } from 'react-redux';
import store from '../../lib/store'; import store from '../../lib/store';
import EmojiPicker from '../EmojiPicker'; import EmojiPicker from '../EmojiPicker';
import styles from './styles'; import styles from './styles';
import { themes } from '../../lib/constants'; import { ThemeContext, TSupportedThemes } from '../../theme';
import { TSupportedThemes } from '../../theme'; import { EventTypes } from '../EmojiPicker/interfaces';
import { IEmoji } from '../../definitions';
import { colors } from '../../lib/constants';
const EmojiKeyboard = ({ theme }: { theme: TSupportedThemes }) => { const EmojiKeyboard = ({ theme }: { theme: TSupportedThemes }) => {
const onEmojiSelected = (emoji: string) => { const onItemClicked = (eventType: EventTypes, emoji?: IEmoji) => {
KeyboardRegistry.onItemSelected('EmojiKeyboard', { emoji }); KeyboardRegistry.onItemSelected('EmojiKeyboard', { eventType, emoji });
}; };
return ( return (
<Provider store={store}> <Provider store={store}>
<ThemeContext.Provider
value={{
theme,
colors: colors[theme]
}}
>
<View <View
style={[styles.emojiKeyboardContainer, { borderTopColor: themes[theme].borderColor }]} style={[styles.emojiKeyboardContainer, { borderTopColor: colors[theme].borderColor }]}
testID='messagebox-keyboard-emoji' testID='messagebox-keyboard-emoji'
> >
<EmojiPicker onEmojiSelected={onEmojiSelected} theme={theme} /> <EmojiPicker onItemClicked={onItemClicked} isEmojiKeyboard={true} />
</View> </View>
</ThemeContext.Provider>
</Provider> </Provider>
); );
}; };

View File

@ -0,0 +1,116 @@
import React, { useState } from 'react';
import { View, Text, Pressable, FlatList, StyleSheet } from 'react-native';
import { useTheme } from '../../theme';
import I18n from '../../i18n';
import { CustomIcon } from '../CustomIcon';
import { IEmoji } from '../../definitions';
import { useFrequentlyUsedEmoji } from '../../lib/hooks';
import { addFrequentlyUsed, searchEmojis } from '../../lib/methods';
import { useDebounce } from '../../lib/methods/helpers';
import sharedStyles from '../../views/Styles';
import { PressableEmoji } from '../EmojiPicker/PressableEmoji';
import { EmojiSearch } from '../EmojiPicker/EmojiSearch';
import { EMOJI_BUTTON_SIZE } from '../EmojiPicker/styles';
import { events, logEvent } from '../../lib/methods/helpers/log';
const BUTTON_HIT_SLOP = { top: 4, right: 4, bottom: 4, left: 4 };
const styles = StyleSheet.create({
listContainer: {
height: EMOJI_BUTTON_SIZE,
margin: 8,
flexGrow: 1
},
container: {
borderTopWidth: 1
},
searchContainer: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
marginRight: 12,
marginBottom: 12
},
backButton: {
width: 32,
height: 32,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 4
},
emptyContainer: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
},
emptyText: {
...sharedStyles.textRegular,
fontSize: 16
},
inputContainer: {
flex: 1
}
});
interface IEmojiSearchBarProps {
openEmoji: () => void;
closeEmoji: () => void;
onEmojiSelected: (emoji: IEmoji) => void;
}
const EmojiSearchBar = ({ openEmoji, closeEmoji, onEmojiSelected }: IEmojiSearchBarProps): React.ReactElement => {
const { colors } = useTheme();
const [searchText, setSearchText] = useState<string>('');
const { frequentlyUsed } = useFrequentlyUsedEmoji(true);
const [emojis, setEmojis] = useState<IEmoji[]>([]);
const handleTextChange = useDebounce(async (text: string) => {
logEvent(events.MB_SB_EMOJI_SEARCH);
setSearchText(text);
const result = await searchEmojis(text);
setEmojis(result);
}, 300);
const handleEmojiSelected = (emoji: IEmoji) => {
logEvent(events.MB_SB_EMOJI_SELECTED);
onEmojiSelected(emoji);
addFrequentlyUsed(emoji);
};
const renderItem = ({ item }: { item: IEmoji }) => <PressableEmoji emoji={item} onPress={handleEmojiSelected} />;
return (
<View style={[styles.container, { borderTopColor: colors.borderColor, backgroundColor: colors.messageboxBackground }]}>
<FlatList
horizontal
data={searchText ? emojis : frequentlyUsed}
renderItem={renderItem}
showsHorizontalScrollIndicator={false}
ListEmptyComponent={() => (
<View style={styles.emptyContainer} testID='no-results-found'>
<Text style={[styles.emptyText, { color: colors.auxiliaryText }]}>{I18n.t('No_results_found')}</Text>
</View>
)}
keyExtractor={item => (typeof item === 'string' ? item : item.name)}
contentContainerStyle={styles.listContainer}
keyboardShouldPersistTaps='always'
/>
<View style={styles.searchContainer}>
<Pressable
style={({ pressed }: { pressed: boolean }) => [styles.backButton, { opacity: pressed ? 0.7 : 1 }]}
onPress={openEmoji}
hitSlop={BUTTON_HIT_SLOP}
testID='openback-emoji-keyboard'
>
<CustomIcon name='chevron-left' size={24} color={colors.auxiliaryTintColor} />
</Pressable>
<View style={styles.inputContainer}>
<EmojiSearch onBlur={closeEmoji} onChangeText={handleTextChange} />
</View>
</View>
</View>
);
};
export default EmojiSearchBar;

View File

@ -1,10 +1,9 @@
import React, { useContext } from 'react'; import React from 'react';
import { Text } from 'react-native'; import { Text } from 'react-native';
import { IEmoji } from '../../../definitions/IEmoji'; import { IEmoji } from '../../../definitions/IEmoji';
import shortnameToUnicode from '../../../lib/methods/helpers/shortnameToUnicode'; import shortnameToUnicode from '../../../lib/methods/helpers/shortnameToUnicode';
import CustomEmoji from '../../EmojiPicker/CustomEmoji'; import CustomEmoji from '../../EmojiPicker/CustomEmoji';
import MessageboxContext from '../Context';
import styles from '../styles'; import styles from '../styles';
interface IMessageBoxMentionEmoji { interface IMessageBoxMentionEmoji {
@ -12,13 +11,10 @@ interface IMessageBoxMentionEmoji {
} }
const MentionEmoji = ({ item }: IMessageBoxMentionEmoji) => { const MentionEmoji = ({ item }: IMessageBoxMentionEmoji) => {
const context = useContext(MessageboxContext); if (typeof item === 'string') {
const { baseUrl } = context;
if (item.name) {
return <CustomEmoji style={styles.mentionItemCustomEmoji} emoji={item} baseUrl={baseUrl} />;
}
return <Text style={styles.mentionItemEmoji}>{shortnameToUnicode(`:${item}:`)}</Text>; return <Text style={styles.mentionItemEmoji}>{shortnameToUnicode(`:${item}:`)}</Text>;
}
return <CustomEmoji style={styles.mentionItemCustomEmoji} emoji={item} />;
}; };
export default MentionEmoji; export default MentionEmoji;

View File

@ -1,5 +1,5 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { Text, TouchableOpacity } from 'react-native'; import { Text, TouchableOpacity, View } from 'react-native';
import { themes } from '../../../lib/constants'; import { themes } from '../../../lib/constants';
import { IEmoji } from '../../../definitions/IEmoji'; import { IEmoji } from '../../../definitions/IEmoji';
@ -37,7 +37,9 @@ const MentionItemContent = React.memo(({ trackingType, item }: IMessageBoxMentio
case MENTIONS_TRACKING_TYPE_COMMANDS: case MENTIONS_TRACKING_TYPE_COMMANDS:
return ( return (
<> <>
<Text style={[styles.slash, { backgroundColor: themes[theme].borderColor, color: themes[theme].tintColor }]}>/</Text> <View style={[styles.slash, { backgroundColor: themes[theme].borderColor }]}>
<Text style={{ color: themes[theme].tintColor }}>/</Text>
</View>
<Text style={[styles.mentionText, { color: themes[theme].titleText }]}>{item.id}</Text> <Text style={[styles.mentionText, { color: themes[theme].titleText }]}>{item.id}</Text>
</> </>
); );

View File

@ -29,7 +29,8 @@ const styles = StyleSheet.create({
}, },
username: { username: {
fontSize: 16, fontSize: 16,
...sharedStyles.textMedium ...sharedStyles.textMedium,
flexShrink: 1
}, },
time: { time: {
fontSize: 12, fontSize: 12,
@ -67,7 +68,7 @@ const ReplyPreview = React.memo(
<View style={[styles.container, { backgroundColor: themes[theme].messageboxBackground }]}> <View style={[styles.container, { backgroundColor: themes[theme].messageboxBackground }]}>
<View style={[styles.messageContainer, { backgroundColor: themes[theme].chatComponentBackground }]}> <View style={[styles.messageContainer, { backgroundColor: themes[theme].chatComponentBackground }]}>
<View style={styles.header}> <View style={styles.header}>
<Text style={[styles.username, { color: themes[theme].tintColor }]}> <Text numberOfLines={1} style={[styles.username, { color: themes[theme].tintColor }]}>
{useRealName ? message.u?.name : message.u?.username} {useRealName ? message.u?.name : message.u?.username}
</Text> </Text>
<Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text> <Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text>

View File

@ -4,5 +4,6 @@ export const MENTIONS_TRACKING_TYPE_COMMANDS = '/';
export const MENTIONS_TRACKING_TYPE_ROOMS = '#'; export const MENTIONS_TRACKING_TYPE_ROOMS = '#';
export const MENTIONS_TRACKING_TYPE_CANNED = '!'; export const MENTIONS_TRACKING_TYPE_CANNED = '!';
export const MENTIONS_COUNT_TO_DISPLAY = 4; export const MENTIONS_COUNT_TO_DISPLAY = 4;
export const MAX_EMOJIS_TO_DISPLAY = 20;
export const TIMEOUT_CLOSE_EMOJI = 300; export const TIMEOUT_CLOSE_EMOJI = 300;

View File

@ -1,5 +1,5 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Alert, Keyboard, NativeModules, Text, View } from 'react-native'; import { Alert, Keyboard, NativeModules, Text, View, BackHandler } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { KeyboardAccessoryView } from 'react-native-ui-lib/keyboard'; import { KeyboardAccessoryView } from 'react-native-ui-lib/keyboard';
import ImagePicker, { Image, ImageOrVideo, Options } from 'react-native-image-crop-picker'; import ImagePicker, { Image, ImageOrVideo, Options } from 'react-native-image-crop-picker';
@ -13,12 +13,11 @@ import { TextInput, IThemedTextInput } from '../TextInput';
import { userTyping as userTypingAction } from '../../actions/room'; import { userTyping as userTypingAction } from '../../actions/room';
import styles from './styles'; import styles from './styles';
import database from '../../lib/database'; import database from '../../lib/database';
import { emojis } from '../EmojiPicker/emojis';
import log, { events, logEvent } from '../../lib/methods/helpers/log'; import log, { events, logEvent } from '../../lib/methods/helpers/log';
import RecordAudio from './RecordAudio'; import RecordAudio from './RecordAudio';
import I18n from '../../i18n'; import I18n from '../../i18n';
import ReplyPreview from './ReplyPreview'; import ReplyPreview from './ReplyPreview';
import { themes } from '../../lib/constants'; import { themes, emojis } from '../../lib/constants';
import LeftButtons from './LeftButtons'; import LeftButtons from './LeftButtons';
import RightButtons from './RightButtons'; import RightButtons from './RightButtons';
import { canUploadFile } from '../../lib/methods/helpers/media'; import { canUploadFile } from '../../lib/methods/helpers/media';
@ -51,14 +50,18 @@ import {
TGetCustomEmoji, TGetCustomEmoji,
TSubscriptionModel, TSubscriptionModel,
TThreadModel, TThreadModel,
IMessage IMessage,
IEmoji
} from '../../definitions'; } from '../../definitions';
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types'; import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
import { getPermalinkMessage, search, sendFileMessage } from '../../lib/methods'; import { getPermalinkMessage, search, sendFileMessage } from '../../lib/methods';
import { hasPermission, debounce, isAndroid, isIOS, isTablet } from '../../lib/methods/helpers'; import { hasPermission, debounce, isAndroid, isIOS, isTablet, compareServerVersion } from '../../lib/methods/helpers';
import { Services } from '../../lib/services'; import { Services } from '../../lib/services';
import { TSupportedThemes } from '../../theme'; import { TSupportedThemes } from '../../theme';
import { ChatsStackParamList } from '../../stacks/types'; import { ChatsStackParamList } from '../../stacks/types';
import { EventTypes } from '../EmojiPicker/interfaces';
import EmojiSearchbar from './EmojiSearchbar';
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
require('./EmojiKeyboard'); require('./EmojiKeyboard');
@ -111,8 +114,8 @@ export interface IMessageBoxProps extends IBaseScreen<ChatsStackParamList & Mast
isActionsEnabled: boolean; isActionsEnabled: boolean;
usedCannedResponse: string; usedCannedResponse: string;
uploadFilePermission: string[]; uploadFilePermission: string[];
serverVersion: string;
goToCannedResponses: () => void | null; goToCannedResponses: () => void | null;
serverVersion: string;
} }
interface IMessageBoxState { interface IMessageBoxState {
@ -129,6 +132,7 @@ interface IMessageBoxState {
tshow: boolean; tshow: boolean;
mentionLoading: boolean; mentionLoading: boolean;
permissionToUpload: boolean; permissionToUpload: boolean;
showEmojiSearchbar: boolean;
} }
class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> { class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
@ -181,9 +185,10 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
commandPreview: [], commandPreview: [],
showCommandPreview: false, showCommandPreview: false,
command: {}, command: {},
tshow: false, tshow: this.sendThreadToChannel,
mentionLoading: false, mentionLoading: false,
permissionToUpload: true permissionToUpload: true,
showEmojiSearchbar: false
}; };
this.text = ''; this.text = '';
this.selection = { start: 0, end: 0 }; this.selection = { start: 0, end: 0 };
@ -209,11 +214,30 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
...videoPickerConfig, ...videoPickerConfig,
...libPickerLabels ...libPickerLabels
}; };
BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
}
get sendThreadToChannel() {
const { user, serverVersion, tmid } = this.props;
if (tmid && compareServerVersion(serverVersion, 'lowerThan', '5.0.0')) {
return false;
}
if (tmid && user.alsoSendThreadToChannel === 'default') {
return false;
}
if (user.alsoSendThreadToChannel === 'always') {
return true;
}
if (user.alsoSendThreadToChannel === 'never') {
return false;
}
return true;
} }
async componentDidMount() { async componentDidMount() {
const db = database.active; const db = database.active;
const { rid, tmid, navigation, sharing, usedCannedResponse, isMasterDetail } = this.props; const { rid, tmid, navigation, sharing, usedCannedResponse } = this.props;
let msg; let msg;
try { try {
const threadsCollection = db.get('threads'); const threadsCollection = db.get('threads');
@ -248,7 +272,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
EventEmiter.addEventListener(KEY_COMMAND, this.handleCommands); EventEmiter.addEventListener(KEY_COMMAND, this.handleCommands);
} }
if (isMasterDetail && usedCannedResponse) { if (usedCannedResponse) {
this.onChangeText(usedCannedResponse); this.onChangeText(usedCannedResponse);
} }
@ -278,7 +302,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
if (usedCannedResponse !== nextProps.usedCannedResponse) { if (usedCannedResponse !== nextProps.usedCannedResponse) {
this.onChangeText(nextProps.usedCannedResponse ?? ''); this.onChangeText(nextProps.usedCannedResponse ?? '');
} }
if (sharing) { if (sharing && !replying) {
this.setInput(nextProps.message.msg ?? ''); this.setInput(nextProps.message.msg ?? '');
return; return;
} }
@ -309,7 +333,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
tshow, tshow,
mentionLoading, mentionLoading,
trackingType, trackingType,
permissionToUpload permissionToUpload,
showEmojiSearchbar
} = this.state; } = this.state;
const { const {
@ -329,6 +354,9 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
if (nextState.showEmojiKeyboard !== showEmojiKeyboard) { if (nextState.showEmojiKeyboard !== showEmojiKeyboard) {
return true; return true;
} }
if (nextState.showEmojiSearchbar !== showEmojiSearchbar) {
return true;
}
if (!isFocused()) { if (!isFocused()) {
return false; return false;
} }
@ -381,7 +409,12 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
} }
componentDidUpdate(prevProps: IMessageBoxProps) { componentDidUpdate(prevProps: IMessageBoxProps) {
const { uploadFilePermission, goToCannedResponses } = this.props; const { uploadFilePermission, goToCannedResponses, replyWithMention, threadsEnabled } = this.props;
if (prevProps.replyWithMention !== replyWithMention) {
if (threadsEnabled && replyWithMention) {
this.setState({ tshow: this.sendThreadToChannel });
}
}
if (!dequal(prevProps.uploadFilePermission, uploadFilePermission) || prevProps.goToCannedResponses !== goToCannedResponses) { if (!dequal(prevProps.uploadFilePermission, uploadFilePermission) || prevProps.goToCannedResponses !== goToCannedResponses) {
this.setOptions(); this.setOptions();
} }
@ -416,6 +449,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
if (isTablet) { if (isTablet) {
EventEmiter.removeListener(KEY_COMMAND, this.handleCommands); EventEmiter.removeListener(KEY_COMMAND, this.handleCommands);
} }
BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress);
} }
setOptions = async () => { setOptions = async () => {
@ -497,7 +531,10 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
}, 100); }, 100);
onKeyboardResigned = () => { onKeyboardResigned = () => {
const { showEmojiSearchbar } = this.state;
if (!showEmojiSearchbar) {
this.closeEmoji(); this.closeEmoji();
}
}; };
onPressMention = (item: any) => { onPressMention = (item: any) => {
@ -555,18 +592,50 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
} }
}; };
onEmojiSelected = (keyboardId: string, params: { emoji: string }) => { onKeyboardItemSelected = (keyboardId: string, params: { eventType: EventTypes; emoji: IEmoji }) => {
const { eventType, emoji } = params;
const { text } = this; const { text } = this;
const { emoji } = params;
let newText = ''; let newText = '';
// if messagebox has an active cursor // if messagebox has an active cursor
const { start, end } = this.selection; const { start, end } = this.selection;
const cursor = Math.max(start, end); const cursor = Math.max(start, end);
newText = `${text.substr(0, cursor)}${emoji}${text.substr(cursor)}`; let newCursor;
const newCursor = cursor + emoji.length;
switch (eventType) {
case EventTypes.BACKSPACE_PRESSED:
logEvent(events.MB_BACKSPACE);
const emojiRegex = /\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff]/;
let charsToRemove = 1;
const lastEmoji = text.substr(cursor > 0 ? cursor - 2 : text.length - 2, cursor > 0 ? cursor : text.length);
// Check if last character is an emoji
if (emojiRegex.test(lastEmoji)) charsToRemove = 2;
newText =
text.substr(0, (cursor > 0 ? cursor : text.length) - charsToRemove) + text.substr(cursor > 0 ? cursor : text.length);
newCursor = cursor - charsToRemove;
this.setInput(newText, { start: newCursor, end: newCursor });
this.setShowSend(newText !== '');
break;
case EventTypes.EMOJI_PRESSED:
logEvent(events.MB_EMOJI_SELECTED);
let emojiText = '';
if (typeof emoji === 'string') {
const shortname = `:${emoji}:`;
emojiText = shortnameToUnicode(shortname);
} else {
emojiText = `:${emoji.name}:`;
}
newText = `${text.substr(0, cursor)}${emojiText}${text.substr(cursor)}`;
newCursor = cursor + emojiText.length;
this.setInput(newText, { start: newCursor, end: newCursor }); this.setInput(newText, { start: newCursor, end: newCursor });
this.setShowSend(true); this.setShowSend(true);
break;
case EventTypes.SEARCH_PRESSED:
logEvent(events.MB_EMOJI_SEARCH_PRESSED);
this.setState({ showEmojiKeyboard: false, showEmojiSearchbar: true });
break;
default:
// Do nothing
}
}; };
getPermalink = async (message: any) => { getPermalink = async (message: any) => {
@ -589,7 +658,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
}; };
getUsers = debounce(async (keyword: any) => { getUsers = debounce(async (keyword: any) => {
let res = await search({ text: keyword, filterRooms: false, filterUsers: true }); const { rid } = this.props;
let res = await search({ text: keyword, filterRooms: false, filterUsers: true, rid });
res = [...this.getFixedMentions(keyword), ...res]; res = [...this.getFixedMentions(keyword), ...res];
this.setState({ mentions: res, mentionLoading: false }); this.setState({ mentions: res, mentionLoading: false });
}, 300); }, 300);
@ -599,16 +669,20 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
this.setState({ mentions: res, mentionLoading: false }); this.setState({ mentions: res, mentionLoading: false });
}, 300); }, 300);
getEmojis = debounce(async (keyword: any) => { getCustomEmojis = async (keyword: any, count: number) => {
const db = database.active;
const customEmojisCollection = db.get('custom_emojis');
const likeString = sanitizeLikeString(keyword); const likeString = sanitizeLikeString(keyword);
const whereClause = []; const whereClause = [];
if (likeString) { if (likeString) {
whereClause.push(Q.where('name', Q.like(`${likeString}%`))); whereClause.push(Q.where('name', Q.like(`${likeString}%`)));
} }
let customEmojis = await customEmojisCollection.query(...whereClause).fetch(); const db = database.active;
customEmojis = customEmojis.slice(0, MENTIONS_COUNT_TO_DISPLAY); const customEmojisCollection = db.get('custom_emojis');
const customEmojis = await (await customEmojisCollection.query(...whereClause).fetch()).slice(0, count);
return customEmojis;
};
getEmojis = debounce(async (keyword: any) => {
const customEmojis = await this.getCustomEmojis(keyword, MENTIONS_COUNT_TO_DISPLAY);
const filteredEmojis = emojis.filter(emoji => emoji.indexOf(keyword) !== -1).slice(0, MENTIONS_COUNT_TO_DISPLAY); const filteredEmojis = emojis.filter(emoji => emoji.indexOf(keyword) !== -1).slice(0, MENTIONS_COUNT_TO_DISPLAY);
const mergedEmojis = [...customEmojis, ...filteredEmojis].slice(0, MENTIONS_COUNT_TO_DISPLAY); const mergedEmojis = [...customEmojis, ...filteredEmojis].slice(0, MENTIONS_COUNT_TO_DISPLAY);
this.setState({ mentions: mergedEmojis || [], mentionLoading: false }); this.setState({ mentions: mergedEmojis || [], mentionLoading: false });
@ -687,9 +761,13 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
}; };
clearInput = () => { clearInput = () => {
const { tshow } = this.state;
const { user, serverVersion } = this.props;
this.setInput(''); this.setInput('');
this.setShowSend(false); this.setShowSend(false);
if (compareServerVersion(serverVersion, 'lowerThan', '5.0.0') || (tshow && user.alsoSendThreadToChannel === 'default')) {
this.setState({ tshow: false }); this.setState({ tshow: false });
}
}; };
canUploadFile = (file: any) => { canUploadFile = (file: any) => {
@ -779,14 +857,21 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
}; };
openShareView = (attachments: any) => { openShareView = (attachments: any) => {
const { message, replyCancel, replyWithMention } = this.props; const { message, replyCancel, replyWithMention, replying } = this.props;
// Start a thread with an attachment // Start a thread with an attachment
let value: TThreadModel | IMessage = this.thread; let value: TThreadModel | IMessage = this.thread;
if (replyWithMention) { if (replyWithMention) {
value = message; value = message;
replyCancel(); replyCancel();
} }
Navigation.navigate('ShareView', { room: this.room, thread: value, attachments }); Navigation.navigate('ShareView', {
room: this.room,
thread: value,
attachments,
replying,
replyingMessage: message,
closeReply: replyCancel
});
}; };
createDiscussion = () => { createDiscussion = () => {
@ -855,7 +940,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
openEmoji = () => { openEmoji = () => {
logEvent(events.ROOM_OPEN_EMOJI); logEvent(events.ROOM_OPEN_EMOJI);
this.setState({ showEmojiKeyboard: true }); this.setState({ showEmojiKeyboard: true, showEmojiSearchbar: false });
this.stopTrackingMention();
}; };
recordingCallback = (recording: any) => { recordingCallback = (recording: any) => {
@ -877,7 +963,13 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
}; };
closeEmoji = () => { closeEmoji = () => {
this.setState({ showEmojiKeyboard: false }); this.setState({ showEmojiKeyboard: false, showEmojiSearchbar: false });
};
closeEmojiKeyboardAndFocus = () => {
logEvent(events.ROOM_CLOSE_EMOJI);
this.closeEmoji();
this.focus();
}; };
closeEmojiAndAction = (action?: Function, params?: any) => { closeEmojiAndAction = (action?: Function, params?: any) => {
@ -900,7 +992,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
this.clearInput(); this.clearInput();
this.debouncedOnChangeText.stop(); this.debouncedOnChangeText.stop();
this.closeEmoji(); this.closeEmojiKeyboardAndFocus();
this.stopTrackingMention(); this.stopTrackingMention();
this.handleTyping(false); this.handleTyping(false);
if (message.trim() === '' && !showSend) { if (message.trim() === '' && !showSend) {
@ -957,7 +1049,20 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
// Legacy reply or quote (quote is a reply without mention) // Legacy reply or quote (quote is a reply without mention)
} else { } else {
const { user, roomType } = this.props; const msg = await this.formatReplyMessage(replyingMessage, message);
onSubmit(msg);
}
replyCancel();
// Normal message
} else {
// @ts-ignore
onSubmit(message, undefined, tmid ? tshow : false);
}
};
formatReplyMessage = async (replyingMessage: IMessage, message = '') => {
const { user, roomType, replyWithMention, serverVersion } = this.props;
const permalink = await this.getPermalink(replyingMessage); const permalink = await this.getPermalink(replyingMessage);
let msg = `[ ](${permalink}) `; let msg = `[ ](${permalink}) `;
@ -966,16 +1071,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
msg += `@${replyingMessage?.u?.username} `; msg += `@${replyingMessage?.u?.username} `;
} }
msg = `${msg} ${message}`; const connectionString = compareServerVersion(serverVersion, 'lowerThan', '5.0.0') ? ' ' : '\n';
onSubmit(msg); return `${msg}${connectionString}${message}`;
}
replyCancel();
// Normal message
} else {
// @ts-ignore
onSubmit(message, undefined, tshow);
}
}; };
updateMentions = (keyword: any, type: string) => { updateMentions = (keyword: any, type: string) => {
@ -1044,7 +1141,12 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
onPress={this.onPressSendToChannel} onPress={this.onPressSendToChannel}
testID='messagebox-send-to-channel' testID='messagebox-send-to-channel'
> >
<CustomIcon name={tshow ? 'checkbox-checked' : 'checkbox-unchecked'} size={24} color={themes[theme].auxiliaryText} /> <CustomIcon
testID={tshow ? 'send-to-channel-checked' : 'send-to-channel-unchecked'}
name={tshow ? 'checkbox-checked' : 'checkbox-unchecked'}
size={24}
color={themes[theme].auxiliaryText}
/>
<Text style={[styles.sendToChannelText, { color: themes[theme].auxiliaryText }]}> <Text style={[styles.sendToChannelText, { color: themes[theme].auxiliaryText }]}>
{I18n.t('Messagebox_Send_to_channel')} {I18n.t('Messagebox_Send_to_channel')}
</Text> </Text>
@ -1052,10 +1154,34 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
); );
}; };
renderEmojiSearchbar = () => {
const { showEmojiSearchbar } = this.state;
return showEmojiSearchbar ? (
<EmojiSearchbar
openEmoji={this.openEmoji}
closeEmoji={this.closeEmoji}
onEmojiSelected={(emoji: IEmoji) => {
this.onKeyboardItemSelected('EmojiKeyboard', { eventType: EventTypes.EMOJI_PRESSED, emoji });
}}
/>
) : null;
};
handleBackPress = () => {
const { showEmojiSearchbar } = this.state;
if (showEmojiSearchbar) {
this.setState({ showEmojiSearchbar: false });
return true;
}
return false;
};
renderContent = () => { renderContent = () => {
const { const {
recording, recording,
showEmojiKeyboard, showEmojiKeyboard,
showEmojiSearchbar,
showSend, showSend,
mentions, mentions,
trackingType, trackingType,
@ -1118,11 +1244,11 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
const textInputAndButtons = !recording ? ( const textInputAndButtons = !recording ? (
<> <>
<LeftButtons <LeftButtons
showEmojiKeyboard={showEmojiKeyboard} showEmojiKeyboard={showEmojiKeyboard || showEmojiSearchbar}
editing={editing} editing={editing}
editCancel={this.editCancel} editCancel={this.editCancel}
openEmoji={this.openEmoji} openEmoji={this.openEmoji}
closeEmoji={this.closeEmoji} closeEmoji={this.closeEmojiKeyboardAndFocus}
/> />
<TextInput <TextInput
ref={component => (this.component = component)} ref={component => (this.component = component)}
@ -1166,6 +1292,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
{recordAudio} {recordAudio}
</View> </View>
{this.renderSendToChannel()} {this.renderSendToChannel()}
{this.renderEmojiSearchbar()}
</View> </View>
{children} {children}
</> </>
@ -1193,7 +1320,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
kbComponent={showEmojiKeyboard ? 'EmojiKeyboard' : null} kbComponent={showEmojiKeyboard ? 'EmojiKeyboard' : null}
kbInitialProps={{ theme }} kbInitialProps={{ theme }}
onKeyboardResigned={this.onKeyboardResigned} onKeyboardResigned={this.onKeyboardResigned}
onItemSelected={this.onEmojiSelected} onItemSelected={this.onKeyboardItemSelected}
trackInteractive trackInteractive
requiresSameParentToManageScrollView requiresSameParentToManageScrollView
addBottomView addBottomView
@ -1213,7 +1340,8 @@ const mapStateToProps = (state: IApplicationState) => ({
FileUpload_MediaTypeWhiteList: state.settings.FileUpload_MediaTypeWhiteList, FileUpload_MediaTypeWhiteList: state.settings.FileUpload_MediaTypeWhiteList,
FileUpload_MaxFileSize: state.settings.FileUpload_MaxFileSize, FileUpload_MaxFileSize: state.settings.FileUpload_MaxFileSize,
Message_AudioRecorderEnabled: state.settings.Message_AudioRecorderEnabled, Message_AudioRecorderEnabled: state.settings.Message_AudioRecorderEnabled,
uploadFilePermission: state.permissions['mobile-upload-file'] uploadFilePermission: state.permissions['mobile-upload-file'],
serverVersion: state.server.version
}); });
const dispatchToProps = { const dispatchToProps = {

View File

@ -105,7 +105,7 @@ export default StyleSheet.create({
}, },
emojiKeyboardContainer: { emojiKeyboardContainer: {
flex: 1, flex: 1,
borderTopWidth: StyleSheet.hairlineWidth borderTopWidth: 1
}, },
slash: { slash: {
height: 30, height: 30,
@ -113,7 +113,7 @@ export default StyleSheet.create({
padding: 5, padding: 5,
paddingHorizontal: 12, paddingHorizontal: 12,
marginHorizontal: 10, marginHorizontal: 10,
borderRadius: 2 borderRadius: 4
}, },
commandPreviewImage: { commandPreviewImage: {
justifyContent: 'center', justifyContent: 'center',

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { Text } from 'react-native'; import { Text, StyleProp, ViewStyle } from 'react-native';
import styles from './styles'; import styles from './styles';
import { themes } from '../../../lib/constants'; import { themes } from '../../../lib/constants';
@ -12,16 +12,17 @@ interface IPasscodeButton {
icon?: TIconsName; icon?: TIconsName;
disabled?: boolean; disabled?: boolean;
onPress?: Function; onPress?: Function;
style?: StyleProp<ViewStyle>;
} }
const Button = React.memo(({ text, disabled, onPress, icon }: IPasscodeButton) => { const Button = React.memo(({ style, text, disabled, onPress, icon }: IPasscodeButton) => {
const { theme } = useTheme(); const { theme } = useTheme();
const press = () => onPress && onPress(text); const press = () => onPress && onPress(text);
return ( return (
<Touch <Touch
style={[styles.buttonView, { backgroundColor: 'transparent' }]} style={[styles.buttonView, { backgroundColor: 'transparent' }, style]}
underlayColor={themes[theme].passcodeButtonActive} underlayColor={themes[theme].passcodeButtonActive}
rippleColor={themes[theme].passcodeButtonActive} rippleColor={themes[theme].passcodeButtonActive}
enabled={!disabled} enabled={!disabled}

View File

@ -1,9 +1,10 @@
import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react'; import React, { forwardRef, useImperativeHandle, useLayoutEffect, useRef, useState } from 'react';
import { Col, Grid, Row } from 'react-native-easy-grid'; import { Col, Grid, Row } from 'react-native-easy-grid';
import range from 'lodash/range'; import range from 'lodash/range';
import { View } from 'react-native'; import { View } from 'react-native';
import * as Animatable from 'react-native-animatable'; import * as Animatable from 'react-native-animatable';
import * as Haptics from 'expo-haptics'; import * as Haptics from 'expo-haptics';
import Orientation from 'react-native-orientation-locker';
import styles from './styles'; import styles from './styles';
import Button from './Button'; import Button from './Button';
@ -14,6 +15,8 @@ import { useTheme } from '../../../theme';
import LockIcon from './LockIcon'; import LockIcon from './LockIcon';
import Title from './Title'; import Title from './Title';
import Subtitle from './Subtitle'; import Subtitle from './Subtitle';
import { useDimensions } from '../../../dimensions';
import { isTablet } from '../../../lib/methods/helpers';
interface IPasscodeBase { interface IPasscodeBase {
type: string; type: string;
@ -34,7 +37,25 @@ export interface IBase {
const Base = forwardRef<IBase, IPasscodeBase>( const Base = forwardRef<IBase, IPasscodeBase>(
({ type, onEndProcess, previousPasscode, title, subtitle, onError, showBiometry, onBiometryPress }, ref) => { ({ type, onEndProcess, previousPasscode, title, subtitle, onError, showBiometry, onBiometryPress }, ref) => {
useLayoutEffect(() => {
if (!isTablet) {
Orientation.lockToPortrait();
}
return () => {
if (!isTablet) {
Orientation.unlockAllOrientations();
}
};
}, []);
const { theme } = useTheme(); const { theme } = useTheme();
const { height } = useDimensions();
// 206 is the height of the header calculating the margins, icon size height, title font size and subtitle height.
// 56 is a fixed number to decrease the height of button numbers.
const dinamicHeight = (height - 206 - 56) / 4;
const heightButtonRow = { height: dinamicHeight > 102 ? 102 : dinamicHeight };
const rootRef = useRef<Animatable.View & View>(null); const rootRef = useRef<Animatable.View & View>(null);
const dotsRef = useRef<Animatable.View & View>(null); const dotsRef = useRef<Animatable.View & View>(null);
@ -103,40 +124,40 @@ const Base = forwardRef<IBase, IPasscodeBase>(
<Dots passcode={passcode} length={PASSCODE_LENGTH} /> <Dots passcode={passcode} length={PASSCODE_LENGTH} />
</Animatable.View> </Animatable.View>
</Row> </Row>
<Row style={[styles.row, styles.buttonRow]}> <Row style={[styles.row, heightButtonRow]}>
{range(1, 4).map(i => ( {range(1, 4).map(i => (
<Col key={i} style={styles.colButton}> <Col key={i} style={[styles.colButton, heightButtonRow]}>
<Button text={i.toString()} onPress={onPressNumber} /> <Button style={heightButtonRow} text={i.toString()} onPress={onPressNumber} />
</Col> </Col>
))} ))}
</Row> </Row>
<Row style={[styles.row, styles.buttonRow]}> <Row style={[styles.row, heightButtonRow]}>
{range(4, 7).map(i => ( {range(4, 7).map(i => (
<Col key={i} style={styles.colButton}> <Col key={i} style={[styles.colButton, heightButtonRow]}>
<Button text={i.toString()} onPress={onPressNumber} /> <Button style={heightButtonRow} text={i.toString()} onPress={onPressNumber} />
</Col> </Col>
))} ))}
</Row> </Row>
<Row style={[styles.row, styles.buttonRow]}> <Row style={[styles.row, heightButtonRow]}>
{range(7, 10).map(i => ( {range(7, 10).map(i => (
<Col key={i} style={styles.colButton}> <Col key={i} style={[styles.colButton, heightButtonRow]}>
<Button text={i.toString()} onPress={onPressNumber} /> <Button style={heightButtonRow} text={i.toString()} onPress={onPressNumber} />
</Col> </Col>
))} ))}
</Row> </Row>
<Row style={[styles.row, styles.buttonRow]}> <Row style={[styles.row, heightButtonRow]}>
{showBiometry ? ( {showBiometry ? (
<Col style={styles.colButton}> <Col style={[styles.colButton, heightButtonRow]}>
<Button icon='fingerprint' onPress={onBiometryPress} /> <Button style={heightButtonRow} icon='fingerprint' onPress={onBiometryPress} />
</Col> </Col>
) : ( ) : (
<Col style={styles.colButton} /> <Col style={[styles.colButton, heightButtonRow]} />
)} )}
<Col style={styles.colButton}> <Col style={[styles.colButton, heightButtonRow]}>
<Button text='0' onPress={onPressNumber} /> <Button style={heightButtonRow} text='0' onPress={onPressNumber} />
</Col> </Col>
<Col style={styles.colButton}> <Col style={[styles.colButton, heightButtonRow]}>
<Button icon='backspace' onPress={onPressDelete} /> <Button style={heightButtonRow} icon='backspace' onPress={onPressDelete} />
</Col> </Col>
</Row> </Row>
</Grid> </Grid>

View File

@ -18,16 +18,12 @@ export default StyleSheet.create({
alignItems: 'center', alignItems: 'center',
justifyContent: 'center' justifyContent: 'center'
}, },
buttonRow: {
height: 102
},
colButton: { colButton: {
flex: 0, flex: 0,
marginLeft: 12, marginLeft: 12,
marginRight: 12, marginRight: 12,
alignItems: 'center', alignItems: 'center',
width: 78, width: 78
height: 78
}, },
buttonText: { buttonText: {
fontSize: 28, fontSize: 28,
@ -37,7 +33,6 @@ export default StyleSheet.create({
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
width: 78, width: 78,
height: 78,
borderRadius: 4 borderRadius: 4
}, },
textTitle: { textTitle: {

View File

@ -0,0 +1,16 @@
import React from 'react';
import { RadioButton as RadioButtonUiLib } from 'react-native-ui-lib';
import { useTheme } from '../../theme';
export const RadioButton = ({ check, testID, size }: { check: boolean; testID?: string; size?: number }): React.ReactElement => {
const { colors } = useTheme();
return (
<RadioButtonUiLib
testID={testID}
selected={check}
size={size || 20}
color={check ? colors.tintActive : colors.auxiliaryTintColor}
/>
);
};

View File

@ -1,145 +0,0 @@
import React from 'react';
import { StyleSheet, Text, Pressable, View, ScrollView } from 'react-native';
import ScrollableTabView from 'react-native-scrollable-tab-view';
import { FlatList } from 'react-native-gesture-handler';
import Emoji from './message/Emoji';
import { useTheme } from '../theme';
import { TGetCustomEmoji } from '../definitions/IEmoji';
import { IReaction } from '../definitions';
import Avatar from './Avatar';
import sharedStyles from '../views/Styles';
const MIN_TAB_WIDTH = 70;
const styles = StyleSheet.create({
reactionsListContainer: { height: '100%', width: '100%' },
tabBarItem: {
paddingHorizontal: 10,
paddingBottom: 10,
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'row'
},
reactionCount: { marginLeft: 5 },
emojiName: { margin: 10 },
userItemContainer: { marginHorizontal: 10, marginVertical: 5, flexDirection: 'row' },
usernameContainer: { marginHorizontal: 10, justifyContent: 'center' },
usernameText: { fontSize: 17, ...sharedStyles.textMedium },
standardEmojiStyle: { fontSize: 20, color: '#fff' },
customEmojiStyle: { width: 25, height: 25 }
});
interface IReactionsListBase {
baseUrl: string;
getCustomEmoji: TGetCustomEmoji;
}
interface IReactionsListProps extends IReactionsListBase {
reactions?: IReaction[];
width: number;
}
interface ITabBarItem extends IReactionsListBase {
tab: IReaction;
index: number;
goToPage?: (index: number) => void;
}
interface IReactionsTabBar extends IReactionsListBase {
activeTab?: number;
tabs?: IReaction[];
goToPage?: (index: number) => void;
width: number;
}
const TabBarItem = ({ tab, index, goToPage, baseUrl, getCustomEmoji }: ITabBarItem) => {
const { colors } = useTheme();
return (
<Pressable
key={tab.emoji}
onPress={() => {
goToPage?.(index);
}}
style={({ pressed }: { pressed: boolean }) => ({
opacity: pressed ? 0.7 : 1
})}
>
<View style={styles.tabBarItem}>
<Emoji
content={tab.emoji}
standardEmojiStyle={styles.standardEmojiStyle}
customEmojiStyle={styles.customEmojiStyle}
baseUrl={baseUrl}
getCustomEmoji={getCustomEmoji}
/>
<Text style={[styles.reactionCount, { color: colors.auxiliaryTintColor }]}>{tab.usernames.length}</Text>
</View>
</Pressable>
);
};
const ReactionsTabBar = ({ tabs, activeTab, goToPage, baseUrl, getCustomEmoji, width }: IReactionsTabBar) => {
const tabWidth = tabs && Math.max(width / tabs.length, MIN_TAB_WIDTH);
const { colors } = useTheme();
return (
<View>
<ScrollView horizontal={true} showsHorizontalScrollIndicator={false}>
{tabs?.map((tab, index) => {
const isActiveTab = activeTab === index;
return (
<View
style={{
width: tabWidth,
borderBottomWidth: isActiveTab ? 2 : 1,
borderColor: isActiveTab ? colors.tintActive : colors.separatorColor
}}
>
<TabBarItem tab={tab} index={index} goToPage={goToPage} baseUrl={baseUrl} getCustomEmoji={getCustomEmoji} />
</View>
);
})}
</ScrollView>
</View>
);
};
const UsersList = ({ tabLabel }: { tabLabel: IReaction }) => {
const { colors } = useTheme();
const { emoji, usernames } = tabLabel;
return (
<FlatList
data={usernames}
ListHeaderComponent={() => (
<View style={styles.emojiName}>
<Text style={{ color: colors.auxiliaryTintColor }}>{emoji}</Text>
</View>
)}
renderItem={({ item }) => (
<View style={styles.userItemContainer}>
<Avatar text={item} size={36} />
<View style={styles.usernameContainer}>
<Text style={[styles.usernameText, { color: colors.titleText }]}>{item}</Text>
</View>
</View>
)}
keyExtractor={item => item}
/>
);
};
const ReactionsList = ({ reactions, baseUrl, getCustomEmoji, width }: IReactionsListProps): React.ReactElement => {
// sorting reactions in descending order on the basic of number of users reacted
const sortedReactions = reactions?.sort((reaction1, reaction2) => reaction2.usernames.length - reaction1.usernames.length);
return (
<View style={styles.reactionsListContainer}>
<ScrollableTabView renderTabBar={() => <ReactionsTabBar baseUrl={baseUrl} getCustomEmoji={getCustomEmoji} width={width} />}>
{sortedReactions?.map(reaction => (
<UsersList tabLabel={reaction} key={reaction.emoji} />
))}
</ScrollableTabView>
</View>
);
};
export default ReactionsList;

View File

@ -0,0 +1,75 @@
import React from 'react';
import { Text, View, FlatList } from 'react-native';
import Emoji from '../message/Emoji';
import { useTheme } from '../../theme';
import { IReaction } from '../../definitions';
import { TGetCustomEmoji } from '../../definitions/IEmoji';
import I18n from '../../i18n';
import styles from './styles';
import { useAppSelector } from '../../lib/hooks';
interface IAllReactionsListItemProps {
getCustomEmoji: TGetCustomEmoji;
item: IReaction;
}
interface IAllTabProps {
getCustomEmoji: TGetCustomEmoji;
tabLabel: IReaction;
reactions?: IReaction[];
}
const AllReactionsListItem = ({ item, getCustomEmoji }: IAllReactionsListItemProps) => {
const { colors } = useTheme();
const useRealName = useAppSelector(state => state.settings.UI_Use_Real_Name);
const username = useAppSelector(state => state.login.user.username);
const count = item.usernames.length;
let displayNames;
if (useRealName && item.names) {
displayNames = item.names
.slice(0, 3)
.map((name, index) => (item.usernames[index] === username ? I18n.t('you') : name))
.join(', ');
} else {
displayNames = item.usernames
.slice(0, 3)
.map((otherUsername: string) => (username === otherUsername ? I18n.t('you') : otherUsername))
.join(', ');
}
if (count > 3) {
displayNames = `${displayNames} ${I18n.t('and_more')} ${count - 3}`;
} else {
displayNames = displayNames.replace(/,(?=[^,]*$)/, ` ${I18n.t('and')}`);
}
return (
<View style={styles.listItemContainer}>
<Emoji
content={item.emoji}
standardEmojiStyle={styles.allTabStandardEmojiStyle}
customEmojiStyle={styles.allTabCustomEmojiStyle}
getCustomEmoji={getCustomEmoji}
/>
<View style={styles.textContainer}>
<Text style={[styles.allListNPeopleReacted, { color: colors.bodyText }]}>
{count === 1 ? I18n.t('1_person_reacted') : I18n.t('N_people_reacted', { n: count })}
</Text>
<Text style={[styles.allListWhoReacted, { color: colors.auxiliaryText }]}>{displayNames}</Text>
</View>
</View>
);
};
const AllTab = ({ reactions, getCustomEmoji }: IAllTabProps): React.ReactElement => (
<View style={styles.allTabContainer} testID='reactionsListAllTab'>
<FlatList
data={reactions}
contentContainerStyle={styles.listContainer}
renderItem={({ item }) => <AllReactionsListItem item={item} getCustomEmoji={getCustomEmoji} />}
keyExtractor={item => item.emoji}
/>
</View>
);
export default AllTab;

View File

@ -0,0 +1,71 @@
import React from 'react';
import { View } from 'react-native';
import { TGetCustomEmoji, ICustomEmoji } from '../../definitions';
import ReactionsList from '.';
import { mockedStore as store } from '../../reducers/mockedStore';
import { updateSettings } from '../../actions/settings';
const getCustomEmoji: TGetCustomEmoji = content => {
const customEmoji = {
marioparty: { name: content, extension: 'gif' },
react_rocket: { name: content, extension: 'png' },
nyan_rocket: { name: content, extension: 'png' }
}[content] as ICustomEmoji;
return customEmoji;
};
const reactions = [
{
emoji: ':marioparty:',
_id: 'marioparty',
usernames: ['rocket.cat', 'diego.mello'],
names: ['Rocket Cat', 'Diego Mello']
},
{
emoji: ':react_rocket:',
_id: 'react_rocket',
usernames: ['rocket.cat', 'diego.mello'],
names: ['Rocket Cat', 'Diego Mello']
},
{
emoji: ':nyan_rocket:',
_id: 'nyan_rocket',
usernames: ['rocket.cat'],
names: ['Rocket Cat']
},
{
emoji: ':grinning:',
_id: 'grinning',
usernames: ['diego.mello'],
names: ['Diego Mello']
},
{
emoji: ':tada:',
_id: 'tada',
usernames: ['diego.mello'],
names: ['Diego Mello']
}
];
export const ReactionsListStory = () => {
store.dispatch(updateSettings('UI_Use_Real_Name', false));
return (
<View style={{ paddingVertical: 10, flex: 1 }}>
<ReactionsList getCustomEmoji={getCustomEmoji} reactions={reactions} />
</View>
);
};
export const ReactionsListFullName = () => {
store.dispatch(updateSettings('UI_Use_Real_Name', true));
return (
<View style={{ paddingVertical: 10, flex: 1 }}>
<ReactionsList getCustomEmoji={getCustomEmoji} reactions={reactions} />
</View>
);
};
export default {
title: 'ReactionsList'
};

View File

@ -0,0 +1,79 @@
import React from 'react';
import { fireEvent, render, within } from '@testing-library/react-native';
import { Provider } from 'react-redux';
import ReactionsList from '.';
import { mockedStore } from '../../reducers/mockedStore';
const getCustomEmoji = jest.fn();
const reactions = [
{
emoji: 'marioparty',
_id: 'marioparty',
usernames: ['rocket.cat', 'diego.mello'],
names: ['Rocket Cat', 'Diego Mello']
},
{
emoji: 'react_rocket',
_id: 'react_rocket',
usernames: ['rocket.cat', 'diego.mello'],
names: ['Rocket Cat', 'Diego Mello']
},
{
emoji: 'nyan_rocket',
_id: 'nyan_rocket',
usernames: ['rocket.cat'],
names: ['Rocket Cat']
},
{
emoji: 'grinning',
_id: 'grinning',
usernames: ['diego.mello'],
names: ['Diego Mello']
}
];
const Render = () => (
<Provider store={mockedStore}>
<ReactionsList getCustomEmoji={getCustomEmoji} reactions={reactions} />
</Provider>
);
describe('ReactionsList', () => {
test('should render Reactions List', async () => {
const { findByTestId } = render(<Render />);
const ReactionsListView = await findByTestId('reactionsList');
expect(ReactionsListView).toBeTruthy();
});
test('should render tab bar', async () => {
const { findByTestId } = render(<Render />);
const AllTab = await findByTestId('reactionsTabBar');
expect(AllTab).toBeTruthy();
});
test('should render All tab', async () => {
const { findByTestId } = render(<Render />);
const AllTab = await findByTestId('reactionsListAllTab');
expect(AllTab).toBeTruthy();
});
test('correct tab on clicking tab item', async () => {
const { findByTestId } = render(<Render />);
const tab = await findByTestId(`tabBarItem-${reactions[0].emoji}`);
fireEvent.press(tab);
const usersList = await findByTestId(`usersList-${reactions[0].emoji}`);
expect(usersList).toBeTruthy();
const emojiName = await within(usersList).getByTestId(`usersListEmojiName`);
expect(emojiName.props.children).toEqual(reactions[0].emoji);
});
test('should render correct number of reactions', async () => {
const { findByTestId } = render(<Render />);
const tab = await findByTestId(`tabBarItem-${reactions[0].emoji}`);
fireEvent.press(tab);
const usersList = await findByTestId(`usersList-${reactions[0].emoji}`);
const allReactions = await within(usersList).getAllByTestId('userItem');
expect(allReactions).toHaveLength(reactions[0].usernames.length);
});
});

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