Merge beta into master (#2003)

* [FIX] Pass isFocused as a function to Messagebox (#1309)

* [CHORE] Remove icons folder (#1290)

* [CHORE] Refactor RoomItem touchable (#1331)

* [FIX] Unnecessary rerender on RoomItem when status is undefined (#1336)

* [UPDATE DEPS] react-navigation and react-navigation-stack (#1337)

* [FIX] Avatars not loading on share extension when Accounts_AvatarBlockUnauthenticatedAccess is enabled (#1339)

* Bump version to 1.20.2 (#1340)

* [FIX] Remove some unnecessary re-renders on Messagebox (#1341)

* [REGRESSION] Use LayoutAnimation instead of Transition API (#1338)

* [FIX] Remove setState from notifications view causing watermelon object to be updated outside an action (#1342)

* [IMPROVEMENT] Save last message as message when subscription is updated (#1344)

* [UPDATE DEPS] Update RN to 0.61.3 (#1345)

* [DOCS] Update Readme (#1346)

* [CHORE] Remove react-native-scrollable-tab-view fork (#1352)

* [FIX] URL preview (#1360)

* [REGRESSION] Decrease list view memory size (#1361)

* [FIX] Paste (#1350)

* [CHORE] Update gems (#1365)

* Bump version to 1.20.3 (#1366)

* [FIX] Use Ruby 2.4 on TestFlight upload (#1368)

* [FIX] Parse Urls (#1371)

* [FIX] Parse image URL only if it's not empty (#1372)

* [FIX] Load messages issues (#1373)

* Bump version to 1.21.0 (#1376)

* [FIX] Crowd login (#1381)

* [FIX] Clicking user avatar in thread previews crashes app (#1363)

* [IMPROVEMENT] Error messages on connect (#1379)

* [FIX] ProfileView input navigation error when custom fields aren't set (#1383)

* [FIX] Batch server deletion on logout (#1382)

* Bump app to 1.22.0 (#1387)

* [FIX] Server Version (#1392)

* Update patch and minor deps (#1386)

* [FIX] Crash when open thread (#1395)

* Bump version to 1.23.0 (#1394)

* [I18N] Update ru.js (#1384)

* [FIX] CAS building wrong URL (#1362)

* [FIX] Delete messages (#1399)

* [FIX] In-app notification showing wrong content on channels (#1400)

* Bump version to 1.24.0 (#1404)

* [FIX] Prevent server with whitespace (#1402)

* [IMPROVEMENT] Keyboard and content type on login (#1403)

* [FIX] Messages stop loading (#1410)

* [NEW] Tablet support (#1300)

* [IMPROVEMENT] Authentication via deep linking (#1418)

* [IMPROVEMENT] Markdown performance when identifying emoji only content (#1422)

* [FIX] BackHandler remove random failing on development (#1423)

* Bump version to 1.25.0 (#1424)

* [CHORE] Update CI Xcode Image (#1430)

* [FIX] Rooms grouping not working properly (#1435)

* [FIX] Take a video (#1437)

* [NEW] Themes (#1298)

* [FIX] Share extension doesn't reconnect to previous selected server on Android (#1429)

* [FIX] Init local settings on notification tap (#1438)

* Bump version to 1.26.0 (#1450)

* [FIX] Emoji parser not working on Hermes  (#1445)

* [NEW] Enable Hermes (#1446)

* [FIX] Automatic theme repeating (#1457)

* [CHORE] Sync Experimental and Official app versions (#1458)

* [DOCS] Update readme (#1459)

* [FIX] Messages being sent but showing as temp status (#1469)

* [FIX] Missing messages after reconnect (#1470)

* [FIX] Few fixes on themes (#1477)

* [I18N] Missing German translations (#1465)

* Missing German translation

* adding a missing space behind colon

* added a missing space after colon

* and another attempt to finally fix this – got confused by all the branches

* some smaller fixes for the translation

* better wording

* fixed another typo

* [FIX] Crash while displaying the attached image with http on file name (#1401)

* [IMPROVEMENT] Tap app and server version to copy to clipboard (#1425)

* [NEW] Reply notification (#1448)

* [FIX] Incorrect background color login on iPad (#1480)

* [FIX] Prevent multiple tap on send (Share Extension) (#1481)

* [NEW] Image Viewer (#1479)

* [DOCS] Update Readme (#1485)

* [FIX] Jitsi with Hermes Enabled (#1523)

* [FIX] Draft messages not working with themed Messagebox (#1525)

* [FIX] Go to direct message from members list (#1519)

* [FIX] Make SAML wait for idp token instead of creating it on client (#1527)

* [FIX] Server Test Push Notification (#1508)

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

* [CHORE] Update to new server response (#1509)

* [FIX] Insert messages with blank users (#1529)

* Bump version to 4.2.1 (#1530)

* [FIX] Error when normalizing empty messages (#1532)

* [REGRESSION] CAS (#1570)

* Bump version to 4.2.2 (#1571)

* [FIX] Add username block condition to prevent error (#1585)

* Bump version to 4.2.3

* Bump version to 4.2.4

* Bump version to 4.3.0 (#1630)

* [FIX] Channels doesn't load (#1586)

* [FIX] Channels doesn't load

* [FIX] Update roomsUpdatedAt when subscriptions.length is 0

* [FIX] Remove unnecessary changes

* [FIX] Improve the code

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

* [FIX] Make SAML to work on Rocket.Chat < 2.3.0 (#1629)

* [NEW] Invite links (#1534)

* [FIX] Set the http-agent to the form that Rocket.Chat requires for logging (#1482)

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

* [FIX] "Following thread" and "Unfollowed Thread" is hardcoded and not translated (#1625)

* [FIX] Disable reset button if form didn't changed (#1569)

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

* [FIX] Header title of RoomInfoView (#1553)

* [I18N] Gallery Permissions DE (#1542)

* [FIX] Not allow to send messages to archived room (#1623)

* [FIX] Profile fields automatically reset (#1502)

* [FIX] Show attachment on ThreadMessagesView (#1493)

* [NEW] Wordpress auth (#1633)

* [CHORE] Add Start Packager script (#1639)

* [CHORE] Update RN to 0.61.5 (#1638)

* [CHORE] Update RN to 0.61.5

* [CHORE] Update react-native patch

Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com>

* Bump version to 4.3.1 (#1641)

* [FIX] Change force logout rule (#1640)

* Bump version to 4.4.0 (#1643)

* [IMPROVEMENT] Use MessagingStyle on Android Notification (#1575)

* [NEW] Request review (#1627)

* [NEW] Pull to refresh RoomView (#1657)

* [FIX] Unsubscribe from room (#1655)

* [FIX] Server with subdirs (#1646)

* [NEW] Clear cache (#1660)

* [IMPROVEMENT] Memoize and batch subscriptions updates (#1642)

* [FIX] Disallow empty sharing (#1664)

* [REGRESSION] Use HTTPS links for sharing and markets protocol for review (#1663)

* [FIX] In some cases, share extension doesn't load images (#1649)

* [i18n] DE translations for new invite function and some minor fixes (#1631)

* [FIX] Remove duplicate jetify step (#1628)

minor: also remove 'cd' calls

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

* [REGRESSION] Read messages (#1666)

* [i18n] German translations missing (#1670)

* [FIX] Notifications crash on older Android Versions (#1672)

* [i18n] Added Dutch translation (#1676)

* [NEW] Omnichannel Beta (#1674)

* [NEW] Confirm logout/clear cache (#1688)

* [I18N] Add es-ES language  (#1495)

* [NEW] UiKit Beta (#1497)

* [IMPROVEMENT] Use reselect (#1696)

* [FIX] Notification in Android API level less than 24 (#1692)

* [IMPROVEMENT] Send tmid on slash commands and media (#1698)

* [FIX] Unhandled action on UIKit (#1703)

* [NEW] Pull to refresh RoomsList (#1701)

* [IMPROVEMENT] Reset app when language is changed (#1702)

* [FIX] Small fixes on UIKit (#1709)

* [FIX] Spotlight (#1719)

* [CHORE] Update react-native-image-crop-picker (#1712)

* [FIX] Messages Overlapping (Android) and MessageBox Scroll (iOS) (#1720)

* [REGRESSION] Remove @ and # from mention (#1721)

* [NEW] Direct message from user info (#1516)

* [FIX] Delete slash commands (#1723)

* [IMPROVEMENT] Hold URL to copy (#1684)

* [FIX] Different sourcemaps generation for Hermes (#1724)

* [FIX] Different sourcemaps generation for Hermes

* Upload sourcemaps after build

* [REVERT] Show emoji keyboard on Android (#1738)

* [FIX] Stop logging react-native-image-crop-picker (#1745)

* [FIX] Prevent toast ref error (#1744)

* [FIX] Prevent reaction map error (#1743)

* [FIX] Add missing calls to user info (#1741)

* [FIX] Catch room unsubscribe error (#1739)

* [i18n] Missing German keys (#1735)

* [FIX] Missing i18n on MessagesView title (#1733)

* [FIX]  UIKit Modal: Weird behavior on Android Tablet (#1742)

* [i18n] Missing key on German (#1747)

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

* [i18n] Add Italian (#1736)

* [CHORE] Memory leaks investigation (#1675)

* [IMPROVEMENT] Alert verify email when enabled (#1725)

* [NEW] Jitsi JWT added to URL (#1746)

* [FIX] UIKit submit when connection lost (#1748)

* Bump version to 4.5.0 (#1761)

* [NEW] Default browser (#1752)

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

* [FIX] HTTP Basic Auth (#1753)

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

* [IMPROVEMENT] Honor profile fields edit settings (#1687)

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

* [IMPROVEMENT] Room announcements (#1726)

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

* [IMPROVEMENT] Honor Register/Login settings (#1727)

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

* [IMPROVEMENT] Make links clickable on Room Info (#1730)

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

* [NEW] Hide system messages (#1755)

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

* [IMPROVEMENT] Honor "Message_AudioRecorderEnabled" (#1764)

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

* [i18n] Missing de keys (#1765)

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

* [FIX] Redirect user to SetUsernameView (#1728)

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

* [FIX] Join Room (#1769)

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

* [FIX] Accept all media types using * (#1770)

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

* [FIX] Use RealName when necessary (#1758)

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

* [FIX] Markdown Line Break (#1783)

* [IMPROVEMENT] Remove useMarkdown (#1774)

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

* [IMPROVEMENT] Open browser rather than webview on Create Workspace (#1788)

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

* [IMPROVEMENT] Markdown perf (#1796)

* [FIX] Stop video when modal is closed (#1787)

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

* [FIX] Hide reply notification action when there are missing data (#1771)

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

* [i18n] Added Japanese translation (#1781)

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

* [FIX] Reset password error message (#1772)

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

* [FIX] Close tablet modal (#1773)

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

* [FIX] Setting not present (#1775)

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

* [FIX] Thread header (#1776)

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

* [FIX] Keyboard tracking loses input ref (#1784)

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

* [NEW] Mark message as unread (#1785)

Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com>

* [IMPROVEMENT] Log server version (#1786)

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

* [IMPROVEMENT] Add loading message on long running tasks (#1798)

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

* [CHORE] Switch Apple account on Fastlane (#1810)

* [FIX] Watermelon throwing "Cannot update a record with pending updates" (#1754)

* [FIX] Detox tests (#1790)

* [CHORE] Use markdown preview on RoomView Header (#1807)

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

* [FIX] LoginSignup blink services (#1809)

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

* [IMPROVEMENT] Request user presence on demand (#1813)

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

* [FIX] Remove all invited users when create a channel (#1814)

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

* [FIX] Pop from room which you have been removed (#1819)

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

* [FIX] Room Info styles (#1820)

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

* [i18n] Add missing German keys (#1800)

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

* [FIX] Empty mentions for @all and @here when real name is enabled (#1822)

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

* [TESTS] Markdown added to Storybook (#1812)

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

* [REGRESSION] Room View header title (#1827)

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

* [FIX] Storybook snapshots (#1831)

Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com>

* [FIX] Mentions (#1829)

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

* [FIX] Thread message not found (#1830)

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

* [FIX] Separate delete and remove channel (#1832)

* Rename to delete room

* Separate delete and remove channel

* handleRemoved -> handleRoomRemoved

* [FIX] Navigate to RoomsList & Handle tablet case

Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com>

* [NEW] Filter system messages per room (#1815)

Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com>
Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [FIX] e2e tests (#1838)

* [FIX] Consecutive clear cache calls freezing app (#1851)

* Bump version to 4.5.1 (#1853)

* [FIX][iOS] Ignore silent mode on audio player (#1862)

* [IMPROVEMENT] Create App Group property on Info.plist (#1858)

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

* [IMPROVEMENT] Make username clickable on message (#1618)

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

* [FIX] Show proper error message on profile (#1768)

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

* [IMPROVEMENT] Show toast when a message is starred/unstarred (#1616)

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

* [FIX] Incorrect size params to avatar endpoint (#1875)

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

* [FIX] Remove unrecognized emoji flags on android  (#1887)

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

* [FIX] Remove react-native global installs (#1886)

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

* [FIX] Emojis transparent on android (#1881)

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

* Bump acorn from 5.7.3 to 5.7.4 (#1876)

Bumps [acorn](https://github.com/acornjs/acorn) from 5.7.3 to 5.7.4.
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/5.7.3...5.7.4)

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>

* Bump version to 4.6.0 (#1911)

* [FIX] Encode Image URI (#1909)

* [FIX] Encode Image URI

* [FIX] Check if Image is Valid

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

* [NEW] Adaptive Icons (#1904)

* Remove unnecessary stuff from debug build

* Adaptive icon for experimental app

* [FIX] Stop showing message on leave channel (#1896)

* [FIX] Leave room don't show 'was removed' message

* [FIX] Remove duplicated code

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

* [i18n] Added missing German translations(#1900)

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

* [FIX] Linkedin OAuth login (#1913)

* [CHORE] Fix typo in CreateChannel View (#1930)

* [FIX] Respect protocol in HTTP Auth IPs (#1933)

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

* [FIX] Use new LinkedIn OAuth url (#1935)

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

* [CHORE] Use storyboard on splash screen (#1939)

* Update react-native-bootsplash

* iOS

* Fix android

* [FIX] Check if avatar exists before create Icon (#1927)

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

* [FIX] Ignore self typing event (#1950)

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

* [FIX] Change default directory listing to Users (#1948)

* fix: change default directory listing to Users

* follow server settings

* Fix state to props

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

* [NEW] Onboarding layout (#1954)

* Onboarding texts

* OnboardingView

* FormContainer

* Minor fixes

* NewServerView

* Remove code

* Refactor

* WorkspaceView

* Stash

* Login with email working

* Login with

* Join open

* Revert "Login with"

This reverts commit d05dc507d2e9a2db76d433b9b1f62192eba35dbd.

* Fix create account styles

* Register

* Refactor

* LoginServices component

* Refactor

* Multiple servers

* Remove native images

* Refactor styles

* Fix testid

* Fix add server on tablet

* i18n

* Fix close modal

* Fix TOTP

* [FIX] Registration disabled

* [FIX] Login Services separator

* Fix logos

* Fix AppVersion name

* I18n

* Minor fixes

* [FIX] Custom Fields

Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com>

* [NEW] Create discussions (#1942)

* [WIP][NEW] Create Discussion

* [FIX] Clear multiselect & Translations

* [NEW] Create Discussion at MessageActions

* [NEW] Disabled Multiselect

* [FIX] Initial channel

* [NEW] Create discussion on MessageBox Actions

* [FIX] Crashing on edit name

* [IMPROVEMENT] New message layout

* [CHORE] Update README

* [NEW] Avatars on MultiSelect

* [FIX] Select Users

* [FIX] Add redirect and Handle tablet

* [IMPROVEMENT] Split CreateDiscussionView

* [FIX] Create a discussion inner discussion

* [FIX] Create a discussion

* [I18N] Add pt-br

* Change icons

* [FIX] Nav to discussion & header title

* Fix header

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

* [FIX] Load messages (#1910)

* Create updateLastOpen param on readMessages

* Remove InteractionManager from load messages

* [NEW] Custom Status (#1811)

* [NEW] Custom Status

* [FIX] Subscribe to changes

* [FIX] Improve code using Banner component

* [IMPROVEMENT] Toggle modal

* [NEW] Edit custom status from Sidebar

* [FIX] Modal when tablet

* [FIX] Styles

* [FIX] Switch to react-native-promp-android

* [FIX] Custom Status UI

* [TESTS] E2E Custom Status

* Fix banner

* Fix banner

* Fix subtitle

* status text

* Fix topic header

* Fix RoomActionsView topic

* Fix header alignment on Android

* [FIX] RoomInfo crashes when without statusText

* [FIX] Use users.setStatus

* [FIX] Remove customStatus of ProfileView

* [FIX] Room View Thread Header

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

* [FIX] UI issues of Create Discussion View (#1965)

* [NEW] Direct Message between multiple users (#1958)

* [WIP] DM between multiple users

* [WIP][NEW] Create new DM between multiple users

* [IMPROVEMENT] Improve createChannel Sagas

* [IMPROVEMENT] Selected Users view

* [IMPROVEMENT] Room Actions of Group DM

* [NEW] Create new DM between multiple users

* [NEW] Group DM avatar

* [FIX] Directory border

* [IMPROVEMENT] Use isGroupChat

* [CHORE] Remove legacy getRoomMemberId

* [NEW] RoomTypeIcon

* [FIX] No use legacy method on RoomInfoView

* [FIX] Blink header when create new DM

* [FIX] Only show create direct message option when allowed

* [FIX] RoomInfoView

* pt-BR

* Few fixes

* Create button name

* Show create button only after a user is selected

* Fix max users issues

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

* [FIX] Add server and hide login (#1968)

* Navigate to new server workspace from ServerDropdown if there's no token

* Hide login button based on login services and Accounts_ShowFormLogin setting

* [FIX] Lint

Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com>

* [FIX] MultiSelect Keyboard behavior (Android) (#1969)

* fixed-modal-position

* made-changes

Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com>

* [FIX] Bottom border style on DirectoryView (#1963)

* [FIX] Border style

* [FIX] Refactoring

* [FIX] fix color of border

* Undo

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

* [FIX] Clear settings on server change (#1967)

* [FIX] Deeplinking without RoomId (#1925)

* [FIX] Deeplinking without rid

* [FIX] Join channel

* [FIX] Deep linking without rid

* Update app/lib/methods/canOpenRoom.js

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

* [NEW] Two Factor authentication via email (#1961)

* First api call working

* [NEW] REST API Post wrapper 2FA

* [NEW] Send 2FA on Email

* [I18n] Add translations

* [NEW] Translations & Cancel totp

* [CHORE] Totp -> TwoFactor

* [NEW] Two Factor by email

* [NEW] Tablet Support

* [FIX] Text colors

* [NEW] Password 2fa

* [FIX] Encrypt password on 2FA

* [NEW] MethodCall2FA

* [FIX] Password fallback

* [FIX] Wrap all post/methodCall with 2fa

* [FIX] Wrap missed function

* few fixes

* [FIX] Use new TOTP on Login

* [improvement] 2fa methodCall

Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com>

* [FIX] Correct message for manual approval user Registration (#1906)

* [FIX] Correct message for manual approval from admin shown on Registeration

* lint fix - added semicolon

* Updated the translations

* [FIX] Translations

* i18n to match server

Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com>
Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [FIX] Direct Message between multiple users REST (#1974)

* [FIX] Investigate app losing connection issues (#1890)

* [WIP] Reopen without timeOut & ping with 5 sec & Fix Unsubscribe

* [FIX] Remove duplicated close

* [FIX] Use no-dist lib

* [FIX] Try minor fix

* [FIX] Try reopen connection when app was put on foreground

* [FIX] Remove timeout

* [FIX] Build

* [FIX] Patch

* [FIX] Snapshot

* [IMPROVEMENT] Decrease time to reopen

* [FIX] Some fixes

* [FIX] Update sdk version

* [FIX] Subscribe Room Once

* [CHORE] Update sdk

* [FIX] Subscribe Room

* [FIX] Try to resend missed subs

* [FIX] Users never show status when start app without network

* [FIX] Subscribe to room

* [FIX] Multiple servers

* [CHORE] Update SDK

* [FIX] Don't duplicate streams on subscribeAll

* [FIX] Server version when start the app offline

* [FIX] Server version cached

* [CHORE] Remove unnecessary code

* [FIX] Offline server version

* [FIX] Subscribe before connect

* [FIX] Remove unncessary props

* [FIX] Update sdk

* [FIX] User status & Unsubscribe Typing

* [FIX] Typing at incorrect room

* [FIX] Multiple Servers

* [CHORE] Update SDK

* [REVERT] Undo some changes on SDK

* [CHORE] Update sdk to prevent incorrect subscribes

* [FIX] Prevent no reconnect

* [FIX] Remove close on open

* [FIX] Clear typing when disconnect/connect to SDK

* [CHORE] Update SDK

* [CHORE] Update SDK

* Update SDK

* fix merge develop

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

* [FIX] Single message thread inserting thread without rid (#1999)

* [FIX] ThreadMessagesView crashing on load (#1997)

* [FIX] Saml (#1996)

* [FIX] SAML incorrect close

* [FIX] Pathname

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

* [FIX] Change user own status (#1995)

* [FIX] Change user own status

* [IMPROVEMENT] Set activeUsers

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

* [FIX] Loading all updated rooms after app resume (#1998)

* [FIX] Loading all updated rooms after app resume

* Fix room date on RoomItem

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

* [FIX] Change notifications preferences (#2000)

* [FIX] Change notifications preferences

* [IMPROVEMENT] Picker View

* [I18N] Translations

* [FIX] Picker Selection

* [FIX] List border

* [FIX] Prevent crash

* [FIX] Not-Pref tablet

* [FIX] Use same style of LanguageView

* [IMPROVEMENT] Send listItem title

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

* Bump version to 4.6.1 (#2001)

Co-authored-by: Calebe Rios <calebersmendes@gmail.com>
Co-authored-by: Prateek Jain <44807945+Prateek93a@users.noreply.github.com>
Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com>
Co-authored-by: Pitstopper <18574776+Pitstopper@users.noreply.github.com>
Co-authored-by: phriedrich <info@phriedrich.de>
Co-authored-by: Guilherme Siqueira <guilhersiqueira@gmail.com>
Co-authored-by: Prateek Jain <prateek93a@gmail.com>
Co-authored-by: devyaniChoubey <52153085+devyaniChoubey@users.noreply.github.com>
Co-authored-by: Bernard Seow <ssbing99@gmail.com>
Co-authored-by: Hiroki Ishiura <ishiura@ja2.so-net.ne.jp>
Co-authored-by: Exordian <jakob.englisch@gmail.com>
Co-authored-by: Daanchaam <daanhendriks97@gmail.com>
Co-authored-by: Youssef Muhamad <emaildeyoussefmuhamad@gmail.com>
Co-authored-by: Iván Álvarez <ialvarezpereira@gmail.com>
Co-authored-by: Sarthak Pranesh <41206172+sarthakpranesh@users.noreply.github.com>
Co-authored-by: Michele Pellegrini <pellettiero@users.noreply.github.com>
Co-authored-by: Tanmoy Bhowmik <tanmoy.openroot@gmail.com>
Co-authored-by: Hibikine Kage <14365761+hibikine@users.noreply.github.com>
Co-authored-by: Ezequiel de Oliveira <ezequiel1de1oliveira@gmail.com>
Co-authored-by: Neil Agarwal <neil@neilagarwal.me>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Govind Dixit <GOVINDDIXIT93@GMAIL.COM>
Co-authored-by: Zhaubassarova Aruzhan <49000079+azhaubassar@users.noreply.github.com>
Co-authored-by: Aroo <azhaubassar@gmail.com>
Co-authored-by: Sarthak Pranesh <sarthak.pranesh2018@vitstudent.ac.in>
This commit is contained in:
Diego Mello 2020-04-07 10:26:19 -03:00 committed by GitHub
parent 96bfe1910e
commit 230c3364a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
239 changed files with 6867 additions and 3599 deletions

View File

@ -190,11 +190,6 @@ jobs:
name: Restore NPM cache
key: node-modules-{{ checksum "yarn.lock" }}
- run:
name: Install React Native CLI
command: |
npm i -g react-native-cli
- run:
name: Install NPM modules
command: |
@ -308,7 +303,6 @@ jobs:
- run:
name: Install NPM modules
command: |
yarn global add react-native react-native-cli
yarn
- run:

View File

@ -56,16 +56,15 @@ Follow the [React Native Getting Started Guide](https://facebook.github.io/react
```bash
$ git clone git@github.com:RocketChat/Rocket.Chat.ReactNative.git
$ cd Rocket.Chat.ReactNative
$ yarn global add react-native-cli
$ yarn
```
- Run application
```bash
$ react-native run-ios
$ npx react-native run-ios
```
```bash
$ react-native run-android
$ npx react-native run-android
```
### Running single server
@ -81,7 +80,7 @@ Readme will guide you on how to config.
|--------------------------------------------------------------- |-------- |
| Jitsi Integration | ✅ |
| Federation (Directory) | ✅ |
| Discussions | |
| Discussions | |
| Omnichannel | ❌ |
| Threads | ✅ |
| Record Audio | ✅ |

View File

@ -0,0 +1,2 @@
export class Rocketchat {}
export const settings = {};

File diff suppressed because it is too large Load Diff

View File

@ -138,7 +138,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode VERSIONCODE as Integer
versionName "4.5.1"
versionName "4.6.1"
vectorDrawables.useSupportLibrary = true
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

View File

@ -1 +0,0 @@
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="primary_dark">#660B0B0B</color> </resources>

View File

@ -1,5 +1,4 @@
<resources>
<string name="app_name">[DEVELOP] RocketChatRN</string>
<string name="no_browser_found">No Browser Found</string>
<string name="app_name">[DEBUG] Rocket.Chat Experimental</string>
<string name="share_extension_name">[DEBUG] Rocket.Chat Experimental</string>
</resources>

View File

@ -1,9 +0,0 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="android:colorEdgeEffect">#aaaaaa</item>
</style>
</resources>

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View File

@ -237,12 +237,19 @@ public class CustomPushNotification extends PushNotification {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
messageStyle.addMessage(m, timestamp, username);
} else {
Person sender = new Person.Builder()
Bitmap avatar = getAvatar(avatarUri);
Person.Builder sender = new Person.Builder()
.setKey(senderId)
.setName(username)
.setIcon(Icon.createWithBitmap(getAvatar(avatarUri)))
.build();
messageStyle.addMessage(m, timestamp, sender);
.setName(username);
if (avatar != null) {
sender.setIcon(Icon.createWithBitmap(avatar));
}
Person person = sender.build();
messageStyle.addMessage(m, timestamp, person);
}
}
}

View File

@ -14,7 +14,7 @@ public class MainActivity extends ReactFragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
RNBootSplash.show(R.drawable.launch_screen, MainActivity.this);
RNBootSplash.init(R.drawable.launch_screen, MainActivity.this);
}
/**

BIN
android/app/src/main/res/drawable-hdpi/logo.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

BIN
android/app/src/main/res/drawable-mdpi/logo.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 987 B

View File

@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:pathData="M0,0h512v512h-512z">
<aapt:attr name="android:fillColor">
<gradient
android:startY="0"
android:startX="256"
android:endY="512"
android:endX="256"
android:type="linear">
<item android:offset="0" android:color="#FF1C82FF"/>
<item android:offset="1" android:color="#FF0066E3"/>
</gradient>
</aapt:attr>
</path>
</vector>

BIN
android/app/src/main/res/drawable-xhdpi/logo.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

BIN
android/app/src/main/res/drawable-xxhdpi/logo.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

BIN
android/app/src/main/res/drawable-xxxhdpi/logo.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -1,5 +1,4 @@
<resources>
<string name="app_name">Rocket.Chat Experimental</string>
<string name="share_extension_name">Rocket.Chat Experimental</string>
<string name="no_browser_found">No Browser Found</string>
</resources>

View File

@ -31,10 +31,11 @@ export const ROOMS = createRequestTypes('ROOMS', [
'OPEN_SEARCH_HEADER',
'CLOSE_SEARCH_HEADER'
]);
export const ROOM = createRequestTypes('ROOM', ['LEAVE', 'DELETE_INIT', 'DELETE_FINISH', 'USER_TYPING']);
export const ROOM = createRequestTypes('ROOM', ['LEAVE', 'DELETE', 'REMOVED', 'USER_TYPING']);
export const APP = createRequestTypes('APP', ['START', 'READY', 'INIT', 'INIT_LOCAL_SETTINGS']);
export const MESSAGES = createRequestTypes('MESSAGES', ['REPLY_BROADCAST']);
export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [...defaultTypes]);
export const CREATE_DISCUSSION = createRequestTypes('CREATE_DISCUSSION', [...defaultTypes]);
export const SELECTED_USERS = createRequestTypes('SELECTED_USERS', ['ADD_USER', 'REMOVE_USER', 'RESET', 'SET_LOADING']);
export const SERVER = createRequestTypes('SERVER', [
...defaultTypes,
@ -62,3 +63,4 @@ export const INVITE_LINKS = createRequestTypes('INVITE_LINKS', [
'CLEAR',
...defaultTypes
]);
export const SETTINGS = createRequestTypes('SETTINGS', ['CLEAR', 'ADD']);

View File

@ -0,0 +1,22 @@
import * as types from './actionsTypes';
export function createDiscussionRequest(data) {
return {
type: types.CREATE_DISCUSSION.REQUEST,
data
};
}
export function createDiscussionSuccess(data) {
return {
type: types.CREATE_DISCUSSION.SUCCESS,
data
};
}
export function createDiscussionFailure(err) {
return {
type: types.CREATE_DISCUSSION.FAILURE,
err
};
}

View File

@ -34,13 +34,6 @@ export function setCurrentServer(server) {
};
}
export function addSettings(settings) {
return {
type: types.ADD_SETTINGS,
payload: settings
};
}
export function login() {
return {
type: 'LOGIN'

View File

@ -8,17 +8,17 @@ export function leaveRoom(rid, t) {
};
}
export function deleteRoomInit(rid, t) {
export function deleteRoom(rid, t) {
return {
type: types.ROOM.DELETE_INIT,
type: types.ROOM.DELETE,
rid,
t
};
}
export function deleteRoomFinish() {
export function removedRoom() {
return {
type: types.ROOM.DELETE_FINISH
type: types.ROOM.REMOVED
};
}

14
app/actions/settings.js Normal file
View File

@ -0,0 +1,14 @@
import { SETTINGS } from './actionsTypes';
export function addSettings(settings) {
return {
type: SETTINGS.ADD,
payload: settings
};
}
export function clearSettings() {
return {
type: SETTINGS.CLEAR
};
}

View File

@ -14,6 +14,9 @@ export default {
Accounts_AllowUserProfileChange: {
type: 'valueAsBoolean'
},
Accounts_AllowUserStatusMessageChange: {
type: 'valueAsBoolean'
},
Accounts_AllowUsernameChange: {
type: 'valueAsBoolean'
},
@ -44,9 +47,18 @@ export default {
Accounts_ShowFormLogin: {
type: 'valueAsBoolean'
},
Accounts_ManuallyApproveNewUsers: {
type: 'valueAsBoolean'
},
CROWD_Enable: {
type: 'valueAsBoolean'
},
DirectMesssage_maxUsers: {
type: 'valueAsNumber'
},
Accounts_Directory_DefaultView: {
type: 'valueAsString'
},
FEDERATION_Enabled: {
type: 'valueAsBoolean'
},

View File

@ -1,4 +1,4 @@
export const MAX_SIDEBAR_WIDTH = 321;
export const MAX_CONTENT_WIDTH = '90%';
export const MAX_SCREEN_CONTENT_WIDTH = '45%';
export const MAX_SCREEN_CONTENT_WIDTH = '50%';
export const MIN_WIDTH_SPLIT_LAYOUT = 700;

View File

@ -1,3 +1,4 @@
export const SET_CURRENT_SERVER = 'SET_CURRENT_SERVER';
export const SET_CUSTOM_EMOJIS = 'SET_CUSTOM_EMOJIS';
export const ADD_SETTINGS = 'ADD_SETTINGS';
export const CLEAR_SETTINGS = 'CLEAR_SETTINGS';

View File

@ -0,0 +1,34 @@
import React from 'react';
import { StyleSheet, View, Text } from 'react-native';
import PropTypes from 'prop-types';
import { themes } from '../constants/colors';
import sharedStyles from '../views/Styles';
import { getReadableVersion } from '../utils/deviceInfo';
import I18n from '../i18n';
const styles = StyleSheet.create({
container: {
alignItems: 'center',
justifyContent: 'flex-end'
},
text: {
...sharedStyles.textRegular,
fontSize: 13
},
bold: {
...sharedStyles.textSemibold
}
});
const AppVersion = React.memo(({ theme }) => (
<View style={styles.container}>
<Text style={[styles.text, { color: themes[theme].auxiliaryText }]}>{I18n.t('Version_no', { version: '' })}<Text style={styles.bold}>{getReadableVersion}</Text></Text>
</View>
));
AppVersion.propTypes = {
theme: PropTypes.string
};
export default AppVersion;

View File

@ -4,10 +4,7 @@ import { View } from 'react-native';
import FastImage from 'react-native-fast-image';
import { settings as RocketChatSettings } from '@rocket.chat/sdk';
import Touch from '../utils/touch';
const formatUrl = (url, baseUrl, uriSize, avatarAuthURLFragment) => (
`${ baseUrl }${ url }?format=png&width=${ uriSize }&height=${ uriSize }${ avatarAuthURLFragment }`
);
import { avatarURL } from '../utils/avatar';
const Avatar = React.memo(({
text, size, baseUrl, borderRadius, style, avatar, type, children, userId, token, onPress, theme
@ -22,24 +19,9 @@ const Avatar = React.memo(({
return null;
}
const room = type === 'd' ? text : `@${ text }`;
// Avoid requesting several sizes by having only two sizes on cache
const uriSize = size === 100 ? 100 : 50;
let avatarAuthURLFragment = '';
if (userId && token) {
avatarAuthURLFragment = `&rc_token=${ token }&rc_uid=${ userId }`;
}
let uri;
if (avatar) {
uri = avatar.includes('http') ? avatar : formatUrl(avatar, baseUrl, uriSize, avatarAuthURLFragment);
} else {
uri = formatUrl(`/avatar/${ room }`, baseUrl, uriSize, avatarAuthURLFragment);
}
const uri = avatarURL({
type, text, size, userId, token, avatar, baseUrl
});
let image = (
<FastImage

View File

@ -9,15 +9,19 @@ import ActivityIndicator from '../ActivityIndicator';
const styles = StyleSheet.create({
container: {
paddingHorizontal: 15,
paddingHorizontal: 14,
justifyContent: 'center',
height: 48,
borderRadius: 2,
marginBottom: 10
marginBottom: 12
},
text: {
fontSize: 18,
textAlign: 'center'
fontSize: 16,
textAlign: 'center',
...sharedStyles.textMedium
},
disabled: {
opacity: 0.3
}
});
@ -30,6 +34,8 @@ export default class Button extends React.PureComponent {
backgroundColor: PropTypes.string,
loading: PropTypes.bool,
theme: PropTypes.string,
color: PropTypes.string,
fontSize: PropTypes.string,
style: PropTypes.any
}
@ -43,9 +49,15 @@ export default class Button extends React.PureComponent {
render() {
const {
title, type, onPress, disabled, backgroundColor, loading, style, theme, ...otherProps
title, type, onPress, disabled, backgroundColor, color, loading, style, theme, fontSize, ...otherProps
} = this.props;
const isPrimary = type === 'primary';
let textColor = isPrimary ? themes[theme].buttonText : themes[theme].bodyText;
if (color) {
textColor = color;
}
return (
<Touchable
onPress={onPress}
@ -55,20 +67,20 @@ export default class Button extends React.PureComponent {
backgroundColor
? { backgroundColor }
: { backgroundColor: isPrimary ? themes[theme].actionTintColor : themes[theme].backgroundColor },
disabled && { backgroundColor: themes[theme].borderColor },
disabled && styles.disabled,
style
]}
{...otherProps}
>
{
loading
? <ActivityIndicator color={isPrimary ? themes[theme].buttonText : themes[theme].actionTintColor} />
? <ActivityIndicator color={textColor} />
: (
<Text
style={[
styles.text,
isPrimary ? sharedStyles.textMedium : sharedStyles.textBold,
{ color: isPrimary ? themes[theme].buttonText : themes[theme].actionTintColor }
{ color: textColor },
fontSize && { fontSize }
]}
>
{title}

View File

@ -13,9 +13,10 @@ const styles = StyleSheet.create({
}
});
const Check = React.memo(({ theme }) => <CustomIcon style={styles.icon} color={themes[theme].tintColor} size={22} name='check' />);
const Check = React.memo(({ theme, style }) => <CustomIcon style={[styles.icon, style]} color={themes[theme].tintColor} size={22} name='check' />);
Check.propTypes = {
style: PropTypes.object,
theme: PropTypes.string
};

View File

@ -47,7 +47,8 @@ export default StyleSheet.create({
},
categoryEmoji: {
backgroundColor: 'transparent',
textAlign: 'center'
textAlign: 'center',
color: '#ffffff'
},
customCategoryEmoji: {
margin: 8

View File

@ -0,0 +1,51 @@
import React from 'react';
import { ScrollView, StyleSheet, View } from 'react-native';
import PropTypes from 'prop-types';
import { SafeAreaView } from 'react-navigation';
import { themes } from '../constants/colors';
import sharedStyles from '../views/Styles';
import scrollPersistTaps from '../utils/scrollPersistTaps';
import KeyboardView from '../presentation/KeyboardView';
import StatusBar from './StatusBar';
import AppVersion from './AppVersion';
import { isTablet } from '../utils/deviceInfo';
const styles = StyleSheet.create({
scrollView: {
minHeight: '100%'
}
});
export const FormContainerInner = ({ children }) => (
<View style={[sharedStyles.container, isTablet && sharedStyles.tabletScreenContent]}>
{children}
</View>
);
const FormContainer = ({ children, theme }) => (
<KeyboardView
style={{ backgroundColor: themes[theme].backgroundColor }}
contentContainerStyle={sharedStyles.container}
keyboardVerticalOffset={128}
>
<StatusBar theme={theme} />
<ScrollView {...scrollPersistTaps} style={sharedStyles.container} contentContainerStyle={[sharedStyles.containerScrollView, styles.scrollView]}>
<SafeAreaView style={sharedStyles.container} forceInset={{ top: 'never' }}>
{children}
<AppVersion theme={theme} />
</SafeAreaView>
</ScrollView>
</KeyboardView>
);
FormContainer.propTypes = {
theme: PropTypes.string,
children: PropTypes.element
};
FormContainerInner.propTypes = {
children: PropTypes.element
};
export default FormContainer;

View File

@ -36,13 +36,13 @@ export const DrawerButton = React.memo(({ navigation, testID, ...otherProps }) =
</CustomHeaderButtons>
));
export const CloseModalButton = React.memo(({ navigation, testID }) => (
export const CloseModalButton = React.memo(({ navigation, testID, onPress = () => navigation.pop() }) => (
<CustomHeaderButtons left>
<Item title='close' iconName='cross' onPress={() => navigation.pop()} testID={testID} />
<Item title='close' iconName='cross' onPress={onPress} testID={testID} />
</CustomHeaderButtons>
));
export const CloseShareExtensionButton = React.memo(({ onPress, testID }) => (
export const CancelModalButton = React.memo(({ onPress, testID }) => (
<CustomHeaderButtons left>
{isIOS
? <Item title={I18n.t('Cancel')} onPress={onPress} testID={testID} />
@ -76,9 +76,10 @@ DrawerButton.propTypes = {
};
CloseModalButton.propTypes = {
navigation: PropTypes.object.isRequired,
testID: PropTypes.string.isRequired
testID: PropTypes.string.isRequired,
onPress: PropTypes.func
};
CloseShareExtensionButton.propTypes = {
CancelModalButton.propTypes = {
onPress: PropTypes.func.isRequired,
testID: PropTypes.string.isRequired
};

View File

@ -33,9 +33,10 @@ const styles = StyleSheet.create({
});
const Content = React.memo(({
title, subtitle, disabled, testID, right, color, theme
title, subtitle, disabled, testID, left, right, color, theme
}) => (
<View style={[styles.container, disabled && styles.disabled]} testID={testID}>
{left ? left() : null}
<View style={styles.textContainer}>
<Text style={[styles.title, { color: color || themes[theme].titleText }]}>{title}</Text>
{subtitle
@ -51,7 +52,7 @@ const Button = React.memo(({
onPress, ...props
}) => (
<Touch
onPress={onPress}
onPress={() => onPress(props.title)}
style={{ backgroundColor: themes[props.theme].backgroundColor }}
enabled={!props.disabled}
theme={props.theme}
@ -79,6 +80,7 @@ Item.propTypes = {
Content.propTypes = {
title: PropTypes.string.isRequired,
subtitle: PropTypes.string,
left: PropTypes.func,
right: PropTypes.func,
disabled: PropTypes.bool,
testID: PropTypes.string,
@ -87,6 +89,7 @@ Content.propTypes = {
};
Button.propTypes = {
title: PropTypes.string,
onPress: PropTypes.func,
disabled: PropTypes.bool,
theme: PropTypes.string

View File

@ -1,42 +1,32 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Text, View, ScrollView, Image, StyleSheet, Animated, Easing
View, StyleSheet, Text, Animated, Easing, Image
} from 'react-native';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Base64 } from 'js-base64';
import { SafeAreaView } from 'react-navigation';
import { BorderlessButton } from 'react-native-gesture-handler';
import equal from 'deep-equal';
import { withNavigation } from 'react-navigation';
import Touch from '../utils/touch';
import sharedStyles from './Styles';
import scrollPersistTaps from '../utils/scrollPersistTaps';
import random from '../utils/random';
import Button from '../containers/Button';
import I18n from '../i18n';
import { LegalButton } from '../containers/HeaderButton';
import StatusBar from '../containers/StatusBar';
import { themes } from '../constants/colors';
import { withTheme } from '../theme';
import { themedHeader } from '../utils/navigation';
import { isTablet } from '../utils/deviceInfo';
import sharedStyles from '../views/Styles';
import { themes } from '../constants/colors';
import { loginRequest as loginRequestAction } from '../actions/login';
import Button from './Button';
import OnboardingSeparator from './OnboardingSeparator';
import Touch from '../utils/touch';
import I18n from '../i18n';
import random from '../utils/random';
const SERVICE_HEIGHT = 58;
const SERVICES_COLLAPSED_HEIGHT = 174;
const styles = StyleSheet.create({
container: {
paddingVertical: 30
},
safeArea: {
paddingBottom: 30,
flex: 1
},
serviceButton: {
borderRadius: 2,
marginBottom: 10
},
serviceButtonContainer: {
borderRadius: 2,
borderWidth: 1,
width: '100%',
height: 48,
flexDirection: 'row',
@ -56,124 +46,32 @@ const styles = StyleSheet.create({
fontSize: 16
},
serviceName: {
...sharedStyles.textBold
...sharedStyles.textSemibold
},
registerDisabled: {
...sharedStyles.textRegular,
...sharedStyles.textAlignCenter,
fontSize: 16
},
servicesTogglerContainer: {
flexDirection: 'row',
alignItems: 'center',
marginTop: 5,
marginBottom: 30
},
servicesToggler: {
width: 32,
height: 31
},
separatorContainer: {
marginTop: 5,
marginBottom: 15
},
separatorLine: {
flex: 1,
height: 1
},
separatorLineLeft: {
marginRight: 15
},
separatorLineRight: {
marginLeft: 15
},
inverted: {
transform: [{ scaleY: -1 }]
options: {
marginBottom: 0
}
});
const SERVICE_HEIGHT = 58;
const SERVICES_COLLAPSED_HEIGHT = 174;
class LoginSignupView extends React.Component {
static navigationOptions = ({ navigation, screenProps }) => {
const title = navigation.getParam('title', 'Rocket.Chat');
return {
...themedHeader(screenProps.theme),
title,
headerRight: <LegalButton testID='welcome-view-more' navigation={navigation} />
};
}
class LoginServices extends React.PureComponent {
static propTypes = {
navigation: PropTypes.object,
server: PropTypes.string,
services: PropTypes.object,
Site_Name: PropTypes.string,
Gitlab_URL: PropTypes.string,
CAS_enabled: PropTypes.bool,
CAS_login_url: PropTypes.string,
Accounts_ShowFormLogin: PropTypes.bool,
Accounts_RegistrationForm: PropTypes.string,
Accounts_RegistrationForm_LinkReplacementText: PropTypes.string,
separator: PropTypes.bool,
theme: PropTypes.string
}
constructor(props) {
super(props);
this.state = {
collapsed: true,
servicesHeight: new Animated.Value(SERVICES_COLLAPSED_HEIGHT)
};
const { Site_Name } = this.props;
this.setTitle(Site_Name);
static defaultProps = {
separator: true
}
shouldComponentUpdate(nextProps, nextState) {
const { collapsed, servicesHeight } = this.state;
const {
server, Site_Name, services, Accounts_ShowFormLogin, Accounts_RegistrationForm, Accounts_RegistrationForm_LinkReplacementText, theme
} = this.props;
if (nextState.collapsed !== collapsed) {
return true;
}
if (nextState.servicesHeight !== servicesHeight) {
return true;
}
if (nextProps.server !== server) {
return true;
}
if (nextProps.Site_Name !== Site_Name) {
return true;
}
if (nextProps.theme !== theme) {
return true;
}
if (nextProps.Accounts_ShowFormLogin !== Accounts_ShowFormLogin) {
return true;
}
if (nextProps.Accounts_RegistrationForm !== Accounts_RegistrationForm) {
return true;
}
if (nextProps.Accounts_RegistrationForm_LinkReplacementText !== Accounts_RegistrationForm_LinkReplacementText) {
return true;
}
if (!equal(nextProps.services, services)) {
return true;
}
return false;
}
componentDidUpdate(prevProps) {
const { Site_Name } = this.props;
if (Site_Name && prevProps.Site_Name !== Site_Name) {
this.setTitle(Site_Name);
}
}
setTitle = (title) => {
const { navigation } = this.props;
navigation.setParams({ title });
state = {
collapsed: true,
servicesHeight: new Animated.Value(SERVICES_COLLAPSED_HEIGHT)
}
onPressFacebook = () => {
@ -224,9 +122,9 @@ class LoginSignupView extends React.Component {
onPressLinkedin = () => {
const { services, server } = this.props;
const { clientId } = services.linkedin;
const endpoint = 'https://www.linkedin.com/uas/oauth2/authorization';
const endpoint = 'https://www.linkedin.com/oauth/v2/authorization';
const redirect_uri = `${ server }/_oauth/linkedin?close`;
const scope = 'r_emailaddress';
const scope = 'r_liteprofile,r_emailaddress';
const state = this.getOAuthState();
const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&response_type=code`;
this.openOAuth({ url: `${ endpoint }${ params }` });
@ -300,16 +198,6 @@ class LoginSignupView extends React.Component {
navigation.navigate('AuthenticationWebView', { url, authType, ssoToken });
}
login = () => {
const { navigation, Site_Name } = this.props;
navigation.navigate('LoginView', { title: Site_Name });
}
register = () => {
const { navigation, Site_Name } = this.props;
navigation.navigate('RegisterView', { title: Site_Name });
}
transitionServicesTo = (height) => {
const { servicesHeight } = this.state;
if (this._animation) {
@ -350,27 +238,28 @@ class LoginSignupView extends React.Component {
renderServicesSeparator = () => {
const { collapsed } = this.state;
const {
services, theme, Accounts_ShowFormLogin, Accounts_RegistrationForm
} = this.props;
const { services, separator, theme } = this.props;
const { length } = Object.values(services);
if (length > 3 && Accounts_ShowFormLogin && Accounts_RegistrationForm) {
if (length > 3 && separator) {
return (
<View style={styles.servicesTogglerContainer}>
<View style={[styles.separatorLine, styles.separatorLineLeft, { backgroundColor: themes[theme].auxiliaryText }]} />
<BorderlessButton onPress={this.toggleServices}>
<Image source={{ uri: 'options' }} style={[styles.servicesToggler, !collapsed && styles.inverted]} />
</BorderlessButton>
<View style={[styles.separatorLine, styles.separatorLineRight, { backgroundColor: themes[theme].auxiliaryText }]} />
</View>
<>
<Button
title={collapsed ? I18n.t('Onboarding_more_options') : I18n.t('Onboarding_less_options')}
type='secondary'
onPress={this.toggleServices}
theme={theme}
style={styles.options}
color={themes[theme].actionTintColor}
/>
<OnboardingSeparator theme={theme} />
</>
);
}
return (
<View style={styles.separatorContainer}>
<View style={styles.separatorLine} />
</View>
);
if (length > 0 && separator) {
return <OnboardingSeparator theme={theme} />;
}
return null;
}
renderItem = (service) => {
@ -412,14 +301,19 @@ class LoginSignupView extends React.Component {
</>
);
}
const backgroundColor = isSaml && service.buttonColor ? service.buttonColor : themes[theme].chatComponentBackground;
return (
<Touch
key={service.name}
onPress={onPress}
style={[styles.serviceButton, isSaml && { backgroundColor: service.buttonColor }]}
style={[styles.serviceButton, { backgroundColor }]}
theme={theme}
activeOpacity={0.5}
underlayColor={themes[theme].buttonText}
>
<View style={[styles.serviceButtonContainer, { borderColor: themes[theme].borderColor }]}>
<View style={styles.serviceButtonContainer}>
{service.authType === 'oauth' ? <Image source={{ uri: icon }} style={styles.serviceIcon} /> : null}
<Text style={[styles.serviceText, { color: themes[theme].titleText }]}>{buttonText}</Text>
</View>
@ -427,100 +321,44 @@ class LoginSignupView extends React.Component {
);
}
renderServices = () => {
render() {
const { servicesHeight } = this.state;
const { services, Accounts_ShowFormLogin, Accounts_RegistrationForm } = this.props;
const { services, separator } = this.props;
const { length } = Object.values(services);
const style = {
overflow: 'hidden',
height: servicesHeight
};
if (length > 3 && Accounts_ShowFormLogin && Accounts_RegistrationForm) {
if (length > 3 && separator) {
return (
<Animated.View style={style}>
{Object.values(services).map(service => this.renderItem(service))}
</Animated.View>
<>
<Animated.View style={style}>
{Object.values(services).map(service => this.renderItem(service))}
</Animated.View>
{this.renderServicesSeparator()}
</>
);
}
return (
<View>
<>
{Object.values(services).map(service => this.renderItem(service))}
</View>
);
}
renderLogin = () => {
const { Accounts_ShowFormLogin, theme } = this.props;
if (!Accounts_ShowFormLogin) {
return null;
}
return (
<Button
title={<Text>{I18n.t('Login_with')} <Text style={{ ...sharedStyles.textBold }}>{I18n.t('email')}</Text></Text>}
type='primary'
onPress={() => this.login()}
theme={theme}
testID='welcome-view-login'
/>
);
}
renderRegister = () => {
const { Accounts_RegistrationForm, Accounts_RegistrationForm_LinkReplacementText, theme } = this.props;
if (Accounts_RegistrationForm !== 'Public') {
return <Text style={[styles.registerDisabled, { color: themes[theme].auxiliaryText }]}>{Accounts_RegistrationForm_LinkReplacementText}</Text>;
}
return (
<Button
title={I18n.t('Create_account')}
type='secondary'
onPress={() => this.register()}
theme={theme}
testID='welcome-view-register'
/>
);
}
render() {
const { theme } = this.props;
return (
<SafeAreaView
testID='welcome-view'
forceInset={{ vertical: 'never' }}
style={[styles.safeArea, { backgroundColor: themes[theme].backgroundColor }]}
>
<ScrollView
style={[
sharedStyles.containerScrollView,
sharedStyles.container,
styles.container,
{ backgroundColor: themes[theme].backgroundColor },
isTablet && sharedStyles.tabletScreenContent
]}
{...scrollPersistTaps}
>
<StatusBar theme={theme} />
{this.renderServices()}
{this.renderServicesSeparator()}
{this.renderLogin()}
{this.renderRegister()}
</ScrollView>
</SafeAreaView>
{this.renderServicesSeparator()}
</>
);
}
}
const mapStateToProps = state => ({
server: state.server.server,
Site_Name: state.settings.Site_Name,
Gitlab_URL: state.settings.API_Gitlab_URL,
CAS_enabled: state.settings.CAS_enabled,
CAS_login_url: state.settings.CAS_login_url,
Accounts_ShowFormLogin: state.settings.Accounts_ShowFormLogin,
Accounts_RegistrationForm: state.settings.Accounts_RegistrationForm,
Accounts_RegistrationForm_LinkReplacementText: state.settings.Accounts_RegistrationForm_LinkReplacementText,
services: state.login.services
});
export default connect(mapStateToProps)(withTheme(LoginSignupView));
const mapDispatchToProps = dispatch => ({
loginRequest: params => dispatch(loginRequestAction(params))
});
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(withNavigation(LoginServices)));

View File

@ -63,6 +63,10 @@ class MessageActions extends React.Component {
this.EDIT_INDEX = this.options.length - 1;
}
// Create Discussion
this.options.push(I18n.t('Create_Discussion'));
this.CREATE_DISCUSSION_INDEX = this.options.length - 1;
// Mark as unread
if (message.u && message.u._id !== user.id) {
this.options.push(I18n.t('Mark_unread'));
@ -294,6 +298,7 @@ class MessageActions extends React.Component {
const { message } = this.props;
try {
await RocketChat.toggleStarMessage(message.id, message.starred);
EventEmitter.emit(LISTENER, { message: message.starred ? I18n.t('Message_unstarred') : I18n.t('Message_starred') });
} catch (e) {
log(e);
}
@ -370,6 +375,11 @@ class MessageActions extends React.Component {
}
}
handleCreateDiscussion = () => {
const { message, room: channel } = this.props;
Navigation.navigate('CreateDiscussionView', { message, channel });
}
handleActionPress = (actionIndex) => {
if (actionIndex) {
switch (actionIndex) {
@ -412,6 +422,9 @@ class MessageActions extends React.Component {
case this.READ_RECEIPT_INDEX:
this.handleReadReceipt();
break;
case this.CREATE_DISCUSSION_INDEX:
this.handleCreateDiscussion();
break;
case this.TOGGLE_TRANSLATION_INDEX:
this.handleToggleTranslation();
break;

View File

@ -1,20 +1,20 @@
import React from 'react';
import PropTypes from 'prop-types';
import { CancelEditingButton, FileButton } from './buttons';
import { CancelEditingButton, ActionsButton } from './buttons';
const LeftButtons = React.memo(({
theme, showFileActions, editing, editCancel
theme, showMessageBoxActions, editing, editCancel
}) => {
if (editing) {
return <CancelEditingButton onPress={editCancel} theme={theme} />;
}
return <FileButton onPress={showFileActions} theme={theme} />;
return <ActionsButton onPress={showMessageBoxActions} theme={theme} />;
});
LeftButtons.propTypes = {
theme: PropTypes.string,
showFileActions: PropTypes.func.isRequired,
showMessageBoxActions: PropTypes.func.isRequired,
editing: PropTypes.bool,
editCancel: PropTypes.func.isRequired
};

View File

@ -1,10 +1,10 @@
import React from 'react';
import PropTypes from 'prop-types';
import { SendButton, AudioButton, FileButton } from './buttons';
import { SendButton, AudioButton, ActionsButton } from './buttons';
const RightButtons = React.memo(({
theme, showSend, submit, recordAudioMessage, recordAudioMessageEnabled, showFileActions
theme, showSend, submit, recordAudioMessage, recordAudioMessageEnabled, showMessageBoxActions
}) => {
if (showSend) {
return <SendButton onPress={submit} theme={theme} />;
@ -13,11 +13,11 @@ const RightButtons = React.memo(({
return (
<>
<AudioButton onPress={recordAudioMessage} theme={theme} />
<FileButton onPress={showFileActions} theme={theme} />
<ActionsButton onPress={showMessageBoxActions} theme={theme} />
</>
);
}
return <FileButton onPress={showFileActions} theme={theme} />;
return <ActionsButton onPress={showMessageBoxActions} theme={theme} />;
});
RightButtons.propTypes = {
@ -26,7 +26,7 @@ RightButtons.propTypes = {
submit: PropTypes.func.isRequired,
recordAudioMessage: PropTypes.func.isRequired,
recordAudioMessageEnabled: PropTypes.bool,
showFileActions: PropTypes.func.isRequired
showMessageBoxActions: PropTypes.func.isRequired
};
export default RightButtons;

View File

@ -225,7 +225,7 @@ class UploadModal extends Component {
hideModalContentWhileAnimating
avoidKeyboard
>
<View style={[styles.container, { width: width - 32, backgroundColor: themes[theme].chatComponentBackground }, split && sharedStyles.modal]}>
<View style={[styles.container, { width: width - 32, backgroundColor: themes[theme].chatComponentBackground }, split && [sharedStyles.modal, sharedStyles.modalFormSheet]]}>
<View style={styles.titleContainer}>
<Text style={[styles.title, { color: themes[theme].titleText }]}>{I18n.t('Upload_file_question_mark')}</Text>
</View>

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import BaseButton from './BaseButton';
const FileButton = React.memo(({ theme, onPress }) => (
const ActionsButton = React.memo(({ theme, onPress }) => (
<BaseButton
onPress={onPress}
testID='messagebox-actions'
@ -13,9 +13,9 @@ const FileButton = React.memo(({ theme, onPress }) => (
/>
));
FileButton.propTypes = {
ActionsButton.propTypes = {
theme: PropTypes.string,
onPress: PropTypes.func.isRequired
};
export default FileButton;
export default ActionsButton;

View File

@ -2,12 +2,12 @@ import CancelEditingButton from './CancelEditingButton';
import ToggleEmojiButton from './ToggleEmojiButton';
import SendButton from './SendButton';
import AudioButton from './AudioButton';
import FileButton from './FileButton';
import ActionsButton from './ActionsButton';
export {
CancelEditingButton,
ToggleEmojiButton,
SendButton,
AudioButton,
FileButton
ActionsButton
};

View File

@ -45,6 +45,7 @@ import {
import CommandsPreview from './CommandsPreview';
import { Review } from '../../utils/review';
import { getUserSelector } from '../../selectors/login';
import Navigation from '../../lib/Navigation';
const imagePickerConfig = {
cropping: true,
@ -65,6 +66,7 @@ const FILE_PHOTO_INDEX = 1;
const FILE_VIDEO_INDEX = 2;
const FILE_LIBRARY_INDEX = 3;
const FILE_DOCUMENT_INDEX = 4;
const CREATE_DISCUSSION_INDEX = 5;
class MessageBox extends Component {
static propTypes = {
@ -113,12 +115,13 @@ class MessageBox extends Component {
};
this.text = '';
this.focused = false;
this.fileOptions = [
this.messageBoxActions = [
I18n.t('Cancel'),
I18n.t('Take_a_photo'),
I18n.t('Take_a_video'),
I18n.t('Choose_from_library'),
I18n.t('Choose_file')
I18n.t('Choose_file'),
I18n.t('Create_Discussion')
];
const libPickerLabels = {
cropperChooseText: I18n.t('Choose'),
@ -157,8 +160,8 @@ class MessageBox extends Component {
}
} else {
try {
const room = await subsCollection.find(rid);
msg = room.draftMessage;
this.room = await subsCollection.find(rid);
msg = this.room.draftMessage;
} catch (error) {
console.log('Messagebox.didMount: Room not found');
}
@ -588,20 +591,24 @@ class MessageBox extends Component {
}
}
createDiscussion = () => {
Navigation.navigate('CreateDiscussionView', { channel: this.room });
}
showUploadModal = (file) => {
this.setState({ file: { ...file, isVisible: true } });
}
showFileActions = () => {
showMessageBoxActions = () => {
ActionSheet.showActionSheetWithOptions({
options: this.fileOptions,
options: this.messageBoxActions,
cancelButtonIndex: FILE_CANCEL_INDEX
}, (actionIndex) => {
this.handleFileActionPress(actionIndex);
this.handleMessageBoxActions(actionIndex);
});
}
handleFileActionPress = (actionIndex) => {
handleMessageBoxActions = (actionIndex) => {
switch (actionIndex) {
case FILE_PHOTO_INDEX:
this.takePhoto();
@ -615,6 +622,9 @@ class MessageBox extends Component {
case FILE_DOCUMENT_INDEX:
this.chooseFile();
break;
case CREATE_DISCUSSION_INDEX:
this.createDiscussion();
break;
default:
break;
}
@ -783,7 +793,7 @@ class MessageBox extends Component {
} else if (handleCommandSubmit(event)) {
this.submit();
} else if (handleCommandShowUpload(event)) {
this.showFileActions();
this.showMessageBoxActions();
}
}
@ -828,7 +838,7 @@ class MessageBox extends Component {
theme={theme}
showEmojiKeyboard={showEmojiKeyboard}
editing={editing}
showFileActions={this.showFileActions}
showMessageBoxActions={this.showMessageBoxActions}
editCancel={this.editCancel}
openEmoji={this.openEmoji}
closeEmoji={this.closeEmoji}
@ -854,7 +864,7 @@ class MessageBox extends Component {
submit={this.submit}
recordAudioMessage={this.recordAudioMessage}
recordAudioMessageEnabled={Message_AudioRecorderEnabled}
showFileActions={this.showFileActions}
showMessageBoxActions={this.showMessageBoxActions}
/>
</View>
</View>

View File

@ -0,0 +1,43 @@
import React from 'react';
import { View, StyleSheet, Text } from 'react-native';
import PropTypes from 'prop-types';
import I18n from '../i18n';
import sharedStyles from '../views/Styles';
import { themes } from '../constants/colors';
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
marginVertical: 24
},
line: {
height: 1,
flex: 1
},
text: {
fontSize: 14,
marginLeft: 14,
marginRight: 14,
...sharedStyles.textMedium
}
});
const DateSeparator = React.memo(({ theme }) => {
const line = { backgroundColor: themes[theme].borderColor };
const text = { color: themes[theme].auxiliaryText };
return (
<View style={styles.container}>
<View style={[styles.line, line]} />
<Text style={[styles.text, styles.marginRight, styles.marginLeft, text]}>{I18n.t('OR')}</Text>
<View style={[styles.line, line]} />
</View>
);
});
DateSeparator.propTypes = {
theme: PropTypes.string
};
export default DateSeparator;

View File

@ -15,7 +15,7 @@ const styles = StyleSheet.create({
});
const RoomTypeIcon = React.memo(({
type, size, style, theme
type, size, isGroupChat, style, theme
}) => {
if (!type) {
return null;
@ -31,6 +31,9 @@ const RoomTypeIcon = React.memo(({
if (type === 'c') {
return <Image source={{ uri: 'hashtag' }} style={[styles.style, style, { width: size, height: size, tintColor: color }]} />;
} if (type === 'd') {
if (isGroupChat) {
return <CustomIcon name='team' size={13} style={[styles.style, styles.discussion, { color }]} />;
}
return <CustomIcon name='at' size={13} style={[styles.style, styles.discussion, { color }]} />;
} if (type === 'l') {
return <CustomIcon name='livechat' size={13} style={[styles.style, styles.discussion, { color }]} />;
@ -41,6 +44,7 @@ const RoomTypeIcon = React.memo(({
RoomTypeIcon.propTypes = {
theme: PropTypes.string,
type: PropTypes.string,
isGroupChat: PropTypes.bool,
size: PropTypes.number,
style: PropTypes.object
};

View File

@ -4,7 +4,7 @@ import { View } from 'react-native';
import { STATUS_COLORS, themes } from '../../constants/colors';
const Status = React.memo(({
status, size, style, theme
status, size, style, theme, ...props
}) => (
<View
style={
@ -18,6 +18,7 @@ const Status = React.memo(({
borderColor: themes[theme].backgroundColor
}
]}
{...props}
/>
));
Status.propTypes = {

View File

@ -26,7 +26,7 @@ class StatusContainer extends React.PureComponent {
}
const mapStateToProps = (state, ownProps) => ({
status: state.meteor.connected ? state.activeUsers[ownProps.id] : 'offline'
status: state.meteor.connected ? (state.activeUsers[ownProps.id] && state.activeUsers[ownProps.id].status) : 'offline'
});
export default connect(mapStateToProps)(withTheme(StatusContainer));

View File

@ -65,6 +65,7 @@ export default class RCTextInput extends React.PureComponent {
testID: PropTypes.string,
iconLeft: PropTypes.string,
placeholder: PropTypes.string,
left: PropTypes.element,
theme: PropTypes.string
}
@ -116,7 +117,7 @@ export default class RCTextInput extends React.PureComponent {
render() {
const { showPassword } = this.state;
const {
label, error, loading, secureTextEntry, containerStyle, inputRef, iconLeft, inputStyle, testID, placeholder, theme, ...inputProps
label, left, error, loading, secureTextEntry, containerStyle, inputRef, iconLeft, inputStyle, testID, placeholder, theme, ...inputProps
} = this.props;
const { dangerColor } = themes[theme];
return (
@ -166,6 +167,7 @@ export default class RCTextInput extends React.PureComponent {
{iconLeft ? this.iconLeft : null}
{secureTextEntry ? this.iconPassword : null}
{loading ? this.loading : null}
{left}
</View>
{error && error.reason ? <Text style={[styles.error, { color: dangerColor }]}>{error.reason}</Text> : null}
</View>

View File

@ -0,0 +1,137 @@
import React, { useEffect, useState } from 'react';
import { View, Text } from 'react-native';
import _ from 'lodash';
import PropTypes from 'prop-types';
import { sha256 } from 'js-sha256';
import Modal from 'react-native-modal';
import useDeepCompareEffect from 'use-deep-compare-effect';
import TextInput from '../TextInput';
import I18n from '../../i18n';
import EventEmitter from '../../utils/events';
import { withTheme } from '../../theme';
import { withSplit } from '../../split';
import { themes } from '../../constants/colors';
import Button from '../Button';
import sharedStyles from '../../views/Styles';
import RocketChat from '../../lib/rocketchat';
import styles from './styles';
export const TWO_FACTOR = 'TWO_FACTOR';
const methods = {
totp: {
text: 'Open_your_authentication_app_and_enter_the_code',
keyboardType: 'numeric'
},
email: {
text: 'Verify_your_email_for_the_code_we_sent',
keyboardType: 'numeric'
},
password: {
title: 'Please_enter_your_password',
text: 'For_your_security_you_must_enter_your_current_password_to_continue',
secureTextEntry: true,
keyboardType: 'default'
}
};
const TwoFactor = React.memo(({ theme, split }) => {
const [visible, setVisible] = useState(false);
const [data, setData] = useState({});
const [code, setCode] = useState('');
const method = methods[data.method];
const isEmail = data.method === 'email';
const sendEmail = () => RocketChat.sendEmailCode();
useDeepCompareEffect(() => {
if (!_.isEmpty(data)) {
setVisible(true);
} else {
setVisible(false);
}
}, [data]);
const showTwoFactor = args => setData(args);
useEffect(() => {
EventEmitter.addEventListener(TWO_FACTOR, showTwoFactor);
return () => EventEmitter.removeListener(TWO_FACTOR);
}, []);
const onCancel = () => {
const { cancel } = data;
if (cancel) {
cancel();
}
setData({});
};
const onSubmit = () => {
const { submit } = data;
if (submit) {
if (data.method === 'password') {
submit(sha256(code));
} else {
submit(code);
}
}
setData({});
};
const color = themes[theme].titleText;
return (
<Modal
transparent
avoidKeyboard
useNativeDriver
isVisible={visible}
hideModalContentWhileAnimating
>
<View style={styles.container}>
<View style={[styles.content, split && [sharedStyles.modal, sharedStyles.modalFormSheet], { backgroundColor: themes[theme].backgroundColor }]}>
<Text style={[styles.title, { color }]}>{I18n.t(method?.title || 'Two_Factor_Authentication')}</Text>
<Text style={[styles.subtitle, { color }]}>{I18n.t(method?.text)}</Text>
<TextInput
value={code}
theme={theme}
returnKeyType='send'
autoCapitalize='none'
onChangeText={setCode}
onSubmitEditing={onSubmit}
keyboardType={method?.keyboardType}
secureTextEntry={method?.secureTextEntry}
error={data.invalid && { error: 'totp-invalid', reason: I18n.t('Code_or_password_invalid') }}
/>
{isEmail && <Text style={[styles.sendEmail, { color }]} onPress={sendEmail}>{I18n.t('Send_me_the_code_again')}</Text>}
<View style={styles.buttonContainer}>
<Button
title={I18n.t('Cancel')}
type='secondary'
backgroundColor={themes[theme].chatComponentBackground}
style={styles.button}
onPress={onCancel}
theme={theme}
/>
<Button
title={I18n.t('Send')}
type='primary'
style={styles.button}
onPress={onSubmit}
theme={theme}
/>
</View>
</View>
</View>
</Modal>
);
});
TwoFactor.propTypes = {
theme: PropTypes.string,
split: PropTypes.bool
};
export default withSplit(withTheme(TwoFactor));

View File

@ -0,0 +1,40 @@
import { StyleSheet } from 'react-native';
import sharedStyles from '../../views/Styles';
export default StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
content: {
padding: 16,
width: '100%',
borderRadius: 4
},
title: {
fontSize: 14,
...sharedStyles.textBold
},
subtitle: {
fontSize: 14,
paddingVertical: 8,
...sharedStyles.textRegular,
...sharedStyles.textAlignCenter
},
sendEmail: {
fontSize: 14,
paddingBottom: 24,
paddingTop: 8,
alignSelf: 'center',
...sharedStyles.textRegular
},
button: {
marginBottom: 0
},
buttonContainer: {
flexDirection: 'row',
justifyContent: 'space-between'
}
});

View File

@ -1,7 +1,8 @@
import React from 'react';
import { Text, View, Image } from 'react-native';
import { Text, View } from 'react-native';
import PropTypes from 'prop-types';
import Touchable from 'react-native-platform-touchable';
import FastImage from 'react-native-fast-image';
import { themes } from '../../../constants/colors';
import { textParser } from '../utils';
@ -19,7 +20,7 @@ const Chip = ({ item, onSelect, theme }) => (
background={Touchable.Ripple(themes[theme].bannerBackground)}
>
<>
{item.imageUrl ? <Image style={styles.chipImage} source={{ uri: item.imageUrl }} /> : null}
{item.imageUrl ? <FastImage style={styles.chipImage} source={{ uri: item.imageUrl }} /> : null}
<Text numberOfLines={1} style={[styles.chipText, { color: themes[theme].titleText }]}>{textParser([item.text])}</Text>
<CustomIcon name='cross' size={16} color={themes[theme].auxiliaryText} />
</>

View File

@ -9,12 +9,13 @@ import ActivityIndicator from '../../ActivityIndicator';
import styles from './styles';
const Input = ({
children, open, theme, loading
children, open, theme, loading, inputStyle, disabled
}) => (
<Touchable
onPress={() => open(true)}
style={{ backgroundColor: themes[theme].backgroundColor }}
style={[{ backgroundColor: themes[theme].backgroundColor }, inputStyle]}
background={Touchable.Ripple(themes[theme].bannerBackground)}
disabled={disabled}
>
<View style={[styles.input, { borderColor: themes[theme].separatorColor }]}>
{children}
@ -30,6 +31,8 @@ Input.propTypes = {
children: PropTypes.node,
open: PropTypes.func,
theme: PropTypes.string,
inputStyle: PropTypes.object,
disabled: PropTypes.bool,
loading: PropTypes.bool
};

View File

@ -2,6 +2,7 @@ import React from 'react';
import { Text, FlatList } from 'react-native';
import PropTypes from 'prop-types';
import Touchable from 'react-native-platform-touchable';
import FastImage from 'react-native-fast-image';
import Separator from '../../Separator';
import Check from '../../Check';
@ -26,6 +27,7 @@ const Item = ({
]}
>
<>
{item.imageUrl ? <FastImage style={styles.itemImage} source={{ uri: item.imageUrl }} /> : null}
<Text style={{ color: themes[theme].titleText }}>{textParser([item.text])}</Text>
{selected ? <Check theme={theme} /> : null}
</>

View File

@ -10,6 +10,8 @@ import TextInput from '../../TextInput';
import { textParser } from '../utils';
import { themes } from '../../../constants/colors';
import I18n from '../../../i18n';
import { isIOS } from '../../../utils/deviceInfo';
import Chips from './Chips';
import Items from './Items';
@ -25,6 +27,8 @@ const ANIMATION_PROPS = {
};
const animatedValue = new Animated.Value(0);
const behavior = isIOS ? 'padding' : null;
export const MultiSelect = React.memo(({
options = [],
onChange,
@ -33,6 +37,10 @@ export const MultiSelect = React.memo(({
loading,
value: values,
multiselect = false,
onSearch,
onClose,
disabled,
inputStyle,
theme
}) => {
const [selected, select] = useState(values || []);
@ -51,6 +59,12 @@ export const MultiSelect = React.memo(({
setOpen(showContent);
}, [showContent]);
useEffect(() => {
if (values && values.length && !multiselect) {
setCurrentValue(values[0].text);
}
}, []);
const onShow = () => {
Animated.timing(
animatedValue,
@ -63,6 +77,7 @@ export const MultiSelect = React.memo(({
};
const onHide = () => {
onClose();
Animated.timing(
animatedValue,
{
@ -73,7 +88,7 @@ export const MultiSelect = React.memo(({
};
const onSelect = (item) => {
const { value } = item;
const { value, text: { text } } = item;
if (multiselect) {
let newSelect = [];
if (!selected.includes(value)) {
@ -85,20 +100,20 @@ export const MultiSelect = React.memo(({
onChange({ value: newSelect });
} else {
onChange({ value });
setCurrentValue(value);
setOpen(false);
setCurrentValue(text);
onHide();
}
};
const renderContent = () => {
const items = options.filter(option => textParser([option.text]).toLowerCase().includes(search.toLowerCase()));
const items = onSearch ? options : options.filter(option => textParser([option.text]).toLowerCase().includes(search.toLowerCase()));
return (
<View style={[styles.modal, { backgroundColor: themes[theme].backgroundColor }]}>
<View style={[styles.content, { backgroundColor: themes[theme].backgroundColor }]}>
<TextInput
onChangeText={onSearchChange}
placeholder={placeholder.text}
onChangeText={onSearch || onSearchChange}
placeholder={I18n.t('Search')}
theme={theme}
/>
<Items items={items} selected={selected} onSelect={onSelect} theme={theme} />
@ -124,19 +139,24 @@ export const MultiSelect = React.memo(({
open={onShow}
theme={theme}
loading={loading}
disabled={disabled}
inputStyle={inputStyle}
>
<Text style={[styles.pickerText, { color: themes[theme].auxiliaryText }]}>{currentValue}</Text>
<Text style={[styles.pickerText, { color: currentValue ? themes[theme].titleText : themes[theme].auxiliaryText }]}>{currentValue || placeholder.text}</Text>
</Input>
);
if (context === BLOCK_CONTEXT.FORM) {
const items = options.filter(option => selected.includes(option.value));
button = (
<Input
open={onShow}
theme={theme}
loading={loading}
disabled={disabled}
inputStyle={inputStyle}
>
<Chips items={options.filter(option => selected.includes(option.value))} onSelect={onSelect} theme={theme} />
{items.length ? <Chips items={items} onSelect={onSelect} theme={theme} /> : <Text style={[styles.pickerText, { color: themes[theme].auxiliaryText }]}>{placeholder.text}</Text>}
</Input>
);
}
@ -153,7 +173,7 @@ export const MultiSelect = React.memo(({
<TouchableWithoutFeedback onPress={onHide}>
<View style={styles.container}>
<View style={[styles.backdrop, { backgroundColor: themes[theme].backdropColor }]} />
<KeyboardAvoidingView style={styles.keyboardView} behavior='padding'>
<KeyboardAvoidingView style={styles.keyboardView} behavior={behavior}>
<Animated.View style={[styles.animatedContent, { transform: [{ translateY }] }]}>
{showContent ? renderContent() : null}
</Animated.View>
@ -172,6 +192,13 @@ MultiSelect.propTypes = {
context: PropTypes.number,
loading: PropTypes.bool,
multiselect: PropTypes.bool,
onSearch: PropTypes.func,
onClose: PropTypes.func,
inputStyle: PropTypes.object,
value: PropTypes.array,
disabled: PropTypes.bool,
theme: PropTypes.string
};
MultiSelect.defaultProps = {
onClose: () => {}
};

View File

@ -30,6 +30,7 @@ export default StyleSheet.create({
},
pickerText: {
...sharedStyles.textRegular,
paddingLeft: 6,
fontSize: 16
},
item: {
@ -40,7 +41,7 @@ export default StyleSheet.create({
},
input: {
minHeight: 48,
padding: 8,
paddingHorizontal: 8,
paddingBottom: 0,
borderWidth: StyleSheet.hairlineWidth,
borderRadius: 2,
@ -58,6 +59,7 @@ export default StyleSheet.create({
height: 226
},
chips: {
paddingTop: 8,
flexDirection: 'row',
flexWrap: 'wrap',
marginRight: 50
@ -82,5 +84,11 @@ export default StyleSheet.create({
borderRadius: 2,
width: 20,
height: 20
},
itemImage: {
marginRight: 8,
borderRadius: 2,
width: 24,
height: 24
}
});

View File

@ -22,6 +22,7 @@ import MarkdownTableCell from './TableCell';
import mergeTextNodes from './mergeTextNodes';
import styles from './styles';
import { isValidURL } from '../../utils/url';
// Support <http://link|Text>
const formatText = text => text.replace(
@ -278,7 +279,18 @@ class Markdown extends PureComponent {
);
}
renderImage = ({ src }) => <Image style={styles.inlineImage} source={{ uri: src }} />;
renderImage = ({ src }) => {
if (!isValidURL(src)) {
return null;
}
return (
<Image
style={styles.inlineImage}
source={{ uri: encodeURI(src) }}
/>
);
}
renderEditedIndicator = () => {
const { theme } = this.props;

View File

@ -1,6 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import { View, Text, StyleSheet } from 'react-native';
import {
View, Text, StyleSheet, TouchableOpacity
} from 'react-native';
import moment from 'moment';
import { themes } from '../../constants/colors';
@ -33,21 +35,30 @@ const styles = StyleSheet.create({
});
const User = React.memo(({
isHeader, useRealName, author, alias, ts, timeFormat, hasError, theme, ...props
isHeader, useRealName, author, alias, ts, timeFormat, hasError, theme, navToRoomInfo, user, ...props
}) => {
if (isHeader || hasError) {
const navParam = {
t: 'd',
rid: author._id
};
const username = (useRealName && author.name) || author.username;
const aliasUsername = alias ? (<Text style={[styles.alias, { color: themes[theme].auxiliaryText }]}> @{username}</Text>) : null;
const time = moment(ts).format(timeFormat);
return (
<View style={styles.container}>
<View style={styles.titleContainer}>
<Text style={[styles.username, { color: themes[theme].titleText }]} numberOfLines={1}>
{alias || username}
{aliasUsername}
</Text>
</View>
<TouchableOpacity
onPress={() => navToRoomInfo(navParam)}
disabled={author._id === user.id}
>
<View style={styles.titleContainer}>
<Text style={[styles.username, { color: themes[theme].titleText }]} numberOfLines={1}>
{alias || username}
{aliasUsername}
</Text>
</View>
</TouchableOpacity>
<Text style={[messageStyles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text>
{ hasError && <MessageError hasError={hasError} theme={theme} {...props} /> }
</View>
@ -64,7 +75,9 @@ User.propTypes = {
alias: PropTypes.string,
ts: PropTypes.instanceOf(Date),
timeFormat: PropTypes.string,
theme: PropTypes.string
theme: PropTypes.string,
user: PropTypes.obj,
navToRoomInfo: PropTypes.func
};
User.displayName = 'MessageUser';

View File

@ -61,7 +61,8 @@ export default StyleSheet.create({
},
reactionEmoji: {
fontSize: 13,
marginLeft: 7
marginLeft: 7,
color: '#ffffff'
},
reactionCustomEmoji: {
width: 19,

View File

@ -1224,7 +1224,6 @@ export const emojisByCategory = {
'flag_er',
'flag_ee',
'flag_et',
'flag_fk',
'flag_fo',
'flag_fj',
'flag_fi',
@ -1264,7 +1263,6 @@ export const emojisByCategory = {
'flag_kz',
'flag_ke',
'flag_ki',
'flag_xk',
'flag_kw',
'flag_kg',
'flag_la',
@ -1301,7 +1299,6 @@ export const emojisByCategory = {
'flag_nr',
'flag_np',
'flag_nl',
'flag_nc',
'flag_nz',
'flag_ni',
'flag_ne',
@ -1376,50 +1373,35 @@ export const emojisByCategory = {
'flag_va',
'flag_ve',
'flag_vn',
'flag_wf',
'flag_eh',
'flag_ye',
'flag_zm',
'flag_zw',
'flag_re',
'flag_ax',
'flag_ta',
'flag_io',
'flag_bq',
'flag_cx',
'flag_cc',
'flag_gg',
'flag_im',
'flag_yt',
'flag_nf',
'flag_pn',
'flag_bl',
'flag_pm',
'flag_gs',
'flag_tk',
'flag_bv',
'flag_hm',
'flag_sj',
'flag_um',
'flag_ic',
'flag_ea',
'flag_cp',
'flag_dg',
'flag_as',
'flag_aq',
'flag_vg',
'flag_ck',
'flag_cw',
'flag_eu',
'flag_gf',
'flag_tf',
'flag_gp',
'flag_mq',
'flag_mp',
'flag_sx',
'flag_ss',
'flag_tc',
'flag_mf'
'flag_tc'
]
};

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